dataface 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. d3_format/__init__.py +14 -0
  2. d3_format/errors.py +19 -0
  3. d3_format/format.py +551 -0
  4. d3_format/spec.py +159 -0
  5. dataface/DATAFACE_SYNTAX.md +1135 -0
  6. dataface/__init__.py +93 -0
  7. dataface/_docs_site.py +20 -0
  8. dataface/_install_hint.py +26 -0
  9. dataface/agent_api/__init__.py +79 -0
  10. dataface/agent_api/_init_templates/__init__.py +0 -0
  11. dataface/agent_api/_init_templates/agents_dft_snippet.md +26 -0
  12. dataface/agent_api/_init_templates/dataface.yml +15 -0
  13. dataface/agent_api/_init_templates/faces-dataface.yml +144 -0
  14. dataface/agent_api/_init_templates/index.md +24 -0
  15. dataface/agent_api/_paths.py +118 -0
  16. dataface/agent_api/_project_agents_md.py +43 -0
  17. dataface/agent_api/_session_store.py +486 -0
  18. dataface/agent_api/_state.py +28 -0
  19. dataface/agent_api/chat.py +221 -0
  20. dataface/agent_api/dashboards.py +257 -0
  21. dataface/agent_api/describe.py +366 -0
  22. dataface/agent_api/describe_query.py +120 -0
  23. dataface/agent_api/docs/__init__.py +25 -0
  24. dataface/agent_api/docs/_loader.py +292 -0
  25. dataface/agent_api/docs/yaml-reference.md +2757 -0
  26. dataface/agent_api/file_refs.py +118 -0
  27. dataface/agent_api/init.py +126 -0
  28. dataface/agent_api/inspect.py +128 -0
  29. dataface/agent_api/mcp_install.py +170 -0
  30. dataface/agent_api/query.py +274 -0
  31. dataface/agent_api/schema.py +658 -0
  32. dataface/agent_api/schema_search.py +284 -0
  33. dataface/agent_api/search.py +270 -0
  34. dataface/agent_api/skill_install.py +141 -0
  35. dataface/agent_api/skill_render.py +90 -0
  36. dataface/agent_api/skills.py +293 -0
  37. dataface/agent_api/surface_aliases.yaml +128 -0
  38. dataface/agent_api/validate.py +175 -0
  39. dataface/agent_api/validate_query.py +84 -0
  40. dataface/ai/__init__.py +39 -0
  41. dataface/ai/agent.py +139 -0
  42. dataface/ai/context.py +45 -0
  43. dataface/ai/events.py +62 -0
  44. dataface/ai/external_mcp.py +610 -0
  45. dataface/ai/generate_sql.py +96 -0
  46. dataface/ai/llm.py +403 -0
  47. dataface/ai/mcp/__init__.py +51 -0
  48. dataface/ai/mcp/server.py +289 -0
  49. dataface/ai/memories.py +85 -0
  50. dataface/ai/prompts.py +177 -0
  51. dataface/ai/schema_context.py +138 -0
  52. dataface/ai/skills/before-after-comparison/SKILL.md +102 -0
  53. dataface/ai/skills/before-after-comparison/examples/before-after-comparison.yml +24 -0
  54. dataface/ai/skills/dashboard-build/SKILL.md +212 -0
  55. dataface/ai/skills/dashboard-build/examples/_smoke.yml +15 -0
  56. dataface/ai/skills/dashboard-design/SKILL.md +182 -0
  57. dataface/ai/skills/dashboard-review/SKILL.md +113 -0
  58. dataface/ai/skills/dashboard-structural-review/SKILL.md +173 -0
  59. dataface/ai/skills/dashboard-visual-review/SKILL.md +139 -0
  60. dataface/ai/skills/dataface-mcp-setup/SKILL.md +177 -0
  61. dataface/ai/skills/dataface-troubleshooting/SKILL.md +225 -0
  62. dataface/ai/skills/drill-down-link/SKILL.md +112 -0
  63. dataface/ai/skills/drill-down-link/examples/drill-down-link.yml +27 -0
  64. dataface/ai/skills/faceted-small-multiples/SKILL.md +116 -0
  65. dataface/ai/skills/faceted-small-multiples/examples/faceted-small-multiples.yml +33 -0
  66. dataface/ai/skills/filter-bar-with-variables/SKILL.md +105 -0
  67. dataface/ai/skills/filter-bar-with-variables/examples/filter-bar-with-variables.yml +49 -0
  68. dataface/ai/skills/kpi-row/SKILL.md +101 -0
  69. dataface/ai/skills/kpi-row/examples/kpi-row.yml +55 -0
  70. dataface/ai/skills/report-design/SKILL.md +184 -0
  71. dataface/ai/skills/single-metric-bignum/SKILL.md +90 -0
  72. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +27 -0
  73. dataface/ai/skills/table-heavy-ops-dashboard/SKILL.md +114 -0
  74. dataface/ai/skills/table-heavy-ops-dashboard/examples/table-heavy-ops-dashboard.yml +48 -0
  75. dataface/ai/skills/time-series-trend/SKILL.md +93 -0
  76. dataface/ai/skills/time-series-trend/examples/time-series-trend.yml +26 -0
  77. dataface/ai/skills/top-n-with-detail/SKILL.md +98 -0
  78. dataface/ai/skills/top-n-with-detail/examples/top-n-with-detail.yml +45 -0
  79. dataface/ai/skills/two-by-two-grid-overview/SKILL.md +78 -0
  80. dataface/ai/skills/two-by-two-grid-overview/examples/two-by-two-grid-overview.yml +64 -0
  81. dataface/ai/tool_schemas.py +132 -0
  82. dataface/ai/tools/__init__.py +312 -0
  83. dataface/ai/yaml_utils.py +57 -0
  84. dataface/cli/__init__.py +3 -0
  85. dataface/cli/_console.py +48 -0
  86. dataface/cli/_error_format.py +83 -0
  87. dataface/cli/_extras.py +190 -0
  88. dataface/cli/_json_output.py +8 -0
  89. dataface/cli/_parsing.py +17 -0
  90. dataface/cli/_version_info.py +56 -0
  91. dataface/cli/commands/__init__.py +3 -0
  92. dataface/cli/commands/_agent_input.py +205 -0
  93. dataface/cli/commands/_agent_server.py +115 -0
  94. dataface/cli/commands/chat.py +645 -0
  95. dataface/cli/commands/describe.py +107 -0
  96. dataface/cli/commands/docs.py +131 -0
  97. dataface/cli/commands/extension.py +179 -0
  98. dataface/cli/commands/init.py +240 -0
  99. dataface/cli/commands/inspect.py +94 -0
  100. dataface/cli/commands/mcp_init.py +167 -0
  101. dataface/cli/commands/query.py +386 -0
  102. dataface/cli/commands/render.py +291 -0
  103. dataface/cli/commands/schema.py +411 -0
  104. dataface/cli/commands/search.py +49 -0
  105. dataface/cli/commands/serve.py +114 -0
  106. dataface/cli/commands/skills.py +133 -0
  107. dataface/cli/commands/skills_init.py +161 -0
  108. dataface/cli/commands/validate.py +63 -0
  109. dataface/cli/main.py +1501 -0
  110. dataface/core/__init__.py +75 -0
  111. dataface/core/compile/__init__.py +244 -0
  112. dataface/core/compile/_jinja_helpers.py +78 -0
  113. dataface/core/compile/channel.py +222 -0
  114. dataface/core/compile/chart_focus.py +101 -0
  115. dataface/core/compile/chart_resolved.py +169 -0
  116. dataface/core/compile/chart_type_detection.py +489 -0
  117. dataface/core/compile/chart_update.py +261 -0
  118. dataface/core/compile/colors.py +64 -0
  119. dataface/core/compile/compiler.py +904 -0
  120. dataface/core/compile/config.py +823 -0
  121. dataface/core/compile/custom_chart_types.py +208 -0
  122. dataface/core/compile/data_table_attachment.py +1287 -0
  123. dataface/core/compile/detect.py +110 -0
  124. dataface/core/compile/errors.py +302 -0
  125. dataface/core/compile/filter_injection.py +319 -0
  126. dataface/core/compile/introspection.py +527 -0
  127. dataface/core/compile/jinja.py +511 -0
  128. dataface/core/compile/labels_env.py +52 -0
  129. dataface/core/compile/markdown.py +154 -0
  130. dataface/core/compile/meta.py +388 -0
  131. dataface/core/compile/models/__init__.py +0 -0
  132. dataface/core/compile/models/chart/__init__.py +0 -0
  133. dataface/core/compile/models/chart/authored.py +2137 -0
  134. dataface/core/compile/models/chart/compiled.py +398 -0
  135. dataface/core/compile/models/config.py +347 -0
  136. dataface/core/compile/models/face/__init__.py +0 -0
  137. dataface/core/compile/models/face/authored.py +659 -0
  138. dataface/core/compile/models/face/compiled.py +522 -0
  139. dataface/core/compile/models/factories.py +201 -0
  140. dataface/core/compile/models/markers.py +40 -0
  141. dataface/core/compile/models/palette.py +36 -0
  142. dataface/core/compile/models/primitives.py +415 -0
  143. dataface/core/compile/models/query/__init__.py +0 -0
  144. dataface/core/compile/models/query/authored.py +246 -0
  145. dataface/core/compile/models/query/compiled.py +710 -0
  146. dataface/core/compile/models/refs.py +137 -0
  147. dataface/core/compile/models/source.py +611 -0
  148. dataface/core/compile/models/style/__init__.py +0 -0
  149. dataface/core/compile/models/style/authored.py +481 -0
  150. dataface/core/compile/models/style/compiled.py +3399 -0
  151. dataface/core/compile/models/style/merged.py +1682 -0
  152. dataface/core/compile/models/theme.py +362 -0
  153. dataface/core/compile/models/variable/__init__.py +0 -0
  154. dataface/core/compile/models/variable/authored.py +254 -0
  155. dataface/core/compile/models/vega_lite/__init__.py +0 -0
  156. dataface/core/compile/models/vega_lite/config.py +510 -0
  157. dataface/core/compile/models/vega_lite/contracts.py +171 -0
  158. dataface/core/compile/normalize_charts.py +494 -0
  159. dataface/core/compile/normalize_layout.py +1000 -0
  160. dataface/core/compile/normalize_queries.py +297 -0
  161. dataface/core/compile/normalize_variables.py +489 -0
  162. dataface/core/compile/normalizer.py +543 -0
  163. dataface/core/compile/palette.py +1100 -0
  164. dataface/core/compile/parameterized.py +658 -0
  165. dataface/core/compile/parser.py +228 -0
  166. dataface/core/compile/schema.py +20 -0
  167. dataface/core/compile/schema_renderers/__init__.py +0 -0
  168. dataface/core/compile/schema_renderers/json_schema.py +163 -0
  169. dataface/core/compile/schema_renderers/prompt.py +152 -0
  170. dataface/core/compile/schema_renderers/vscode_schema.py +301 -0
  171. dataface/core/compile/sizing.py +2126 -0
  172. dataface/core/compile/sources.py +518 -0
  173. dataface/core/compile/sql_authoring_lint.py +56 -0
  174. dataface/core/compile/style_cascade.py +471 -0
  175. dataface/core/compile/typography.py +299 -0
  176. dataface/core/compile/validator.py +301 -0
  177. dataface/core/compile/variables.py +53 -0
  178. dataface/core/compile/vega_config.py +98 -0
  179. dataface/core/compile/vega_lite/__init__.py +6 -0
  180. dataface/core/compile/vega_lite/validation.py +95 -0
  181. dataface/core/compile/yaml_error_formatter.py +838 -0
  182. dataface/core/connections.py +38 -0
  183. dataface/core/dashboard.py +358 -0
  184. dataface/core/defaults/default_config.yml +101 -0
  185. dataface/core/defaults/palettes/categorical/category-10-dark.yml +32 -0
  186. dataface/core/defaults/palettes/categorical/category-10-light.yml +43 -0
  187. dataface/core/defaults/palettes/categorical/category-10.yml +31 -0
  188. dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +22 -0
  189. dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +29 -0
  190. dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +20 -0
  191. dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +21 -0
  192. dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +20 -0
  193. dataface/core/defaults/palettes/categorical/editorial-10-dark.yml +32 -0
  194. dataface/core/defaults/palettes/categorical/editorial-10.yml +40 -0
  195. dataface/core/defaults/palettes/categorical/hero-6.yml +17 -0
  196. dataface/core/defaults/palettes/categorical/single-blue.yml +11 -0
  197. dataface/core/defaults/palettes/categorical/tableau.yml +20 -0
  198. dataface/core/defaults/palettes/data/xkcd_colors.json +3803 -0
  199. dataface/core/defaults/palettes/diverging/blue-red.yml +25 -0
  200. dataface/core/defaults/palettes/diverging/coolwarm.yml +24 -0
  201. dataface/core/defaults/palettes/diverging/crimson-green.yml +23 -0
  202. dataface/core/defaults/palettes/diverging/orange-teal.yml +23 -0
  203. dataface/core/defaults/palettes/diverging/sunset.yml +24 -0
  204. dataface/core/defaults/palettes/scaffold/dft-creams.yml +38 -0
  205. dataface/core/defaults/palettes/scaffold/dft-grays.yml +53 -0
  206. dataface/core/defaults/palettes/sequential/amber.yml +22 -0
  207. dataface/core/defaults/palettes/sequential/blue.yml +22 -0
  208. dataface/core/defaults/palettes/sequential/brown.yml +22 -0
  209. dataface/core/defaults/palettes/sequential/gray.yml +22 -0
  210. dataface/core/defaults/palettes/sequential/green.yml +22 -0
  211. dataface/core/defaults/palettes/sequential/purple.yml +22 -0
  212. dataface/core/defaults/palettes/sequential/rust.yml +22 -0
  213. dataface/core/defaults/palettes/sequential/teal.yml +22 -0
  214. dataface/core/defaults/palettes/tone/negative.yml +32 -0
  215. dataface/core/defaults/palettes/tone/positive.yml +22 -0
  216. dataface/core/defaults/palettes/tone/warning.yml +22 -0
  217. dataface/core/defaults/themes/_base.yaml +786 -0
  218. dataface/core/defaults/themes/bi.yaml +16 -0
  219. dataface/core/defaults/themes/carbong100.yaml +41 -0
  220. dataface/core/defaults/themes/cream.yaml +122 -0
  221. dataface/core/defaults/themes/dark.yaml +40 -0
  222. dataface/core/defaults/themes/diagnostics-title-angle-extreme.yaml +9 -0
  223. dataface/core/defaults/themes/diagnostics-title-baseline-extreme.yaml +9 -0
  224. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +24 -0
  225. dataface/core/defaults/themes/diagnostics-title-center.yaml +8 -0
  226. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +24 -0
  227. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +25 -0
  228. dataface/core/defaults/themes/diagnostics-title-left.yaml +8 -0
  229. dataface/core/defaults/themes/diagnostics-title-offset-extreme.yaml +9 -0
  230. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +24 -0
  231. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +24 -0
  232. dataface/core/defaults/themes/editorial.yaml +147 -0
  233. dataface/core/defaults/themes/light.yaml +30 -0
  234. dataface/core/defaults/themes/looker.yaml +17 -0
  235. dataface/core/defaults/themes/stark.yaml +134 -0
  236. dataface/core/errors/__init__.py +67 -0
  237. dataface/core/errors/codes_compile.py +56 -0
  238. dataface/core/errors/codes_execute.py +177 -0
  239. dataface/core/errors/codes_render.py +106 -0
  240. dataface/core/errors/codes_unknown.py +15 -0
  241. dataface/core/errors/hints.py +74 -0
  242. dataface/core/errors/registry.py +42 -0
  243. dataface/core/errors/structured.py +92 -0
  244. dataface/core/execute/__init__.py +91 -0
  245. dataface/core/execute/adapters/__init__.py +49 -0
  246. dataface/core/execute/adapters/adapter_registry.py +400 -0
  247. dataface/core/execute/adapters/base.py +245 -0
  248. dataface/core/execute/adapters/csv_adapter.py +239 -0
  249. dataface/core/execute/adapters/dbt_adapter.py +283 -0
  250. dataface/core/execute/adapters/dbt_adapter_factory.py +212 -0
  251. dataface/core/execute/adapters/dbt_macro_loader.py +95 -0
  252. dataface/core/execute/adapters/dbt_utils.py +150 -0
  253. dataface/core/execute/adapters/http_adapter.py +224 -0
  254. dataface/core/execute/adapters/metricflow_adapter.py +94 -0
  255. dataface/core/execute/adapters/schema_resolver_adapter.py +144 -0
  256. dataface/core/execute/adapters/sql_adapter.py +710 -0
  257. dataface/core/execute/adapters/values_adapter.py +58 -0
  258. dataface/core/execute/batch.py +744 -0
  259. dataface/core/execute/cache_backend.py +135 -0
  260. dataface/core/execute/cache_keys.py +66 -0
  261. dataface/core/execute/dbt_jinja.py +21 -0
  262. dataface/core/execute/dialects/__init__.py +121 -0
  263. dataface/core/execute/dialects/athena.py +75 -0
  264. dataface/core/execute/dialects/base.py +302 -0
  265. dataface/core/execute/dialects/bigquery.py +38 -0
  266. dataface/core/execute/dialects/databricks.py +68 -0
  267. dataface/core/execute/dialects/duckdb.py +35 -0
  268. dataface/core/execute/dialects/mysql.py +68 -0
  269. dataface/core/execute/dialects/postgres.py +39 -0
  270. dataface/core/execute/dialects/redshift.py +12 -0
  271. dataface/core/execute/dialects/snowflake.py +51 -0
  272. dataface/core/execute/dialects/sqlserver.py +92 -0
  273. dataface/core/execute/duckdb_cache.py +712 -0
  274. dataface/core/execute/duckdb_config.py +26 -0
  275. dataface/core/execute/errors.py +213 -0
  276. dataface/core/execute/executor.py +1249 -0
  277. dataface/core/execute/parallel.py +162 -0
  278. dataface/core/execute/setup_sql.py +58 -0
  279. dataface/core/execute/source_registry.py +72 -0
  280. dataface/core/execute/source_resolver.py +255 -0
  281. dataface/core/execute/sql_guard.py +387 -0
  282. dataface/core/execute/sql_literals.py +199 -0
  283. dataface/core/fonts.py +52 -0
  284. dataface/core/inspect/__init__.py +32 -0
  285. dataface/core/inspect/cache_factory.py +98 -0
  286. dataface/core/inspect/db_types.py +162 -0
  287. dataface/core/inspect/dbt_schema.py +96 -0
  288. dataface/core/inspect/defaults.yml +37 -0
  289. dataface/core/inspect/fanout_risk.py +109 -0
  290. dataface/core/inspect/manifest_utils.py +77 -0
  291. dataface/core/inspect/partials/categorical.yml +40 -0
  292. dataface/core/inspect/partials/date.yml +40 -0
  293. dataface/core/inspect/partials/numeric.yml +55 -0
  294. dataface/core/inspect/partition_types.py +38 -0
  295. dataface/core/inspect/query_validator.py +975 -0
  296. dataface/core/inspect/renderer.py +354 -0
  297. dataface/core/inspect/resolver.py +808 -0
  298. dataface/core/inspect/search.py +461 -0
  299. dataface/core/inspect/sources/__init__.py +32 -0
  300. dataface/core/inspect/sources/dbt.py +738 -0
  301. dataface/core/inspect/sources/duckdb_utils.py +66 -0
  302. dataface/core/inspect/templates/__init__.py +1 -0
  303. dataface/core/inspect/templates/categorical_column.yml +196 -0
  304. dataface/core/inspect/templates/charts.yml +109 -0
  305. dataface/core/inspect/templates/date_column.yml +248 -0
  306. dataface/core/inspect/templates/model.yml +138 -0
  307. dataface/core/inspect/templates/numeric_column.yml +261 -0
  308. dataface/core/inspect/templates/quality.yml +80 -0
  309. dataface/core/inspect/templates/string_column.yml +263 -0
  310. dataface/core/project_roots.py +165 -0
  311. dataface/core/render/__init__.py +87 -0
  312. dataface/core/render/board_links.py +176 -0
  313. dataface/core/render/chart/__init__.py +27 -0
  314. dataface/core/render/chart/arc_attached_table.py +251 -0
  315. dataface/core/render/chart/artifacts.py +16 -0
  316. dataface/core/render/chart/callout.py +225 -0
  317. dataface/core/render/chart/decisions.py +358 -0
  318. dataface/core/render/chart/geo.py +700 -0
  319. dataface/core/render/chart/kpi.py +916 -0
  320. dataface/core/render/chart/labels.py +76 -0
  321. dataface/core/render/chart/pipeline.py +818 -0
  322. dataface/core/render/chart/presentation.py +36 -0
  323. dataface/core/render/chart/profile.py +3438 -0
  324. dataface/core/render/chart/render_single.py +347 -0
  325. dataface/core/render/chart/renderers.py +193 -0
  326. dataface/core/render/chart/rendering.py +565 -0
  327. dataface/core/render/chart/serialization.py +90 -0
  328. dataface/core/render/chart/spark.py +496 -0
  329. dataface/core/render/chart/spark_bar.py +370 -0
  330. dataface/core/render/chart/spec_builders.py +154 -0
  331. dataface/core/render/chart/standard_renderer.py +2645 -0
  332. dataface/core/render/chart/table.py +2957 -0
  333. dataface/core/render/chart/table_support.py +1452 -0
  334. dataface/core/render/chart/tick_values.py +66 -0
  335. dataface/core/render/chart/time_unit_detect.py +809 -0
  336. dataface/core/render/chart/title_overflow.py +157 -0
  337. dataface/core/render/chart/type_inference.py +122 -0
  338. dataface/core/render/chart/validation.py +99 -0
  339. dataface/core/render/chart/vega_lite.py +125 -0
  340. dataface/core/render/chart/vega_lite_types.py +268 -0
  341. dataface/core/render/chart/vl_field_maps.py +346 -0
  342. dataface/core/render/chart_interactivity.py +24 -0
  343. dataface/core/render/control_registry.py +287 -0
  344. dataface/core/render/converters/__init__.py +24 -0
  345. dataface/core/render/converters/chart.py +276 -0
  346. dataface/core/render/converters/html.py +98 -0
  347. dataface/core/render/converters/pdf.py +40 -0
  348. dataface/core/render/converters/png.py +41 -0
  349. dataface/core/render/errors.py +144 -0
  350. dataface/core/render/face_api.py +160 -0
  351. dataface/core/render/faces.py +1194 -0
  352. dataface/core/render/font_measurement.py +48 -0
  353. dataface/core/render/font_support.py +197 -0
  354. dataface/core/render/fonts/DFTSansTabular-Regular.ttf +0 -0
  355. dataface/core/render/fonts/DFTSansTabular-Regular.woff2 +0 -0
  356. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.ttf +0 -0
  357. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.ttf +0 -0
  358. dataface/core/render/fonts/InterVariable.ttf +0 -0
  359. dataface/core/render/fonts/InterVariable.woff2 +0 -0
  360. dataface/core/render/fonts/NOTO_COLOR_EMOJI_LICENSE.txt +93 -0
  361. dataface/core/render/fonts/NOTO_EMOJI_LICENSE.txt +93 -0
  362. dataface/core/render/fonts/NotoColorEmoji-Regular.ttf +0 -0
  363. dataface/core/render/fonts/NotoColorEmoji-Regular.woff2 +0 -0
  364. dataface/core/render/fonts/NotoEmoji-Regular.ttf +0 -0
  365. dataface/core/render/fonts/NotoEmoji-Regular.woff2 +0 -0
  366. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -0
  367. dataface/core/render/fonts/SOURCE_SERIF_4_LICENSE.txt +98 -0
  368. dataface/core/render/fonts/SourceCodePro-Regular.ttf +0 -0
  369. dataface/core/render/fonts/SourceSerif4-Regular.ttf +0 -0
  370. dataface/core/render/fonts/_emoji_font_face.css +43 -0
  371. dataface/core/render/fonts/source-serif-4-variable-latin.woff2 +0 -0
  372. dataface/core/render/format_utils.py +329 -0
  373. dataface/core/render/geo_defaults.yml +28 -0
  374. dataface/core/render/json_format.py +146 -0
  375. dataface/core/render/layout_sizing.py +865 -0
  376. dataface/core/render/layouts.py +541 -0
  377. dataface/core/render/markdown_defaults.yml +16 -0
  378. dataface/core/render/missing_vars_prompt.py +79 -0
  379. dataface/core/render/placeholder.py +389 -0
  380. dataface/core/render/render_result.py +14 -0
  381. dataface/core/render/renderer.py +467 -0
  382. dataface/core/render/script_embedding.py +16 -0
  383. dataface/core/render/svg_utils.py +212 -0
  384. dataface/core/render/template_loader.py +69 -0
  385. dataface/core/render/templates/controls/_styles.css +606 -0
  386. dataface/core/render/templates/controls/checkbox.html +16 -0
  387. dataface/core/render/templates/controls/date.html +16 -0
  388. dataface/core/render/templates/controls/number.html +19 -0
  389. dataface/core/render/templates/controls/readonly.html +9 -0
  390. dataface/core/render/templates/controls/select.html +21 -0
  391. dataface/core/render/templates/controls/slider.html +22 -0
  392. dataface/core/render/templates/controls/text.html +16 -0
  393. dataface/core/render/templates/scripts/chart_interactivity.js +191 -0
  394. dataface/core/render/templates/scripts/variables.js +976 -0
  395. dataface/core/render/templates/svg/grid_pattern.svg +3 -0
  396. dataface/core/render/templates/svg/styles.css +51 -0
  397. dataface/core/render/terminal.py +311 -0
  398. dataface/core/render/terminal_charts.py +563 -0
  399. dataface/core/render/terminal_defaults.yml +2 -0
  400. dataface/core/render/terminal_layouts.py +299 -0
  401. dataface/core/render/terminal_text.py +31 -0
  402. dataface/core/render/text/__init__.py +1 -0
  403. dataface/core/render/text/case.py +113 -0
  404. dataface/core/render/text_format.py +129 -0
  405. dataface/core/render/utils.py +106 -0
  406. dataface/core/render/variable_controls.py +946 -0
  407. dataface/core/render/variable_input_refinement.py +140 -0
  408. dataface/core/render/warnings/__init__.py +15 -0
  409. dataface/core/render/warnings/bar_color_1_to_1_with_x.py +80 -0
  410. dataface/core/render/warnings/base.py +44 -0
  411. dataface/core/render/warnings/fanout_risk.py +15 -0
  412. dataface/core/render/warnings/from_query_diagnostic.py +56 -0
  413. dataface/core/render/warnings/missing_join_predicate.py +13 -0
  414. dataface/core/render/warnings/query_parse_error.py +14 -0
  415. dataface/core/render/warnings/query_returned_zero_rows.py +42 -0
  416. dataface/core/render/warnings/reaggregation.py +14 -0
  417. dataface/core/render/warnings/registry.py +45 -0
  418. dataface/core/render/warnings/suppression.py +46 -0
  419. dataface/core/render/warnings/temporal_single_point.py +63 -0
  420. dataface/core/render/warnings/unreferenced_chart.py +15 -0
  421. dataface/core/render/warnings/y_encoding_mostly_null.py +76 -0
  422. dataface/core/render/yaml_format.py +167 -0
  423. dataface/core/resolve_face.py +195 -0
  424. dataface/core/schema/__init__.py +0 -0
  425. dataface/core/schema/guidance.py +151 -0
  426. dataface/core/scoped_paths.py +59 -0
  427. dataface/core/serve/__init__.py +14 -0
  428. dataface/core/serve/bootstrap.py +39 -0
  429. dataface/core/serve/embedded.py +57 -0
  430. dataface/core/serve/port.py +129 -0
  431. dataface/core/serve/server.py +938 -0
  432. dataface/core/serve/templates/__init__.py +0 -0
  433. dataface/core/serve/templates/directory.yml +6 -0
  434. dataface/core/serve/templates/error.html.j2 +217 -0
  435. dataface/core/utils.py +121 -0
  436. dataface/core/validate.py +64 -0
  437. dataface/integrations/__init__.py +0 -0
  438. dataface/integrations/highlighting.py +351 -0
  439. dataface/integrations/markdown.py +537 -0
  440. dataface/py.typed +0 -0
  441. dataface-0.1.2.dist-info/METADATA +375 -0
  442. dataface-0.1.2.dist-info/RECORD +455 -0
  443. dataface-0.1.2.dist-info/WHEEL +4 -0
  444. dataface-0.1.2.dist-info/entry_points.txt +2 -0
  445. dataface-0.1.2.dist-info/licenses/LICENSE +202 -0
  446. mdsvg/__init__.py +168 -0
  447. mdsvg/fonts.py +656 -0
  448. mdsvg/images.py +299 -0
  449. mdsvg/parser.py +629 -0
  450. mdsvg/playground.py +284 -0
  451. mdsvg/py.typed +2 -0
  452. mdsvg/renderer.py +1623 -0
  453. mdsvg/style.py +355 -0
  454. mdsvg/types.py +200 -0
  455. mdsvg/utils.py +86 -0
@@ -0,0 +1,98 @@
1
+ """Cache-source factory and schema resolver builder.
2
+
3
+ Builds a LayeredSchemaResolver from an adapter registry. When the private
4
+ ``dataface-super-schema`` package is installed, the resolver uses a warm
5
+ SuperSchemaSource cache; otherwise it operates in dbt-only mode.
6
+
7
+ This module provides factory helpers for building resolvers with optional
8
+ super-schema caches. Other modules that only need a presence check use their
9
+ own ``importlib.util.find_spec("dataface_super_schema")`` probe — that is
10
+ intentional and avoids import cycles.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import importlib.util
16
+ from pathlib import Path
17
+ from typing import TYPE_CHECKING, Any
18
+
19
+ from dataface.core.execute.adapters import AdapterRegistry
20
+ from dataface.core.inspect.resolver import LayeredSchemaResolver
21
+
22
+ if TYPE_CHECKING:
23
+ from dataface_super_schema.inspect.sources.super_schema import SuperSchemaSource
24
+
25
+ # Probe at module load time — single find_spec call per process.
26
+ _SUPER_SCHEMA_AVAILABLE: bool = (
27
+ importlib.util.find_spec("dataface_super_schema") is not None
28
+ )
29
+
30
+
31
+ def project_root_for_registry(registry: AdapterRegistry) -> Path:
32
+ """Resolve the registry's project root without consulting cwd."""
33
+ raw = registry.project_root
34
+ if raw is not None:
35
+ return Path(raw).resolve()
36
+ raise ValueError("adapter_registry.project_root is required for schema requests")
37
+
38
+
39
+ def make_cache_source(cache_path: Path) -> Any:
40
+ """Build a SuperSchemaSource when the private package is available.
41
+
42
+ Args:
43
+ cache_path: Absolute path to a ``super_schema.json`` cache file.
44
+
45
+ Returns ``None`` when ``dataface-super-schema`` is not installed.
46
+ The resolver degrades to dbt-only mode when ``None`` is returned.
47
+ """
48
+ if not _SUPER_SCHEMA_AVAILABLE:
49
+ return None
50
+ from dataface_super_schema.inspect.sources.super_schema import ( # noqa: PLC0415
51
+ SuperSchemaSource,
52
+ )
53
+ from dataface_super_schema.inspect.storage import InspectionStorage # noqa: PLC0415
54
+
55
+ return SuperSchemaSource(InspectionStorage(output_path=cache_path))
56
+
57
+
58
+ def make_cache_source_for_project(project_root: Path) -> Any:
59
+ """Build a SuperSchemaSource from the project's default cache path.
60
+
61
+ Equivalent to make_cache_source(project_root / InspectionStorage.DEFAULT_PATH)
62
+ without importing InspectionStorage in the caller.
63
+ """
64
+ if not _SUPER_SCHEMA_AVAILABLE:
65
+ return None
66
+ from dataface_super_schema.inspect.storage import InspectionStorage # noqa: PLC0415
67
+
68
+ return make_cache_source(project_root / InspectionStorage.DEFAULT_PATH)
69
+
70
+
71
+ def build_resolver(
72
+ adapter_registry: AdapterRegistry,
73
+ cache_path: Path | None = None,
74
+ cache_source: SuperSchemaSource | None = None,
75
+ ) -> LayeredSchemaResolver:
76
+ """Build a LayeredSchemaResolver from an adapter registry and optional cache.
77
+
78
+ Args:
79
+ adapter_registry: Registry providing source configs and project root.
80
+ cache_path: Explicit path to a ``super_schema.json`` file. When
81
+ ``None``, the cache is auto-derived from the registry's
82
+ project_root via ``make_cache_source_for_project``.
83
+ cache_source: Explicit warm-cache source. Takes priority over
84
+ ``cache_path`` when provided.
85
+ """
86
+ project_root = project_root_for_registry(adapter_registry)
87
+ resolved_cache: Any = cache_source
88
+ if resolved_cache is None:
89
+ resolved_cache = (
90
+ make_cache_source(cache_path)
91
+ if cache_path is not None
92
+ else make_cache_source_for_project(project_root)
93
+ )
94
+ return LayeredSchemaResolver(
95
+ cache=resolved_cache,
96
+ adapter_registry=adapter_registry,
97
+ project_root=project_root,
98
+ )
@@ -0,0 +1,162 @@
1
+ """Shared SQL type classification for the inspect module.
2
+
3
+ Provides a single source of truth for categorising database column types
4
+ as numeric, string, temporal, or complex. Used by query_builder,
5
+ quality_detector, semantic_detector, and inspector.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # Base-type normaliser
14
+ # ---------------------------------------------------------------------------
15
+
16
+ # Postgres-specific aliases → standard SQL type names
17
+ _POSTGRES_TYPE_MAP: dict[str, str] = {
18
+ "INT2": "SMALLINT",
19
+ "INT4": "INTEGER",
20
+ "INT8": "BIGINT",
21
+ "FLOAT4": "FLOAT",
22
+ "FLOAT8": "DOUBLE",
23
+ "BOOL": "BOOLEAN",
24
+ "BPCHAR": "CHAR",
25
+ "TIMESTAMPTZ": "TIMESTAMP",
26
+ "TIMETZ": "TIME",
27
+ }
28
+
29
+
30
+ def extract_base_type(db_type: str, *, normalize_aliases: bool = False) -> str:
31
+ """Extract base type name without precision, scale, or modifiers.
32
+
33
+ Args:
34
+ db_type: Raw database type string, e.g. ``"DECIMAL(18,2) NOT NULL"``.
35
+ normalize_aliases: When True, map Postgres aliases to standard
36
+ names (e.g. ``INT8`` → ``BIGINT``).
37
+
38
+ Examples:
39
+ >>> extract_base_type("DECIMAL(18,2)")
40
+ 'DECIMAL'
41
+ >>> extract_base_type("int8", normalize_aliases=True)
42
+ 'BIGINT'
43
+ """
44
+ base = re.sub(r"\([^)]*\)", "", db_type.upper()).strip()
45
+ for modifier in ("NOT NULL", "NULL", "PRIMARY KEY", "UNIQUE", "DEFAULT", "ARRAY"):
46
+ base = base.replace(modifier, "").strip()
47
+ base = base.split()[0] if base else base
48
+ if normalize_aliases:
49
+ base = _POSTGRES_TYPE_MAP.get(base, base)
50
+ return base
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Canonical type sets (superset across all supported databases)
55
+ # ---------------------------------------------------------------------------
56
+
57
+ NUMERIC_TYPES: frozenset[str] = frozenset(
58
+ {
59
+ "INTEGER",
60
+ "INT",
61
+ "BIGINT",
62
+ "SMALLINT",
63
+ "TINYINT",
64
+ "DECIMAL",
65
+ "NUMERIC",
66
+ "FLOAT",
67
+ "DOUBLE",
68
+ "REAL",
69
+ "NUMBER", # Oracle / Snowflake
70
+ "INT64",
71
+ "FLOAT64", # BigQuery
72
+ "INT2",
73
+ "INT4",
74
+ "INT8",
75
+ "FLOAT4",
76
+ "FLOAT8", # Postgres aliases
77
+ "SERIAL",
78
+ "BIGSERIAL",
79
+ "SMALLSERIAL", # Postgres auto-increment
80
+ "HUGEINT",
81
+ "UBIGINT",
82
+ "UINTEGER", # DuckDB unsigned
83
+ "USMALLINT",
84
+ "UTINYINT",
85
+ "MONEY", # Postgres money
86
+ }
87
+ )
88
+
89
+ STRING_TYPES: frozenset[str] = frozenset(
90
+ {
91
+ "VARCHAR",
92
+ "CHAR",
93
+ "TEXT",
94
+ "STRING",
95
+ "CLOB",
96
+ "NVARCHAR",
97
+ "NCHAR",
98
+ "NTEXT",
99
+ "BPCHAR", # Postgres blank-padded char
100
+ }
101
+ )
102
+
103
+ TEMPORAL_TYPES: frozenset[str] = frozenset(
104
+ {
105
+ "DATE",
106
+ "TIMESTAMP",
107
+ "DATETIME",
108
+ "TIME",
109
+ "TIMESTAMPTZ",
110
+ "TIMETZ", # Postgres with timezone
111
+ "TIMESTAMP_NTZ",
112
+ "TIMESTAMP_LTZ",
113
+ "TIMESTAMP_TZ", # Snowflake variants
114
+ "INTERVAL",
115
+ }
116
+ )
117
+
118
+ COMPLEX_TYPES: frozenset[str] = frozenset(
119
+ {
120
+ "ARRAY",
121
+ "STRUCT",
122
+ "RECORD",
123
+ "GEOGRAPHY",
124
+ "GEOMETRY",
125
+ "BYTES",
126
+ "JSON", # BigQuery
127
+ "VARIANT",
128
+ "OBJECT", # Snowflake
129
+ "MAP", # Databricks
130
+ }
131
+ )
132
+
133
+ # Prefixes that indicate a complex parameterised type (e.g. ARRAY<INT64>)
134
+ _COMPLEX_PREFIXES = ("ARRAY<", "STRUCT<", "RECORD<", "MAP<")
135
+
136
+
137
+ # ---------------------------------------------------------------------------
138
+ # Convenience helpers
139
+ # ---------------------------------------------------------------------------
140
+
141
+
142
+ def is_numeric(db_type: str) -> bool:
143
+ """Return True if *db_type* is a numeric column type."""
144
+ return extract_base_type(db_type) in NUMERIC_TYPES
145
+
146
+
147
+ def is_string(db_type: str) -> bool:
148
+ """Return True if *db_type* is a string/text column type."""
149
+ return extract_base_type(db_type) in STRING_TYPES
150
+
151
+
152
+ def is_temporal(db_type: str) -> bool:
153
+ """Return True if *db_type* is a date, time, or timestamp type."""
154
+ return extract_base_type(db_type) in TEMPORAL_TYPES
155
+
156
+
157
+ def is_complex(db_type: str) -> bool:
158
+ """Return True if *db_type* is a complex type unsuitable for standard aggregates."""
159
+ upper = db_type.upper()
160
+ if upper.startswith(_COMPLEX_PREFIXES):
161
+ return True
162
+ return extract_base_type(db_type) in COMPLEX_TYPES
@@ -0,0 +1,96 @@
1
+ """Parse dbt schema.yml files and map descriptions to inspected tables/columns.
2
+
3
+ Scans ``models/**/schema.yml`` (and ``.yaml``) for model-level and
4
+ column-level ``description`` fields, then maps them onto inspected
5
+ table/column names using case-insensitive matching.
6
+
7
+ Descriptions are stored as explicit source entries with
8
+ ``source=dbt_schema_yml`` — they never overwrite inferred metadata.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ import yaml
17
+
18
+
19
+ @dataclass
20
+ class DbtDescription:
21
+ """A single description extracted from a dbt schema.yml file."""
22
+
23
+ model_name: str
24
+ column_name: str | None # None = table-level description
25
+ description: str
26
+ source: str = "dbt_schema_yml"
27
+
28
+
29
+ class DbtSchemaParser:
30
+ """Discover and parse dbt schema.yml files under a project root."""
31
+
32
+ def __init__(self, project_root: Path) -> None:
33
+ self.project_root = project_root
34
+ self.warnings: list[str] = []
35
+
36
+ def parse(self) -> list[DbtDescription]:
37
+ """Scan ``models/**/schema.{yml,yaml}`` and extract descriptions."""
38
+ models_dir = self.project_root / "models"
39
+ if not models_dir.is_dir():
40
+ return []
41
+
42
+ descriptions: list[DbtDescription] = []
43
+ for pattern in ("**/*.yml", "**/*.yaml"):
44
+ for path in sorted(models_dir.glob(pattern)):
45
+ descriptions.extend(self._parse_file(path))
46
+ return descriptions
47
+
48
+ def _parse_file(self, path: Path) -> list[DbtDescription]:
49
+ """Parse a single schema file, returning descriptions found."""
50
+ try:
51
+ content = yaml.safe_load(path.read_text())
52
+ except yaml.YAMLError as e:
53
+ self.warnings.append(f"Failed to parse {path}: {e}")
54
+ return []
55
+
56
+ if not isinstance(content, dict):
57
+ return []
58
+
59
+ models = content.get("models")
60
+ if not isinstance(models, list):
61
+ return []
62
+
63
+ descriptions: list[DbtDescription] = []
64
+ for model in models:
65
+ if not isinstance(model, dict):
66
+ continue
67
+ model_name = model.get("name")
68
+ if not model_name:
69
+ continue
70
+
71
+ model_desc = model.get("description")
72
+ if model_desc:
73
+ descriptions.append(
74
+ DbtDescription(
75
+ model_name=model_name, column_name=None, description=model_desc
76
+ )
77
+ )
78
+
79
+ columns = model.get("columns")
80
+ if not isinstance(columns, list):
81
+ continue
82
+ for col in columns:
83
+ if not isinstance(col, dict):
84
+ continue
85
+ col_name = col.get("name")
86
+ col_desc = col.get("description")
87
+ if col_name and col_desc:
88
+ descriptions.append(
89
+ DbtDescription(
90
+ model_name=model_name,
91
+ column_name=col_name,
92
+ description=col_desc,
93
+ )
94
+ )
95
+
96
+ return descriptions
@@ -0,0 +1,37 @@
1
+ inspector:
2
+ sample_size: 100
3
+ collect_top_values: true
4
+ top_values_limit: 10
5
+ top_values_max_cardinality: 1000
6
+ top_values_skip_types:
7
+ - INTEGER
8
+ - INT
9
+ - BIGINT
10
+ - SMALLINT
11
+ - TINYINT
12
+ - FLOAT
13
+ - DOUBLE
14
+ - REAL
15
+ - DECIMAL
16
+ - NUMERIC
17
+ - NUMBER
18
+ - DATE
19
+ - TIME
20
+ - TIMESTAMP
21
+ - DATETIME
22
+ - INTERVAL
23
+ - BOOLEAN
24
+ - BOOL
25
+ - UUID
26
+ - BLOB
27
+ - BINARY
28
+ - BYTEA
29
+ - VARBINARY
30
+ - BIT
31
+ deep_profile: false
32
+ collect_histogram_bins: false
33
+ collect_date_distribution: false
34
+ collect_enum_values: false
35
+ enum_cardinality_threshold: 20
36
+ bigquery_exact_threshold_bytes: 1073741824
37
+ fk_range_slack_percent: 2
@@ -0,0 +1,109 @@
1
+ """Fanout risk scoring.
2
+
3
+ Scores the risk of aggregate inflation when joining table pairs.
4
+ Pure functions — no DB queries.
5
+
6
+ Risk levels:
7
+ - ``none`` — safe join, no action needed
8
+ - ``low`` — join changes grain but may be intentional
9
+ - ``medium`` — aggregation after this join may be incorrect
10
+ - ``high`` — strong likelihood of aggregate inflation
11
+ - ``critical`` — N:M join with aggregation, almost certainly wrong
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass
17
+
18
+ # Fanout thresholds — public, imported by query_validator.py.
19
+ HIGH_FANOUT_THRESHOLD = 10.0
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Data model
24
+ # ---------------------------------------------------------------------------
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class FanoutRisk:
29
+ """Fanout risk assessment for a join relationship."""
30
+
31
+ level: str # "none" | "low" | "medium" | "high" | "critical"
32
+ reason: str
33
+ recommendation: str
34
+
35
+ def to_dict(self) -> dict[str, str]:
36
+ """Dict for serialization.
37
+
38
+ All keys are single words (no underscores) so snake_case and
39
+ camelCase are identical — one method serves both formats.
40
+ """
41
+ return {
42
+ "level": self.level,
43
+ "reason": self.reason,
44
+ "recommendation": self.recommendation,
45
+ }
46
+
47
+
48
+ # ---------------------------------------------------------------------------
49
+ # Scoring
50
+ # ---------------------------------------------------------------------------
51
+
52
+
53
+ def score_fanout_risk(
54
+ multiplicity: str,
55
+ fanout_factor: float,
56
+ has_aggregation: bool = False,
57
+ ) -> FanoutRisk:
58
+ """Score fanout risk for a join pattern.
59
+
60
+ Args:
61
+ multiplicity: Join cardinality ("one-to-one", "one-to-many",
62
+ "many-to-one", "many-to-many").
63
+ fanout_factor: Average row multiplication factor.
64
+ has_aggregation: Whether the query aggregates after the join.
65
+ """
66
+ # 1:1 — always safe
67
+ if multiplicity == "one-to-one":
68
+ return FanoutRisk("none", "1:1 join — safe", "No action needed")
69
+
70
+ # N:1 — dimension lookup, always safe
71
+ if multiplicity == "many-to-one":
72
+ return FanoutRisk("none", "N:1 dimension lookup — safe", "No action needed")
73
+
74
+ # N:M — always dangerous
75
+ if multiplicity == "many-to-many":
76
+ if has_aggregation:
77
+ return FanoutRisk(
78
+ "critical",
79
+ "N:M join with aggregation — almost certainly inflates results",
80
+ "Use a bridge table or pre-aggregate to the correct grain before joining",
81
+ )
82
+ return FanoutRisk(
83
+ "high",
84
+ "N:M join — row multiplication likely unintended",
85
+ "Consider using a bridge table or pre-aggregating before joining",
86
+ )
87
+
88
+ # 1:N — risk depends on aggregation context and fanout factor
89
+ if multiplicity == "one-to-many":
90
+ if not has_aggregation:
91
+ return FanoutRisk(
92
+ "low",
93
+ "1:N join without aggregation — row expansion expected",
94
+ "Verify this is a detail query, not an aggregate",
95
+ )
96
+ if fanout_factor > HIGH_FANOUT_THRESHOLD:
97
+ return FanoutRisk(
98
+ "high",
99
+ f"1:N join with aggregation and high fanout ({fanout_factor:.1f}x)",
100
+ "Pre-aggregate the many-side to the join key grain before joining",
101
+ )
102
+ return FanoutRisk(
103
+ "medium",
104
+ f"1:N join with aggregation ({fanout_factor:.1f}x fanout)",
105
+ "Pre-aggregate the many-side to the join key grain before joining",
106
+ )
107
+
108
+ # Unknown multiplicity — safe default
109
+ return FanoutRisk("none", "Unknown multiplicity", "Review join pattern")
@@ -0,0 +1,77 @@
1
+ """Manifest I/O and template-comparison helpers for the inspect verb.
2
+
3
+ Pure functions — no agent-api or Pydantic coupling. Used by
4
+ ``dataface.agent_api.inspect`` to persist ejection state and compare
5
+ local templates against built-in versions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from datetime import datetime, timezone
12
+ from hashlib import sha256
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ INSPECT_TEMPLATE_MANIFEST = ".inspect-template-manifest.json"
17
+ MANIFEST_SCHEMA_VERSION = 1
18
+
19
+
20
+ def load_manifest(target_dir: Path, *, dataface_version: str) -> dict:
21
+ """Load the inspect-template manifest from target_dir, or return a fresh one."""
22
+ manifest_path = target_dir / INSPECT_TEMPLATE_MANIFEST
23
+ if not manifest_path.exists():
24
+ return {
25
+ "schema_version": MANIFEST_SCHEMA_VERSION,
26
+ "dataface_version": dataface_version,
27
+ "generated_at": datetime.now(timezone.utc).isoformat(),
28
+ "templates": {},
29
+ }
30
+ with open(manifest_path) as f:
31
+ return json.load(f)
32
+
33
+
34
+ def save_manifest(target_dir: Path, manifest: dict) -> None:
35
+ """Write the inspect-template manifest to target_dir."""
36
+ manifest_path = target_dir / INSPECT_TEMPLATE_MANIFEST
37
+ with open(manifest_path, "w") as f:
38
+ json.dump(manifest, f, indent=2, sort_keys=True)
39
+ f.write("\n")
40
+
41
+
42
+ def compare_templates(
43
+ resolved: Path,
44
+ manifest_templates: dict[str, dict],
45
+ templates_pkg: Any, # importlib.resources Traversable; Any avoids 3.10/3.11 compat shim
46
+ ) -> tuple[list[str], list[str], list[str], list[str]]:
47
+ """Compare ejected templates against built-in versions.
48
+
49
+ Returns ``(missing, upstream_changed, custom_safe, unchanged)`` name lists.
50
+ ``templates_pkg`` is an ``importlib.resources`` traversable (the package
51
+ containing the canonical ``.yml`` template files).
52
+ """
53
+ missing: list[str] = []
54
+ upstream_changed: list[str] = []
55
+ custom_safe: list[str] = []
56
+ unchanged: list[str] = []
57
+
58
+ for name, meta in sorted(manifest_templates.items()):
59
+ filename = meta.get("filename") or f"{name}.yml"
60
+ local_path = resolved / filename
61
+ if not local_path.exists():
62
+ missing.append(filename)
63
+ continue
64
+
65
+ built_in = templates_pkg.joinpath(filename).read_text()
66
+ built_in_hash = sha256(built_in.encode("utf-8")).hexdigest()
67
+ baseline_hash = meta.get("source_sha256")
68
+ local_hash = sha256(local_path.read_text().encode("utf-8")).hexdigest()
69
+
70
+ if local_hash == built_in_hash:
71
+ unchanged.append(name)
72
+ elif baseline_hash != built_in_hash:
73
+ upstream_changed.append(name)
74
+ else:
75
+ custom_safe.append(name)
76
+
77
+ return missing, upstream_changed, custom_safe, unchanged
@@ -0,0 +1,40 @@
1
+ # Categorical Column Partial - Bar chart visualization
2
+ #
3
+ # A reusable partial for visualizing categorical column distributions.
4
+ # Designed for use with foreach construct.
5
+ #
6
+ # Expected loop variable (col):
7
+ # - col.column: Column name to visualize
8
+ #
9
+ # Expected parent variables:
10
+ # - model: Table/model name
11
+ # - connection: DuckDB file path or :memory:
12
+
13
+ title: "{{ col.column }}"
14
+
15
+ queries:
16
+ category_counts:
17
+ source:
18
+ type: duckdb
19
+ path: "{{ connection }}"
20
+ sql: |
21
+ SELECT
22
+ COALESCE(CAST({{ col.column }} AS VARCHAR), '(null)') as category,
23
+ COUNT(*) as count
24
+ FROM {{ model }}
25
+ GROUP BY {{ col.column }}
26
+ ORDER BY COUNT(*) DESC
27
+ LIMIT 20
28
+
29
+ charts:
30
+ bar_chart:
31
+ title: "{{ col.column }} Distribution"
32
+ type: bar
33
+ query: category_counts
34
+ x: category
35
+ y: count
36
+ style:
37
+ orientation: horizontal
38
+
39
+ rows:
40
+ - bar_chart
@@ -0,0 +1,40 @@
1
+ # Date Column Partial - Time series visualization
2
+ #
3
+ # A reusable partial for visualizing date column distributions.
4
+ # Designed for use with foreach construct.
5
+ #
6
+ # Expected loop variable (col):
7
+ # - col.column: Column name to visualize
8
+ #
9
+ # Expected parent variables:
10
+ # - model: Table/model name
11
+ # - connection: DuckDB file path or :memory:
12
+
13
+ title: "{{ col.column }}"
14
+
15
+ queries:
16
+ daily_counts:
17
+ source:
18
+ type: duckdb
19
+ path: "{{ connection }}"
20
+ sql: |
21
+ SELECT
22
+ {{ col.column }}::DATE as date,
23
+ COUNT(*) as count
24
+ FROM {{ model }}
25
+ WHERE {{ col.column }} IS NOT NULL
26
+ GROUP BY {{ col.column }}::DATE
27
+ ORDER BY date
28
+
29
+ charts:
30
+ time_series:
31
+ title: "{{ col.column }} Over Time"
32
+ type: line
33
+ query: daily_counts
34
+ x: date
35
+ y: count
36
+ x_label: Date
37
+ y_label: Count
38
+
39
+ rows:
40
+ - time_series