dataface 0.1.6.dev76__py3-none-any.whl → 0.1.6.dev129__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/data_paths.py +6 -12
- dataface/agent_api/docs/yaml-reference.md +13 -12
- dataface/agent_api/files.py +17 -13
- dataface/agent_api/pack.py +2 -2
- dataface/agent_api/project_session.py +7 -44
- dataface/agent_api/render_face.py +1 -14
- dataface/agent_api/validate.py +1 -1
- dataface/ai/context.py +2 -1
- dataface/ai/tools/__init__.py +5 -5
- dataface/cli/commands/render.py +2 -10
- dataface/cli/commands/schema.py +23 -24
- dataface/core/compile/compiler.py +14 -10
- dataface/core/compile/config.py +10 -5
- dataface/core/compile/detect.py +32 -6
- dataface/core/compile/markdown.py +109 -18
- dataface/core/compile/models/chart/authored.py +25 -31
- dataface/core/compile/models/config.py +4 -0
- dataface/core/compile/models/style/theme.py +4 -3
- dataface/core/compile/sizing.py +41 -12
- dataface/core/compile/typography.py +23 -29
- dataface/core/compile/yaml_error_formatter.py +102 -33
- dataface/core/defaults/default_config.yml +1 -0
- dataface/core/defaults/themes/_base.yaml +6 -6
- dataface/core/defaults/themes/cream.yaml +9 -0
- dataface/core/defaults/themes/stark.yaml +11 -6
- dataface/core/execute/adapters/adapter_registry.py +3 -8
- dataface/core/execute/duckdb_cache.py +4 -3
- dataface/core/fonts.py +13 -0
- dataface/core/project.py +4 -0
- dataface/core/project_roots.py +15 -35
- dataface/core/render/board_links.py +71 -18
- dataface/core/render/chart/geo.py +74 -11
- dataface/core/render/chart/profile.py +65 -17
- dataface/core/render/chart/table.py +4 -32
- dataface/core/render/chart/time_unit_detect.py +28 -19
- dataface/core/render/dir_context.py +5 -3
- dataface/core/render/faces.py +25 -37
- dataface/core/render/font_measurement.py +2 -15
- dataface/core/render/layout_sizing.py +3 -0
- dataface/core/render/svg_utils.py +16 -7
- dataface/core/render/templates/nav/nav-fragment.html +1 -1
- dataface/core/render/templates/nav/nav.js +3 -3
- dataface/core/scoped_paths.py +30 -9
- dataface/core/serve/alias_index.py +24 -24
- dataface/core/serve/server.py +91 -61
- {dataface-0.1.6.dev76.dist-info → dataface-0.1.6.dev129.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev76.dist-info → dataface-0.1.6.dev129.dist-info}/RECORD +53 -53
- mdsvg/renderer.py +68 -25
- mdsvg/style.py +2 -2
- {dataface-0.1.6.dev76.dist-info → dataface-0.1.6.dev129.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev76.dist-info → dataface-0.1.6.dev129.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev76.dist-info → dataface-0.1.6.dev129.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
|
)
|
dataface/agent_api/data_paths.py
CHANGED
|
@@ -25,6 +25,7 @@ from dataface.core.registered_views.data_urls import (
|
|
|
25
25
|
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from dataface.agent_api.schema import SchemaResponse
|
|
28
|
+
from dataface.core.project import Project
|
|
28
29
|
from dataface.core.serve.alias_index import AliasIndex
|
|
29
30
|
|
|
30
31
|
|
|
@@ -186,22 +187,15 @@ def data_paths_list(
|
|
|
186
187
|
return result
|
|
187
188
|
|
|
188
189
|
|
|
189
|
-
def build_alias_index_for_project(
|
|
190
|
-
"""Build an AliasIndex from a project
|
|
190
|
+
def build_alias_index_for_project(project: Project) -> AliasIndex:
|
|
191
|
+
"""Build an AliasIndex from a project.
|
|
191
192
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
starting a server.
|
|
193
|
+
Intended for CLI commands (dft schema --data-paths) that need alias lookup
|
|
194
|
+
without starting a server.
|
|
195
195
|
"""
|
|
196
196
|
from dataface.core.serve.alias_index import AliasIndex
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
faces_at_root = faces_dir.is_dir()
|
|
200
|
-
return AliasIndex.build(
|
|
201
|
-
project_dir=project_dir,
|
|
202
|
-
faces_dir=faces_dir,
|
|
203
|
-
faces_at_root=faces_at_root,
|
|
204
|
-
)
|
|
198
|
+
return AliasIndex.build(project, faces_at_root=project.faces_dir.is_dir())
|
|
205
199
|
|
|
206
200
|
|
|
207
201
|
def data_alias_errors_for_file(
|
|
@@ -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>
|
|
@@ -1081,7 +1081,7 @@ Authored overlay for TitleStyle. Board and face titles.
|
|
|
1081
1081
|
| Field | Type | Optional | Description |
|
|
1082
1082
|
|-------|------|:--------:|-------------|
|
|
1083
1083
|
| `font` | [FontStyle](#fontstyle) | ✓ | Title font style overrides. |
|
|
1084
|
-
| `line_height` | float | ✓ | Line height multiplier for titles and markdown headings. Headings typically want a tighter multiplier than body prose
|
|
1084
|
+
| `line_height` | float | ✓ | Line height multiplier for titles and markdown headings. Headings typically want a tighter multiplier than body prose. |
|
|
1085
1085
|
| `sizes` | list[float] | ✓ | Font sizes for the H1–H6 heading ramp, indexed by ``face.level - 1``. Combined with ``width_offsets`` at render time to size titles responsively by card width. |
|
|
1086
1086
|
| `width_offsets` | [TitleWidthOffsetsStyle](#titlewidthoffsetsstyle) | ✓ | Additive level offsets by card width (tiny/narrow/medium/wide). Added to the title's base level before indexing ``sizes``. Consumed by chart_title_spec / face_title_spec. |
|
|
1087
1087
|
| `min_height` | float | ✓ | Minimum title row height in pixels. |
|
|
@@ -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>
|
dataface/agent_api/files.py
CHANGED
|
@@ -16,6 +16,8 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
from pydantic import BaseModel, Field
|
|
18
18
|
|
|
19
|
+
from dataface.core.project import Project
|
|
20
|
+
|
|
19
21
|
# Cap grep/glob payloads — read_file returns full content (model must handle size).
|
|
20
22
|
_MAX_GREP_MATCHES = 200
|
|
21
23
|
_MAX_GLOB_MATCHES = 500
|
|
@@ -146,9 +148,9 @@ def _walk_project(root: Path) -> list[Path]:
|
|
|
146
148
|
return result
|
|
147
149
|
|
|
148
150
|
|
|
149
|
-
def read_file(path: str,
|
|
151
|
+
def read_file(path: str, project: Project) -> ReadFileResult:
|
|
150
152
|
try:
|
|
151
|
-
target = _resolve_within(
|
|
153
|
+
target = _resolve_within(project.root, path)
|
|
152
154
|
except ValueError as exc:
|
|
153
155
|
return ReadFileResult(success=False, path=path, error=str(exc))
|
|
154
156
|
if not target.is_file():
|
|
@@ -157,12 +159,14 @@ def read_file(path: str, project_dir: Path) -> ReadFileResult:
|
|
|
157
159
|
content = target.read_text(encoding="utf-8")
|
|
158
160
|
except (OSError, UnicodeDecodeError) as exc:
|
|
159
161
|
return ReadFileResult(success=False, path=path, error=str(exc))
|
|
160
|
-
return ReadFileResult(
|
|
162
|
+
return ReadFileResult(
|
|
163
|
+
success=True, path=_rel(project.root, target), content=content
|
|
164
|
+
)
|
|
161
165
|
|
|
162
166
|
|
|
163
|
-
def write_file(path: str, content: str,
|
|
167
|
+
def write_file(path: str, content: str, project: Project) -> WriteFileResult:
|
|
164
168
|
try:
|
|
165
|
-
target = _resolve_within(
|
|
169
|
+
target = _resolve_within(project.root, path)
|
|
166
170
|
except ValueError as exc:
|
|
167
171
|
return WriteFileResult(success=False, path=path, error=str(exc))
|
|
168
172
|
try:
|
|
@@ -172,16 +176,16 @@ def write_file(path: str, content: str, project_dir: Path) -> WriteFileResult:
|
|
|
172
176
|
return WriteFileResult(success=False, path=path, error=str(exc))
|
|
173
177
|
return WriteFileResult(
|
|
174
178
|
success=True,
|
|
175
|
-
path=_rel(
|
|
179
|
+
path=_rel(project.root, target),
|
|
176
180
|
bytes_written=len(content.encode("utf-8")),
|
|
177
181
|
)
|
|
178
182
|
|
|
179
183
|
|
|
180
184
|
def edit_file(
|
|
181
|
-
path: str, old_string: str, new_string: str,
|
|
185
|
+
path: str, old_string: str, new_string: str, project: Project
|
|
182
186
|
) -> EditFileResult:
|
|
183
187
|
try:
|
|
184
|
-
target = _resolve_within(
|
|
188
|
+
target = _resolve_within(project.root, path)
|
|
185
189
|
except ValueError as exc:
|
|
186
190
|
return EditFileResult(success=False, path=path, error=str(exc))
|
|
187
191
|
if not target.is_file():
|
|
@@ -205,11 +209,11 @@ def edit_file(
|
|
|
205
209
|
target.write_text(original.replace(old_string, new_string), encoding="utf-8")
|
|
206
210
|
except OSError as exc:
|
|
207
211
|
return EditFileResult(success=False, path=path, error=str(exc))
|
|
208
|
-
return EditFileResult(success=True, path=_rel(
|
|
212
|
+
return EditFileResult(success=True, path=_rel(project.root, target), replacements=1)
|
|
209
213
|
|
|
210
214
|
|
|
211
|
-
def glob_files(pattern: str,
|
|
212
|
-
root =
|
|
215
|
+
def glob_files(pattern: str, project: Project) -> GlobResult:
|
|
216
|
+
root = project.root.resolve()
|
|
213
217
|
matches: list[str] = []
|
|
214
218
|
try:
|
|
215
219
|
candidates = root.glob(pattern)
|
|
@@ -228,8 +232,8 @@ def glob_files(pattern: str, project_dir: Path) -> GlobResult:
|
|
|
228
232
|
return GlobResult(success=True, matches=matches)
|
|
229
233
|
|
|
230
234
|
|
|
231
|
-
def grep_files(pattern: str,
|
|
232
|
-
root =
|
|
235
|
+
def grep_files(pattern: str, project: Project, glob: str | None = None) -> GrepResult:
|
|
236
|
+
root = project.root.resolve()
|
|
233
237
|
matches: list[GrepMatch] = []
|
|
234
238
|
try:
|
|
235
239
|
candidates: list[Path] = (
|
dataface/agent_api/pack.py
CHANGED
|
@@ -446,8 +446,8 @@ def apply_proposal(
|
|
|
446
446
|
)
|
|
447
447
|
|
|
448
448
|
# Ensure base dirs exist
|
|
449
|
-
|
|
450
|
-
(project.
|
|
449
|
+
project.faces_dir.mkdir(exist_ok=True)
|
|
450
|
+
(project.faces_dir / "partials").mkdir(exist_ok=True)
|
|
451
451
|
|
|
452
452
|
# Write partials — skipped by validate_paths (_*.yml convention)
|
|
453
453
|
for partial_filename, partial_path, rel in partial_targets:
|
|
@@ -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,
|
|
@@ -253,6 +240,10 @@ class ProjectSession:
|
|
|
253
240
|
def warnings_ignore(self) -> frozenset[str]:
|
|
254
241
|
return self.project.warnings_ignore
|
|
255
242
|
|
|
243
|
+
@property
|
|
244
|
+
def faces_dir(self) -> Path:
|
|
245
|
+
return self.project.faces_dir
|
|
246
|
+
|
|
256
247
|
@cached_property
|
|
257
248
|
def _relationship_context(self) -> RelationshipContext | None:
|
|
258
249
|
"""Load relationship context from the super-schema cache (lazy, per-instance).
|
|
@@ -275,11 +266,6 @@ class ProjectSession:
|
|
|
275
266
|
|
|
276
267
|
# ── Verb forwarders ──────────────────────────────────────────────────────
|
|
277
268
|
|
|
278
|
-
def validate(self, face_path: Path) -> ValidateResult:
|
|
279
|
-
result = _validate.validate(face_path, project=self.project)
|
|
280
|
-
annotated = _validate.annotate_with_data_lint([result], project_session=self)
|
|
281
|
-
return annotated[0]
|
|
282
|
-
|
|
283
269
|
def validate_paths(self, paths: list[Path] | None) -> list[ValidateResult]:
|
|
284
270
|
results = _validate.validate_paths(paths, project=self.project)
|
|
285
271
|
return _validate.annotate_with_data_lint(results, project_session=self)
|
|
@@ -342,32 +328,9 @@ class ProjectSession:
|
|
|
342
328
|
relationship_context=self._relationship_context,
|
|
343
329
|
)
|
|
344
330
|
|
|
345
|
-
def describe_face(self, path: Path) -> _describe.DescribeFaceResult:
|
|
346
|
-
return _describe.describe_face(path, project=self.project)
|
|
347
|
-
|
|
348
331
|
def describe_paths(self, paths: list[Path]) -> list[_describe.DescribeFaceResult]:
|
|
349
332
|
return _describe.describe_paths(paths, project=self.project)
|
|
350
333
|
|
|
351
|
-
def list_dashboards(
|
|
352
|
-
self,
|
|
353
|
-
directory: Path | None = None,
|
|
354
|
-
recursive: bool = True,
|
|
355
|
-
) -> _dashboards.ListDashboardsResult:
|
|
356
|
-
"""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."""
|
|
357
|
-
return _dashboards.list_dashboards(
|
|
358
|
-
directory=directory if directory is not None else self.project.root,
|
|
359
|
-
recursive=recursive,
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
def get_dashboard(
|
|
363
|
-
self,
|
|
364
|
-
path: Path,
|
|
365
|
-
include_raw: bool = False,
|
|
366
|
-
) -> _dashboards.CompiledDashboard:
|
|
367
|
-
return _dashboards.get_dashboard(
|
|
368
|
-
path, include_raw=include_raw, project=self.project
|
|
369
|
-
)
|
|
370
|
-
|
|
371
334
|
def lookup_face_query_sql(
|
|
372
335
|
self,
|
|
373
336
|
name: str,
|
|
@@ -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/agent_api/validate.py
CHANGED
|
@@ -90,7 +90,7 @@ def _validate_one_path(
|
|
|
90
90
|
# detectors. Keep this lazy so `dft --help` doesn't pay that startup cost.
|
|
91
91
|
from dataface.core.inspect.manifest_utils import INSPECT_TEMPLATE_MANIFEST
|
|
92
92
|
|
|
93
|
-
raw_path = path if path is not None else project.
|
|
93
|
+
raw_path = path if path is not None else project.faces_dir
|
|
94
94
|
|
|
95
95
|
try:
|
|
96
96
|
resolved = resolve_scoped_path(raw_path, project.root)
|
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.
|
|
@@ -31,7 +32,7 @@ class DatafaceAIContext:
|
|
|
31
32
|
if path.is_absolute():
|
|
32
33
|
raise ValueError(f"Dashboard path must be relative: {path}")
|
|
33
34
|
if self.dashboards_directory is None:
|
|
34
|
-
base_dir =
|
|
35
|
+
base_dir = self.project_session.faces_dir.resolve()
|
|
35
36
|
else:
|
|
36
37
|
base_dir = self.dashboards_directory.resolve()
|
|
37
38
|
resolved = (base_dir / path).resolve()
|
dataface/ai/tools/__init__.py
CHANGED
|
@@ -293,14 +293,14 @@ def _handle_get_warning_code(
|
|
|
293
293
|
def _handle_read_file(args: dict[str, Any], ctx: DatafaceAIContext) -> dict[str, Any]:
|
|
294
294
|
parsed = _files.ReadFileArgs.model_validate(args)
|
|
295
295
|
return _files.read_file(
|
|
296
|
-
parsed.path,
|
|
296
|
+
parsed.path, project=Project(_render_project_dir(args, ctx))
|
|
297
297
|
).model_dump(mode="json", exclude_none=True)
|
|
298
298
|
|
|
299
299
|
|
|
300
300
|
def _handle_write_file(args: dict[str, Any], ctx: DatafaceAIContext) -> dict[str, Any]:
|
|
301
301
|
parsed = _files.WriteFileArgs.model_validate(args)
|
|
302
302
|
return _files.write_file(
|
|
303
|
-
parsed.path, parsed.content,
|
|
303
|
+
parsed.path, parsed.content, project=Project(_render_project_dir(args, ctx))
|
|
304
304
|
).model_dump(mode="json", exclude_none=True)
|
|
305
305
|
|
|
306
306
|
|
|
@@ -310,14 +310,14 @@ def _handle_edit_file(args: dict[str, Any], ctx: DatafaceAIContext) -> dict[str,
|
|
|
310
310
|
parsed.path,
|
|
311
311
|
parsed.old_string,
|
|
312
312
|
parsed.new_string,
|
|
313
|
-
|
|
313
|
+
project=Project(_render_project_dir(args, ctx)),
|
|
314
314
|
).model_dump(mode="json", exclude_none=True)
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def _handle_glob_files(args: dict[str, Any], ctx: DatafaceAIContext) -> dict[str, Any]:
|
|
318
318
|
parsed = _files.GlobFilesArgs.model_validate(args)
|
|
319
319
|
return _files.glob_files(
|
|
320
|
-
parsed.pattern,
|
|
320
|
+
parsed.pattern, project=Project(_render_project_dir(args, ctx))
|
|
321
321
|
).model_dump(mode="json", exclude_none=True)
|
|
322
322
|
|
|
323
323
|
|
|
@@ -325,7 +325,7 @@ def _handle_grep_files(args: dict[str, Any], ctx: DatafaceAIContext) -> dict[str
|
|
|
325
325
|
parsed = _files.GrepFilesArgs.model_validate(args)
|
|
326
326
|
return _files.grep_files(
|
|
327
327
|
parsed.pattern,
|
|
328
|
-
|
|
328
|
+
project=Project(_render_project_dir(args, ctx)),
|
|
329
329
|
glob=parsed.glob,
|
|
330
330
|
).model_dump(mode="json", exclude_none=True)
|
|
331
331
|
|
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,
|
dataface/cli/commands/schema.py
CHANGED
|
@@ -193,32 +193,31 @@ def schema_command(
|
|
|
193
193
|
lineage_depth=lineage_depth,
|
|
194
194
|
surface="cli",
|
|
195
195
|
)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
196
|
+
if data_paths:
|
|
197
|
+
if not drill_result.success:
|
|
198
|
+
print_structured_errors(
|
|
199
|
+
drill_result.structured_errors
|
|
200
|
+
or [
|
|
201
|
+
StructuredError(
|
|
202
|
+
code=DF_UNKNOWN_INTERNAL.code,
|
|
203
|
+
domain=DF_UNKNOWN_INTERNAL.domain,
|
|
204
|
+
doc_url=DF_UNKNOWN_INTERNAL.doc_url,
|
|
205
|
+
docs_topic=DF_UNKNOWN_INTERNAL.docs_topic,
|
|
206
|
+
message="; ".join(drill_result.errors),
|
|
207
|
+
)
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
raise typer.Exit(1)
|
|
211
|
+
from dataface.agent_api.data_paths import (
|
|
212
|
+
build_alias_index_for_project,
|
|
213
|
+
data_paths_list,
|
|
210
214
|
)
|
|
211
|
-
raise typer.Exit(1)
|
|
212
|
-
from dataface.agent_api.data_paths import (
|
|
213
|
-
build_alias_index_for_project,
|
|
214
|
-
data_paths_list,
|
|
215
|
-
)
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
216
|
+
alias_index = build_alias_index_for_project(project_session.project)
|
|
217
|
+
ep_list = data_paths_list(drill_result, alias_index=alias_index)
|
|
218
|
+
wire = [p.to_dict() for p in ep_list]
|
|
219
|
+
typer.echo(json.dumps(wire, indent=2))
|
|
220
|
+
return
|
|
222
221
|
|
|
223
222
|
if json_output:
|
|
224
223
|
typer.echo(
|