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,527 @@
1
+ """Pydantic model introspection — Layer 1 of the two-layer schema IR.
2
+
3
+ Walks AuthoredFace and all reachable authored models once, producing an
4
+ AuthorableSchema IR. Renderers (json_schema, prompt, markdown, erd) consume
5
+ this IR — no rendering logic lives here.
6
+
7
+ Entry point: introspect() -> AuthorableSchema
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import dataclasses
13
+ import typing
14
+ from enum import Enum
15
+ from typing import Any, ForwardRef, Literal, get_args, get_origin
16
+
17
+ from pydantic import BaseModel
18
+ from pydantic.fields import FieldInfo
19
+ from pydantic_core import PydanticUndefinedType
20
+
21
+ from dataface.core.compile.models.face.authored import AuthoredFace
22
+ from dataface.core.compile.models.factories import _PatchBase
23
+ from dataface.core.compile.models.query.authored import AuthoredQuery
24
+ from dataface.core.compile.models.source import (
25
+ BigQuerySourceConfig,
26
+ CsvSourceConfig,
27
+ DbtProfileSourceConfig,
28
+ DuckDBSourceConfig,
29
+ HttpSourceConfig,
30
+ JsonSourceConfig,
31
+ MySQLSourceConfig,
32
+ ParquetSourceConfig,
33
+ PostgresSourceConfig,
34
+ RedshiftSourceConfig,
35
+ SnowflakeSourceConfig,
36
+ )
37
+ from dataface.core.compile.models.style.compiled import PaginationConfig
38
+
39
+
40
+ @dataclasses.dataclass
41
+ class SchemaField:
42
+ name: str
43
+ description: str
44
+ type_repr: str
45
+ required: bool
46
+ default: Any
47
+ default_repr: (
48
+ str | None
49
+ ) # human-readable default for display (None = no meaningful default)
50
+ enum_values: list[str] | None
51
+ nested_models: list[str] # class names of nested AuthorableModels (empty = none)
52
+ container: str | None = None # "list" or "dict" if the model is in a container
53
+ extra_union_types: list[str] = dataclasses.field(
54
+ default_factory=list
55
+ ) # primitive type names alongside models or enums in a union
56
+ cascades: bool = False # True when value propagates (face or style cascade)
57
+ cascade_key: str | None = None # face cascade: context key
58
+ cascade_targets: tuple[str, ...] = () # style cascade: sibling fields this fills in
59
+ is_extra_key: bool = (
60
+ False # True for synthetic '<name>' fields from __pydantic_extra__
61
+ )
62
+
63
+
64
+ @dataclasses.dataclass
65
+ class AuthorableModel:
66
+ name: str
67
+ doc: str
68
+ fields: list[SchemaField]
69
+ generated: bool = False # True for _PatchBase subclasses (build_patch_model output)
70
+
71
+
72
+ @dataclasses.dataclass
73
+ class AuthorableSchema:
74
+ root: str
75
+ models: dict[str, AuthorableModel]
76
+
77
+
78
+ def _unwrap_annotated(annotation: Any) -> Any:
79
+ """Strip Annotated[X, *metadata] → X. Idempotent on plain types."""
80
+ if hasattr(annotation, "__metadata__"):
81
+ return annotation.__origin__
82
+ return annotation
83
+
84
+
85
+ def _is_authored_model(cls: Any) -> bool:
86
+ """Return True if cls should be included in the IR.
87
+
88
+ Includes authored.* models, primitives, and generated _PatchBase subclasses.
89
+ """
90
+ if not (isinstance(cls, type) and issubclass(cls, BaseModel)):
91
+ return False
92
+ if issubclass(cls, _PatchBase):
93
+ return True
94
+ module = getattr(cls, "__module__", "")
95
+ return ".authored" in module or module.endswith("primitives")
96
+
97
+
98
+ def _extract_enum_values(annotation: Any) -> list[str] | None:
99
+ """Return string enum values if annotation is an Enum or Literal, else None."""
100
+ annotation = _unwrap_annotated(annotation)
101
+ origin = get_origin(annotation)
102
+ args = get_args(annotation)
103
+
104
+ # Literal["foo", "bar", ...]
105
+ if origin is Literal:
106
+ return [str(a) for a in args if a is not None]
107
+
108
+ # str Enum subclass
109
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
110
+ return [e.value for e in annotation]
111
+
112
+ # Union — look for a Literal or Enum inside (e.g. ChartType | None)
113
+ if origin is type(None) or origin is None:
114
+ return None
115
+ # handle Union (typing.Union has origin = types.UnionType or typing.Union)
116
+ if args:
117
+ for arg in args:
118
+ result = _extract_enum_values(arg)
119
+ if result:
120
+ return result
121
+
122
+ return None
123
+
124
+
125
+ def _nested_model_names(annotation: Any) -> list[str]:
126
+ """Return all authored model class names reachable from annotation.
127
+
128
+ Returns an empty list if no authored models are found.
129
+ For plain types, returns a one-element list; for discriminated unions,
130
+ returns one entry per branch.
131
+ """
132
+ annotation = _unwrap_annotated(annotation)
133
+ if _is_authored_model(annotation):
134
+ return [annotation.__name__]
135
+
136
+ origin = get_origin(annotation)
137
+ args = get_args(annotation)
138
+ if not args:
139
+ return []
140
+
141
+ # dict[K, V] — recurse into the value type only
142
+ if origin is dict and len(args) == 2:
143
+ return _nested_model_names(args[1])
144
+
145
+ # list[SomeModel] or Union/Optional — collect all authored branches, dedup by class name
146
+ result: list[str] = []
147
+ for arg in args:
148
+ result.extend(_nested_model_names(arg))
149
+ return list(dict.fromkeys(result))
150
+
151
+
152
+ def _all_model_names_in_annotation(annotation: Any) -> list[str]:
153
+ """Return class names of ALL BaseModel subclasses reachable from annotation.
154
+
155
+ Unlike _nested_model_names, this is not limited to "authored" modules.
156
+ Used for __pydantic_extra__ synthetic fields where the value type may
157
+ reference source connector configs (models.source module).
158
+ """
159
+ annotation = _unwrap_annotated(annotation)
160
+ if isinstance(annotation, type) and issubclass(annotation, BaseModel):
161
+ return [annotation.__name__]
162
+
163
+ origin = get_origin(annotation)
164
+ args = get_args(annotation)
165
+ if not args:
166
+ return []
167
+
168
+ if origin is dict and len(args) == 2:
169
+ return _all_model_names_in_annotation(args[1])
170
+
171
+ result: list[str] = []
172
+ for arg in args:
173
+ if arg is not type(None):
174
+ result.extend(_all_model_names_in_annotation(arg))
175
+ return result
176
+
177
+
178
+ def _type_repr(annotation: Any) -> str:
179
+ """Human-readable type string for display in docs/prompts."""
180
+ annotation = _unwrap_annotated(annotation)
181
+ if annotation is None or annotation is type(None):
182
+ return "None"
183
+ if isinstance(annotation, ForwardRef):
184
+ return annotation.__forward_arg__
185
+
186
+ origin = get_origin(annotation)
187
+ args = get_args(annotation)
188
+
189
+ if origin is Literal:
190
+ values = [repr(a) for a in args]
191
+ return f"one of: {', '.join(values)}"
192
+
193
+ # Guard: only match plain types, not generic aliases (list[X], dict[K,V])
194
+ # On Python 3.10, isinstance(list[X], type) can return True for GenericAlias.
195
+ if origin is None and isinstance(annotation, type):
196
+ if issubclass(annotation, Enum):
197
+ return f"one of: {', '.join(e.value for e in annotation)}"
198
+ return annotation.__name__
199
+
200
+ if origin is list:
201
+ if args:
202
+ return f"list[{_type_repr(args[0])}]"
203
+ return "list"
204
+
205
+ if origin is dict:
206
+ if len(args) == 2:
207
+ return f"dict[{_type_repr(args[0])}, {_type_repr(args[1])}]"
208
+ return "dict"
209
+
210
+ # Union types (X | Y | None) — deduplicate branches (discriminated unions may
211
+ # have the same class under multiple Tag() aliases, e.g. BarChart for bar+histogram)
212
+ if args:
213
+ non_none = [a for a in args if a is not type(None)]
214
+ nullable = any(a is type(None) for a in args)
215
+ parts = list(dict.fromkeys(_type_repr(a) for a in non_none))
216
+ result = " | ".join(parts)
217
+ if nullable and len(non_none) > 0:
218
+ result += " | None"
219
+ return result
220
+
221
+ if annotation is Any:
222
+ return "Any"
223
+ return str(annotation)
224
+
225
+
226
+ def _get_container(annotation: Any) -> str | None:
227
+ """Return 'list' or 'dict' if annotation wraps a nested model in a container."""
228
+ annotation = _unwrap_annotated(annotation)
229
+ origin = get_origin(annotation)
230
+ args = get_args(annotation)
231
+ if origin is list:
232
+ return "list"
233
+ if origin is dict:
234
+ return "dict"
235
+ if args:
236
+ non_none = [a for a in args if a is not type(None)]
237
+ if len(non_none) == 1:
238
+ return _get_container(non_none[0])
239
+ return None
240
+
241
+
242
+ _PRIMITIVE_NAMES = frozenset({"str", "int", "float", "bool"})
243
+
244
+
245
+ def _default_repr(field_info: FieldInfo) -> str | None:
246
+ """Return a human-readable default string for display, or None if no meaningful default."""
247
+ if field_info.is_required():
248
+ return None
249
+ default = field_info.default
250
+ if isinstance(default, PydanticUndefinedType):
251
+ factory = field_info.default_factory
252
+ if factory is dict:
253
+ return "{}"
254
+ if factory is list:
255
+ return "[]"
256
+ return None
257
+ if default is None:
258
+ return None
259
+ if isinstance(default, bool):
260
+ return str(default).lower()
261
+ if isinstance(default, str):
262
+ return f'"{default}"'
263
+ if isinstance(default, (int, float)):
264
+ return str(default)
265
+ return None
266
+
267
+
268
+ def _extra_union_types(annotation: Any) -> list[str]:
269
+ """Primitive type names that sit alongside models or enums in a union.
270
+
271
+ Returns the names (e.g. ["str", "bool"]) so the renderer can include
272
+ them in anyOf alongside $ref or enum branches.
273
+ Only meaningful for bare Union types — list/dict containers return [].
274
+ """
275
+ annotation = _unwrap_annotated(annotation)
276
+ origin = get_origin(annotation)
277
+ args = get_args(annotation)
278
+ if not args or origin in (list, dict):
279
+ return []
280
+
281
+ result: list[str] = []
282
+ for arg in args:
283
+ if arg is type(None):
284
+ continue
285
+ if _is_authored_model(arg):
286
+ continue
287
+ arg_origin = get_origin(arg)
288
+ if arg_origin is Literal:
289
+ continue
290
+ if isinstance(arg, type) and issubclass(arg, Enum):
291
+ continue
292
+ if (
293
+ arg_origin is None
294
+ and isinstance(arg, type)
295
+ and arg.__name__ in _PRIMITIVE_NAMES
296
+ ):
297
+ result.append(arg.__name__)
298
+ return result
299
+
300
+
301
+ def _build_schema_field(
302
+ name: str, field_info: FieldInfo, annotation: Any
303
+ ) -> SchemaField:
304
+ from dataface.core.compile.models.markers import Cascade
305
+
306
+ required = field_info.is_required()
307
+ default = field_info.default if not required else dataclasses.MISSING
308
+ cascade = next(
309
+ (m for m in (field_info.metadata or []) if isinstance(m, Cascade)), None
310
+ )
311
+ return SchemaField(
312
+ name=name,
313
+ description=field_info.description or "",
314
+ type_repr=_type_repr(annotation),
315
+ required=required,
316
+ default=default,
317
+ default_repr=_default_repr(field_info),
318
+ enum_values=_extract_enum_values(annotation),
319
+ nested_models=_nested_model_names(annotation),
320
+ container=_get_container(annotation),
321
+ extra_union_types=_extra_union_types(annotation),
322
+ cascades=cascade is not None,
323
+ cascade_key=(cascade.key or name) if cascade is not None else None,
324
+ cascade_targets=cascade.targets if cascade is not None else (),
325
+ )
326
+
327
+
328
+ def _collect_models(
329
+ model_cls: type[BaseModel],
330
+ collected: dict[str, AuthorableModel],
331
+ ) -> None:
332
+ """Recursively walk model_cls and all reachable authored sub-models."""
333
+ name = model_cls.__name__
334
+ if name in collected:
335
+ return
336
+
337
+ # Use __doc__ directly (not inspect.getdoc) to avoid MRO walk that pulls in
338
+ # Pydantic BaseModel's "[Models](../concepts/models.md)" doc for generated patches.
339
+ raw_doc = model_cls.__doc__ or ""
340
+ doc = ""
341
+ for line in raw_doc.split("\n"):
342
+ stripped = line.strip()
343
+ if stripped and not stripped.startswith(("!!! ", "??? ", ":::")):
344
+ doc = stripped
345
+ break
346
+
347
+ fields: list[SchemaField] = []
348
+
349
+ for field_name, field_info in model_cls.model_fields.items():
350
+ if field_info.exclude:
351
+ continue
352
+ annotation = field_info.annotation
353
+ display_name = field_info.alias or field_name
354
+ sf = _build_schema_field(display_name, field_info, annotation)
355
+ fields.append(sf)
356
+
357
+ # Synthesize a '<name>' field for models with typed __pydantic_extra__.
358
+ # get_type_hints() is needed because from __future__ import annotations turns
359
+ # annotations into strings; vars() gives the raw (unresolved) form.
360
+ try:
361
+ hints = typing.get_type_hints(model_cls)
362
+ extra_annotation = hints.get("__pydantic_extra__")
363
+ except NameError:
364
+ extra_annotation = None
365
+ if extra_annotation is not None:
366
+ ea_origin = get_origin(extra_annotation)
367
+ ea_args = get_args(extra_annotation)
368
+ if ea_origin is dict and len(ea_args) == 2:
369
+ value_annotation = ea_args[1]
370
+ fields.append(
371
+ SchemaField(
372
+ name="<name>",
373
+ description="Additional named entry.",
374
+ type_repr=_type_repr(value_annotation),
375
+ required=False,
376
+ default=None,
377
+ default_repr=None,
378
+ enum_values=None,
379
+ nested_models=_all_model_names_in_annotation(value_annotation),
380
+ is_extra_key=True,
381
+ )
382
+ )
383
+
384
+ collected[name] = AuthorableModel(
385
+ name=name,
386
+ doc=doc,
387
+ fields=fields,
388
+ generated=issubclass(model_cls, _PatchBase),
389
+ )
390
+
391
+ # Recurse into nested authored models
392
+ for sf in fields:
393
+ for model_name in sf.nested_models:
394
+ if model_name not in collected:
395
+ nested_cls = _find_nested_class(model_cls, model_name)
396
+ if nested_cls is not None and _is_authored_model(nested_cls):
397
+ _collect_models(nested_cls, collected)
398
+
399
+
400
+ def _find_nested_class(parent_cls: type[BaseModel], class_name: str) -> type | None:
401
+ """Find a nested model class by name from a parent model's field annotations."""
402
+ for field_info in parent_cls.model_fields.values():
403
+ annotation = field_info.annotation
404
+ cls = _find_in_annotation(annotation, class_name)
405
+ if cls is not None:
406
+ return cls
407
+ return None
408
+
409
+
410
+ def _find_in_annotation(annotation: Any, class_name: str) -> type | None:
411
+ """Recursively search annotation tree for a class with the given name."""
412
+ if isinstance(annotation, type) and annotation.__name__ == class_name:
413
+ return annotation
414
+ args = get_args(annotation)
415
+ for arg in args:
416
+ result = _find_in_annotation(arg, class_name)
417
+ if result is not None:
418
+ return result
419
+ return None
420
+
421
+
422
+ def introspect() -> AuthorableSchema:
423
+ """Walk AuthoredFace and all reachable authored models, returning an IR.
424
+
425
+ The returned AuthorableSchema is the single source of truth consumed by
426
+ render_json_schema, render_prompt, render_markdown, and render_erd.
427
+ """
428
+ collected: dict[str, AuthorableModel] = {}
429
+ _collect_models(AuthoredFace, collected)
430
+ # Walk extra roots not reachable via AuthoredFace type annotations.
431
+ # AuthoredQuery uses untyped dicts in the authored YAML (normalizer coerces).
432
+ # Source connector configs are reached via sources: dict[str, Any] — no type annotation.
433
+ # Per-family chart patch classes are not reachable via AuthoredFace annotations either
434
+ # (AuthoredChart is a discriminated union alias, not a typed field on AuthoredFace).
435
+ from dataface.core.compile.models.chart.authored import ( # noqa: PLC0415
436
+ AreaChart,
437
+ BarChart,
438
+ CalloutChart,
439
+ GeoshapeChart,
440
+ HeatmapChart,
441
+ KpiChart,
442
+ LayeredChart,
443
+ LineChart,
444
+ PieChart,
445
+ PointMapChart,
446
+ ScatterChart,
447
+ SparkBarChart,
448
+ TableChart,
449
+ )
450
+
451
+ for extra in (
452
+ BarChart,
453
+ LineChart,
454
+ AreaChart,
455
+ ScatterChart,
456
+ HeatmapChart,
457
+ PieChart,
458
+ KpiChart,
459
+ TableChart,
460
+ PointMapChart,
461
+ GeoshapeChart,
462
+ LayeredChart,
463
+ CalloutChart,
464
+ SparkBarChart,
465
+ AuthoredQuery,
466
+ PaginationConfig,
467
+ PostgresSourceConfig,
468
+ SnowflakeSourceConfig,
469
+ BigQuerySourceConfig,
470
+ RedshiftSourceConfig,
471
+ MySQLSourceConfig,
472
+ DuckDBSourceConfig,
473
+ CsvSourceConfig,
474
+ ParquetSourceConfig,
475
+ JsonSourceConfig,
476
+ HttpSourceConfig,
477
+ DbtProfileSourceConfig,
478
+ ):
479
+ _collect_models(extra, collected)
480
+ # Build a synthetic "AuthoredChart" IR entry as a union of all per-family patches.
481
+ # This gives prompt.py and vscode_schema.py a single named surface to BFS from.
482
+ # The entry lists all fields across all families — renderers that need per-family
483
+ # discrimination should walk the individual *Patch entries directly.
484
+ all_patch_fields: dict[str, SchemaField] = {}
485
+ for patch_name in (
486
+ "BarChart",
487
+ "LineChart",
488
+ "AreaChart",
489
+ "ScatterChart",
490
+ "HeatmapChart",
491
+ "PieChart",
492
+ "KpiChart",
493
+ "TableChart",
494
+ "PointMapChart",
495
+ "GeoshapeChart",
496
+ "LayeredChart",
497
+ "CalloutChart",
498
+ "SparkBarChart",
499
+ ):
500
+ if patch_name not in collected:
501
+ continue
502
+ for f in collected[patch_name].fields:
503
+ if f.name not in all_patch_fields:
504
+ all_patch_fields[f.name] = f
505
+ if all_patch_fields:
506
+ from dataface.core.compile.models.chart.authored import ( # noqa: PLC0415
507
+ ChartType,
508
+ )
509
+
510
+ chart_type_values = [t.value for t in ChartType]
511
+ chart_patch_fields = [
512
+ (
513
+ dataclasses.replace(f, enum_values=chart_type_values)
514
+ if f.name == "type"
515
+ else f
516
+ )
517
+ for f in all_patch_fields.values()
518
+ ]
519
+ collected["AuthoredChart"] = AuthorableModel(
520
+ name="AuthoredChart",
521
+ doc=(
522
+ "Discriminated union of per-family chart patches. "
523
+ "type: is mandatory; missing or unknown type raises ValidationError."
524
+ ),
525
+ fields=chart_patch_fields,
526
+ )
527
+ return AuthorableSchema(root="AuthoredFace", models=collected)