dataface 0.1.6.dev82__py3-none-any.whl → 0.1.6.dev183__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.
- dataface/agent_api/_paths.py +7 -18
- dataface/agent_api/docs/yaml-reference.md +14 -13
- dataface/agent_api/project_session.py +5 -44
- dataface/agent_api/query.py +6 -0
- dataface/agent_api/render_face.py +1 -14
- dataface/ai/context.py +1 -0
- dataface/ai/mcp/server.py +5 -5
- dataface/ai/skills/dashboard-build/SKILL.md +7 -0
- dataface/cli/commands/_agent_input.py +3 -3
- dataface/cli/commands/render.py +2 -10
- dataface/core/compile/__init__.py +0 -2
- dataface/core/compile/channel.py +1 -6
- dataface/core/compile/colors.py +1 -1
- dataface/core/compile/compiler.py +15 -13
- dataface/core/compile/config.py +13 -8
- dataface/core/compile/dbt_jinja.py +66 -0
- dataface/core/compile/detect.py +32 -6
- dataface/core/compile/inherit_graph.py +31 -4
- dataface/core/compile/inherit_resolver.py +13 -4
- dataface/core/compile/jinja.py +2 -2
- dataface/core/compile/markdown.py +109 -18
- dataface/core/compile/models/chart/authored.py +33 -39
- dataface/core/compile/models/config.py +4 -0
- dataface/core/compile/models/face/resolved.py +3 -3
- dataface/core/compile/models/markers.py +14 -3
- dataface/core/compile/models/query/normalized.py +4 -0
- dataface/core/compile/models/source.py +4 -86
- dataface/core/compile/models/style/authored.py +2 -2
- dataface/core/compile/models/style/resolved.py +15 -518
- dataface/core/compile/models/style/theme.py +64 -58
- dataface/core/compile/normalize_charts.py +3 -3
- dataface/core/compile/normalize_layout.py +6 -33
- dataface/core/compile/normalize_variables.py +10 -10
- dataface/core/compile/normalizer.py +24 -24
- dataface/core/compile/parameterized.py +1 -6
- dataface/core/compile/sizing.py +30 -8
- dataface/core/compile/sources.py +19 -6
- dataface/core/compile/style_cascade.py +202 -60
- dataface/core/compile/typography.py +92 -89
- dataface/core/compile/yaml_error_formatter.py +102 -33
- dataface/core/defaults/default_config.yml +1 -0
- dataface/core/defaults/themes/_base.yaml +2 -2
- dataface/core/defaults/themes/stark.yaml +4 -0
- dataface/core/execute/adapters/adapter_registry.py +3 -8
- dataface/core/execute/adapters/dbt_adapter_factory.py +7 -7
- dataface/core/execute/adapters/sql_adapter.py +10 -6
- dataface/core/execute/batch.py +4 -2
- dataface/core/execute/duckdb_cache.py +4 -3
- dataface/core/execute/executor.py +3 -6
- dataface/core/inspect/query_validator.py +1 -1
- dataface/core/inspect/renderer.py +0 -1
- dataface/core/project_roots.py +15 -35
- dataface/core/render/__init__.py +0 -3
- dataface/core/render/board_links.py +71 -18
- dataface/core/render/chart/callout.py +5 -9
- dataface/core/render/chart/geo.py +71 -29
- dataface/core/render/chart/kpi.py +18 -11
- dataface/core/render/chart/pipeline.py +15 -98
- dataface/core/render/chart/profile.py +89 -77
- dataface/core/render/chart/render_single.py +116 -67
- dataface/core/render/chart/renderers.py +45 -95
- dataface/core/render/chart/rendering.py +67 -64
- dataface/core/render/chart/spark.py +1 -1
- dataface/core/render/chart/spark_bar.py +34 -20
- dataface/core/render/chart/standard_renderer.py +102 -228
- dataface/core/render/chart/table.py +58 -82
- dataface/core/render/chart/table_support.py +2 -5
- dataface/core/render/chart/time_unit_detect.py +28 -19
- dataface/core/render/chart/vega_lite.py +13 -23
- dataface/core/render/chart/vega_lite_types.py +5 -5
- dataface/core/render/converters/chart.py +5 -6
- dataface/core/render/dir_context.py +42 -38
- dataface/core/render/faces.py +35 -27
- dataface/core/render/font_support.py +8 -8
- dataface/core/render/layout_sizing.py +44 -53
- dataface/core/render/layouts.py +9 -17
- dataface/core/render/renderer.py +21 -18
- dataface/core/render/svg_utils.py +11 -8
- dataface/core/render/templates/nav/nav-fragment.html +1 -1
- dataface/core/render/templates/nav/nav.js +3 -3
- dataface/core/render/terminal_charts.py +2 -2
- dataface/core/render/utils.py +1 -1
- dataface/core/render/variable_controls.py +20 -22
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +4 -3
- dataface/core/render/warnings/base.py +2 -2
- dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +2 -2
- dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +2 -2
- dataface/core/render/warnings/redundant_encoding.py +10 -6
- dataface/core/render/warnings/temporal_single_point.py +3 -3
- dataface/core/resolve_face.py +21 -6
- dataface/core/scoped_paths.py +30 -9
- dataface/core/serve/alias_index.py +9 -17
- dataface/core/serve/bootstrap.py +2 -2
- dataface/core/serve/server.py +91 -46
- dataface/core/validate.py +2 -2
- dataface/integrations/highlighting.py +4 -3
- dataface/integrations/markdown.py +2 -2
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev183.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev183.dist-info}/RECORD +104 -111
- mdsvg/renderer.py +76 -16
- mdsvg/style.py +16 -4
- dataface/core/render/control_registry.py +0 -287
- dataface/core/render/templates/controls/checkbox.html +0 -16
- dataface/core/render/templates/controls/date.html +0 -16
- dataface/core/render/templates/controls/number.html +0 -19
- dataface/core/render/templates/controls/readonly.html +0 -9
- dataface/core/render/templates/controls/select.html +0 -20
- dataface/core/render/templates/controls/slider.html +0 -22
- dataface/core/render/templates/controls/text.html +0 -16
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev183.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev183.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev183.dist-info}/licenses/LICENSE +0 -0
dataface/agent_api/_paths.py
CHANGED
|
@@ -6,9 +6,8 @@ from dataclasses import dataclass
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from dataface.core.project_roots import (
|
|
9
|
-
discover_render_context as discover_render_context,
|
|
10
|
-
discovery_boundary_for_face as discovery_boundary_for_face,
|
|
11
9
|
find_dft_root as find_dft_root,
|
|
10
|
+
find_project_root as find_project_root,
|
|
12
11
|
)
|
|
13
12
|
from dataface.core.scoped_paths import (
|
|
14
13
|
resolve_scoped_path as resolve_scoped_path,
|
|
@@ -60,8 +59,8 @@ class FaceRenderContext:
|
|
|
60
59
|
"""Path resolution result from a face path + project root.
|
|
61
60
|
|
|
62
61
|
Adapter registry is built by `ProjectSession.open`, not by the context — call sites
|
|
63
|
-
open a `ProjectSession` with `project_root`
|
|
64
|
-
|
|
62
|
+
open a `ProjectSession` with `project_root` and read the registry off
|
|
63
|
+
`project.adapter_registry`.
|
|
65
64
|
"""
|
|
66
65
|
|
|
67
66
|
face_file: Path
|
|
@@ -69,18 +68,16 @@ class FaceRenderContext:
|
|
|
69
68
|
scoped_base: Path
|
|
70
69
|
project_root: Path
|
|
71
70
|
output_dir: Path
|
|
72
|
-
dbt_project_path: Path | None = None
|
|
73
71
|
|
|
74
72
|
|
|
75
73
|
def build_face_render_context(
|
|
76
74
|
face_path: Path,
|
|
77
75
|
project_dir: Path,
|
|
78
76
|
) -> FaceRenderContext:
|
|
79
|
-
"""Resolve a face path
|
|
77
|
+
"""Resolve a face path against the given project root.
|
|
80
78
|
|
|
81
|
-
``project_dir`` is authoritative
|
|
82
|
-
|
|
83
|
-
``resolve_project_dir(raw_dir)`` at the CLI boundary).
|
|
79
|
+
``project_dir`` is authoritative. Callers must resolve their project dir first
|
|
80
|
+
(e.g. via ``resolve_project_dir(raw_dir)`` at the CLI boundary).
|
|
84
81
|
"""
|
|
85
82
|
if face_path.is_absolute():
|
|
86
83
|
face_file = face_path.resolve()
|
|
@@ -89,10 +86,6 @@ def build_face_render_context(
|
|
|
89
86
|
else:
|
|
90
87
|
face_file = (project_dir / face_path).resolve()
|
|
91
88
|
|
|
92
|
-
_, dbt_project_path = discover_render_context(
|
|
93
|
-
face_file.parent,
|
|
94
|
-
discovery_boundary_for_face(face_file.parent, project_dir),
|
|
95
|
-
)
|
|
96
89
|
project_root = project_dir
|
|
97
90
|
|
|
98
91
|
try:
|
|
@@ -109,7 +102,6 @@ def build_face_render_context(
|
|
|
109
102
|
scoped_base=project_root,
|
|
110
103
|
project_root=project_root,
|
|
111
104
|
output_dir=project_root,
|
|
112
|
-
dbt_project_path=dbt_project_path,
|
|
113
105
|
)
|
|
114
106
|
|
|
115
107
|
|
|
@@ -122,21 +114,18 @@ class YamlRenderContext:
|
|
|
122
114
|
|
|
123
115
|
project_root: Path
|
|
124
116
|
output_dir: Path
|
|
125
|
-
dbt_project_path: Path | None = None
|
|
126
117
|
|
|
127
118
|
|
|
128
119
|
def build_yaml_render_context(
|
|
129
120
|
project_dir: Path,
|
|
130
121
|
) -> YamlRenderContext:
|
|
131
|
-
"""
|
|
122
|
+
"""Resolve the project root for rendering inline YAML.
|
|
132
123
|
|
|
133
124
|
``project_dir`` is authoritative. Callers must resolve their project dir
|
|
134
125
|
first (e.g. via ``resolve_project_dir(raw_dir)`` at the CLI boundary).
|
|
135
126
|
"""
|
|
136
127
|
project_root = project_dir.resolve()
|
|
137
|
-
_, dbt_project_path = discover_render_context(project_root, None)
|
|
138
128
|
return YamlRenderContext(
|
|
139
129
|
project_root=project_root,
|
|
140
130
|
output_dir=project_root,
|
|
141
|
-
dbt_project_path=dbt_project_path,
|
|
142
131
|
)
|
|
@@ -128,8 +128,8 @@ Authored patch for bar and histogram charts.
|
|
|
128
128
|
| `sort` | [ChartSort](#chartsort) | ✓ | Sort configuration: field to sort by and direction (asc/desc). |
|
|
129
129
|
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
130
130
|
| `data_table` | [ChartDataTable](#chartdatatable) | ✓ | Optional mini data-grid attached below/above the chart. |
|
|
131
|
-
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade.
|
|
132
|
-
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container.
|
|
131
|
+
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
132
|
+
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
133
133
|
| `style` | [BarChartStyle](#barchartstyle) | ✓ | Chart-local style overrides. |
|
|
134
134
|
|
|
135
135
|
<a id="linechart"></a>
|
|
@@ -156,8 +156,8 @@ Authored patch for line charts.
|
|
|
156
156
|
| `sort` | [ChartSort](#chartsort) | ✓ | Sort configuration: field to sort by and direction (asc/desc). |
|
|
157
157
|
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
158
158
|
| `data_table` | [ChartDataTable](#chartdatatable) | ✓ | Optional mini data-grid attached below/above the chart. |
|
|
159
|
-
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade.
|
|
160
|
-
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container.
|
|
159
|
+
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
160
|
+
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
161
161
|
| `style` | [LineChartStyle](#linechartstyle) | ✓ | Chart-local style overrides. |
|
|
162
162
|
|
|
163
163
|
<a id="areachart"></a>
|
|
@@ -184,8 +184,8 @@ Authored patch for area charts.
|
|
|
184
184
|
| `sort` | [ChartSort](#chartsort) | ✓ | Sort configuration: field to sort by and direction (asc/desc). |
|
|
185
185
|
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
186
186
|
| `data_table` | [ChartDataTable](#chartdatatable) | ✓ | Optional mini data-grid attached below/above the chart. |
|
|
187
|
-
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade.
|
|
188
|
-
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container.
|
|
187
|
+
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
188
|
+
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
189
189
|
| `style` | [AreaChartStyle](#areachartstyle) | ✓ | Chart-local style overrides. |
|
|
190
190
|
|
|
191
191
|
<a id="scatterchart"></a>
|
|
@@ -212,8 +212,8 @@ Authored patch for scatter charts.
|
|
|
212
212
|
| `sort` | [ChartSort](#chartsort) | ✓ | Sort configuration: field to sort by and direction (asc/desc). |
|
|
213
213
|
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
214
214
|
| `data_table` | [ChartDataTable](#chartdatatable) | ✓ | Optional mini data-grid attached below/above the chart. |
|
|
215
|
-
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade.
|
|
216
|
-
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container.
|
|
215
|
+
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
216
|
+
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
217
217
|
| `size` | str | ✓ | Field used to size-encode data points (quantitative). |
|
|
218
218
|
| `shape` | str | ✓ | Field used to shape-encode data points (categorical). |
|
|
219
219
|
| `style` | [ScatterChartStyle](#scatterchartstyle) | ✓ | Chart-local style overrides. |
|
|
@@ -242,8 +242,8 @@ Authored patch for heatmap charts.
|
|
|
242
242
|
| `sort` | [ChartSort](#chartsort) | ✓ | Sort configuration: field to sort by and direction (asc/desc). |
|
|
243
243
|
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
244
244
|
| `data_table` | [ChartDataTable](#chartdatatable) | ✓ | Optional mini data-grid attached below/above the chart. |
|
|
245
|
-
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade.
|
|
246
|
-
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container.
|
|
245
|
+
| `height` | int \| float | ✓ | Explicit chart height in pixels. Positive number only. When set, overrides aspect_ratio and theme cascade. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
246
|
+
| `width` | int \| float | ✓ | Chart width hint in pixels. Positive number only. Used by the label-overlap heuristic to determine whether x-axis labels need tilting. Does not override the layout slot width — the chart still fills its allocated container. Only valid on cartesian chart families: area, bar, heatmap, histogram, line, and scatter. Other chart families use renderer-owned or layout-owned sizing contracts. |
|
|
247
247
|
| `style` | [HeatmapChartStyle](#heatmapchartstyle) | ✓ | Chart-local style overrides. |
|
|
248
248
|
|
|
249
249
|
<a id="piechart"></a>
|
|
@@ -1082,8 +1082,8 @@ Authored overlay for TitleStyle. Board and face titles.
|
|
|
1082
1082
|
|-------|------|:--------:|-------------|
|
|
1083
1083
|
| `font` | [FontStyle](#fontstyle) | ✓ | Title font style overrides. |
|
|
1084
1084
|
| `line_height` | float | ✓ | Line height multiplier for titles and markdown headings. Headings typically want a tighter multiplier than body prose. |
|
|
1085
|
-
| `sizes` | list[float] | ✓ | Font sizes for the H1–H6 heading ramp, indexed by ``face.level - 1``.
|
|
1086
|
-
| `width_offsets` | [TitleWidthOffsetsStyle](#titlewidthoffsetsstyle) | ✓ | Additive level offsets by card width (tiny/narrow/medium/wide).
|
|
1085
|
+
| `sizes` | list[float] | ✓ | Font sizes for the H1–H6 heading ramp, indexed by ``face.level - 1``. Face/prose titles index this ramp directly (no width input). Object titles (chart/table/spark) combine ``width_offsets`` with a fixed object-title anchor to pick a slot. |
|
|
1086
|
+
| `width_offsets` | [TitleWidthOffsetsStyle](#titlewidthoffsetsstyle) | ✓ | Additive level offsets by card width (tiny/narrow/medium/wide). Consumed by ``chart_title_spec`` only — object titles add the tier offset to a fixed anchor to pick an H slot from ``sizes``. Face/prose titles are level-only and do not consult these. |
|
|
1087
1087
|
| `min_height` | float | ✓ | Minimum title row height in pixels. |
|
|
1088
1088
|
| `overflow` | str | ✓ | Text overflow mode (clip, truncate, wrap-two, wrap). |
|
|
1089
1089
|
| `position` | [TitlePositionStyle](#titlepositionstyle) | ✓ | Vega-Lite title positioning: anchor, angle, offset, baseline. |
|
|
@@ -2372,10 +2372,11 @@ Authored overlay for SparkAreaStyle.
|
|
|
2372
2372
|
|
|
2373
2373
|
<a id="geoshapemarkstyle"></a>
|
|
2374
2374
|
## GeoshapeMarkStyle
|
|
2375
|
-
Authored overlay for GeoshapeMarkStyle. Geoshape (choropleth) mark boundary stroke.
|
|
2375
|
+
Authored overlay for GeoshapeMarkStyle. Geoshape (choropleth) mark fill and boundary stroke.
|
|
2376
2376
|
|
|
2377
2377
|
| Field | Type | Optional | Description |
|
|
2378
2378
|
|-------|------|:--------:|-------------|
|
|
2379
|
+
| `fill` | str | ✓ | Neutral geoshape fill color. |
|
|
2379
2380
|
| `stroke` | [StrokeStyle](#strokestyle) | ✓ | Geoshape boundary stroke style. |
|
|
2380
2381
|
|
|
2381
2382
|
<a id="circlemarkstyle"></a>
|
|
@@ -17,7 +17,6 @@ else:
|
|
|
17
17
|
# Verb-forwarder imports use SUBMODULES so test monkeypatches on the
|
|
18
18
|
# module attribute affect the call site.
|
|
19
19
|
from dataface.agent_api import (
|
|
20
|
-
dashboards as _dashboards,
|
|
21
20
|
describe as _describe,
|
|
22
21
|
describe_query as _describe_query,
|
|
23
22
|
query as _query,
|
|
@@ -42,7 +41,7 @@ from dataface.core.inspect.query_validator import (
|
|
|
42
41
|
validate_query as _core_validate_query,
|
|
43
42
|
)
|
|
44
43
|
from dataface.core.project import Project
|
|
45
|
-
from dataface.core.project_roots import
|
|
44
|
+
from dataface.core.project_roots import find_project_root
|
|
46
45
|
|
|
47
46
|
if TYPE_CHECKING:
|
|
48
47
|
from dataface.core.dashboard import RenderedDashboard, RenderFormat
|
|
@@ -71,7 +70,6 @@ class ProjectSession:
|
|
|
71
70
|
cache: DuckDBCache | None
|
|
72
71
|
_owns_registry: bool
|
|
73
72
|
_read_only: bool
|
|
74
|
-
_dbt_project_path: Path | None
|
|
75
73
|
_connection_string: str | None
|
|
76
74
|
_dialect: str
|
|
77
75
|
_target: str
|
|
@@ -96,7 +94,6 @@ class ProjectSession:
|
|
|
96
94
|
# short-circuit the build on first access.
|
|
97
95
|
self.__dict__["adapter_registry"] = adapter_registry
|
|
98
96
|
self._read_only = read_only
|
|
99
|
-
self._dbt_project_path = None
|
|
100
97
|
self._connection_string = None
|
|
101
98
|
self._dialect = "duckdb"
|
|
102
99
|
self._target = "dev"
|
|
@@ -111,7 +108,6 @@ class ProjectSession:
|
|
|
111
108
|
*,
|
|
112
109
|
cache: DuckDBCache | None = None,
|
|
113
110
|
read_only: bool = True,
|
|
114
|
-
dbt_project_path: Path | None = None,
|
|
115
111
|
connection_string: str | None = None,
|
|
116
112
|
dialect: str = "duckdb",
|
|
117
113
|
target: str = "dev",
|
|
@@ -135,7 +131,6 @@ class ProjectSession:
|
|
|
135
131
|
cache=cache,
|
|
136
132
|
read_only=read_only,
|
|
137
133
|
)
|
|
138
|
-
session._dbt_project_path = dbt_project_path
|
|
139
134
|
session._connection_string = connection_string
|
|
140
135
|
session._dialect = dialect
|
|
141
136
|
session._target = target
|
|
@@ -151,7 +146,6 @@ class ProjectSession:
|
|
|
151
146
|
*,
|
|
152
147
|
cache: DuckDBCache | None = None,
|
|
153
148
|
read_only: bool = True,
|
|
154
|
-
dbt_project_path: Path | None = None,
|
|
155
149
|
connection_string: str | None = None,
|
|
156
150
|
dialect: str = "duckdb",
|
|
157
151
|
target: str = "dev",
|
|
@@ -166,7 +160,6 @@ class ProjectSession:
|
|
|
166
160
|
this method stores it directly without re-wrapping.
|
|
167
161
|
"""
|
|
168
162
|
session = cls(project=project, cache=cache, read_only=read_only)
|
|
169
|
-
session._dbt_project_path = dbt_project_path
|
|
170
163
|
session._connection_string = connection_string
|
|
171
164
|
session._dialect = dialect
|
|
172
165
|
session._target = target
|
|
@@ -185,13 +178,8 @@ class ProjectSession:
|
|
|
185
178
|
) -> Self:
|
|
186
179
|
"""Open a ProjectSession rooted at the project directory discovered upward from face_file."""
|
|
187
180
|
resolved = Path(face_file).resolve()
|
|
188
|
-
project_root
|
|
189
|
-
return cls.open(
|
|
190
|
-
project_root,
|
|
191
|
-
read_only=read_only,
|
|
192
|
-
cache=cache,
|
|
193
|
-
dbt_project_path=dbt_project_path,
|
|
194
|
-
)
|
|
181
|
+
project_root = find_project_root(resolved.parent, boundary=None)
|
|
182
|
+
return cls.open(project_root, read_only=read_only, cache=cache)
|
|
195
183
|
|
|
196
184
|
def __enter__(self) -> Self:
|
|
197
185
|
return self
|
|
@@ -210,7 +198,6 @@ class ProjectSession:
|
|
|
210
198
|
return build_adapter_registry(
|
|
211
199
|
self.project,
|
|
212
200
|
read_only=self._read_only,
|
|
213
|
-
dbt_project_path=self._dbt_project_path,
|
|
214
201
|
connection_string=self._connection_string,
|
|
215
202
|
profile_type=self._dialect,
|
|
216
203
|
target=self._target,
|
|
@@ -279,11 +266,6 @@ class ProjectSession:
|
|
|
279
266
|
|
|
280
267
|
# ── Verb forwarders ──────────────────────────────────────────────────────
|
|
281
268
|
|
|
282
|
-
def validate(self, face_path: Path) -> ValidateResult:
|
|
283
|
-
result = _validate.validate(face_path, project=self.project)
|
|
284
|
-
annotated = _validate.annotate_with_data_lint([result], project_session=self)
|
|
285
|
-
return annotated[0]
|
|
286
|
-
|
|
287
269
|
def validate_paths(self, paths: list[Path] | None) -> list[ValidateResult]:
|
|
288
270
|
results = _validate.validate_paths(paths, project=self.project)
|
|
289
271
|
return _validate.annotate_with_data_lint(results, project_session=self)
|
|
@@ -346,32 +328,9 @@ class ProjectSession:
|
|
|
346
328
|
relationship_context=self._relationship_context,
|
|
347
329
|
)
|
|
348
330
|
|
|
349
|
-
def describe_face(self, path: Path) -> _describe.DescribeFaceResult:
|
|
350
|
-
return _describe.describe_face(path, project=self.project)
|
|
351
|
-
|
|
352
331
|
def describe_paths(self, paths: list[Path]) -> list[_describe.DescribeFaceResult]:
|
|
353
332
|
return _describe.describe_paths(paths, project=self.project)
|
|
354
333
|
|
|
355
|
-
def list_dashboards(
|
|
356
|
-
self,
|
|
357
|
-
directory: Path | None = None,
|
|
358
|
-
recursive: bool = True,
|
|
359
|
-
) -> _dashboards.ListDashboardsResult:
|
|
360
|
-
"""When ``directory`` is None, defaults to ``self.project.root``. When provided, callers are responsible for passing an absolute path; relative paths resolve against the process working directory."""
|
|
361
|
-
return _dashboards.list_dashboards(
|
|
362
|
-
directory=directory if directory is not None else self.project.root,
|
|
363
|
-
recursive=recursive,
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
def get_dashboard(
|
|
367
|
-
self,
|
|
368
|
-
path: Path,
|
|
369
|
-
include_raw: bool = False,
|
|
370
|
-
) -> _dashboards.CompiledDashboard:
|
|
371
|
-
return _dashboards.get_dashboard(
|
|
372
|
-
path, include_raw=include_raw, project=self.project
|
|
373
|
-
)
|
|
374
|
-
|
|
375
334
|
def lookup_face_query_sql(
|
|
376
335
|
self,
|
|
377
336
|
name: str,
|
|
@@ -401,6 +360,7 @@ class ProjectSession:
|
|
|
401
360
|
variables: dict[str, Any] | None = None,
|
|
402
361
|
source: str | None = None,
|
|
403
362
|
limit: int = 50,
|
|
363
|
+
lenient_variables: bool = False,
|
|
404
364
|
) -> _query.ExecuteQueryResult:
|
|
405
365
|
return _query.execute_query(
|
|
406
366
|
sql,
|
|
@@ -408,6 +368,7 @@ class ProjectSession:
|
|
|
408
368
|
source=source,
|
|
409
369
|
limit=limit,
|
|
410
370
|
adapter_registry=self.adapter_registry,
|
|
371
|
+
lenient_variables=lenient_variables,
|
|
411
372
|
)
|
|
412
373
|
|
|
413
374
|
def describe_query(
|
dataface/agent_api/query.py
CHANGED
|
@@ -88,6 +88,10 @@ class ExecuteQueryArgs(BaseModel):
|
|
|
88
88
|
)
|
|
89
89
|
source: str | None = Field(None, description="Data source name to execute against")
|
|
90
90
|
limit: int | None = Field(None, description="Maximum rows to return (default 50)")
|
|
91
|
+
lenient_variables: bool = Field(
|
|
92
|
+
False,
|
|
93
|
+
description="When True, undefined Jinja variables degrade gracefully (useful for iterative chart editing where not all variables are set).",
|
|
94
|
+
)
|
|
91
95
|
|
|
92
96
|
|
|
93
97
|
class ExecuteQueryResult(BaseModel):
|
|
@@ -135,6 +139,7 @@ def execute_query(
|
|
|
135
139
|
limit: int = 50,
|
|
136
140
|
*,
|
|
137
141
|
adapter_registry: AdapterRegistry,
|
|
142
|
+
lenient_variables: bool = False,
|
|
138
143
|
) -> ExecuteQueryResult:
|
|
139
144
|
"""Execute a SQL query and return the results.
|
|
140
145
|
|
|
@@ -155,6 +160,7 @@ def execute_query(
|
|
|
155
160
|
sql=sql,
|
|
156
161
|
source=source,
|
|
157
162
|
limit=fetch_limit,
|
|
163
|
+
lenient_variables=lenient_variables,
|
|
158
164
|
)
|
|
159
165
|
|
|
160
166
|
result = adapter_registry.execute(query, variables=variables)
|
|
@@ -15,10 +15,6 @@ from dataface.core.compile.markdown import (
|
|
|
15
15
|
)
|
|
16
16
|
from dataface.core.execute.duckdb_cache import DuckDBCache
|
|
17
17
|
from dataface.core.project import Project
|
|
18
|
-
from dataface.core.project_roots import (
|
|
19
|
-
discover_render_context,
|
|
20
|
-
discovery_boundary_for_face,
|
|
21
|
-
)
|
|
22
18
|
from dataface.core.render.face_api import compile_and_render
|
|
23
19
|
|
|
24
20
|
|
|
@@ -72,16 +68,7 @@ def render_face(
|
|
|
72
68
|
if project is not None:
|
|
73
69
|
if warnings_ignore is None:
|
|
74
70
|
warnings_ignore = project.warnings_ignore
|
|
75
|
-
|
|
76
|
-
# build_adapter_registry's own fallback would only walk upward from
|
|
77
|
-
# project.root, missing nested dbt projects under it.
|
|
78
|
-
_, dbt_project_path = discover_render_context(
|
|
79
|
-
face_file.parent,
|
|
80
|
-
discovery_boundary_for_face(face_file.parent, project.root),
|
|
81
|
-
)
|
|
82
|
-
project_session = ProjectSession.open(
|
|
83
|
-
project.root, cache=cache, dbt_project_path=dbt_project_path
|
|
84
|
-
)
|
|
71
|
+
project_session = ProjectSession.open(project.root, cache=cache)
|
|
85
72
|
else:
|
|
86
73
|
if warnings_ignore is None:
|
|
87
74
|
warnings_ignore = frozenset()
|
dataface/ai/context.py
CHANGED
|
@@ -21,6 +21,7 @@ class DatafaceAIContext:
|
|
|
21
21
|
# `dft chat` after binding; consumed by `_view_url` to build clickable
|
|
22
22
|
# localhost URLs in `render_dashboard` responses (including as_link=True).
|
|
23
23
|
server_port: int | None = None
|
|
24
|
+
prompt_context: dict[str, str] | None = None
|
|
24
25
|
|
|
25
26
|
def resolve_dashboard_path(self, path: Path) -> Path:
|
|
26
27
|
"""Resolve a dashboard path inside the scoped dashboards directory.
|
dataface/ai/mcp/server.py
CHANGED
|
@@ -180,14 +180,14 @@ def create_server(context: DatafaceAIContext) -> Any:
|
|
|
180
180
|
return TypeAdapter(AnyUrl).validate_python(value)
|
|
181
181
|
|
|
182
182
|
@server.list_resources() # type: ignore[no-untyped-call]
|
|
183
|
-
async def handle_list_resources() -> list[Resource]:
|
|
183
|
+
async def handle_list_resources() -> list[Resource]: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
184
184
|
return [
|
|
185
185
|
Resource(uri=_uri(u), mimeType=m, name=n, description=d)
|
|
186
186
|
for u, m, n, d in (*_BASE_RESOURCES, *_docs_topic_resources())
|
|
187
187
|
]
|
|
188
188
|
|
|
189
189
|
@server.list_resource_templates() # type: ignore[no-untyped-call]
|
|
190
|
-
async def handle_list_resource_templates() -> list[ResourceTemplate]:
|
|
190
|
+
async def handle_list_resource_templates() -> list[ResourceTemplate]: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
191
191
|
return [
|
|
192
192
|
ResourceTemplate(
|
|
193
193
|
uriTemplate="dataface://dashboard/{path}",
|
|
@@ -204,11 +204,11 @@ def create_server(context: DatafaceAIContext) -> Any:
|
|
|
204
204
|
]
|
|
205
205
|
|
|
206
206
|
@server.read_resource() # type: ignore[no-untyped-call]
|
|
207
|
-
async def handle_read_resource(uri: AnyUrl) -> str:
|
|
207
|
+
async def handle_read_resource(uri: AnyUrl) -> str: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
208
208
|
return _read_resource_content(str(uri), context=context)
|
|
209
209
|
|
|
210
210
|
@server.list_tools() # type: ignore[no-untyped-call]
|
|
211
|
-
async def handle_list_tools() -> list[Tool]:
|
|
211
|
+
async def handle_list_tools() -> list[Tool]: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
212
212
|
return [
|
|
213
213
|
Tool(
|
|
214
214
|
name=cast(str, t["name"]),
|
|
@@ -234,7 +234,7 @@ def create_server(context: DatafaceAIContext) -> Any:
|
|
|
234
234
|
]
|
|
235
235
|
|
|
236
236
|
@server.call_tool()
|
|
237
|
-
async def handle_call_tool(
|
|
237
|
+
async def handle_call_tool( # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration
|
|
238
238
|
name: str, arguments: dict[str, Any] | None
|
|
239
239
|
) -> CallToolResult:
|
|
240
240
|
arguments = arguments or {}
|
|
@@ -19,6 +19,13 @@ metadata:
|
|
|
19
19
|
|
|
20
20
|
**`{{ s_render_dashboard }}` is how you deliver a dashboard** — call it and return its output. Do not skip it.
|
|
21
21
|
|
|
22
|
+
## Delivery discipline
|
|
23
|
+
|
|
24
|
+
- `{{ s_render_dashboard }}` delivers the dashboard — call it and let the result stand. Do not also paste the rendered output or the full YAML back as prose; that just duplicates the deliverable as an unreadable dump.
|
|
25
|
+
- Act, don't ask. Apply edits and render directly — do not ask for permission or offer optional follow-ups ("if you want, I can save this", "would you like me to…").
|
|
26
|
+
- Fail loud. If a tool returns an error, report it and either fix the input and call the tool again, or stop and say what's blocking. Never route around a failed tool by fabricating output, dumping query rows as a text table in place of a chart, or improvising a different format. A failed render is a failure to report, not to paper over.
|
|
27
|
+
- Never claim a render, query, or save succeeded unless the tool returned success. Never invent columns, tables, or data — if a schema or query call fails, fix it against the real schema.
|
|
28
|
+
|
|
22
29
|
Build dashboards and reports **incrementally** — one chart at a time, validating at every step. Never one-shot an entire dashboard.
|
|
23
30
|
|
|
24
31
|
## Companion Skills
|
|
@@ -140,11 +140,11 @@ class PromptToolkitInput:
|
|
|
140
140
|
kb = KeyBindings()
|
|
141
141
|
|
|
142
142
|
@kb.add("enter")
|
|
143
|
-
def _submit(event: Any) -> None:
|
|
143
|
+
def _submit(event: Any) -> None: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
144
144
|
event.current_buffer.validate_and_handle()
|
|
145
145
|
|
|
146
146
|
@kb.add("escape", "enter")
|
|
147
|
-
def _newline_meta(event: Any) -> None:
|
|
147
|
+
def _newline_meta(event: Any) -> None: # pyright: ignore[reportUnusedFunction] # decorator-registered — pyright cannot model runtime registration # fmt: skip
|
|
148
148
|
event.current_buffer.insert_text("\n")
|
|
149
149
|
|
|
150
150
|
self._session: PromptSession = PromptSession(
|
|
@@ -205,7 +205,7 @@ def select_input_layer(
|
|
|
205
205
|
return StdlibInput(hist_path)
|
|
206
206
|
|
|
207
207
|
try:
|
|
208
|
-
import prompt_toolkit # noqa: PLC0415, F401
|
|
208
|
+
import prompt_toolkit # noqa: PLC0415, F401 # pyright: ignore[reportUnusedImport] — availability probe; ImportError selects StdlibInput
|
|
209
209
|
except ImportError:
|
|
210
210
|
return StdlibInput(hist_path)
|
|
211
211
|
|
dataface/cli/commands/render.py
CHANGED
|
@@ -129,11 +129,7 @@ def render_command(
|
|
|
129
129
|
|
|
130
130
|
with (
|
|
131
131
|
cache_from_env() as cache,
|
|
132
|
-
ProjectSession.open(
|
|
133
|
-
ctx.project_root,
|
|
134
|
-
cache=cache,
|
|
135
|
-
dbt_project_path=ctx.dbt_project_path,
|
|
136
|
-
) as project_session,
|
|
132
|
+
ProjectSession.open(ctx.project_root, cache=cache) as project_session,
|
|
137
133
|
):
|
|
138
134
|
result = project_session.render_dashboard(
|
|
139
135
|
path=ctx.scoped_path,
|
|
@@ -219,11 +215,7 @@ def render_command_from_yaml(
|
|
|
219
215
|
|
|
220
216
|
with (
|
|
221
217
|
cache_from_env() as cache,
|
|
222
|
-
ProjectSession.open(
|
|
223
|
-
ctx.project_root,
|
|
224
|
-
cache=cache,
|
|
225
|
-
dbt_project_path=ctx.dbt_project_path,
|
|
226
|
-
) as project_session,
|
|
218
|
+
ProjectSession.open(ctx.project_root, cache=cache) as project_session,
|
|
227
219
|
):
|
|
228
220
|
result = project_session.render_dashboard(
|
|
229
221
|
yaml_content=yaml_content,
|
|
@@ -120,7 +120,6 @@ from dataface.core.compile.models.source import (
|
|
|
120
120
|
is_database_source,
|
|
121
121
|
is_file_source,
|
|
122
122
|
parse_source_config,
|
|
123
|
-
resolve_env_var,
|
|
124
123
|
)
|
|
125
124
|
from dataface.core.compile.models.variable.authored import (
|
|
126
125
|
Variable,
|
|
@@ -201,7 +200,6 @@ __all__ = [
|
|
|
201
200
|
"is_database_source",
|
|
202
201
|
"is_file_source",
|
|
203
202
|
"is_api_source",
|
|
204
|
-
"resolve_env_var",
|
|
205
203
|
# Config
|
|
206
204
|
"ProjectSourcesConfig",
|
|
207
205
|
"get_config",
|
dataface/core/compile/channel.py
CHANGED
|
@@ -52,11 +52,6 @@ def parse_style_channel(
|
|
|
52
52
|
if isinstance(raw, str):
|
|
53
53
|
return ResolvedStyleChannel(channel=channel_name, mode="series", data_field=raw)
|
|
54
54
|
|
|
55
|
-
if not isinstance(raw, dict):
|
|
56
|
-
raise ValueError(
|
|
57
|
-
f"Channel '{channel_name}' must be a string or dict, got {type(raw).__name__}"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
55
|
has_column = "column" in raw
|
|
61
56
|
has_value = "value" in raw
|
|
62
57
|
has_scale = "scale" in raw
|
|
@@ -188,7 +183,7 @@ def normalize_chart_channels(
|
|
|
188
183
|
channels["color"] = parse_style_channel(color_raw, "color")
|
|
189
184
|
|
|
190
185
|
# Upgrade series channel to gradient when style.color carries a scale.
|
|
191
|
-
if isinstance(style_color, StyleColorConfig)
|
|
186
|
+
if isinstance(style_color, StyleColorConfig):
|
|
192
187
|
if "color" not in channels:
|
|
193
188
|
raise ValueError(
|
|
194
189
|
"style.color: {scale: ...} requires chart.color to name a data field.\n"
|
dataface/core/compile/colors.py
CHANGED
|
@@ -43,7 +43,7 @@ def sanitize_color(color: str | None, fallback: str | None = None) -> str | None
|
|
|
43
43
|
immediately — authored colors that are not valid are a configuration error,
|
|
44
44
|
not a case for silent defaulting.
|
|
45
45
|
"""
|
|
46
|
-
if not color
|
|
46
|
+
if not color:
|
|
47
47
|
return fallback
|
|
48
48
|
if _CSS_HEX_COLOR_PATTERN.match(color):
|
|
49
49
|
return color
|
|
@@ -271,9 +271,7 @@ def compile(
|
|
|
271
271
|
yaml_content: YAML string to compile
|
|
272
272
|
options: Optional compilation options
|
|
273
273
|
base_dir: Base directory for resolving file references
|
|
274
|
-
project_sources: Project-level sources
|
|
275
|
-
hold a ``ProjectSession`` thread ``project_session.sources``;
|
|
276
|
-
one-shot callers pre-load via ``load_project_sources(Project(project_dir))``.
|
|
274
|
+
project_sources: Project-level sources.
|
|
277
275
|
|
|
278
276
|
Returns:
|
|
279
277
|
CompileResult with compiled face or errors
|
|
@@ -440,6 +438,7 @@ def compile_file(
|
|
|
440
438
|
apply_meta: bool = True,
|
|
441
439
|
*,
|
|
442
440
|
project: Project,
|
|
441
|
+
markdown_metadata_table: bool = False,
|
|
443
442
|
) -> CompileResult:
|
|
444
443
|
"""Compile a YAML file to a Face.
|
|
445
444
|
|
|
@@ -451,6 +450,8 @@ def compile_file(
|
|
|
451
450
|
options: Optional compilation options
|
|
452
451
|
apply_meta: If True, resolve and apply meta.yaml chain (default: True)
|
|
453
452
|
project: Project for meta resolution and source loading
|
|
453
|
+
markdown_metadata_table: When True and file_path is a .md, prepend
|
|
454
|
+
non-face frontmatter keys as a metadata table before the body.
|
|
454
455
|
|
|
455
456
|
Returns:
|
|
456
457
|
CompileResult with compiled face or errors
|
|
@@ -471,13 +472,14 @@ def compile_file(
|
|
|
471
472
|
)
|
|
472
473
|
|
|
473
474
|
# Markdown report files: translate to YAML before compiling
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
475
|
+
from dataface.core.compile.markdown import (
|
|
476
|
+
MARKDOWN_NOT_FACE_MESSAGE,
|
|
477
|
+
MARKDOWN_SUFFIXES,
|
|
478
|
+
is_markdown_face,
|
|
479
|
+
markdown_to_yaml,
|
|
480
|
+
)
|
|
480
481
|
|
|
482
|
+
if file_path.suffix.lower() in MARKDOWN_SUFFIXES:
|
|
481
483
|
if not is_markdown_face(file_path):
|
|
482
484
|
return CompileResult(
|
|
483
485
|
errors=[CompilationError(MARKDOWN_NOT_FACE_MESSAGE).to_structured()]
|
|
@@ -485,7 +487,9 @@ def compile_file(
|
|
|
485
487
|
|
|
486
488
|
try:
|
|
487
489
|
raw_text = file_path.read_text(encoding="utf-8")
|
|
488
|
-
yaml_content = markdown_to_yaml(
|
|
490
|
+
yaml_content = markdown_to_yaml(
|
|
491
|
+
raw_text, metadata_table=markdown_metadata_table
|
|
492
|
+
)
|
|
489
493
|
except (OSError, ValueError) as e:
|
|
490
494
|
return CompileResult(
|
|
491
495
|
errors=[CompilationError(f"Markdown parse error: {e}").to_structured()]
|
|
@@ -820,10 +824,8 @@ def load_from_reference(
|
|
|
820
824
|
section_name = "variables"
|
|
821
825
|
elif isinstance(reference, QueryRef):
|
|
822
826
|
section_name = "queries"
|
|
823
|
-
elif isinstance(reference, ChartRef):
|
|
824
|
-
section_name = "charts"
|
|
825
827
|
else:
|
|
826
|
-
|
|
828
|
+
section_name = "charts"
|
|
827
829
|
|
|
828
830
|
# Grammar already validated by the typed model; split is deterministic.
|
|
829
831
|
file_path_str, item_name = reference.ref.rsplit(f".{section_name}.", 1)
|