dataface 0.3.2.dev22__py3-none-any.whl → 0.3.2.dev111__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 +70 -23
- dataface/agent_api/__init__.py +162 -68
- dataface/agent_api/_paths.py +9 -3
- dataface/agent_api/docs/yaml-reference.md +240 -106
- dataface/agent_api/init.py +1 -1
- dataface/agent_api/project_session.py +2 -1
- dataface/agent_api/serve.py +115 -0
- dataface/ai/agent.py +28 -2
- dataface/cli/_project.py +100 -1
- dataface/cli/commands/board_artifact.py +5 -2
- dataface/cli/commands/describe.py +7 -3
- dataface/cli/commands/docs.py +12 -3
- dataface/cli/commands/init.py +7 -14
- dataface/cli/commands/inspect.py +6 -1
- dataface/cli/commands/mcp_init.py +23 -43
- dataface/cli/commands/migrate.py +8 -3
- dataface/cli/commands/query.py +11 -8
- dataface/cli/commands/render.py +20 -9
- dataface/cli/commands/search.py +8 -2
- dataface/cli/commands/serve.py +26 -76
- dataface/cli/commands/skills_init.py +2 -19
- dataface/cli/commands/validate.py +7 -2
- dataface/cli/filesystem_project.py +5 -4
- dataface/cli/main.py +2 -19
- dataface/core/__init__.py +73 -28
- dataface/core/{compile/colors.py → colors.py} +20 -5
- dataface/core/compile/compiler.py +97 -34
- dataface/core/compile/data_table.py +7 -2
- dataface/core/compile/enrich.py +18 -44
- dataface/core/compile/face_warnings.py +46 -8
- dataface/core/compile/format.py +47 -1
- dataface/core/compile/inherit_graph.py +3 -3
- dataface/core/compile/inherit_registry.yaml +1 -1
- dataface/core/compile/migrations.py +112 -9
- dataface/core/compile/models/chart/normalized/kpi.py +0 -4
- dataface/core/compile/models/chart/resolved/_base.py +28 -10
- dataface/core/compile/models/chart/resolved/callout.py +2 -3
- dataface/core/compile/models/chart/resolved/kpi.py +7 -3
- dataface/core/compile/models/chart/resolved/table.py +16 -2
- dataface/core/compile/models/markers.py +1 -1
- dataface/core/compile/models/primitives.py +175 -2
- dataface/core/compile/models/source.py +5 -5
- dataface/core/compile/models/style/authored/__init__.py +32 -8
- dataface/core/compile/models/style/authored/_base.py +126 -69
- dataface/core/compile/models/style/authored/table.py +61 -3
- dataface/core/compile/models/style/resolved/__init__.py +12 -2
- dataface/core/compile/models/style/resolved/_base.py +142 -57
- dataface/core/compile/models/style/resolved/_cartesian.py +6 -11
- dataface/core/compile/models/style/resolved/_cascade.py +141 -54
- dataface/core/compile/models/style/resolved/area.py +1 -1
- dataface/core/compile/models/style/resolved/bar.py +1 -1
- dataface/core/compile/models/style/resolved/line.py +1 -1
- dataface/core/compile/models/style/resolved/scatter.py +3 -2
- dataface/core/compile/models/style/theme/__init__.py +36 -14
- dataface/core/compile/models/style/theme/_chart_base.py +17 -9
- dataface/core/compile/models/style/theme/axis.py +432 -312
- dataface/core/compile/models/style/theme/charts.py +10 -6
- dataface/core/compile/models/style/theme/kpi.py +2 -1
- dataface/core/compile/models/style/theme/page.py +5 -2
- dataface/core/compile/models/style/theme/table.py +2 -2
- dataface/core/compile/models/vega_lite/config.py +1 -1
- dataface/core/compile/normalize_charts.py +6 -2
- dataface/core/compile/normalize_queries.py +21 -7
- dataface/core/compile/resolve/__init__.py +2 -0
- dataface/core/compile/resolve/_axes.py +147 -44
- dataface/core/compile/resolve/_channels.py +1 -1
- dataface/core/compile/resolve/_dispatch.py +1 -1
- dataface/core/compile/resolve/_domain.py +49 -32
- dataface/core/compile/resolve/_kwargs.py +39 -12
- dataface/core/compile/resolve/_marks.py +8 -3
- dataface/core/compile/resolve/_palette.py +7 -3
- dataface/core/compile/resolve/_table.py +124 -33
- dataface/core/compile/resolve/area.py +48 -18
- dataface/core/compile/resolve/bar.py +245 -64
- dataface/core/compile/resolve/geo.py +10 -3
- dataface/core/compile/resolve/heatmap.py +16 -8
- dataface/core/compile/resolve/line.py +35 -12
- dataface/core/compile/resolve/pie.py +4 -1
- dataface/core/compile/resolve/pie_attachment.py +1 -1
- dataface/core/compile/resolve/scatter.py +25 -13
- dataface/core/compile/resolve/simple.py +25 -2
- dataface/core/compile/schema_renderers/prompt.py +1 -1
- dataface/core/compile/schema_renderers/textmate_grammar.py +8 -1
- dataface/core/compile/source_map.py +211 -28
- dataface/core/compile/style_cascade.py +126 -55
- dataface/core/compile/yaml_error_formatter.py +1 -1
- dataface/core/connections.py +10 -3
- dataface/core/dashboard.py +24 -18
- dataface/core/defaults/themes/_base.yaml +49 -39
- dataface/core/defaults/themes/cream.yaml +4 -4
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +1 -1
- dataface/core/defaults/themes/editorial.yaml +6 -6
- dataface/core/defaults/themes/neon.yaml +15 -8
- dataface/core/defaults/themes/plain.yaml +6 -4
- dataface/core/defaults/themes/stark.yaml +16 -7
- dataface/core/defaults/themes/vivid.yaml +15 -8
- dataface/core/diagnostics/__init__.py +2 -2
- dataface/core/diagnostics/codes_compile.py +13 -31
- dataface/core/diagnostics/codes_execute.py +79 -0
- dataface/core/diagnostics/codes_render.py +87 -28
- dataface/core/diagnostics/diagnostic.py +30 -0
- dataface/core/diagnostics/hints.py +19 -0
- dataface/core/dialects/base.py +17 -0
- dataface/core/dialects/postgres.py +14 -0
- dataface/core/dialects/redshift.py +5 -0
- dataface/core/execute/__init__.py +59 -14
- dataface/core/execute/adapters/dbt_adapter.py +4 -1
- dataface/core/execute/adapters/dbt_adapter_factory.py +19 -0
- dataface/core/execute/adapters/dbt_utils.py +84 -20
- dataface/core/execute/adapters/postgres_connection_manager.py +48 -0
- dataface/core/execute/adapters/sql_adapter.py +72 -4
- dataface/core/execute/dbt_jinja.py +65 -10
- dataface/core/execute/executor.py +69 -16
- dataface/core/font_measure.py +28 -0
- dataface/core/fonts.py +8 -5
- dataface/core/inspect/__init__.py +46 -6
- dataface/core/project.py +13 -1
- dataface/core/render/chart/data_table_attachment.py +14 -12
- dataface/core/render/chart/emitters/_cartesian.py +21 -18
- dataface/core/render/chart/emitters/_channels.py +56 -8
- dataface/core/render/chart/emitters/_label_overlap.py +18 -15
- dataface/core/render/chart/emitters/_measured_label_padding.py +4 -4
- dataface/core/render/chart/emitters/area.py +3 -3
- dataface/core/render/chart/emitters/bar.py +60 -35
- dataface/core/render/chart/emitters/geo.py +8 -13
- dataface/core/render/chart/emitters/heatmap.py +20 -26
- dataface/core/render/chart/emitters/line.py +2 -2
- dataface/core/render/chart/emitters/scatter.py +7 -30
- dataface/core/render/chart/features/baseline.py +22 -9
- dataface/core/render/chart/features/endpoint_labels.py +55 -32
- dataface/core/render/chart/features/mirror_axis.py +12 -9
- dataface/core/render/chart/features/value_labels.py +1 -1
- dataface/core/render/chart/kpi.py +11 -90
- dataface/core/render/chart/presentation.py +6 -8
- dataface/core/render/chart/rendering.py +3 -12
- dataface/core/render/chart/session.py +10 -0
- dataface/core/render/chart/spec.py +3 -0
- dataface/core/render/chart/spec_builders.py +18 -18
- dataface/core/render/chart/table.py +81 -36
- dataface/core/render/chart/table_support.py +2 -53
- dataface/core/render/chart/time_unit_detect.py +3 -2
- dataface/core/render/chart/translate.py +82 -44
- dataface/core/render/chart/type_inference.py +45 -25
- dataface/core/render/chart/validation.py +91 -0
- dataface/core/render/chart/vega_lite.py +30 -11
- dataface/core/render/chart/vl_field_maps.py +111 -76
- dataface/core/render/converters/chart.py +16 -6
- dataface/core/render/faces.py +3 -2
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -93
- dataface/core/render/fonts/_font_face.css +10 -0
- dataface/core/render/layout_sizing.py +267 -238
- dataface/core/render/numeral_expr.py +80 -0
- dataface/core/render/warnings/bar_band_width_too_narrow.py +64 -6
- dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +25 -3
- dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +12 -6
- dataface/core/render/warnings/palette_unsupported.py +6 -0
- dataface/core/render/warnings/pie_dominant_segment.py +1 -0
- dataface/core/render/warnings/pie_too_many_segments.py +1 -0
- dataface/core/render/warnings/point_map_out_of_projection.py +1 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +1 -0
- dataface/core/render/warnings/redundant_encoding.py +20 -1
- dataface/core/render/warnings/registry.py +7 -3
- dataface/core/render/warnings/temporal_single_point.py +1 -0
- dataface/core/render/warnings/too_many_color_categories.py +1 -0
- dataface/core/render/warnings/too_many_x_categories.py +18 -2
- dataface/core/render/warnings/value_labels_crowd_width.py +9 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +1 -0
- dataface/core/render_format.py +18 -0
- dataface/core/text/format_d3.py +56 -0
- dataface/core/text/numeral_scale.py +218 -0
- dataface/core/utils.py +103 -10
- dataface/data/schemas/board-resolved/board-resolved.schema.json +9487 -7978
- dataface/data/schemas/yaml/0.3.2.json +1 -0
- dataface/data/schemas/yaml/manifest.json +7 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/METADATA +1 -1
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/RECORD +185 -177
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/WHEEL +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/entry_points.txt +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/licenses/LICENSE +0 -0
dataface/cli/commands/render.py
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
"""Render command implementation."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import json as _json
|
|
4
6
|
import sys
|
|
5
7
|
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
6
9
|
|
|
7
10
|
import typer
|
|
8
11
|
|
|
9
|
-
from dataface.agent_api import Diagnostic,
|
|
10
|
-
from dataface.agent_api._paths import (
|
|
11
|
-
build_face_render_context,
|
|
12
|
-
build_yaml_render_context,
|
|
13
|
-
resolve_face_or_error,
|
|
14
|
-
)
|
|
15
|
-
from dataface.agent_api.cache import project_cache_ctx
|
|
16
|
-
from dataface.agent_api.dashboards import RenderedDashboard
|
|
17
|
-
from dataface.agent_api.diagnostics import REGISTRY as _diag_registry
|
|
12
|
+
from dataface.agent_api import Diagnostic, RenderFormat
|
|
18
13
|
from dataface.cli._error_format import print_diagnostics
|
|
19
14
|
from dataface.cli._project import with_project
|
|
20
15
|
from dataface.cli.filesystem_project import FilesystemProject
|
|
21
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from dataface.agent_api.dashboards import RenderedDashboard
|
|
19
|
+
|
|
22
20
|
# Maps common file extensions to render formats for format inference.
|
|
23
21
|
_EXT_TO_FORMAT: dict[str, RenderFormat] = {
|
|
24
22
|
"svg": "svg",
|
|
@@ -64,6 +62,8 @@ def _validate_ignore_codes(ignore_codes: set[str]) -> None:
|
|
|
64
62
|
at error level (suppressing an error is never valid) versus a code that
|
|
65
63
|
isn't registered at all (a typo or stale suppression stub).
|
|
66
64
|
"""
|
|
65
|
+
from dataface.agent_api.diagnostics import REGISTRY as _diag_registry
|
|
66
|
+
|
|
67
67
|
warning_codes = _diag_registry.codes(level="warning")
|
|
68
68
|
error_codes = _diag_registry.codes(level="error")
|
|
69
69
|
for code in sorted(ignore_codes):
|
|
@@ -204,6 +204,13 @@ def render_command(
|
|
|
204
204
|
Returns:
|
|
205
205
|
Output file path on success, None for terminal/stdout output
|
|
206
206
|
"""
|
|
207
|
+
from dataface.agent_api import ProjectSession
|
|
208
|
+
from dataface.agent_api._paths import (
|
|
209
|
+
build_face_render_context,
|
|
210
|
+
resolve_face_or_error,
|
|
211
|
+
)
|
|
212
|
+
from dataface.agent_api.cache import project_cache_ctx
|
|
213
|
+
|
|
207
214
|
if ignore_codes:
|
|
208
215
|
_validate_ignore_codes(ignore_codes)
|
|
209
216
|
|
|
@@ -296,6 +303,10 @@ def render_command_from_yaml(
|
|
|
296
303
|
Output file path on success, None for terminal/stdout output. Exits 1
|
|
297
304
|
(typer.Exit) after reporting on a validation/chart failure.
|
|
298
305
|
"""
|
|
306
|
+
from dataface.agent_api import InMemoryFace, ProjectSession
|
|
307
|
+
from dataface.agent_api._paths import build_yaml_render_context
|
|
308
|
+
from dataface.agent_api.cache import project_cache_ctx
|
|
309
|
+
|
|
299
310
|
if ignore_codes:
|
|
300
311
|
_validate_ignore_codes(ignore_codes)
|
|
301
312
|
|
dataface/cli/commands/search.py
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
5
7
|
import typer
|
|
6
8
|
|
|
7
|
-
from dataface.agent_api import Project
|
|
8
|
-
from dataface.agent_api.search import SearchResult
|
|
9
|
+
from dataface.agent_api import Project
|
|
9
10
|
from dataface.cli._error_format import print_diagnostics
|
|
10
11
|
from dataface.cli._project import with_project
|
|
11
12
|
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from dataface.agent_api.search import SearchResult
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
@with_project
|
|
14
18
|
def search_command(
|
|
@@ -19,6 +23,8 @@ def search_command(
|
|
|
19
23
|
limit: int = 10,
|
|
20
24
|
) -> None:
|
|
21
25
|
"""Search dashboards by keyword with ranked results."""
|
|
26
|
+
from dataface.agent_api import ProjectSession
|
|
27
|
+
|
|
22
28
|
with ProjectSession.from_project(project) as project_session:
|
|
23
29
|
result = project_session.search_dashboards(query, limit=limit)
|
|
24
30
|
|
dataface/cli/commands/serve.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
5
|
import sys
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import TYPE_CHECKING
|
|
@@ -10,15 +9,10 @@ from typing import TYPE_CHECKING
|
|
|
10
9
|
import typer
|
|
11
10
|
import uvicorn
|
|
12
11
|
|
|
12
|
+
from dataface.agent_api import Diagnostic
|
|
13
13
|
from dataface.cli._error_format import print_diagnostics
|
|
14
14
|
from dataface.cli._project import with_project
|
|
15
15
|
|
|
16
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
17
|
-
from dataface.core.diagnostics import ERR_STARTUP_FAILED
|
|
18
|
-
|
|
19
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
20
|
-
from dataface.core.diagnostics.base import DatafaceError
|
|
21
|
-
|
|
22
16
|
if TYPE_CHECKING:
|
|
23
17
|
from dataface.cli.filesystem_project import FilesystemProject
|
|
24
18
|
|
|
@@ -68,86 +62,43 @@ def serve_command(
|
|
|
68
62
|
None (exits with code 0 on success, 1 on errors)
|
|
69
63
|
"""
|
|
70
64
|
from dataface.agent_api import set_surface
|
|
71
|
-
|
|
72
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
73
|
-
from dataface.core.compile.config import list_built_in_themes, load_config
|
|
74
|
-
|
|
75
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
76
|
-
from dataface.core.diagnostics import ERR_INVALID_DEFAULT_THEME
|
|
77
|
-
|
|
78
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
79
|
-
from dataface.core.project_roots import infer_dialect_from_dbt
|
|
80
|
-
|
|
81
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
82
|
-
from dataface.core.serve.port import resolve_port
|
|
83
|
-
|
|
84
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
85
|
-
from dataface.core.serve.server import create_server
|
|
65
|
+
from dataface.agent_api.serve import format_startup_failure, prepare_serve
|
|
86
66
|
|
|
87
67
|
# Narrows the root callback's "cli": a served dashboard's queries are a
|
|
88
68
|
# different cost story from a one-shot `dft render`.
|
|
89
69
|
set_surface("serve")
|
|
90
70
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# here ensures the user sees a clear error at startup rather than on first render.
|
|
96
|
-
env_theme = os.environ.get(
|
|
97
|
-
"DFT_DEFAULT_THEME"
|
|
98
|
-
) # noqa: TID251 — DFT_DEFAULT_THEME knob
|
|
99
|
-
# Empty string is treated as unset (matches get_default_theme_name, which
|
|
100
|
-
# falls back to the shipped default) — only a non-empty unknown name errors.
|
|
101
|
-
if env_theme:
|
|
102
|
-
available = list_built_in_themes()
|
|
103
|
-
if env_theme not in available:
|
|
104
|
-
err = DatafaceError.from_code(
|
|
105
|
-
ERR_INVALID_DEFAULT_THEME,
|
|
106
|
-
source="DFT_DEFAULT_THEME env var",
|
|
107
|
-
theme=env_theme,
|
|
108
|
-
available=available,
|
|
109
|
-
)
|
|
110
|
-
print_diagnostics([err.to_diagnostic()])
|
|
111
|
-
raise typer.Exit(1) from None
|
|
112
|
-
|
|
113
|
-
# Validate dataface.yml wholesale: stray keys (style:, board:, theme:, etc.) are a
|
|
114
|
-
# hard error here at startup rather than silently ignored.
|
|
71
|
+
# Validate DFT_DEFAULT_THEME/dataface.yml, resolve port/dialect, and build the
|
|
72
|
+
# ASGI app — a dataface.yml validation failure raises pydantic's ValidationError
|
|
73
|
+
# uncaught (matches pre-existing behavior: a plain echo, not the Diagnostic
|
|
74
|
+
# envelope used everywhere else in this command).
|
|
115
75
|
from pydantic import ValidationError as _PydanticError
|
|
116
76
|
|
|
117
77
|
try:
|
|
118
|
-
|
|
78
|
+
setup = prepare_serve(
|
|
79
|
+
project,
|
|
80
|
+
port=port,
|
|
81
|
+
host=host,
|
|
82
|
+
dialect=dialect,
|
|
83
|
+
target=target,
|
|
84
|
+
max_workers=max_workers,
|
|
85
|
+
no_cache=no_cache,
|
|
86
|
+
cache_path=cache_path,
|
|
87
|
+
allow_external_access=allow_external_access,
|
|
88
|
+
)
|
|
119
89
|
except _PydanticError as e:
|
|
120
90
|
typer.echo(f"dataface.yml validation error: {e}", err=True)
|
|
121
91
|
raise typer.Exit(1) from None
|
|
122
92
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
project_dir=project_dir,
|
|
127
|
-
host=host,
|
|
128
|
-
)
|
|
93
|
+
if isinstance(setup, Diagnostic):
|
|
94
|
+
print_diagnostics([setup])
|
|
95
|
+
raise typer.Exit(1) from None
|
|
129
96
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if effective_dialect is None:
|
|
136
|
-
inferred = infer_dialect_from_dbt(project_dir, effective_target)
|
|
137
|
-
if inferred:
|
|
138
|
-
typer.echo(f" Auto-detected dialect: {inferred} (from dbt profile)")
|
|
139
|
-
effective_dialect = inferred or "duckdb"
|
|
140
|
-
|
|
141
|
-
# Create FastAPI app
|
|
142
|
-
app = create_server(
|
|
143
|
-
project,
|
|
144
|
-
dialect=effective_dialect,
|
|
145
|
-
target=effective_target,
|
|
146
|
-
max_workers=max_workers,
|
|
147
|
-
no_cache=no_cache,
|
|
148
|
-
cache_path=cache_path,
|
|
149
|
-
allow_external_access_in_readonly=allow_external_access,
|
|
150
|
-
)
|
|
97
|
+
if setup.dialect_inferred:
|
|
98
|
+
typer.echo(f" Auto-detected dialect: {setup.dialect} (from dbt profile)")
|
|
99
|
+
|
|
100
|
+
port = setup.port
|
|
101
|
+
app = setup.app
|
|
151
102
|
|
|
152
103
|
# Start server
|
|
153
104
|
typer.echo("🚀 Starting dashboard server")
|
|
@@ -175,6 +126,5 @@ def serve_command(
|
|
|
175
126
|
typer.echo("\n👋 Server stopped")
|
|
176
127
|
sys.exit(0)
|
|
177
128
|
except Exception as e: # noqa: BLE001 — uvicorn surface
|
|
178
|
-
|
|
179
|
-
print_diagnostics([err.to_diagnostic()])
|
|
129
|
+
print_diagnostics([format_startup_failure(e)])
|
|
180
130
|
raise typer.Exit(1) from None
|
|
@@ -7,28 +7,11 @@ from pathlib import Path
|
|
|
7
7
|
import typer
|
|
8
8
|
|
|
9
9
|
from dataface.agent_api import skill_install
|
|
10
|
-
|
|
11
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
12
|
-
from dataface.core.project_roots import find_dft_root, find_repo_root
|
|
10
|
+
from dataface.cli._project import resolve_skill_install_root
|
|
13
11
|
|
|
14
12
|
_VALID_TARGETS = skill_install.SKILL_INSTALL_TARGETS
|
|
15
13
|
|
|
16
14
|
|
|
17
|
-
def _resolve_project_dir(start: Path) -> Path:
|
|
18
|
-
"""Return the directory where skills should be installed.
|
|
19
|
-
|
|
20
|
-
Skills are agent tooling that belong at the git root, not the operational
|
|
21
|
-
project root. When a .git directory exists above the dataface.yml, install
|
|
22
|
-
at the git root so monorepos with nested dataface projects don't accumulate
|
|
23
|
-
divergent skill copies.
|
|
24
|
-
"""
|
|
25
|
-
project_root = find_dft_root(start) or start.resolve()
|
|
26
|
-
git_root = find_repo_root(project_root)
|
|
27
|
-
if git_root is not None:
|
|
28
|
-
return git_root
|
|
29
|
-
return project_root
|
|
30
|
-
|
|
31
|
-
|
|
32
15
|
def _resolve_targets(
|
|
33
16
|
target: str | None,
|
|
34
17
|
*,
|
|
@@ -110,7 +93,7 @@ def run_init_skills(
|
|
|
110
93
|
typer.echo("Specify either --all or --dir, not both.", err=True)
|
|
111
94
|
raise typer.Exit(1)
|
|
112
95
|
|
|
113
|
-
project_root =
|
|
96
|
+
project_root = resolve_skill_install_root(
|
|
114
97
|
project_dir if project_dir is not None else Path.cwd()
|
|
115
98
|
)
|
|
116
99
|
target_dirs = _resolve_targets(
|
|
@@ -4,15 +4,18 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
import typer
|
|
9
10
|
|
|
10
|
-
from dataface.agent_api import Project
|
|
11
|
-
from dataface.agent_api.validate import ValidateResult
|
|
11
|
+
from dataface.agent_api import Project
|
|
12
12
|
from dataface.cli._console import dft_console
|
|
13
13
|
from dataface.cli._error_format import print_diagnostics
|
|
14
14
|
from dataface.cli._project import with_project
|
|
15
15
|
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from dataface.agent_api.validate import ValidateResult
|
|
18
|
+
|
|
16
19
|
_console = dft_console()
|
|
17
20
|
|
|
18
21
|
|
|
@@ -24,6 +27,8 @@ def validate_command(
|
|
|
24
27
|
json_output: bool = False,
|
|
25
28
|
strict: bool = False,
|
|
26
29
|
) -> None:
|
|
30
|
+
from dataface.agent_api import ProjectSession
|
|
31
|
+
|
|
27
32
|
with ProjectSession.from_project(project) as project_session:
|
|
28
33
|
results = project_session.validate_paths(paths)
|
|
29
34
|
_emit(results, json_output=json_output, strict=strict)
|
|
@@ -30,6 +30,7 @@ from dataface.core.project import (
|
|
|
30
30
|
assert_relpath,
|
|
31
31
|
glob_literal_prefix,
|
|
32
32
|
glob_to_regex,
|
|
33
|
+
posix_relpath,
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
if TYPE_CHECKING:
|
|
@@ -143,7 +144,7 @@ class FilesystemProject(Project):
|
|
|
143
144
|
# First try: fully resolved path. Handles OS-level symlinks (/var on macOS).
|
|
144
145
|
resolved = path.resolve()
|
|
145
146
|
try:
|
|
146
|
-
return
|
|
147
|
+
return posix_relpath(resolved, self.root)
|
|
147
148
|
except ValueError:
|
|
148
149
|
pass
|
|
149
150
|
|
|
@@ -151,7 +152,7 @@ class FilesystemProject(Project):
|
|
|
151
152
|
# paths through project-internal symlinks (e.g. faces/tasks/ -> ../../tasks/).
|
|
152
153
|
normalized = Path(os.path.normpath(path.absolute()))
|
|
153
154
|
try:
|
|
154
|
-
return
|
|
155
|
+
return posix_relpath(normalized, self.root)
|
|
155
156
|
except ValueError:
|
|
156
157
|
raise ValueError(
|
|
157
158
|
f"Path {path!r} is outside project root {self.root!r}"
|
|
@@ -218,12 +219,12 @@ class FilesystemProject(Project):
|
|
|
218
219
|
dirs[:] = sorted(d for d in dirs if d not in SKIP_SCAN_DIRS)
|
|
219
220
|
root_path = Path(root_str)
|
|
220
221
|
for name in files:
|
|
221
|
-
candidates.append(
|
|
222
|
+
candidates.append(posix_relpath(root_path / name, self.root))
|
|
222
223
|
yield from sorted(candidates)
|
|
223
224
|
else:
|
|
224
225
|
for p in sorted(subtree.iterdir()):
|
|
225
226
|
if p.is_file():
|
|
226
|
-
yield
|
|
227
|
+
yield posix_relpath(p, self.root)
|
|
227
228
|
|
|
228
229
|
def iter_dir(self, under: str) -> Iterator[ProjectPath | ProjectDirectory]:
|
|
229
230
|
"""``os.scandir`` over *under*, filtered/sorted per ``Project.iter_dir``.
|
dataface/cli/main.py
CHANGED
|
@@ -16,6 +16,7 @@ from dataface.agent_api import RenderFormat, set_surface
|
|
|
16
16
|
from dataface.cli._console import is_plain_output
|
|
17
17
|
from dataface.cli._extras import require_extras
|
|
18
18
|
from dataface.cli._parsing import parse_kv_pairs
|
|
19
|
+
from dataface.cli._project import has_faces_marker
|
|
19
20
|
from dataface.cli.commands import (
|
|
20
21
|
board_artifact as board_artifact_cmd,
|
|
21
22
|
describe as describe_cmd,
|
|
@@ -35,9 +36,6 @@ from dataface.cli.commands import (
|
|
|
35
36
|
from dataface.cli.commands.mcp_init import run_init as _mcp_run_init
|
|
36
37
|
from dataface.cli.commands.skills_init import run_init_skills as _skills_run_init
|
|
37
38
|
|
|
38
|
-
# tach-ignore(cli->core debt: route via agent_api; burn-down task)
|
|
39
|
-
from dataface.core.project_roots import find_dft_root
|
|
40
|
-
|
|
41
39
|
# Evaluated once at process startup — mid-session env changes won't take effect.
|
|
42
40
|
_RICH_MARKUP_MODE: Literal["rich"] | None = None if is_plain_output() else "rich"
|
|
43
41
|
# Probe for private package once at startup; avoids repeated find_spec calls.
|
|
@@ -107,21 +105,6 @@ if not logging.getLogger().handlers:
|
|
|
107
105
|
)
|
|
108
106
|
|
|
109
107
|
|
|
110
|
-
def _has_faces_marker(start: Path) -> bool:
|
|
111
|
-
"""True iff *start* sits inside a Dataface project that has a ``faces/`` directory.
|
|
112
|
-
|
|
113
|
-
Anchors on :func:`find_dft_root` so the walk-up stops at the
|
|
114
|
-
project root (any of ``dataface.yml``/``dataface.yaml``/``dbt_project.yml``)
|
|
115
|
-
rather than blindly hunting upward for stray ``faces/`` directories
|
|
116
|
-
elsewhere on the filesystem. A bare-dbt repo with no scaffolded
|
|
117
|
-
``faces/`` returns False — that's the case the banner exists for.
|
|
118
|
-
"""
|
|
119
|
-
if (start / "faces").is_dir():
|
|
120
|
-
return True
|
|
121
|
-
project = find_dft_root(start)
|
|
122
|
-
return project is not None and (project / "faces").is_dir()
|
|
123
|
-
|
|
124
|
-
|
|
125
108
|
def _render_init_banner() -> None:
|
|
126
109
|
"""Print a nudge to run ``dft init``.
|
|
127
110
|
|
|
@@ -203,7 +186,7 @@ class _RootHelpGroup(TyperGroup):
|
|
|
203
186
|
)
|
|
204
187
|
|
|
205
188
|
def format_help(self, ctx: Any, formatter: Any) -> None:
|
|
206
|
-
if not
|
|
189
|
+
if not has_faces_marker(Path.cwd()):
|
|
207
190
|
_render_init_banner()
|
|
208
191
|
super().format_help(ctx, formatter)
|
|
209
192
|
|
dataface/core/__init__.py
CHANGED
|
@@ -13,8 +13,10 @@ Submodules:
|
|
|
13
13
|
|
|
14
14
|
High-Level API:
|
|
15
15
|
>>> from pathlib import Path
|
|
16
|
-
>>> from dataface.core import compile
|
|
16
|
+
>>> from dataface.core.compile import compile
|
|
17
|
+
>>> from dataface.core.execute import Executor
|
|
17
18
|
>>> from dataface.core.execute.adapters import build_adapter_registry
|
|
19
|
+
>>> from dataface.core.render import render
|
|
18
20
|
>>> result = compile(yaml_content)
|
|
19
21
|
>>> from dataface.cli.filesystem_project import FilesystemProject
|
|
20
22
|
>>> registry = build_adapter_registry(FilesystemProject(Path.cwd()))
|
|
@@ -22,37 +24,45 @@ High-Level API:
|
|
|
22
24
|
>>> html = render(result.face, executor, format="html")
|
|
23
25
|
"""
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
# Re-export validate module
|
|
27
|
-
from dataface.core import validate
|
|
28
|
-
from dataface.core.compile import (
|
|
29
|
-
AnyQuery,
|
|
30
|
-
AuthoredFace,
|
|
31
|
-
CompilationError,
|
|
32
|
-
CompileResult,
|
|
33
|
-
Face,
|
|
34
|
-
Layout,
|
|
35
|
-
LayoutItem,
|
|
36
|
-
Variable,
|
|
37
|
-
compile,
|
|
38
|
-
compile_file,
|
|
39
|
-
)
|
|
27
|
+
from __future__ import annotations
|
|
40
28
|
|
|
41
|
-
|
|
42
|
-
from
|
|
43
|
-
ExecutionError,
|
|
44
|
-
Executor,
|
|
45
|
-
)
|
|
29
|
+
import importlib
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
render
|
|
51
|
-
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
# Import-time-only: PEP 562 __getattr__ below resolves these lazily at
|
|
34
|
+
# runtime, so `import dataface.core.<anything>` (this file runs first,
|
|
35
|
+
# always) doesn't eagerly pull in the whole compile/execute/render stack
|
|
36
|
+
# just for this convenience re-export.
|
|
37
|
+
from dataface.core import validate as validate
|
|
38
|
+
from dataface.core.compile import (
|
|
39
|
+
AnyQuery as AnyQuery,
|
|
40
|
+
AuthoredFace as AuthoredFace,
|
|
41
|
+
CompilationError as CompilationError,
|
|
42
|
+
CompileResult as CompileResult,
|
|
43
|
+
Face as Face,
|
|
44
|
+
Layout as Layout,
|
|
45
|
+
LayoutItem as LayoutItem,
|
|
46
|
+
Variable as Variable,
|
|
47
|
+
compile_file as compile_file,
|
|
48
|
+
)
|
|
49
|
+
from dataface.core.execute import (
|
|
50
|
+
ExecutionError as ExecutionError,
|
|
51
|
+
Executor as Executor,
|
|
52
|
+
)
|
|
53
|
+
from dataface.core.render import RenderError as RenderError
|
|
52
54
|
|
|
55
|
+
# `compile` and `render` are deliberately NOT re-exported here: both names
|
|
56
|
+
# collide with a same-named submodule one level down (`dataface.core.compile`,
|
|
57
|
+
# `dataface.core.render`). Importing that submodule for *any* reason binds it
|
|
58
|
+
# onto this package under that exact name as a side effect of Python's own
|
|
59
|
+
# import machinery — a rebind PEP 562 __getattr__ cannot see or prevent — so a
|
|
60
|
+
# lazy alias sharing a submodule's leaf name silently flips from function to
|
|
61
|
+
# module depending on import order. Import the function directly from its
|
|
62
|
+
# submodule instead: `from dataface.core.compile import compile`,
|
|
63
|
+
# `from dataface.core.render import render`.
|
|
53
64
|
__all__ = [
|
|
54
65
|
# Compile
|
|
55
|
-
"compile",
|
|
56
66
|
"compile_file",
|
|
57
67
|
"CompileResult",
|
|
58
68
|
"Face",
|
|
@@ -66,8 +76,43 @@ __all__ = [
|
|
|
66
76
|
"Executor",
|
|
67
77
|
"ExecutionError",
|
|
68
78
|
# Render
|
|
69
|
-
"render",
|
|
70
79
|
"RenderError",
|
|
71
80
|
# Validate
|
|
72
81
|
"validate",
|
|
73
82
|
]
|
|
83
|
+
|
|
84
|
+
_LAZY_ATTRS: dict[str, tuple[str, str]] = {
|
|
85
|
+
"validate": ("dataface.core", "validate"),
|
|
86
|
+
"AnyQuery": ("dataface.core.compile", "AnyQuery"),
|
|
87
|
+
"AuthoredFace": ("dataface.core.compile", "AuthoredFace"),
|
|
88
|
+
"CompilationError": ("dataface.core.compile", "CompilationError"),
|
|
89
|
+
"CompileResult": ("dataface.core.compile", "CompileResult"),
|
|
90
|
+
"Face": ("dataface.core.compile", "Face"),
|
|
91
|
+
"Layout": ("dataface.core.compile", "Layout"),
|
|
92
|
+
"LayoutItem": ("dataface.core.compile", "LayoutItem"),
|
|
93
|
+
"Variable": ("dataface.core.compile", "Variable"),
|
|
94
|
+
"compile_file": ("dataface.core.compile", "compile_file"),
|
|
95
|
+
"ExecutionError": ("dataface.core.execute", "ExecutionError"),
|
|
96
|
+
"Executor": ("dataface.core.execute", "Executor"),
|
|
97
|
+
"RenderError": ("dataface.core.render", "RenderError"),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def __getattr__(name: str) -> Any:
|
|
102
|
+
try:
|
|
103
|
+
module_path, attr_name = _LAZY_ATTRS[name]
|
|
104
|
+
except KeyError:
|
|
105
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") from None
|
|
106
|
+
if module_path == __name__ and attr_name == name:
|
|
107
|
+
# "validate" is a submodule of this very package, not another
|
|
108
|
+
# module's attribute — import it directly to avoid recursing back
|
|
109
|
+
# into this __getattr__.
|
|
110
|
+
module = importlib.import_module(f"{__name__}.{attr_name}")
|
|
111
|
+
else:
|
|
112
|
+
module = getattr(importlib.import_module(module_path), attr_name)
|
|
113
|
+
globals()[name] = module # cache: repeated access is O(1) and `is` identity holds
|
|
114
|
+
return module
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def __dir__() -> list[str]:
|
|
118
|
+
return sorted(set(globals()) | set(_LAZY_ATTRS))
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""CSS/SVG color-string validation.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lives in core (below compile/execute/render) so both compile (pie-attachment
|
|
4
|
+
table style resolution) and render (KPI/table SVG emission) can validate a
|
|
5
|
+
foreign color string without either reaching into the other's layer.
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
8
|
from __future__ import annotations
|
|
@@ -8,11 +10,24 @@ from __future__ import annotations
|
|
|
8
10
|
import re
|
|
9
11
|
from typing import overload
|
|
10
12
|
|
|
11
|
-
from dataface.core.
|
|
13
|
+
from dataface.core.diagnostics.base import DatafaceError
|
|
12
14
|
|
|
13
15
|
_CSS_HEX_COLOR_PATTERN = re.compile(r"^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
class InvalidColorError(DatafaceError):
|
|
19
|
+
"""A color string is neither a valid hex color nor a recognized CSS keyword."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str):
|
|
22
|
+
self.message = message
|
|
23
|
+
self.fields = {}
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
if self.code is None:
|
|
26
|
+
from dataface.core.diagnostics.codes_unknown import ERR_INTERNAL
|
|
27
|
+
|
|
28
|
+
self.code = ERR_INTERNAL
|
|
29
|
+
|
|
30
|
+
|
|
16
31
|
def is_sanitizable_color(color: str) -> bool:
|
|
17
32
|
"""Return True if ``color`` is accepted by sanitize_color (hex or transparent/none)."""
|
|
18
33
|
return bool(_CSS_HEX_COLOR_PATTERN.match(color)) or color.lower() in {
|
|
@@ -39,7 +54,7 @@ def sanitize_color(color: str | None, fallback: str | None = None) -> str | None
|
|
|
39
54
|
inherited fill).
|
|
40
55
|
|
|
41
56
|
``fallback`` is returned when ``color`` is None or empty. A non-None
|
|
42
|
-
``color`` that is not a valid hex or keyword raises ``
|
|
57
|
+
``color`` that is not a valid hex or keyword raises ``InvalidColorError``
|
|
43
58
|
immediately — authored colors that are not valid are a configuration error,
|
|
44
59
|
not a case for silent defaulting.
|
|
45
60
|
"""
|
|
@@ -49,4 +64,4 @@ def sanitize_color(color: str | None, fallback: str | None = None) -> str | None
|
|
|
49
64
|
return color
|
|
50
65
|
if color.lower() in {"transparent", "none"}:
|
|
51
66
|
return "transparent"
|
|
52
|
-
raise
|
|
67
|
+
raise InvalidColorError(f"Invalid color value: {color!r}")
|