dataface 0.2.1.dev47__py3-none-any.whl → 0.2.1.dev164__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 +15 -39
- dataface/__init__.py +2 -2
- dataface/agent_api/dashboards.py +10 -7
- dataface/agent_api/data_paths.py +5 -137
- dataface/agent_api/describe.py +9 -11
- dataface/agent_api/docs/_loader.py +28 -1
- dataface/agent_api/docs/yaml-reference.md +17 -15
- dataface/agent_api/files.py +74 -70
- dataface/agent_api/pack.py +2 -2
- dataface/agent_api/project_session.py +22 -70
- dataface/agent_api/query.py +1 -4
- dataface/agent_api/render_face.py +28 -13
- dataface/agent_api/surface_aliases.yaml +4 -12
- dataface/agent_api/validate.py +22 -15
- dataface/ai/__init__.py +1 -6
- dataface/ai/agent.py +81 -23
- dataface/ai/generate_sql.py +3 -5
- dataface/ai/llm.py +69 -4
- dataface/ai/mcp/server.py +8 -7
- dataface/ai/skills/analyst-runbook/SKILL.md +3 -2
- dataface/ai/skills/dashboard-build/SKILL.md +6 -3
- dataface/ai/skills/dashboard-pack-scaffolding/SKILL.md +15 -31
- dataface/ai/skills/data-exploration/SKILL.md +48 -40
- dataface/ai/skills/dataface-mcp-setup/SKILL.md +0 -2
- dataface/ai/skills/dataface-troubleshooting/SKILL.md +11 -5
- dataface/ai/tool_schemas.py +0 -6
- dataface/ai/tools/__init__.py +14 -86
- dataface/cli/commands/chat.py +2 -2
- dataface/cli/commands/mcp_init.py +1 -1
- dataface/cli/commands/search.py +4 -7
- dataface/cli/commands/serve.py +2 -2
- dataface/cli/main.py +0 -251
- dataface/core/__init__.py +2 -2
- dataface/core/compile/authoring_warnings.py +85 -18
- dataface/core/compile/config.py +23 -56
- dataface/core/compile/filter_injection.py +2 -4
- dataface/core/compile/labels_env.py +25 -4
- dataface/core/compile/markdown.py +3 -1
- dataface/core/compile/merge.py +95 -32
- dataface/core/compile/meta.py +1 -1
- dataface/core/compile/models/chart/authored/__init__.py +5 -6
- dataface/core/compile/models/chart/authored/_annotations.py +2 -45
- dataface/core/compile/models/chart/authored/_base.py +1 -8
- dataface/core/compile/models/chart/authored/_type.py +0 -4
- dataface/core/compile/models/chart/authored/kpi.py +2 -2
- dataface/core/compile/models/chart/authored/layered.py +5 -4
- dataface/core/compile/models/chart/normalized/pie.py +1 -4
- dataface/core/compile/models/chart/resolved/pie.py +1 -5
- dataface/core/compile/models/source.py +20 -6
- dataface/core/compile/models/style/authored/__init__.py +22 -1
- dataface/core/compile/models/style/authored/_base.py +30 -14
- dataface/core/compile/models/style/authored/table.py +41 -4
- dataface/core/compile/models/style/resolved/_base.py +17 -2
- dataface/core/compile/models/style/resolved/_cartesian.py +5 -0
- dataface/core/compile/models/style/resolved/_cascade.py +26 -2
- dataface/core/compile/models/style/resolved/geoshape.py +5 -0
- dataface/core/compile/models/style/resolved/kpi.py +5 -0
- dataface/core/compile/models/style/resolved/pie.py +5 -0
- dataface/core/compile/models/style/resolved/point_map.py +5 -0
- dataface/core/compile/models/style/resolved/spark_bar.py +5 -0
- dataface/core/compile/models/style/resolved/table.py +5 -0
- dataface/core/compile/models/style/theme/__init__.py +4 -0
- dataface/core/compile/models/style/theme/axis.py +145 -11
- dataface/core/compile/models/style/theme/board.py +6 -0
- dataface/core/compile/models/style/theme/marks.py +21 -14
- dataface/core/compile/models/style/theme/page.py +7 -0
- dataface/core/compile/models/style/theme/variables.py +8 -6
- dataface/core/compile/normalize_charts.py +0 -1
- dataface/core/compile/normalize_layout.py +2 -1
- dataface/core/compile/normalize_queries.py +13 -12
- dataface/core/compile/palette.py +65 -43
- dataface/core/compile/parameterized.py +2 -4
- dataface/core/compile/parser.py +2 -1
- dataface/core/compile/resolve/_resolve.py +164 -36
- dataface/core/compile/sql_authoring_lint.py +1 -2
- dataface/core/{execute → compile}/sql_guard.py +1 -1
- dataface/core/compile/style_cascade.py +9 -5
- dataface/core/compile/typography.py +48 -0
- dataface/core/compile/vega_lite/mapping.py +2 -12
- dataface/core/dashboard.py +63 -15
- dataface/core/defaults/palettes/diverging/blue-red-dark.yml +22 -0
- dataface/core/defaults/palettes/diverging/coolwarm-dark.yml +23 -0
- dataface/core/defaults/palettes/diverging/crimson-green-dark.yml +22 -0
- dataface/core/defaults/palettes/diverging/orange-teal-dark.yml +23 -0
- dataface/core/defaults/palettes/diverging/sunset-dark.yml +22 -0
- dataface/core/defaults/palettes/scaffold/dft-creams.yml +5 -2
- dataface/core/defaults/palettes/scaffold/dft-grays.yml +6 -3
- dataface/core/defaults/palettes/sequential/amber-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/blue-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/brown-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/gray-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/green-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/purple-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/rust-dark.yml +22 -0
- dataface/core/defaults/palettes/sequential/teal-dark.yml +22 -0
- dataface/core/defaults/themes/_base.yaml +10 -0
- dataface/core/defaults/themes/neon.yaml +35 -24
- dataface/core/{execute/dialects → dialects}/__init__.py +12 -12
- dataface/core/{execute/dialects → dialects}/athena.py +1 -1
- dataface/core/{execute/dialects → dialects}/base.py +43 -1
- dataface/core/{execute/dialects → dialects}/bigquery.py +19 -1
- dataface/core/{execute/dialects → dialects}/databricks.py +1 -1
- dataface/core/{execute/dialects → dialects}/duckdb.py +8 -1
- dataface/core/{execute/dialects → dialects}/mysql.py +1 -1
- dataface/core/{execute/dialects → dialects}/postgres.py +8 -1
- dataface/core/{execute/dialects → dialects}/redshift.py +1 -1
- dataface/core/{execute/dialects → dialects}/snowflake.py +19 -1
- dataface/core/{execute/dialects → dialects}/sqlite.py +18 -1
- dataface/core/{execute/dialects → dialects}/sqlserver.py +1 -1
- dataface/core/errors/__init__.py +2 -0
- dataface/core/errors/codes_compile.py +27 -16
- dataface/core/errors/codes_execute.py +18 -18
- dataface/core/errors/codes_render.py +40 -15
- dataface/core/errors/codes_serve.py +2 -2
- dataface/core/errors/codes_unknown.py +1 -1
- dataface/core/{execute/errors.py → errors/execution.py} +4 -2
- dataface/core/errors/registry.py +4 -0
- dataface/core/errors/render_error_reference.py +42 -0
- dataface/core/execute/__init__.py +6 -6
- dataface/core/execute/adapters/adapter_registry.py +35 -4
- dataface/core/execute/adapters/dbt_adapter.py +17 -4
- dataface/core/execute/adapters/dbt_utils.py +4 -24
- dataface/core/execute/adapters/duckdb_adapter.py +49 -13
- dataface/core/execute/adapters/sql_adapter.py +10 -14
- dataface/core/execute/adapters/sqlite_adapter.py +7 -7
- dataface/core/execute/batch.py +2 -2
- dataface/core/execute/executor.py +15 -7
- dataface/core/execute/source_resolver.py +1 -1
- dataface/core/inspect/bulk_schema.py +96 -0
- dataface/core/inspect/cache_factory.py +21 -11
- dataface/core/inspect/query_validator.py +6 -34
- dataface/core/inspect/renderer.py +7 -3
- dataface/core/inspect/resolver.py +14 -8
- dataface/core/inspect/sources/dbt.py +2 -2
- dataface/core/pack/planner.py +1 -1
- dataface/core/project.py +166 -79
- dataface/core/registered_views/render_pipeline.py +1 -1
- dataface/core/render/board_links.py +39 -0
- dataface/core/render/chart/arc_attached_table.py +0 -6
- dataface/core/render/chart/callout.py +6 -1
- dataface/core/render/chart/data_table_attachment.py +14 -0
- dataface/core/render/chart/emitters/_cartesian.py +39 -2
- dataface/core/render/chart/emitters/area.py +8 -5
- dataface/core/render/chart/emitters/bar.py +5 -2
- dataface/core/render/chart/emitters/heatmap.py +2 -1
- dataface/core/render/chart/emitters/layered.py +15 -4
- dataface/core/render/chart/emitters/line.py +5 -5
- dataface/core/render/chart/emitters/pie.py +9 -20
- dataface/core/render/chart/emitters/scatter.py +2 -1
- dataface/core/render/chart/features/mirror_axis.py +31 -0
- dataface/core/render/chart/labels.py +2 -2
- dataface/core/render/chart/presentation.py +3 -42
- dataface/core/render/chart/rendering.py +4 -8
- dataface/core/render/chart/session.py +6 -6
- dataface/core/render/chart/spark_bar.py +12 -8
- dataface/core/render/chart/spec.py +5 -1
- dataface/core/render/chart/table.py +21 -17
- dataface/core/render/chart/table_support.py +13 -3
- dataface/core/render/chart/time_unit_detect.py +19 -7
- dataface/core/render/chart/translate.py +36 -38
- dataface/core/render/chart/type_inference.py +22 -0
- dataface/core/render/chart/vega_lite.py +14 -8
- dataface/core/render/chart/vl_field_maps.py +63 -0
- dataface/core/render/converters/__init__.py +3 -4
- dataface/core/render/converters/html.py +27 -33
- dataface/core/render/face_resolve.py +5 -160
- dataface/core/render/face_to_dict.py +1 -1
- dataface/core/render/faces.py +77 -91
- dataface/core/render/layout_sizing.py +166 -21
- dataface/core/render/layouts.py +0 -8
- dataface/core/render/renderer.py +5 -15
- dataface/core/render/templates/controls/_styles.css +3 -1
- dataface/core/render/templates/scripts/chart_interactivity.js +38 -4
- dataface/core/render/templates/scripts/variables.js +13 -0
- dataface/core/render/templates/svg/styles.css +11 -0
- dataface/core/render/templates/variable_controls/container.html +1 -1
- dataface/core/render/variable_controls.py +52 -16
- dataface/core/serve/alias_index.py +44 -29
- dataface/core/serve/server.py +25 -11
- dataface/core/utils.py +35 -0
- dataface/integrations/markdown.py +10 -9
- {dataface-0.2.1.dev47.dist-info → dataface-0.2.1.dev164.dist-info}/METADATA +1 -1
- {dataface-0.2.1.dev47.dist-info → dataface-0.2.1.dev164.dist-info}/RECORD +186 -181
- dataface/agent_api/schema.py +0 -656
- dataface/agent_api/schema_search.py +0 -286
- dataface/ai/schema_context.py +0 -164
- dataface/cli/commands/schema.py +0 -593
- dataface/core/inspect/dbt_schema.py +0 -96
- dataface/core/inspect/fanout_risk.py +0 -109
- dataface/core/inspect/search.py +0 -461
- dataface/core/render/face_api.py +0 -74
- dataface/core/schema/__init__.py +0 -0
- dataface/core/schema/guidance.py +0 -150
- {dataface-0.2.1.dev47.dist-info → dataface-0.2.1.dev164.dist-info}/WHEEL +0 -0
- {dataface-0.2.1.dev47.dist-info → dataface-0.2.1.dev164.dist-info}/entry_points.txt +0 -0
- {dataface-0.2.1.dev47.dist-info → dataface-0.2.1.dev164.dist-info}/licenses/LICENSE +0 -0
dataface/DATAFACE_SYNTAX.md
CHANGED
|
@@ -26,17 +26,17 @@ A successful result means your data source is reachable.
|
|
|
26
26
|
|
|
27
27
|
### Step 2 — Browse your schema
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Explore available tables and columns with metadata SQL before writing queries:
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
dft
|
|
33
|
-
dft
|
|
34
|
-
dft schema mydb main # list tables in that schema
|
|
35
|
-
dft schema mydb main orders # profile the orders table (columns, types, samples)
|
|
36
|
-
dft schema mydb main orders id # profile one column (distribution, sample values)
|
|
32
|
+
dft query mydb "SELECT table_schema, table_name FROM INFORMATION_SCHEMA.TABLES"
|
|
33
|
+
dft query mydb "SELECT column_name, data_type FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'orders'"
|
|
37
34
|
```
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
(DuckDB also supports `DESCRIBE orders`; SQLite uses `sqlite_master` and
|
|
37
|
+
`PRAGMA table_info(orders)`.)
|
|
38
|
+
|
|
39
|
+
Never invent column names — always verify them against the schema first.
|
|
40
40
|
|
|
41
41
|
### Step 3 — Build one chart
|
|
42
42
|
|
|
@@ -573,7 +573,7 @@ All chart types accept the channels and style fields below — but each type rej
|
|
|
573
573
|
| `theta` | string | Angular field (pie/donut/arc) |
|
|
574
574
|
| `style.inner_radius` | float 0–1 | Donut hole ratio (hole/outer disk; pie/donut only); `type: donut` sets it to `0.6` automatically |
|
|
575
575
|
| `total` | object | `{label, format}` — center total for donut |
|
|
576
|
-
| `labels` | object | `{template, where}` — per-row Jinja annotations near
|
|
576
|
+
| `style.marks.slice.labels` | object | `{template, where}` — per-row Jinja annotations near slice callouts |
|
|
577
577
|
| `x_label` | string | X-axis title override |
|
|
578
578
|
| `y_label` | string | Y-axis title override |
|
|
579
579
|
| `message` | string | Static text for `type: callout` |
|
|
@@ -648,11 +648,13 @@ theta: revenue
|
|
|
648
648
|
color: segment
|
|
649
649
|
style:
|
|
650
650
|
inner_radius: 0.6 # 0 = solid pie; >0 = donut (type: donut sets 0.6 by default)
|
|
651
|
+
marks:
|
|
652
|
+
slice:
|
|
653
|
+
labels:
|
|
654
|
+
template: "{{ segment }}\n{{ revenue | format(',.0f') }}"
|
|
651
655
|
total:
|
|
652
656
|
label: Total
|
|
653
657
|
format: integer
|
|
654
|
-
labels:
|
|
655
|
-
template: "{{ segment }}\n{{ revenue | format(',.0f') }}"
|
|
656
658
|
|
|
657
659
|
# kpi — requires exactly 1 row; value: is a column reference
|
|
658
660
|
type: kpi
|
|
@@ -1136,38 +1138,12 @@ Active code categories (see `dataface/core/errors/codes_*.py` for the authoritat
|
|
|
1136
1138
|
| `DF-COMPILE-*` | Compile-time errors (missing fields, unknown references, malformed YAML) |
|
|
1137
1139
|
| `DF-RENDER-*` | Chart rendering errors (wrong row counts, unknown chart types, format issues) |
|
|
1138
1140
|
| `DF-EXECUTE-*` | Query execution errors (missing sources, inline-source policy, source typing) |
|
|
1141
|
+
| `DF-SERVE-*` | Server startup errors (invalid theme, launch failures) |
|
|
1139
1142
|
| `DF-UNKNOWN-*` | Fallback codes for legacy string-message errors not yet migrated |
|
|
1140
1143
|
|
|
1141
|
-
The registry is being filled out incrementally — some compile-time and variable-resolution errors still raise as plain `DatafaceError` without a structured code.
|
|
1142
|
-
|
|
1143
|
-
### Compile errors
|
|
1144
|
-
|
|
1145
|
-
- **`DF-COMPILE-SOURCE-REQUIRED`** — a SQL query has no `source:` and no default is configured. Set the source on the query (`source: my_db`) or at the face level (`source: my_db` at the top of the face YAML).
|
|
1146
|
-
- **`DF-COMPILE-SOURCE-INLINE-FORBIDDEN`** — a query's `source:` is an inline connection dict (e.g. `{type: postgres, ...}`). Reference a named source instead (`source: my_db`); connection details live only in `dataface.yml`.
|
|
1147
|
-
- **`DF-COMPILE-SOURCE-CREDENTIAL-LITERAL`** — a `dataface.yml` source holds a raw secret literal in a known-secret field (`password`, `private_key`, `keyfile_json`, `token`, `secret`). `dataface.yml` is git-committed, so use `{{ env_var('SECRET_NAME') }}` or a `type: dbt_profile` source instead.
|
|
1148
|
-
- **`DF-COMPILE-SOURCE-CONFIG-INVALID`** — a `dataface.yml` `sources:` entry fails typed validation for its `type:` (unknown/missing field, wrong shape).
|
|
1149
|
-
- **`DF-COMPILE-SOURCE-NOT-FOUND`** — a query's `source:` names a source that is not in the loaded registry. Check for a typo, or declare the source under `sources:` in `dataface.yml`.
|
|
1150
|
-
- **`DF-COMPILE-UNKNOWN-QUERY`** — a chart's `query:` references a name that does not appear under `queries:`. Check for a typo or add the missing query.
|
|
1151
|
-
- **`DF-COMPILE-EXTRA-FIELD`** — the face YAML contains a field not recognised by the schema. Remove it or check the schema for supported keys.
|
|
1152
|
-
- **`DF-COMPILE-WRONG-SHAPE`** — a YAML field that expects a mapping (nested block) received a scalar or sequence instead. The error hint lists the available keys. For example, `axis_x.label: "hi"` must be a mapping: `axis_x:\n label:\n angle: -45`.
|
|
1153
|
-
- **`DF-COMPILE-SQL-LITERAL-NEWLINES`** — a SQL field contains a literal `\n` (backslash + n) from single-quoted YAML. Use a YAML block scalar (`sql: |`) to write multiline SQL.
|
|
1154
|
-
|
|
1155
|
-
### Render errors
|
|
1156
|
-
|
|
1157
|
-
- **`DF-RENDER-KPI-MULTIROW`** — a KPI chart's query returned more than one row but `value` is a column reference. Add `LIMIT 1` or aggregate down to one row.
|
|
1158
|
-
- **`DF-RENDER-UNKNOWN-CHART-TYPE`** — `type:` is not a recognized chart type. See the [Charts](#charts) section for the list.
|
|
1159
|
-
- **`DF-RENDER-FORMAT-UNSUPPORTED`** — the render `format` argument is not one of `svg`/`html`/`png`.
|
|
1160
|
-
- **`DF-RENDER-NO-LAYOUT`** — the face has no `rows`/`cols`/`grid`/`tabs` and nothing to render.
|
|
1161
|
-
- **`DF-RENDER-INPUT-INVALID`** — the render call received structurally invalid input (e.g. both `path` and `yaml_content`).
|
|
1162
|
-
|
|
1163
|
-
### Execute errors
|
|
1164
|
-
|
|
1165
|
-
- **`DF-EXECUTE-SOURCE-NOT-FOUND`** / **`-NOT-FOUND-EMPTY`** — the query's `source:` (or default source) is not configured in `dataface.yml`.
|
|
1166
|
-
- **`DF-EXECUTE-NO-DEFAULT-SOURCE`** — multiple sources are defined and no `default_source` was set.
|
|
1167
|
-
- **`DF-EXECUTE-SOURCE-INLINE-FORBIDDEN`** / **`-CROSS-FILE-FORBIDDEN`** — inline source definitions are restricted by project policy.
|
|
1168
|
-
- **`DF-EXECUTE-SOURCE-INVALID-TYPE`** / **`-MISSING-TYPE`** — a source definition has an unknown or missing `type:` field.
|
|
1144
|
+
The registry is being filled out incrementally — some compile-time and variable-resolution errors still raise as plain `DatafaceError` without a structured code.
|
|
1169
1145
|
|
|
1170
|
-
Run `dft validate <face>` to validate a face and see structured error details.
|
|
1146
|
+
Run `dft docs error-reference` for the full generated reference — every registered code, its message template, and its docs pointer, straight from `dataface.core.errors.REGISTRY`. Run `dft validate <face>` to validate a face and see structured error details.
|
|
1171
1147
|
|
|
1172
1148
|
**See also:** `dft docs queries`, `dft docs charts`, `dft docs variables` (where most errors originate),
|
|
1173
1149
|
`dft docs all` (whole reference for context).
|
dataface/__init__.py
CHANGED
|
@@ -22,8 +22,8 @@ Quick Start:
|
|
|
22
22
|
... face = result.face
|
|
23
23
|
...
|
|
24
24
|
... # Create executor and render
|
|
25
|
-
... from dataface.core.project import
|
|
26
|
-
... registry = build_adapter_registry(
|
|
25
|
+
... from dataface.core.project import FilesystemProject
|
|
26
|
+
... registry = build_adapter_registry(FilesystemProject(Path.cwd()))
|
|
27
27
|
... executor = Executor(face, registry, query_registry=result.query_registry)
|
|
28
28
|
... svg = render(face, executor, format="svg")
|
|
29
29
|
"""
|
dataface/agent_api/dashboards.py
CHANGED
|
@@ -13,10 +13,14 @@ from typing import Any, Literal
|
|
|
13
13
|
import yaml
|
|
14
14
|
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
15
15
|
|
|
16
|
-
from dataface.agent_api._paths import resolve_scoped_path
|
|
16
|
+
from dataface.agent_api._paths import no_project_hint, resolve_scoped_path
|
|
17
17
|
from dataface.core.compile import Face, compile_file
|
|
18
18
|
from dataface.core.dashboard import RenderedDashboard as RenderedDashboard
|
|
19
|
-
from dataface.core.errors import
|
|
19
|
+
from dataface.core.errors import (
|
|
20
|
+
DF_COMPILE_FILE_NOT_FOUND,
|
|
21
|
+
DF_UNKNOWN_INTERNAL,
|
|
22
|
+
StructuredError,
|
|
23
|
+
)
|
|
20
24
|
from dataface.core.errors.base import DatafaceError
|
|
21
25
|
from dataface.core.project import FACES_SUBDIR, Project, ProjectPath
|
|
22
26
|
from dataface.core.warnings.base import RenderWarning
|
|
@@ -89,9 +93,6 @@ class RenderDashboardArgs(BaseModel):
|
|
|
89
93
|
yaml_content: str | None = Field(
|
|
90
94
|
None, description="YAML content to render directly (use this OR path)"
|
|
91
95
|
)
|
|
92
|
-
project_dir: Path | None = Field(
|
|
93
|
-
None, description="Project root for resolving relative paths"
|
|
94
|
-
)
|
|
95
96
|
variables: dict[str, Any] | None = Field(
|
|
96
97
|
None, description="Variable values to apply to the dashboard"
|
|
97
98
|
)
|
|
@@ -190,12 +191,14 @@ def get_dashboard(
|
|
|
190
191
|
)
|
|
191
192
|
|
|
192
193
|
if not file_path.exists():
|
|
194
|
+
hint = no_project_hint(project.root)
|
|
193
195
|
return CompiledDashboard(
|
|
194
196
|
success=False,
|
|
195
197
|
errors=[
|
|
196
198
|
DatafaceError.from_code(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
+
DF_COMPILE_FILE_NOT_FOUND,
|
|
200
|
+
path=str(file_path),
|
|
201
|
+
hint=hint,
|
|
199
202
|
).to_structured(file=str(file_path))
|
|
200
203
|
],
|
|
201
204
|
)
|
dataface/agent_api/data_paths.py
CHANGED
|
@@ -1,107 +1,16 @@
|
|
|
1
|
-
"""Data
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(url, resolves_to: "generic" | "authored", face)
|
|
6
|
-
- data_paths_for_source/schema/table: compute data URLs and check the
|
|
7
|
-
alias index to determine whether a face overrides the generic system view
|
|
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
|
-
source names only (config only, no DB connection required).
|
|
1
|
+
"""Data-alias typo lint for /data/ prefixed face aliases.
|
|
2
|
+
|
|
3
|
+
Checks source names only (config only, no DB connection required) — called
|
|
4
|
+
from the validate path, which must stay connection-free.
|
|
12
5
|
"""
|
|
13
6
|
|
|
14
7
|
from __future__ import annotations
|
|
15
8
|
|
|
16
|
-
from dataclasses import dataclass
|
|
17
9
|
from pathlib import Path
|
|
18
|
-
from typing import TYPE_CHECKING
|
|
19
|
-
|
|
20
|
-
from dataface.core.registered_views.data_urls import (
|
|
21
|
-
data_schema_url,
|
|
22
|
-
data_source_url,
|
|
23
|
-
data_table_url,
|
|
24
|
-
)
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
25
11
|
|
|
26
12
|
if TYPE_CHECKING:
|
|
27
|
-
from dataface.agent_api.schema import SchemaResponse
|
|
28
13
|
from dataface.core.project import Project
|
|
29
|
-
from dataface.core.serve.alias_index import AliasIndex
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass
|
|
33
|
-
class DataPathInfo:
|
|
34
|
-
"""Canonical data URL with resolution status.
|
|
35
|
-
|
|
36
|
-
Wire shape (pinned by contract test — any change is a breaking API change):
|
|
37
|
-
{"url": "/data/src/schema/table/", "resolves_to": "generic"}
|
|
38
|
-
{"url": "...", "resolves_to": "authored", "face": "/sales/"}
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
url: str
|
|
42
|
-
resolves_to: Literal["generic", "authored"]
|
|
43
|
-
face: str | None = None
|
|
44
|
-
|
|
45
|
-
def to_dict(self) -> dict[str, Any]:
|
|
46
|
-
"""Serialize to the agent wire shape, omitting face when generic."""
|
|
47
|
-
d: dict[str, Any] = {"url": self.url, "resolves_to": self.resolves_to}
|
|
48
|
-
if self.face is not None:
|
|
49
|
-
d["face"] = self.face
|
|
50
|
-
return d
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def data_paths_for_source(
|
|
54
|
-
source: str,
|
|
55
|
-
*,
|
|
56
|
-
alias_index: AliasIndex,
|
|
57
|
-
) -> DataPathInfo:
|
|
58
|
-
"""Return the data URL info for a source.
|
|
59
|
-
|
|
60
|
-
The canonical URL is /data/<source>/. When a face aliases it the
|
|
61
|
-
resolves_to is 'authored' and face is the canonical face URL.
|
|
62
|
-
"""
|
|
63
|
-
url = data_source_url(source)
|
|
64
|
-
face = alias_index.lookup(url)
|
|
65
|
-
if face is not None:
|
|
66
|
-
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
67
|
-
return DataPathInfo(url=url, resolves_to="generic")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def data_paths_for_schema(
|
|
71
|
-
source: str,
|
|
72
|
-
schema: str,
|
|
73
|
-
*,
|
|
74
|
-
alias_index: AliasIndex,
|
|
75
|
-
) -> DataPathInfo:
|
|
76
|
-
"""Return the data URL info for a schema.
|
|
77
|
-
|
|
78
|
-
The canonical URL is /data/<source>/<schema>/. When a face aliases it
|
|
79
|
-
the resolves_to is 'authored' and face is the canonical face URL.
|
|
80
|
-
"""
|
|
81
|
-
url = data_schema_url(source, schema)
|
|
82
|
-
face = alias_index.lookup(url)
|
|
83
|
-
if face is not None:
|
|
84
|
-
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
85
|
-
return DataPathInfo(url=url, resolves_to="generic")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def data_paths_for_table(
|
|
89
|
-
source: str,
|
|
90
|
-
schema: str,
|
|
91
|
-
table: str,
|
|
92
|
-
*,
|
|
93
|
-
alias_index: AliasIndex,
|
|
94
|
-
) -> DataPathInfo:
|
|
95
|
-
"""Return the data URL info for a table.
|
|
96
|
-
|
|
97
|
-
The canonical URL is /data/<source>/<schema>/<table>/. When a face
|
|
98
|
-
aliases it the resolves_to is 'authored' and face is the canonical face URL.
|
|
99
|
-
"""
|
|
100
|
-
url = data_table_url(source, schema, table)
|
|
101
|
-
face = alias_index.lookup(url)
|
|
102
|
-
if face is not None:
|
|
103
|
-
return DataPathInfo(url=url, resolves_to="authored", face=face)
|
|
104
|
-
return DataPathInfo(url=url, resolves_to="generic")
|
|
105
14
|
|
|
106
15
|
|
|
107
16
|
def validate_data_aliases(
|
|
@@ -157,47 +66,6 @@ def validate_data_aliases(
|
|
|
157
66
|
return errors
|
|
158
67
|
|
|
159
68
|
|
|
160
|
-
def data_paths_list(
|
|
161
|
-
schema_response: SchemaResponse,
|
|
162
|
-
*,
|
|
163
|
-
alias_index: AliasIndex,
|
|
164
|
-
) -> list[DataPathInfo]:
|
|
165
|
-
"""Build a flat list of DataPathInfo from a SchemaResponse.
|
|
166
|
-
|
|
167
|
-
Iterates all sources → schemas → tables in the response and returns one
|
|
168
|
-
DataPathInfo per source, per schema, and per table, checking the alias
|
|
169
|
-
index for author overrides at each level.
|
|
170
|
-
"""
|
|
171
|
-
result: list[DataPathInfo] = []
|
|
172
|
-
for source_name, source_data in schema_response.sources.items():
|
|
173
|
-
result.append(data_paths_for_source(source_name, alias_index=alias_index))
|
|
174
|
-
for schema_name, schema_data in (source_data.get("schemas") or {}).items():
|
|
175
|
-
result.append(
|
|
176
|
-
data_paths_for_schema(source_name, schema_name, alias_index=alias_index)
|
|
177
|
-
)
|
|
178
|
-
for table_name in schema_data.get("tables") or {}:
|
|
179
|
-
result.append(
|
|
180
|
-
data_paths_for_table(
|
|
181
|
-
source_name,
|
|
182
|
-
schema_name,
|
|
183
|
-
table_name,
|
|
184
|
-
alias_index=alias_index,
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
return result
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def build_alias_index_for_project(project: Project) -> AliasIndex:
|
|
191
|
-
"""Build an AliasIndex from a project.
|
|
192
|
-
|
|
193
|
-
Intended for CLI commands (dft schema --data-paths) that need alias lookup
|
|
194
|
-
without starting a server.
|
|
195
|
-
"""
|
|
196
|
-
from dataface.core.serve.alias_index import AliasIndex
|
|
197
|
-
|
|
198
|
-
return AliasIndex.build(project)
|
|
199
|
-
|
|
200
|
-
|
|
201
69
|
def data_alias_errors_for_file(
|
|
202
70
|
face_file: Path,
|
|
203
71
|
source_names: frozenset[str],
|
dataface/agent_api/describe.py
CHANGED
|
@@ -18,7 +18,11 @@ from dataface.core.compile.models.query.normalized import (
|
|
|
18
18
|
SqlQuery,
|
|
19
19
|
ValuesQuery,
|
|
20
20
|
)
|
|
21
|
-
from dataface.core.errors import
|
|
21
|
+
from dataface.core.errors import (
|
|
22
|
+
DF_COMPILE_FILE_NOT_FOUND,
|
|
23
|
+
DF_UNKNOWN_INTERNAL,
|
|
24
|
+
StructuredError,
|
|
25
|
+
)
|
|
22
26
|
from dataface.core.project import Project
|
|
23
27
|
|
|
24
28
|
# Chart-encoding fields surfaced to agents. Covers the stable channel fields
|
|
@@ -90,13 +94,6 @@ class DescribeFaceArgs(BaseModel):
|
|
|
90
94
|
"""Describe the structure of a face file: queries (with type and source), charts (with query bindings and encoding), variables, and layout. Use this for orientation when picking up an unfamiliar dashboard — it reads the YAML and returns a curated narrative summary without executing any queries."""
|
|
91
95
|
|
|
92
96
|
path: Path = Field(description="Path to the dashboard YAML file to describe")
|
|
93
|
-
project_dir: Path | None = Field(
|
|
94
|
-
default=None,
|
|
95
|
-
description=(
|
|
96
|
-
"Project root for resolving relative paths. Optional on the wire; "
|
|
97
|
-
"the MCP server injects the project root when omitted."
|
|
98
|
-
),
|
|
99
|
-
)
|
|
100
97
|
|
|
101
98
|
|
|
102
99
|
class DescribeFaceResult(BaseModel):
|
|
@@ -208,15 +205,16 @@ def describe_face(path: Path, *, project: Project) -> DescribeFaceResult:
|
|
|
208
205
|
],
|
|
209
206
|
)
|
|
210
207
|
|
|
211
|
-
if not resolved.exists():
|
|
208
|
+
if not project.path_for_fspath(resolved).exists():
|
|
212
209
|
hint = no_project_hint(project.root)
|
|
213
210
|
return DescribeFaceResult(
|
|
214
211
|
success=False,
|
|
215
212
|
path=path,
|
|
216
213
|
errors=[
|
|
217
214
|
DatafaceError.from_code(
|
|
218
|
-
|
|
219
|
-
|
|
215
|
+
DF_COMPILE_FILE_NOT_FOUND,
|
|
216
|
+
path=str(resolved),
|
|
217
|
+
hint=hint,
|
|
220
218
|
).to_structured()
|
|
221
219
|
],
|
|
222
220
|
)
|
|
@@ -5,6 +5,8 @@ are slugified headings. Bare `docs()` returns the topic index (slug + one-line
|
|
|
5
5
|
description per H2). `docs(topic="<slug>")` returns one slice.
|
|
6
6
|
`docs(topic="all")` returns the whole file. `docs(topic="reference")` returns
|
|
7
7
|
the auto-generated field spec from yaml-reference.md.
|
|
8
|
+
`docs(topic="error-reference")` returns the auto-generated error reference from
|
|
9
|
+
error-reference.md.
|
|
8
10
|
`docs(search=...)` substring-matches across topics.
|
|
9
11
|
"""
|
|
10
12
|
|
|
@@ -25,9 +27,15 @@ _REFERENCE_FILE = (
|
|
|
25
27
|
importlib.resources.files("dataface.agent_api.docs") / "yaml-reference.md"
|
|
26
28
|
)
|
|
27
29
|
|
|
30
|
+
# Generated error reference ships inside the wheel at dataface/agent_api/docs/error-reference.md.
|
|
31
|
+
_ERROR_REFERENCE_FILE = (
|
|
32
|
+
importlib.resources.files("dataface.agent_api.docs") / "error-reference.md"
|
|
33
|
+
)
|
|
34
|
+
|
|
28
35
|
_TOPIC_RE = re.compile(r"^[a-z0-9-]+$")
|
|
29
36
|
_ALL_TOPIC = "all"
|
|
30
37
|
_REFERENCE_TOPIC = "reference"
|
|
38
|
+
_ERROR_REFERENCE_TOPIC = "error-reference"
|
|
31
39
|
|
|
32
40
|
DocsMode = Literal["index", "topic", "search"]
|
|
33
41
|
|
|
@@ -68,7 +76,7 @@ class DocsArgs(BaseModel):
|
|
|
68
76
|
|
|
69
77
|
topic: str | None = Field(
|
|
70
78
|
None,
|
|
71
|
-
description="Topic slug from the `dft docs` topic index (e.g. 'face', 'charts', 'all', 'reference')",
|
|
79
|
+
description="Topic slug from the `dft docs` topic index (e.g. 'face', 'charts', 'all', 'reference', 'error-reference')",
|
|
72
80
|
)
|
|
73
81
|
search: str | None = Field(
|
|
74
82
|
None, description="Full-text query — runs substring search across all topics"
|
|
@@ -101,6 +109,7 @@ def docs(
|
|
|
101
109
|
- ``docs(topic="face")`` — return content for one H2 section.
|
|
102
110
|
- ``docs(topic="all")`` — return the full ``DATAFACE_SYNTAX.md`` unsliced.
|
|
103
111
|
- ``docs(topic="reference")`` — return the auto-generated field spec.
|
|
112
|
+
- ``docs(topic="error-reference")`` — return the auto-generated error reference.
|
|
104
113
|
- ``docs(search="grid")`` — substring search across H2 sections.
|
|
105
114
|
"""
|
|
106
115
|
if topic is not None and search is not None:
|
|
@@ -145,6 +154,24 @@ def docs(
|
|
|
145
154
|
),
|
|
146
155
|
)
|
|
147
156
|
|
|
157
|
+
if topic == _ERROR_REFERENCE_TOPIC:
|
|
158
|
+
try:
|
|
159
|
+
content = _ERROR_REFERENCE_FILE.read_text(encoding="utf-8")
|
|
160
|
+
except FileNotFoundError:
|
|
161
|
+
return DocsResult(
|
|
162
|
+
success=False,
|
|
163
|
+
mode="topic",
|
|
164
|
+
errors=["error-reference.md is missing from the installed package."],
|
|
165
|
+
)
|
|
166
|
+
return DocsResult(
|
|
167
|
+
mode="topic",
|
|
168
|
+
topic=Topic(
|
|
169
|
+
id=_ERROR_REFERENCE_TOPIC,
|
|
170
|
+
title="Dataface Error Reference (generated)",
|
|
171
|
+
content=content,
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
148
175
|
if not _TOPIC_RE.fullmatch(topic):
|
|
149
176
|
return DocsResult(
|
|
150
177
|
success=False,
|
|
@@ -348,7 +348,6 @@ Authored patch for pie and donut charts.
|
|
|
348
348
|
| `subtitle` | str | ✓ | Chart subtitle displayed below the title. |
|
|
349
349
|
| `color` | str | ✓ | Color data channel: bare column name only. |
|
|
350
350
|
| `total` | [ChartTotal](#charttotal) | ✓ | Donut center total. |
|
|
351
|
-
| `labels` | [ChartLabels](#chartlabels) | ✓ | Per-row text annotations near each data anchor. |
|
|
352
351
|
| `style` | [PieChartStyle](#piechartstyle) | ✓ | Chart-local style overrides. |
|
|
353
352
|
|
|
354
353
|
<a id="kpichart"></a>
|
|
@@ -805,15 +804,6 @@ Donut center total — auto-rendered sum at the center of a donut, with author o
|
|
|
805
804
|
| `label` | str | ✓ | Caption text displayed below the center total value. |
|
|
806
805
|
| `format` | str \| [FormatConfig](#formatconfig) | ✓ | Number format (D3 spec, preset, or FormatConfig). |
|
|
807
806
|
|
|
808
|
-
<a id="chartlabels"></a>
|
|
809
|
-
## ChartLabels
|
|
810
|
-
Per-row text annotations rendered near each data anchor.
|
|
811
|
-
|
|
812
|
-
| Field | Type | Optional | Description |
|
|
813
|
-
|-------|------|:--------:|-------------|
|
|
814
|
-
| `template` | str | | Jinja2 template for the label text. Row fields are available as variables. |
|
|
815
|
-
| `where` | str | ✓ | Jinja2 boolean filter expression. Labels only render on rows where this is truthy. |
|
|
816
|
-
|
|
817
807
|
<a id="piechartstyle"></a>
|
|
818
808
|
## PieChartStyle
|
|
819
809
|
Authored overlay for PieChartStyle. Pie/donut chart style: geometry + total (flat) + marks sub-block.
|
|
@@ -1290,6 +1280,7 @@ Authored overlay for FooterStyle. Page footer chrome: visibility, attribution te
|
|
|
1290
1280
|
|-------|------|:--------:|-------------|
|
|
1291
1281
|
| `visible` | bool | ✓ | Show the footer attribution line. |
|
|
1292
1282
|
| `text` | str | ✓ | Attribution text shown in the footer. |
|
|
1283
|
+
| `link` | str | ✓ | URL the footer brand word 'dataface' links to; null renders plain text. |
|
|
1293
1284
|
| `font` | [FontStyle](#fontstyle) | ✓ | Footer text font style (size and color required). |
|
|
1294
1285
|
| `y_offset` | float | ✓ | Vertical offset from bottom edge in pixels. |
|
|
1295
1286
|
| `rule` | [FooterRule](#footerrule) | ✓ | Hairline rule above footer text; null disables the rule. |
|
|
@@ -1451,7 +1442,7 @@ Authored overlay for AxisStyle.
|
|
|
1451
1442
|
| `title` | [AxisElementStyle](#axiselementstyle) | ✓ | Axis title style. |
|
|
1452
1443
|
| `position` | enum: "left", "right", "top", "bottom", "auto" | ✓ | Axis position; auto flips y-axis when endpoint labels are present. |
|
|
1453
1444
|
| `categorical_orient` | str | ✓ | Orientation for categorical y-axis (e.g. 'left'); None skips this VL property. |
|
|
1454
|
-
| `mirror` | bool | ✓ | Draw the y-scale on both left and right edges (wide charts). Only meaningful on axis_y. |
|
|
1445
|
+
| `mirror` | bool \| [AxisMirrorStyle](#axismirrorstyle) | ✓ | Draw the y-scale on both left and right edges (wide charts). true mirrors the primary axis's label verbatim; an object (format/expr) relabels only the mirrored edge — e.g. a percent-of-total right axis next to an absolute-value left axis — while ticks stay aligned to the single shared scale. Only meaningful on axis_y. |
|
|
1455
1446
|
| `band_position` | float | ✓ | Band position within the step (0–1); None uses Vega-Lite's default. |
|
|
1456
1447
|
| `offset` | float | ✓ | Pixel offset of the axis from its default position; None means no offset. |
|
|
1457
1448
|
| `format` | str | ✓ | Tick value format string; None uses auto-format. |
|
|
@@ -1480,8 +1471,8 @@ Authored overlay for ScaleStyle. Scale configuration primitive.
|
|
|
1480
1471
|
| `quantize_count` | int | ✓ | Number of quantize buckets; None uses Vega-Lite's default. |
|
|
1481
1472
|
| `use_unaggregated_domain` | bool | ✓ | Use unaggregated domain for scale extent; None uses Vega-Lite's default. |
|
|
1482
1473
|
| `x_reverse` | bool | ✓ | Reverse the x-axis scale direction; None means no reversal. |
|
|
1483
|
-
| `type` |
|
|
1484
|
-
| `domain` | list[Any] | ✓ | Explicit scale domain
|
|
1474
|
+
| `type` | enum: "linear", "log", "pow", "sqrt", "symlog", "temporal" | ✓ | Scale type override. 'linear'/'log'/'pow'/'sqrt'/'symlog' pass straight through to Vega-Lite's quantitative scale.type. 'temporal' is a Dataface escape hatch for a cartesian x-axis: it forces a continuous temporal scale instead of the auto-inferred ordinal/nominal bucketed type, which is required before an authored ``domain`` of ISO dates can extend the visible range past the data extent. None lets Dataface/Vega-Lite infer from the field type. |
|
|
1475
|
+
| `domain` | list[Any] | ✓ | Explicit [low, high] scale domain; None lets Vega-Lite auto-determine from data. Must have exactly 2 elements — Dataface only wires a continuous range override through to Vega-Lite, not an explicit category enumeration. When ``type: temporal`` is set, both elements must be ISO-8601 date/datetime strings. |
|
|
1485
1476
|
| `nice` | bool | ✓ | Round scale domain to nice values; None uses Vega-Lite's default. |
|
|
1486
1477
|
| `padding` | float | ✓ | Unified scale padding shortcut; dispatches to band, point, or continuous padding per scale type. |
|
|
1487
1478
|
|
|
@@ -2205,7 +2196,7 @@ Authored overlay for AxisTicksStyle.
|
|
|
2205
2196
|
| `color` | str | ✓ | Tick color; None uses Vega-Lite's default. |
|
|
2206
2197
|
| `size` | float | ✓ | Tick length in pixels; None uses Vega-Lite's default. |
|
|
2207
2198
|
| `width` | float | ✓ | Tick stroke width in pixels; None uses Vega-Lite's default. |
|
|
2208
|
-
| `count` | int | ✓ | Target number of
|
|
2199
|
+
| `count` | int | ✓ | Target number of axis ticks. On a quantitative axis the renderer computes explicit tickValues so VL emits exactly this many ticks — VL's tickCount is advisory and frequently ignored there. On a temporal axis_x, count is passed through as VL's axis.tickCount, which VL's time-scale tick logic honours as a target. |
|
|
2209
2200
|
|
|
2210
2201
|
<a id="axiselementstyle"></a>
|
|
2211
2202
|
## AxisElementStyle
|
|
@@ -2231,6 +2222,15 @@ Authored overlay for AxisElementStyle. Axis label or title: font + padding + VL-
|
|
|
2231
2222
|
| `anchor` | enum: "start", "middle", "end" | ✓ | Label anchor position; None uses Vega-Lite's default. |
|
|
2232
2223
|
| `tilt_increments` | list[float] | ✓ | Descending tilt angles for smart label overlap on discrete x-axes; None disables the picker. |
|
|
2233
2224
|
|
|
2225
|
+
<a id="axismirrorstyle"></a>
|
|
2226
|
+
## AxisMirrorStyle
|
|
2227
|
+
Per-edge label override for the mirrored ``axis_y.mirror`` ghost axis.
|
|
2228
|
+
|
|
2229
|
+
| Field | Type | Optional | Description |
|
|
2230
|
+
|-------|------|:--------:|-------------|
|
|
2231
|
+
| `format` | str | ✓ | Tick value format string for the mirrored edge; None reuses the primary axis's format. |
|
|
2232
|
+
| `expr` | str | ✓ | Custom Vega expression for the mirrored edge's label text; None reuses the primary axis's label expression. |
|
|
2233
|
+
|
|
2234
2234
|
<a id="scalebandstyle"></a>
|
|
2235
2235
|
## ScaleBandStyle
|
|
2236
2236
|
Authored overlay for ScaleBandStyle. Band mark size constraints (min/max pixel width for band-scale marks).
|
|
@@ -2645,7 +2645,9 @@ Authored overlay for SliceLabelsStyle. Pie labels: typography + positioning offs
|
|
|
2645
2645
|
| `offset` | float | ✓ | Radial offset of slice labels from the arc in pixels. |
|
|
2646
2646
|
| `line_height` | float | ✓ | Line height for slice labels in pixels. Reserved vertical space above the disk is ``line_height × <rendered lines>`` per row, so the same value handles 1-line, 2-line, and multi-line templates. |
|
|
2647
2647
|
| `font` | [FontStyle](#fontstyle) | ✓ | Slice label font style overrides. Unset fields fall back to [`style.charts.font`](#chartsstyle). |
|
|
2648
|
-
| `default_template` | [LabelsDefaultTemplate](#labelsdefaulttemplate) | ✓ | Default Jinja templates for per-slice labels when
|
|
2648
|
+
| `default_template` | [LabelsDefaultTemplate](#labelsdefaulttemplate) | ✓ | Default Jinja templates for per-slice labels when template is not authored. |
|
|
2649
|
+
| `template` | str | ✓ | Jinja2 label template. Overrides default_template when authored. |
|
|
2650
|
+
| `where` | str | ✓ | Jinja2 boolean filter — labels only render on rows where this is truthy. |
|
|
2649
2651
|
|
|
2650
2652
|
<a id="tablerowrolestyle"></a>
|
|
2651
2653
|
## TableRowRoleStyle
|