dataface 0.1.6.dev34__py3-none-any.whl → 0.1.6.dev62__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/__init__.py +1 -2
- dataface/agent_api/_paths.py +4 -18
- dataface/agent_api/chat.py +13 -10
- dataface/agent_api/dashboards.py +4 -3
- dataface/agent_api/describe.py +11 -10
- dataface/agent_api/pack.py +13 -17
- dataface/agent_api/project_session.py +39 -13
- dataface/agent_api/query.py +8 -7
- dataface/agent_api/render_face.py +19 -15
- dataface/agent_api/validate.py +13 -13
- dataface/ai/llm.py +28 -12
- dataface/ai/mcp/server.py +3 -2
- dataface/ai/skills/dashboard-pack-scaffolding/SKILL.md +2 -1
- dataface/ai/tools/__init__.py +5 -5
- dataface/cli/commands/chat.py +5 -4
- dataface/core/__init__.py +1 -2
- dataface/core/compile/__init__.py +1 -1
- dataface/core/compile/compiler.py +6 -14
- dataface/core/dashboard.py +13 -9
- dataface/core/errors/__init__.py +2 -0
- dataface/core/errors/codes_execute.py +10 -0
- dataface/core/errors/registry.py +4 -2
- dataface/core/execute/adapters/adapter_registry.py +11 -10
- dataface/core/execute/adapters/csv_adapter.py +2 -0
- dataface/core/execute/executor.py +7 -3
- dataface/core/fonts.py +10 -0
- dataface/core/inspect/renderer.py +7 -11
- dataface/core/render/chart/profile.py +57 -23
- dataface/core/render/face_api.py +6 -6
- dataface/core/render/font_support.py +18 -6
- dataface/core/render/fonts/InterVariable-Italic.ttf +0 -0
- dataface/core/render/fonts/SourceSerif4-Italic.ttf +0 -0
- dataface/core/render/fonts/_emoji_font_face.css +14 -0
- dataface/core/serve/server.py +57 -68
- dataface/integrations/markdown.py +1 -1
- {dataface-0.1.6.dev34.dist-info → dataface-0.1.6.dev62.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev34.dist-info → dataface-0.1.6.dev62.dist-info}/RECORD +40 -38
- {dataface-0.1.6.dev34.dist-info → dataface-0.1.6.dev62.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev34.dist-info → dataface-0.1.6.dev62.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev34.dist-info → dataface-0.1.6.dev62.dist-info}/licenses/LICENSE +0 -0
dataface/__init__.py
CHANGED
|
@@ -22,9 +22,8 @@ Quick Start:
|
|
|
22
22
|
... face = result.face
|
|
23
23
|
...
|
|
24
24
|
... # Create executor and render
|
|
25
|
-
... from dataface.core.compile.config import load_project_sources
|
|
26
25
|
... from dataface.core.project import Project
|
|
27
|
-
... registry = build_adapter_registry(
|
|
26
|
+
... registry = build_adapter_registry(Project(Path.cwd()))
|
|
28
27
|
... executor = Executor(face, registry, query_registry=result.query_registry)
|
|
29
28
|
... svg = render(face, executor, format="svg")
|
|
30
29
|
"""
|
dataface/agent_api/_paths.py
CHANGED
|
@@ -11,7 +11,7 @@ from dataface.core.project_roots import (
|
|
|
11
11
|
find_dft_root as find_dft_root,
|
|
12
12
|
)
|
|
13
13
|
from dataface.core.scoped_paths import (
|
|
14
|
-
resolve_scoped_path as
|
|
14
|
+
resolve_scoped_path as resolve_scoped_path,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
@@ -45,26 +45,12 @@ def resolve_project_dir_from_paths(
|
|
|
45
45
|
return resolve_project_dir(None)
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
def
|
|
49
|
-
"""agent_api wrapper: resolves `None → cwd-walked root` at the boundary,
|
|
50
|
-
then delegates to `core.scoped_paths.resolve_scoped_path`.
|
|
51
|
-
|
|
52
|
-
Absolute paths with no explicit `project_dir` resolve directly (no
|
|
53
|
-
containment check) — the caller supplied the path, so no project context is
|
|
54
|
-
needed to validate containment.
|
|
55
|
-
"""
|
|
56
|
-
if path.is_absolute() and project_dir is None:
|
|
57
|
-
return path.resolve()
|
|
58
|
-
return _core_resolve_scoped_path(path, resolve_project_dir(project_dir))
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def no_project_hint(project_dir: Path | None) -> str:
|
|
48
|
+
def no_project_hint(project_root: Path) -> str:
|
|
62
49
|
"""Return a hint string when no project marker is found, else empty string."""
|
|
63
|
-
|
|
64
|
-
if find_dft_root(check) is not None:
|
|
50
|
+
if find_dft_root(project_root) is not None:
|
|
65
51
|
return ""
|
|
66
52
|
return (
|
|
67
|
-
f" No Dataface project marker found at or above {
|
|
53
|
+
f" No Dataface project marker found at or above {project_root}."
|
|
68
54
|
f" Run from inside a Dataface project, or set project_dir."
|
|
69
55
|
)
|
|
70
56
|
|
dataface/agent_api/chat.py
CHANGED
|
@@ -24,6 +24,7 @@ from dataface.ai.agent import run_agent
|
|
|
24
24
|
from dataface.ai.context import DatafaceAIContext
|
|
25
25
|
from dataface.ai.events import AgentEvent
|
|
26
26
|
from dataface.ai.llm import create_client
|
|
27
|
+
from dataface.core.project import Project
|
|
27
28
|
|
|
28
29
|
if TYPE_CHECKING:
|
|
29
30
|
from dataface.ai.external_mcp import ExternalMCPManager
|
|
@@ -71,26 +72,27 @@ class ChatSessionSummary:
|
|
|
71
72
|
def start_session(
|
|
72
73
|
*,
|
|
73
74
|
model: str | None = None,
|
|
74
|
-
|
|
75
|
+
project: Project,
|
|
75
76
|
server_port: int | None = None,
|
|
76
77
|
) -> ChatSession:
|
|
77
78
|
"""Create a new chat session.
|
|
78
79
|
|
|
79
80
|
Args:
|
|
80
81
|
model: LLM model name, optionally provider-prefixed.
|
|
81
|
-
|
|
82
|
+
project: The Dataface project for this session.
|
|
82
83
|
server_port: Port of the embedded HTTP preview server, if running.
|
|
83
84
|
"""
|
|
84
85
|
from dataface.agent_api.project_session import ProjectSession
|
|
85
86
|
from dataface.core.execute.adapters import LOCAL_AUTHORING_REGISTRY_KWARGS
|
|
86
87
|
|
|
87
|
-
cwd = (project_dir or Path.cwd()).resolve()
|
|
88
88
|
client = create_client(model=model)
|
|
89
89
|
context = DatafaceAIContext(
|
|
90
|
-
project_session=ProjectSession.
|
|
90
|
+
project_session=ProjectSession.from_project(
|
|
91
|
+
project, **LOCAL_AUTHORING_REGISTRY_KWARGS
|
|
92
|
+
),
|
|
91
93
|
server_port=server_port,
|
|
92
94
|
)
|
|
93
|
-
writer, _ = new_session(
|
|
95
|
+
writer, _ = new_session(project.root, provider=client.provider, model=client.model)
|
|
94
96
|
return ChatSession(
|
|
95
97
|
session_id=writer.session_id,
|
|
96
98
|
messages=[],
|
|
@@ -106,7 +108,7 @@ def start_session(
|
|
|
106
108
|
def resume_session(
|
|
107
109
|
session_id: str,
|
|
108
110
|
*,
|
|
109
|
-
|
|
111
|
+
project: Project,
|
|
110
112
|
server_port: int | None = None,
|
|
111
113
|
model: str | None = None,
|
|
112
114
|
max_tokens: int = 32_000,
|
|
@@ -115,7 +117,7 @@ def resume_session(
|
|
|
115
117
|
|
|
116
118
|
Args:
|
|
117
119
|
session_id: The session id to resume.
|
|
118
|
-
|
|
120
|
+
project: The Dataface project for the resumed session.
|
|
119
121
|
server_port: Port of the embedded HTTP preview server, if running.
|
|
120
122
|
model: LLM model to use. Defaults to the same provider as the session.
|
|
121
123
|
max_tokens: Token budget for trimming old messages. 0 disables trimming.
|
|
@@ -126,7 +128,6 @@ def resume_session(
|
|
|
126
128
|
from dataface.agent_api.project_session import ProjectSession
|
|
127
129
|
from dataface.core.execute.adapters import LOCAL_AUTHORING_REGISTRY_KWARGS
|
|
128
130
|
|
|
129
|
-
cwd = (project_dir or Path.cwd()).resolve()
|
|
130
131
|
client = create_client(model=model)
|
|
131
132
|
|
|
132
133
|
messages, meta, _ = load_session_for_resume(
|
|
@@ -143,10 +144,12 @@ def resume_session(
|
|
|
143
144
|
)
|
|
144
145
|
|
|
145
146
|
context = DatafaceAIContext(
|
|
146
|
-
project_session=ProjectSession.
|
|
147
|
+
project_session=ProjectSession.from_project(
|
|
148
|
+
project, **LOCAL_AUTHORING_REGISTRY_KWARGS
|
|
149
|
+
),
|
|
147
150
|
server_port=server_port,
|
|
148
151
|
)
|
|
149
|
-
writer, _ = new_session(
|
|
152
|
+
writer, _ = new_session(project.root, provider=client.provider, model=client.model)
|
|
150
153
|
return ChatSession(
|
|
151
154
|
session_id=session_id,
|
|
152
155
|
messages=messages,
|
dataface/agent_api/dashboards.py
CHANGED
|
@@ -18,6 +18,7 @@ from dataface.core.compile import Face, compile_file
|
|
|
18
18
|
from dataface.core.compile.errors import DatafaceError
|
|
19
19
|
from dataface.core.dashboard import RenderedDashboard as RenderedDashboard
|
|
20
20
|
from dataface.core.errors import DF_UNKNOWN_INTERNAL, StructuredError
|
|
21
|
+
from dataface.core.project import Project
|
|
21
22
|
from dataface.core.render.warnings.base import RenderWarning
|
|
22
23
|
|
|
23
24
|
# ---------------------------------------------------------------------------
|
|
@@ -193,12 +194,12 @@ def list_dashboards(
|
|
|
193
194
|
def get_dashboard(
|
|
194
195
|
path: Path,
|
|
195
196
|
*,
|
|
196
|
-
|
|
197
|
+
project: Project,
|
|
197
198
|
include_raw: bool = False,
|
|
198
199
|
) -> CompiledDashboard:
|
|
199
200
|
"""Get the compiled structure of a dashboard."""
|
|
200
201
|
try:
|
|
201
|
-
file_path = resolve_scoped_path(path,
|
|
202
|
+
file_path = resolve_scoped_path(path, project.root)
|
|
202
203
|
except ValueError as exc:
|
|
203
204
|
return CompiledDashboard(
|
|
204
205
|
success=False,
|
|
@@ -243,7 +244,7 @@ def get_dashboard(
|
|
|
243
244
|
],
|
|
244
245
|
)
|
|
245
246
|
|
|
246
|
-
result = compile_file(file_path)
|
|
247
|
+
result = compile_file(file_path, project=project)
|
|
247
248
|
return CompiledDashboard(
|
|
248
249
|
success=result.success,
|
|
249
250
|
dashboard=result.face if result.success else None,
|
dataface/agent_api/describe.py
CHANGED
|
@@ -21,6 +21,7 @@ from dataface.core.compile.models.query.normalized import (
|
|
|
21
21
|
ValuesQuery,
|
|
22
22
|
)
|
|
23
23
|
from dataface.core.errors import DF_UNKNOWN_INTERNAL, StructuredError
|
|
24
|
+
from dataface.core.project import Project
|
|
24
25
|
|
|
25
26
|
# Chart-encoding fields surfaced to agents. Covers the stable channel fields
|
|
26
27
|
# across chart families. `label` is excluded — it's a KPI-specific render hint,
|
|
@@ -190,14 +191,14 @@ def _encoding_for_chart(chart: Chart) -> dict[str, Any]:
|
|
|
190
191
|
# ---------------------------------------------------------------------------
|
|
191
192
|
|
|
192
193
|
|
|
193
|
-
def describe_face(path: Path, *,
|
|
194
|
+
def describe_face(path: Path, *, project: Project) -> DescribeFaceResult:
|
|
194
195
|
"""Describe the structure of a face: queries, charts, variables, layout."""
|
|
195
196
|
from dataface.agent_api._paths import no_project_hint, resolve_scoped_path
|
|
196
197
|
from dataface.core.compile.compiler import compile_file
|
|
197
198
|
from dataface.core.compile.errors import DatafaceError
|
|
198
199
|
|
|
199
200
|
try:
|
|
200
|
-
resolved = resolve_scoped_path(path,
|
|
201
|
+
resolved = resolve_scoped_path(path, project.root)
|
|
201
202
|
except ValueError as exc:
|
|
202
203
|
return DescribeFaceResult(
|
|
203
204
|
success=False,
|
|
@@ -210,7 +211,7 @@ def describe_face(path: Path, *, project_dir: Path) -> DescribeFaceResult:
|
|
|
210
211
|
)
|
|
211
212
|
|
|
212
213
|
if not resolved.exists():
|
|
213
|
-
hint = no_project_hint(
|
|
214
|
+
hint = no_project_hint(project.root)
|
|
214
215
|
return DescribeFaceResult(
|
|
215
216
|
success=False,
|
|
216
217
|
path=path,
|
|
@@ -223,7 +224,7 @@ def describe_face(path: Path, *, project_dir: Path) -> DescribeFaceResult:
|
|
|
223
224
|
)
|
|
224
225
|
|
|
225
226
|
try:
|
|
226
|
-
result = compile_file(resolved)
|
|
227
|
+
result = compile_file(resolved, project=project)
|
|
227
228
|
except OSError as exc:
|
|
228
229
|
return DescribeFaceResult(
|
|
229
230
|
success=False,
|
|
@@ -305,7 +306,7 @@ def describe_face(path: Path, *, project_dir: Path) -> DescribeFaceResult:
|
|
|
305
306
|
def describe_paths(
|
|
306
307
|
paths: list[Path],
|
|
307
308
|
*,
|
|
308
|
-
|
|
309
|
+
project: Project,
|
|
309
310
|
) -> list[DescribeFaceResult]:
|
|
310
311
|
"""Describe N face files / directories.
|
|
311
312
|
|
|
@@ -315,13 +316,13 @@ def describe_paths(
|
|
|
315
316
|
"""
|
|
316
317
|
out: list[DescribeFaceResult] = []
|
|
317
318
|
for p in paths:
|
|
318
|
-
out.extend(_describe_one_path(p,
|
|
319
|
+
out.extend(_describe_one_path(p, project))
|
|
319
320
|
return out
|
|
320
321
|
|
|
321
322
|
|
|
322
323
|
def _describe_one_path(
|
|
323
324
|
path: Path,
|
|
324
|
-
|
|
325
|
+
project: Project,
|
|
325
326
|
) -> list[DescribeFaceResult]:
|
|
326
327
|
"""Per-argv expansion: file → [one], dir → walk."""
|
|
327
328
|
# WHY: dataface.core.inspect.manifest_utils triggers the inspect package
|
|
@@ -332,7 +333,7 @@ def _describe_one_path(
|
|
|
332
333
|
from dataface.core.inspect.manifest_utils import INSPECT_TEMPLATE_MANIFEST
|
|
333
334
|
|
|
334
335
|
try:
|
|
335
|
-
resolved = resolve_scoped_path(path,
|
|
336
|
+
resolved = resolve_scoped_path(path, project.root)
|
|
336
337
|
except ValueError as exc:
|
|
337
338
|
return [
|
|
338
339
|
DescribeFaceResult(
|
|
@@ -347,7 +348,7 @@ def _describe_one_path(
|
|
|
347
348
|
]
|
|
348
349
|
|
|
349
350
|
if not resolved.is_dir():
|
|
350
|
-
return [describe_face(resolved,
|
|
351
|
+
return [describe_face(resolved, project=project)]
|
|
351
352
|
|
|
352
353
|
template_dirs = {m.parent for m in resolved.glob(f"**/{INSPECT_TEMPLATE_MANIFEST}")}
|
|
353
354
|
yaml_files = sorted(
|
|
@@ -368,4 +369,4 @@ def _describe_one_path(
|
|
|
368
369
|
],
|
|
369
370
|
)
|
|
370
371
|
]
|
|
371
|
-
return [describe_face(f,
|
|
372
|
+
return [describe_face(f, project=project) for f in yaml_files]
|
dataface/agent_api/pack.py
CHANGED
|
@@ -18,7 +18,6 @@ from pathlib import Path
|
|
|
18
18
|
import yaml
|
|
19
19
|
from pydantic import BaseModel
|
|
20
20
|
|
|
21
|
-
from dataface.core.compile.config import load_project_sources
|
|
22
21
|
from dataface.core.execute.adapters import AdapterRegistry, build_adapter_registry
|
|
23
22
|
from dataface.core.inspect.resolver import LayeredSchemaResolver
|
|
24
23
|
from dataface.core.pack.models import PackProposal, ProposedDashboard
|
|
@@ -140,16 +139,16 @@ def _collect_source_entries(
|
|
|
140
139
|
|
|
141
140
|
|
|
142
141
|
def propose_pack(
|
|
143
|
-
|
|
142
|
+
project: Project,
|
|
144
143
|
mode: str | None = None,
|
|
145
144
|
) -> tuple[PackProposal, Path]:
|
|
146
145
|
"""Read schema via the resolver and emit a deterministic PackProposal.
|
|
147
146
|
|
|
148
147
|
The proposal is written to
|
|
149
|
-
``<
|
|
148
|
+
``<project.root>/target/dataface/proposals/<slug>/proposal.yml``.
|
|
150
149
|
|
|
151
150
|
Args:
|
|
152
|
-
|
|
151
|
+
project: Dataface project (root must contain ``dataface.yml``).
|
|
153
152
|
mode: Organization mode override (``"connector-first"``,
|
|
154
153
|
``"domain-first"``, or ``"hybrid"``). When ``None``, the planner
|
|
155
154
|
chooses automatically.
|
|
@@ -160,17 +159,15 @@ def propose_pack(
|
|
|
160
159
|
of the written YAML file.
|
|
161
160
|
|
|
162
161
|
Raises:
|
|
163
|
-
FileNotFoundError: When
|
|
162
|
+
FileNotFoundError: When ``project.root`` does not exist.
|
|
164
163
|
ValueError: When no SQL sources are configured in the project, or when
|
|
165
164
|
all configured sources are unreachable.
|
|
166
165
|
"""
|
|
167
|
-
project_dir =
|
|
166
|
+
project_dir = project.root
|
|
168
167
|
if not project_dir.exists():
|
|
169
168
|
raise FileNotFoundError(f"Project directory not found: {project_dir}")
|
|
170
169
|
|
|
171
|
-
registry = build_adapter_registry(
|
|
172
|
-
project_dir, project_sources=load_project_sources(Project(project_dir))
|
|
173
|
-
)
|
|
170
|
+
registry = build_adapter_registry(project)
|
|
174
171
|
sql_sources = registry.list_sql_sources()
|
|
175
172
|
|
|
176
173
|
if not sql_sources:
|
|
@@ -409,7 +406,7 @@ def _validate_scaffold_targets(
|
|
|
409
406
|
|
|
410
407
|
def apply_proposal(
|
|
411
408
|
proposal: PackProposal,
|
|
412
|
-
|
|
409
|
+
project: Project,
|
|
413
410
|
overwrite: bool = False,
|
|
414
411
|
) -> ScaffoldResult:
|
|
415
412
|
"""Apply an approved PackProposal, writing a sparse ``faces/`` folder tree.
|
|
@@ -426,7 +423,7 @@ def apply_proposal(
|
|
|
426
423
|
|
|
427
424
|
Args:
|
|
428
425
|
proposal: An approved :class:`~dataface.core.pack.models.PackProposal`.
|
|
429
|
-
|
|
426
|
+
project: Dataface project (root is the directory containing
|
|
430
427
|
``dataface.yml``).
|
|
431
428
|
overwrite: When ``False`` (default), existing non-empty files are
|
|
432
429
|
skipped and recorded in ``ScaffoldResult.skipped_files``. When
|
|
@@ -434,24 +431,23 @@ def apply_proposal(
|
|
|
434
431
|
|
|
435
432
|
Returns:
|
|
436
433
|
A :class:`ScaffoldResult` with ``created_files``, ``skipped_files``,
|
|
437
|
-
and ``errors`` (relative to ``
|
|
434
|
+
and ``errors`` (relative to ``project.root``).
|
|
438
435
|
|
|
439
436
|
Raises:
|
|
440
437
|
ValueError: When any generated file fails ``validate_paths()``.
|
|
441
438
|
"""
|
|
442
439
|
from dataface.agent_api.validate import validate_paths
|
|
443
440
|
|
|
444
|
-
project_dir = project_dir.resolve()
|
|
445
441
|
result = ScaffoldResult()
|
|
446
442
|
validated_paths: list[Path] = []
|
|
447
443
|
partial_targets, landing_targets, dashboard_targets = _validate_scaffold_targets(
|
|
448
444
|
proposal,
|
|
449
|
-
|
|
445
|
+
project.root,
|
|
450
446
|
)
|
|
451
447
|
|
|
452
448
|
# Ensure base dirs exist
|
|
453
|
-
(
|
|
454
|
-
(
|
|
449
|
+
(project.root / "faces").mkdir(exist_ok=True)
|
|
450
|
+
(project.root / "faces" / "partials").mkdir(exist_ok=True)
|
|
455
451
|
|
|
456
452
|
# Write partials — skipped by validate_paths (_*.yml convention)
|
|
457
453
|
for partial_filename, partial_path, rel in partial_targets:
|
|
@@ -490,7 +486,7 @@ def apply_proposal(
|
|
|
490
486
|
|
|
491
487
|
# Validate gate — validate all newly written face files
|
|
492
488
|
if validated_paths:
|
|
493
|
-
validate_results = validate_paths(validated_paths,
|
|
489
|
+
validate_results = validate_paths(validated_paths, project=project)
|
|
494
490
|
for vr in validate_results:
|
|
495
491
|
if not vr.success:
|
|
496
492
|
for err in vr.errors:
|
|
@@ -144,6 +144,37 @@ class ProjectSession:
|
|
|
144
144
|
session._resolver = resolver
|
|
145
145
|
return session
|
|
146
146
|
|
|
147
|
+
@classmethod
|
|
148
|
+
def from_project(
|
|
149
|
+
cls,
|
|
150
|
+
project: Project,
|
|
151
|
+
*,
|
|
152
|
+
cache: DuckDBCache | None = None,
|
|
153
|
+
read_only: bool = True,
|
|
154
|
+
dbt_project_path: Path | None = None,
|
|
155
|
+
connection_string: str | None = None,
|
|
156
|
+
dialect: str = "duckdb",
|
|
157
|
+
target: str = "dev",
|
|
158
|
+
duckdb_config: dict[str, Any] | None = None,
|
|
159
|
+
allow_external_access_in_readonly: bool = False,
|
|
160
|
+
resolver: SourceResolver | None = None,
|
|
161
|
+
) -> Self:
|
|
162
|
+
"""Construct a ProjectSession from a pre-built Project.
|
|
163
|
+
|
|
164
|
+
Use this when the caller already holds a ``project: Project`` and does
|
|
165
|
+
not want to re-resolve the path. The caller owns the Project lifecycle;
|
|
166
|
+
this method stores it directly without re-wrapping.
|
|
167
|
+
"""
|
|
168
|
+
session = cls(project=project, cache=cache, read_only=read_only)
|
|
169
|
+
session._dbt_project_path = dbt_project_path
|
|
170
|
+
session._connection_string = connection_string
|
|
171
|
+
session._dialect = dialect
|
|
172
|
+
session._target = target
|
|
173
|
+
session._duckdb_config = duckdb_config
|
|
174
|
+
session._allow_external_access_in_readonly = allow_external_access_in_readonly
|
|
175
|
+
session._resolver = resolver
|
|
176
|
+
return session
|
|
177
|
+
|
|
147
178
|
@classmethod
|
|
148
179
|
def from_face(
|
|
149
180
|
cls,
|
|
@@ -177,8 +208,7 @@ class ProjectSession:
|
|
|
177
208
|
When constructed with an injected registry, returns it directly.
|
|
178
209
|
"""
|
|
179
210
|
return build_adapter_registry(
|
|
180
|
-
self.project
|
|
181
|
-
project_sources=self.project.sources,
|
|
211
|
+
self.project,
|
|
182
212
|
read_only=self._read_only,
|
|
183
213
|
dbt_project_path=self._dbt_project_path,
|
|
184
214
|
connection_string=self._connection_string,
|
|
@@ -246,12 +276,12 @@ class ProjectSession:
|
|
|
246
276
|
# ── Verb forwarders ──────────────────────────────────────────────────────
|
|
247
277
|
|
|
248
278
|
def validate(self, face_path: Path) -> ValidateResult:
|
|
249
|
-
result = _validate.validate(face_path,
|
|
279
|
+
result = _validate.validate(face_path, project=self.project)
|
|
250
280
|
annotated = _validate.annotate_with_data_lint([result], project_session=self)
|
|
251
281
|
return annotated[0]
|
|
252
282
|
|
|
253
283
|
def validate_paths(self, paths: list[Path] | None) -> list[ValidateResult]:
|
|
254
|
-
results = _validate.validate_paths(paths,
|
|
284
|
+
results = _validate.validate_paths(paths, project=self.project)
|
|
255
285
|
return _validate.annotate_with_data_lint(results, project_session=self)
|
|
256
286
|
|
|
257
287
|
def _source_names(self) -> frozenset[str]:
|
|
@@ -313,10 +343,10 @@ class ProjectSession:
|
|
|
313
343
|
)
|
|
314
344
|
|
|
315
345
|
def describe_face(self, path: Path) -> _describe.DescribeFaceResult:
|
|
316
|
-
return _describe.describe_face(path,
|
|
346
|
+
return _describe.describe_face(path, project=self.project)
|
|
317
347
|
|
|
318
348
|
def describe_paths(self, paths: list[Path]) -> list[_describe.DescribeFaceResult]:
|
|
319
|
-
return _describe.describe_paths(paths,
|
|
349
|
+
return _describe.describe_paths(paths, project=self.project)
|
|
320
350
|
|
|
321
351
|
def list_dashboards(
|
|
322
352
|
self,
|
|
@@ -335,7 +365,7 @@ class ProjectSession:
|
|
|
335
365
|
include_raw: bool = False,
|
|
336
366
|
) -> _dashboards.CompiledDashboard:
|
|
337
367
|
return _dashboards.get_dashboard(
|
|
338
|
-
path, include_raw=include_raw,
|
|
368
|
+
path, include_raw=include_raw, project=self.project
|
|
339
369
|
)
|
|
340
370
|
|
|
341
371
|
def lookup_face_query_sql(
|
|
@@ -343,9 +373,7 @@ class ProjectSession:
|
|
|
343
373
|
name: str,
|
|
344
374
|
path: Path,
|
|
345
375
|
) -> _query.FaceQueryLookupResult:
|
|
346
|
-
return _query.lookup_face_query_sql(
|
|
347
|
-
name=name, path=path, project_dir=self.project.root
|
|
348
|
-
)
|
|
376
|
+
return _query.lookup_face_query_sql(name=name, path=path, project=self.project)
|
|
349
377
|
|
|
350
378
|
def query_face(
|
|
351
379
|
self,
|
|
@@ -357,7 +385,7 @@ class ProjectSession:
|
|
|
357
385
|
return _query.query_face(
|
|
358
386
|
name,
|
|
359
387
|
path,
|
|
360
|
-
|
|
388
|
+
project=self.project,
|
|
361
389
|
vars=vars,
|
|
362
390
|
limit=limit,
|
|
363
391
|
adapter_registry=self.adapter_registry,
|
|
@@ -412,7 +440,6 @@ class ProjectSession:
|
|
|
412
440
|
yaml_content=yaml_content,
|
|
413
441
|
variables=variables,
|
|
414
442
|
adapter_registry=self.adapter_registry,
|
|
415
|
-
project_dir=self.project.root,
|
|
416
443
|
project=self.project,
|
|
417
444
|
duckdb_cache=self.cache,
|
|
418
445
|
format=format,
|
|
@@ -422,7 +449,6 @@ class ProjectSession:
|
|
|
422
449
|
scale=scale,
|
|
423
450
|
ignore_codes=ignore_codes,
|
|
424
451
|
max_workers=max_workers,
|
|
425
|
-
warnings_ignore=self.warnings_ignore,
|
|
426
452
|
link_context=link_context,
|
|
427
453
|
**render_options,
|
|
428
454
|
)
|
dataface/agent_api/query.py
CHANGED
|
@@ -10,6 +10,7 @@ from dataface.core.compile import compile_file
|
|
|
10
10
|
from dataface.core.compile.models.query.normalized import SqlQuery
|
|
11
11
|
from dataface.core.execute.adapters import AdapterRegistry
|
|
12
12
|
from dataface.core.inspect.query_validator import validate_query
|
|
13
|
+
from dataface.core.project import Project
|
|
13
14
|
from dataface.core.validate import normalize_data_for_json
|
|
14
15
|
|
|
15
16
|
MAX_QUERY_LIMIT = 1000
|
|
@@ -29,7 +30,7 @@ def lookup_face_query_sql(
|
|
|
29
30
|
name: str,
|
|
30
31
|
path: Path,
|
|
31
32
|
*,
|
|
32
|
-
|
|
33
|
+
project: Project,
|
|
33
34
|
) -> FaceQueryLookupResult:
|
|
34
35
|
"""Compile a face file and extract the SQL + source for one named SQL query.
|
|
35
36
|
|
|
@@ -37,7 +38,7 @@ def lookup_face_query_sql(
|
|
|
37
38
|
executing it against a warehouse.
|
|
38
39
|
"""
|
|
39
40
|
try:
|
|
40
|
-
file_path = resolve_scoped_path(path,
|
|
41
|
+
file_path = resolve_scoped_path(path, project.root)
|
|
41
42
|
except ValueError as e:
|
|
42
43
|
return FaceQueryLookupResult(success=False, errors=[str(e)])
|
|
43
44
|
|
|
@@ -46,7 +47,7 @@ def lookup_face_query_sql(
|
|
|
46
47
|
success=False, errors=[f"File not found: {file_path}"]
|
|
47
48
|
)
|
|
48
49
|
|
|
49
|
-
compile_result = compile_file(file_path)
|
|
50
|
+
compile_result = compile_file(file_path, project=project)
|
|
50
51
|
if not compile_result.success:
|
|
51
52
|
return FaceQueryLookupResult(
|
|
52
53
|
success=False, errors=[e.message for e in compile_result.errors]
|
|
@@ -248,7 +249,7 @@ def _fail(
|
|
|
248
249
|
def query_face(
|
|
249
250
|
name: str,
|
|
250
251
|
path: Path,
|
|
251
|
-
|
|
252
|
+
project: Project,
|
|
252
253
|
vars: dict[str, Any] | None = None,
|
|
253
254
|
limit: int = 20,
|
|
254
255
|
*,
|
|
@@ -258,17 +259,17 @@ def query_face(
|
|
|
258
259
|
limit = min(limit, MAX_QUERY_LIMIT)
|
|
259
260
|
|
|
260
261
|
try:
|
|
261
|
-
file_path = resolve_scoped_path(path,
|
|
262
|
+
file_path = resolve_scoped_path(path, project.root)
|
|
262
263
|
except ValueError as e:
|
|
263
264
|
return _fail(name, path, [str(e)])
|
|
264
265
|
|
|
265
266
|
if not file_path.exists():
|
|
266
267
|
from dataface.agent_api._paths import no_project_hint
|
|
267
268
|
|
|
268
|
-
hint = no_project_hint(
|
|
269
|
+
hint = no_project_hint(project.root)
|
|
269
270
|
return _fail(name, file_path, [f"File not found: {file_path}{hint}"])
|
|
270
271
|
|
|
271
|
-
compile_result = compile_file(file_path)
|
|
272
|
+
compile_result = compile_file(file_path, project=project)
|
|
272
273
|
if not compile_result.success:
|
|
273
274
|
return _fail(name, file_path, [e.message for e in compile_result.errors])
|
|
274
275
|
|
|
@@ -14,6 +14,7 @@ from dataface.core.compile.markdown import (
|
|
|
14
14
|
markdown_to_yaml,
|
|
15
15
|
)
|
|
16
16
|
from dataface.core.execute.duckdb_cache import DuckDBCache
|
|
17
|
+
from dataface.core.project import Project
|
|
17
18
|
from dataface.core.project_roots import (
|
|
18
19
|
discover_render_context,
|
|
19
20
|
discovery_boundary_for_face,
|
|
@@ -24,9 +25,9 @@ from dataface.core.render.face_api import compile_and_render
|
|
|
24
25
|
def render_face(
|
|
25
26
|
face_path: str | Path,
|
|
26
27
|
*,
|
|
27
|
-
warnings_ignore: frozenset[str],
|
|
28
|
+
warnings_ignore: frozenset[str] | None = None,
|
|
28
29
|
format: str = "svg",
|
|
29
|
-
|
|
30
|
+
project: Project | None = None,
|
|
30
31
|
base_dir: str | Path | None = None,
|
|
31
32
|
variables: dict[str, str] | None = None,
|
|
32
33
|
use_cache: bool = True,
|
|
@@ -37,13 +38,12 @@ def render_face(
|
|
|
37
38
|
|
|
38
39
|
Args:
|
|
39
40
|
face_path: Path to the .yml or .md face file to render.
|
|
40
|
-
warnings_ignore: Project-level warning codes to suppress.
|
|
41
|
-
|
|
42
|
-
``
|
|
43
|
-
does no disk I/O for warnings config.
|
|
41
|
+
warnings_ignore: Project-level warning codes to suppress. When omitted
|
|
42
|
+
and ``project`` is provided, defaults to ``project.warnings_ignore``.
|
|
43
|
+
When both are omitted, defaults to ``frozenset()`` (no suppression).
|
|
44
44
|
format: Output format — "svg", "html", "png", "pdf", etc.
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
project: Explicit project. When omitted, auto-discovered upward from
|
|
46
|
+
the face file's directory via ``ProjectSession.from_face``.
|
|
47
47
|
base_dir: Base directory for resolving relative paths in the face.
|
|
48
48
|
Defaults to the face file's parent directory.
|
|
49
49
|
variables: Runtime variable overrides.
|
|
@@ -69,23 +69,27 @@ def render_face(
|
|
|
69
69
|
|
|
70
70
|
resolved_base_dir = Path(base_dir).resolve() if base_dir else face_file.parent
|
|
71
71
|
|
|
72
|
-
if
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
72
|
+
if project is not None:
|
|
73
|
+
if warnings_ignore is None:
|
|
74
|
+
warnings_ignore = project.warnings_ignore
|
|
75
|
+
# Discover dbt_project.yml between the face and the project root.
|
|
76
|
+
# build_adapter_registry's own fallback would only walk upward from
|
|
77
|
+
# project.root, missing nested dbt projects under it.
|
|
76
78
|
_, dbt_project_path = discover_render_context(
|
|
77
79
|
face_file.parent,
|
|
78
|
-
discovery_boundary_for_face(face_file.parent,
|
|
80
|
+
discovery_boundary_for_face(face_file.parent, project.root),
|
|
79
81
|
)
|
|
80
82
|
project_session = ProjectSession.open(
|
|
81
|
-
|
|
83
|
+
project.root, cache=cache, dbt_project_path=dbt_project_path
|
|
82
84
|
)
|
|
83
85
|
else:
|
|
86
|
+
if warnings_ignore is None:
|
|
87
|
+
warnings_ignore = frozenset()
|
|
84
88
|
project_session = ProjectSession.from_face(face_file, cache=cache)
|
|
85
89
|
try:
|
|
86
90
|
return compile_and_render(
|
|
87
91
|
yaml_content,
|
|
88
|
-
|
|
92
|
+
project_session.project,
|
|
89
93
|
adapter_registry=project_session.adapter_registry,
|
|
90
94
|
cache=cache,
|
|
91
95
|
warnings_ignore=warnings_ignore,
|