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,92 @@
1
+ """StructuredError wire shape and build_structured helper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from dataface.core.errors.registry import ErrorCode
10
+
11
+
12
+ class NextCommand(BaseModel):
13
+ label: str
14
+ command: str
15
+
16
+
17
+ class StructuredError(BaseModel):
18
+ code: str
19
+ message: str
20
+ domain: str
21
+ fields: dict[str, Any] = {}
22
+ file: str | None = None
23
+ line: int | None = None
24
+ column: int | None = None
25
+ field_path: str | None = None
26
+ hint: str | None = None
27
+ doc_url: str
28
+ docs_topic: str | None = None
29
+ next_commands: list[NextCommand] = []
30
+ # Nested when __cause__ is itself a DatafaceError (preserves code/domain/
31
+ # doc_url through the chain); a bare string when __cause__ is a foreign
32
+ # exception type. None when there is no cause.
33
+ cause: StructuredError | str | None = None
34
+
35
+
36
+ def build_structured(exc: Any, *, file: str | None = None) -> StructuredError:
37
+ """Build a StructuredError from any DatafaceError subclass.
38
+
39
+ The exc must have a populated .code attribute (an ErrorCode instance).
40
+ Raises RuntimeError if .code is None — that path should never happen once
41
+ every subclass __init__ stamps the legacy fallback code; raise (not
42
+ assert) so the guard survives `python -O`.
43
+ """
44
+ ec = getattr(exc, "code", None)
45
+ if not isinstance(ec, ErrorCode):
46
+ raise RuntimeError(
47
+ f"build_structured called on {type(exc).__name__!r} whose .code is "
48
+ f"{type(ec).__name__!r}, not an ErrorCode. Every DatafaceError "
49
+ "subclass must stamp a code at construction."
50
+ )
51
+
52
+ fields: dict[str, Any] = getattr(exc, "fields", {})
53
+
54
+ hint: str | None = None
55
+ if ec.hint_generator is not None:
56
+ hint = ec.hint_generator(**fields)
57
+
58
+ next_commands: list[NextCommand] = []
59
+ if file:
60
+ next_commands.append(
61
+ NextCommand(label="Validate", command=f"dft validate {file}")
62
+ )
63
+
64
+ cause: StructuredError | str | None = None
65
+ raw_cause = exc.__cause__
66
+ if raw_cause is not None:
67
+ # Recurse for our own DatafaceError chain so structured consumers
68
+ # keep code/domain/doc_url; flatten foreign exceptions to a string.
69
+ # Use isinstance(ErrorCode) — duck-typing on `.code is not None`
70
+ # would falsely recurse into foreign exceptions like
71
+ # urllib.error.HTTPError that carry a non-ErrorCode `.code` attr.
72
+ raw_code = getattr(raw_cause, "code", None)
73
+ if isinstance(raw_code, ErrorCode):
74
+ cause = build_structured(raw_cause)
75
+ else:
76
+ cause = str(raw_cause)
77
+
78
+ return StructuredError(
79
+ code=ec.code,
80
+ message=str(exc),
81
+ domain=ec.domain,
82
+ fields=fields,
83
+ file=file,
84
+ line=getattr(exc, "line", None),
85
+ column=getattr(exc, "column", None),
86
+ field_path=getattr(exc, "field_path", None),
87
+ hint=hint,
88
+ doc_url=ec.doc_url,
89
+ docs_topic=ec.docs_topic,
90
+ next_commands=next_commands,
91
+ cause=cause,
92
+ )
@@ -0,0 +1,91 @@
1
+ """Query execution module.
2
+
3
+ Stage: EXECUTE (Service)
4
+ Purpose: Execute queries for compiled datafaces.
5
+
6
+ This is a SERVICE module, not a sequential stage. It's called lazily
7
+ by the render module when charts need data.
8
+
9
+ Entry Points:
10
+ - Executor: Main executor class
11
+ - Executor.execute_query(query_name, variables) -> List[Dict]
12
+ - Executor.execute_chart(chart, variables) -> List[Dict]
13
+
14
+ Inputs:
15
+ - Face: Face with compiled queries
16
+ - Query name and variables
17
+
18
+ Outputs:
19
+ - List[Dict[str, Any]]: Query results (list of row dictionaries)
20
+
21
+ Dependencies:
22
+ - dataface.compile (for Face, AnyQuery types)
23
+
24
+ This module can import from compile/ (needs types) but should NOT
25
+ import from render/ (render imports us, not vice versa).
26
+ """
27
+
28
+ from dataface.core.execute.batch import (
29
+ TEMP_TABLE_PREFIX,
30
+ BatchStatement,
31
+ CrossSourceReferenceError,
32
+ DependencyGraph,
33
+ TempTableNotSupportedError,
34
+ build_dependency_graph,
35
+ collect_face_queries,
36
+ create_batch_execution_plan,
37
+ extract_query_refs,
38
+ generate_batch_sql,
39
+ group_by_source,
40
+ topological_sort,
41
+ validate_cross_source_references,
42
+ )
43
+ from dataface.core.execute.cache_backend import CachedQueryFailure, QueryResultCache
44
+ from dataface.core.execute.cache_keys import SECRET_FIELDS, source_identity
45
+ from dataface.core.execute.duckdb_cache import (
46
+ DuckDBCache,
47
+ compute_query_hash,
48
+ compute_source_hash,
49
+ compute_variables_hash,
50
+ )
51
+ from dataface.core.execute.errors import (
52
+ AdapterError,
53
+ ExecutionError,
54
+ QueryError,
55
+ QueryTimeoutError,
56
+ )
57
+ from dataface.core.execute.executor import Executor
58
+
59
+ __all__ = [
60
+ # Executor
61
+ "Executor",
62
+ # Cache Protocol
63
+ "QueryResultCache",
64
+ # DuckDB Cache
65
+ "CachedQueryFailure",
66
+ "DuckDBCache",
67
+ "SECRET_FIELDS",
68
+ "compute_query_hash",
69
+ "compute_source_hash",
70
+ "compute_variables_hash",
71
+ "source_identity",
72
+ # Errors
73
+ "ExecutionError",
74
+ "QueryError",
75
+ "QueryTimeoutError",
76
+ "AdapterError",
77
+ "CrossSourceReferenceError",
78
+ "TempTableNotSupportedError",
79
+ # Batch execution
80
+ "BatchStatement",
81
+ "DependencyGraph",
82
+ "TEMP_TABLE_PREFIX",
83
+ "build_dependency_graph",
84
+ "collect_face_queries",
85
+ "create_batch_execution_plan",
86
+ "extract_query_refs",
87
+ "generate_batch_sql",
88
+ "group_by_source",
89
+ "topological_sort",
90
+ "validate_cross_source_references",
91
+ ]
@@ -0,0 +1,49 @@
1
+ """Data source adapters.
2
+
3
+ Stage: EXECUTE
4
+ Purpose: Provide adapters for different data sources.
5
+
6
+ Available Adapters:
7
+ - SqlAdapter: Raw SQL queries
8
+ - CsvAdapter: CSV file queries
9
+ - HttpAdapter: REST API queries
10
+ - DbtAdapter: dbt-integrated SQL queries
11
+ - MetricFlowAdapter: dbt Semantic Layer queries
12
+ """
13
+
14
+ from dataface.core.execute.adapters.adapter_registry import (
15
+ LOCAL_AUTHORING_REGISTRY_KWARGS,
16
+ AdapterRegistry,
17
+ build_adapter_registry,
18
+ )
19
+ from dataface.core.execute.adapters.base import (
20
+ BaseAdapter,
21
+ QueryResult,
22
+ handle_adapter_error,
23
+ )
24
+ from dataface.core.execute.adapters.csv_adapter import CsvAdapter
25
+ from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
26
+ from dataface.core.execute.adapters.http_adapter import HttpAdapter
27
+ from dataface.core.execute.adapters.metricflow_adapter import MetricFlowAdapter
28
+ from dataface.core.execute.adapters.schema_resolver_adapter import SchemaResolverAdapter
29
+ from dataface.core.execute.adapters.sql_adapter import SqlAdapter
30
+ from dataface.core.execute.adapters.values_adapter import ValuesAdapter
31
+
32
+ __all__ = [
33
+ # Base
34
+ "BaseAdapter",
35
+ "QueryResult",
36
+ "handle_adapter_error",
37
+ # Registry
38
+ "AdapterRegistry",
39
+ "LOCAL_AUTHORING_REGISTRY_KWARGS",
40
+ "build_adapter_registry",
41
+ # Adapters
42
+ "SqlAdapter",
43
+ "CsvAdapter",
44
+ "HttpAdapter",
45
+ "DbtAdapter",
46
+ "MetricFlowAdapter",
47
+ "ValuesAdapter",
48
+ "SchemaResolverAdapter",
49
+ ]
@@ -0,0 +1,400 @@
1
+ """Registry for query adapters.
2
+
3
+ Stage: EXECUTE
4
+ Purpose: Manage and select appropriate adapters for query execution.
5
+
6
+ The AdapterRegistry maintains a list of available adapters and uses
7
+ type-based routing via the unified query interface.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from dataface.core.compile.errors import DatafaceError
16
+ from dataface.core.compile.models.face.compiled import VariableValues
17
+ from dataface.core.compile.models.query.compiled import AnyQuery
18
+ from dataface.core.errors.codes_execute import (
19
+ DF_EXECUTE_NO_DEFAULT_SOURCE,
20
+ DF_EXECUTE_SOURCE_NOT_FOUND,
21
+ DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY,
22
+ )
23
+ from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
24
+ from dataface.core.execute.source_registry import SourceRegistry
25
+ from dataface.core.execute.source_resolver import DbtContext, DefaultSourceResolver
26
+
27
+ if TYPE_CHECKING:
28
+ from dataface.core.compile.models.face.compiled import Face
29
+ from dataface.core.execute.source_resolver import SourceResolver
30
+
31
+ # Approved local-dev surfaces that need non-exclusive DuckDB locks AND SQL
32
+ # read_csv/read_json_auto (see m3-duckdb-concurrency-and-file-sources initiative).
33
+ # Grep this symbol before adding allow_external_access_in_readonly=True elsewhere.
34
+ LOCAL_AUTHORING_REGISTRY_KWARGS: dict[str, Any] = {
35
+ "read_only": True,
36
+ "allow_external_access_in_readonly": True,
37
+ "duckdb_config": {"enable_external_access": True},
38
+ }
39
+
40
+
41
+ def build_adapter_registry(
42
+ project_root: Path,
43
+ *,
44
+ read_only: bool = True,
45
+ allowed_types: set[str] | None = None,
46
+ dbt_project_path: Path | None = None,
47
+ connection_string: str | None = None,
48
+ profile_type: str = "duckdb",
49
+ target: str | None = None,
50
+ duckdb_config: dict[str, Any] | None = None,
51
+ resolver: SourceResolver | None = None,
52
+ allow_external_access_in_readonly: bool = False,
53
+ ) -> AdapterRegistry:
54
+ """Build an AdapterRegistry with standard adapters.
55
+
56
+ This is the single canonical way to create an adapter registry.
57
+ All entry points should use this instead of hand-wiring adapters.
58
+
59
+ Args:
60
+ project_root: Root directory for resolving relative file paths.
61
+ read_only: Whether SQL adapters should be read-only (default True).
62
+ allowed_types: If set, only register adapters whose supported_types
63
+ intersect with this set. None means register all.
64
+ dbt_project_path: Explicit dbt project path. If None, auto-discovers
65
+ by looking for dbt_project.yml in/above project_root.
66
+ connection_string: Override SQL connection string.
67
+ profile_type: SQL dialect (default "duckdb").
68
+ target: dbt target name override.
69
+ duckdb_config: Optional DuckDB config dict passed to the default
70
+ connection. When read_only=True (the default) and
71
+ allow_external_access_in_readonly=False, enable_external_access is
72
+ hard-forced to False regardless of what this dict contains. To combine
73
+ read_only=True with external file access (read_csv, httpfs, etc.), set
74
+ both allow_external_access_in_readonly=True AND
75
+ duckdb_config={"enable_external_access": True}. The default path
76
+ (read_only=False) also allows external access without the flag.
77
+ resolver: Source resolver instance. Defaults to DefaultSourceResolver when
78
+ None. Pass an AllowlistedSourceResolver for deployed-mode enforcement.
79
+ allow_external_access_in_readonly: Security opt-in; passed through to
80
+ SqlAdapter. See SqlAdapter docstring for full semantics. Default False
81
+ preserves existing behavior. Approved callsites passing True are listed
82
+ on LOCAL_AUTHORING_REGISTRY_KWARGS in this module (playground, MCP,
83
+ chat, serve).
84
+ """
85
+ from dataface.core.execute.adapters.csv_adapter import CsvAdapter
86
+ from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
87
+ from dataface.core.execute.adapters.http_adapter import HttpAdapter
88
+ from dataface.core.execute.adapters.metricflow_adapter import MetricFlowAdapter
89
+ from dataface.core.execute.adapters.sql_adapter import SqlAdapter
90
+ from dataface.core.execute.adapters.values_adapter import ValuesAdapter
91
+
92
+ registry = AdapterRegistry(project_root=project_root, resolver=resolver)
93
+
94
+ def _should_register(types: set[str]) -> bool:
95
+ return allowed_types is None or bool(types & allowed_types)
96
+
97
+ # Auto-discover dbt project if not explicitly provided
98
+ resolved_dbt_path = dbt_project_path
99
+ if resolved_dbt_path is None:
100
+ candidate = project_root.resolve()
101
+ while True:
102
+ if (candidate / "dbt_project.yml").exists():
103
+ resolved_dbt_path = candidate
104
+ break
105
+ parent = candidate.parent
106
+ if parent == candidate:
107
+ break
108
+ candidate = parent
109
+
110
+ # DbtAdapter first (handles SQL queries with dbt features, takes priority)
111
+ if resolved_dbt_path is not None and _should_register({"sql"}):
112
+ registry.register(
113
+ DbtAdapter(
114
+ dbt_project_path=resolved_dbt_path,
115
+ target_name=target,
116
+ )
117
+ )
118
+
119
+ # SqlAdapter (fallback for plain SQL)
120
+ if _should_register({"sql"}):
121
+ registry.register(
122
+ SqlAdapter(
123
+ dbt_project_path=str(resolved_dbt_path) if resolved_dbt_path else None,
124
+ connection_string=connection_string,
125
+ project_root=project_root,
126
+ profile_type=profile_type,
127
+ read_only=read_only,
128
+ duckdb_config=duckdb_config,
129
+ allow_external_access_in_readonly=allow_external_access_in_readonly,
130
+ )
131
+ )
132
+
133
+ # Lazy adapters — zero cost until actually called
134
+ if _should_register({"metricflow"}):
135
+ registry.register(MetricFlowAdapter())
136
+ if _should_register({"http"}):
137
+ registry.register(HttpAdapter())
138
+
139
+ # File-based adapters
140
+ if _should_register({"csv"}):
141
+ registry.register(CsvAdapter(project_root=project_root))
142
+ if _should_register({"values"}):
143
+ registry.register(ValuesAdapter())
144
+
145
+ if _should_register({"schema_resolver"}):
146
+ from dataface.core.execute.adapters.schema_resolver_adapter import (
147
+ SchemaResolverAdapter,
148
+ )
149
+
150
+ registry.register(SchemaResolverAdapter(registry))
151
+
152
+ return registry
153
+
154
+
155
+ class AdapterRegistry:
156
+ """Registry for managing and selecting query adapters.
157
+
158
+ The registry uses type-based routing from the unified query interface.
159
+ Adapters declare their supported types via the `supported_types` property,
160
+ and the registry routes queries to the appropriate adapter.
161
+
162
+ Priority order:
163
+ 1. First registered adapter that supports the query type wins
164
+ 2. dbt adapter is registered first for SQL (with dbt features)
165
+ 3. Fallback SQL adapter for plain SQL without dbt
166
+
167
+ Example:
168
+ >>> from dataface.core.execute.adapters import build_adapter_registry
169
+ >>> registry = build_adapter_registry(Path("."))
170
+ >>> result = registry.execute(SqlQuery(sql="SELECT 1"))
171
+ """
172
+
173
+ def __init__(
174
+ self,
175
+ project_root: Path | None = None,
176
+ resolver: SourceResolver | None = None,
177
+ ) -> None:
178
+ """Initialize adapter registry.
179
+
180
+ Use ``build_adapter_registry()`` to get a fully configured registry.
181
+
182
+ Args:
183
+ project_root: Root directory for resolving relative file paths.
184
+ resolver: Source resolver instance. Defaults to DefaultSourceResolver.
185
+ """
186
+ self._adapters: list[BaseAdapter] = []
187
+ self._type_index: dict[str, list[BaseAdapter]] = {}
188
+ self._project_root = project_root
189
+ self._sources = SourceRegistry(project_root=project_root)
190
+ self._resolver: SourceResolver = (
191
+ resolver if resolver is not None else DefaultSourceResolver()
192
+ )
193
+
194
+ @property
195
+ def project_root(self) -> Path | None:
196
+ """Return the registry's configured project root, if any."""
197
+ return self._project_root
198
+
199
+ def register(self, adapter: BaseAdapter) -> None:
200
+ """Register an adapter.
201
+
202
+ Adapters are indexed by their supported types for fast lookup.
203
+
204
+ Args:
205
+ adapter: Adapter instance to register
206
+ """
207
+ self._adapters.append(adapter)
208
+
209
+ # Update type index
210
+ for query_type in adapter.supported_types:
211
+ if query_type not in self._type_index:
212
+ self._type_index[query_type] = []
213
+ self._type_index[query_type].append(adapter)
214
+
215
+ def close(self) -> None:
216
+ """Close adapters that hold external resources (e.g. DuckDB file handles)."""
217
+ for adapter in self._adapters:
218
+ close = getattr(adapter, "close", None)
219
+ if close is not None:
220
+ close()
221
+
222
+ def get_adapters_for_type(self, query_type: str) -> list[BaseAdapter]:
223
+ """Get all adapters that support a given query type.
224
+
225
+ Args:
226
+ query_type: Query type string (e.g., "sql", "csv")
227
+
228
+ Returns:
229
+ List of adapters supporting this type (in registration order)
230
+ """
231
+ return self._type_index.get(query_type, [])
232
+
233
+ def get_adapter(self, query: AnyQuery) -> BaseAdapter | None:
234
+ """Get an adapter that can execute the given query.
235
+
236
+ Uses type-based routing for fast lookup, then checks additional
237
+ eligibility via can_execute().
238
+
239
+ Args:
240
+ query: AnyQuery object
241
+
242
+ Returns:
243
+ Adapter instance or None if no adapter can execute the query
244
+ """
245
+ # Fast path: lookup by type
246
+ candidates = self.get_adapters_for_type(query.query_type)
247
+
248
+ for adapter in candidates:
249
+ if adapter.can_execute(query):
250
+ return adapter
251
+
252
+ # Fallback: check all adapters (for adapters with custom can_execute)
253
+ for adapter in self._adapters:
254
+ if adapter not in candidates and adapter.can_execute(query):
255
+ return adapter
256
+
257
+ return None
258
+
259
+ def register_source(self, name: str, config: dict[str, Any]) -> None:
260
+ """Register a synthetic source. Idempotent: first registration wins."""
261
+ self._sources.register(name, config)
262
+
263
+ def _derive_dbt_context(self) -> DbtContext | None:
264
+ from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
265
+
266
+ for adapter in self._adapters:
267
+ if isinstance(adapter, DbtAdapter):
268
+ return DbtContext()
269
+ return None
270
+
271
+ def execute(
272
+ self,
273
+ query: AnyQuery,
274
+ variables: VariableValues | None = None,
275
+ params: QueryParams = None,
276
+ *,
277
+ face: Face | None = None,
278
+ dbt_context: DbtContext | None = None,
279
+ ) -> QueryResult:
280
+ """Execute a query using the appropriate adapter.
281
+
282
+ Args:
283
+ query: AnyQuery object
284
+ variables: Variable values for query resolution
285
+ params: Optional pre-computed parameter values for parameterized execution.
286
+ When provided, the adapter should use these params directly instead
287
+ of re-processing variables. This is used by batch execution where
288
+ parameterization happens before adapter execution.
289
+ face: Compiled face; used to extract face-level sources for resolution.
290
+ dbt_context: Explicit dbt context override. When None, derived from
291
+ registered adapters.
292
+
293
+ Returns:
294
+ QueryResult with data or error
295
+ """
296
+ adapter = self.get_adapter(query)
297
+ if not adapter:
298
+ return QueryResult(
299
+ data=[],
300
+ error=f"No adapter found for query type: {query.query_type}",
301
+ )
302
+
303
+ authored = getattr(query, "source", None)
304
+ face_sources: dict[str, dict[str, Any]] = (
305
+ face.sources if face is not None else {}
306
+ )
307
+ project_sources = self._sources.project_sources_config()
308
+ if dbt_context is None:
309
+ dbt_context = self._derive_dbt_context()
310
+
311
+ try:
312
+ source_config = self._resolver.resolve(
313
+ authored=authored,
314
+ face_sources=face_sources,
315
+ project_sources=project_sources,
316
+ dbt_context=dbt_context,
317
+ )
318
+ except DatafaceError as exc:
319
+ return QueryResult(data=[], error=str(exc))
320
+
321
+ return adapter.execute(query, variables, params, source_config=source_config)
322
+
323
+ @property
324
+ def adapters(self) -> list[BaseAdapter]:
325
+ """Get all registered adapters."""
326
+ return list(self._adapters)
327
+
328
+ @property
329
+ def supported_types(self) -> set[str]:
330
+ """Get all query types supported by registered adapters.
331
+
332
+ Returns:
333
+ Set of query type strings
334
+ """
335
+ return set(self._type_index.keys())
336
+
337
+ def list_sql_sources(self) -> list[dict[str, Any]]:
338
+ """Return configured SQL sources from the project source registry."""
339
+ sources: dict[str, dict[str, Any]] = {}
340
+ for name, config in self._sources.all().items():
341
+ source_type = str(config.get("type", "unknown"))
342
+ source_info: dict[str, Any] = {"name": name, "type": source_type}
343
+ db_path = config.get("path")
344
+ if db_path:
345
+ source_info["path"] = str(db_path)
346
+ if source_type == "duckdb" and str(db_path) == ":memory:":
347
+ source_info["in_memory"] = True
348
+ sources[name] = source_info
349
+ return [sources[name] for name in sorted(sources)]
350
+
351
+ def resolve_source_config(self, source: str | None = None) -> dict[str, Any]:
352
+ """Resolve a full source_config dict for use with InspectConnection.
353
+
354
+ Returns the warehouse-specific dict (type + credentials).
355
+
356
+ Args:
357
+ source: Named source profile. When None, falls back to the
358
+ registry's default SqlAdapter — but only if it's a DuckDB
359
+ adapter (the connection_string is :memory: or a .duckdb path).
360
+ For non-DuckDB SqlAdapters, raises DatafaceError because
361
+ reconstructing config from (connection_string, profile_type)
362
+ drops credentials, host, port, keyfile_json, etc.
363
+
364
+ Returns:
365
+ source_config dict with at least a 'type' key.
366
+
367
+ Raises:
368
+ DatafaceError: DF-EXECUTE-NO-DEFAULT-SOURCE if source is None and
369
+ the default adapter is non-DuckDB; DF-EXECUTE-SOURCE-NOT-FOUND
370
+ if the named source is missing but others are configured;
371
+ DF-EXECUTE-SOURCE-NOT-FOUND-EMPTY if no sources are configured.
372
+ """
373
+ from dataface.core.execute.adapters.sql_adapter import SqlAdapter
374
+
375
+ if source is None:
376
+ # No-source fallback only works for DuckDB SqlAdapters — they have
377
+ # no credentials, host, port, keyfile_json to lose.
378
+ for adapter in self._adapters:
379
+ if isinstance(adapter, SqlAdapter) and adapter.profile_type == "duckdb":
380
+ # Inline DuckDB source config: matches what source_config_from_url
381
+ # returns for the duckdb branch ({"type": "duckdb", "path": ...}).
382
+ # Handles all DuckDB URL shapes: file paths, :memory:, md: (MotherDuck).
383
+ # The loop already filters to profile_type=="duckdb" so no dialect
384
+ # ambiguity; the inline form avoids importing from the private package.
385
+ return {
386
+ "type": "duckdb",
387
+ "path": adapter.connection_string,
388
+ }
389
+ raise DatafaceError.from_code(DF_EXECUTE_NO_DEFAULT_SOURCE)
390
+
391
+ config = self._sources.get(source)
392
+ if config and config.get("type"):
393
+ return config
394
+
395
+ available = ", ".join(item["name"] for item in self.list_sql_sources())
396
+ if available:
397
+ raise DatafaceError.from_code(
398
+ DF_EXECUTE_SOURCE_NOT_FOUND, source=source, available=available
399
+ )
400
+ raise DatafaceError.from_code(DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY, source=source)