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,486 @@
1
+ """Session auto-save and resume for dft chat.
2
+
3
+ Storage layout (mirrors Codex CLI's date-partitioned shape):
4
+
5
+ ~/.dft/sessions/
6
+ 2026/04/28/
7
+ rollout-20260428142133-7e3b9f.jsonl
8
+ index.json # cwd → list of SessionMeta; rebuild-able from JSONL files
9
+
10
+ JSONL schema (one JSON object per line):
11
+
12
+ {"type":"meta","session_id":"...","cwd":"...","provider":"...","model":"...","started_at":"..."}
13
+
14
+ After each user prompt:
15
+ {"type":"turn","messages":[
16
+ {"role":"user","content":"..."},
17
+ {"role":"assistant","content":"...","tool_calls":[...]},
18
+ {"role":"tool","tool_call_id":"...","name":"...","content":"..."},
19
+ ...
20
+ ]}
21
+
22
+ The ``turn`` event holds the exact message dicts that run_agent appended to the
23
+ conversation for one prompt/response cycle. Replay is trivial: concatenate all
24
+ turn["messages"] arrays in order.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import logging
31
+ import uuid
32
+ from collections.abc import Iterator
33
+ from datetime import datetime, timezone
34
+ from pathlib import Path
35
+ from typing import Any
36
+
37
+ from dataface.agent_api._state import dft_home, dft_state_path
38
+
39
+ logger = logging.getLogger(__name__)
40
+
41
+
42
+ def _now_iso() -> str:
43
+ return datetime.now(tz=timezone.utc).astimezone().isoformat()
44
+
45
+
46
+ def _session_filename(dt: datetime, short_id: str) -> str:
47
+ """Build the filename from a parsed datetime + short id."""
48
+ return f"rollout-{dt.strftime('%Y%m%d%H%M%S')}-{short_id}.jsonl"
49
+
50
+
51
+ class SessionWriter:
52
+ """Append-only JSONL session writer.
53
+
54
+ Writes a ``meta`` line first, then one ``turn`` JSON line per prompt/response
55
+ cycle. Every write flushes so a kill -9 loses at most the current in-flight
56
+ turn, not the whole transcript.
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ session_id: str,
62
+ cwd: Path,
63
+ *,
64
+ provider: str,
65
+ model: str = "",
66
+ started_at: str | None = None,
67
+ ) -> None:
68
+ self.session_id = session_id
69
+ self.cwd = cwd
70
+ self.provider = provider
71
+ self.model = model
72
+ self.started_at = started_at or _now_iso()
73
+
74
+ # Derive storage path from the started_at timestamp
75
+ dt = datetime.fromisoformat(self.started_at)
76
+ yyyy = f"{dt.year:04d}"
77
+ mm = f"{dt.month:02d}"
78
+ dd = f"{dt.day:02d}"
79
+ fname = _session_filename(dt, session_id[:6])
80
+ self.path: Path = dft_state_path("sessions", yyyy, mm, dd, fname)
81
+
82
+ # Open for append; write meta line immediately.
83
+ self._fh = self.path.open("a")
84
+ meta: dict[str, Any] = {
85
+ "type": "meta",
86
+ "session_id": session_id,
87
+ "cwd": str(cwd),
88
+ "provider": provider,
89
+ "model": model,
90
+ "started_at": self.started_at,
91
+ }
92
+ self._fh.write(json.dumps(meta) + "\n")
93
+ self._fh.flush()
94
+
95
+ def write_turn(self, new_messages: list[dict[str, Any]]) -> None:
96
+ """Persist the messages appended during one prompt/response cycle."""
97
+ self._fh.write(
98
+ json.dumps({"type": "turn", "messages": new_messages}, default=str) + "\n"
99
+ )
100
+ self._fh.flush()
101
+
102
+ def close(self) -> None:
103
+ self._fh.close()
104
+
105
+
106
+ class SessionReader:
107
+ """Iterate events from a JSONL session file."""
108
+
109
+ def __init__(self, path: Path) -> None:
110
+ self.path = path
111
+
112
+ def __iter__(self) -> Iterator[dict[str, Any]]:
113
+ with self.path.open() as f:
114
+ for line in f:
115
+ line = line.strip()
116
+ if not line:
117
+ continue
118
+ try:
119
+ yield json.loads(line)
120
+ except json.JSONDecodeError:
121
+ logger.warning("Skipping malformed JSONL line in %s", self.path)
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Index
126
+ # ---------------------------------------------------------------------------
127
+
128
+ SessionMeta = dict[str, Any]
129
+
130
+
131
+ class SessionIndex:
132
+ """Persistent index mapping cwd → list of SessionMeta.
133
+
134
+ The index is stored as ~/.dft/sessions/index.json.
135
+ If the file is missing or malformed the index is rebuilt by scanning
136
+ all JSONL files under ~/.dft/sessions/ — it is a cache, not a source of truth.
137
+ """
138
+
139
+ def __init__(self) -> None:
140
+ self._path = dft_home() / "sessions" / "index.json"
141
+ self._data: dict[str, list[SessionMeta]] = self._load()
142
+
143
+ # ------------------------------------------------------------------
144
+ # Public API
145
+ # ------------------------------------------------------------------
146
+
147
+ def record(
148
+ self,
149
+ session_id: str,
150
+ cwd: Path,
151
+ started_at: str,
152
+ path: Path,
153
+ ) -> None:
154
+ """Add or update a session entry and persist the index."""
155
+ cwd_str = str(cwd)
156
+ entry: SessionMeta = {
157
+ "session_id": session_id,
158
+ "cwd": cwd_str,
159
+ "started_at": started_at,
160
+ "path": str(path),
161
+ }
162
+ bucket = self._data.setdefault(cwd_str, [])
163
+ # Replace if already present (same id), else append.
164
+ for i, existing in enumerate(bucket):
165
+ if existing["session_id"] == session_id:
166
+ bucket[i] = entry
167
+ self._save()
168
+ return
169
+ bucket.append(entry)
170
+ self._save()
171
+
172
+ def list_for_cwd(self, cwd: Path) -> list[SessionMeta]:
173
+ """Return all session metadata entries for *cwd*, newest first."""
174
+ entries = list(self._data.get(str(cwd), []))
175
+ entries.sort(key=lambda e: e.get("started_at", ""), reverse=True)
176
+ return entries
177
+
178
+ def list_all(self) -> list[SessionMeta]:
179
+ """Return all session metadata entries across all cwds, newest first."""
180
+ all_entries: list[SessionMeta] = []
181
+ for entries in self._data.values():
182
+ all_entries.extend(entries)
183
+ all_entries.sort(key=lambda e: e.get("started_at", ""), reverse=True)
184
+ return all_entries
185
+
186
+ def find(self, session_id: str) -> SessionMeta | None:
187
+ """Find a session by id, searching across all cwds."""
188
+ for entries in self._data.values():
189
+ for entry in entries:
190
+ if entry["session_id"] == session_id:
191
+ return entry
192
+ return None
193
+
194
+ # ------------------------------------------------------------------
195
+ # Persistence helpers
196
+ # ------------------------------------------------------------------
197
+
198
+ def _load(self) -> dict[str, list[SessionMeta]]:
199
+ if self._path.exists():
200
+ try:
201
+ raw = json.loads(self._path.read_text())
202
+ if isinstance(raw, dict):
203
+ return raw
204
+ logger.warning("sessions/index.json has unexpected shape; rebuilding")
205
+ except (json.JSONDecodeError, OSError) as exc:
206
+ logger.warning("sessions/index.json unreadable (%s); rebuilding", exc)
207
+ return self._rebuild()
208
+
209
+ def _save(self) -> None:
210
+ self._path.parent.mkdir(parents=True, exist_ok=True)
211
+ self._path.write_text(json.dumps(self._data, indent=2))
212
+
213
+ def _rebuild(self) -> dict[str, list[SessionMeta]]:
214
+ """Scan all JSONL files under ~/.dft/sessions/ and reconstruct the index."""
215
+ sessions_root = dft_home() / "sessions"
216
+ data: dict[str, list[SessionMeta]] = {}
217
+ if not sessions_root.exists():
218
+ return data
219
+ for jsonl_file in sessions_root.rglob("*.jsonl"):
220
+ meta = _read_meta_line(jsonl_file)
221
+ if meta is None:
222
+ continue
223
+ cwd = meta.get("cwd", "")
224
+ session_id = meta.get("session_id", "")
225
+ started_at = meta.get("started_at", "")
226
+ if not (cwd and session_id):
227
+ continue
228
+ entry: SessionMeta = {
229
+ "session_id": session_id,
230
+ "cwd": cwd,
231
+ "started_at": started_at,
232
+ "path": str(jsonl_file.relative_to(dft_home())),
233
+ }
234
+ data.setdefault(cwd, []).append(entry)
235
+ # Persist the rebuilt index
236
+ self._path.parent.mkdir(parents=True, exist_ok=True)
237
+ self._path.write_text(json.dumps(data, indent=2))
238
+ return data
239
+
240
+
241
+ def _read_meta_line(path: Path) -> dict[str, Any] | None:
242
+ """Read and return the first line of a JSONL file as a dict, or None."""
243
+ try:
244
+ with path.open() as f:
245
+ first = f.readline().strip()
246
+ if not first:
247
+ return None
248
+ obj = json.loads(first)
249
+ return obj if isinstance(obj, dict) and obj.get("type") == "meta" else None
250
+ except (OSError, json.JSONDecodeError):
251
+ return None
252
+
253
+
254
+ # ---------------------------------------------------------------------------
255
+ # Replay
256
+ # ---------------------------------------------------------------------------
257
+
258
+
259
+ def _estimate_tokens(messages: list[dict[str, Any]]) -> int:
260
+ """Rough token estimate via JSON-bytes / 4. No tokenizer dep.
261
+
262
+ The 4-bytes-per-token heuristic is pessimistic enough for English-heavy
263
+ LLM messages; the safety margin baked into trim callers covers the noise.
264
+ """
265
+ if not messages:
266
+ return 0
267
+ return sum(len(json.dumps(m, default=str)) for m in messages) // 4
268
+
269
+
270
+ def _split_into_units(messages: list[dict[str, Any]]) -> list[list[dict[str, Any]]]:
271
+ """Group messages into atomic units that must drop or stay together.
272
+
273
+ A unit is one of:
274
+ - A user turn (one message).
275
+ - An assistant turn without tool_calls (one message).
276
+ - An assistant turn WITH tool_calls + all following tool responses (1 + N).
277
+
278
+ Tool responses (`role == "tool"`) are never standalone — they belong to
279
+ the immediately preceding assistant turn that issued the tool_call.
280
+ Splitting on assistant boundaries preserves tool_call/tool_result pairs.
281
+ """
282
+ units: list[list[dict[str, Any]]] = []
283
+ current: list[dict[str, Any]] = []
284
+ for msg in messages:
285
+ role = msg.get("role")
286
+ if role == "tool":
287
+ # A tool response must follow the assistant turn that issued
288
+ # the tool_call — otherwise the JSONL session is corrupt.
289
+ if not current:
290
+ raise ValueError(
291
+ "Session corrupt: tool response without preceding "
292
+ "assistant turn. The session JSONL file is malformed."
293
+ )
294
+ current.append(msg)
295
+ continue
296
+ # New user / assistant message starts a new unit.
297
+ if current:
298
+ units.append(current)
299
+ current = [msg]
300
+ if current:
301
+ units.append(current)
302
+ return units
303
+
304
+
305
+ def trim_messages_to_context(
306
+ messages: list[dict[str, Any]],
307
+ *,
308
+ max_tokens: int,
309
+ ) -> tuple[list[dict[str, Any]], int]:
310
+ """Drop oldest message-units until the estimated token count fits.
311
+
312
+ Tool_call/tool_result pairs drop together so the LLM never sees an
313
+ orphan tool response (which would error or hallucinate).
314
+
315
+ Args:
316
+ messages: The replayed message list.
317
+ max_tokens: Target token ceiling (model context minus safety margin).
318
+
319
+ Returns:
320
+ ``(trimmed_messages, dropped_message_count)``.
321
+
322
+ Raises:
323
+ ValueError: If the most-recent unit alone exceeds *max_tokens*.
324
+ Resuming would corrupt the conversation; the caller should ask
325
+ the user to start a fresh session or shrink the prompt.
326
+ """
327
+ if _estimate_tokens(messages) <= max_tokens and (
328
+ not messages or messages[0].get("role") == "user"
329
+ ):
330
+ return messages, 0
331
+
332
+ units = _split_into_units(messages)
333
+ if not units:
334
+ return messages, 0
335
+
336
+ def _flat(us: list[list[dict[str, Any]]]) -> list[dict[str, Any]]:
337
+ return [m for unit in us for m in unit]
338
+
339
+ # Drop oldest units until we fit AND the resulting first message is a
340
+ # user turn — LLM APIs reject conversations that start with assistant
341
+ # or tool. If the most-recent unit alone can't satisfy both invariants,
342
+ # raise rather than corrupt the conversation.
343
+ dropped = 0
344
+ while units and (
345
+ _estimate_tokens(_flat(units)) > max_tokens or units[0][0].get("role") != "user"
346
+ ):
347
+ if len(units) == 1:
348
+ raise ValueError(
349
+ f"Session too large to resume: the most-recent turn alone "
350
+ f"exceeds the {max_tokens}-token budget, or doesn't start "
351
+ f"with a user message. Start a fresh session or trim the inputs."
352
+ )
353
+ dropped += len(units[0])
354
+ units.pop(0)
355
+
356
+ return _flat(units), dropped
357
+
358
+
359
+ def replay_into_messages(
360
+ reader: SessionReader,
361
+ *,
362
+ expected_provider: str,
363
+ ) -> list[dict[str, Any]]:
364
+ """Convert a JSONL session stream back into the LLM messages list.
365
+
366
+ Each ``turn`` event contains the exact message dicts that run_agent appended
367
+ during one prompt/response cycle. Replay concatenates them in order.
368
+
369
+ Args:
370
+ reader: SessionReader over a session JSONL file.
371
+ expected_provider: The provider the current client uses. Raises
372
+ ValueError if it doesn't match the session's recorded provider.
373
+
374
+ Returns:
375
+ List of ``{"role": ..., "content": ...}`` dicts suitable for
376
+ passing to ``run_agent(..., messages=...)``.
377
+ """
378
+ messages: list[dict[str, Any]] = []
379
+ meta_seen = False
380
+
381
+ for event in reader:
382
+ event_type = event.get("type", "")
383
+
384
+ if event_type == "meta":
385
+ if not meta_seen:
386
+ meta_seen = True
387
+ recorded_provider = event.get("provider", "")
388
+ if recorded_provider and recorded_provider != expected_provider:
389
+ raise ValueError(
390
+ f"Cannot resume session {event.get('session_id', '?')}: "
391
+ f"it was started with {recorded_provider}; "
392
+ f"current default is {expected_provider}. "
393
+ f"Run with --model {recorded_provider}:<model> to resume."
394
+ )
395
+ continue
396
+
397
+ elif event_type == "turn":
398
+ turn_messages = event.get("messages", [])
399
+ if isinstance(turn_messages, list):
400
+ messages.extend(turn_messages)
401
+ else:
402
+ raise ValueError(
403
+ f"Unknown JSONL event type {event_type!r} in session file. "
404
+ "The session file may be from a newer version of dft."
405
+ )
406
+
407
+ return messages
408
+
409
+
410
+ # ---------------------------------------------------------------------------
411
+ # Load helpers used by the CLI
412
+ # ---------------------------------------------------------------------------
413
+
414
+
415
+ def new_session(
416
+ cwd: Path,
417
+ *,
418
+ provider: str,
419
+ model: str = "",
420
+ ) -> tuple[SessionWriter, SessionIndex]:
421
+ """Create a new session writer and register it in the index.
422
+
423
+ Returns:
424
+ (writer, index)
425
+ """
426
+ session_id = uuid.uuid4().hex
427
+ started_at = _now_iso()
428
+ writer = SessionWriter(
429
+ session_id=session_id,
430
+ cwd=cwd,
431
+ provider=provider,
432
+ model=model,
433
+ started_at=started_at,
434
+ )
435
+ index = SessionIndex()
436
+ index.record(
437
+ session_id=session_id,
438
+ cwd=cwd,
439
+ started_at=started_at,
440
+ path=writer.path.relative_to(dft_home()),
441
+ )
442
+ return writer, index
443
+
444
+
445
+ def load_session_for_resume(
446
+ session_id: str,
447
+ *,
448
+ expected_provider: str,
449
+ max_tokens: int | None = None,
450
+ ) -> tuple[list[dict[str, Any]], SessionMeta, int]:
451
+ """Load and replay a session by id, optionally trimming to fit context.
452
+
453
+ Args:
454
+ session_id: The session id to load.
455
+ expected_provider: Provider to validate against.
456
+ max_tokens: When set, oldest tool_call/tool_result-preserving units
457
+ are dropped until the message count fits. ``None`` skips trim.
458
+
459
+ Returns:
460
+ ``(messages, meta, dropped_count)`` — replayed messages, the session
461
+ meta dict, and the number of messages dropped to fit ``max_tokens``
462
+ (0 when no trim was needed or requested).
463
+
464
+ Raises:
465
+ ValueError: If session not found, cross-provider mismatch, or the
466
+ most-recent unit alone exceeds ``max_tokens``.
467
+ """
468
+ index = SessionIndex()
469
+ meta = index.find(session_id)
470
+ if meta is None:
471
+ raise ValueError(f"No session found with id {session_id}")
472
+
473
+ session_path_str = meta.get("path", "")
474
+ if not session_path_str:
475
+ raise ValueError(f"No session found with id {session_id}")
476
+
477
+ session_path = dft_home() / session_path_str
478
+ if not session_path.exists():
479
+ raise ValueError(f"No session found with id {session_id}")
480
+
481
+ reader = SessionReader(session_path)
482
+ messages = replay_into_messages(reader, expected_provider=expected_provider)
483
+ dropped = 0
484
+ if max_tokens is not None:
485
+ messages, dropped = trim_messages_to_context(messages, max_tokens=max_tokens)
486
+ return messages, meta, dropped
@@ -0,0 +1,28 @@
1
+ """On-disk state directory for dft.
2
+
3
+ Default: ~/.dft/
4
+ Override via DFT_HOME (unconditional) or XDG_CONFIG_HOME (maps to <XDG>/dft).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ from pathlib import Path
11
+
12
+
13
+ def dft_home() -> Path:
14
+ if dft := os.environ.get("DFT_HOME"):
15
+ return Path(dft)
16
+ if xdg := os.environ.get("XDG_CONFIG_HOME"):
17
+ return Path(xdg) / "dft"
18
+ return Path.home() / ".dft"
19
+
20
+
21
+ def dft_state_path(*parts: str) -> Path:
22
+ if not parts:
23
+ raise ValueError(
24
+ "dft_state_path requires at least one path component; call dft_home() for the bare home dir"
25
+ )
26
+ path = dft_home().joinpath(*parts)
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ return path