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,75 @@
1
+ """Dataface Core Engine.
2
+
3
+ This module provides the core compilation, execution, and rendering
4
+ functionality for Dataface dashboards.
5
+
6
+ Submodules:
7
+ - compile/: Transform YAML → Face
8
+ - execute/: Execute queries and fetch data
9
+ - render/: Transform Face + data → output
10
+ - serve/: HTTP server for dashboard serving
11
+ - inspect/: Database inspection and profiling
12
+ - validate: Validation utilities
13
+
14
+ High-Level API:
15
+ >>> from pathlib import Path
16
+ >>> from dataface.core import compile, Executor, render
17
+ >>> from dataface.core.execute.adapters import build_adapter_registry
18
+ >>> result = compile(yaml_content)
19
+ >>> registry = build_adapter_registry(Path.cwd())
20
+ >>> executor = Executor(result.face, registry, query_registry=result.query_registry)
21
+ >>> html = render(result.face, executor, format="html")
22
+ """
23
+
24
+ # Re-export main compile functions and types
25
+ # Re-export validate module
26
+ from dataface.core import validate
27
+ from dataface.core.compile import (
28
+ AnyQuery,
29
+ AuthoredFace,
30
+ Chart,
31
+ CompilationError,
32
+ CompileResult,
33
+ Face,
34
+ Layout,
35
+ LayoutItem,
36
+ Variable,
37
+ compile,
38
+ compile_file,
39
+ )
40
+
41
+ # Re-export execute functions and types
42
+ from dataface.core.execute import (
43
+ ExecutionError,
44
+ Executor,
45
+ )
46
+
47
+ # Re-export render functions and types
48
+ from dataface.core.render import (
49
+ RenderError,
50
+ render,
51
+ )
52
+
53
+ __all__ = [
54
+ # Compile
55
+ "compile",
56
+ "compile_file",
57
+ "CompileResult",
58
+ "Face",
59
+ "Chart",
60
+ "AnyQuery",
61
+ "Layout",
62
+ "LayoutItem",
63
+ "AuthoredFace",
64
+ "Chart",
65
+ "Variable",
66
+ "CompilationError",
67
+ # Execute
68
+ "Executor",
69
+ "ExecutionError",
70
+ # Render
71
+ "render",
72
+ "RenderError",
73
+ # Validate
74
+ "validate",
75
+ ]
@@ -0,0 +1,244 @@
1
+ """AuthoredFace compilation module.
2
+
3
+ Stage: COMPILE
4
+ Purpose: Transform YAML face definitions into Face objects
5
+ ready for execution and rendering.
6
+
7
+ Entry Points:
8
+ - compile(yaml_content: str) -> CompileResult
9
+ - compile_file(file_path: Path) -> CompileResult
10
+
11
+ Outputs:
12
+ - Face: Fully normalized face with all references resolved
13
+ - CompileResult: Result container with face, errors, and warnings
14
+
15
+ Dependencies:
16
+ - yaml (PyYAML)
17
+ - pydantic
18
+
19
+ This module is pure - it does NOT import from execute/ or render/.
20
+ """
21
+
22
+ from dataface.core.compile.chart_focus import focus_on_chart
23
+ from dataface.core.compile.compiler import CompileResult, compile, compile_file
24
+ from dataface.core.compile.config import (
25
+ AccessConfig,
26
+ MetaConfig,
27
+ ProjectSourcesConfig,
28
+ get_config,
29
+ get_project_sources,
30
+ get_project_warnings_ignore,
31
+ load_config,
32
+ )
33
+ from dataface.core.compile.errors import (
34
+ CompilationError,
35
+ JinjaError,
36
+ ParseError,
37
+ ReferenceError,
38
+ ValidationError,
39
+ )
40
+ from dataface.core.compile.meta import (
41
+ get_meta_for_face,
42
+ load_meta_file,
43
+ merge_meta_with_face,
44
+ resolve_meta_chain,
45
+ )
46
+ from dataface.core.compile.models.chart.authored import (
47
+ AreaChart,
48
+ AuthoredChart,
49
+ BarChart,
50
+ CalloutChart,
51
+ ChartType,
52
+ GeoshapeChart,
53
+ HeatmapChart,
54
+ KpiChart,
55
+ LayeredChart,
56
+ LineChart,
57
+ PieChart,
58
+ PointMapChart,
59
+ ScatterChart,
60
+ SparkBarChart,
61
+ SparkConfig,
62
+ SparkTypeLiteral,
63
+ TableChart,
64
+ TableColumnConfig,
65
+ )
66
+ from dataface.core.compile.models.chart.compiled import (
67
+ Chart,
68
+ ChartDependencies,
69
+ is_chart,
70
+ )
71
+ from dataface.core.compile.models.face.authored import (
72
+ AuthoredFace,
73
+ LayoutType,
74
+ SourcesSection,
75
+ )
76
+ from dataface.core.compile.models.face.compiled import (
77
+ Face,
78
+ Layout,
79
+ LayoutItem,
80
+ VariableValues,
81
+ is_face,
82
+ )
83
+ from dataface.core.compile.models.query.authored import AuthoredQuery
84
+ from dataface.core.compile.models.query.compiled import (
85
+ VALID_QUERY_TYPES,
86
+ AnyQuery,
87
+ CsvQuery,
88
+ DbtModelQuery,
89
+ HttpQuery,
90
+ MetricFlowQuery,
91
+ SqlQuery,
92
+ is_csv_query,
93
+ is_dbt_model_query,
94
+ is_http_query,
95
+ is_metricflow_query,
96
+ is_sql_query,
97
+ )
98
+ from dataface.core.compile.models.source import (
99
+ VALID_SOURCE_TYPES,
100
+ BigQuerySourceConfig,
101
+ CsvSourceConfig,
102
+ DbtProfileSourceConfig,
103
+ DuckDBSourceConfig,
104
+ HttpSourceConfig,
105
+ JsonSourceConfig,
106
+ MySQLSourceConfig,
107
+ ParquetSourceConfig,
108
+ PostgresSourceConfig,
109
+ RedshiftSourceConfig,
110
+ SnowflakeSourceConfig,
111
+ SourceConfig,
112
+ is_api_source,
113
+ is_database_source,
114
+ is_file_source,
115
+ parse_source_config,
116
+ resolve_env_var,
117
+ )
118
+ from dataface.core.compile.models.variable.authored import (
119
+ Variable,
120
+ VariableInputType,
121
+ )
122
+ from dataface.core.compile.parameterized import (
123
+ ParameterizedQuery,
124
+ render_parameterized,
125
+ render_parameterized_with_queries,
126
+ )
127
+ from dataface.core.compile.sources import (
128
+ DB_INFO_MAP,
129
+ detect_database_type_from_registry,
130
+ detect_dbt_connection_string,
131
+ detect_dbt_connections,
132
+ detect_dbt_database_type,
133
+ get_database_info,
134
+ )
135
+ from dataface.core.compile.variables import parse_variable_json_strings
136
+
137
+ __all__ = [
138
+ # Main entry points
139
+ "compile",
140
+ "compile_file",
141
+ "CompileResult",
142
+ "focus_on_chart",
143
+ # Input types
144
+ "AuthoredFace",
145
+ "AuthoredChart",
146
+ "BarChart",
147
+ "LineChart",
148
+ "AreaChart",
149
+ "ScatterChart",
150
+ "HeatmapChart",
151
+ "PieChart",
152
+ "KpiChart",
153
+ "TableChart",
154
+ "PointMapChart",
155
+ "GeoshapeChart",
156
+ "LayeredChart",
157
+ "CalloutChart",
158
+ "SparkBarChart",
159
+ "AuthoredQuery",
160
+ "Variable",
161
+ "SourcesSection",
162
+ # Compiled types
163
+ "Face",
164
+ "Chart",
165
+ "AnyQuery",
166
+ "Layout",
167
+ "LayoutItem",
168
+ "VariableValues",
169
+ "ChartDependencies",
170
+ # Unified query types
171
+ "SqlQuery",
172
+ "CsvQuery",
173
+ "MetricFlowQuery",
174
+ "HttpQuery",
175
+ "DbtModelQuery",
176
+ "VALID_QUERY_TYPES",
177
+ # Source types
178
+ "SourceConfig",
179
+ "PostgresSourceConfig",
180
+ "SnowflakeSourceConfig",
181
+ "BigQuerySourceConfig",
182
+ "RedshiftSourceConfig",
183
+ "MySQLSourceConfig",
184
+ "DuckDBSourceConfig",
185
+ "CsvSourceConfig",
186
+ "ParquetSourceConfig",
187
+ "JsonSourceConfig",
188
+ "HttpSourceConfig",
189
+ "DbtProfileSourceConfig",
190
+ "VALID_SOURCE_TYPES",
191
+ "parse_source_config",
192
+ "is_database_source",
193
+ "is_file_source",
194
+ "is_api_source",
195
+ "resolve_env_var",
196
+ # Config
197
+ "ProjectSourcesConfig",
198
+ "get_config",
199
+ "load_config",
200
+ "get_project_sources",
201
+ "get_project_warnings_ignore",
202
+ # Meta configuration
203
+ "MetaConfig",
204
+ "AccessConfig",
205
+ "get_meta_for_face",
206
+ "load_meta_file",
207
+ "merge_meta_with_face",
208
+ "resolve_meta_chain",
209
+ # Type guards
210
+ "is_face",
211
+ "is_chart",
212
+ "is_sql_query",
213
+ "is_csv_query",
214
+ "is_metricflow_query",
215
+ "is_http_query",
216
+ "is_dbt_model_query",
217
+ # Enums
218
+ "ChartType",
219
+ "LayoutType",
220
+ "VariableInputType",
221
+ "SparkTypeLiteral",
222
+ # Spark charts
223
+ "SparkConfig",
224
+ "TableColumnConfig",
225
+ # Errors
226
+ "CompilationError",
227
+ "ParseError",
228
+ "ValidationError",
229
+ "ReferenceError",
230
+ "JinjaError",
231
+ # Parameterized queries
232
+ "ParameterizedQuery",
233
+ "render_parameterized",
234
+ "render_parameterized_with_queries",
235
+ # Variable utilities
236
+ "parse_variable_json_strings",
237
+ # Source detection utilities
238
+ "DB_INFO_MAP",
239
+ "detect_database_type_from_registry",
240
+ "detect_dbt_connection_string",
241
+ "detect_dbt_connections",
242
+ "detect_dbt_database_type",
243
+ "get_database_info",
244
+ ]
@@ -0,0 +1,78 @@
1
+ """Shared Jinja helpers used by compile/parameterized.py and compile/jinja.py.
2
+
3
+ Both modules need identical lenient-undefined and query-namespace behaviour;
4
+ this module is the single canonical home. Consumers import from here.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from jinja2 import Undefined
12
+
13
+
14
+ class _LenientUndefined(Undefined):
15
+ """Lenient undefined for non-strict Jinja rendering.
16
+
17
+ Undefined variables become empty strings rather than raising errors.
18
+ Used in interactive editing contexts where variables may not yet be defined.
19
+ """
20
+
21
+ def __str__(self) -> str:
22
+ return ""
23
+
24
+ def __repr__(self) -> str:
25
+ return ""
26
+
27
+ def __bool__(self) -> bool:
28
+ return False
29
+
30
+ def __iter__(self) -> Any:
31
+ return iter([])
32
+
33
+ def __len__(self) -> int:
34
+ return 0
35
+
36
+ def __eq__(self, other: Any) -> bool:
37
+ return isinstance(other, Undefined) or other is None or other == ""
38
+
39
+ def __ne__(self, other: Any) -> bool:
40
+ return not self.__eq__(other)
41
+
42
+ def __getitem__(self, key: Any) -> _LenientUndefined: # type: ignore[override]
43
+ return self
44
+
45
+ def __getattr__(self, name: str) -> _LenientUndefined:
46
+ # Return self for chaining like {{ foo.bar.baz }}; guard dunder lookups.
47
+ if name.startswith("_"):
48
+ raise AttributeError(name)
49
+ return self
50
+
51
+
52
+ class _QueryNamespace:
53
+ """Proxy for {{ queries.query_name }} template resolution.
54
+
55
+ Returns the SQL body of the named query. Raises AttributeError (the
56
+ correct Python __getattr__ protocol) when the query is not found.
57
+ """
58
+
59
+ def __init__(self, queries: dict[str, Any]):
60
+ self._queries = queries
61
+
62
+ def __getattr__(self, name: str) -> str:
63
+ if name.startswith("_"):
64
+ raise AttributeError(name)
65
+
66
+ if name not in self._queries:
67
+ raise AttributeError(f"Query '{name}' not found")
68
+
69
+ query = self._queries[name]
70
+
71
+ if hasattr(query, "sql"):
72
+ sql = query.sql
73
+ return sql if isinstance(sql, str) else ""
74
+ elif isinstance(query, dict):
75
+ sql = query.get("sql", "")
76
+ return sql if isinstance(sql, str) else ""
77
+
78
+ return str(query)
@@ -0,0 +1,222 @@
1
+ """Chart style channel parsing and normalization.
2
+
3
+ Handles the {column, value, scale} grammar for chart-level color
4
+ channels (color, background, opacity, stroke.color, stroke.width).
5
+
6
+ Rule-driven styling lives in the chart-level ``conditional_formatting:``
7
+ block (indexed by column name), not per-channel. See ``types.py`` for
8
+ ``FieldConditionalFormatting``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, Literal
15
+
16
+ from dataface.core.compile.models.chart.authored import (
17
+ GEO_CHART_TYPES,
18
+ ConditionalRule,
19
+ )
20
+ from dataface.core.compile.models.primitives import ScaleTargetConfig, StyleColorConfig
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class ResolvedStyleChannel:
25
+ channel: str
26
+ mode: Literal["series", "literal", "gradient", "conditional"]
27
+ data_field: str = ""
28
+ literal_value: Any = None
29
+ scale: ScaleTargetConfig | None = None
30
+ rules: tuple[ConditionalRule, ...] = ()
31
+ # When mode == "conditional", optional gradient fallback used when no
32
+ # threshold rule matches. Enables Looker-style "scale with rule override"
33
+ # where threshold rules take priority and scale shows through otherwise.
34
+ fallback_scale: ScaleTargetConfig | None = None
35
+
36
+
37
+ def parse_style_channel(
38
+ raw: str | dict[str, Any], channel_name: str
39
+ ) -> ResolvedStyleChannel:
40
+ """Parse a raw authored channel value into a typed ResolvedStyleChannel.
41
+
42
+ Handles shorthand (string) and four expanded forms.
43
+ Validates mutual exclusion. Does NOT validate field references (needs data).
44
+
45
+ Args:
46
+ raw: The authored value — string shorthand or {column,value,scale,when} dict
47
+ channel_name: The channel name for error messages
48
+
49
+ Raises:
50
+ ValueError: On mutual exclusion violations or unknown form
51
+ """
52
+ if isinstance(raw, str):
53
+ return ResolvedStyleChannel(channel=channel_name, mode="series", data_field=raw)
54
+
55
+ if not isinstance(raw, dict):
56
+ raise ValueError(
57
+ f"Channel '{channel_name}' must be a string or dict, got {type(raw).__name__}"
58
+ )
59
+
60
+ has_column = "column" in raw
61
+ has_value = "value" in raw
62
+ has_scale = "scale" in raw
63
+
64
+ extra = set(raw.keys()) - {"column", "value", "scale"}
65
+ if extra:
66
+ raise ValueError(
67
+ f"Channel '{channel_name}': unknown keys {sorted(extra)} (supported: column, value, scale)."
68
+ )
69
+
70
+ if has_value and (has_column or has_scale):
71
+ raise ValueError(
72
+ f"Channel '{channel_name}': 'value' cannot be combined with 'column' or 'scale'."
73
+ )
74
+
75
+ if has_value:
76
+ if raw["value"] is None:
77
+ raise ValueError(
78
+ f"Channel '{channel_name}': 'value' cannot be None — use a non-null style value."
79
+ )
80
+ return ResolvedStyleChannel(
81
+ channel=channel_name, mode="literal", literal_value=raw["value"]
82
+ )
83
+
84
+ if not has_column:
85
+ raise ValueError(
86
+ f"Channel '{channel_name}': must specify 'column' or 'value' (got keys: {list(raw.keys())})."
87
+ )
88
+
89
+ data_field = raw["column"]
90
+
91
+ if has_scale:
92
+ if not isinstance(raw["scale"], dict):
93
+ raise ValueError(
94
+ f"Channel '{channel_name}': 'scale' must be a mapping, "
95
+ f"got {type(raw['scale']).__name__}."
96
+ )
97
+ scale_dict = dict(raw["scale"])
98
+ if channel_name in ("opacity", "stroke_width") and "palette" not in scale_dict:
99
+ scale_dict["palette"] = [0.2, 1.0]
100
+ scale_cfg = ScaleTargetConfig.model_validate(scale_dict)
101
+ return ResolvedStyleChannel(
102
+ channel=channel_name,
103
+ mode="gradient",
104
+ data_field=data_field,
105
+ scale=scale_cfg,
106
+ )
107
+
108
+ return ResolvedStyleChannel(
109
+ channel=channel_name, mode="series", data_field=data_field
110
+ )
111
+
112
+
113
+ def validate_channel_fields(
114
+ channels: dict[str, ResolvedStyleChannel],
115
+ available_columns: set[str],
116
+ ) -> None:
117
+ """Raise ValueError if any channel references a field not in available_columns.
118
+
119
+ Only validates data-bound channels (mode in series/gradient/conditional).
120
+ """
121
+ for channel_name, ch in channels.items():
122
+ if ch.mode == "literal":
123
+ continue
124
+ if ch.data_field and ch.data_field not in available_columns:
125
+ # Common mistake: author writes `color: "#fff"` intending a literal.
126
+ # The string shorthand parses as series mode with data_field="#fff".
127
+ if ch.data_field.startswith("#"):
128
+ raise ValueError(
129
+ f"Channel '{channel_name}': '{ch.data_field}' looks like a literal color — "
130
+ f'use {{{channel_name}: {{value: "{ch.data_field}"}}}}'
131
+ )
132
+ raise ValueError(
133
+ f"Channel '{channel_name}' column '{ch.data_field}' not found. "
134
+ f"Available: {sorted(available_columns)}"
135
+ )
136
+
137
+
138
+ _COLOR_CHANNELS = frozenset({"color"})
139
+ _NUMERIC_CHANNELS: frozenset[str] = frozenset()
140
+
141
+
142
+ def _validate_palette_types(channels: dict[str, ResolvedStyleChannel]) -> None:
143
+ """Raise ValueError if palette type doesn't match channel semantics."""
144
+ for ch_name, ch in channels.items():
145
+ if ch.mode != "gradient" or ch.scale is None:
146
+ continue
147
+ palette = ch.scale.palette
148
+ if not palette:
149
+ continue
150
+ if ch_name in _COLOR_CHANNELS:
151
+ if not all(isinstance(c, str) for c in palette):
152
+ raise ValueError(
153
+ f"Channel '{ch_name}' expects a color palette (list of strings), "
154
+ f"got numeric values. Use strings like '#ffffff'."
155
+ )
156
+ elif ch_name in _NUMERIC_CHANNELS:
157
+ if not all(
158
+ isinstance(c, (int, float)) and not isinstance(c, bool) for c in palette
159
+ ):
160
+ raise ValueError(
161
+ f"Channel '{ch_name}' expects a numeric palette (list of floats), "
162
+ f"got non-numeric values."
163
+ )
164
+
165
+
166
+ def normalize_chart_channels(
167
+ chart: Any,
168
+ available_columns: set[str],
169
+ style_color: str | StyleColorConfig | None = None,
170
+ ) -> dict[str, ResolvedStyleChannel]:
171
+ """Parse and validate chart-level style channels.
172
+
173
+ Only ``color`` is a data channel at chart root. background/opacity/stroke
174
+ were removed as data channels (they are paint fields, not data bindings).
175
+
176
+ ``style_color``: when a ``StyleColorConfig`` with a scale is provided, the
177
+ series color channel is upgraded to gradient mode. A plain string value
178
+ means static paint — no data channel is created.
179
+
180
+ Raises ValueError on unresolvable field references or invalid syntax.
181
+ """
182
+ channels: dict[str, ResolvedStyleChannel] = {}
183
+
184
+ chart_type = getattr(chart, "type", None)
185
+
186
+ color_raw = getattr(chart, "color", None)
187
+ if color_raw is not None:
188
+ channels["color"] = parse_style_channel(color_raw, "color")
189
+
190
+ # Upgrade series channel to gradient when style.color carries a scale.
191
+ if isinstance(style_color, StyleColorConfig) and style_color.scale is not None:
192
+ if "color" not in channels:
193
+ raise ValueError(
194
+ "style.color: {scale: ...} requires chart.color to name a data field.\n"
195
+ "Add a data column binding, e.g.:\n"
196
+ " color: revenue\n"
197
+ " style:\n color: {scale: {palette: ['#fff', '#00f']}}"
198
+ )
199
+ if channels["color"].mode == "series":
200
+ channels["color"] = ResolvedStyleChannel(
201
+ channel="color",
202
+ mode="gradient",
203
+ data_field=channels["color"].data_field,
204
+ scale=style_color.scale,
205
+ )
206
+
207
+ # Geo renderers only consume the color field name — gradient/conditional modes
208
+ # silently drop palette or rules. Raise early so the author gets a clear error.
209
+ if chart_type in GEO_CHART_TYPES and "color" in channels:
210
+ color_mode = channels["color"].mode
211
+ if color_mode not in ("series", "literal"):
212
+ raise ValueError(
213
+ f"Geo charts ('{chart_type}') only support series or literal color channels — "
214
+ f"got mode '{color_mode}'. Use chart.color: <field> or chart.style.color: <hex>."
215
+ )
216
+
217
+ if available_columns:
218
+ validate_channel_fields(channels, available_columns)
219
+
220
+ _validate_palette_types(channels)
221
+
222
+ return channels