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,537 @@
1
+ """Superfences handlers for embedding Dataface boards in markdown.
2
+
3
+ Two handlers for use with ``pymdownx.superfences`` custom fences:
4
+
5
+ - ``fence_dataface`` — render-only: YAML in, SVG/HTML out.
6
+ - ``fence_dataface_example`` — code + render: syntax-highlighted YAML
7
+ alongside rendered output, with playground links.
8
+
9
+ Register in ``mkdocs.yml``::
10
+
11
+ markdown_extensions:
12
+ - pymdownx.superfences:
13
+ custom_fences:
14
+ - name: dataface
15
+ class: dataface
16
+ format: !!python/name:dataface.integrations.markdown.fence_dataface
17
+ - name: dataface-example
18
+ class: dataface-example
19
+ format: !!python/name:dataface.integrations.markdown.fence_dataface_example
20
+
21
+ Then in any ``.md`` page::
22
+
23
+ ```dataface
24
+ charts:
25
+ revenue:
26
+ type: bar
27
+ x: product
28
+ y: revenue
29
+ ```
30
+
31
+ ```dataface-example {file=charts/examples/bar-charts/minimum.yml}
32
+ ```
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import base64
38
+ import functools
39
+ import hashlib
40
+ import html
41
+ import logging
42
+ import os
43
+ import re
44
+ import zlib
45
+ from collections.abc import Iterator
46
+ from contextlib import contextmanager
47
+ from contextvars import ContextVar
48
+ from pathlib import Path
49
+ from typing import Any, cast
50
+
51
+ import yaml
52
+
53
+ from dataface.core.render.errors import RenderError
54
+ from dataface.core.render.face_api import compile_and_render, render_face
55
+ from dataface.integrations.highlighting import highlight_face_yaml
56
+
57
+ logger = logging.getLogger("dataface.integrations.markdown")
58
+
59
+ _PLAYGROUND_DEFAULT_URL = "https://play.dataface.com"
60
+ _VALID_LAYOUTS = {"side-by-side", "stacked", "render-only", "yaml-only"}
61
+ _EXTERNAL_QUERY_NOT_FOUND_RE = re.compile(r"External query file not found", re.I)
62
+
63
+ _PROJECT_DIR_OVERRIDE: ContextVar[Path | None] = ContextVar(
64
+ "dataface_markdown_project_dir", default=None
65
+ )
66
+
67
+
68
+ @contextmanager
69
+ def project_dir_override(path: Path) -> Iterator[None]:
70
+ """Pin fence rendering to *path* instead of walking up from ``cwd``."""
71
+ token = _PROJECT_DIR_OVERRIDE.set(path.resolve())
72
+ try:
73
+ yield
74
+ finally:
75
+ _PROJECT_DIR_OVERRIDE.reset(token)
76
+
77
+
78
+ @functools.cache
79
+ def _cached_project_dir_from_cwd() -> Path:
80
+ """Walk up from ``cwd`` for a ``pyproject.toml`` or ``dbt_project.yml``."""
81
+ cwd = Path.cwd().resolve()
82
+ for parent in [cwd, *cwd.parents]:
83
+ if (parent / "pyproject.toml").exists() or (
84
+ parent / "dbt_project.yml"
85
+ ).exists():
86
+ return parent
87
+ raise RuntimeError(
88
+ "Cannot resolve project root: no pyproject.toml or dbt_project.yml "
89
+ "found above cwd"
90
+ )
91
+
92
+
93
+ def _resolve_project_dir() -> Path:
94
+ """Resolve the project root for markdown fence rendering."""
95
+ override = _PROJECT_DIR_OVERRIDE.get()
96
+ if override is not None:
97
+ return override
98
+ return _cached_project_dir_from_cwd()
99
+
100
+
101
+ def _highlight_yaml(source: str) -> str:
102
+ """Syntax-highlight Dataface face YAML using DatafaceYamlLexer.
103
+
104
+ SQL inside ``sql: |`` and ``query: |`` block scalars is delegated to
105
+ SqlLexer so SQL keywords receive keyword styling.
106
+
107
+ Returns HTML wrapped in ``<div class="yaml"><pre><code>...</code></pre></div>``.
108
+ """
109
+ highlighted = highlight_face_yaml(source)
110
+ return (
111
+ f'<div class="yaml"><pre><code class="language-yaml">'
112
+ f"{highlighted}</code></pre></div>"
113
+ )
114
+
115
+
116
+ def _playground_url(
117
+ yaml_source: str,
118
+ base_url: str | None = None,
119
+ ) -> str:
120
+ """Generate a playground URL with compressed YAML payload."""
121
+ resolved_base_url = base_url or os.getenv(
122
+ "DFT_PLAYGROUND_URL", _PLAYGROUND_DEFAULT_URL
123
+ )
124
+ compressed = zlib.compress(yaml_source.encode("utf-8"), level=9)
125
+ encoded = base64.urlsafe_b64encode(compressed).decode("ascii").rstrip("=")
126
+ return f"{resolved_base_url}/?y={encoded}"
127
+
128
+
129
+ def _resolve_file(file_path: str, project_dir: Path) -> Path:
130
+ """Resolve a file= path against project_dir with traversal guard."""
131
+ resolved = (project_dir / file_path).resolve()
132
+ if not resolved.is_relative_to(project_dir.resolve()):
133
+ raise ValueError(f"Path escapes project directory: {file_path}")
134
+ if not resolved.exists():
135
+ raise FileNotFoundError(f"File not found: {resolved}")
136
+ return resolved
137
+
138
+
139
+ def _fence_options(
140
+ options: dict[str, Any],
141
+ kwargs: dict[str, Any],
142
+ ) -> dict[str, Any]:
143
+ """Merge legacy ``options`` with SuperFences attr-list kwargs."""
144
+ merged = dict(options)
145
+ attrs = kwargs.get("attrs") or {}
146
+ for key, value in attrs.items():
147
+ merged.setdefault(key, value)
148
+ return merged
149
+
150
+
151
+ def _shared_examples_dir(project_dir: Path) -> Path | None:
152
+ """Return ``examples/`` when it ships the shared ``_sources.yaml`` project root."""
153
+ examples_dir = project_dir / "examples"
154
+ if examples_dir.is_dir() and (examples_dir / "_sources.yaml").is_file():
155
+ return examples_dir
156
+ return None
157
+
158
+
159
+ def _inline_render_contexts(project_dir: Path) -> list[tuple[Path, Path]]:
160
+ """Return candidate ``(project_root, base_dir)`` contexts for inline YAML."""
161
+ examples_dir = _shared_examples_dir(project_dir)
162
+ if examples_dir is not None:
163
+ # Docs inline examples resolve shared queries and named sources from here.
164
+ return [(examples_dir, examples_dir), (project_dir, project_dir)]
165
+ candidates: list[tuple[Path, Path]] = [(project_dir, project_dir)]
166
+ fallback = project_dir / "examples"
167
+ if fallback.is_dir():
168
+ candidates.append((fallback, fallback))
169
+ return candidates
170
+
171
+
172
+ def _file_render_contexts(
173
+ face_path: Path, project_dir: Path
174
+ ) -> list[tuple[Path, Path]]:
175
+ """Return candidate ``(project_root, base_dir)`` contexts for file-backed YAML."""
176
+ file_ctx = (project_dir, face_path.parent)
177
+ examples_dir = _shared_examples_dir(project_dir)
178
+ if examples_dir is not None:
179
+ return [(examples_dir, examples_dir), file_ctx]
180
+ candidates: list[tuple[Path, Path]] = [file_ctx]
181
+ fallback = project_dir / "examples"
182
+ if fallback.is_dir():
183
+ candidates.append((fallback, fallback))
184
+ return candidates
185
+
186
+
187
+ _SOURCE_NOT_FOUND_RE = re.compile(
188
+ r"Source .+ not found|No source profiles are configured",
189
+ re.I,
190
+ )
191
+
192
+
193
+ def _should_retry_with_examples(exc: Exception, current_project_dir: Path) -> bool:
194
+ """Retry against ``examples/`` for shared docs fixtures the repo root lacks."""
195
+ if current_project_dir.name == "examples":
196
+ return False
197
+ message = str(exc)
198
+ return bool(
199
+ _EXTERNAL_QUERY_NOT_FOUND_RE.search(message)
200
+ or _SOURCE_NOT_FOUND_RE.search(message)
201
+ )
202
+
203
+
204
+ def _render_inline_with_fallback(yaml_source: str, project_dir: Path) -> str:
205
+ """Render inline YAML, retrying against ``examples/`` when docs shared queries need it."""
206
+ last_exc: Exception | None = None
207
+ for candidate_project_dir, candidate_base_dir in _inline_render_contexts(
208
+ project_dir
209
+ ):
210
+ try:
211
+ result = compile_and_render(
212
+ yaml_source,
213
+ project_root=candidate_project_dir,
214
+ dbt_project_path=None,
215
+ base_dir=candidate_base_dir,
216
+ format="svg",
217
+ )
218
+ return cast(str, result)
219
+ except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
220
+ last_exc = exc
221
+ if not _should_retry_with_examples(exc, candidate_project_dir):
222
+ raise
223
+ assert last_exc is not None
224
+ raise last_exc
225
+
226
+
227
+ def _render_file_with_fallback(face_path: Path, project_dir: Path) -> str:
228
+ """Render a face file, retrying against ``examples/`` when docs shared queries need it."""
229
+ last_exc: Exception | None = None
230
+ for candidate_project_dir, candidate_base_dir in _file_render_contexts(
231
+ face_path, project_dir
232
+ ):
233
+ try:
234
+ result = render_face(
235
+ face_path,
236
+ format="svg",
237
+ project_dir=candidate_project_dir,
238
+ base_dir=candidate_base_dir,
239
+ )
240
+ return cast(str, result)
241
+ except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
242
+ last_exc = exc
243
+ if not _should_retry_with_examples(exc, candidate_project_dir):
244
+ raise
245
+ assert last_exc is not None
246
+ raise last_exc
247
+
248
+
249
+ def _candidate_external_bases(
250
+ project_dir: Path,
251
+ source_base_dir: Path | None,
252
+ ) -> list[Path]:
253
+ """Return candidate base dirs for resolving external query references."""
254
+ candidates: list[Path] = []
255
+ for candidate in (
256
+ source_base_dir,
257
+ project_dir,
258
+ project_dir / "examples",
259
+ ):
260
+ if candidate is None:
261
+ continue
262
+ resolved = candidate.resolve()
263
+ if resolved not in candidates:
264
+ candidates.append(resolved)
265
+ return candidates
266
+
267
+
268
+ def _load_external_query_definition(
269
+ query_ref: str,
270
+ project_dir: Path,
271
+ source_base_dir: Path | None,
272
+ ) -> tuple[str, Any]:
273
+ """Load a raw external query definition for playground-link inlining."""
274
+ file_part, query_name = query_ref.split("#", 1)
275
+
276
+ for candidate_base in _candidate_external_bases(project_dir, source_base_dir):
277
+ candidate_file = (candidate_base / file_part).resolve()
278
+ if not candidate_file.exists():
279
+ continue
280
+
281
+ raw = yaml.safe_load(candidate_file.read_text(encoding="utf-8")) or {}
282
+ queries = raw.get("queries", {}) if isinstance(raw, dict) else {}
283
+ if query_name in queries:
284
+ return query_name, queries[query_name]
285
+ raise ValueError(
286
+ f"Query '{query_name}' not found in '{candidate_file}'. "
287
+ f"Available queries: {', '.join(sorted(queries)) if queries else 'none'}"
288
+ )
289
+
290
+ raise FileNotFoundError(
291
+ f"External query file not found for playground link: {file_part}"
292
+ )
293
+
294
+
295
+ def _inline_playground_queries(
296
+ yaml_source: str,
297
+ project_dir: Path,
298
+ source_base_dir: Path | None,
299
+ ) -> str:
300
+ """Rewrite external query refs into local inline queries for playground URLs."""
301
+ doc = yaml.safe_load(yaml_source)
302
+ if not isinstance(doc, dict):
303
+ return yaml_source
304
+
305
+ query_defs = doc.get("queries")
306
+ if query_defs is None:
307
+ query_defs = {}
308
+ doc["queries"] = query_defs
309
+ if not isinstance(query_defs, dict):
310
+ return yaml_source
311
+
312
+ inlined_any = False
313
+
314
+ def visit(node: Any) -> None:
315
+ nonlocal inlined_any
316
+ if isinstance(node, dict):
317
+ query_ref = node.get("query")
318
+ if isinstance(query_ref, str) and "#" in query_ref:
319
+ query_name, query_def = _load_external_query_definition(
320
+ query_ref,
321
+ project_dir,
322
+ source_base_dir,
323
+ )
324
+ existing = query_defs.get(query_name)
325
+ if existing is None:
326
+ query_defs[query_name] = query_def
327
+ elif existing != query_def:
328
+ suffix = 2
329
+ rewritten_name = f"{query_name}_{suffix}"
330
+ while (
331
+ rewritten_name in query_defs
332
+ and query_defs[rewritten_name] != query_def
333
+ ):
334
+ suffix += 1
335
+ rewritten_name = f"{query_name}_{suffix}"
336
+ query_defs.setdefault(rewritten_name, query_def)
337
+ query_name = rewritten_name
338
+ node["query"] = query_name
339
+ inlined_any = True
340
+
341
+ for value in node.values():
342
+ visit(value)
343
+ elif isinstance(node, list):
344
+ for item in node:
345
+ visit(item)
346
+
347
+ visit(doc)
348
+ if not inlined_any:
349
+ return yaml_source
350
+ return yaml.safe_dump(doc, sort_keys=False)
351
+
352
+
353
+ def _pg_link(pg_url: str, extra_class: str = "") -> str:
354
+ """Generate a playground link anchor tag."""
355
+ cls = f"playground-link {extra_class}".strip()
356
+ return (
357
+ f'<a href="{pg_url}" target="_blank" class="{cls}" '
358
+ f'rel="noopener noreferrer" title="Try in Playground">'
359
+ f'<span class="playground-icon">\u25b6</span></a>'
360
+ )
361
+
362
+
363
+ def _error_div(message: str) -> str:
364
+ escaped = html.escape(message)
365
+ return (
366
+ f'<div class="dataface-error">'
367
+ f"<strong>Dataface render error:</strong> {escaped}</div>"
368
+ )
369
+
370
+
371
+ # ---------------------------------------------------------------------------
372
+ # fence_dataface — render-only
373
+ # ---------------------------------------------------------------------------
374
+
375
+
376
+ def fence_dataface(
377
+ source: str,
378
+ language: str,
379
+ class_name: str,
380
+ options: dict[str, Any],
381
+ md: Any,
382
+ **kwargs: Any,
383
+ ) -> str:
384
+ """Superfences handler: render YAML to embedded SVG/HTML.
385
+
386
+ Usage::
387
+
388
+ ```dataface
389
+ charts:
390
+ c1:
391
+ type: bar
392
+ x: product
393
+ y: revenue
394
+ ```
395
+
396
+ ```dataface {file=path/to/face.yml}
397
+ ```
398
+ """
399
+ try:
400
+ project_dir = _resolve_project_dir()
401
+ except RuntimeError as exc:
402
+ return _error_div(str(exc))
403
+
404
+ resolved_options = _fence_options(options, kwargs)
405
+ file_opt = resolved_options.get("file")
406
+ try:
407
+ if file_opt:
408
+ resolved = _resolve_file(file_opt, project_dir)
409
+ rendered = _render_file_with_fallback(resolved, project_dir)
410
+ else:
411
+ yaml_source = source.strip()
412
+ if not yaml_source:
413
+ return _error_div(
414
+ "no YAML source provided (use inline content or file= option)"
415
+ )
416
+ rendered = _render_inline_with_fallback(yaml_source, project_dir)
417
+ return f'<div class="dataface-embed">{rendered}</div>'
418
+ except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
419
+ logger.warning("Dataface render failed: %s", exc)
420
+ return _error_div(str(exc))
421
+
422
+
423
+ # ---------------------------------------------------------------------------
424
+ # fence_dataface_example — code + render
425
+ # ---------------------------------------------------------------------------
426
+
427
+
428
+ def fence_dataface_example(
429
+ source: str,
430
+ language: str,
431
+ class_name: str,
432
+ options: dict[str, Any],
433
+ md: Any,
434
+ **kwargs: Any,
435
+ ) -> str:
436
+ """Superfences handler: code-plus-render example layout.
437
+
438
+ Options (via ``{key=value}`` in the info string):
439
+
440
+ - ``file=path`` — read YAML from a file instead of inline
441
+ - ``format=side-by-side`` (default), ``stacked``, ``render-only``, ``yaml-only``
442
+
443
+ Usage::
444
+
445
+ ```dataface-example {file=charts/examples/bar-charts/minimum.yml}
446
+ ```
447
+
448
+ ```dataface-example {format=stacked}
449
+ charts:
450
+ c1:
451
+ type: bar
452
+ ```
453
+ """
454
+ try:
455
+ project_dir = _resolve_project_dir()
456
+ except RuntimeError as exc:
457
+ return _error_div(str(exc))
458
+
459
+ resolved_options = _fence_options(options, kwargs)
460
+ layout = resolved_options.get("format", "side-by-side")
461
+ if layout not in _VALID_LAYOUTS:
462
+ return _error_div(
463
+ f"unknown format {layout!r}, expected one of: "
464
+ f"{', '.join(sorted(_VALID_LAYOUTS))}"
465
+ )
466
+
467
+ # Resolve source: file or inline
468
+ file_opt = resolved_options.get("file")
469
+ resolved_path: Path | None = None
470
+ if file_opt:
471
+ try:
472
+ resolved_path = _resolve_file(file_opt, project_dir)
473
+ yaml_source = resolved_path.read_text(encoding="utf-8").strip()
474
+ except (FileNotFoundError, ValueError, OSError) as exc:
475
+ return _error_div(str(exc))
476
+ else:
477
+ yaml_source = source.strip()
478
+
479
+ if not yaml_source:
480
+ return _error_div("no YAML source provided")
481
+
482
+ example_id = hashlib.md5(yaml_source.encode(), usedforsecurity=False).hexdigest()[
483
+ :12
484
+ ]
485
+ playground_yaml = yaml_source
486
+ try:
487
+ playground_yaml = _inline_playground_queries(
488
+ yaml_source,
489
+ project_dir,
490
+ resolved_path.parent if resolved_path else project_dir,
491
+ )
492
+ except (FileNotFoundError, ValueError, OSError, yaml.YAMLError) as exc:
493
+ logger.warning("Playground payload rewrite failed: %s", exc)
494
+ pg_url = _playground_url(playground_yaml)
495
+
496
+ # yaml-only: no rendering
497
+ if layout == "yaml-only":
498
+ highlighted = _highlight_yaml(yaml_source)
499
+ return (
500
+ f'<div class="example-container example-yaml-only" '
501
+ f'id="example-container-{example_id}">\n'
502
+ f'<div class="example-code">\n{highlighted}\n'
503
+ f"{_pg_link(pg_url, 'playground-link-inline')}\n"
504
+ f"</div>\n</div>"
505
+ )
506
+
507
+ # Render the face
508
+ rendered_html: str
509
+ try:
510
+ if resolved_path:
511
+ rendered = _render_file_with_fallback(resolved_path, project_dir)
512
+ else:
513
+ rendered = _render_inline_with_fallback(yaml_source, project_dir)
514
+ rendered_html = f'<div class="rendered-dashboard">{rendered}</div>'
515
+ except (FileNotFoundError, ValueError, OSError, RenderError) as exc:
516
+ logger.warning("Dataface render failed: %s", exc)
517
+ rendered_html = _error_div(str(exc))
518
+
519
+ # render-only: no code panel
520
+ if layout == "render-only":
521
+ return (
522
+ f'<div class="example-container example-render-only" '
523
+ f'id="example-container-{example_id}">\n'
524
+ f'<div class="example-render" id="example-render-{example_id}">\n'
525
+ f"{rendered_html}\n{_pg_link(pg_url)}\n</div>\n</div>"
526
+ )
527
+
528
+ # side-by-side (default) or stacked
529
+ highlighted = _highlight_yaml(yaml_source)
530
+ layout_class = " example-stacked" if layout == "stacked" else ""
531
+ return (
532
+ f'<div class="example-container{layout_class}" '
533
+ f'id="example-container-{example_id}">\n'
534
+ f'<div class="example-code">\n{highlighted}\n</div>\n'
535
+ f'<div class="example-render" id="example-render-{example_id}">\n'
536
+ f"{rendered_html}\n{_pg_link(pg_url)}\n</div>\n</div>"
537
+ )
dataface/py.typed ADDED
File without changes