dataface 0.1.5.dev237__py3-none-any.whl → 0.1.5.dev322__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/DATAFACE_SYNTAX.md +25 -33
- dataface/__init__.py +2 -1
- dataface/agent_api/__init__.py +4 -4
- dataface/agent_api/_init_templates/guide.yaml +6 -2
- dataface/agent_api/dashboards.py +1 -8
- dataface/agent_api/{entity_paths.py → data_paths.py} +60 -56
- dataface/agent_api/docs/yaml-reference.md +52 -161
- dataface/agent_api/pack.py +45 -3
- dataface/agent_api/project.py +175 -5
- dataface/agent_api/render_face.py +17 -18
- dataface/agent_api/schema_hints.py +3 -0
- dataface/agent_api/search.py +32 -1
- dataface/agent_api/validate.py +5 -7
- dataface/ai/skills/dashboard-pack-scaffolding/SKILL.md +98 -10
- dataface/ai/skills/kpi-row/SKILL.md +3 -1
- dataface/ai/skills/kpi-row/examples/kpi-row.yml +6 -2
- dataface/ai/skills/single-metric-bignum/SKILL.md +3 -1
- dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +3 -1
- dataface/ai/tools/__init__.py +4 -3
- dataface/cli/commands/query.py +22 -22
- dataface/cli/commands/schema.py +50 -48
- dataface/cli/commands/search.py +3 -3
- dataface/cli/main.py +5 -5
- dataface/core/__init__.py +2 -1
- dataface/core/compile/authoring_warnings.py +11 -3
- dataface/core/compile/colors.py +20 -4
- dataface/core/compile/compiler.py +5 -1
- dataface/core/compile/config.py +11 -12
- dataface/core/compile/filter_injection.py +6 -1
- dataface/core/compile/inherit_graph.py +186 -0
- dataface/core/compile/inherit_registry.yaml +1 -0
- dataface/core/compile/introspection.py +20 -6
- dataface/core/compile/jinja.py +8 -54
- dataface/core/compile/meta.py +6 -11
- dataface/core/compile/models/chart/authored.py +48 -84
- dataface/core/compile/models/chart/resolved.py +1 -1
- dataface/core/compile/models/config.py +4 -0
- dataface/core/compile/models/markers.py +36 -0
- dataface/core/compile/models/query/normalized.py +13 -5
- dataface/core/compile/models/source.py +1 -1
- dataface/core/compile/models/style/authored.py +1 -7
- dataface/core/compile/models/style/resolved.py +24 -27
- dataface/core/compile/models/style/theme.py +38 -80
- dataface/core/compile/models/variable/authored.py +7 -7
- dataface/core/compile/normalize_charts.py +13 -58
- dataface/core/compile/normalize_layout.py +4 -4
- dataface/core/compile/normalize_queries.py +4 -2
- dataface/core/compile/normalize_variables.py +5 -5
- dataface/core/compile/normalizer.py +8 -8
- dataface/core/compile/sizing.py +145 -81
- dataface/core/compile/style_cascade.py +7 -7
- dataface/core/compile/typography.py +18 -5
- dataface/core/compile/validator.py +3 -3
- dataface/core/compile/yaml_error_formatter.py +0 -13
- dataface/core/dashboard.py +1 -1
- dataface/core/defaults/default_config.yml +5 -0
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +1 -1
- dataface/core/defaults/palettes/tone/info.yml +21 -0
- dataface/core/defaults/themes/_base.yaml +14 -11
- dataface/core/defaults/themes/carbong100.yaml +16 -1
- dataface/core/defaults/themes/editorial.yaml +3 -3
- dataface/core/errors/__init__.py +6 -0
- dataface/core/errors/codes_search.py +36 -0
- dataface/core/execute/adapters/adapter_registry.py +19 -12
- dataface/core/execute/adapters/dbt_adapter.py +5 -6
- dataface/core/execute/adapters/schema_adapter.py +15 -5
- dataface/core/execute/adapters/sql_adapter.py +1 -1
- dataface/core/execute/duckdb_cache.py +11 -6
- dataface/core/execute/executor.py +2 -1
- dataface/core/execute/sql_guard.py +3 -11
- dataface/core/inspect/db_types.py +29 -18
- dataface/core/inspect/renderer.py +7 -2
- dataface/core/inspect/templates/categorical_column.yml +9 -3
- dataface/core/inspect/templates/date_column.yml +9 -3
- dataface/core/inspect/templates/numeric_column.yml +18 -6
- dataface/core/inspect/templates/string_column.yml +18 -6
- dataface/core/pack/models.py +14 -0
- dataface/core/pack/planner.py +43 -13
- dataface/core/project_roots.py +2 -2
- dataface/core/registered_views/data_urls.py +31 -0
- dataface/core/{system_views → registered_views}/expander.py +37 -28
- dataface/core/{system_views → registered_views}/loader.py +2 -3
- dataface/core/{system_views → registered_views}/models.py +10 -26
- dataface/core/{system_views → registered_views}/query_runner.py +11 -12
- dataface/core/{system_views → registered_views}/registry.yaml +20 -26
- dataface/core/registered_views/render_pipeline.py +221 -0
- dataface/core/{system_views → registered_views}/router.py +4 -5
- dataface/core/registered_views/templates/data/root.yaml +19 -0
- dataface/core/{system_views/templates/entity → registered_views/templates/data}/schema-index.yaml +1 -1
- dataface/core/{system_views/templates/entity → registered_views/templates/data}/source-index.yaml +1 -1
- dataface/core/{system_views/templates/entity → registered_views/templates/data}/table-index.yaml +1 -1
- dataface/core/{system_views → registered_views}/variable_planner.py +37 -121
- dataface/core/render/chart/callout.py +39 -41
- dataface/core/render/chart/decisions.py +11 -1
- dataface/core/render/chart/geo.py +30 -101
- dataface/core/render/chart/kpi.py +4 -3
- dataface/core/render/chart/pipeline.py +16 -18
- dataface/core/render/chart/profile.py +49 -20
- dataface/core/render/chart/rendering.py +4 -14
- dataface/core/render/chart/spark_bar.py +4 -6
- dataface/core/render/chart/standard_renderer.py +5 -5
- dataface/core/render/chart/table.py +38 -12
- dataface/core/render/chart/vl_field_maps.py +4 -4
- dataface/core/render/chart_interactivity.py +0 -1
- dataface/core/render/converters/chart.py +5 -3
- dataface/core/render/converters/html.py +20 -44
- dataface/core/render/dir_context.py +136 -44
- dataface/core/render/face_to_dict.py +109 -0
- dataface/core/render/faces.py +56 -56
- dataface/core/render/geo_defaults.yml +40 -0
- dataface/core/render/json_format.py +3 -98
- dataface/core/render/layout_sizing.py +4 -1
- dataface/core/render/layouts.py +6 -6
- dataface/core/render/nav.py +73 -162
- dataface/core/render/placeholder.py +17 -11
- dataface/core/render/renderer.py +20 -7
- dataface/core/render/svg_utils.py +4 -15
- dataface/core/render/templates/nav/nav-fragment.html +3 -0
- dataface/core/render/templates/nav/nav.css +29 -0
- dataface/core/render/templates/nav/nav.html +1 -0
- dataface/core/render/templates/nav/nav.js +15 -0
- dataface/core/render/templates/page.css +22 -0
- dataface/core/render/templates/page.html +19 -0
- dataface/core/render/terminal.py +31 -11
- dataface/core/render/terminal_layouts.py +44 -4
- dataface/core/render/text_format.py +2 -3
- dataface/core/render/variable_controls.py +18 -19
- dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +29 -2
- dataface/core/render/yaml_format.py +2 -3
- dataface/core/ruff.toml +10 -0
- dataface/core/scoped_paths.py +2 -1
- dataface/core/serve/alias_index.py +5 -5
- dataface/core/serve/bootstrap.py +1 -1
- dataface/core/serve/port.py +1 -1
- dataface/core/serve/server.py +88 -181
- dataface/integrations/markdown.py +26 -47
- {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/METADATA +1 -1
- {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/RECORD +161 -147
- mdsvg/renderer.py +6 -1
- mdsvg/style.py +3 -0
- dataface/core/serve/templates/nav.yml +0 -12
- /dataface/core/{system_views → registered_views}/__init__.py +0 -0
- /dataface/core/{system_views/templates/entity → registered_views/templates/data}/table-detail.yaml +0 -0
- /dataface/core/{system_views → registered_views}/templates/inspector/column.yaml +0 -0
- /dataface/core/{system_views → registered_views}/templates/inspector/schema.yaml +0 -0
- /dataface/core/{system_views → registered_views}/templates/inspector/source.yaml +0 -0
- /dataface/core/{system_views → registered_views}/templates/inspector/table.yaml +0 -0
- {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/WHEEL +0 -0
- {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/licenses/LICENSE +0 -0
dataface/DATAFACE_SYNTAX.md
CHANGED
|
@@ -19,7 +19,7 @@ dft validate faces/my_dashboard.yml
|
|
|
19
19
|
To verify that your database connection works, run a simple query:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
dft query
|
|
22
|
+
dft query 'SELECT 1' --source <your_source>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
A successful result means your data source is reachable.
|
|
@@ -138,7 +138,7 @@ variables:
|
|
|
138
138
|
region:
|
|
139
139
|
input: select # See `dft docs variables` for all 14 input types
|
|
140
140
|
options: { static: [US, EU, APAC] }
|
|
141
|
-
|
|
141
|
+
default: US
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
Reference variables inside queries with bare `{{ region }}` — no `variables.` prefix.
|
|
@@ -206,20 +206,15 @@ chart_focus: revenue_trend # Render only one chart with its dependent variab
|
|
|
206
206
|
details: "Click to expand" # Collapsible section
|
|
207
207
|
expanded_title: "Hide details"
|
|
208
208
|
expanded: false
|
|
209
|
-
|
|
210
|
-
aliases: # Additional URLs that 302-redirect to this face
|
|
211
|
-
- /old-reports/
|
|
212
|
-
- /legacy/sales/
|
|
213
209
|
```
|
|
214
210
|
|
|
215
|
-
Top-level fields (
|
|
211
|
+
Top-level fields (23 total):
|
|
216
212
|
|
|
217
213
|
| Field | Type | Notes |
|
|
218
214
|
|-------|------|-------|
|
|
219
215
|
| `title` | string | Display title |
|
|
220
216
|
| `description` | string | Description text |
|
|
221
217
|
| `tags` | list[string] | Tags for categorization/search |
|
|
222
|
-
| `aliases` | list[string] | See [Aliases](#aliases) below |
|
|
223
218
|
| `text` | string | Markdown body for text-only faces |
|
|
224
219
|
| `source` | string | Default source shorthand (equivalent to `sources.default`) |
|
|
225
220
|
| `sources` | object | `{default: name, <name>: {type: ...}}` |
|
|
@@ -256,11 +251,11 @@ aliases:
|
|
|
256
251
|
Rules:
|
|
257
252
|
- An alias must not collide with a real face file path — the server raises an error at startup if it does.
|
|
258
253
|
- Each alias must be unique across the project — two faces claiming the same alias is a startup error.
|
|
259
|
-
- Aliasing a generated system route (
|
|
254
|
+
- Aliasing a generated system route (data or inspector view) is allowed: the redirect takes precedence, letting you override what that URL serves.
|
|
260
255
|
|
|
261
|
-
There are two ways to override
|
|
262
|
-
- **Face file at that path** — create `faces/
|
|
263
|
-
- **`aliases:` entry** — any face can declare `/
|
|
256
|
+
There are two ways to override a data/inspector route for a given path:
|
|
257
|
+
- **Face file at that path** — create `faces/data/warehouse/schema/table.yml` and the server renders it directly (no redirect).
|
|
258
|
+
- **`aliases:` entry** — any face can declare `/data/…/` as an alias; the server issues a 302 to the declaring face's canonical URL.
|
|
264
259
|
|
|
265
260
|
Both approaches work. A face file wins without a redirect round-trip; an alias lets a face live anywhere and still capture a system-view URL.
|
|
266
261
|
|
|
@@ -372,7 +367,7 @@ queries:
|
|
|
372
367
|
- [Bob, 87.1]
|
|
373
368
|
```
|
|
374
369
|
|
|
375
|
-
Query types (`type:` literals): `sql`, `csv`, `http`, `dbt_model`, `metricflow`, `values`. `
|
|
370
|
+
Query types (`type:` literals): `sql`, `csv`, `http`, `dbt_model`, `metricflow`, `values`. `schema_resolver` is internal-only and not part of the authored surface.
|
|
376
371
|
|
|
377
372
|
Common fields (all query types):
|
|
378
373
|
|
|
@@ -487,7 +482,6 @@ charts:
|
|
|
487
482
|
|
|
488
483
|
# Style + behavior
|
|
489
484
|
sort: { by: total, order: desc }
|
|
490
|
-
format: integer # named alias or raw D3 spec (e.g. ",.0f")
|
|
491
485
|
x_label: "Month"
|
|
492
486
|
y_label: "Revenue (USD)"
|
|
493
487
|
link: "/orders?month={{ month }}" # Click-through URL template (drill-down)
|
|
@@ -495,6 +489,7 @@ charts:
|
|
|
495
489
|
|
|
496
490
|
style: # Chart-local style patch (typed; not raw CSS) — paint only
|
|
497
491
|
orientation: vertical # style: does NOT accept height or aspect_ratio
|
|
492
|
+
number_format: ",.0f" # D3 format string or named alias for axis/tooltip format
|
|
498
493
|
stack: zero # false | true | zero | normalize | center
|
|
499
494
|
```
|
|
500
495
|
|
|
@@ -547,7 +542,7 @@ All chart types accept the channels and style fields below — but each type rej
|
|
|
547
542
|
| `latitude` | string | Latitude field (point/bubble map) |
|
|
548
543
|
| `longitude` | string | Longitude field (point/bubble map) |
|
|
549
544
|
| `background` | string \| object | Background channel — color, `{value}`, `{field, scale/when}`, or map layer |
|
|
550
|
-
| `sort` | object | `{by, order}` — categorical sort |
|
|
545
|
+
| `sort` | object | `{by, order}` — categorical sort. Horizontal bar charts default to value-descending order when omitted. |
|
|
551
546
|
| `link` | string | Click-through URL template for drill-down links |
|
|
552
547
|
| `filters` | object | Post-execution row filters: `{col: var_name}` (implicit `eq`) or `{col: {op: var}}` where op is one of `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `in`, `not_in`, `between`. Implicit-eq values may also be Jinja templates (e.g. `"{{ region }}"`) resolved at execute time. A filter is skipped when the variable is `None`, `""`, or renders to `"none"`. |
|
|
553
548
|
| `layers` | list | Layer definitions for `type: layered` (see [Composition](#composition)) |
|
|
@@ -623,14 +618,12 @@ support: # Optional support line (same shape: value/label/format/
|
|
|
623
618
|
glyph: "▲"
|
|
624
619
|
tone: positive
|
|
625
620
|
|
|
626
|
-
# table — renders all query columns unless `style.columns` selects a subset
|
|
627
|
-
# `columns` is a MAPPING keyed by column name (NOT a list). Omit it to show
|
|
628
|
-
# every query column; include it to choose a subset and/or style columns.
|
|
621
|
+
# table — renders all query columns unless `style.columns` selects a subset
|
|
629
622
|
type: table
|
|
630
623
|
style:
|
|
631
624
|
columns:
|
|
632
|
-
|
|
633
|
-
|
|
625
|
+
- column: order_id
|
|
626
|
+
- column: amount
|
|
634
627
|
label: Amount
|
|
635
628
|
format: currency_whole
|
|
636
629
|
align: right # left | center | right
|
|
@@ -703,11 +696,11 @@ y: count
|
|
|
703
696
|
# Pure marks (typically inside layered):
|
|
704
697
|
# circle, square, tick, rule, trail, rect, arc, image
|
|
705
698
|
|
|
706
|
-
# callout — message card with a style.tone: field (negative | warning | positive)
|
|
699
|
+
# callout — message card with a style.tone: field (info | negative | warning | positive)
|
|
707
700
|
type: callout
|
|
708
701
|
message: "Query is disabled in this environment."
|
|
709
702
|
style:
|
|
710
|
-
tone: warning # optional; defaults to
|
|
703
|
+
tone: warning # optional; defaults to info
|
|
711
704
|
|
|
712
705
|
# auto — internal; engine picks the chart type from the data shape.
|
|
713
706
|
type: auto
|
|
@@ -726,7 +719,7 @@ layers:
|
|
|
726
719
|
y: target
|
|
727
720
|
label: Target
|
|
728
721
|
axis_y:
|
|
729
|
-
|
|
722
|
+
position: right # left | right
|
|
730
723
|
title: "Target"
|
|
731
724
|
```
|
|
732
725
|
|
|
@@ -872,7 +865,7 @@ variables:
|
|
|
872
865
|
description: "Restrict every query to one region."
|
|
873
866
|
options:
|
|
874
867
|
static: [US, EU, APAC]
|
|
875
|
-
|
|
868
|
+
default: US
|
|
876
869
|
```
|
|
877
870
|
|
|
878
871
|
Input types (14 total):
|
|
@@ -904,6 +897,7 @@ Common variable fields:
|
|
|
904
897
|
| `default` | any | Default value when no URL param is set |
|
|
905
898
|
| `placeholder` | string | Placeholder text |
|
|
906
899
|
| `required` | bool | Block rendering until a value exists |
|
|
900
|
+
| `allow_null` | bool | `null` is a valid selection |
|
|
907
901
|
| `visible` | bool | Hidden when `false`; still settable via URL param |
|
|
908
902
|
| `disabled` | bool \| string \| `{query, column}` | Static, Jinja expr, or query-backed disable |
|
|
909
903
|
| `data_type` | string | Upstream type hint (informational; preserved through migrations) |
|
|
@@ -919,7 +913,7 @@ variables:
|
|
|
919
913
|
product:
|
|
920
914
|
input: select
|
|
921
915
|
options:
|
|
922
|
-
static: [Electronics, Clothing]
|
|
916
|
+
static: [All, Electronics, Clothing] # Hardcoded list
|
|
923
917
|
# OR
|
|
924
918
|
query: products_list # Query whose first column is the option list
|
|
925
919
|
column: product_name # Optional: which column in that query
|
|
@@ -932,25 +926,23 @@ variables:
|
|
|
932
926
|
|
|
933
927
|
Top-level option-source binding (alternative to `options:`): `column`, `query`, `dimension` (MetricFlow), `measure` (MetricFlow), `model` (dbt).
|
|
934
928
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
Disabled forms:
|
|
929
|
+
Enabled/disabled forms (`enabled: false` means the control is inactive):
|
|
938
930
|
|
|
939
931
|
```yaml
|
|
940
932
|
variables:
|
|
941
|
-
# Static bool
|
|
942
|
-
closed: { input: checkbox,
|
|
933
|
+
# Static bool — enabled: false disables the control
|
|
934
|
+
closed: { input: checkbox, enabled: false }
|
|
943
935
|
|
|
944
936
|
# Jinja expression against current variable values
|
|
945
|
-
q4_only: { input: select, options: { static: [Q4] },
|
|
937
|
+
q4_only: { input: select, options: { static: [Q4] }, enabled: "{{ year >= 2024 }}" }
|
|
946
938
|
|
|
947
939
|
# Query-backed (must return exactly 1 row with the named boolean column)
|
|
948
940
|
territory:
|
|
949
941
|
input: select
|
|
950
942
|
options: { query: territory_options }
|
|
951
|
-
|
|
943
|
+
enabled:
|
|
952
944
|
query: control_state
|
|
953
|
-
column:
|
|
945
|
+
column: territory_enabled
|
|
954
946
|
```
|
|
955
947
|
|
|
956
948
|
**Multiselect SQL antipattern** — the first instinct for filtering a multiselect variable in SQL is `IN ({{ plans | map('tojson') | join(', ') }})`. This is wrong: `tojson` produces double-quoted strings (`"trial"`), which most SQL dialects (including DuckDB) treat as *column references*, not string literals. The query silently returns an empty result and `dft render` exits 0.
|
dataface/__init__.py
CHANGED
|
@@ -22,7 +22,8 @@ Quick Start:
|
|
|
22
22
|
... face = result.face
|
|
23
23
|
...
|
|
24
24
|
... # Create executor and render
|
|
25
|
-
...
|
|
25
|
+
... from dataface.core.compile.config import load_project_sources
|
|
26
|
+
... registry = build_adapter_registry(Path.cwd(), project_sources=load_project_sources(Path.cwd()))
|
|
26
27
|
... executor = Executor(face, registry, query_registry=result.query_registry)
|
|
27
28
|
... svg = render(face, executor, format="svg")
|
|
28
29
|
"""
|
dataface/agent_api/__init__.py
CHANGED
|
@@ -37,10 +37,6 @@ CLI/MCP files that contain logic beyond parse-and-dispatch.
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
from dataface.agent_api import skills as skills
|
|
40
|
-
from dataface.agent_api.dashboards import (
|
|
41
|
-
RenderedDashboard as RenderedDashboard,
|
|
42
|
-
render_dashboard as render_dashboard,
|
|
43
|
-
)
|
|
44
40
|
from dataface.agent_api.describe import (
|
|
45
41
|
DescribeFaceArgs,
|
|
46
42
|
DescribeFaceResult,
|
|
@@ -63,6 +59,10 @@ from dataface.agent_api.validate_query import (
|
|
|
63
59
|
validate_query as validate_query,
|
|
64
60
|
)
|
|
65
61
|
from dataface.core.compile.config import ProjectSourcesConfig as ProjectSourcesConfig
|
|
62
|
+
from dataface.core.dashboard import (
|
|
63
|
+
RenderedDashboard as RenderedDashboard,
|
|
64
|
+
render_dashboard as render_dashboard,
|
|
65
|
+
)
|
|
66
66
|
from dataface.core.errors.structured import StructuredError as StructuredError
|
|
67
67
|
from dataface.core.fonts import get_inter_font_path as get_inter_font_path
|
|
68
68
|
from dataface.core.render.board_links import LinkContext as LinkContext
|
|
@@ -82,7 +82,9 @@ charts:
|
|
|
82
82
|
query: kpi_summary
|
|
83
83
|
label: Total Revenue (H1)
|
|
84
84
|
value: total_revenue
|
|
85
|
-
|
|
85
|
+
style:
|
|
86
|
+
value:
|
|
87
|
+
format: currency_compact
|
|
86
88
|
support:
|
|
87
89
|
value: revenue_delta
|
|
88
90
|
label: vs H1 last year
|
|
@@ -95,7 +97,9 @@ charts:
|
|
|
95
97
|
query: kpi_summary
|
|
96
98
|
label: Avg Deal Size
|
|
97
99
|
value: avg_deal
|
|
98
|
-
|
|
100
|
+
style:
|
|
101
|
+
value:
|
|
102
|
+
format: currency_compact
|
|
99
103
|
|
|
100
104
|
data_table:
|
|
101
105
|
type: table
|
dataface/agent_api/dashboards.py
CHANGED
|
@@ -3,10 +3,6 @@
|
|
|
3
3
|
All public functions have concrete typed arguments and typed Pydantic return
|
|
4
4
|
values. No bare dict[str, Any] in any return position. Tool/CLI callers use
|
|
5
5
|
.model_dump() at the wire boundary.
|
|
6
|
-
|
|
7
|
-
`RenderedDashboard` and `render_dashboard` live in `dataface.core.dashboard`
|
|
8
|
-
so the embedded HTTP server can call them without inverting the layer stack; they
|
|
9
|
-
are re-exported here so the agent_api thin-wrapper rule still holds.
|
|
10
6
|
"""
|
|
11
7
|
|
|
12
8
|
from __future__ import annotations
|
|
@@ -20,10 +16,7 @@ from pydantic import BaseModel, Field
|
|
|
20
16
|
from dataface.agent_api._paths import resolve_scoped_path
|
|
21
17
|
from dataface.core.compile import Face, compile_file
|
|
22
18
|
from dataface.core.compile.errors import DatafaceError
|
|
23
|
-
from dataface.core.dashboard import
|
|
24
|
-
RenderedDashboard as RenderedDashboard,
|
|
25
|
-
render_dashboard as render_dashboard,
|
|
26
|
-
)
|
|
19
|
+
from dataface.core.dashboard import RenderedDashboard as RenderedDashboard
|
|
27
20
|
from dataface.core.errors import DF_UNKNOWN_INTERNAL, StructuredError
|
|
28
21
|
from dataface.core.render.warnings.base import RenderWarning
|
|
29
22
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Data URL surfacing and alias typo lint.
|
|
2
2
|
|
|
3
3
|
Provides:
|
|
4
|
-
-
|
|
4
|
+
- DataPathInfo: the agent-facing wire shape for a canonical data URL
|
|
5
5
|
(url, resolves_to: "generic" | "authored", face)
|
|
6
|
-
-
|
|
6
|
+
- data_paths_for_source/schema/table: compute data URLs and check the
|
|
7
7
|
alias index to determine whether a face overrides the generic system view
|
|
8
|
-
-
|
|
9
|
-
from a SchemaResponse — used for --
|
|
10
|
-
-
|
|
8
|
+
- data_paths_list: flat list of DataPathInfo (source + schema + table level)
|
|
9
|
+
from a SchemaResponse — used for --data-paths CLI flag and agent discovery
|
|
10
|
+
- validate_data_aliases: typo lint for /data/ prefixed aliases — checks
|
|
11
11
|
source names only (config only, no DB connection required).
|
|
12
12
|
"""
|
|
13
13
|
|
|
@@ -17,17 +17,23 @@ from dataclasses import dataclass
|
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
from typing import TYPE_CHECKING, Any, Literal
|
|
19
19
|
|
|
20
|
+
from dataface.core.registered_views.data_urls import (
|
|
21
|
+
data_schema_url,
|
|
22
|
+
data_source_url,
|
|
23
|
+
data_table_url,
|
|
24
|
+
)
|
|
25
|
+
|
|
20
26
|
if TYPE_CHECKING:
|
|
21
27
|
from dataface.agent_api.schema import SchemaResponse
|
|
22
28
|
from dataface.core.serve.alias_index import AliasIndex
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
@dataclass
|
|
26
|
-
class
|
|
27
|
-
"""Canonical
|
|
32
|
+
class DataPathInfo:
|
|
33
|
+
"""Canonical data URL with resolution status.
|
|
28
34
|
|
|
29
35
|
Wire shape (pinned by contract test — any change is a breaking API change):
|
|
30
|
-
{"url": "/
|
|
36
|
+
{"url": "/data/src/schema/table/", "resolves_to": "generic"}
|
|
31
37
|
{"url": "...", "resolves_to": "authored", "face": "/sales/"}
|
|
32
38
|
"""
|
|
33
39
|
|
|
@@ -43,73 +49,73 @@ class EntityPathInfo:
|
|
|
43
49
|
return d
|
|
44
50
|
|
|
45
51
|
|
|
46
|
-
def
|
|
52
|
+
def data_paths_for_source(
|
|
47
53
|
source: str,
|
|
48
54
|
*,
|
|
49
55
|
alias_index: AliasIndex,
|
|
50
|
-
) ->
|
|
51
|
-
"""Return the
|
|
56
|
+
) -> DataPathInfo:
|
|
57
|
+
"""Return the data URL info for a source.
|
|
52
58
|
|
|
53
|
-
The canonical URL is /
|
|
59
|
+
The canonical URL is /data/<source>/. When a face aliases it the
|
|
54
60
|
resolves_to is 'authored' and face is the canonical face URL.
|
|
55
61
|
"""
|
|
56
|
-
url =
|
|
62
|
+
url = data_source_url(source)
|
|
57
63
|
face = alias_index.lookup(url)
|
|
58
64
|
if face is not None:
|
|
59
|
-
return
|
|
60
|
-
return
|
|
65
|
+
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
66
|
+
return DataPathInfo(url=url, resolves_to="generic")
|
|
61
67
|
|
|
62
68
|
|
|
63
|
-
def
|
|
69
|
+
def data_paths_for_schema(
|
|
64
70
|
source: str,
|
|
65
71
|
schema: str,
|
|
66
72
|
*,
|
|
67
73
|
alias_index: AliasIndex,
|
|
68
|
-
) ->
|
|
69
|
-
"""Return the
|
|
74
|
+
) -> DataPathInfo:
|
|
75
|
+
"""Return the data URL info for a schema.
|
|
70
76
|
|
|
71
|
-
The canonical URL is /
|
|
77
|
+
The canonical URL is /data/<source>/<schema>/. When a face aliases it
|
|
72
78
|
the resolves_to is 'authored' and face is the canonical face URL.
|
|
73
79
|
"""
|
|
74
|
-
url =
|
|
80
|
+
url = data_schema_url(source, schema)
|
|
75
81
|
face = alias_index.lookup(url)
|
|
76
82
|
if face is not None:
|
|
77
|
-
return
|
|
78
|
-
return
|
|
83
|
+
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
84
|
+
return DataPathInfo(url=url, resolves_to="generic")
|
|
79
85
|
|
|
80
86
|
|
|
81
|
-
def
|
|
87
|
+
def data_paths_for_table(
|
|
82
88
|
source: str,
|
|
83
89
|
schema: str,
|
|
84
90
|
table: str,
|
|
85
91
|
*,
|
|
86
92
|
alias_index: AliasIndex,
|
|
87
|
-
) ->
|
|
88
|
-
"""Return the
|
|
93
|
+
) -> DataPathInfo:
|
|
94
|
+
"""Return the data URL info for a table.
|
|
89
95
|
|
|
90
|
-
The canonical URL is /
|
|
96
|
+
The canonical URL is /data/<source>/<schema>/<table>/. When a face
|
|
91
97
|
aliases it the resolves_to is 'authored' and face is the canonical face URL.
|
|
92
98
|
"""
|
|
93
|
-
url =
|
|
99
|
+
url = data_table_url(source, schema, table)
|
|
94
100
|
face = alias_index.lookup(url)
|
|
95
101
|
if face is not None:
|
|
96
|
-
return
|
|
97
|
-
return
|
|
102
|
+
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
103
|
+
return DataPathInfo(url=url, resolves_to="generic")
|
|
98
104
|
|
|
99
105
|
|
|
100
|
-
def
|
|
106
|
+
def validate_data_aliases(
|
|
101
107
|
aliases: list[str],
|
|
102
108
|
*,
|
|
103
109
|
source_names: frozenset[str],
|
|
104
110
|
) -> list[str]:
|
|
105
|
-
"""Lint /
|
|
111
|
+
"""Lint /data/ prefixed aliases against configured source names.
|
|
106
112
|
|
|
107
113
|
Called at dft-validate time (connection-free). Checks that every
|
|
108
|
-
/
|
|
114
|
+
/data/ URL references a known source, and errors with available
|
|
109
115
|
sources as a hint when not.
|
|
110
116
|
|
|
111
117
|
Returns a list of error message strings (empty = all valid).
|
|
112
|
-
Aliases not prefixed with /
|
|
118
|
+
Aliases not prefixed with /data/ are silently skipped.
|
|
113
119
|
"""
|
|
114
120
|
errors: list[str] = []
|
|
115
121
|
for raw_alias in aliases:
|
|
@@ -120,17 +126,17 @@ def validate_entity_aliases(
|
|
|
120
126
|
if not alias.endswith("/"):
|
|
121
127
|
alias = alias + "/"
|
|
122
128
|
|
|
123
|
-
if not alias.startswith("/
|
|
129
|
+
if not alias.startswith("/data/"):
|
|
124
130
|
continue
|
|
125
131
|
|
|
126
|
-
# Strip leading "/
|
|
127
|
-
inner = alias[len("/
|
|
132
|
+
# Strip leading "/data/" and trailing "/" to get segments
|
|
133
|
+
inner = alias[len("/data/") :]
|
|
128
134
|
if inner.endswith("/"):
|
|
129
135
|
inner = inner[:-1]
|
|
130
136
|
segments = [s for s in inner.split("/") if s]
|
|
131
137
|
|
|
132
138
|
if not segments:
|
|
133
|
-
# Bare /
|
|
139
|
+
# Bare /data/ — no validation needed
|
|
134
140
|
continue
|
|
135
141
|
|
|
136
142
|
# Segment 0: source
|
|
@@ -143,36 +149,34 @@ def validate_entity_aliases(
|
|
|
143
149
|
else "no sources configured"
|
|
144
150
|
)
|
|
145
151
|
errors.append(
|
|
146
|
-
f"
|
|
152
|
+
f"Data alias {raw_alias!r} references unknown source {source!r}. "
|
|
147
153
|
f"Did you mean one of: {hint}"
|
|
148
154
|
)
|
|
149
155
|
|
|
150
156
|
return errors
|
|
151
157
|
|
|
152
158
|
|
|
153
|
-
def
|
|
159
|
+
def data_paths_list(
|
|
154
160
|
schema_response: SchemaResponse,
|
|
155
161
|
*,
|
|
156
162
|
alias_index: AliasIndex,
|
|
157
|
-
) -> list[
|
|
158
|
-
"""Build a flat list of
|
|
163
|
+
) -> list[DataPathInfo]:
|
|
164
|
+
"""Build a flat list of DataPathInfo from a SchemaResponse.
|
|
159
165
|
|
|
160
166
|
Iterates all sources → schemas → tables in the response and returns one
|
|
161
|
-
|
|
167
|
+
DataPathInfo per source, per schema, and per table, checking the alias
|
|
162
168
|
index for author overrides at each level.
|
|
163
169
|
"""
|
|
164
|
-
result: list[
|
|
170
|
+
result: list[DataPathInfo] = []
|
|
165
171
|
for source_name, source_data in schema_response.sources.items():
|
|
166
|
-
result.append(
|
|
172
|
+
result.append(data_paths_for_source(source_name, alias_index=alias_index))
|
|
167
173
|
for schema_name, schema_data in (source_data.get("schemas") or {}).items():
|
|
168
174
|
result.append(
|
|
169
|
-
|
|
170
|
-
source_name, schema_name, alias_index=alias_index
|
|
171
|
-
)
|
|
175
|
+
data_paths_for_schema(source_name, schema_name, alias_index=alias_index)
|
|
172
176
|
)
|
|
173
177
|
for table_name in schema_data.get("tables") or {}:
|
|
174
178
|
result.append(
|
|
175
|
-
|
|
179
|
+
data_paths_for_table(
|
|
176
180
|
source_name,
|
|
177
181
|
schema_name,
|
|
178
182
|
table_name,
|
|
@@ -186,7 +190,7 @@ def build_alias_index_for_project(project_dir: Path) -> AliasIndex:
|
|
|
186
190
|
"""Build an AliasIndex from a project directory.
|
|
187
191
|
|
|
188
192
|
Uses the same faces_at_root detection logic as the server. Intended for
|
|
189
|
-
CLI commands (dft schema --
|
|
193
|
+
CLI commands (dft schema --data-paths) that need alias lookup without
|
|
190
194
|
starting a server.
|
|
191
195
|
"""
|
|
192
196
|
from dataface.core.serve.alias_index import AliasIndex
|
|
@@ -200,22 +204,22 @@ def build_alias_index_for_project(project_dir: Path) -> AliasIndex:
|
|
|
200
204
|
)
|
|
201
205
|
|
|
202
206
|
|
|
203
|
-
def
|
|
207
|
+
def data_alias_errors_for_file(
|
|
204
208
|
face_file: Path,
|
|
205
209
|
source_names: frozenset[str],
|
|
206
210
|
) -> list[str]:
|
|
207
|
-
"""Read aliases from a face file and lint any /
|
|
211
|
+
"""Read aliases from a face file and lint any /data/ prefixed ones.
|
|
208
212
|
|
|
209
213
|
Source-name check only — no DB connection, no resolver. Called from the
|
|
210
214
|
validate path which must stay connection-free.
|
|
211
215
|
|
|
212
|
-
Returns a list of error message strings; empty means no
|
|
216
|
+
Returns a list of error message strings; empty means no data alias issues.
|
|
213
217
|
Silently returns [] when the file cannot be parsed (compile catches that).
|
|
214
218
|
"""
|
|
215
|
-
from dataface.core.serve.alias_index import
|
|
219
|
+
from dataface.core.serve.alias_index import read_aliases_from_file
|
|
216
220
|
|
|
217
221
|
try:
|
|
218
|
-
aliases =
|
|
222
|
+
aliases = read_aliases_from_file(face_file)
|
|
219
223
|
except ValueError:
|
|
220
224
|
return [] # compile path reports alias parse errors with better context
|
|
221
|
-
return
|
|
225
|
+
return validate_data_aliases(aliases, source_names=source_names)
|