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,362 @@
1
+ """Theme configuration types and canonical theme normalization.
2
+
3
+ Stage: COMPILE
4
+ Purpose: Define the Dataface-facing theme schema and map it to Vega-Lite config.
5
+
6
+ The public authoring surface is a canonical Dataface theme model:
7
+ - top-level shared tokens
8
+ - `charts:` for chart defaults and chart-type overrides
9
+
10
+ Vega-Lite remains an implementation detail. Raw Vega config still passes through as
11
+ an escape hatch, but `to_vega_config()` is the boundary that maps canonical theme
12
+ values into Vega-Lite config for rendering.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from collections.abc import Mapping
18
+ from copy import deepcopy
19
+ from typing import Any
20
+
21
+ from pydantic import BaseModel, ConfigDict, model_validator
22
+
23
+ from dataface.core.compile.models.vega_lite.config import (
24
+ AxisConfig,
25
+ LegendConfig,
26
+ MarkConfig,
27
+ RangeConfig,
28
+ TitleConfig,
29
+ ViewConfig,
30
+ )
31
+
32
+
33
+ def _deep_merge(base: dict[str, Any], overlay: Mapping[str, Any]) -> dict[str, Any]:
34
+ merged = deepcopy(base)
35
+ for key, value in overlay.items():
36
+ if (
37
+ key in merged
38
+ and isinstance(merged[key], dict)
39
+ and isinstance(value, Mapping)
40
+ ):
41
+ merged[key] = _deep_merge(merged[key], dict(value))
42
+ else:
43
+ merged[key] = deepcopy(value)
44
+ return merged
45
+
46
+
47
+ def _to_plain_vega_value(value: object) -> object:
48
+ """Recursively convert pydantic models and mapping-like values to plain Python."""
49
+ if isinstance(value, BaseModel):
50
+ data: dict[str, object] = {}
51
+
52
+ for key in type(value).model_fields:
53
+ field_value = getattr(value, key, None)
54
+ if field_value is not None:
55
+ data[key] = _to_plain_vega_value(field_value)
56
+
57
+ extra = getattr(value, "model_extra", None) or {}
58
+ for key, extra_value in extra.items():
59
+ if extra_value is not None:
60
+ data[key] = _to_plain_vega_value(extra_value)
61
+
62
+ return data
63
+
64
+ if isinstance(value, dict):
65
+ return {
66
+ key: _to_plain_vega_value(item)
67
+ for key, item in value.items()
68
+ if item is not None
69
+ }
70
+
71
+ if isinstance(value, list):
72
+ return [_to_plain_vega_value(item) for item in value]
73
+
74
+ return value
75
+
76
+
77
+ class ThemeTokens(BaseModel):
78
+ """Canonical shared theme tokens."""
79
+
80
+ model_config = ConfigDict(extra="forbid")
81
+
82
+ background: str | None = None
83
+ color: str | None = None
84
+ muted: str | None = None
85
+ border_color: str | None = None
86
+ accent_color: str | None = None
87
+ title_color: str | None = None
88
+
89
+
90
+ class ThemeMarkConfig(BaseModel):
91
+ model_config = ConfigDict(extra="forbid")
92
+
93
+ color: str | None = None
94
+
95
+
96
+ class ThemeTableStripeConfig(BaseModel):
97
+ model_config = ConfigDict(extra="forbid")
98
+
99
+ color: str | None = None
100
+
101
+
102
+ class ThemeTableConfig(ThemeTokens):
103
+ header_background: str | None = None
104
+ header_color: str | None = None
105
+ stripe: ThemeTableStripeConfig | None = None
106
+
107
+
108
+ class ThemeChartsConfig(ThemeTokens):
109
+ palette: list[str] | None = None
110
+ dashes: list[list[int]] | None = None
111
+ table: ThemeTableConfig | None = None
112
+ bar: ThemeMarkConfig | None = None
113
+ arc: ThemeMarkConfig | None = None
114
+ area: ThemeMarkConfig | None = None
115
+ line: ThemeMarkConfig | None = None
116
+ path: ThemeMarkConfig | None = None
117
+ point: ThemeMarkConfig | None = None
118
+ rect: ThemeMarkConfig | None = None
119
+ shape: ThemeMarkConfig | None = None
120
+ symbol: ThemeMarkConfig | None = None
121
+
122
+
123
+ class ThemeConfig(ThemeTokens):
124
+ """Canonical Dataface theme plus optional raw Vega-Lite config overrides.
125
+
126
+ Typed fields (view, title, axis, legend, range, mark-type configs) cover the
127
+ most common VL config sections and validate their contents strictly.
128
+
129
+ top-level extra="allow" is a deliberate VL escape hatch: any Vega-Lite config
130
+ key not listed as a typed field passes through as-is to to_vega_config(). This
131
+ covers sections Dataface does not enumerate typed fields for — axisBand, axisX,
132
+ axisY, axisQuantitative, axisOrdinal, headerRow, headerColumn, projectionConfig,
133
+ scale, and others. Closing this surface would require enumerating every Vega-Lite
134
+ config section, which is out of scope and creates unnecessary churn as VL evolves.
135
+
136
+ Contrast with vega.config (VegaRuntimeConfig.config: VegaLiteConfig) — that path
137
+ is closed (extra="forbid") because it represents the Dataface-emitted style output,
138
+ not the user-authored theme surface.
139
+
140
+ Note: unknown keys in theme YAML are silently accepted (no typo detection). This
141
+ is the accepted trade-off for an escape hatch surface.
142
+ """
143
+
144
+ # VL theme escape hatch — intentional, not a migration shim. See class docstring.
145
+ model_config = ConfigDict(extra="allow")
146
+
147
+ charts: ThemeChartsConfig | None = None
148
+
149
+ # Raw Vega-Lite override surface / pass-through.
150
+ view: ViewConfig | None = None
151
+ title: TitleConfig | None = None
152
+ axis: AxisConfig | None = None
153
+ legend: LegendConfig | None = None
154
+ range: RangeConfig | None = None
155
+ bar: MarkConfig | None = None
156
+ arc: MarkConfig | None = None
157
+ line: MarkConfig | None = None
158
+ area: MarkConfig | None = None
159
+ rect: MarkConfig | None = None
160
+ symbol: MarkConfig | None = None
161
+ point: MarkConfig | None = None
162
+ shape: MarkConfig | None = None
163
+ path: MarkConfig | None = None
164
+
165
+ @model_validator(mode="before")
166
+ @classmethod
167
+ def _reject_legacy_dataface_keys(cls, value: object) -> object:
168
+ if not isinstance(value, Mapping):
169
+ return value
170
+
171
+ legacy_root_keys = {
172
+ "table",
173
+ "variable",
174
+ "variables",
175
+ "borderColor",
176
+ "accentColor",
177
+ "titleColor",
178
+ }
179
+ present_legacy_keys = sorted(key for key in legacy_root_keys if key in value)
180
+ if present_legacy_keys:
181
+ invalid_keys = ", ".join(present_legacy_keys)
182
+ raise ValueError(
183
+ "Theme config uses unsupported legacy Dataface keys: "
184
+ f"{invalid_keys}. Use snake_case canonical keys instead."
185
+ )
186
+
187
+ return value
188
+
189
+ def resolved_top_level(self) -> ThemeTokens:
190
+ return ThemeTokens.model_validate(
191
+ self.model_dump(include=set(ThemeTokens.model_fields))
192
+ )
193
+
194
+ def resolved_charts(self) -> ThemeChartsConfig:
195
+ base = self.resolved_top_level().model_dump(exclude_none=True)
196
+ overlay = (
197
+ self.charts.model_dump(exclude_none=True) if self.charts is not None else {}
198
+ )
199
+ return ThemeChartsConfig.model_validate(_deep_merge(base, overlay))
200
+
201
+ def resolved_table(self) -> ThemeTableConfig:
202
+ charts = self.resolved_charts()
203
+ base = charts.model_dump(
204
+ include=set(ThemeTokens.model_fields),
205
+ exclude_none=True,
206
+ )
207
+ overlay = (
208
+ charts.table.model_dump(exclude_none=True)
209
+ if charts.table is not None
210
+ else {}
211
+ )
212
+ return ThemeTableConfig.model_validate(_deep_merge(base, overlay))
213
+
214
+ def _generated_vega_config(self) -> dict[str, Any]:
215
+ charts = self.resolved_charts()
216
+ generated: dict[str, Any] = {}
217
+
218
+ if self.background is not None:
219
+ generated["background"] = self.background
220
+
221
+ title_color = charts.title_color
222
+ if title_color is not None:
223
+ generated["title"] = {"color": title_color}
224
+ generated["axis"] = {
225
+ "labelColor": charts.muted,
226
+ "titleColor": title_color,
227
+ "gridColor": charts.border_color,
228
+ "tickColor": charts.border_color,
229
+ "domainColor": charts.border_color,
230
+ }
231
+ generated["legend"] = {
232
+ "labelColor": charts.muted,
233
+ "titleColor": title_color,
234
+ }
235
+ elif charts.muted is not None or charts.border_color is not None:
236
+ generated["axis"] = {
237
+ "labelColor": charts.muted,
238
+ "gridColor": charts.border_color,
239
+ "tickColor": charts.border_color,
240
+ "domainColor": charts.border_color,
241
+ }
242
+ generated["legend"] = {"labelColor": charts.muted}
243
+
244
+ if charts.palette is not None:
245
+ generated["range"] = {"category": charts.palette}
246
+
247
+ default_mark_color = charts.accent_color
248
+ mark_map = {
249
+ "bar": ("fill", charts.bar.color if charts.bar else None),
250
+ "arc": ("fill", charts.arc.color if charts.arc else None),
251
+ "area": ("fill", charts.area.color if charts.area else None),
252
+ "line": ("stroke", charts.line.color if charts.line else None),
253
+ "path": ("stroke", charts.path.color if charts.path else None),
254
+ "point": ("fill", charts.point.color if charts.point else None),
255
+ "rect": ("fill", charts.rect.color if charts.rect else None),
256
+ "shape": ("stroke", charts.shape.color if charts.shape else None),
257
+ "symbol": ("fill", charts.symbol.color if charts.symbol else None),
258
+ }
259
+ for key, (field, value) in mark_map.items():
260
+ color = value or default_mark_color
261
+ if color is not None:
262
+ generated[key] = {field: color}
263
+
264
+ return _to_plain_vega_value(generated) # type: ignore[return-value]
265
+
266
+ def to_vega_config(self) -> dict:
267
+ """Export theme as Vega-Lite config.
268
+
269
+ Canonical Dataface theme tokens synthesize the common Vega-Lite config.
270
+ Any explicit raw Vega-Lite keys on the theme override the generated values.
271
+ """
272
+ raw = _to_plain_vega_value(self)
273
+ if not isinstance(raw, dict):
274
+ raise TypeError("ThemeConfig must serialize to a dictionary")
275
+
276
+ # Strip Dataface-internal keys that must not appear in Vega-Lite config.
277
+ # "table", "variable", "variables" are rejected by the model validator so
278
+ # they cannot reach here; the remaining keys are valid model fields that
279
+ # must be excluded from Vega output.
280
+ for key in (
281
+ "color",
282
+ "muted",
283
+ "border_color",
284
+ "accent_color",
285
+ "title_color",
286
+ "charts",
287
+ ):
288
+ raw.pop(key, None)
289
+
290
+ generated = self._generated_vega_config()
291
+ if not isinstance(generated, dict):
292
+ raise TypeError("Generated theme config must serialize to a dictionary")
293
+ return _deep_merge(generated, raw)
294
+
295
+
296
+ def normalize_raw_theme_config(raw: Mapping[str, Any]) -> dict[str, Any]:
297
+ """Normalize raw theme dictionaries into the canonical theme surface."""
298
+ data = deepcopy(dict(raw))
299
+
300
+ def _mapping(name: str) -> Mapping[str, Any]:
301
+ value = data.get(name)
302
+ return value if isinstance(value, Mapping) else {}
303
+
304
+ axis = _mapping("axis")
305
+ legend = _mapping("legend")
306
+ title = _mapping("title")
307
+ range_config = _mapping("range")
308
+ charts = _mapping("charts")
309
+ table_raw = charts.get("table")
310
+ table: Mapping[str, Any] = table_raw if isinstance(table_raw, Mapping) else {}
311
+
312
+ mark_sources = (
313
+ ("bar", "fill"),
314
+ ("arc", "fill"),
315
+ ("line", "stroke"),
316
+ ("area", "fill"),
317
+ ("rect", "fill"),
318
+ ("symbol", "fill"),
319
+ ("point", "fill"),
320
+ ("shape", "stroke"),
321
+ ("path", "stroke"),
322
+ )
323
+ accent = None
324
+ chart_marks: dict[str, Any] = {}
325
+ for mark, field in mark_sources:
326
+ mark_value = _mapping(mark)
327
+ color = mark_value.get(field)
328
+ if color and accent is None:
329
+ accent = color
330
+ if color:
331
+ chart_marks[mark] = {"color": color}
332
+ if accent is None and isinstance(range_config.get("category"), list):
333
+ accent = range_config["category"][0] if range_config["category"] else None
334
+
335
+ derived_tokens: dict[str, Any] = {
336
+ "background": data.get("background"),
337
+ "color": table.get("color") or title.get("color"),
338
+ "muted": axis.get("labelColor") or legend.get("labelColor"),
339
+ "border_color": axis.get("gridColor")
340
+ or axis.get("tickColor")
341
+ or axis.get("domainColor"),
342
+ "accent_color": accent,
343
+ "title_color": table.get("header_color")
344
+ or axis.get("titleColor")
345
+ or title.get("color")
346
+ or legend.get("titleColor"),
347
+ }
348
+
349
+ normalized = dict(data)
350
+ for key, value in derived_tokens.items():
351
+ if value is not None and key not in normalized:
352
+ normalized[key] = value
353
+
354
+ chart_overrides: dict[str, Any] = {}
355
+ if isinstance(range_config.get("category"), list):
356
+ chart_overrides["palette"] = list(range_config["category"])
357
+ if chart_marks:
358
+ chart_overrides.update(chart_marks)
359
+ if chart_overrides:
360
+ normalized["charts"] = _deep_merge(dict(charts), chart_overrides)
361
+
362
+ return normalized
File without changes
@@ -0,0 +1,254 @@
1
+ """Authored variable models: Variable, VariableOptions, SingleRowBoolProbe, VariableInputType.
2
+
3
+ Stage: COMPILE (Input)
4
+ Purpose: Define variable types that map directly to the YAML schema.
5
+
6
+ Variables provide dynamic values to queries and charts. They can be bound to
7
+ user inputs (select, slider, etc.) or have static default values.
8
+ """
9
+
10
+ from typing import Any, Literal
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field
13
+
14
+ # ============================================================================
15
+ # ENUMS & LITERALS
16
+ # ============================================================================
17
+
18
+ VariableInputType = Literal[
19
+ "auto",
20
+ "select",
21
+ "multiselect",
22
+ "input",
23
+ "text",
24
+ "number",
25
+ "textarea",
26
+ "slider",
27
+ "range",
28
+ "date",
29
+ "datepicker",
30
+ "daterange",
31
+ "checkbox",
32
+ "radio",
33
+ ]
34
+
35
+ # ============================================================================
36
+ # VARIABLE TYPES
37
+ # ============================================================================
38
+
39
+
40
+ class VariableOptions(BaseModel):
41
+ """Options configuration for variable inputs.
42
+
43
+ Defines where the options come from for select/multiselect inputs.
44
+ """
45
+
46
+ model_config = ConfigDict(extra="forbid")
47
+
48
+ static: list[str | int | float] | None = Field(
49
+ default=None, description="List of static option values (strings or numbers)."
50
+ )
51
+ query: str | None = Field(
52
+ default=None, description="Query name whose result rows provide option values."
53
+ )
54
+ column: str | None = Field(
55
+ default=None, description="Column in the query result to use as option values."
56
+ )
57
+ label_column: str | None = Field(
58
+ default=None,
59
+ description="Column in the query result to use as display labels (separate from values).",
60
+ )
61
+
62
+
63
+ class SingleRowBoolProbe(BaseModel):
64
+ """Single-row boolean query probe.
65
+
66
+ Executes a named query and reads one boolean cell to decide a yes/no
67
+ condition. Used by ``Variable.disabled`` and ``LayoutItem.visible``.
68
+ The query must return exactly one row; the named column must hold a
69
+ boolean-coercible value (true/false/1/0/yes/no).
70
+
71
+ Example YAML (disabled)::
72
+
73
+ disabled:
74
+ query: seed_control_state
75
+ column: is_disabled
76
+
77
+ Example YAML (visible)::
78
+
79
+ visible:
80
+ query: layout_flags
81
+ column: show_panel
82
+ """
83
+
84
+ model_config = ConfigDict(extra="forbid")
85
+
86
+ query: str = Field(description="Name of the query to execute.")
87
+ column: str = Field(
88
+ description="Column in the single result row holding the boolean value."
89
+ )
90
+
91
+
92
+ class Variable(BaseModel):
93
+ """Variable definition from YAML.
94
+
95
+ Variables provide dynamic values to queries and charts.
96
+ They can be bound to user inputs (select, slider, etc.) or
97
+ have static default values.
98
+
99
+ Cascading Variables:
100
+ Variables can depend on other variables when their options query
101
+ uses Jinja templates referencing other variables. For example:
102
+
103
+ variables:
104
+ country:
105
+ input: select
106
+ options:
107
+ query: country_options
108
+
109
+ state:
110
+ input: select
111
+ options:
112
+ query: state_options # SQL uses {{ filter('country', country) }}
113
+
114
+ When country changes, state's options are re-fetched with the new
115
+ country value, and state's current value is reset.
116
+
117
+ Invisible Variables:
118
+ Variables with visible=False are not rendered in the UI but can still
119
+ be used in queries. This is useful for:
120
+ - Server-side variables passed via URL query params
121
+ - Internal system values (theme, model name, connection strings)
122
+ - Variables that shouldn't be user-editable
123
+
124
+ Example YAML:
125
+ variables:
126
+ date_range:
127
+ input: daterange
128
+ label: "Date Range"
129
+ default: ["2024-01-01", "2024-12-31"]
130
+ category:
131
+ input: select
132
+ options:
133
+ static: ["All", "Electronics", "Clothing"]
134
+ default: "All"
135
+ model:
136
+ input: text
137
+ visible: false # Set via URL param, not shown in UI
138
+ """
139
+
140
+ model_config = ConfigDict(extra="forbid")
141
+
142
+ input: VariableInputType = Field(
143
+ default="auto",
144
+ description="UI control type (select, multiselect, slider, daterange, etc.). 'auto' detects from options.",
145
+ )
146
+ input_auto_detected: bool = Field(
147
+ default=False,
148
+ exclude=True,
149
+ description="Internal: True when input type was resolved from 'auto' to a concrete type.",
150
+ )
151
+ label: str | None = Field(
152
+ default=None, description="Display label shown above the input control."
153
+ )
154
+ description: str | None = Field(
155
+ default=None, description="Help text shown below the input control."
156
+ )
157
+ default: Any | None = Field(
158
+ default=None, description="Default value used when no URL param is set."
159
+ )
160
+ placeholder: str | None = Field(
161
+ default=None, description="Placeholder text shown in the input when empty."
162
+ )
163
+ required: bool | None = Field(
164
+ default=False,
165
+ description="When True, a value must be provided before queries execute.",
166
+ )
167
+ allow_null: bool | None = Field(
168
+ default=False,
169
+ description="When True, 'null' is a valid selection (useful for optional filters).",
170
+ )
171
+ visible: bool = Field(
172
+ default=True,
173
+ description="When False, the variable is not rendered in the UI but can still be set via URL params.",
174
+ )
175
+ disabled: bool | str | SingleRowBoolProbe | None = Field(
176
+ default=None,
177
+ description=(
178
+ "Disable this control. Accepts: static bool; a variable name or Jinja "
179
+ "boolean expression string (no {{ }} required — bare names auto-wrap); "
180
+ "or a {query, column} form that reads a single boolean cell from a named "
181
+ "query. Absent variable in a string expression raises (use a default)."
182
+ ),
183
+ )
184
+
185
+ # Source binding (one of these for data-driven options)
186
+ column: str | None = Field(
187
+ default=None,
188
+ description="Column name in the query result to use as option values.",
189
+ )
190
+ query: str | dict[str, Any] | None = Field(
191
+ default=None,
192
+ description="Query name or inline query definition for populating options.",
193
+ )
194
+ dimension: str | None = Field(
195
+ default=None, description="MetricFlow dimension name for populating options."
196
+ )
197
+ measure: str | None = Field(
198
+ default=None, description="MetricFlow measure name for populating options."
199
+ )
200
+ model: str | None = Field(
201
+ default=None, description="dbt model name for populating options."
202
+ )
203
+
204
+ # Options
205
+ options: VariableOptions | None = Field(
206
+ default=None, description="Static or query-driven option list configuration."
207
+ )
208
+
209
+ # Migration metadata
210
+ data_type: str | None = Field(
211
+ default=None,
212
+ description=(
213
+ "Upstream data-type hint preserved through migrations (e.g. 'string', "
214
+ "'number'). Informational; not currently consumed at compile time."
215
+ ),
216
+ )
217
+
218
+ # Slider-specific
219
+ min: int | float | None = Field(
220
+ default=None, description="Minimum value for slider/range inputs."
221
+ )
222
+ max: int | float | None = Field(
223
+ default=None, description="Maximum value for slider/range inputs."
224
+ )
225
+ step: int | float | None = Field(
226
+ default=None, description="Step size for slider/range inputs."
227
+ )
228
+
229
+ # Operator for filter generation
230
+ operator: str | None = Field(
231
+ default=None,
232
+ description="SQL operator used when generating filter expressions (e.g., '=', 'IN', 'LIKE').",
233
+ )
234
+
235
+ # Computed during compilation — not an authored field.
236
+ variable_dependencies: set[str] = Field(
237
+ default_factory=set,
238
+ exclude=True,
239
+ description="Other variable names this variable's options query depends on (computed during compilation).",
240
+ )
241
+
242
+ def get_option_query(self) -> str | None:
243
+ """Get the query name used for populating options.
244
+
245
+ Returns:
246
+ Query name if options are populated from a query, None otherwise
247
+ """
248
+ # Check options.query first (structured options)
249
+ if self.options and self.options.query:
250
+ return self.options.query
251
+ # Check direct query binding
252
+ if isinstance(self.query, str):
253
+ return self.query
254
+ return None
File without changes