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,162 @@
1
+ """Query execution helpers for the two-phase render pipeline.
2
+
3
+ Phase 1: Execute chart-direct queries that do *not* use ``{{ results.X }}``
4
+ concurrently with ``ThreadPoolExecutor``.
5
+
6
+ Phase 2: Execute chart-direct ``{{ results.X }}`` queries sequentially after
7
+ phase 1, before the render walk begins.
8
+
9
+ Only queries directly referenced by charts are pre-executed. Transitive
10
+ ``{{ queries.X }}`` dependencies are *not* pre-executed separately because
11
+ the executor inlines them as subqueries — executing the dependency query on
12
+ its own would be wasted work.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ import re
19
+ from concurrent.futures import Future, ThreadPoolExecutor, as_completed
20
+ from typing import TYPE_CHECKING
21
+
22
+ from dataface.core.compile.models.face.compiled import Face, VariableValues
23
+ from dataface.core.compile.models.query.compiled import (
24
+ AnyQuery,
25
+ is_sql_query,
26
+ )
27
+ from dataface.core.execute.batch import DependencyGraph, topological_sort
28
+
29
+ if TYPE_CHECKING:
30
+ from dataface.core.execute.executor import Executor
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ _RESULTS_REF_PATTERN = re.compile(r"\{\{\s*results\.(\w+)\s*\}\}")
35
+
36
+
37
+ def _has_results_refs(query: AnyQuery) -> bool:
38
+ """Check if a SQL query references ``{{ results.X }}``."""
39
+ if not is_sql_query(query):
40
+ return False
41
+ return "{{ results." in query.sql or "{{results." in query.sql
42
+
43
+
44
+ def _extract_results_refs(query: AnyQuery) -> set[str]:
45
+ """Extract ``{{ results.X }}`` references from a SQL query."""
46
+ if not is_sql_query(query):
47
+ return set()
48
+ return set(_RESULTS_REF_PATTERN.findall(query.sql))
49
+
50
+
51
+ def plan_query_execution_phases(
52
+ face: Face,
53
+ query_names: set[str],
54
+ ) -> tuple[set[str], list[str]]:
55
+ """Split chart-direct queries into parallel and sequential execution phases.
56
+
57
+ Queries that reference ``{{ results.X }}`` must run after the parallel-safe
58
+ phase because they depend on previously executed query results in DuckDB.
59
+ These queries still run before rendering; they are not deferred as a render
60
+ time fallback.
61
+ """
62
+ parallel_names: set[str] = set()
63
+ sequential_queries: dict[str, AnyQuery] = {}
64
+
65
+ for name in query_names:
66
+ query = face.queries.get(name)
67
+ if query and _has_results_refs(query):
68
+ sequential_queries[name] = query
69
+ else:
70
+ parallel_names.add(name)
71
+
72
+ if not sequential_queries:
73
+ return parallel_names, []
74
+
75
+ graph = DependencyGraph()
76
+ sequential_names = set(sequential_queries)
77
+ for name, query in sequential_queries.items():
78
+ graph.add_node(name)
79
+ for ref in _extract_results_refs(query) & sequential_names:
80
+ graph.add_edge(ref, name)
81
+
82
+ return parallel_names, topological_sort(graph)
83
+
84
+
85
+ def execute_queries_parallel(
86
+ executor: Executor,
87
+ face: Face,
88
+ query_names: set[str],
89
+ variables: VariableValues | None = None,
90
+ max_workers: int = 8,
91
+ ) -> dict[str, Exception | None]:
92
+ """Execute *query_names* concurrently via ``ThreadPoolExecutor``.
93
+
94
+ All queries are submitted immediately — ``{{ queries.X }}`` references are
95
+ Jinja-inlined by the executor so no ordering is needed.
96
+
97
+ ``{{ results.X }}`` queries are intentionally *not* accepted here. They
98
+ must be routed through the explicit sequential phase instead of falling
99
+ through to a render-time execution path.
100
+
101
+ Returns ``{query_name: None}`` on success or ``{query_name: exc}``
102
+ on failure. Errors in one query don't block others.
103
+ """
104
+ if not query_names:
105
+ return {}
106
+
107
+ unsafe_names = sorted(
108
+ name
109
+ for name in query_names
110
+ if (query := face.queries.get(name)) and _has_results_refs(query)
111
+ )
112
+ if unsafe_names:
113
+ joined = ", ".join(unsafe_names)
114
+ raise ValueError(
115
+ "execute_queries_parallel received queries with {{ results.X }} "
116
+ f"references: {joined}"
117
+ )
118
+
119
+ results: dict[str, Exception | None] = {}
120
+
121
+ with ThreadPoolExecutor(max_workers=max_workers) as pool:
122
+ futures: dict[Future, str] = {
123
+ pool.submit(executor.execute_query, name, variables): name
124
+ for name in query_names
125
+ }
126
+
127
+ for fut in as_completed(futures):
128
+ name = futures[fut]
129
+ try:
130
+ fut.result()
131
+ results[name] = None
132
+ except Exception as exc: # noqa: BLE001
133
+ logger.warning("Parallel execution of query '%s' failed: %s", name, exc)
134
+ results[name] = exc
135
+ executor._query_errors[name] = exc
136
+
137
+ return results
138
+
139
+
140
+ def execute_queries_sequential(
141
+ executor: Executor,
142
+ query_names: list[str],
143
+ variables: VariableValues | None = None,
144
+ ) -> dict[str, Exception | None]:
145
+ """Execute *query_names* sequentially before the render walk.
146
+
147
+ This phase is for direct chart queries that use ``{{ results.X }}``.
148
+ They have runtime dependencies on previously executed query results, so
149
+ they run after the parallel-safe phase but still before rendering.
150
+ """
151
+ results: dict[str, Exception | None] = {}
152
+
153
+ for name in query_names:
154
+ try:
155
+ executor.execute_query(name, variables)
156
+ results[name] = None
157
+ except Exception as exc: # noqa: BLE001
158
+ logger.warning("Sequential execution of query '%s' failed: %s", name, exc)
159
+ results[name] = exc
160
+ executor._query_errors[name] = exc
161
+
162
+ return results
@@ -0,0 +1,58 @@
1
+ """Collect and order setup_sql preambles across query dependencies.
2
+
3
+ When query B depends on query A via {{ queries.A }}, and A declares
4
+ setup_sql, that preamble must execute before B's main body runs.
5
+ This module walks the dependency graph, collects setup_sql blocks
6
+ in topological order, and deduplicates identical blocks.
7
+ """
8
+
9
+ import re
10
+
11
+ from dataface.core.compile.models.query.compiled import (
12
+ AnyQuery,
13
+ is_sql_query,
14
+ )
15
+
16
+ _QUERY_REF_PATTERN = re.compile(r"\{\{\s*queries\.(\w+)\s*\}\}")
17
+
18
+
19
+ def collect_setup_sql(
20
+ query_name: str,
21
+ queries: dict[str, AnyQuery],
22
+ ) -> list[str]:
23
+ """Collect setup_sql statements for a query and all its dependencies.
24
+
25
+ Walks the dependency graph depth-first, returns setup_sql blocks in
26
+ dependency order (dependencies before dependents). Identical blocks
27
+ are deduplicated while preserving the earliest occurrence.
28
+
29
+ Args:
30
+ query_name: Name of the query to collect setup_sql for.
31
+ queries: Full query registry.
32
+
33
+ Returns:
34
+ Ordered list of unique setup_sql strings to execute before the main query.
35
+ """
36
+ seen_names: set[str] = set()
37
+ ordered: list[str] = []
38
+ seen_sql: set[str] = set()
39
+
40
+ def _walk(name: str) -> None:
41
+ if name in seen_names or name not in queries:
42
+ return
43
+ seen_names.add(name)
44
+
45
+ query = queries[name]
46
+ # Walk dependencies first (depth-first, dependencies before self)
47
+ if is_sql_query(query):
48
+ for ref in _QUERY_REF_PATTERN.findall(query.sql):
49
+ _walk(ref)
50
+
51
+ # Then collect this query's setup_sql (after deps)
52
+ if is_sql_query(query) and query.setup_sql:
53
+ if query.setup_sql not in seen_sql:
54
+ seen_sql.add(query.setup_sql)
55
+ ordered.append(query.setup_sql)
56
+
57
+ _walk(query_name)
58
+ return ordered
@@ -0,0 +1,72 @@
1
+ """Project-source state for the execute boundary.
2
+
3
+ Owns the project-source allowlist that the resolver consults: ``_sources.yaml``
4
+ / ``dataface.yml`` (already cached by ``get_project_sources``) plus any
5
+ synthetic sources registered at runtime (the ``dft inspect`` HTML server is
6
+ the canonical registrar — it synthesizes a ``warehouse`` source when no
7
+ ``_sources.yaml`` exists).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ from dataface.core.compile.compiler import resolve_project_source_paths
17
+ from dataface.core.compile.config import ProjectSourcesConfig, get_project_sources
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class SourceRegistry:
23
+ """Merged view of project-level sources plus synthetic registrations.
24
+
25
+ A project source by a given name always wins over a synthetic registration
26
+ of the same name (``register`` uses set-if-absent semantics on the synthetic
27
+ map; the project map is layered on top at read time).
28
+
29
+ ``project_root=None`` is supported because ``AdapterRegistry`` accepts the
30
+ same — ad-hoc constructions (tests, agent_api one-offs) yield an empty
31
+ source map.
32
+ """
33
+
34
+ def __init__(self, project_root: Path | None = None) -> None:
35
+ self._project_root = project_root
36
+ self._synthetic: dict[str, dict[str, Any]] = {}
37
+
38
+ def get(self, name: str) -> dict[str, Any] | None:
39
+ """Return the resolved config for ``name``, or ``None`` if unknown."""
40
+ return self.all().get(name)
41
+
42
+ def all(self) -> dict[str, dict[str, Any]]:
43
+ """Return the merged source map (project sources win over synthetics)."""
44
+ return {**self._synthetic, **self._load_project()}
45
+
46
+ def register(self, name: str, config: dict[str, Any]) -> None:
47
+ """Register a synthetic source. Idempotent: first registration wins."""
48
+ self._synthetic.setdefault(name, dict(config))
49
+
50
+ def project_sources_config(self) -> ProjectSourcesConfig:
51
+ """Snapshot for the resolver — merged sources plus project default."""
52
+ return ProjectSourcesConfig(default=self._project_default(), sources=self.all())
53
+
54
+ def _load_project(self) -> dict[str, dict[str, Any]]:
55
+ if self._project_root is None:
56
+ return {}
57
+ try:
58
+ project_sources = get_project_sources(self._project_root)
59
+ return resolve_project_source_paths(
60
+ project_sources.sources, self._project_root
61
+ )
62
+ except (OSError, TypeError, ValueError) as e:
63
+ logger.warning("Failed to load project sources: %s", e)
64
+ return {}
65
+
66
+ def _project_default(self) -> str | None:
67
+ if self._project_root is None:
68
+ return None
69
+ try:
70
+ return get_project_sources(self._project_root).default
71
+ except (OSError, TypeError, ValueError):
72
+ return None
@@ -0,0 +1,255 @@
1
+ """Source resolver: execute-boundary resolution of authored `source:` values.
2
+
3
+ The `SourceResolver` Protocol runs in `AdapterRegistry.execute()` before the
4
+ adapter receives the query. When the authored source resolves to a configured
5
+ profile (face-level, project-level, or an inline dict), the adapter receives a
6
+ typed `SourceConfig`. For source-less queries with no project default
7
+ configured, the resolver returns `None` and the adapter falls through to its
8
+ own default connection (DuckDB ``:memory:`` or the dbt-aware lookup).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import Any, Protocol
15
+
16
+ from dataface.core.compile.config import ProjectSourcesConfig
17
+ from dataface.core.compile.errors import DatafaceError
18
+ from dataface.core.compile.models.source import (
19
+ VALID_SOURCE_TYPES,
20
+ SourceConfig,
21
+ parse_source_config,
22
+ )
23
+ from dataface.core.errors.codes_execute import (
24
+ DF_EXECUTE_SOURCE_CROSS_FILE_FORBIDDEN,
25
+ DF_EXECUTE_SOURCE_INLINE_FORBIDDEN,
26
+ DF_EXECUTE_SOURCE_INVALID_TYPE,
27
+ DF_EXECUTE_SOURCE_MISSING_TYPE,
28
+ DF_EXECUTE_SOURCE_NOT_FOUND,
29
+ DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY,
30
+ )
31
+
32
+
33
+ @dataclass
34
+ class DbtContext:
35
+ """Opaque marker: a dbt project is in scope for this execute call.
36
+
37
+ When present, unknown string source names fall through to the dbt adapter
38
+ (which resolves profile names via the dbt manifest).
39
+ When absent, unknown names raise DF-EXECUTE-SOURCE-NOT-FOUND.
40
+ """
41
+
42
+
43
+ class SourceResolver(Protocol):
44
+ """Single execute-boundary chokepoint for source resolution.
45
+
46
+ Implementations map an authored `source:` value (str | dict | None) to a
47
+ typed `SourceConfig | None` before any database driver is loaded.
48
+
49
+ Callers (AdapterRegistry) invoke resolve() once per query, then forward the
50
+ result to the matched adapter via a `source_config` kwarg.
51
+ """
52
+
53
+ def resolve(
54
+ self,
55
+ authored: str | dict[str, Any] | None,
56
+ face_sources: dict[str, dict[str, Any]],
57
+ project_sources: ProjectSourcesConfig,
58
+ dbt_context: DbtContext | None,
59
+ ) -> SourceConfig | None:
60
+ """Resolve an authored source value to a typed SourceConfig.
61
+
62
+ Args:
63
+ authored: The raw `source:` value from the compiled query.
64
+ face_sources: Named sources declared in the face's `sources:` block.
65
+ project_sources: Project-level sources from `_sources.yaml` / dataface.yml.
66
+ dbt_context: Present when a dbt project is in scope; allows unknown
67
+ string names to fall through to the dbt adapter.
68
+
69
+ Returns:
70
+ Typed SourceConfig, or None when no source applies (adapter uses
71
+ its own default connection) or when dbt_context covers the name.
72
+
73
+ Raises:
74
+ DatafaceError: With a DF-EXECUTE-SOURCE-* code on policy violations.
75
+ """
76
+ ...
77
+
78
+
79
+ class DefaultSourceResolver:
80
+ """Behavior-preserving default resolver for CLI, local playground, and inspect surfaces.
81
+
82
+ Resolves authored source values using face-level sources first, then project-level
83
+ sources. Does not apply policy gates — inline dicts and unknown names are accepted
84
+ as-is. Unknown string names with a dbt context fall through to the dbt adapter.
85
+
86
+ Resolution order for a string authored value:
87
+ 1. face_sources lookup (face's `sources:` block)
88
+ 2. project_sources lookup (`_sources.yaml` / dataface.yml)
89
+ 3. dbt fallback: return None when dbt_context is set
90
+ 4. raise DF-EXECUTE-SOURCE-NOT-FOUND otherwise
91
+
92
+ For None authored: apply project default when configured, else return None.
93
+ For dict authored: validate and parse directly.
94
+
95
+ Subclasses (e.g. an allowlist-enforcing resolver) override resolve(), apply
96
+ their own policy gates, and delegate to super().resolve() for the shared
97
+ lookup algorithm.
98
+ """
99
+
100
+ def resolve(
101
+ self,
102
+ authored: str | dict[str, Any] | None,
103
+ face_sources: dict[str, dict[str, Any]],
104
+ project_sources: ProjectSourcesConfig,
105
+ dbt_context: DbtContext | None,
106
+ ) -> SourceConfig | None:
107
+ if authored is None:
108
+ default_name = project_sources.default
109
+ if not default_name:
110
+ return None
111
+ source_dict = project_sources.sources.get(default_name)
112
+ if source_dict is None:
113
+ return None
114
+ return self._parse(source_dict)
115
+
116
+ if isinstance(authored, dict):
117
+ return self._parse(authored)
118
+
119
+ source_dict = face_sources.get(authored) or project_sources.sources.get(
120
+ authored
121
+ )
122
+ if source_dict is not None:
123
+ return self._parse(source_dict)
124
+
125
+ if dbt_context is not None:
126
+ return None
127
+
128
+ available = sorted(project_sources.sources.keys())
129
+ if available:
130
+ raise DatafaceError.from_code(
131
+ DF_EXECUTE_SOURCE_NOT_FOUND,
132
+ source=authored,
133
+ available=available,
134
+ )
135
+ raise DatafaceError.from_code(
136
+ DF_EXECUTE_SOURCE_NOT_FOUND_EMPTY,
137
+ source=authored,
138
+ )
139
+
140
+ def _parse(self, source_dict: dict[str, Any]) -> SourceConfig:
141
+ """Validate and parse a source dict to a typed SourceConfig."""
142
+ if "type" not in source_dict:
143
+ raise DatafaceError.from_code(
144
+ DF_EXECUTE_SOURCE_MISSING_TYPE,
145
+ offending_value=repr(source_dict),
146
+ )
147
+ if source_dict["type"] not in VALID_SOURCE_TYPES:
148
+ raise DatafaceError.from_code(
149
+ DF_EXECUTE_SOURCE_INVALID_TYPE,
150
+ offending_value=source_dict["type"],
151
+ available=sorted(VALID_SOURCE_TYPES),
152
+ )
153
+ return parse_source_config(source_dict)
154
+
155
+
156
+ def _summarize_inline_source(source_dict: dict[str, Any]) -> str:
157
+ """Summarize a rejected inline source without echoing secret values.
158
+
159
+ The rendered error message surfaces via `QueryResult.error` and structured
160
+ logs — echoing the raw dict (which may carry `password`, `secret`, `token`,
161
+ …) compounds the original mistake. Emit `type` plus the sorted key list so
162
+ authors get actionable feedback ("you authored an inline postgres source")
163
+ without the values.
164
+ """
165
+ type_value = source_dict.get("type", "<missing type>")
166
+ keys = sorted(source_dict.keys())
167
+ return f"type={type_value!r}, keys={keys}"
168
+
169
+
170
+ class AllowlistedSourceResolver(DefaultSourceResolver):
171
+ """Strict source resolver: only names in `project_sources.sources` resolve.
172
+
173
+ Three rejection rules fire before any resolution:
174
+
175
+ 1. Inline source dicts — both per-query `source: {type: ..., host: ...}`
176
+ and any face-level `face.sources` entry are refused with
177
+ `DF-EXECUTE-SOURCE-INLINE-FORBIDDEN` (face.sources values are inline
178
+ source definitions by construction, not name references).
179
+ 2. Cross-file `#` references — any authored string containing `#` is
180
+ refused with `DF-EXECUTE-SOURCE-CROSS-FILE-FORBIDDEN`.
181
+ 3. Unknown names — a string not in `project_sources.sources` is refused
182
+ with `DF-EXECUTE-SOURCE-NOT-FOUND`; the payload's `available` field
183
+ carries the sorted allowlist.
184
+
185
+ Accepted inputs delegate to `DefaultSourceResolver.resolve` for the shared
186
+ lookup. The dbt-context fallback is intentionally disabled: an unknown
187
+ name raises regardless of `dbt_context`, because dbt-profile names are not
188
+ part of the configured allowlist by definition.
189
+
190
+ This is the closed-allowlist resolver — pick it when the caller does not
191
+ trust authored YAML to declare arbitrary connections (e.g. hosted /
192
+ multi-tenant deployments). Loosening any rule changes the trust boundary;
193
+ treat changes here as security decisions, not stylistic ones.
194
+ """
195
+
196
+ def resolve(
197
+ self,
198
+ authored: str | dict[str, Any] | None,
199
+ face_sources: dict[str, dict[str, Any]],
200
+ project_sources: ProjectSourcesConfig,
201
+ dbt_context: DbtContext | None,
202
+ yaml_path: str = "",
203
+ ) -> SourceConfig | None:
204
+ if isinstance(authored, dict):
205
+ raise DatafaceError.from_code(
206
+ DF_EXECUTE_SOURCE_INLINE_FORBIDDEN,
207
+ yaml_path=yaml_path,
208
+ offending_value=_summarize_inline_source(authored),
209
+ )
210
+
211
+ if isinstance(authored, str) and "#" in authored:
212
+ raise DatafaceError.from_code(
213
+ DF_EXECUTE_SOURCE_CROSS_FILE_FORBIDDEN,
214
+ yaml_path=yaml_path,
215
+ offending_value=authored,
216
+ )
217
+
218
+ if (
219
+ isinstance(authored, str)
220
+ and authored in face_sources
221
+ and authored not in project_sources.sources
222
+ ):
223
+ raise DatafaceError.from_code(
224
+ DF_EXECUTE_SOURCE_INLINE_FORBIDDEN,
225
+ yaml_path=yaml_path,
226
+ offending_value=_summarize_inline_source(face_sources[authored]),
227
+ )
228
+
229
+ if isinstance(authored, str):
230
+ if authored not in project_sources.sources:
231
+ self._raise_not_found(authored, project_sources, yaml_path)
232
+ elif authored is None:
233
+ default_name = project_sources.default
234
+ if default_name is not None and default_name not in project_sources.sources:
235
+ self._raise_not_found(default_name, project_sources, yaml_path)
236
+
237
+ return super().resolve(
238
+ authored=authored,
239
+ face_sources={},
240
+ project_sources=project_sources,
241
+ dbt_context=None,
242
+ )
243
+
244
+ @staticmethod
245
+ def _raise_not_found(
246
+ offending: str,
247
+ project_sources: ProjectSourcesConfig,
248
+ yaml_path: str,
249
+ ) -> None:
250
+ raise DatafaceError.from_code(
251
+ DF_EXECUTE_SOURCE_NOT_FOUND,
252
+ source=offending,
253
+ available=sorted(project_sources.sources.keys()),
254
+ yaml_path=yaml_path,
255
+ )