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,283 @@
1
+ """dbt adapter integration for SQL query execution.
2
+
3
+ Stage: EXECUTE (inside RENDER stage)
4
+ Purpose: Execute SQL queries via dbt's adapter system.
5
+
6
+ This adapter leverages dbt's adapter API to execute SQL queries,
7
+ supporting dbt-specific features like ref() and source() resolution.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ import yaml
16
+
17
+ if TYPE_CHECKING:
18
+ from dataface.core.compile.models.source import SourceConfig
19
+
20
+ from dataface.core.compile.jinja import resolve_jinja_template
21
+ from dataface.core.compile.models.face.compiled import VariableValues
22
+ from dataface.core.compile.models.query.compiled import (
23
+ AnyQuery,
24
+ is_sql_query,
25
+ )
26
+ from dataface.core.execute.adapters.base import (
27
+ BaseAdapter,
28
+ QueryParams,
29
+ QueryResult,
30
+ handle_adapter_error,
31
+ )
32
+ from dataface.core.execute.dbt_jinja import has_dbt_jinja
33
+ from dataface.core.execute.sql_guard import validate_select_only
34
+
35
+
36
+ def _read_profiles_yml(dbt_project_path: Path) -> dict[str, Any]:
37
+ """Read and return the profiles.yml nearest to the project.
38
+
39
+ Checks project-local profiles.yml first, then ~/.dbt/profiles.yml.
40
+ Raises FileNotFoundError if neither exists.
41
+ """
42
+ for candidate in (
43
+ dbt_project_path / "profiles.yml",
44
+ Path.home() / ".dbt" / "profiles.yml",
45
+ ):
46
+ if candidate.exists():
47
+ with open(candidate) as f:
48
+ data = yaml.safe_load(f)
49
+ if not isinstance(data, dict):
50
+ raise ValueError(
51
+ f"profiles.yml at {candidate} did not parse to a mapping. "
52
+ f"Got: {type(data).__name__}"
53
+ )
54
+ return data
55
+ raise FileNotFoundError(f"No profiles.yml found in {dbt_project_path} or ~/.dbt/")
56
+
57
+
58
+ def _read_target_dict(
59
+ dbt_project_path: Path, profile_name: str, target_name: str
60
+ ) -> dict[str, Any]:
61
+ """Return the target dict for the given profile+target from profiles.yml.
62
+
63
+ Relative `path:` entries (DuckDB) are resolved against `dbt_project_path`
64
+ so the adapter opens the file dbt would have opened, regardless of CWD.
65
+ """
66
+ profiles = _read_profiles_yml(dbt_project_path)
67
+ profile = profiles.get(profile_name)
68
+ if profile is None:
69
+ available = [k for k in profiles if k != "config"]
70
+ raise ValueError(
71
+ f"Profile '{profile_name}' not found in profiles.yml "
72
+ f"(checked {dbt_project_path}). "
73
+ f"Available profiles: {available}"
74
+ )
75
+ outputs = profile.get("outputs", {})
76
+ target = outputs.get(target_name)
77
+ if target is None:
78
+ raise ValueError(
79
+ f"Target '{target_name}' not found in profile '{profile_name}'. "
80
+ f"Available targets: {list(outputs.keys())}"
81
+ )
82
+ target = dict(target)
83
+ raw_path = target.get("path")
84
+ if isinstance(raw_path, str) and raw_path and raw_path != ":memory:":
85
+ path_obj = Path(raw_path)
86
+ if not path_obj.is_absolute():
87
+ target["path"] = str((dbt_project_path / path_obj).resolve())
88
+ return target
89
+
90
+
91
+ class DbtAdapter(BaseAdapter):
92
+ """Adapter for executing SQL queries via dbt's adapter system.
93
+
94
+ Supported query types: sql
95
+
96
+ Leverages dbt's adapter API to execute SQL queries with support for
97
+ dbt-specific features like ref(), source(), etc.
98
+
99
+ This adapter requires dbt-adapters and a warehouse-specific dbt package
100
+ (e.g. dbt-duckdb, dbt-postgres) — not dbt-core. A valid dbt project
101
+ with profiles.yml configuration is also required.
102
+
103
+ Example:
104
+ >>> adapter = DbtAdapter(dbt_project_path=Path("./my_dbt_project"))
105
+ >>> query = SqlQuery(sql="SELECT * FROM {{ ref('customers') }}")
106
+ >>> result = adapter.execute(query)
107
+ """
108
+
109
+ def __init__(
110
+ self,
111
+ dbt_project_path: Path | None = None,
112
+ profile_name: str | None = None,
113
+ target_name: str | None = None,
114
+ ):
115
+ """Initialize dbt adapter.
116
+
117
+ Args:
118
+ dbt_project_path: Path to dbt project (default: current directory)
119
+ profile_name: dbt profile name (default: from dbt_project.yml)
120
+ target_name: dbt target name (default: DBT_TARGET env var, then 'dev')
121
+ """
122
+ import os
123
+
124
+ self.dbt_project_path = (
125
+ Path(dbt_project_path).resolve()
126
+ if dbt_project_path
127
+ else Path.cwd().resolve()
128
+ )
129
+ self.profile_name = profile_name
130
+ self.target_name = target_name or os.environ.get("DBT_TARGET") or "dev"
131
+ self._adapter: Any = None
132
+ # _dialect is populated by _get_dbt_adapter() — which always runs before
133
+ # any validate_select_only() call site — and `build_adapter` raises if
134
+ # the target dict lacks 'type', so the empty-string default is never
135
+ # observed by the guard in practice.
136
+ self._dialect: str = ""
137
+ self._manifest: dict[str, Any] | None = None
138
+ self._manifest_loaded: bool = False
139
+
140
+ @property
141
+ def supported_types(self) -> set[str]:
142
+ """Return supported query types."""
143
+ return {"sql"}
144
+
145
+ def _can_execute(self, query: AnyQuery) -> bool:
146
+ """Return True if this adapter is certain it can handle the query.
147
+
148
+ Pure predicate — never constructs the adapter, never reads profiles.yml.
149
+ Misconfiguration errors surface in _execute() as QueryResult.error,
150
+ not here in routing.
151
+
152
+ Routing contract:
153
+ - Explicit source (dict or named string) → SqlAdapter owns it
154
+ (it has the inline-source resolver and the named-source registry).
155
+ - No source AND SQL has `{{ ref() }}` / `{{ source() }}` jinja →
156
+ DbtAdapter (needs manifest resolution).
157
+ - No source, no jinja → SqlAdapter (default DuckDB).
158
+ """
159
+ if not is_sql_query(query):
160
+ return False
161
+ if query.source is not None:
162
+ return False
163
+ return has_dbt_jinja(query.sql)
164
+
165
+ def _execute(
166
+ self,
167
+ query: AnyQuery,
168
+ variables: VariableValues | None = None,
169
+ params: QueryParams = None,
170
+ source_config: SourceConfig | None = None,
171
+ ) -> QueryResult:
172
+ """Execute a SQL query via dbt adapter.
173
+
174
+ Args:
175
+ query: AnyQuery object (SqlQuery expected)
176
+ variables: Variable values for Jinja resolution
177
+ params: Accepted for interface compatibility; not used by dbt adapter.
178
+
179
+ Returns:
180
+ QueryResult with data or error
181
+ """
182
+ if not is_sql_query(query):
183
+ return QueryResult(
184
+ data=[],
185
+ error=f"Expected SQL query, got {query.query_type}",
186
+ )
187
+
188
+ try:
189
+ if self._adapter is None:
190
+ self._get_dbt_adapter()
191
+
192
+ resolved_sql = self._resolve_dbt_sql(query.sql, variables)
193
+ validate_select_only(resolved_sql, dialect=self._dialect)
194
+
195
+ with self._adapter.connection_named("dataface_query"):
196
+ _, table = self._adapter.execute(
197
+ resolved_sql, auto_begin=True, fetch=True
198
+ )
199
+
200
+ columns = list(table.column_names)
201
+ rows = list(table.rows)
202
+ if query.limit and query.limit > 0:
203
+ rows = rows[: query.limit]
204
+ data = [dict(zip(columns, row, strict=False)) for row in rows]
205
+ return QueryResult(data=data, columns=columns)
206
+
207
+ except Exception as e: # noqa: BLE001
208
+ return handle_adapter_error("Query execution", e)
209
+
210
+ def _get_dbt_adapter(self) -> Any:
211
+ """Get dbt adapter instance (lazy-loaded).
212
+
213
+ Always loads the manifest first (doesn't require dbt).
214
+ Then delegates adapter construction to build_adapter() in the factory —
215
+ single point of truth for source_config → dbt adapter.
216
+ """
217
+ if self._adapter is not None:
218
+ return self._adapter
219
+
220
+ # Always try to load manifest first — this doesn't require dbt
221
+ self._load_manifest()
222
+
223
+ if not self.profile_name:
224
+ project_config_path = self.dbt_project_path / "dbt_project.yml"
225
+ if project_config_path.exists():
226
+ with open(project_config_path) as f:
227
+ project_dict = yaml.safe_load(f)
228
+ profile_from_project = project_dict.get("profile")
229
+ if not profile_from_project:
230
+ raise ValueError(
231
+ f"dbt_project.yml at {project_config_path} has no 'profile:' key"
232
+ )
233
+ self.profile_name = profile_from_project
234
+ else:
235
+ raise FileNotFoundError(
236
+ f"No dbt_project.yml found at {project_config_path} and no "
237
+ f"profile_name was provided to DbtAdapter"
238
+ )
239
+
240
+ target_dict = _read_target_dict(
241
+ self.dbt_project_path, self.profile_name, self.target_name
242
+ )
243
+ from dataface.core.execute.adapters.dbt_adapter_factory import build_adapter
244
+
245
+ # build_adapter raises ValueError if target_dict has no 'type'; reordering
246
+ # so the build happens first means `target_dict["type"]` is guaranteed
247
+ # populated when we read it.
248
+ self._adapter = build_adapter(target_dict)
249
+ self._dialect = target_dict["type"]
250
+ return self._adapter
251
+
252
+ def _load_manifest(self) -> None:
253
+ """Load dbt manifest from target/ or snapshot file.
254
+
255
+ Tries target/manifest.json first (for development), then falls back
256
+ to manifest.snapshot.json (for production deployments).
257
+ """
258
+ if self._manifest_loaded:
259
+ return # Already attempted (result may be None if no manifest exists)
260
+
261
+ from dataface.core.execute.adapters.dbt_utils import load_dbt_manifest
262
+
263
+ self._manifest = load_dbt_manifest(self.dbt_project_path)
264
+ self._manifest_loaded = True
265
+
266
+ def _resolve_dbt_sql(
267
+ self, sql: str, variables: dict[str, Any] | None = None
268
+ ) -> str:
269
+ """Resolve SQL with dbt-specific Jinja functions."""
270
+ from dataface.core.execute.adapters.dbt_utils import resolve_dbt_refs
271
+
272
+ if has_dbt_jinja(sql) and not self._manifest:
273
+ kind = "ref()" if "ref(" in sql else "source()"
274
+ raise ValueError(
275
+ f"SQL contains {kind} but no dbt manifest found. "
276
+ f"Checked paths: {self.dbt_project_path / 'target' / 'manifest.json'}, "
277
+ f"{self.dbt_project_path / 'manifest.snapshot.json'}"
278
+ )
279
+
280
+ if self._manifest:
281
+ sql = resolve_dbt_refs(sql, self._manifest)
282
+
283
+ return resolve_jinja_template(sql, variables)
@@ -0,0 +1,212 @@
1
+ """Single point of truth for source_config → dbt adapter instantiation.
2
+
3
+ Every consumer that needs a live dbt adapter calls build_adapter().
4
+ The adapter lives as long as the caller holds a reference; GC reclaims
5
+ it automatically via weakref.finalize, which deletes the temp target dir.
6
+ """
7
+
8
+ import importlib
9
+ import logging
10
+ import multiprocessing
11
+ import shutil
12
+ import tempfile
13
+ import weakref
14
+ from types import SimpleNamespace
15
+ from typing import Any, cast
16
+
17
+ from dataface.core.compile.models.source import infer_bq_method
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Mapping: source_config type → (module_path, AdapterClass, CredentialsClass)
22
+ # Add entries here when new warehouse adapters are needed.
23
+ # This is the single source of truth — dbt_adapter.py imports from here.
24
+ _ADAPTER_TYPE_MAP: dict[str, tuple[str, str, str]] = {
25
+ "duckdb": ("dbt.adapters.duckdb", "DuckDBAdapter", "DuckDBCredentials"),
26
+ "postgres": ("dbt.adapters.postgres", "PostgresAdapter", "PostgresCredentials"),
27
+ "postgresql": ("dbt.adapters.postgres", "PostgresAdapter", "PostgresCredentials"),
28
+ "redshift": ("dbt.adapters.redshift", "RedshiftAdapter", "RedshiftCredentials"),
29
+ "snowflake": ("dbt.adapters.snowflake", "SnowflakeAdapter", "SnowflakeCredentials"),
30
+ "bigquery": ("dbt.adapters.bigquery", "BigQueryAdapter", "BigQueryCredentials"),
31
+ "spark": ("dbt.adapters.spark", "SparkAdapter", "SparkCredentials"),
32
+ "databricks": (
33
+ "dbt.adapters.databricks",
34
+ "DatabricksAdapter",
35
+ "DatabricksCredentials",
36
+ ),
37
+ }
38
+
39
+
40
+ _DUCKDB_INITIALIZE_DB_PATCHED = False
41
+ _ORIGINAL_DUCKDB_INITIALIZE_DB: Any = None
42
+
43
+
44
+ def _patched_duckdb_initialize_db(
45
+ cls: type[Any],
46
+ creds: Any,
47
+ plugins: dict[str, Any] | None = None,
48
+ ) -> Any:
49
+ if getattr(creds, "_dft_read_only", False) and creds.path != ":memory:":
50
+ import duckdb
51
+
52
+ config = dict(creds.config_options or {})
53
+ config.setdefault("enable_external_access", False)
54
+ return duckdb.connect(creds.path, read_only=True, config=config)
55
+ return _ORIGINAL_DUCKDB_INITIALIZE_DB(cls, creds, plugins)
56
+
57
+
58
+ def _ensure_duckdb_readonly_initialize_db_patch() -> None:
59
+ """Patch dbt-duckdb's hardcoded read_only=False for schema/introspection paths."""
60
+ global _DUCKDB_INITIALIZE_DB_PATCHED, _ORIGINAL_DUCKDB_INITIALIZE_DB
61
+ if _DUCKDB_INITIALIZE_DB_PATCHED:
62
+ return
63
+
64
+ from dbt.adapters.duckdb import environments as duckdb_envs
65
+
66
+ _orig_cm = cast(Any, duckdb_envs.Environment.initialize_db)
67
+ _ORIGINAL_DUCKDB_INITIALIZE_DB = _orig_cm.__func__
68
+ duckdb_envs.Environment.initialize_db = classmethod( # type: ignore[method-assign,assignment]
69
+ _patched_duckdb_initialize_db
70
+ )
71
+ _DUCKDB_INITIALIZE_DB_PATCHED = True
72
+
73
+
74
+ def build_adapter(source_config: dict[str, Any], *, read_only: bool = False) -> Any:
75
+ """Construct a fresh dbt adapter instance.
76
+
77
+ Args:
78
+ source_config: Dict with at minimum a 'type' key matching a known
79
+ warehouse adapter. Other keys are translated via the credentials
80
+ class's alias map (e.g. BigQuery project→database) and deserialized
81
+ through from_dict, so profile-level extras like threads are dropped.
82
+ read_only: When True, open DuckDB file sources read-only so pure-metadata
83
+ verbs coexist with other readers (e.g. a running ``dft serve``).
84
+ In-memory DuckDB stays read-write. Other warehouses ignore this
85
+ flag until a driver-specific read-only hook exists.
86
+
87
+ Returns:
88
+ A dbt adapter instance. Callers use adapter.connection_named("name")
89
+ as a context manager before calling adapter.execute(...). The temp
90
+ target directory is deleted automatically when the adapter is GC'd.
91
+
92
+ Raises:
93
+ ValueError: 'type' key is missing or names an unsupported adapter.
94
+ ImportError: The dbt adapter package for this warehouse is not installed.
95
+ """
96
+ adapter_type = source_config.get("type")
97
+ if not adapter_type:
98
+ raise ValueError(
99
+ "source_config must have a 'type' field. "
100
+ f"Got: {list(source_config.keys())}"
101
+ )
102
+
103
+ adapter_type_lower = str(adapter_type).lower()
104
+ entry = _ADAPTER_TYPE_MAP.get(adapter_type_lower)
105
+ if entry is None:
106
+ raise ValueError(
107
+ f"Unsupported adapter type '{adapter_type}'. "
108
+ f"Supported: {', '.join(sorted(_ADAPTER_TYPE_MAP))}. "
109
+ f"For other warehouses, install the relevant dbt-<warehouse> package "
110
+ f"and add an entry to _ADAPTER_TYPE_MAP in dbt_adapter_factory.py."
111
+ )
112
+
113
+ module_path, adapter_name, creds_name = entry
114
+
115
+ try:
116
+ mod = importlib.import_module(module_path)
117
+ except ImportError as e:
118
+ raise ImportError(
119
+ f"dbt-{adapter_type_lower} is not installed. "
120
+ f"Install it with: pip install dbt-{adapter_type_lower}"
121
+ ) from e
122
+
123
+ adapter_cls = getattr(mod, adapter_name)
124
+ creds_cls = getattr(mod, creds_name)
125
+
126
+ # Strip 'type', translate profile-style aliases (e.g. BigQuery project→database),
127
+ # then use from_dict so extras like threads are silently dropped.
128
+ creds_kwargs = {k: v for k, v in source_config.items() if k != "type"}
129
+ if adapter_type_lower == "bigquery" and "method" not in creds_kwargs:
130
+ creds_kwargs["method"] = infer_bq_method(
131
+ creds_kwargs.get("keyfile"), creds_kwargs.get("keyfile_json")
132
+ )
133
+ creds_kwargs = creds_cls.translate_aliases(creds_kwargs)
134
+ creds = creds_cls.from_dict(creds_kwargs)
135
+ if read_only and adapter_type_lower == "duckdb":
136
+ _ensure_duckdb_readonly_initialize_db_patch()
137
+ creds._dft_read_only = True
138
+
139
+ # Build a minimal duck-typed config satisfying AdapterRequiredConfig.
140
+ # No dbt_project.yml, no manifest — but enough for dbt's macro context to
141
+ # construct itself, since list_schemas / list_relations / get_columns route
142
+ # through execute_macro -> generate_runtime_macro_context, which reads
143
+ # quoting / dependencies / args / vars / load_dependencies / project_root /
144
+ # get_macro_search_order off the config.
145
+ profile_name = f"dft_{adapter_type_lower}"
146
+ target_path = tempfile.mkdtemp(prefix="dft-target-")
147
+ cfg = SimpleNamespace(
148
+ credentials=creds,
149
+ profile_name=profile_name,
150
+ target_name="dev",
151
+ threads=1,
152
+ project_name=profile_name,
153
+ query_comment=SimpleNamespace(comment="", append=False, job_label=False),
154
+ cli_vars={},
155
+ target_path=target_path,
156
+ project_root=target_path,
157
+ log_cache_events=False,
158
+ quoting={"database": True, "schema": True, "identifier": True},
159
+ dependencies={},
160
+ args=SimpleNamespace(vars={}),
161
+ vars=_NoVars(),
162
+ flags={},
163
+ load_dependencies=dict,
164
+ get_macro_search_order=lambda namespace: None,
165
+ to_target_dict=lambda c=source_config: dict(c),
166
+ )
167
+
168
+ adapter = adapter_cls(cfg, multiprocessing.get_context("spawn"))
169
+ # Register cleanup to fire when the adapter is GC'd.
170
+ # shutil.rmtree is called with ignore_errors=True so a missing dir is skipped.
171
+ weakref.finalize(adapter, shutil.rmtree, target_path, True)
172
+ _bootstrap_macros(adapter, adapter_type_lower)
173
+ return adapter
174
+
175
+
176
+ def _bootstrap_macros(adapter: Any, adapter_type: str) -> None:
177
+ """Wire dbt-bundled macros onto the adapter so introspection works.
178
+
179
+ Without this, every BaseAdapter introspection method (list_schemas,
180
+ list_relations, get_columns_in_relation, calculate_freshness_from_metadata,
181
+ get_partitions_metadata) raises "Macro resolver was None" because
182
+ execute_macro() needs a populated _macro_resolver.
183
+
184
+ Also registers a weakref.proxy of the adapter in dbt's FACTORY so the
185
+ macro context can resolve `adapter` via get_adapter(config) — the macro
186
+ context is constructed in dbt-core's MacroContext.__init__, which calls
187
+ get_adapter(self.config). A weakref keeps GC working: the FACTORY entry
188
+ doesn't pin the adapter, so weakref.finalize cleanup still fires when the
189
+ caller drops their reference. Last build_adapter() call wins per type.
190
+ """
191
+ from dbt.adapters.factory import FACTORY
192
+ from dbt.context.providers import generate_runtime_macro_context
193
+
194
+ from dataface.core.execute.adapters.dbt_macro_loader import load_macro_manifest
195
+
196
+ with FACTORY.lock:
197
+ # weakref.proxy: FACTORY.adapters[type] doesn't pin the adapter, so
198
+ # weakref.finalize cleanup still fires when the caller drops their ref.
199
+ FACTORY.adapters[adapter_type] = weakref.proxy(adapter) # type: ignore[assignment]
200
+ adapter.set_macro_resolver(load_macro_manifest(adapter_type))
201
+ adapter.set_macro_context_generator(generate_runtime_macro_context)
202
+
203
+
204
+ class _NoVars:
205
+ """Stub for `RuntimeConfig.vars` accessed during macro context build.
206
+
207
+ Macros we care about (list_schemas, list_relations, get_columns_in_relation)
208
+ don't reference any project vars, so an empty `vars_for` is the right shape.
209
+ """
210
+
211
+ def vars_for(self, lookup: Any, adapter_type: str) -> dict:
212
+ return {}
@@ -0,0 +1,95 @@
1
+ """Bootstrap a dbt MacroManifest from pip-installed dbt packages.
2
+
3
+ dbt-core's BaseAdapter introspection API (`list_schemas`, `list_relations`,
4
+ `get_columns_in_relation`, ...) routes through `execute_macro()`, which needs
5
+ a populated `_macro_resolver`. Without it, every adapter built by
6
+ `build_adapter()` raises "no macro registered" the moment we try to introspect.
7
+
8
+ This module loads the bundled macros — dbt-core's global project plus the
9
+ active warehouse adapter's package — into a fresh `MacroManifest` that can be
10
+ attached to an adapter via `set_macro_resolver()`. No user files required,
11
+ no `dbt_project.yml`, no `target/manifest.json`. Just pip-installed packages.
12
+
13
+ Coupled to dbt-internal API (`MacroParser`, `MacroManifest`,
14
+ `load_source_file`, `FACTORY.packages`); the smoke tests are the canary for
15
+ upgrades.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from functools import cache
22
+ from pathlib import Path
23
+ from types import SimpleNamespace
24
+ from typing import TYPE_CHECKING
25
+
26
+ if TYPE_CHECKING:
27
+ from dbt.contracts.graph.manifest import MacroManifest
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class DbtMacroLoaderError(RuntimeError):
33
+ """Raised when bootstrapping the dbt MacroManifest fails."""
34
+
35
+
36
+ def _resolve_macro_packages(adapter_type: str) -> list[tuple[str, Path]]:
37
+ """Return [(package_name, package_root), ...] for warehouse + global macros.
38
+
39
+ Uses dbt's adapter FACTORY to resolve include paths the same way dbt-core
40
+ does — by loading the plugin and reading the registered include directory.
41
+ Warehouse package comes first so its macros parse before the global ones,
42
+ matching dbt-core's lookup precedence (warehouse-specific overrides win).
43
+ """
44
+ from dbt.adapters.factory import FACTORY
45
+
46
+ FACTORY.load_plugin(adapter_type)
47
+ plugin = FACTORY.get_plugin_by_name(adapter_type)
48
+ return [
49
+ (plugin.project_name, FACTORY.packages[plugin.project_name]),
50
+ ("dbt", FACTORY.packages["dbt"]),
51
+ ]
52
+
53
+
54
+ @cache
55
+ def load_macro_manifest(adapter_type: str) -> MacroManifest:
56
+ """Build a MacroManifest containing dbt-core + warehouse macros.
57
+
58
+ Cached per adapter_type for the process lifetime — the bundled macros
59
+ don't change between calls within a single session.
60
+ """
61
+ from dbt.contracts.files import ParseFileType
62
+ from dbt.contracts.graph.manifest import MacroManifest, Manifest
63
+ from dbt.parser.macros import MacroParser
64
+ from dbt.parser.read_files import load_source_file
65
+ from dbt.parser.search import FileBlock
66
+
67
+ packages = _resolve_macro_packages(adapter_type)
68
+ manifest = Manifest()
69
+ for package_name, package_root in packages:
70
+ macros_dir = Path(package_root) / "macros"
71
+ if not macros_dir.is_dir():
72
+ raise DbtMacroLoaderError(
73
+ f"dbt {package_name} macros not found at {macros_dir}. "
74
+ f"This usually means the dbt-core or dbt-{adapter_type} "
75
+ f"package layout changed; run the smoke test in "
76
+ f"test_dbt_macro_loader.py to see what dbt-core version "
77
+ f"this code was last validated against."
78
+ )
79
+ # MacroParser only reads project_root / project_name / macro_paths off
80
+ # the project arg, so a SimpleNamespace duck-type is sufficient.
81
+ fake_project = SimpleNamespace(
82
+ project_root=str(package_root),
83
+ project_name=package_name,
84
+ macro_paths=["macros"],
85
+ )
86
+ parser = MacroParser(fake_project, manifest) # type: ignore[arg-type]
87
+ for path in parser.get_paths():
88
+ source_file = load_source_file(path, ParseFileType.Macro, package_name, {})
89
+ assert source_file is not None
90
+ parser.parse_file(FileBlock(source_file))
91
+
92
+ logger.debug(
93
+ "Loaded %d dbt macros for adapter_type=%s", len(manifest.macros), adapter_type
94
+ )
95
+ return MacroManifest(manifest.macros)