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,150 @@
1
+ """Shared dbt utilities for SQL adapters."""
2
+
3
+ import json
4
+ import logging
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from dataface.core.execute.adapters.base import ResolvedRelation, SchemaStatus
10
+
11
+ DBT_PROJECT_DB_NAMES = ["sample.duckdb", "dataface_examples.duckdb", "dev.duckdb"]
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def load_dbt_manifest(project_path: Path) -> dict[str, Any] | None:
16
+ """Load a dbt manifest from target/ or the committed snapshot fallback."""
17
+ manifest_path = project_path / "target" / "manifest.json"
18
+ snapshot_path = project_path / "manifest.snapshot.json"
19
+
20
+ manifest_to_load = None
21
+ if manifest_path.exists():
22
+ manifest_to_load = manifest_path
23
+ elif snapshot_path.exists():
24
+ manifest_to_load = snapshot_path
25
+
26
+ if manifest_to_load is None:
27
+ return None
28
+
29
+ try:
30
+ with open(manifest_to_load) as f:
31
+ return json.load(f)
32
+ except (OSError, json.JSONDecodeError) as e:
33
+ logger.debug("Failed to load dbt manifest from %s: %s", manifest_to_load, e)
34
+ return None
35
+
36
+
37
+ def classify_schema_status(
38
+ dev_schema: str,
39
+ prod_schema: str | None,
40
+ *,
41
+ prod_manifest_available: bool = True,
42
+ ) -> SchemaStatus:
43
+ """Compare dev vs prod schema for a resolved dbt relation."""
44
+ if not prod_manifest_available:
45
+ return "unknown"
46
+ if prod_schema is None:
47
+ return "changed"
48
+ if dev_schema.lower() == prod_schema.lower():
49
+ return "unchanged"
50
+ return "changed"
51
+
52
+
53
+ def _find_model_node(
54
+ manifest: dict[str, Any] | None, model_name: str
55
+ ) -> dict[str, Any] | None:
56
+ if not manifest or "nodes" not in manifest:
57
+ return None
58
+ for node in manifest["nodes"].values():
59
+ if node.get("resource_type") == "model" and node.get("name") == model_name:
60
+ return node
61
+ return None
62
+
63
+
64
+ def _find_source_node(
65
+ manifest: dict[str, Any] | None, source_name: str, table_name: str
66
+ ) -> dict[str, Any] | None:
67
+ if not manifest or "sources" not in manifest:
68
+ return None
69
+ for node in manifest["sources"].values():
70
+ if node.get("source_name") == source_name and node.get("name") == table_name:
71
+ return node
72
+ return None
73
+
74
+
75
+ def _node_schema_and_sql(node: dict[str, Any], fallback_name: str) -> tuple[str, str]:
76
+ """Return (logical_schema_for_provenance, sql_fragment)."""
77
+ schema = str(node.get("schema", "main"))
78
+ if node.get("relation_name"):
79
+ rel = str(node["relation_name"])
80
+ return schema, rel
81
+ alias = str(node.get("alias", fallback_name))
82
+ return schema, f"{schema}.{alias}"
83
+
84
+
85
+ def resolve_dbt_refs_with_provenance(
86
+ sql: str,
87
+ manifest: dict[str, Any],
88
+ prod_manifest: dict[str, Any] | None,
89
+ ) -> tuple[str, list[ResolvedRelation]]:
90
+ """Resolve ref/source in SQL and record dev-vs-prod schema status per relation."""
91
+ relations: list[ResolvedRelation] = []
92
+ prod_available = prod_manifest is not None
93
+
94
+ def resolve_ref(match: re.Match[str]) -> str:
95
+ model_name = match.group(1)
96
+ node = _find_model_node(manifest, model_name)
97
+ if not node:
98
+ return model_name
99
+ schema, fragment = _node_schema_and_sql(node, model_name)
100
+ prod_node = _find_model_node(prod_manifest, model_name)
101
+ prod_schema = (
102
+ _node_schema_and_sql(prod_node, model_name)[0] if prod_node else None
103
+ )
104
+ status = classify_schema_status(
105
+ schema,
106
+ prod_schema,
107
+ prod_manifest_available=prod_available,
108
+ )
109
+ relations.append(
110
+ ResolvedRelation(ref_name=model_name, schema=schema, status=status)
111
+ )
112
+ return fragment
113
+
114
+ sql = re.sub(r"\{\{\s*ref\(['\"]([^'\"]+)['\"]\)\s*\}\}", resolve_ref, sql)
115
+
116
+ def resolve_source(match: re.Match[str]) -> str:
117
+ source_name = match.group(1)
118
+ table_name = match.group(2)
119
+ node = _find_source_node(manifest, source_name, table_name)
120
+ if not node:
121
+ return f"{source_name}.{table_name}"
122
+ schema, fragment = _node_schema_and_sql(node, table_name)
123
+ prod_node = _find_source_node(prod_manifest, source_name, table_name)
124
+ prod_schema = (
125
+ _node_schema_and_sql(prod_node, table_name)[0] if prod_node else None
126
+ )
127
+ status = classify_schema_status(
128
+ schema,
129
+ prod_schema,
130
+ prod_manifest_available=prod_available,
131
+ )
132
+ ref_name = f"{source_name}.{table_name}"
133
+ relations.append(
134
+ ResolvedRelation(ref_name=ref_name, schema=schema, status=status)
135
+ )
136
+ return fragment
137
+
138
+ sql = re.sub(
139
+ r"\{\{\s*source\(['\"]([^'\"]+)['\"],\s*['\"]([^'\"]+)['\"]\)\s*\}\}",
140
+ resolve_source,
141
+ sql,
142
+ )
143
+
144
+ return sql, relations
145
+
146
+
147
+ def resolve_dbt_refs(sql: str, manifest: dict[str, Any]) -> str:
148
+ """Resolve refs/sources in SQL using the dbt manifest."""
149
+ resolved_sql, _ = resolve_dbt_refs_with_provenance(sql, manifest, None)
150
+ return resolved_sql
@@ -0,0 +1,224 @@
1
+ """HTTP adapter for executing REST API queries.
2
+
3
+ Stage: EXECUTE
4
+ Purpose: Execute queries against HTTP/REST API endpoints.
5
+
6
+ This adapter allows fetching data from external APIs, supporting
7
+ GET, POST, PUT, DELETE, and PATCH methods with headers, params, and body.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ import httpx
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_http_query,
25
+ )
26
+ from dataface.core.execute.adapters.base import (
27
+ BaseAdapter,
28
+ QueryParams,
29
+ QueryResult,
30
+ handle_adapter_error,
31
+ )
32
+
33
+
34
+ def _resolve_json_path(data: Any, json_path: str) -> list[Any]:
35
+ """Extract a list from data using a dot-notation JSONPath expression.
36
+
37
+ Supports $.key and $.key.subkey format. Raises ValueError if the path
38
+ is missing or the resolved value is not a list.
39
+ """
40
+ path = json_path.lstrip("$").lstrip(".")
41
+ keys = path.split(".") if path else []
42
+
43
+ node: Any = data
44
+ traversed: list[str] = []
45
+ for key in keys:
46
+ if not isinstance(node, dict) or key not in node:
47
+ location = ".".join(traversed) or "$"
48
+ raise ValueError(
49
+ f"json_path '{json_path}': key '{key}' not found at '{location}'"
50
+ )
51
+ node = node[key]
52
+ traversed.append(key)
53
+
54
+ if not isinstance(node, list):
55
+ raise ValueError(
56
+ f"json_path '{json_path}': expected a list, got {type(node).__name__}"
57
+ )
58
+ return node
59
+
60
+
61
+ class HttpAdapter(BaseAdapter):
62
+ """Adapter for executing HTTP/REST API queries.
63
+
64
+ Supported query types: http
65
+
66
+ Fetches data from REST API endpoints with support for:
67
+ - Multiple HTTP methods (GET, POST, PUT, DELETE, PATCH)
68
+ - Custom headers
69
+ - Query parameters
70
+ - Request body (JSON)
71
+
72
+ Example:
73
+ >>> adapter = HttpAdapter()
74
+ >>> query = HttpQuery(
75
+ ... url="https://api.example.com/users",
76
+ ... headers={"Authorization": "Bearer {{ token }}"}
77
+ ... )
78
+ >>> result = adapter.execute(query, {"token": "xyz"})
79
+ """
80
+
81
+ def __init__(self, timeout: int = 30):
82
+ """Initialize HTTP adapter.
83
+
84
+ Args:
85
+ timeout: Request timeout in seconds
86
+ """
87
+ self.timeout = timeout
88
+
89
+ @property
90
+ def supported_types(self) -> set[str]:
91
+ """Return supported query types."""
92
+ return {"http"}
93
+
94
+ def _execute(
95
+ self,
96
+ query: AnyQuery,
97
+ variables: VariableValues | None = None,
98
+ params: QueryParams = None,
99
+ source_config: SourceConfig | None = None,
100
+ ) -> QueryResult:
101
+ """Execute an HTTP query.
102
+
103
+ Uses type guard for type-safe field access.
104
+
105
+ Args:
106
+ query: AnyQuery object (HttpQuery expected)
107
+ variables: Variable values for Jinja resolution
108
+ params: Not used for HTTP queries (HTTP doesn't use SQL parameterization)
109
+
110
+ Returns:
111
+ QueryResult with data or error
112
+ """
113
+ if not is_http_query(query):
114
+ return QueryResult(
115
+ data=[],
116
+ error=f"Expected HTTP query, got {query.query_type}",
117
+ )
118
+
119
+ url = query.url
120
+
121
+ # Resolve Jinja templates in URL and other fields
122
+ try:
123
+ resolved_url = resolve_jinja_template(url, variables)
124
+ method = (query.method or "GET").upper()
125
+ headers = query.headers or {}
126
+ params_dict: dict[str, Any] = query.params or {}
127
+ body = query.body
128
+
129
+ # Resolve templates in headers, params, body
130
+ resolved_headers: dict[str, Any] = {}
131
+ for k, v in headers.items():
132
+ if isinstance(v, str):
133
+ resolved_headers[k] = resolve_jinja_template(v, variables)
134
+ else:
135
+ resolved_headers[k] = v
136
+
137
+ resolved_params: dict[str, Any] = {}
138
+ for k, v in params_dict.items():
139
+ if isinstance(v, str):
140
+ resolved_params[k] = resolve_jinja_template(v, variables)
141
+ else:
142
+ resolved_params[k] = v
143
+
144
+ resolved_body: Any = None
145
+ if body:
146
+ if isinstance(body, str):
147
+ resolved_body = resolve_jinja_template(body, variables)
148
+ elif isinstance(body, dict):
149
+ resolved_body = {}
150
+ for k, v in body.items():
151
+ if isinstance(v, str):
152
+ resolved_body[k] = resolve_jinja_template(v, variables)
153
+ else:
154
+ resolved_body[k] = v
155
+ else:
156
+ resolved_body = body
157
+
158
+ except (ValueError, KeyError, TypeError) as e:
159
+ return handle_adapter_error("HTTP query template resolution", e)
160
+
161
+ # Execute HTTP request
162
+ try:
163
+ with httpx.Client(timeout=self.timeout) as client:
164
+ if method == "GET":
165
+ response = client.get(
166
+ resolved_url, headers=resolved_headers, params=resolved_params
167
+ )
168
+ elif method == "POST":
169
+ response = client.post(
170
+ resolved_url,
171
+ headers=resolved_headers,
172
+ json=resolved_body,
173
+ params=resolved_params,
174
+ )
175
+ elif method == "PUT":
176
+ response = client.put(
177
+ resolved_url,
178
+ headers=resolved_headers,
179
+ json=resolved_body,
180
+ params=resolved_params,
181
+ )
182
+ elif method == "DELETE":
183
+ response = client.delete(
184
+ resolved_url, headers=resolved_headers, params=resolved_params
185
+ )
186
+ elif method == "PATCH":
187
+ response = client.patch(
188
+ resolved_url,
189
+ headers=resolved_headers,
190
+ json=resolved_body,
191
+ params=resolved_params,
192
+ )
193
+ else:
194
+ return QueryResult(
195
+ data=[],
196
+ error=f"Unsupported HTTP method: {method}",
197
+ )
198
+
199
+ response.raise_for_status()
200
+ data = response.json()
201
+
202
+ if query.json_path is not None:
203
+ try:
204
+ result_data = _resolve_json_path(data, query.json_path)
205
+ except ValueError as e:
206
+ return QueryResult(data=[], error=str(e))
207
+ elif isinstance(data, list):
208
+ result_data = data
209
+ else:
210
+ return QueryResult(
211
+ data=[],
212
+ error=(
213
+ "HTTP response is a JSON object. "
214
+ "Set json_path to extract the data array "
215
+ "(e.g., json_path: $.data)."
216
+ ),
217
+ )
218
+
219
+ return QueryResult(data=result_data)
220
+
221
+ except httpx.HTTPError as e:
222
+ return handle_adapter_error("HTTP request", e)
223
+ except (json.JSONDecodeError, KeyError, TypeError) as e:
224
+ return handle_adapter_error("HTTP query execution", e)
@@ -0,0 +1,94 @@
1
+ """MetricFlow adapter for executing dbt Semantic Layer queries.
2
+
3
+ Stage: EXECUTE
4
+ Purpose: Execute queries against dbt's Semantic Layer using MetricFlow.
5
+
6
+ This adapter enables querying dbt metrics and dimensions using the
7
+ MetricFlow query engine, supporting the dbt Semantic Layer.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import TYPE_CHECKING
13
+
14
+ if TYPE_CHECKING:
15
+ from dataface.core.compile.models.source import SourceConfig
16
+
17
+ from dataface.core.compile.jinja import resolve_query_filters
18
+ from dataface.core.compile.models.face.compiled import VariableValues
19
+ from dataface.core.compile.models.query.compiled import (
20
+ AnyQuery,
21
+ is_metricflow_query,
22
+ )
23
+ from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
24
+
25
+
26
+ class MetricFlowAdapter(BaseAdapter):
27
+ """Adapter for executing MetricFlow (dbt Semantic Layer) queries.
28
+
29
+ Supported query types: metricflow
30
+
31
+ Executes queries against dbt's Semantic Layer using MetricFlow.
32
+ In a full implementation, this uses the MetricFlow Python API or CLI.
33
+
34
+ Example:
35
+ >>> adapter = MetricFlowAdapter()
36
+ >>> query = MetricFlowQuery(
37
+ ... metrics=["revenue", "orders"],
38
+ ... dimensions=["date", "region"]
39
+ ... )
40
+ >>> result = adapter.execute(query)
41
+ """
42
+
43
+ def __init__(self, config_path: str | None = None):
44
+ """Initialize MetricFlow adapter.
45
+
46
+ Args:
47
+ config_path: Optional path to MetricFlow configuration
48
+ """
49
+ self.config_path = config_path
50
+
51
+ @property
52
+ def supported_types(self) -> set[str]:
53
+ """Return supported query types."""
54
+ return {"metricflow"}
55
+
56
+ def _execute(
57
+ self,
58
+ query: AnyQuery,
59
+ variables: VariableValues | None = None,
60
+ params: QueryParams = None,
61
+ source_config: SourceConfig | None = None,
62
+ ) -> QueryResult:
63
+ """Execute a MetricFlow query.
64
+
65
+ Uses type guard for type-safe field access.
66
+
67
+ Args:
68
+ query: AnyQuery object (MetricFlowQuery expected)
69
+ variables: Variable values for filter resolution
70
+ params: Not used for MetricFlow queries (handled differently)
71
+
72
+ Returns:
73
+ QueryResult with data or error
74
+ """
75
+ # Type guard ensures query.metrics is List[str]
76
+ if not is_metricflow_query(query):
77
+ return QueryResult(
78
+ data=[],
79
+ error=f"Expected MetricFlow query, got {query.query_type}",
80
+ )
81
+
82
+ # Resolve filters with variables (for future use)
83
+ filters = query.filters or {}
84
+ _ = resolve_query_filters(filters, variables)
85
+
86
+ # In a real implementation, this would:
87
+ # 1. Construct MetricFlow query object
88
+ # 2. Apply filters
89
+ # 3. Execute via MetricFlow API/CLI
90
+ # 4. Convert results to list of dicts
91
+ return QueryResult(
92
+ data=[],
93
+ error="MetricFlow execution not yet implemented - requires MetricFlow setup",
94
+ )
@@ -0,0 +1,144 @@
1
+ """Schema resolver adapter — queries the LayeredSchemaResolver in-process.
2
+
3
+ Stage: EXECUTE
4
+ Purpose: Dispatch face queries to the LayeredSchemaResolver without a subprocess.
5
+
6
+ Routing by field population:
7
+ - source only → list_schemas(source)
8
+ - source + schema → list_tables(source, schema)
9
+ - source + schema + table → profile_table → column rows
10
+ - source + schema + table + column → profile_column → single column row
11
+
12
+ Field-prerequisite and Jinja-rejection invariants are enforced at compile time
13
+ by SchemaResolverQuery's Pydantic validators — the adapter trusts them.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import TYPE_CHECKING, Any
19
+
20
+ if TYPE_CHECKING:
21
+ from dataface.core.compile.models.source import SourceConfig
22
+
23
+ from dataface.core.compile.models.face.compiled import VariableValues
24
+ from dataface.core.compile.models.query.compiled import (
25
+ AnyQuery,
26
+ is_schema_resolver_query,
27
+ )
28
+ from dataface.core.execute.adapters.base import BaseAdapter, QueryParams, QueryResult
29
+
30
+ if TYPE_CHECKING:
31
+ from dataface.core.execute.adapters import AdapterRegistry
32
+
33
+
34
+ class SchemaResolverAdapter(BaseAdapter):
35
+ """Adapter for in-process schema resolver queries.
36
+
37
+ Supported query types: schema_resolver
38
+
39
+ Dispatches to list_schemas / list_tables / profile_table / profile_column
40
+ on the LayeredSchemaResolver based on which fields are populated.
41
+ """
42
+
43
+ def __init__(self, adapter_registry: AdapterRegistry) -> None:
44
+ self._registry = adapter_registry
45
+
46
+ @property
47
+ def supported_types(self) -> set[str]:
48
+ return {"schema_resolver"}
49
+
50
+ def _execute(
51
+ self,
52
+ query: AnyQuery,
53
+ variables: VariableValues | None = None,
54
+ params: QueryParams = None,
55
+ source_config: SourceConfig | None = None,
56
+ ) -> QueryResult:
57
+ if not is_schema_resolver_query(query):
58
+ return QueryResult(
59
+ data=[],
60
+ error=f"Expected schema_resolver query, got {query.query_type}",
61
+ )
62
+
63
+ from dataface.core.inspect.cache_factory import build_resolver # noqa: PLC0415
64
+
65
+ resolver = build_resolver(self._registry)
66
+
67
+ source = query.source
68
+ schema = query.schema_name
69
+ table = query.table
70
+ column = query.column
71
+
72
+ if schema is None:
73
+ envelope = resolver.list_schemas(source)
74
+ schemas: dict[str, Any] = (
75
+ envelope.get("sources", {}).get(source, {}).get("schemas", {})
76
+ )
77
+ rows = [{**m, "name": n} for n, m in schemas.items()]
78
+ return QueryResult(data=query.apply_limit(rows))
79
+
80
+ if table is None:
81
+ envelope = resolver.list_tables(source, schema)
82
+ source_schemas: dict[str, Any] = (
83
+ envelope.get("sources", {}).get(source, {}).get("schemas", {})
84
+ )
85
+ if schema not in source_schemas:
86
+ return QueryResult(
87
+ data=[],
88
+ error=f"Schema {schema!r} not found in source {source!r}",
89
+ )
90
+ tables: dict[str, Any] = source_schemas[schema].get("tables", {})
91
+ rows = [{**m, "name": n} for n, m in tables.items()]
92
+ return QueryResult(data=query.apply_limit(rows))
93
+
94
+ if column is None:
95
+ envelope = resolver.profile_table(source, schema, table)
96
+ all_tables: dict[str, Any] = (
97
+ envelope.get("sources", {})
98
+ .get(source, {})
99
+ .get("schemas", {})
100
+ .get(schema, {})
101
+ .get("tables", {})
102
+ )
103
+ if table not in all_tables:
104
+ return QueryResult(
105
+ data=[],
106
+ error=f"Table {schema}.{table} not found in source {source!r}",
107
+ )
108
+ columns: dict[str, Any] = all_tables[table].get("columns") or {}
109
+ rows = [{**m, "name": n} for n, m in columns.items()]
110
+ return QueryResult(data=query.apply_limit(rows))
111
+
112
+ # column specified → profile_column
113
+ envelope = resolver.profile_column(source, schema, table, column)
114
+ col_map: dict[str, Any] = (
115
+ envelope.get("sources", {})
116
+ .get(source, {})
117
+ .get("schemas", {})
118
+ .get(schema, {})
119
+ .get("tables", {})
120
+ .get(table, {})
121
+ .get("columns")
122
+ or {}
123
+ )
124
+ if column not in col_map:
125
+ # The resolver drops the schemas key for both "table missing" and
126
+ # "column missing" — probe profile_table to emit the right message.
127
+ table_env = resolver.profile_table(source, schema, table)
128
+ table_found = table in (
129
+ table_env.get("sources", {})
130
+ .get(source, {})
131
+ .get("schemas", {})
132
+ .get(schema, {})
133
+ .get("tables", {})
134
+ )
135
+ if not table_found:
136
+ return QueryResult(
137
+ data=[],
138
+ error=f"Table {schema}.{table} not found in source {source!r}",
139
+ )
140
+ return QueryResult(
141
+ data=[],
142
+ error=f"Column {column!r} not found in {schema}.{table}",
143
+ )
144
+ return QueryResult(data=[{**col_map[column], "name": column}])