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,710 @@
1
+ """Unified query interface types — compiled representation.
2
+
3
+ Stage: COMPILE (Output) / EXECUTE (Input)
4
+ Purpose: Define type-safe query classes with a unified interface.
5
+
6
+ This module implements the unified query interface pattern where:
7
+ - All queries inherit from a common Query base class
8
+ - Each query type has its own class with type-specific fields
9
+ - Type guards enable type-safe narrowing in adapter code
10
+ - The interface makes adapter routing clear and type-safe
11
+
12
+ Benefits:
13
+ - Type safety: Compile-time verification of query fields
14
+ - Self-documenting: Each query class shows exactly which fields apply
15
+ - Extensibility: Adding new query types is isolated
16
+ - IDE support: Autocomplete and refactoring work correctly
17
+
18
+ Dependencies:
19
+ - pydantic (BaseModel)
20
+ - abc (ABC, abstractmethod)
21
+
22
+ See also:
23
+ - execute/adapters/base.py: Adapters consume these types
24
+ """
25
+
26
+ import hashlib
27
+ import json
28
+ from abc import ABC, abstractmethod
29
+ from typing import Any, TypeGuard
30
+
31
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
32
+
33
+ from dataface.core.compile.models.query.authored import Pivot, RestMethod, TimeGrain
34
+
35
+ # Note: source field can be a string reference or inline Dict config
36
+
37
+
38
+ # ============================================================================
39
+ # BASE QUERY CLASS
40
+ # ============================================================================
41
+
42
+
43
+ class Query(BaseModel, ABC):
44
+ """Base query interface - all queries return tabular data.
45
+
46
+ This abstract base class defines the common interface for all query types.
47
+ Subclasses must implement query_type and source_description properties.
48
+
49
+ Common Fields (available on all queries):
50
+ filters: Optional filter conditions to apply to results
51
+ limit: Optional maximum number of rows to return
52
+
53
+ Abstract Properties:
54
+ query_type: Type identifier for adapter routing (e.g., "sql", "csv")
55
+ source_description: Human-readable description of data source
56
+
57
+ Example:
58
+ >>> class CustomQuery(Query):
59
+ ... custom_field: str
60
+ ...
61
+ ... @property
62
+ ... def query_type(self) -> str:
63
+ ... return "custom"
64
+ ...
65
+ ... @property
66
+ ... def source_description(self) -> str:
67
+ ... return f"Custom: {self.custom_field}"
68
+ """
69
+
70
+ # Common fields all queries can have
71
+ description: str | None = Field(
72
+ default=None, description="Human-readable description of this query."
73
+ )
74
+ filters: dict[str, Any] | None = Field(
75
+ default=None, description="Filter conditions applied to query results."
76
+ )
77
+ limit: int | None = Field(
78
+ default=None, description="Maximum number of rows to return."
79
+ )
80
+
81
+ # Query-level pivot rendering hint.
82
+ # Note: pivot is a table-renderer hint (cross-tab layout), not a SQL transformation.
83
+ # Chart consumers receive long-form rows; only render_table_svg consumes this field.
84
+ # This lives on the compiled Query base (not just types.Query) so it survives
85
+ # normalize_query's model_dump → SqlQuery(**dict) round-trip.
86
+ pivot: Pivot | None = Field(
87
+ default=None,
88
+ description="Cross-tab pivot hint: column values become headers, value fills cells.",
89
+ )
90
+
91
+ # Diagnostic suppression — list of diagnostic codes to suppress
92
+ ignore: list[str] | None = Field(
93
+ default=None,
94
+ description="Diagnostic codes to suppress (e.g., ['fanout_risk', 'reaggregation']).",
95
+ )
96
+
97
+ # Variable dependencies - computed during normalization
98
+ variable_dependencies: set[str] = Field(
99
+ default_factory=set,
100
+ description="Variable names this query references in SQL/filters (computed during normalization).",
101
+ )
102
+
103
+ model_config = ConfigDict(extra="forbid")
104
+
105
+ @property
106
+ @abstractmethod
107
+ def query_type(self) -> str:
108
+ """Type identifier for adapter routing.
109
+
110
+ Returns:
111
+ String identifying the query type (e.g., "sql", "csv", "http")
112
+ """
113
+ pass
114
+
115
+ @property
116
+ @abstractmethod
117
+ def source_description(self) -> str:
118
+ """Human-readable description of data source.
119
+
120
+ Returns:
121
+ String describing the data source for debugging/logging
122
+ """
123
+ pass
124
+
125
+ def apply_limit(self, data: list[dict[str, Any]]) -> list[dict[str, Any]]:
126
+ """Apply limit to result data.
127
+
128
+ Args:
129
+ data: Query results as list of dicts
130
+
131
+ Returns:
132
+ Possibly truncated list of dicts
133
+ """
134
+ if self.limit and self.limit > 0:
135
+ return data[: self.limit]
136
+ return data
137
+
138
+
139
+ # ============================================================================
140
+ # TYPE-SPECIFIC QUERY CLASSES
141
+ # ============================================================================
142
+
143
+
144
+ class SqlQuery(Query):
145
+ """SQL query against a database.
146
+
147
+ Executes raw SQL against the configured database connection.
148
+ Supports Jinja templating for variable substitution.
149
+
150
+ Required Fields:
151
+ sql: SQL query string (may contain Jinja templates)
152
+
153
+ Connection Fields (one required):
154
+ source: Source name or inline config
155
+
156
+ Optional Fields:
157
+ target: dbt target name (defaults to 'dev' if not specified)
158
+
159
+ Example:
160
+ >>> query = SqlQuery(
161
+ ... sql="SELECT * FROM users WHERE id = {{ user_id }}",
162
+ ... source="my_postgres"
163
+ ... )
164
+ >>> query.query_type # "sql"
165
+ >>> query.source_description # "SQL: SELECT * FROM users WHERE id = {{ user_..."
166
+ """
167
+
168
+ sql: str = Field(
169
+ description="SQL query string. May contain Jinja templates (e.g., {{ variable_name }})."
170
+ )
171
+ setup_sql: str | None = Field(
172
+ default=None,
173
+ description="Non-nestable SQL preamble (e.g., CREATE TEMP FUNCTION). Runs before the main query.",
174
+ )
175
+ # Connection configuration
176
+ source: str | dict[str, Any] | None = Field(
177
+ default=None,
178
+ description="Source name or inline source config dict for the database connection.",
179
+ )
180
+ target: str | None = Field(
181
+ default=None,
182
+ description="dbt target name (defaults to 'dev' if not specified).",
183
+ )
184
+
185
+ @property
186
+ def query_type(self) -> str:
187
+ """Return 'sql' as query type."""
188
+ return "sql"
189
+
190
+ @property
191
+ def source_description(self) -> str:
192
+ """Return truncated SQL for description."""
193
+ sql_preview = self.sql[:50] if len(self.sql) > 50 else self.sql
194
+ return f"SQL: {sql_preview}..."
195
+
196
+
197
+ class CsvQuery(Query):
198
+ """Query data from a CSV file.
199
+
200
+ Loads and queries data from CSV files. Supports column selection,
201
+ filtering, and limiting results.
202
+
203
+ Fields (one required):
204
+ file: Path to CSV file (relative to project root or absolute)
205
+ source: Source reference or inline CSV source config
206
+
207
+ Optional Fields:
208
+ columns: List of columns to select (default: all)
209
+ filter: Dict of column->value for exact match filtering
210
+
211
+ Example:
212
+ >>> query = CsvQuery(file="data/sales.csv", columns=["date", "amount"])
213
+ >>> query.query_type # "csv"
214
+ >>> query.source_description # "CSV: data/sales.csv"
215
+ """
216
+
217
+ file: str | None = Field(
218
+ default=None,
219
+ description="Path to CSV file (relative to project root or absolute). Use 'source' for named sources.",
220
+ )
221
+ source: str | dict[str, Any] | None = Field(
222
+ default=None, description="Source name or inline CSV source config dict."
223
+ )
224
+ columns: list[str] | None = Field(
225
+ default=None, description="List of columns to select. Default: all columns."
226
+ )
227
+ filter: dict[str, Any] | None = Field(
228
+ default=None, description="Dict of column->value for exact match row filtering."
229
+ )
230
+ # Additional CSV options
231
+ delimiter: str | None = Field(
232
+ default=None, description="Column delimiter character. Default: comma (',')."
233
+ )
234
+ encoding: str | None = Field(
235
+ default=None, description="File encoding. Default: UTF-8."
236
+ )
237
+
238
+ @property
239
+ def query_type(self) -> str:
240
+ """Return 'csv' as query type."""
241
+ return "csv"
242
+
243
+ @property
244
+ def source_description(self) -> str:
245
+ """Return file path for description."""
246
+ if self.file:
247
+ return f"CSV: {self.file}"
248
+ elif isinstance(self.source, str):
249
+ return f"CSV source: {self.source}"
250
+ elif isinstance(self.source, dict) and "file" in self.source:
251
+ return f"CSV: {self.source['file']}"
252
+ return "CSV query"
253
+
254
+ @property
255
+ def effective_file(self) -> str | None:
256
+ """Get effective file path from file or source config."""
257
+ if self.file:
258
+ return self.file
259
+ if isinstance(self.source, dict) and "file" in self.source:
260
+ return self.source["file"]
261
+ return None
262
+
263
+
264
+ class MetricFlowQuery(Query):
265
+ """Query the dbt Semantic Layer.
266
+
267
+ Executes queries against dbt's Semantic Layer using MetricFlow.
268
+ Supports metrics, dimensions, filters, and time grains.
269
+
270
+ Required Fields:
271
+ metrics: List of metric names to query
272
+
273
+ Optional Fields:
274
+ dimensions: List of dimension names to group by
275
+ time_grain: Time grain for time-based dimensions (day, week, month, etc.)
276
+
277
+ Example:
278
+ >>> query = MetricFlowQuery(
279
+ ... metrics=["revenue", "orders"],
280
+ ... dimensions=["date_day", "region"]
281
+ ... )
282
+ >>> query.query_type # "metricflow"
283
+ >>> query.source_description # "MetricFlow: revenue, orders"
284
+ """
285
+
286
+ metrics: list[str] = Field(
287
+ description="Metric names to query from the dbt Semantic Layer."
288
+ )
289
+ dimensions: list[str] | None = Field(
290
+ default=None,
291
+ description="Dimension names to group by (e.g., 'date_day', 'region').",
292
+ )
293
+ time_grain: TimeGrain | None = Field(
294
+ default=None,
295
+ description="Time grain for time-based dimensions (day, week, month, quarter, year).",
296
+ )
297
+ source: str | dict[str, Any] | None = Field(
298
+ default=None, description="Source name or inline MetricFlow source config."
299
+ )
300
+
301
+ @property
302
+ def query_type(self) -> str:
303
+ """Return 'metricflow' as query type."""
304
+ return "metricflow"
305
+
306
+ @property
307
+ def source_description(self) -> str:
308
+ """Return metrics list for description."""
309
+ return f"MetricFlow: {', '.join(self.metrics)}"
310
+
311
+
312
+ class HttpQuery(Query):
313
+ """Query data from an HTTP API.
314
+
315
+ Fetches data from REST API endpoints. Supports various HTTP methods,
316
+ headers, query parameters, and request bodies.
317
+
318
+ Required Fields:
319
+ url: Full URL of the API endpoint
320
+
321
+ Optional Fields:
322
+ method: HTTP method (default: GET)
323
+ headers: Request headers
324
+ params: Query parameters
325
+ body: Request body (for POST/PUT/PATCH)
326
+ json_path: JSONPath expression to extract data from response
327
+
328
+ Example:
329
+ >>> query = HttpQuery(
330
+ ... url="https://api.example.com/predict",
331
+ ... method="POST",
332
+ ... body={"input": "data"}
333
+ ... )
334
+ >>> query.query_type # "http"
335
+ >>> query.source_description # "HTTP: POST https://api.example.com/predict"
336
+ """
337
+
338
+ url: str = Field(
339
+ description="Full URL of the HTTP endpoint.",
340
+ )
341
+ method: RestMethod = Field(
342
+ default="GET",
343
+ description="HTTP method (GET, POST, PUT, DELETE, PATCH). Default: GET.",
344
+ )
345
+ headers: dict[str, str] | None = Field(
346
+ default=None, description="Request headers (merged with source-level headers)."
347
+ )
348
+ params: dict[str, Any] | None = Field(
349
+ default=None, description="URL query parameters."
350
+ )
351
+ body: dict[str, Any] | str | None = Field(
352
+ default=None,
353
+ description="Request body for POST/PUT/PATCH (dict or JSON string).",
354
+ )
355
+ json_path: str | None = Field(
356
+ default=None,
357
+ description="JSONPath expression to extract tabular data from the response.",
358
+ )
359
+
360
+ @property
361
+ def query_type(self) -> str:
362
+ """Return 'http' as query type."""
363
+ return "http"
364
+
365
+ @property
366
+ def source_description(self) -> str:
367
+ """Return method and URL for description."""
368
+ return f"HTTP: {self.method} {self.url}"
369
+
370
+
371
+ class ValuesQuery(Query):
372
+ """Inline data query with rows embedded directly in the YAML.
373
+
374
+ Provides two syntaxes:
375
+ 1. Dict rows (verbose): rows: [{month: "Jan", revenue: 100}, ...]
376
+ 2. Columns + values (compact, SQL-style): columns: [month, revenue], values: [["Jan", 100], ...]
377
+
378
+ When using columns + values, rows are computed automatically via model_post_init.
379
+
380
+ Example (dict rows):
381
+ >>> query = ValuesQuery(rows=[{"month": "Jan", "revenue": 100}])
382
+
383
+ Example (columns + values):
384
+ >>> query = ValuesQuery(
385
+ ... columns=["month", "revenue"],
386
+ ... values=[["Jan", 100], ["Feb", 140]],
387
+ ... )
388
+ >>> query.rows # [{"month": "Jan", "revenue": 100}, {"month": "Feb", "revenue": 140}]
389
+ """
390
+
391
+ rows: list[dict[str, Any]] = Field(
392
+ default_factory=list,
393
+ description="Inline data rows as a list of dicts (e.g., [{month: 'Jan', revenue: 100}]).",
394
+ )
395
+ columns: list[str] | None = Field(
396
+ default=None, description="Column names for compact 'columns + values' syntax."
397
+ )
398
+ values: list[list[Any]] | None = Field(
399
+ default=None,
400
+ description="Row values for compact syntax, parallel to 'columns' (e.g., [['Jan', 100], ['Feb', 140]]).",
401
+ )
402
+
403
+ def model_post_init(self, _context: Any) -> None:
404
+ """Build rows from columns + values if provided."""
405
+ if self.columns and self.values is not None and not self.rows:
406
+ ncols = len(self.columns)
407
+ for i, row in enumerate(self.values):
408
+ if len(row) != ncols:
409
+ raise ValueError(
410
+ f"Values row {i} has {len(row)} items, "
411
+ f"expected {ncols} (columns: {self.columns})"
412
+ )
413
+ self.rows = [
414
+ dict(zip(self.columns, row, strict=True)) for row in self.values
415
+ ]
416
+
417
+ @property
418
+ def query_type(self) -> str:
419
+ """Return 'values' as query type."""
420
+ return "values"
421
+
422
+ @property
423
+ def source_description(self) -> str:
424
+ """Return content-based description for caching.
425
+
426
+ Includes a hash of actual row data so that distinct ValuesQuery
427
+ instances with the same row count produce different cache keys.
428
+ Cached after first computation since rows are set once in model_post_init.
429
+ """
430
+ if not hasattr(self, "_cached_source_description"):
431
+ n = len(self.rows)
432
+ content_hash = hashlib.sha256(
433
+ json.dumps(self.rows, sort_keys=True, default=str).encode()
434
+ ).hexdigest()[:12]
435
+ object.__setattr__(
436
+ self,
437
+ "_cached_source_description",
438
+ f"Values: {n} {'row' if n == 1 else 'rows'} [{content_hash}]",
439
+ )
440
+ return self._cached_source_description # type: ignore[attr-defined]
441
+
442
+
443
+ class DbtModelQuery(Query):
444
+ """Query against a dbt model.
445
+
446
+ Executes queries against a dbt model, using the model's compiled SQL
447
+ or the model's relation name for direct querying.
448
+
449
+ Required Fields:
450
+ model: Name of the dbt model
451
+
452
+ Optional Fields:
453
+ columns: List of columns to select (default: all)
454
+
455
+ Example:
456
+ >>> query = DbtModelQuery(model="stg_customers", columns=["id", "name"])
457
+ >>> query.query_type # "dbt_model"
458
+ >>> query.source_description # "dbt Model: stg_customers"
459
+ """
460
+
461
+ model: str = Field(
462
+ description="Name of the dbt model to query (e.g., 'stg_customers', 'fct_orders')."
463
+ )
464
+ columns: list[str] | None = Field(
465
+ default=None,
466
+ description="Columns to select from the model. Default: all columns.",
467
+ )
468
+
469
+ @property
470
+ def query_type(self) -> str:
471
+ """Return 'dbt_model' as query type."""
472
+ return "dbt_model"
473
+
474
+ @property
475
+ def source_description(self) -> str:
476
+ """Return model name for description."""
477
+ return f"dbt Model: {self.model}"
478
+
479
+
480
+ class SchemaResolverQuery(Query):
481
+ """Query the LayeredSchemaResolver in-process.
482
+
483
+ Dispatches to list_schemas / list_tables / profile_table / profile_column
484
+ based on which optional fields are populated:
485
+ - source only → list_schemas(source)
486
+ - source + schema → list_tables(source, schema)
487
+ - source + schema + table → profile_table(source, schema, table) → column rows
488
+ - source + schema + table + column → profile_column(source, schema, table, column)
489
+
490
+ Note: The YAML field is ``schema`` but the Python attribute is ``schema_name``
491
+ to avoid shadowing ``BaseModel.schema`` (a Pydantic v2 legacy classmethod).
492
+
493
+ Fields must be literal strings — Jinja templates (``{{``, ``{%``, ``{#``)
494
+ are rejected at model construction time. Field prerequisites are also
495
+ validated at construction time: ``table`` requires ``schema``; ``column``
496
+ requires both ``schema`` and ``table``.
497
+ """
498
+
499
+ source: str = Field(description="dbt source name to query.")
500
+ # alias="schema" so YAML authors write `schema: analytics`; Python code uses
501
+ # query.schema_name to avoid the BaseModel.schema classmethod conflict.
502
+ schema_name: str | None = Field(
503
+ default=None, alias="schema", description="Schema name (omit to list schemas)."
504
+ )
505
+ table: str | None = Field(
506
+ default=None, description="Table name (omit to list tables)."
507
+ )
508
+ column: str | None = Field(
509
+ default=None, description="Column name (omit for full table profile)."
510
+ )
511
+
512
+ # populate_by_name=True: 'schema' is a Pydantic BaseModel class method name
513
+ # (BaseModel.schema() in v1 / model_json_schema() in v2), so using it as a
514
+ # field name directly would shadow the classmethod. The Python field is
515
+ # `schema_name`; YAML authors write `schema:` and the alias bridges the two.
516
+ model_config = ConfigDict(extra="forbid", populate_by_name=True)
517
+
518
+ @field_validator("source", "schema_name", "table", "column", mode="before")
519
+ @classmethod
520
+ def _no_jinja(cls, v: Any) -> Any:
521
+ if isinstance(v, str) and any(tok in v for tok in ("{{", "{%", "{#")):
522
+ raise ValueError(
523
+ "schema_resolver fields must be literal strings; "
524
+ "Jinja templates ({{ }}, {% %}, {# #}) are not supported."
525
+ )
526
+ return v
527
+
528
+ @model_validator(mode="after")
529
+ def _field_prerequisites(self) -> "SchemaResolverQuery":
530
+ if self.table is not None and self.schema_name is None:
531
+ raise ValueError("schema_resolver: 'table' requires 'schema' to be set")
532
+ if self.column is not None and self.table is None:
533
+ raise ValueError(
534
+ "schema_resolver: 'column' requires 'table' (and 'schema') to be set"
535
+ )
536
+ return self
537
+
538
+ @property
539
+ def query_type(self) -> str:
540
+ return "schema_resolver"
541
+
542
+ @property
543
+ def source_description(self) -> str:
544
+ parts = [self.source]
545
+ if self.schema_name:
546
+ parts.append(self.schema_name)
547
+ if self.table:
548
+ parts.append(self.table)
549
+ if self.column:
550
+ parts.append(self.column)
551
+ return f"schema_resolver: {'.'.join(parts)}"
552
+
553
+
554
+ # ============================================================================
555
+ # TYPE ALIASES
556
+ # ============================================================================
557
+
558
+
559
+ # Type alias for any query - union of all query types
560
+ # Use this for type annotations when you want to accept any query type
561
+ AnyQuery = (
562
+ SqlQuery
563
+ | CsvQuery
564
+ | MetricFlowQuery
565
+ | HttpQuery
566
+ | DbtModelQuery
567
+ | ValuesQuery
568
+ | SchemaResolverQuery
569
+ )
570
+
571
+
572
+ # Set of valid query type strings
573
+ VALID_QUERY_TYPES: set[str] = {
574
+ "sql",
575
+ "csv",
576
+ "metricflow",
577
+ "http",
578
+ "dbt_model",
579
+ "values",
580
+ "schema_resolver",
581
+ }
582
+
583
+
584
+ # ============================================================================
585
+ # TYPE GUARDS
586
+ # ============================================================================
587
+
588
+
589
+ def is_sql_query(query: AnyQuery) -> TypeGuard[SqlQuery]:
590
+ """Type guard for SQL queries.
591
+
592
+ Use this for type-safe narrowing in conditional blocks.
593
+ After this check, the type checker knows the query is SqlQuery.
594
+
595
+ Args:
596
+ query: Any compiled query
597
+
598
+ Returns:
599
+ True if query is a SqlQuery
600
+
601
+ Example:
602
+ >>> if is_sql_query(query):
603
+ ... # Type checker knows query.sql exists
604
+ ... print(query.sql)
605
+ """
606
+ return query.query_type == "sql"
607
+
608
+
609
+ def is_csv_query(query: AnyQuery) -> TypeGuard[CsvQuery]:
610
+ """Type guard for CSV queries.
611
+
612
+ Use this for type-safe narrowing in conditional blocks.
613
+ After this check, the type checker knows the query is CsvQuery.
614
+
615
+ Args:
616
+ query: Any compiled query
617
+
618
+ Returns:
619
+ True if query is a CsvQuery
620
+
621
+ Example:
622
+ >>> if is_csv_query(query):
623
+ ... # Type checker knows query.file exists
624
+ ... print(query.file)
625
+ """
626
+ return query.query_type == "csv"
627
+
628
+
629
+ def is_metricflow_query(query: AnyQuery) -> TypeGuard[MetricFlowQuery]:
630
+ """Type guard for MetricFlow queries.
631
+
632
+ Use this for type-safe narrowing in conditional blocks.
633
+ After this check, the type checker knows the query is MetricFlowQuery.
634
+
635
+ Args:
636
+ query: Any compiled query
637
+
638
+ Returns:
639
+ True if query is a MetricFlowQuery
640
+
641
+ Example:
642
+ >>> if is_metricflow_query(query):
643
+ ... # Type checker knows query.metrics exists
644
+ ... print(query.metrics)
645
+ """
646
+ return query.query_type == "metricflow"
647
+
648
+
649
+ def is_http_query(query: AnyQuery) -> TypeGuard[HttpQuery]:
650
+ """Type guard for HTTP queries.
651
+
652
+ Use this for type-safe narrowing in conditional blocks.
653
+ After this check, the type checker knows the query is HttpQuery.
654
+
655
+ Args:
656
+ query: Any compiled query
657
+
658
+ Returns:
659
+ True if query is a HttpQuery
660
+
661
+ Example:
662
+ >>> if is_http_query(query):
663
+ ... # Type checker knows query.url exists
664
+ ... print(query.url)
665
+ """
666
+ return query.query_type == "http"
667
+
668
+
669
+ def is_dbt_model_query(query: AnyQuery) -> TypeGuard[DbtModelQuery]:
670
+ """Type guard for dbt model queries.
671
+
672
+ Use this for type-safe narrowing in conditional blocks.
673
+ After this check, the type checker knows the query is DbtModelQuery.
674
+
675
+ Args:
676
+ query: Any compiled query
677
+
678
+ Returns:
679
+ True if query is a DbtModelQuery
680
+
681
+ Example:
682
+ >>> if is_dbt_model_query(query):
683
+ ... # Type checker knows query.model exists
684
+ ... print(query.model)
685
+ """
686
+ return query.query_type == "dbt_model"
687
+
688
+
689
+ def is_values_query(query: AnyQuery) -> TypeGuard[ValuesQuery]:
690
+ """Type guard for values queries.
691
+
692
+ Args:
693
+ query: Any compiled query
694
+
695
+ Returns:
696
+ True if query is a ValuesQuery
697
+ """
698
+ return query.query_type == "values"
699
+
700
+
701
+ def is_schema_resolver_query(query: AnyQuery) -> TypeGuard[SchemaResolverQuery]:
702
+ """Type guard for schema resolver queries.
703
+
704
+ Args:
705
+ query: Any compiled query
706
+
707
+ Returns:
708
+ True if query is a SchemaResolverQuery
709
+ """
710
+ return query.query_type == "schema_resolver"