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.
Files changed (150) hide show
  1. dataface/DATAFACE_SYNTAX.md +25 -33
  2. dataface/__init__.py +2 -1
  3. dataface/agent_api/__init__.py +4 -4
  4. dataface/agent_api/_init_templates/guide.yaml +6 -2
  5. dataface/agent_api/dashboards.py +1 -8
  6. dataface/agent_api/{entity_paths.py → data_paths.py} +60 -56
  7. dataface/agent_api/docs/yaml-reference.md +52 -161
  8. dataface/agent_api/pack.py +45 -3
  9. dataface/agent_api/project.py +175 -5
  10. dataface/agent_api/render_face.py +17 -18
  11. dataface/agent_api/schema_hints.py +3 -0
  12. dataface/agent_api/search.py +32 -1
  13. dataface/agent_api/validate.py +5 -7
  14. dataface/ai/skills/dashboard-pack-scaffolding/SKILL.md +98 -10
  15. dataface/ai/skills/kpi-row/SKILL.md +3 -1
  16. dataface/ai/skills/kpi-row/examples/kpi-row.yml +6 -2
  17. dataface/ai/skills/single-metric-bignum/SKILL.md +3 -1
  18. dataface/ai/skills/single-metric-bignum/examples/single-metric-bignum.yml +3 -1
  19. dataface/ai/tools/__init__.py +4 -3
  20. dataface/cli/commands/query.py +22 -22
  21. dataface/cli/commands/schema.py +50 -48
  22. dataface/cli/commands/search.py +3 -3
  23. dataface/cli/main.py +5 -5
  24. dataface/core/__init__.py +2 -1
  25. dataface/core/compile/authoring_warnings.py +11 -3
  26. dataface/core/compile/colors.py +20 -4
  27. dataface/core/compile/compiler.py +5 -1
  28. dataface/core/compile/config.py +11 -12
  29. dataface/core/compile/filter_injection.py +6 -1
  30. dataface/core/compile/inherit_graph.py +186 -0
  31. dataface/core/compile/inherit_registry.yaml +1 -0
  32. dataface/core/compile/introspection.py +20 -6
  33. dataface/core/compile/jinja.py +8 -54
  34. dataface/core/compile/meta.py +6 -11
  35. dataface/core/compile/models/chart/authored.py +48 -84
  36. dataface/core/compile/models/chart/resolved.py +1 -1
  37. dataface/core/compile/models/config.py +4 -0
  38. dataface/core/compile/models/markers.py +36 -0
  39. dataface/core/compile/models/query/normalized.py +13 -5
  40. dataface/core/compile/models/source.py +1 -1
  41. dataface/core/compile/models/style/authored.py +1 -7
  42. dataface/core/compile/models/style/resolved.py +24 -27
  43. dataface/core/compile/models/style/theme.py +38 -80
  44. dataface/core/compile/models/variable/authored.py +7 -7
  45. dataface/core/compile/normalize_charts.py +13 -58
  46. dataface/core/compile/normalize_layout.py +4 -4
  47. dataface/core/compile/normalize_queries.py +4 -2
  48. dataface/core/compile/normalize_variables.py +5 -5
  49. dataface/core/compile/normalizer.py +8 -8
  50. dataface/core/compile/sizing.py +145 -81
  51. dataface/core/compile/style_cascade.py +7 -7
  52. dataface/core/compile/typography.py +18 -5
  53. dataface/core/compile/validator.py +3 -3
  54. dataface/core/compile/yaml_error_formatter.py +0 -13
  55. dataface/core/dashboard.py +1 -1
  56. dataface/core/defaults/default_config.yml +5 -0
  57. dataface/core/defaults/palettes/scaffold/dft-grays.yml +1 -1
  58. dataface/core/defaults/palettes/tone/info.yml +21 -0
  59. dataface/core/defaults/themes/_base.yaml +14 -11
  60. dataface/core/defaults/themes/carbong100.yaml +16 -1
  61. dataface/core/defaults/themes/editorial.yaml +3 -3
  62. dataface/core/errors/__init__.py +6 -0
  63. dataface/core/errors/codes_search.py +36 -0
  64. dataface/core/execute/adapters/adapter_registry.py +19 -12
  65. dataface/core/execute/adapters/dbt_adapter.py +5 -6
  66. dataface/core/execute/adapters/schema_adapter.py +15 -5
  67. dataface/core/execute/adapters/sql_adapter.py +1 -1
  68. dataface/core/execute/duckdb_cache.py +11 -6
  69. dataface/core/execute/executor.py +2 -1
  70. dataface/core/execute/sql_guard.py +3 -11
  71. dataface/core/inspect/db_types.py +29 -18
  72. dataface/core/inspect/renderer.py +7 -2
  73. dataface/core/inspect/templates/categorical_column.yml +9 -3
  74. dataface/core/inspect/templates/date_column.yml +9 -3
  75. dataface/core/inspect/templates/numeric_column.yml +18 -6
  76. dataface/core/inspect/templates/string_column.yml +18 -6
  77. dataface/core/pack/models.py +14 -0
  78. dataface/core/pack/planner.py +43 -13
  79. dataface/core/project_roots.py +2 -2
  80. dataface/core/registered_views/data_urls.py +31 -0
  81. dataface/core/{system_views → registered_views}/expander.py +37 -28
  82. dataface/core/{system_views → registered_views}/loader.py +2 -3
  83. dataface/core/{system_views → registered_views}/models.py +10 -26
  84. dataface/core/{system_views → registered_views}/query_runner.py +11 -12
  85. dataface/core/{system_views → registered_views}/registry.yaml +20 -26
  86. dataface/core/registered_views/render_pipeline.py +221 -0
  87. dataface/core/{system_views → registered_views}/router.py +4 -5
  88. dataface/core/registered_views/templates/data/root.yaml +19 -0
  89. dataface/core/{system_views/templates/entity → registered_views/templates/data}/schema-index.yaml +1 -1
  90. dataface/core/{system_views/templates/entity → registered_views/templates/data}/source-index.yaml +1 -1
  91. dataface/core/{system_views/templates/entity → registered_views/templates/data}/table-index.yaml +1 -1
  92. dataface/core/{system_views → registered_views}/variable_planner.py +37 -121
  93. dataface/core/render/chart/callout.py +39 -41
  94. dataface/core/render/chart/decisions.py +11 -1
  95. dataface/core/render/chart/geo.py +30 -101
  96. dataface/core/render/chart/kpi.py +4 -3
  97. dataface/core/render/chart/pipeline.py +16 -18
  98. dataface/core/render/chart/profile.py +49 -20
  99. dataface/core/render/chart/rendering.py +4 -14
  100. dataface/core/render/chart/spark_bar.py +4 -6
  101. dataface/core/render/chart/standard_renderer.py +5 -5
  102. dataface/core/render/chart/table.py +38 -12
  103. dataface/core/render/chart/vl_field_maps.py +4 -4
  104. dataface/core/render/chart_interactivity.py +0 -1
  105. dataface/core/render/converters/chart.py +5 -3
  106. dataface/core/render/converters/html.py +20 -44
  107. dataface/core/render/dir_context.py +136 -44
  108. dataface/core/render/face_to_dict.py +109 -0
  109. dataface/core/render/faces.py +56 -56
  110. dataface/core/render/geo_defaults.yml +40 -0
  111. dataface/core/render/json_format.py +3 -98
  112. dataface/core/render/layout_sizing.py +4 -1
  113. dataface/core/render/layouts.py +6 -6
  114. dataface/core/render/nav.py +73 -162
  115. dataface/core/render/placeholder.py +17 -11
  116. dataface/core/render/renderer.py +20 -7
  117. dataface/core/render/svg_utils.py +4 -15
  118. dataface/core/render/templates/nav/nav-fragment.html +3 -0
  119. dataface/core/render/templates/nav/nav.css +29 -0
  120. dataface/core/render/templates/nav/nav.html +1 -0
  121. dataface/core/render/templates/nav/nav.js +15 -0
  122. dataface/core/render/templates/page.css +22 -0
  123. dataface/core/render/templates/page.html +19 -0
  124. dataface/core/render/terminal.py +31 -11
  125. dataface/core/render/terminal_layouts.py +44 -4
  126. dataface/core/render/text_format.py +2 -3
  127. dataface/core/render/variable_controls.py +18 -19
  128. dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +29 -2
  129. dataface/core/render/yaml_format.py +2 -3
  130. dataface/core/ruff.toml +10 -0
  131. dataface/core/scoped_paths.py +2 -1
  132. dataface/core/serve/alias_index.py +5 -5
  133. dataface/core/serve/bootstrap.py +1 -1
  134. dataface/core/serve/port.py +1 -1
  135. dataface/core/serve/server.py +88 -181
  136. dataface/integrations/markdown.py +26 -47
  137. {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/METADATA +1 -1
  138. {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/RECORD +161 -147
  139. mdsvg/renderer.py +6 -1
  140. mdsvg/style.py +3 -0
  141. dataface/core/serve/templates/nav.yml +0 -12
  142. /dataface/core/{system_views → registered_views}/__init__.py +0 -0
  143. /dataface/core/{system_views/templates/entity → registered_views/templates/data}/table-detail.yaml +0 -0
  144. /dataface/core/{system_views → registered_views}/templates/inspector/column.yaml +0 -0
  145. /dataface/core/{system_views → registered_views}/templates/inspector/schema.yaml +0 -0
  146. /dataface/core/{system_views → registered_views}/templates/inspector/source.yaml +0 -0
  147. /dataface/core/{system_views → registered_views}/templates/inspector/table.yaml +0 -0
  148. {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/WHEEL +0 -0
  149. {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/entry_points.txt +0 -0
  150. {dataface-0.1.5.dev237.dist-info → dataface-0.1.5.dev322.dist-info}/licenses/LICENSE +0 -0
@@ -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 <your_source> 'SELECT 1'
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
- # No default: starts on All regions
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 (24 total):
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 (entity or inspector view) is allowed: the redirect takes precedence, letting you override what that URL serves.
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 an entity/inspector route for a given path:
262
- - **Face file at that path** — create `faces/entity/warehouse/schema/table.yml` and the server renders it directly (no redirect).
263
- - **`aliases:` entry** — any face can declare `/entity/…/` as an alias; the server issues a 302 to the declaring face's canonical URL.
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`. `schema` is internal-only and not part of the authored surface.
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
- order_id: {} # include with default styling
633
- amount: # the key is the column name
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 negative
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
- orient: right # left | right
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
- # No default: starts on All regions
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] # Hardcoded list; blank -- All -- is implicit
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
- Variables are optional by default. For `select` and `multiselect`, omitting `default` starts the variable unset. The renderer adds a blank `-- All --` option for that unset state; `{{ filter('column', variable) }}` emits `1=1` unless the SQL call site opts into `none='deny'`. Add `required: true` only when the dashboard cannot render without a value.
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, disabled: true }
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] }, disabled: "{{ year < 2024 }}" }
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
- disabled:
943
+ enabled:
952
944
  query: control_state
953
- column: territory_disabled
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
- ... registry = build_adapter_registry(Path.cwd())
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
  """
@@ -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
- format: currency_compact
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
- format: currency_compact
100
+ style:
101
+ value:
102
+ format: currency_compact
99
103
 
100
104
  data_table:
101
105
  type: table
@@ -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
- """Entity URL surfacing and alias typo lint.
1
+ """Data URL surfacing and alias typo lint.
2
2
 
3
3
  Provides:
4
- - EntityPathInfo: the agent-facing wire shape for a canonical entity URL
4
+ - DataPathInfo: the agent-facing wire shape for a canonical data URL
5
5
  (url, resolves_to: "generic" | "authored", face)
6
- - entity_paths_for_source/schema/table: compute entity URLs and check the
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
- - entity_paths_list: flat list of EntityPathInfo (source + schema + table level)
9
- from a SchemaResponse — used for --entity-paths CLI flag and agent discovery
10
- - validate_entity_aliases: typo lint for /entity/ prefixed aliases — checks
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 EntityPathInfo:
27
- """Canonical entity URL with resolution status.
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": "/entity/src/schema/table/", "resolves_to": "generic"}
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 entity_paths_for_source(
52
+ def data_paths_for_source(
47
53
  source: str,
48
54
  *,
49
55
  alias_index: AliasIndex,
50
- ) -> EntityPathInfo:
51
- """Return the entity URL info for a source.
56
+ ) -> DataPathInfo:
57
+ """Return the data URL info for a source.
52
58
 
53
- The canonical URL is /entity/<source>/. When a face aliases it the
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 = f"/entity/{source}/"
62
+ url = data_source_url(source)
57
63
  face = alias_index.lookup(url)
58
64
  if face is not None:
59
- return EntityPathInfo(url=url, resolves_to="authored", face=face)
60
- return EntityPathInfo(url=url, resolves_to="generic")
65
+ return DataPathInfo(url=url, resolves_to="authored", face=face)
66
+ return DataPathInfo(url=url, resolves_to="generic")
61
67
 
62
68
 
63
- def entity_paths_for_schema(
69
+ def data_paths_for_schema(
64
70
  source: str,
65
71
  schema: str,
66
72
  *,
67
73
  alias_index: AliasIndex,
68
- ) -> EntityPathInfo:
69
- """Return the entity URL info for a schema.
74
+ ) -> DataPathInfo:
75
+ """Return the data URL info for a schema.
70
76
 
71
- The canonical URL is /entity/<source>/<schema>/. When a face aliases it
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 = f"/entity/{source}/{schema}/"
80
+ url = data_schema_url(source, schema)
75
81
  face = alias_index.lookup(url)
76
82
  if face is not None:
77
- return EntityPathInfo(url=url, resolves_to="authored", face=face)
78
- return EntityPathInfo(url=url, resolves_to="generic")
83
+ return DataPathInfo(url=url, resolves_to="authored", face=face)
84
+ return DataPathInfo(url=url, resolves_to="generic")
79
85
 
80
86
 
81
- def entity_paths_for_table(
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
- ) -> EntityPathInfo:
88
- """Return the entity URL info for a table.
93
+ ) -> DataPathInfo:
94
+ """Return the data URL info for a table.
89
95
 
90
- The canonical URL is /entity/<source>/<schema>/<table>/. When a face
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 = f"/entity/{source}/{schema}/{table}/"
99
+ url = data_table_url(source, schema, table)
94
100
  face = alias_index.lookup(url)
95
101
  if face is not None:
96
- return EntityPathInfo(url=url, resolves_to="authored", face=face)
97
- return EntityPathInfo(url=url, resolves_to="generic")
102
+ return DataPathInfo(url=url, resolves_to="authored", face=face)
103
+ return DataPathInfo(url=url, resolves_to="generic")
98
104
 
99
105
 
100
- def validate_entity_aliases(
106
+ def validate_data_aliases(
101
107
  aliases: list[str],
102
108
  *,
103
109
  source_names: frozenset[str],
104
110
  ) -> list[str]:
105
- """Lint /entity/ prefixed aliases against configured source names.
111
+ """Lint /data/ prefixed aliases against configured source names.
106
112
 
107
113
  Called at dft-validate time (connection-free). Checks that every
108
- /entity/ URL references a known source, and errors with available
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 /entity/ are silently skipped.
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("/entity/"):
129
+ if not alias.startswith("/data/"):
124
130
  continue
125
131
 
126
- # Strip leading "/entity/" and trailing "/" to get segments
127
- inner = alias[len("/entity/") :]
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 /entity/ — no validation needed
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"Entity alias {raw_alias!r} references unknown source {source!r}. "
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 entity_paths_list(
159
+ def data_paths_list(
154
160
  schema_response: SchemaResponse,
155
161
  *,
156
162
  alias_index: AliasIndex,
157
- ) -> list[EntityPathInfo]:
158
- """Build a flat list of EntityPathInfo from a SchemaResponse.
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
- EntityPathInfo per source, per schema, and per table, checking the alias
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[EntityPathInfo] = []
170
+ result: list[DataPathInfo] = []
165
171
  for source_name, source_data in schema_response.sources.items():
166
- result.append(entity_paths_for_source(source_name, alias_index=alias_index))
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
- entity_paths_for_schema(
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
- entity_paths_for_table(
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 --entity-paths) that need alias lookup without
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 entity_alias_errors_for_file(
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 /entity/ prefixed ones.
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 entity alias issues.
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 _read_aliases_from_file
219
+ from dataface.core.serve.alias_index import read_aliases_from_file
216
220
 
217
221
  try:
218
- aliases = _read_aliases_from_file(face_file)
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 validate_entity_aliases(aliases, source_names=source_names)
225
+ return validate_data_aliases(aliases, source_names=source_names)