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,518 @@
1
+ """Source detection utilities for database and dbt profiles.
2
+
3
+ Stage: COMPILE
4
+ Purpose: Centralized utilities for detecting database types and reading dbt profiles.
5
+
6
+ Entry Points:
7
+ - detect_dbt_database_type(profiles_path, profile_name, target_name) -> dict
8
+ - detect_dbt_connections(project_path) -> list[dict]
9
+ - detect_database_type_from_registry(registry) -> dict
10
+ - detect_dbt_connection_string(cwd) -> tuple[str, str] | None
11
+ - DB_INFO_MAP: Database type information dictionary
12
+
13
+ This module is the single canonical home for all database/source detection logic.
14
+ Surfaces (playground, cloud, CLI) import from here rather than implementing their own.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ from pathlib import Path
21
+ from typing import TYPE_CHECKING, Any
22
+ from urllib.parse import quote_plus
23
+
24
+ if TYPE_CHECKING:
25
+ from dataface.core.execute.adapters.adapter_registry import AdapterRegistry
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Single source of truth for database-specific SQL syntax and introspection
30
+ DB_INFO_MAP: dict[str, dict[str, str]] = {
31
+ "duckdb": {
32
+ "type": "duckdb",
33
+ "engine": "DuckDB",
34
+ "dialect": "DuckDB SQL",
35
+ "introspection": (
36
+ "List tables: `SHOW TABLES;` | "
37
+ "Describe table: `DESCRIBE table_name;` or `PRAGMA table_info('table_name');`"
38
+ ),
39
+ "notes": (
40
+ "DuckDB does NOT support `SHOW COLUMNS FROM` or `PRAGMA show_columns()`. "
41
+ "Use DESCRIBE or PRAGMA table_info instead."
42
+ ),
43
+ },
44
+ "postgresql": {
45
+ "type": "postgresql",
46
+ "engine": "PostgreSQL",
47
+ "dialect": "PostgreSQL SQL",
48
+ "introspection": (
49
+ "List tables: `SELECT table_name FROM information_schema.tables "
50
+ "WHERE table_schema = 'public';` | "
51
+ "Describe table: `SELECT column_name, data_type FROM information_schema.columns "
52
+ "WHERE table_name = 'table_name';`"
53
+ ),
54
+ "notes": "PostgreSQL supports full information_schema queries.",
55
+ },
56
+ "snowflake": {
57
+ "type": "snowflake",
58
+ "engine": "Snowflake",
59
+ "dialect": "Snowflake SQL",
60
+ "introspection": (
61
+ "List tables: `SHOW TABLES;` | "
62
+ "Describe table: `DESCRIBE TABLE table_name;`"
63
+ ),
64
+ "notes": "Snowflake has its own SHOW/DESCRIBE commands and information_schema.",
65
+ },
66
+ "bigquery": {
67
+ "type": "bigquery",
68
+ "engine": "BigQuery",
69
+ "dialect": "BigQuery SQL",
70
+ "introspection": (
71
+ "List tables: `SELECT table_name FROM project.dataset.INFORMATION_SCHEMA.TABLES;` | "
72
+ "Describe table: `SELECT column_name, data_type FROM "
73
+ "project.dataset.INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'table_name';`"
74
+ ),
75
+ "notes": "BigQuery uses backtick-quoted identifiers for projects/datasets.",
76
+ },
77
+ "redshift": {
78
+ "type": "redshift",
79
+ "engine": "Amazon Redshift",
80
+ "dialect": "Redshift SQL",
81
+ "introspection": (
82
+ "List tables: `SELECT tablename FROM pg_tables WHERE schemaname = 'public';` | "
83
+ "Describe table: `SELECT column_name, data_type FROM information_schema.columns "
84
+ "WHERE table_name = 'table_name';`"
85
+ ),
86
+ "notes": "Redshift supports PostgreSQL-style information_schema.",
87
+ },
88
+ "mysql": {
89
+ "type": "mysql",
90
+ "engine": "MySQL",
91
+ "dialect": "MySQL SQL",
92
+ "introspection": (
93
+ "List tables: `SHOW TABLES;` | "
94
+ "Describe table: `DESCRIBE table_name;` or `SHOW COLUMNS FROM table_name;`"
95
+ ),
96
+ "notes": "MySQL supports SHOW commands and information_schema.",
97
+ },
98
+ "databricks": {
99
+ "type": "databricks",
100
+ "engine": "Databricks",
101
+ "dialect": "Databricks SQL (Spark SQL)",
102
+ "introspection": (
103
+ "List tables: `SHOW TABLES;` | "
104
+ "Describe table: `DESCRIBE TABLE table_name;`"
105
+ ),
106
+ "notes": "Databricks supports Spark SQL syntax with SHOW/DESCRIBE commands.",
107
+ },
108
+ "athena": {
109
+ "type": "athena",
110
+ "engine": "AWS Athena",
111
+ "dialect": "Athena SQL (Presto/Trino)",
112
+ "introspection": (
113
+ "List tables: `SHOW TABLES IN database_name;` | "
114
+ "Describe table: `DESCRIBE table_name;`"
115
+ ),
116
+ "notes": "Athena uses Presto/Trino syntax.",
117
+ },
118
+ "sqlserver": {
119
+ "type": "sqlserver",
120
+ "engine": "Microsoft SQL Server",
121
+ "dialect": "T-SQL",
122
+ "introspection": (
123
+ "List tables: `SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES;` | "
124
+ "Describe table: `SELECT COLUMN_NAME, DATA_TYPE FROM "
125
+ "INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'table_name';`"
126
+ ),
127
+ "notes": "SQL Server supports information_schema and sp_help procedures.",
128
+ },
129
+ }
130
+
131
+
132
+ def get_database_info(db_type: str) -> dict[str, str] | None:
133
+ """Get database info for a given type.
134
+
135
+ Args:
136
+ db_type: Database type string (e.g., "duckdb", "postgres", "snowflake")
137
+
138
+ Returns:
139
+ Database info dict with type, engine, dialect, introspection, notes
140
+ Returns None if db_type is not recognized
141
+ """
142
+ normalized_type = db_type.lower().strip()
143
+ # Normalize common aliases
144
+ if normalized_type == "postgres":
145
+ normalized_type = "postgresql"
146
+ return DB_INFO_MAP.get(normalized_type)
147
+
148
+
149
+ def detect_dbt_database_type(
150
+ profiles_path: Path,
151
+ profile_name: str,
152
+ target_name: str | None = None,
153
+ *,
154
+ fallback_type: str | None = None,
155
+ ) -> dict[str, Any] | None:
156
+ """Detect database type from dbt profiles.yml file.
157
+
158
+ Reads the profiles.yml configuration and extracts database type information
159
+ for the specified profile and target.
160
+
161
+ Args:
162
+ profiles_path: Path to profiles.yml file
163
+ profile_name: Profile name to read (e.g., "my_project")
164
+ target_name: Target name to read (e.g., "dev", "prod"). If None, uses
165
+ the default target or first available.
166
+ fallback_type: If set and the profile is not found but the file exists,
167
+ return info for this database type instead of None.
168
+
169
+ Returns:
170
+ Dict with database type info including:
171
+ - type: Database type (e.g., "duckdb", "postgresql")
172
+ - engine: Human-readable engine name
173
+ - dialect: SQL dialect name
174
+ - introspection: Introspection query examples
175
+ - notes: Important notes about SQL syntax
176
+ - description: Auto-generated description
177
+ - profile_name: The profile name used
178
+ - target_name: The target name used
179
+ Returns None if profile not found or file doesn't exist
180
+ (unless fallback_type is set and the file exists)
181
+
182
+ Example:
183
+ >>> info = detect_dbt_database_type(
184
+ ... Path("profiles.yml"), "my_project", "dev"
185
+ ... )
186
+ >>> info["engine"]
187
+ 'DuckDB'
188
+ """
189
+ if not profiles_path.exists():
190
+ return None
191
+
192
+ import yaml
193
+
194
+ try:
195
+ with open(profiles_path) as f:
196
+ profiles = yaml.safe_load(f)
197
+
198
+ if not profiles or profile_name not in profiles:
199
+ return _fallback_info(fallback_type) if fallback_type else None
200
+
201
+ profile = profiles[profile_name]
202
+ if not isinstance(profile, dict) or "outputs" not in profile:
203
+ return _fallback_info(fallback_type) if fallback_type else None
204
+
205
+ outputs = profile["outputs"]
206
+ if not outputs:
207
+ return _fallback_info(fallback_type) if fallback_type else None
208
+
209
+ # Use specified target, default target, or first available
210
+ actual_target = target_name
211
+ if actual_target is None or actual_target not in outputs:
212
+ # Try default target from profile
213
+ actual_target = profile.get("target")
214
+ if actual_target is None or actual_target not in outputs:
215
+ # Fall back to first available target
216
+ actual_target = next(iter(outputs.keys()))
217
+
218
+ target_config = outputs[actual_target]
219
+ db_type = target_config.get("type", "").lower()
220
+
221
+ db_info = get_database_info(db_type)
222
+ if db_info:
223
+ result = db_info.copy()
224
+ result["description"] = f"SQL queries execute via {db_info['engine']}"
225
+ result["profile_name"] = profile_name
226
+ result["target_name"] = actual_target
227
+ return result
228
+
229
+ # Unknown type - return basic info
230
+ return {
231
+ "type": db_type or "unknown",
232
+ "engine": db_type.title() if db_type else "Unknown",
233
+ "dialect": "SQL",
234
+ "introspection": "",
235
+ "notes": f"Unknown database type: {db_type}",
236
+ "description": f"SQL queries execute via unknown database type: {db_type}",
237
+ "profile_name": profile_name,
238
+ "target_name": actual_target,
239
+ }
240
+
241
+ except (OSError, yaml.YAMLError, AttributeError, KeyError, TypeError) as e:
242
+ logger.debug(f"Failed to read or parse profiles.yml: {e}")
243
+ return _fallback_info(fallback_type) if fallback_type else None
244
+
245
+
246
+ def _fallback_info(fallback_type: str) -> dict[str, Any]:
247
+ """Build a fallback database info dict for a given type."""
248
+ db_info = get_database_info(fallback_type)
249
+ if db_info:
250
+ result = db_info.copy()
251
+ result["description"] = (
252
+ f"SQL queries execute via {db_info['engine']} (fallback)"
253
+ )
254
+ return result
255
+ return {
256
+ "type": fallback_type,
257
+ "engine": fallback_type.title(),
258
+ "dialect": "SQL",
259
+ "introspection": "",
260
+ "notes": f"Fallback database type: {fallback_type}",
261
+ "description": f"SQL queries execute via {fallback_type} (fallback)",
262
+ }
263
+
264
+
265
+ def detect_dbt_connections(project_path: Path) -> list[dict[str, Any]]:
266
+ """Detect connections from dbt profiles.yml.
267
+
268
+ Searches for profiles.yml in the project directory or ~/.dbt and extracts
269
+ all connection configurations for all profiles and targets.
270
+
271
+ Args:
272
+ project_path: Path to the project directory
273
+
274
+ Returns:
275
+ List of connection configuration dicts, each containing:
276
+ - name: Connection name (format: "profile_name (target_name)")
277
+ - type: Database type
278
+ - host: Database host
279
+ - port: Database port
280
+ - database: Database name
281
+ - schema: Schema name
282
+ - username: Username
283
+ - account: Snowflake account (if applicable)
284
+ - project: BigQuery project (if applicable)
285
+
286
+ Example:
287
+ >>> connections = detect_dbt_connections(Path("/my/project"))
288
+ >>> connections[0]
289
+ {'name': 'my_project (dev)', 'type': 'duckdb', ...}
290
+ """
291
+ import yaml
292
+
293
+ # Look for profiles.yml in project or ~/.dbt
294
+ profiles_path = project_path / "profiles.yml"
295
+ if not profiles_path.exists():
296
+ profiles_path = Path.home() / ".dbt" / "profiles.yml"
297
+
298
+ if not profiles_path.exists():
299
+ return []
300
+
301
+ try:
302
+ profiles = yaml.safe_load(profiles_path.read_text())
303
+ except (OSError, yaml.YAMLError):
304
+ return [] # Can't read or invalid YAML - no connections to detect
305
+
306
+ if not profiles:
307
+ return []
308
+
309
+ connections: list[dict[str, Any]] = []
310
+ for profile_name, profile in profiles.items():
311
+ if not isinstance(profile, dict):
312
+ continue
313
+
314
+ for target_name, target in profile.get("outputs", {}).items():
315
+ if not isinstance(target, dict):
316
+ continue
317
+
318
+ connections.append(
319
+ {
320
+ "name": f"{profile_name} ({target_name})",
321
+ "type": target.get("type"),
322
+ "host": target.get("host", target.get("server", "")),
323
+ "port": target.get("port"),
324
+ "database": target.get("database", target.get("dbname", "")),
325
+ "schema": target.get("schema", ""),
326
+ "username": target.get("user", target.get("username", "")),
327
+ "account": target.get("account", ""), # Snowflake
328
+ "project": target.get("project", ""), # BigQuery
329
+ }
330
+ )
331
+
332
+ return connections
333
+
334
+
335
+ def detect_database_type_from_registry(
336
+ registry: AdapterRegistry,
337
+ *,
338
+ default_profile_name: str = "default",
339
+ default_target_name: str = "dev",
340
+ ) -> dict[str, str]:
341
+ """Detect database type information from an adapter registry.
342
+
343
+ Inspects registered SQL adapters (DbtAdapter, SqlAdapter) to determine
344
+ the database type by reading their dbt profiles.yml configuration.
345
+
346
+ Args:
347
+ registry: AdapterRegistry instance to inspect.
348
+ default_profile_name: Profile name to use when adapter doesn't specify one.
349
+ default_target_name: Target name to use when adapter doesn't specify one.
350
+
351
+ Returns:
352
+ Dict with database type information. Always returns a dict — never None.
353
+ Keys: type, engine, dialect, notes (and optionally profile_name, target_name).
354
+ """
355
+ from dataface.core.execute.adapters.dbt_adapter import DbtAdapter
356
+ from dataface.core.execute.adapters.sql_adapter import SqlAdapter
357
+
358
+ sql_adapters = registry.get_adapters_for_type("sql")
359
+
360
+ for adapter in sql_adapters:
361
+ if isinstance(adapter, DbtAdapter):
362
+ dbt_path = Path(adapter.dbt_project_path)
363
+ profiles_path = dbt_path / "profiles.yml"
364
+ profile_name = adapter.profile_name or default_profile_name
365
+ target_name = adapter.target_name or default_target_name
366
+
367
+ db_info = detect_dbt_database_type(
368
+ profiles_path, profile_name, target_name, fallback_type="duckdb"
369
+ )
370
+ if db_info:
371
+ db_info["profile_name"] = profile_name
372
+ db_info["target_name"] = target_name
373
+ return db_info
374
+
375
+ # DbtAdapter but can't determine type — default to DuckDB
376
+ info = DB_INFO_MAP["duckdb"].copy()
377
+ info["description"] = "SQL queries execute via DuckDB (default)"
378
+ return info
379
+
380
+ elif isinstance(adapter, SqlAdapter):
381
+ if adapter.dbt_project_path:
382
+ dbt_path = Path(adapter.dbt_project_path)
383
+ profiles_path = dbt_path / "profiles.yml"
384
+
385
+ db_info = detect_dbt_database_type(
386
+ profiles_path,
387
+ default_profile_name,
388
+ default_target_name,
389
+ fallback_type="duckdb",
390
+ )
391
+ if db_info:
392
+ return db_info
393
+
394
+ # SqlAdapter without dbt project
395
+ return {
396
+ "type": "unknown",
397
+ "engine": "Unknown",
398
+ "description": "SQL adapter configured but database type unknown",
399
+ "dialect": "SQL",
400
+ "notes": "Database type could not be determined. Use generic SQL syntax.",
401
+ }
402
+
403
+ return {
404
+ "type": "none",
405
+ "engine": "None",
406
+ "description": "No SQL adapter available",
407
+ "dialect": "N/A",
408
+ "notes": "",
409
+ }
410
+
411
+
412
+ def detect_dbt_connection_string(cwd: Path) -> tuple[str, str] | None:
413
+ """Auto-detect database connection from dbt project files.
414
+
415
+ Walks up from *cwd* to find ``dbt_project.yml``, reads the matching
416
+ ``profiles.yml``, and builds a connection string + dialect pair.
417
+
418
+ Args:
419
+ cwd: Directory to start searching from.
420
+
421
+ Returns:
422
+ ``(connection_string, dialect)`` or ``None`` if no dbt project found.
423
+ """
424
+ import yaml
425
+
426
+ # Walk up to find dbt_project.yml
427
+ search_path = cwd
428
+ dbt_project_path = None
429
+ for _ in range(10):
430
+ candidate = search_path / "dbt_project.yml"
431
+ if candidate.exists():
432
+ dbt_project_path = candidate
433
+ break
434
+ if search_path.parent == search_path:
435
+ break
436
+ search_path = search_path.parent
437
+
438
+ if not dbt_project_path:
439
+ return None
440
+
441
+ project_dir = dbt_project_path.parent
442
+
443
+ try:
444
+ with open(dbt_project_path) as f:
445
+ dbt_project = yaml.safe_load(f)
446
+ profile_name = dbt_project.get("profile", "default")
447
+ except (FileNotFoundError, yaml.YAMLError, TypeError):
448
+ return None
449
+
450
+ # Find profiles.yml
451
+ profiles_path = project_dir / "profiles.yml"
452
+ if not profiles_path.exists():
453
+ profiles_path = Path.home() / ".dbt" / "profiles.yml"
454
+ if not profiles_path.exists():
455
+ return None
456
+
457
+ try:
458
+ with open(profiles_path) as f:
459
+ profiles = yaml.safe_load(f)
460
+
461
+ profile = profiles.get(profile_name, {})
462
+ target_name = profile.get("target", "dev")
463
+ outputs = profile.get("outputs", {})
464
+ target = outputs.get(target_name, {})
465
+ db_type = target.get("type", "duckdb")
466
+
467
+ if db_type == "duckdb":
468
+ path = target.get("path", ":memory:")
469
+ if not path.startswith(":") and not Path(path).is_absolute():
470
+ path = str(project_dir / path)
471
+ return (path, "duckdb")
472
+ elif db_type == "postgres":
473
+ host = target.get("host", "localhost")
474
+ port = target.get("port", 5432)
475
+ user = quote_plus(target.get("user", "postgres"))
476
+ password = quote_plus(target.get("password", ""))
477
+ dbname = target.get("dbname", "postgres")
478
+ return (
479
+ f"postgresql://{user}:{password}@{host}:{port}/{dbname}",
480
+ "postgres",
481
+ )
482
+ elif db_type == "bigquery":
483
+ project = target.get("project", target.get("database", ""))
484
+ if project:
485
+ return (f"bigquery://{project}", "bigquery")
486
+ elif db_type == "snowflake":
487
+ account = target.get("account", "")
488
+ database = target.get("database", "")
489
+ db_schema = target.get("schema", "")
490
+ warehouse = target.get("warehouse", "")
491
+ if account:
492
+ conn = f"snowflake://{account}/{database}/{db_schema}"
493
+ if warehouse:
494
+ conn += f"?warehouse={warehouse}"
495
+ return (conn, "snowflake")
496
+ elif db_type == "databricks":
497
+ host = target.get("host", "")
498
+ http_path = target.get("http_path", "")
499
+ token = target.get("token", "")
500
+ if host:
501
+ conn = f"databricks://{host}/{http_path}"
502
+ if token:
503
+ conn += f"?token={token}"
504
+ return (conn, "databricks")
505
+ except (FileNotFoundError, yaml.YAMLError, TypeError, AttributeError):
506
+ return None
507
+
508
+ return None
509
+
510
+
511
+ __all__ = [
512
+ "DB_INFO_MAP",
513
+ "detect_database_type_from_registry",
514
+ "detect_dbt_connection_string",
515
+ "detect_dbt_connections",
516
+ "detect_dbt_database_type",
517
+ "get_database_info",
518
+ ]
@@ -0,0 +1,56 @@
1
+ """Compile-time authoring checks for SQL query strings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sqlglot
6
+ import sqlglot.errors
7
+
8
+ from dataface.core.execute.sql_guard import sqlglot_dialect
9
+
10
+
11
+ def _sql_parses(sql: str, dialect: str | None) -> bool:
12
+ """Return True if sqlglot.parse succeeds without error, False otherwise."""
13
+ try:
14
+ sqlglot.parse(sql, read=sqlglot_dialect(dialect))
15
+ except (sqlglot.errors.ParseError, sqlglot.errors.TokenError):
16
+ return False
17
+ return True
18
+
19
+
20
+ def has_literal_escaped_newlines(sql: str, *, dialect: str | None) -> bool:
21
+ """Return True if sql contains literal \\n that is the cause of a parse failure.
22
+
23
+ Three-step parse-replace-parse heuristic:
24
+ 1. If '\\n' not in sql → False (fast path, no parse needed)
25
+ 2. If sqlglot.parse(sql) succeeds → False (\\n is inside a SQL string literal
26
+ or otherwise accepted — step 1 guards the cost of this parse)
27
+ 3. If sqlglot.parse(sql.replace('\\n', newline)) succeeds → True
28
+ (the replacement fixed the failure: author used single-quoted YAML instead of
29
+ a block scalar, so \\n was preserved literally instead of becoming a real newline)
30
+ 4. Else → False (genuine syntax/Jinja issue unrelated to \\n; step 4 is the
31
+ absence of step 3)
32
+
33
+ Note: SQL with Jinja expressions ({{ ref('orders') }}) bypasses detection — both
34
+ step 2 and step 3 fail to parse Jinja skeletons, so the function returns False
35
+ and the existing UnparseableSqlError path handles it at runtime. This is intentional.
36
+
37
+ Args:
38
+ sql: Raw SQL string after YAML parsing.
39
+ dialect: Dataface dialect name (e.g. "duckdb", "sqlserver"), or None for the default.
40
+ Normalised to the sqlglot equivalent via sqlglot_dialect().
41
+
42
+ Returns:
43
+ True if the literal-\\n authoring error is detected, False otherwise.
44
+ """
45
+ # Step 1: fast exit — no literal \n present, skip all parsing.
46
+ if "\\n" not in sql:
47
+ return False
48
+
49
+ # Step 2: parses fine as-is — \n is inside a SQL string literal or similar.
50
+ if _sql_parses(sql, dialect):
51
+ return False
52
+
53
+ # Step 3: parses after replacing literal \n with a real newline — authoring mistake.
54
+ # Step 4 is the implicit False when step 3 also fails (genuine syntax/Jinja issue).
55
+ # Author wrote sql: 'SELECT\n...' in YAML single-quotes instead of sql: | block scalar.
56
+ return _sql_parses(sql.replace("\\n", "\n"), dialect)