dataface 0.1.6.dev62__py3-none-any.whl → 0.1.6.dev82__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 +19 -25
- dataface/agent_api/data_paths.py +6 -12
- dataface/agent_api/docs/yaml-reference.md +1 -1
- dataface/agent_api/files.py +17 -13
- dataface/agent_api/pack.py +2 -2
- dataface/agent_api/project_session.py +4 -0
- dataface/agent_api/validate.py +1 -1
- dataface/ai/context.py +1 -1
- dataface/ai/llm.py +36 -5
- dataface/ai/tools/__init__.py +5 -5
- dataface/cli/commands/render.py +8 -2
- dataface/cli/commands/schema.py +23 -24
- dataface/core/compile/models/style/theme.py +1 -2
- dataface/core/compile/sizing.py +41 -12
- dataface/core/compile/typography.py +23 -29
- dataface/core/defaults/themes/_base.yaml +6 -6
- dataface/core/defaults/themes/cream.yaml +9 -0
- dataface/core/defaults/themes/stark.yaml +7 -6
- dataface/core/fonts.py +13 -0
- dataface/core/project.py +4 -0
- dataface/core/render/faces.py +24 -35
- 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/serve/alias_index.py +15 -7
- dataface/core/serve/server.py +9 -24
- {dataface-0.1.6.dev62.dist-info → dataface-0.1.6.dev82.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev62.dist-info → dataface-0.1.6.dev82.dist-info}/RECORD +33 -33
- mdsvg/renderer.py +68 -25
- mdsvg/style.py +2 -2
- {dataface-0.1.6.dev62.dist-info → dataface-0.1.6.dev82.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev62.dist-info → dataface-0.1.6.dev82.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev62.dist-info → dataface-0.1.6.dev82.dist-info}/licenses/LICENSE +0 -0
dataface/agent_api/_paths.py
CHANGED
|
@@ -65,7 +65,7 @@ class FaceRenderContext:
|
|
|
65
65
|
"""
|
|
66
66
|
|
|
67
67
|
face_file: Path
|
|
68
|
-
scoped_path: Path
|
|
68
|
+
scoped_path: Path
|
|
69
69
|
scoped_base: Path
|
|
70
70
|
project_root: Path
|
|
71
71
|
output_dir: Path
|
|
@@ -74,37 +74,34 @@ class FaceRenderContext:
|
|
|
74
74
|
|
|
75
75
|
def build_face_render_context(
|
|
76
76
|
face_path: Path,
|
|
77
|
-
project_dir: Path
|
|
77
|
+
project_dir: Path,
|
|
78
78
|
) -> FaceRenderContext:
|
|
79
79
|
"""Resolve a face path and walk for dbt context.
|
|
80
80
|
|
|
81
|
-
``project_dir
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
authoritative; the walk only contributes the dbt project path.
|
|
81
|
+
``project_dir`` is authoritative; the walk only contributes the dbt project
|
|
82
|
+
path. Callers must resolve their project dir first (e.g. via
|
|
83
|
+
``resolve_project_dir(raw_dir)`` at the CLI boundary).
|
|
85
84
|
"""
|
|
86
85
|
if face_path.is_absolute():
|
|
87
86
|
face_file = face_path.resolve()
|
|
88
87
|
elif ".." in face_path.parts:
|
|
89
88
|
face_file = (Path.cwd() / face_path).resolve()
|
|
90
89
|
else:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else resolve_project_dir(None)
|
|
95
|
-
)
|
|
96
|
-
face_file = (anchor / face_path).resolve()
|
|
97
|
-
|
|
98
|
-
walk_root, dbt_project_path = discover_render_context(
|
|
90
|
+
face_file = (project_dir / face_path).resolve()
|
|
91
|
+
|
|
92
|
+
_, dbt_project_path = discover_render_context(
|
|
99
93
|
face_file.parent,
|
|
100
94
|
discovery_boundary_for_face(face_file.parent, project_dir),
|
|
101
95
|
)
|
|
102
|
-
project_root = project_dir
|
|
96
|
+
project_root = project_dir
|
|
103
97
|
|
|
104
98
|
try:
|
|
105
|
-
scoped_path: Path
|
|
99
|
+
scoped_path: Path = face_file.relative_to(project_root)
|
|
106
100
|
except ValueError:
|
|
107
|
-
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Face file {face_file} is outside project_dir {project_root}. "
|
|
103
|
+
f"Pass an explicit --project-dir that contains the face file."
|
|
104
|
+
) from None
|
|
108
105
|
|
|
109
106
|
return FaceRenderContext(
|
|
110
107
|
face_file=face_file,
|
|
@@ -129,18 +126,15 @@ class YamlRenderContext:
|
|
|
129
126
|
|
|
130
127
|
|
|
131
128
|
def build_yaml_render_context(
|
|
132
|
-
project_dir: Path
|
|
129
|
+
project_dir: Path,
|
|
133
130
|
) -> YamlRenderContext:
|
|
134
131
|
"""Walk for dbt context anchored at the given project root.
|
|
135
132
|
|
|
136
|
-
``project_dir
|
|
137
|
-
``
|
|
133
|
+
``project_dir`` is authoritative. Callers must resolve their project dir
|
|
134
|
+
first (e.g. via ``resolve_project_dir(raw_dir)`` at the CLI boundary).
|
|
138
135
|
"""
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
walk_root, dbt_project_path = discover_render_context(anchor, None)
|
|
143
|
-
project_root = anchor if project_dir is not None else walk_root
|
|
136
|
+
project_root = project_dir.resolve()
|
|
137
|
+
_, dbt_project_path = discover_render_context(project_root, None)
|
|
144
138
|
return YamlRenderContext(
|
|
145
139
|
project_root=project_root,
|
|
146
140
|
output_dir=project_root,
|
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(
|
|
@@ -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. |
|
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:
|
|
@@ -253,6 +253,10 @@ class ProjectSession:
|
|
|
253
253
|
def warnings_ignore(self) -> frozenset[str]:
|
|
254
254
|
return self.project.warnings_ignore
|
|
255
255
|
|
|
256
|
+
@property
|
|
257
|
+
def faces_dir(self) -> Path:
|
|
258
|
+
return self.project.faces_dir
|
|
259
|
+
|
|
256
260
|
@cached_property
|
|
257
261
|
def _relationship_context(self) -> RelationshipContext | None:
|
|
258
262
|
"""Load relationship context from the super-schema cache (lazy, per-instance).
|
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
|
@@ -31,7 +31,7 @@ class DatafaceAIContext:
|
|
|
31
31
|
if path.is_absolute():
|
|
32
32
|
raise ValueError(f"Dashboard path must be relative: {path}")
|
|
33
33
|
if self.dashboards_directory is None:
|
|
34
|
-
base_dir =
|
|
34
|
+
base_dir = self.project_session.faces_dir.resolve()
|
|
35
35
|
else:
|
|
36
36
|
base_dir = self.dashboards_directory.resolve()
|
|
37
37
|
resolved = (base_dir / path).resolve()
|
dataface/ai/llm.py
CHANGED
|
@@ -50,7 +50,8 @@ def _resolve_model(
|
|
|
50
50
|
def _normalize_openai_tools(
|
|
51
51
|
tools: list[dict[str, Any]] | None,
|
|
52
52
|
) -> list[dict[str, Any]]:
|
|
53
|
-
|
|
53
|
+
# None means "use default agent tools"; [] means "no tools, text-only completion".
|
|
54
|
+
source_tools = ALL_TOOLS if tools is None else tools
|
|
54
55
|
if not source_tools:
|
|
55
56
|
return []
|
|
56
57
|
if "input_schema" in source_tools[0]:
|
|
@@ -69,7 +70,8 @@ def _normalize_openai_tools(
|
|
|
69
70
|
def _normalize_anthropic_tools(
|
|
70
71
|
tools: list[dict[str, Any]] | None,
|
|
71
72
|
) -> list[dict[str, Any]]:
|
|
72
|
-
|
|
73
|
+
# None means "use default agent tools"; [] means "no tools, text-only completion".
|
|
74
|
+
source_tools = ALL_TOOLS if tools is None else tools
|
|
73
75
|
if not source_tools:
|
|
74
76
|
return []
|
|
75
77
|
if "input_schema" in source_tools[0]:
|
|
@@ -120,8 +122,14 @@ def _is_anthropic_api_error(exc: Exception) -> bool:
|
|
|
120
122
|
# -- Provider stream adapter ------------------------------------------------
|
|
121
123
|
|
|
122
124
|
|
|
123
|
-
def _iter_anthropic_stream(
|
|
124
|
-
|
|
125
|
+
def _iter_anthropic_stream(
|
|
126
|
+
stream_ctx: Any, usage_sink: Any = None
|
|
127
|
+
) -> Iterator[StreamEvent]:
|
|
128
|
+
"""Parse Anthropic Messages API stream context manager into typed events.
|
|
129
|
+
|
|
130
|
+
``usage_sink``, when given, is called once with the final message's usage
|
|
131
|
+
payload so the client can accumulate token totals.
|
|
132
|
+
"""
|
|
125
133
|
with stream_ctx as stream:
|
|
126
134
|
for event in stream:
|
|
127
135
|
if event.type == "content_block_delta" and event.delta.type == "text_delta":
|
|
@@ -129,6 +137,9 @@ def _iter_anthropic_stream(stream_ctx: Any) -> Iterator[StreamEvent]:
|
|
|
129
137
|
|
|
130
138
|
final_message = stream.get_final_message()
|
|
131
139
|
|
|
140
|
+
if usage_sink is not None:
|
|
141
|
+
usage_sink(final_message.usage)
|
|
142
|
+
|
|
132
143
|
for block in final_message.content:
|
|
133
144
|
if block.type == "tool_use":
|
|
134
145
|
yield ToolCallEvent(
|
|
@@ -144,6 +155,13 @@ class LLMClient(Protocol):
|
|
|
144
155
|
|
|
145
156
|
provider: str
|
|
146
157
|
model: str
|
|
158
|
+
# Cumulative usage across this client's lifetime. Every implementation must
|
|
159
|
+
# maintain these honestly — eval solvers read them to attribute per-case
|
|
160
|
+
# cost/calls. Declared here so callers access them directly (no getattr
|
|
161
|
+
# defaults, which would silently fabricate zeros for a client that forgot them).
|
|
162
|
+
total_input_tokens: int
|
|
163
|
+
total_output_tokens: int
|
|
164
|
+
llm_calls: int
|
|
147
165
|
|
|
148
166
|
def stream_with_tools(
|
|
149
167
|
self,
|
|
@@ -332,6 +350,17 @@ class AnthropicClient:
|
|
|
332
350
|
)
|
|
333
351
|
self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY")
|
|
334
352
|
self._client: Any = None
|
|
353
|
+
# Cumulative token usage + call count (see LLMClient protocol).
|
|
354
|
+
self.total_input_tokens = 0
|
|
355
|
+
self.total_output_tokens = 0
|
|
356
|
+
self.llm_calls = 0
|
|
357
|
+
|
|
358
|
+
def _accumulate_usage(self, usage: Any) -> None:
|
|
359
|
+
"""Add an Anthropic Messages usage payload to the running token totals."""
|
|
360
|
+
if usage is None:
|
|
361
|
+
return
|
|
362
|
+
self.total_input_tokens += getattr(usage, "input_tokens", 0) or 0
|
|
363
|
+
self.total_output_tokens += getattr(usage, "output_tokens", 0) or 0
|
|
335
364
|
|
|
336
365
|
@property
|
|
337
366
|
def client(self) -> Any:
|
|
@@ -404,6 +433,7 @@ class AnthropicClient:
|
|
|
404
433
|
system_prompt: str,
|
|
405
434
|
tools: list[dict[str, Any]] | None = None,
|
|
406
435
|
) -> Iterator[StreamEvent]:
|
|
436
|
+
self.llm_calls += 1
|
|
407
437
|
try:
|
|
408
438
|
yield from _iter_anthropic_stream(
|
|
409
439
|
self.client.messages.stream(
|
|
@@ -412,7 +442,8 @@ class AnthropicClient:
|
|
|
412
442
|
messages=self._messages_to_input(messages),
|
|
413
443
|
tools=_normalize_anthropic_tools(tools),
|
|
414
444
|
max_tokens=4096,
|
|
415
|
-
)
|
|
445
|
+
),
|
|
446
|
+
usage_sink=self._accumulate_usage,
|
|
416
447
|
)
|
|
417
448
|
except Exception as exc:
|
|
418
449
|
if _is_anthropic_api_error(exc):
|
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
|
@@ -8,6 +8,8 @@ from dataface.agent_api import ProjectSession
|
|
|
8
8
|
from dataface.agent_api._paths import (
|
|
9
9
|
build_face_render_context,
|
|
10
10
|
build_yaml_render_context,
|
|
11
|
+
resolve_project_dir,
|
|
12
|
+
resolve_project_dir_from_paths,
|
|
11
13
|
)
|
|
12
14
|
from dataface.agent_api.cache import cache_from_env
|
|
13
15
|
from dataface.agent_api.dashboards import RenderedDashboard
|
|
@@ -119,7 +121,10 @@ def render_command(
|
|
|
119
121
|
if ignore_codes:
|
|
120
122
|
_emit_unknown_code_notices(ignore_codes)
|
|
121
123
|
|
|
122
|
-
|
|
124
|
+
resolved_project_dir = resolve_project_dir_from_paths(
|
|
125
|
+
[face_path.resolve()], project_dir
|
|
126
|
+
)
|
|
127
|
+
ctx = build_face_render_context(face_path, resolved_project_dir)
|
|
123
128
|
output_dir = ctx.output_dir
|
|
124
129
|
|
|
125
130
|
with (
|
|
@@ -208,7 +213,8 @@ def render_command_from_yaml(
|
|
|
208
213
|
if ignore_codes:
|
|
209
214
|
_emit_unknown_code_notices(ignore_codes)
|
|
210
215
|
|
|
211
|
-
|
|
216
|
+
resolved_project_dir = resolve_project_dir(project_dir)
|
|
217
|
+
ctx = build_yaml_render_context(resolved_project_dir)
|
|
212
218
|
output_dir = ctx.output_dir
|
|
213
219
|
|
|
214
220
|
with (
|
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(
|
|
@@ -332,8 +332,7 @@ class TitleStyle(BaseModel):
|
|
|
332
332
|
line_height: float = Field(
|
|
333
333
|
description=(
|
|
334
334
|
"Line height multiplier for titles and markdown headings. Headings "
|
|
335
|
-
"typically want a tighter multiplier than body prose
|
|
336
|
-
"the body 1.5-1.6)."
|
|
335
|
+
"typically want a tighter multiplier than body prose."
|
|
337
336
|
)
|
|
338
337
|
)
|
|
339
338
|
sizes: list[float] = Field(
|