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,611 @@
1
+ """Source configuration types for database connections and data sources.
2
+
3
+ Stage: COMPILE
4
+ Purpose: Define Pydantic models for source configurations.
5
+
6
+ Sources represent "where data comes from" - databases, CSV files, HTTP APIs, etc.
7
+ Field names match dbt's profiles.yml exactly where applicable.
8
+
9
+ Design Principles:
10
+ - Match dbt field names exactly for database types
11
+ - Support env_var() syntax like dbt profiles
12
+ - Use discriminated union via 'type' field
13
+ - DB source configs inherit extra="allow" from _DbSourceShim (ADR-009 passthrough contract)
14
+ - File/HTTP/dbt_profile configs inherit extra="forbid" from BaseSourceConfig (closed contracts)
15
+
16
+ Dependencies:
17
+ - pydantic (BaseModel, Field, field_validator)
18
+
19
+ Read-only posture per warehouse:
20
+ DuckDB opens connections with read_only=True in-process (see SqlAdapter.read_only).
21
+ Every other warehouse lacks a native read-only driver flag. For those, the operator
22
+ must bind SELECT-only credentials at the database/IAM layer:
23
+
24
+ | Warehouse | In-process flag | Operator posture |
25
+ |-----------------|-----------------|-------------------------------------------------------|
26
+ | DuckDB | read_only=True | Handled in-process by SqlAdapter |
27
+ | Postgres | none | Bind a SELECT-only role to the dbt profile credential |
28
+ | MySQL/MariaDB | none | Bind a SELECT-only user to the dbt profile credential |
29
+ | Snowflake | none | Bind a SELECT-only role to the Snowflake user/key-pair|
30
+ | BigQuery | none | Bind credentials to roles/bigquery.dataViewer |
31
+ | Databricks | none | Unity Catalog: bind service principal to SELECT-only |
32
+ | Redshift | none | IAM/grants: bind to a SELECT-only IAM role or DB user |
33
+ | SQL Server | none | Login-level SELECT-only grants |
34
+
35
+ sql_guard (allowlist enforcement) provides the in-process defense for all warehouses.
36
+ SELECT-only credentials are the connection-level defense for non-DuckDB warehouses.
37
+
38
+ See also:
39
+ - config.py: Project-level configuration
40
+ - models/query/compiled.py: Query interface types
41
+ - normalizer.py: Resolves source references
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ import os
47
+ import re
48
+ from typing import Any, Literal
49
+
50
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
51
+
52
+ # ============================================================================
53
+ # BIGQUERY AUTH HELPERS
54
+ # ============================================================================
55
+
56
+
57
+ def infer_bq_method(
58
+ keyfile: str | None, keyfile_json: dict[str, Any] | None
59
+ ) -> Literal["oauth", "service-account", "service-account-json"]:
60
+ """Return the dbt-bigquery method for the given credential shape."""
61
+ if keyfile_json is not None:
62
+ return "service-account-json"
63
+ if keyfile is not None:
64
+ return "service-account"
65
+ return "oauth"
66
+
67
+
68
+ # ============================================================================
69
+ # ENVIRONMENT VARIABLE RESOLUTION
70
+ # ============================================================================
71
+
72
+
73
+ def resolve_env_var(value: str) -> str:
74
+ """Resolve {{ env_var('VAR') }} or {{ env_var('VAR', 'default') }} syntax.
75
+
76
+ Matches dbt's env_var() function behavior.
77
+
78
+ Args:
79
+ value: String that may contain env_var() expressions
80
+
81
+ Returns:
82
+ String with env_var() resolved to actual values
83
+
84
+ Raises:
85
+ ValueError: If required env var is not set
86
+ """
87
+ if not isinstance(value, str):
88
+ return value
89
+
90
+ # Pattern: {{ env_var('VAR') }} or {{ env_var('VAR', 'default') }}
91
+ pattern = r"\{\{\s*env_var\(\s*['\"]([^'\"]+)['\"]\s*(?:,\s*['\"]([^'\"]*)['\"])?\s*\)\s*\}\}"
92
+
93
+ def replace_env_var(match: re.Match[str]) -> str:
94
+ var_name = match.group(1)
95
+ default_value = match.group(2)
96
+
97
+ env_value = os.environ.get(var_name)
98
+ if env_value is not None:
99
+ return env_value
100
+ elif default_value is not None:
101
+ return default_value
102
+ else:
103
+ raise ValueError(
104
+ f"Environment variable '{var_name}' is not set and no default provided"
105
+ )
106
+
107
+ return re.sub(pattern, replace_env_var, value)
108
+
109
+
110
+ def resolve_env_vars_in_dict(data: dict[str, Any]) -> dict[str, Any]:
111
+ """Recursively resolve env_var() expressions in a dictionary.
112
+
113
+ Args:
114
+ data: Dictionary that may contain env_var() expressions
115
+
116
+ Returns:
117
+ Dictionary with all env_var() expressions resolved
118
+ """
119
+ result: dict[str, Any] = {}
120
+ for key, value in data.items():
121
+ if isinstance(value, str):
122
+ result[key] = resolve_env_var(value)
123
+ elif isinstance(value, dict):
124
+ result[key] = resolve_env_vars_in_dict(value)
125
+ else:
126
+ result[key] = value
127
+ return result
128
+
129
+
130
+ # ============================================================================
131
+ # BASE SOURCE CONFIG
132
+ # ============================================================================
133
+
134
+
135
+ class BaseSourceConfig(BaseModel):
136
+ """Closed-contract root for all source configurations.
137
+
138
+ extra="forbid" — file/HTTP/dbt_profile source configs have defined Dataface
139
+ contracts; unknown fields are validation errors, not silent pass-throughs.
140
+ DB source configs widen to extra="allow" via _DbSourceShim (ADR-009).
141
+ """
142
+
143
+ model_config = ConfigDict(extra="forbid")
144
+
145
+ @model_validator(mode="before")
146
+ @classmethod
147
+ def _resolve_all_env_vars(cls, data: Any) -> Any:
148
+ """Resolve {{ env_var('VAR') }} in all string fields before Pydantic validates."""
149
+ if isinstance(data, dict):
150
+ return resolve_env_vars_in_dict(data)
151
+ return data
152
+
153
+
154
+ class _DbSourceShim(BaseSourceConfig):
155
+ """Passthrough shim for DB source configs.
156
+
157
+ extra="allow" — dbt profiles.yml passthrough contract (ADR-009): dbt can
158
+ add or rename target fields across versions; Dataface reads whatever the
159
+ user's profile contains without raising a ValidationError. Only DB source
160
+ configs (Postgres, Snowflake, BigQuery, Redshift, MySQL, DuckDB) inherit
161
+ from this shim. File/HTTP/dbt_profile configs inherit BaseSourceConfig directly.
162
+ """
163
+
164
+ model_config = ConfigDict(extra="allow")
165
+
166
+
167
+ # ============================================================================
168
+ # DATABASE SOURCE CONFIGS
169
+ # ============================================================================
170
+
171
+
172
+ class PostgresSourceConfig(_DbSourceShim):
173
+ """Postgres source configuration.
174
+
175
+ Field names match dbt profiles.yml exactly.
176
+
177
+ Read-only posture: no native driver flag. Bind a SELECT-only role to the
178
+ dbt profile credential at the database level.
179
+ """
180
+
181
+ type: Literal["postgres"] = Field(description="Source type identifier.")
182
+ host: str = Field(description="Database host name or IP address.")
183
+ port: int = Field(default=5432, description="Database port number.")
184
+ dbname: str = Field(description="Database name (matches dbt 'dbname').")
185
+ schema_: str = Field(
186
+ alias="schema", default="public", description="Default schema for queries."
187
+ )
188
+ user: str = Field(description="Database user name.")
189
+ password: str = Field(description="Database password.")
190
+
191
+
192
+ class SnowflakeSourceConfig(_DbSourceShim):
193
+ """Snowflake source configuration.
194
+
195
+ Field names match dbt profiles.yml exactly.
196
+
197
+ Read-only posture: no native driver flag. Bind a SELECT-only role to the
198
+ Snowflake user or key-pair in the dbt profile.
199
+ """
200
+
201
+ type: Literal["snowflake"] = Field(description="Source type identifier.")
202
+ account: str = Field(
203
+ description="Snowflake account identifier (e.g. xy12345.us-east-1)."
204
+ )
205
+ user: str = Field(description="Snowflake user name.")
206
+ password: str | None = Field(
207
+ default=None,
208
+ description="Snowflake password. Omit when using OAuth or key-pair auth.",
209
+ )
210
+ database: str = Field(description="Snowflake database name.")
211
+ warehouse: str = Field(description="Snowflake virtual warehouse name.")
212
+ schema_: str = Field(
213
+ alias="schema", default="PUBLIC", description="Default schema for queries."
214
+ )
215
+ role: str | None = Field(
216
+ default=None, description="Snowflake role to assume for the session."
217
+ )
218
+
219
+
220
+ class BigQuerySourceConfig(_DbSourceShim):
221
+ """BigQuery source configuration.
222
+
223
+ Field names match dbt profiles.yml exactly.
224
+
225
+ Read-only posture: no native driver flag. Bind credentials to
226
+ roles/bigquery.dataViewer (or equivalent) at the IAM layer.
227
+ """
228
+
229
+ type: Literal["bigquery"] = Field(description="Source type identifier.")
230
+ project: str = Field(description="GCP project ID.")
231
+ dataset: str = Field(description="BigQuery dataset name (equivalent to schema).")
232
+ keyfile: str | None = Field(
233
+ default=None, description="Path to service account JSON key file."
234
+ )
235
+ keyfile_json: dict[str, Any] | None = Field(
236
+ default=None, description="Inline service account JSON dict."
237
+ )
238
+ location: str | None = Field(
239
+ default=None, description="Dataset location (e.g. US, EU)."
240
+ )
241
+ method: Literal["oauth", "service-account", "service-account-json"] | None = Field(
242
+ default=None,
243
+ description=(
244
+ "dbt-bigquery authentication method. "
245
+ "Inferred from keyfile/keyfile_json when omitted: "
246
+ "'service-account-json' if keyfile_json set, "
247
+ "'service-account' if keyfile set, "
248
+ "'oauth' (Application Default Credentials) otherwise."
249
+ ),
250
+ )
251
+
252
+ @model_validator(mode="after")
253
+ def _resolve_auth(self) -> BigQuerySourceConfig:
254
+ """Validate credential fields and infer method when not explicitly set."""
255
+ if self.keyfile is not None and self.keyfile_json is not None:
256
+ raise ValueError(
257
+ "BigQuery credentials must use either 'keyfile' (path to JSON file) "
258
+ "or 'keyfile_json' (inline dict), not both."
259
+ )
260
+ if self.method is None:
261
+ self.method = infer_bq_method(self.keyfile, self.keyfile_json)
262
+ return self
263
+
264
+
265
+ class RedshiftSourceConfig(_DbSourceShim):
266
+ """Redshift source configuration.
267
+
268
+ Field names match dbt profiles.yml exactly.
269
+
270
+ Read-only posture: no native driver flag. Bind to a SELECT-only IAM role
271
+ or database user at the Redshift/IAM layer.
272
+ """
273
+
274
+ type: Literal["redshift"] = Field(description="Source type identifier.")
275
+ host: str = Field(description="Redshift cluster host name.")
276
+ port: int = Field(default=5439, description="Redshift port number.")
277
+ dbname: str = Field(description="Redshift database name.")
278
+ schema_: str = Field(
279
+ alias="schema", default="public", description="Default schema for queries."
280
+ )
281
+ user: str = Field(description="Redshift user name.")
282
+ password: str = Field(description="Redshift password.")
283
+
284
+
285
+ class MySQLSourceConfig(_DbSourceShim):
286
+ """MySQL source configuration.
287
+
288
+ Field names match dbt profiles.yml exactly.
289
+
290
+ Read-only posture: no native driver flag. Bind a SELECT-only user to the
291
+ dbt profile credential at the database level.
292
+ """
293
+
294
+ type: Literal["mysql"] = Field(description="Source type identifier.")
295
+ host: str = Field(description="MySQL host name or IP address.")
296
+ port: int = Field(default=3306, description="MySQL port number.")
297
+ database: str = Field(description="MySQL database name.")
298
+ schema_: str = Field(
299
+ alias="schema", default="", description="Default schema for queries."
300
+ )
301
+ user: str = Field(description="MySQL user name.")
302
+ password: str = Field(description="MySQL password.")
303
+
304
+
305
+ class DuckDBSourceConfig(_DbSourceShim):
306
+ """DuckDB source configuration.
307
+
308
+ Read-only posture: handled in-process. SqlAdapter opens file-based DuckDB
309
+ connections with read_only=True and forces enable_external_access=False.
310
+ Callers that need external access (e.g. the playground for local dev) can
311
+ pass allow_external_access_in_readonly=True on SqlAdapter /
312
+ build_adapter_registry together with duckdb_config={"enable_external_access":
313
+ True} to explicitly opt back in.
314
+
315
+ Example:
316
+ sources:
317
+ analytics:
318
+ type: duckdb
319
+ path: ./data/analytics.duckdb
320
+ """
321
+
322
+ type: Literal["duckdb"] = Field(description="Source type identifier.")
323
+ path: str = Field(
324
+ default=":memory:",
325
+ description="DuckDB file path or ':memory:' for an in-memory database.",
326
+ )
327
+ schema_: str | None = Field(
328
+ alias="schema",
329
+ default=None,
330
+ description="Default schema for unqualified table names (sets search_path).",
331
+ )
332
+
333
+ @field_validator("schema_", mode="before")
334
+ @classmethod
335
+ def validate_schema_identifier(cls, v: Any) -> Any:
336
+ if v is None:
337
+ return v
338
+ if not isinstance(v, str) or not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", v):
339
+ raise ValueError(
340
+ f"schema must be a valid SQL identifier (letters, digits, underscores; "
341
+ f"no leading digit): got {v!r}"
342
+ )
343
+ return v
344
+
345
+ @model_validator(mode="before")
346
+ @classmethod
347
+ def reject_database_key(cls, data: Any) -> Any:
348
+ """Reject the legacy 'database' key at parse time.
349
+
350
+ The runtime already raises on 'database', so reject it here too so
351
+ the two layers agree and the error fires as early as possible.
352
+ """
353
+ if isinstance(data, dict) and "database" in data:
354
+ raise ValueError(
355
+ "DuckDB source config uses 'path', not 'database'. "
356
+ "Replace 'database' with 'path' "
357
+ '(e.g. {"type": "duckdb", "path": "db.duckdb"}).'
358
+ )
359
+ return data
360
+
361
+
362
+ # ============================================================================
363
+ # FILE SOURCE CONFIGS
364
+ # ============================================================================
365
+
366
+
367
+ class CsvSourceConfig(BaseSourceConfig):
368
+ """CSV file source configuration."""
369
+
370
+ type: Literal["csv"] = Field(description="Source type identifier.")
371
+ file: str | None = Field(default=None, description="Local path to the CSV file.")
372
+ url: str | None = Field(default=None, description="Remote URL of the CSV file.")
373
+ headers: dict[str, str] | None = Field(
374
+ default=None, description="HTTP headers for authenticated URLs."
375
+ )
376
+ delimiter: str = Field(default=",", description="Field delimiter character.")
377
+ encoding: str = Field(default="utf-8", description="File encoding.")
378
+
379
+ @model_validator(mode="after")
380
+ def validate_file_or_url(self) -> CsvSourceConfig:
381
+ """Ensure either file or url is provided."""
382
+ if not self.file and not self.url:
383
+ raise ValueError("CsvSourceConfig requires either 'file' or 'url'")
384
+ return self
385
+
386
+ @field_validator("file", "url", mode="before")
387
+ @classmethod
388
+ def resolve_env_vars(cls, v: Any) -> Any:
389
+ """Resolve env_var() syntax in string fields."""
390
+ if isinstance(v, str):
391
+ return resolve_env_var(v)
392
+ return v
393
+
394
+
395
+ class ParquetSourceConfig(BaseSourceConfig):
396
+ """Parquet file source configuration."""
397
+
398
+ type: Literal["parquet"] = Field(description="Source type identifier.")
399
+ file: str | None = Field(
400
+ default=None, description="Local path to the Parquet file."
401
+ )
402
+ url: str | None = Field(default=None, description="Remote URL of the Parquet file.")
403
+ headers: dict[str, str] | None = Field(
404
+ default=None, description="HTTP headers for authenticated URLs."
405
+ )
406
+
407
+ @model_validator(mode="after")
408
+ def validate_file_or_url(self) -> ParquetSourceConfig:
409
+ """Ensure either file or url is provided."""
410
+ if not self.file and not self.url:
411
+ raise ValueError("ParquetSourceConfig requires either 'file' or 'url'")
412
+ return self
413
+
414
+
415
+ class JsonSourceConfig(BaseSourceConfig):
416
+ """JSON file source configuration."""
417
+
418
+ type: Literal["json"] = Field(description="Source type identifier.")
419
+ file: str | None = Field(default=None, description="Local path to the JSON file.")
420
+ url: str | None = Field(default=None, description="Remote URL of the JSON file.")
421
+ headers: dict[str, str] | None = Field(
422
+ default=None, description="HTTP headers for authenticated URLs."
423
+ )
424
+ json_path: str | None = Field(
425
+ default=None, description="JSONPath expression to extract the data array."
426
+ )
427
+
428
+ @model_validator(mode="after")
429
+ def validate_file_or_url(self) -> JsonSourceConfig:
430
+ """Ensure either file or url is provided."""
431
+ if not self.file and not self.url:
432
+ raise ValueError("JsonSourceConfig requires either 'file' or 'url'")
433
+ return self
434
+
435
+
436
+ # ============================================================================
437
+ # HTTP/API SOURCE CONFIG
438
+ # ============================================================================
439
+
440
+
441
+ class HttpSourceConfig(BaseSourceConfig):
442
+ """HTTP/REST API source configuration."""
443
+
444
+ type: Literal["http"] = Field(description="Source type identifier.")
445
+ url: str = Field(description="Base URL for HTTP requests.")
446
+ headers: dict[str, str] | None = Field(
447
+ default=None, description="Default HTTP headers (e.g. Authorization)."
448
+ )
449
+
450
+ @field_validator("url", mode="before")
451
+ @classmethod
452
+ def resolve_url_env_var(cls, v: Any) -> Any:
453
+ """Resolve env_var() syntax in URL."""
454
+ if isinstance(v, str):
455
+ return resolve_env_var(v)
456
+ return v
457
+
458
+ @field_validator("headers", mode="before")
459
+ @classmethod
460
+ def resolve_headers_env_vars(cls, v: Any) -> Any:
461
+ """Resolve env_var() syntax in headers."""
462
+ if isinstance(v, dict):
463
+ return resolve_env_vars_in_dict(v)
464
+ return v
465
+
466
+
467
+ # ============================================================================
468
+ # DBT PROFILE SOURCE CONFIG
469
+ # ============================================================================
470
+
471
+
472
+ class DbtProfileSourceConfig(BaseSourceConfig):
473
+ """Reference to a dbt profile (for backward compatibility).
474
+
475
+ Instead of defining connection details, references an existing
476
+ dbt profile from profiles.yml.
477
+ """
478
+
479
+ type: Literal["dbt_profile"] = Field(description="Source type identifier.")
480
+ profile: str = Field(description="dbt profile name from profiles.yml.")
481
+ target: str | None = Field(
482
+ default=None,
483
+ description="dbt target to use; defaults to the profile's default target.",
484
+ )
485
+
486
+
487
+ # ============================================================================
488
+ # UNION TYPE
489
+ # ============================================================================
490
+
491
+
492
+ # Union of all source config types
493
+ SourceConfig = (
494
+ PostgresSourceConfig
495
+ | SnowflakeSourceConfig
496
+ | BigQuerySourceConfig
497
+ | RedshiftSourceConfig
498
+ | MySQLSourceConfig
499
+ | DuckDBSourceConfig
500
+ | CsvSourceConfig
501
+ | ParquetSourceConfig
502
+ | JsonSourceConfig
503
+ | HttpSourceConfig
504
+ | DbtProfileSourceConfig
505
+ )
506
+
507
+
508
+ # Valid source type strings
509
+ VALID_SOURCE_TYPES = {
510
+ "postgres",
511
+ "snowflake",
512
+ "bigquery",
513
+ "redshift",
514
+ "mysql",
515
+ "duckdb",
516
+ "csv",
517
+ "parquet",
518
+ "json",
519
+ "http",
520
+ "dbt_profile",
521
+ }
522
+
523
+
524
+ # ============================================================================
525
+ # HELPER FUNCTIONS
526
+ # ============================================================================
527
+
528
+
529
+ def parse_source_config(data: dict[str, Any]) -> SourceConfig:
530
+ """Parse a source configuration dictionary into the appropriate type.
531
+
532
+ Uses the 'type' field to determine which Pydantic model to use.
533
+
534
+ Args:
535
+ data: Source configuration dictionary
536
+
537
+ Returns:
538
+ Appropriate SourceConfig subclass instance
539
+
540
+ Raises:
541
+ ValueError: If type is missing or invalid
542
+ """
543
+ if "type" not in data:
544
+ raise ValueError("Source configuration must have a 'type' field")
545
+
546
+ source_type = data["type"]
547
+
548
+ type_map = {
549
+ "postgres": PostgresSourceConfig,
550
+ "snowflake": SnowflakeSourceConfig,
551
+ "bigquery": BigQuerySourceConfig,
552
+ "redshift": RedshiftSourceConfig,
553
+ "mysql": MySQLSourceConfig,
554
+ "duckdb": DuckDBSourceConfig,
555
+ "csv": CsvSourceConfig,
556
+ "parquet": ParquetSourceConfig,
557
+ "json": JsonSourceConfig,
558
+ "http": HttpSourceConfig,
559
+ "dbt_profile": DbtProfileSourceConfig,
560
+ }
561
+
562
+ if source_type not in type_map:
563
+ raise ValueError(
564
+ f"Unknown source type: '{source_type}'. "
565
+ f"Valid types: {', '.join(sorted(type_map.keys()))}"
566
+ )
567
+
568
+ return type_map[source_type](**data)
569
+
570
+
571
+ def is_database_source(config: SourceConfig) -> bool:
572
+ """Check if source config is a database type.
573
+
574
+ Args:
575
+ config: Source configuration
576
+
577
+ Returns:
578
+ True if source is a database connection
579
+ """
580
+ return config.type in {
581
+ "postgres",
582
+ "snowflake",
583
+ "bigquery",
584
+ "redshift",
585
+ "mysql",
586
+ "duckdb",
587
+ }
588
+
589
+
590
+ def is_file_source(config: SourceConfig) -> bool:
591
+ """Check if source config is a file type.
592
+
593
+ Args:
594
+ config: Source configuration
595
+
596
+ Returns:
597
+ True if source is a file-based source
598
+ """
599
+ return config.type in {"csv", "parquet", "json"}
600
+
601
+
602
+ def is_api_source(config: SourceConfig) -> bool:
603
+ """Check if source config is an API type.
604
+
605
+ Args:
606
+ config: Source configuration
607
+
608
+ Returns:
609
+ True if source is an HTTP API
610
+ """
611
+ return config.type == "http"
File without changes