dataface 0.1.6.dev82__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/docs/yaml-reference.md +12 -11
- dataface/agent_api/project_session.py +3 -44
- dataface/agent_api/render_face.py +1 -14
- dataface/ai/context.py +1 -0
- dataface/cli/commands/render.py +2 -10
- 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 +3 -1
- dataface/core/compile/yaml_error_formatter.py +102 -33
- dataface/core/defaults/default_config.yml +1 -0
- dataface/core/defaults/themes/stark.yaml +4 -0
- dataface/core/execute/adapters/adapter_registry.py +3 -8
- dataface/core/execute/duckdb_cache.py +4 -3
- 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 +1 -2
- 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 +9 -17
- dataface/core/serve/server.py +82 -37
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev129.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev129.dist-info}/RECORD +36 -36
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev129.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev82.dist-info → dataface-0.1.6.dev129.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev82.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
|
)
|
|
@@ -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>
|
|
@@ -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,
|
|
@@ -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/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,
|
|
@@ -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()]
|
dataface/core/compile/config.py
CHANGED
|
@@ -200,6 +200,11 @@ def get_project_server_config(project: Project) -> ServerConfig:
|
|
|
200
200
|
return ServerConfig.model_validate(_deep_merge(base, server_section))
|
|
201
201
|
|
|
202
202
|
|
|
203
|
+
def get_project_markdown_metadata_table(project: Project) -> bool:
|
|
204
|
+
"""Return whether the project has the markdown_metadata_table flag enabled."""
|
|
205
|
+
return get_project_server_config(project).markdown_metadata_table
|
|
206
|
+
|
|
207
|
+
|
|
203
208
|
def load_config(path: Path) -> Config:
|
|
204
209
|
"""Load configuration from a custom YAML file.
|
|
205
210
|
|
|
@@ -610,15 +615,15 @@ def load_project_sources(project: Project) -> ProjectSourcesConfig:
|
|
|
610
615
|
config.default = yml_default
|
|
611
616
|
|
|
612
617
|
# Absolutize paths now that all sources are merged
|
|
613
|
-
config.sources = _absolutize_source_paths(config.sources, project
|
|
618
|
+
config.sources = _absolutize_source_paths(config.sources, project)
|
|
614
619
|
return config
|
|
615
620
|
|
|
616
621
|
|
|
617
622
|
def _absolutize_source_paths(
|
|
618
623
|
sources: dict[str, dict[str, Any]],
|
|
619
|
-
|
|
624
|
+
project: Project,
|
|
620
625
|
) -> dict[str, dict[str, Any]]:
|
|
621
|
-
"""Absolutize relative file/database paths against ``
|
|
626
|
+
"""Absolutize relative file/database paths against ``project.root``."""
|
|
622
627
|
resolved: dict[str, dict[str, Any]] = {}
|
|
623
628
|
for name, config in sources.items():
|
|
624
629
|
cfg = dict(config)
|
|
@@ -630,11 +635,11 @@ def _absolutize_source_paths(
|
|
|
630
635
|
and path_value != ":memory:"
|
|
631
636
|
and not Path(path_value).is_absolute()
|
|
632
637
|
):
|
|
633
|
-
cfg["path"] = str((
|
|
638
|
+
cfg["path"] = str((project.root / path_value).resolve())
|
|
634
639
|
elif source_type in {"csv", "parquet", "json"}:
|
|
635
640
|
file_value = cfg.get("file")
|
|
636
641
|
if isinstance(file_value, str) and not Path(file_value).is_absolute():
|
|
637
|
-
cfg["file"] = str((
|
|
642
|
+
cfg["file"] = str((project.root / file_value).resolve())
|
|
638
643
|
resolved[name] = cfg
|
|
639
644
|
return resolved
|
|
640
645
|
|
dataface/core/compile/detect.py
CHANGED
|
@@ -16,9 +16,21 @@ Detection methods (in order of precedence):
|
|
|
16
16
|
4. Markdown reports: .md files with Dataface YAML frontmatter
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
import os
|
|
19
20
|
import re
|
|
20
21
|
from pathlib import Path
|
|
21
22
|
|
|
23
|
+
_SKIP_SCAN_DIRS = {
|
|
24
|
+
".git",
|
|
25
|
+
".venv",
|
|
26
|
+
"__pycache__",
|
|
27
|
+
".mypy_cache",
|
|
28
|
+
".pytest_cache",
|
|
29
|
+
"build",
|
|
30
|
+
"dist",
|
|
31
|
+
"node_modules",
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
|
|
23
35
|
def is_dataface_file(path: Path | str) -> bool:
|
|
24
36
|
"""
|
|
@@ -33,9 +45,9 @@ def is_dataface_file(path: Path | str) -> bool:
|
|
|
33
45
|
path = Path(path)
|
|
34
46
|
|
|
35
47
|
# Markdown report files
|
|
36
|
-
|
|
37
|
-
from dataface.core.compile.markdown import is_markdown_face
|
|
48
|
+
from dataface.core.compile.markdown import MARKDOWN_SUFFIXES, is_markdown_face
|
|
38
49
|
|
|
50
|
+
if path.suffix.lower() in MARKDOWN_SUFFIXES:
|
|
39
51
|
return is_markdown_face(path)
|
|
40
52
|
|
|
41
53
|
# Must be a YAML file
|
|
@@ -103,8 +115,22 @@ def find_dataface_files(directory: Path | str, recursive: bool = True) -> list[P
|
|
|
103
115
|
List of paths to Dataface files
|
|
104
116
|
"""
|
|
105
117
|
directory = Path(directory)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
face_suffixes = (".yml", ".yaml", ".md", ".markdown")
|
|
119
|
+
|
|
120
|
+
if recursive:
|
|
121
|
+
candidates: list[Path] = []
|
|
122
|
+
for root, dirs, files in os.walk(directory, topdown=True):
|
|
123
|
+
dirs[:] = [d for d in dirs if d not in _SKIP_SCAN_DIRS]
|
|
124
|
+
root_path = Path(root)
|
|
125
|
+
candidates.extend(
|
|
126
|
+
root_path / name for name in files if name.endswith(face_suffixes)
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
candidates = [
|
|
130
|
+
p
|
|
131
|
+
for p in directory.iterdir()
|
|
132
|
+
if p.is_file() and p.name.endswith(face_suffixes)
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
candidates = sorted(candidates)
|
|
110
136
|
return [p for p in candidates if is_dataface_file(p)]
|