dataface 0.1.5.dev48__py3-none-any.whl → 0.1.5.dev151__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 +10 -10
- dataface/_docs_site.py +1 -1
- dataface/agent_api/__init__.py +4 -6
- dataface/agent_api/_init_templates/README.md +3 -3
- dataface/agent_api/_init_templates/{guide.yml → guide.yaml} +3 -4
- dataface/agent_api/_paths.py +111 -63
- dataface/agent_api/dashboards.py +3 -2
- dataface/agent_api/describe.py +13 -8
- dataface/agent_api/describe_query.py +1 -1
- dataface/agent_api/docs/yaml-reference.md +254 -259
- dataface/agent_api/init.py +3 -36
- dataface/agent_api/inspect.py +4 -7
- dataface/agent_api/mcp_install.py +3 -3
- dataface/agent_api/project.py +224 -0
- dataface/agent_api/query.py +3 -2
- dataface/agent_api/surface_aliases.yaml +3 -3
- dataface/agent_api/validate.py +13 -8
- dataface/ai/mcp/server.py +4 -3
- dataface/ai/tools/__init__.py +16 -6
- dataface/cli/commands/describe.py +4 -1
- dataface/cli/commands/extension.py +21 -115
- dataface/cli/commands/init.py +13 -51
- dataface/cli/commands/inspect.py +2 -1
- dataface/cli/commands/mcp_init.py +23 -14
- dataface/cli/commands/query.py +54 -44
- dataface/cli/commands/render.py +18 -13
- dataface/cli/commands/serve.py +8 -9
- dataface/cli/commands/skills_init.py +3 -5
- dataface/cli/commands/validate.py +4 -1
- dataface/cli/main.py +47 -42
- dataface/core/compile/__init__.py +3 -3
- dataface/core/compile/chart_focus.py +5 -1
- dataface/core/compile/compiler.py +3 -4
- dataface/core/compile/config.py +17 -65
- dataface/core/compile/data_table_attachment.py +18 -18
- dataface/core/compile/introspection.py +9 -1
- dataface/core/compile/meta.py +2 -25
- dataface/core/compile/models/chart/authored.py +20 -240
- dataface/core/compile/models/chart/{compiled.py → normalized.py} +1 -1
- dataface/core/compile/{chart_resolved.py → models/chart/resolved.py} +9 -9
- dataface/core/compile/models/config.py +13 -10
- dataface/core/compile/models/face/{compiled.py → normalized.py} +7 -111
- dataface/core/compile/models/face/resolved.py +111 -0
- dataface/core/compile/models/palette.py +1 -1
- dataface/core/compile/models/primitives.py +1 -1
- dataface/core/compile/models/query/{compiled.py → normalized.py} +30 -13
- dataface/core/compile/models/refs.py +16 -10
- dataface/core/compile/models/style/authored.py +441 -11
- dataface/core/compile/models/style/{merged.py → resolved.py} +88 -86
- dataface/core/compile/models/style/{compiled.py → theme.py} +18 -205
- dataface/core/compile/models/variable/authored.py +27 -11
- dataface/core/compile/models/vega_lite/config.py +2 -14
- dataface/core/compile/normalize_charts.py +6 -4
- dataface/core/compile/normalize_layout.py +10 -6
- dataface/core/compile/normalize_queries.py +5 -2
- dataface/core/compile/normalize_variables.py +25 -7
- dataface/core/compile/normalizer.py +14 -14
- dataface/core/compile/palette.py +2 -2
- dataface/core/compile/parser.py +0 -1
- dataface/core/compile/schema_renderers/prompt.py +29 -11
- dataface/core/compile/sizing.py +29 -27
- dataface/core/compile/style_cascade.py +21 -21
- dataface/core/compile/typography.py +10 -6
- dataface/core/compile/validator.py +8 -7
- dataface/core/compile/variables.py +1 -1
- dataface/core/compile/vega_config.py +10 -27
- dataface/core/dashboard.py +5 -3
- dataface/core/defaults/default_config.yml +11 -0
- dataface/core/defaults/palettes/categorical/category-6-tonal-blue.yml +2 -2
- dataface/core/defaults/palettes/categorical/category-6-tonal-brown.yml +1 -1
- dataface/core/defaults/palettes/categorical/category-6-tonal-green.yml +1 -1
- dataface/core/defaults/palettes/categorical/category-6-tonal-orange.yml +1 -1
- dataface/core/defaults/palettes/categorical/category-6-tonal-purple.yml +1 -1
- dataface/core/execute/adapters/adapter_registry.py +9 -16
- dataface/core/execute/adapters/base.py +2 -2
- dataface/core/execute/adapters/csv_adapter.py +5 -5
- dataface/core/execute/adapters/dbt_adapter.py +14 -16
- dataface/core/execute/adapters/dbt_adapter_factory.py +8 -0
- dataface/core/execute/adapters/http_adapter.py +2 -2
- dataface/core/execute/adapters/metricflow_adapter.py +2 -2
- dataface/core/execute/adapters/schema_resolver_adapter.py +2 -2
- dataface/core/execute/adapters/sql_adapter.py +13 -17
- dataface/core/execute/adapters/values_adapter.py +2 -2
- dataface/core/execute/batch.py +3 -3
- dataface/core/execute/cache_keys.py +7 -2
- dataface/core/execute/duckdb_cache.py +2 -1
- dataface/core/execute/executor.py +6 -3
- dataface/core/execute/parallel.py +5 -2
- dataface/core/execute/setup_sql.py +1 -1
- dataface/core/execute/source_resolver.py +17 -4
- dataface/core/pack/models.py +1 -1
- dataface/core/project_roots.py +32 -51
- dataface/core/render/chart/callout.py +6 -6
- dataface/core/render/chart/decisions.py +1 -1
- dataface/core/render/chart/geo.py +11 -5
- dataface/core/render/chart/kpi.py +12 -9
- dataface/core/render/chart/pipeline.py +25 -19
- dataface/core/render/chart/profile.py +56 -45
- dataface/core/render/chart/render_single.py +6 -6
- dataface/core/render/chart/renderers.py +9 -9
- dataface/core/render/chart/rendering.py +19 -16
- dataface/core/render/chart/serialization.py +1 -1
- dataface/core/render/chart/spark.py +7 -7
- dataface/core/render/chart/spark_bar.py +6 -6
- dataface/core/render/chart/spec_builders.py +5 -3
- dataface/core/render/chart/standard_renderer.py +42 -54
- dataface/core/render/chart/table.py +22 -16
- dataface/core/render/chart/table_support.py +5 -3
- dataface/core/render/chart/type_inference.py +3 -3
- dataface/core/render/chart/validation.py +4 -1
- dataface/core/render/chart/vega_lite.py +9 -7
- dataface/core/render/chart/vega_lite_types.py +1 -1
- dataface/core/render/chart_interactivity.py +3 -3
- dataface/core/render/converters/chart.py +3 -3
- dataface/core/render/converters/html.py +4 -1
- dataface/core/render/faces.py +80 -26
- dataface/core/render/font_support.py +13 -44
- dataface/core/render/json_format.py +4 -1
- dataface/core/render/layout_sizing.py +9 -5
- dataface/core/render/layouts.py +11 -11
- dataface/core/render/placeholder.py +1 -1
- dataface/core/render/renderer.py +20 -2
- dataface/core/render/svg_utils.py +3 -3
- dataface/core/render/terminal.py +2 -2
- dataface/core/render/terminal_charts.py +1 -1
- dataface/core/render/terminal_layouts.py +4 -1
- dataface/core/render/text/case.py +3 -3
- dataface/core/render/text_format.py +4 -1
- dataface/core/render/utils.py +1 -1
- dataface/core/render/variable_controls.py +14 -9
- dataface/core/render/warnings/bar_color_1_to_1_with_x.py +3 -3
- dataface/core/render/warnings/base.py +1 -1
- dataface/core/render/warnings/redundant_encoding.py +69 -0
- dataface/core/render/warnings/registry.py +2 -0
- dataface/core/render/yaml_format.py +4 -1
- dataface/core/resolve_face.py +13 -13
- dataface/core/scoped_paths.py +14 -29
- dataface/core/serve/server.py +8 -1
- dataface/integrations/highlighting.py +112 -181
- dataface/integrations/markdown.py +4 -2
- {dataface-0.1.5.dev48.dist-info → dataface-0.1.5.dev151.dist-info}/METADATA +7 -8
- {dataface-0.1.5.dev48.dist-info → dataface-0.1.5.dev151.dist-info}/RECORD +145 -145
- dataface/agent_api/_init_templates/agents_dft_snippet.md +0 -26
- dataface/agent_api/_project_agents_md.py +0 -43
- dataface/core/compile/models/theme.py +0 -362
- {dataface-0.1.5.dev48.dist-info → dataface-0.1.5.dev151.dist-info}/WHEEL +0 -0
- {dataface-0.1.5.dev48.dist-info → dataface-0.1.5.dev151.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.5.dev48.dist-info → dataface-0.1.5.dev151.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 'SELECT 1'
|
|
22
|
+
dft query <your_source> 'SELECT 1'
|
|
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
|
-
default:
|
|
141
|
+
# No default: starts on All regions
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
Reference variables inside queries with bare `{{ region }}` — no `variables.` prefix.
|
|
@@ -461,7 +461,6 @@ charts:
|
|
|
461
461
|
|
|
462
462
|
# Style + behavior
|
|
463
463
|
sort: { by: total, order: desc }
|
|
464
|
-
stack: zero # false | zero | normalize | center
|
|
465
464
|
format: integer # named alias or raw D3 spec (e.g. ",.0f")
|
|
466
465
|
x_label: "Month"
|
|
467
466
|
y_label: "Revenue (USD)"
|
|
@@ -470,7 +469,7 @@ charts:
|
|
|
470
469
|
|
|
471
470
|
style: # Chart-local style patch (typed; not raw CSS) — paint only
|
|
472
471
|
orientation: vertical # style: does NOT accept height or aspect_ratio
|
|
473
|
-
stack: true
|
|
472
|
+
stack: zero # false | true | zero | normalize | center
|
|
474
473
|
```
|
|
475
474
|
|
|
476
475
|
### Chart types (29 total)
|
|
@@ -523,7 +522,6 @@ All chart types accept the channels and style fields below — but each type rej
|
|
|
523
522
|
| `longitude` | string | Longitude field (point/bubble map) |
|
|
524
523
|
| `background` | string \| object | Background channel — color, `{value}`, `{field, scale/when}`, or map layer |
|
|
525
524
|
| `sort` | object | `{by, order}` — categorical sort |
|
|
526
|
-
| `stack` | bool \| enum | `false`, `zero`, `normalize`, or `center` |
|
|
527
525
|
| `link` | string | Click-through URL template for drill-down links |
|
|
528
526
|
| `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"`. |
|
|
529
527
|
| `layers` | list | Layer definitions for `type: layered` (see [Composition](#composition)) |
|
|
@@ -561,7 +559,8 @@ color: segment # Optional: one line per segment
|
|
|
561
559
|
type: area
|
|
562
560
|
x: date
|
|
563
561
|
y: value
|
|
564
|
-
|
|
562
|
+
style:
|
|
563
|
+
stack: normalize # 100% stacked
|
|
565
564
|
|
|
566
565
|
# scatter — x and y numeric
|
|
567
566
|
type: scatter
|
|
@@ -695,7 +694,6 @@ layers:
|
|
|
695
694
|
- type: bar # bar | line | area | circle | square | tick | rule | trail | rect | image | scatter
|
|
696
695
|
y: actual
|
|
697
696
|
label: Actual
|
|
698
|
-
fill: "#0369a1" # static paint (use fill:, not color: {value:})
|
|
699
697
|
- type: line
|
|
700
698
|
y: target
|
|
701
699
|
label: Target
|
|
@@ -704,7 +702,7 @@ layers:
|
|
|
704
702
|
title: "Target"
|
|
705
703
|
```
|
|
706
704
|
|
|
707
|
-
Each layer accepts: `type`, `query` (optional layer-specific query), `x`, `y`, `label`, `color` (data channel, bare field name), `
|
|
705
|
+
Each layer accepts: `type`, `query` (optional layer-specific query), `x`, `y`, `label`, `color` (data channel, bare field name), `axis_y`. Vega-Lite `encoding:` is not allowed inside a layer — use the typed channels.
|
|
708
706
|
|
|
709
707
|
### Conditional formatting
|
|
710
708
|
|
|
@@ -846,7 +844,7 @@ variables:
|
|
|
846
844
|
description: "Restrict every query to one region."
|
|
847
845
|
options:
|
|
848
846
|
static: [US, EU, APAC]
|
|
849
|
-
default:
|
|
847
|
+
# No default: starts on All regions
|
|
850
848
|
```
|
|
851
849
|
|
|
852
850
|
Input types (14 total):
|
|
@@ -894,7 +892,7 @@ variables:
|
|
|
894
892
|
product:
|
|
895
893
|
input: select
|
|
896
894
|
options:
|
|
897
|
-
static: [
|
|
895
|
+
static: [Electronics, Clothing] # Hardcoded list; blank -- All -- is implicit
|
|
898
896
|
# OR
|
|
899
897
|
query: products_list # Query whose first column is the option list
|
|
900
898
|
column: product_name # Optional: which column in that query
|
|
@@ -907,6 +905,8 @@ variables:
|
|
|
907
905
|
|
|
908
906
|
Top-level option-source binding (alternative to `options:`): `column`, `query`, `dimension` (MetricFlow), `measure` (MetricFlow), `model` (dbt).
|
|
909
907
|
|
|
908
|
+
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'`.
|
|
909
|
+
|
|
910
910
|
Disabled forms:
|
|
911
911
|
|
|
912
912
|
```yaml
|
dataface/_docs_site.py
CHANGED
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._paths import (
|
|
41
|
-
RenderSetup as RenderSetup,
|
|
42
|
-
setup_render_for_face as setup_render_for_face,
|
|
43
|
-
)
|
|
44
40
|
from dataface.agent_api.dashboards import (
|
|
45
41
|
RenderedDashboard as RenderedDashboard,
|
|
46
42
|
render_dashboard as render_dashboard,
|
|
@@ -55,8 +51,10 @@ from dataface.agent_api.init import (
|
|
|
55
51
|
InitResult as InitResult,
|
|
56
52
|
init_project as init_project,
|
|
57
53
|
)
|
|
54
|
+
from dataface.agent_api.project import Project as Project
|
|
58
55
|
from dataface.agent_api.query import QueryFaceResult, query_face
|
|
59
56
|
from dataface.agent_api.validate import ValidateDashboardArgs, ValidateResult
|
|
57
|
+
from dataface.core.compile.config import ProjectSourcesConfig as ProjectSourcesConfig
|
|
60
58
|
|
|
61
59
|
__all__ = [
|
|
62
60
|
"DescribeFaceArgs",
|
|
@@ -65,8 +63,9 @@ __all__ = [
|
|
|
65
63
|
"DocsResult",
|
|
66
64
|
"DocsSearchHit",
|
|
67
65
|
"InitResult",
|
|
66
|
+
"Project",
|
|
67
|
+
"ProjectSourcesConfig",
|
|
68
68
|
"QueryFaceResult",
|
|
69
|
-
"RenderSetup",
|
|
70
69
|
"RenderedDashboard",
|
|
71
70
|
"Topic",
|
|
72
71
|
"ValidateDashboardArgs",
|
|
@@ -75,5 +74,4 @@ __all__ = [
|
|
|
75
74
|
"init_project",
|
|
76
75
|
"query_face",
|
|
77
76
|
"render_dashboard",
|
|
78
|
-
"setup_render_for_face",
|
|
79
77
|
]
|
|
@@ -10,7 +10,7 @@ for your data project.
|
|
|
10
10
|
|
|
11
11
|
## Authoring modes
|
|
12
12
|
|
|
13
|
-
**YAML (`.
|
|
13
|
+
**YAML (`.yaml`)** — structured dashboards with queries, charts, and layout.
|
|
14
14
|
See the [dataface guide](guide) for a working example covering queries, charts, and variables.
|
|
15
15
|
|
|
16
16
|
**Markdown (`.md`)** — prose pages like this one. Add YAML frontmatter for
|
|
@@ -18,7 +18,7 @@ queries and charts, then embed them inline with `{% raw %}{{ chart my_chart }}{%
|
|
|
18
18
|
|
|
19
19
|
## Next steps
|
|
20
20
|
|
|
21
|
-
1. Open `faces/guide.
|
|
21
|
+
1. Open `faces/guide.yaml` and tweak the content.
|
|
22
22
|
2. Run `dft serve` and open the URL it prints.
|
|
23
|
-
3. Add new `.
|
|
23
|
+
3. Add new `.yaml` or `.md` files under `faces/` — they appear automatically.
|
|
24
24
|
4. Run `dft validate` to validate your face YAML for errors.
|
|
@@ -20,7 +20,7 @@ description: "A tour of Dataface: queries, charts, layout, KPIs, and variables."
|
|
|
20
20
|
text: |
|
|
21
21
|
Welcome to **Dataface** — dashboards as YAML files.
|
|
22
22
|
|
|
23
|
-
This file is your starter guide. Edit it, add new `.
|
|
23
|
+
This file is your starter guide. Edit it, add new `.yaml` files under
|
|
24
24
|
`faces/`, and run `dft serve` to preview everything in your browser.
|
|
25
25
|
|
|
26
26
|
# ── Queries ───────────────────────────────────────────────────────────────────
|
|
@@ -105,14 +105,13 @@ charts:
|
|
|
105
105
|
# ── Variables ─────────────────────────────────────────────────────────────────
|
|
106
106
|
# Variables add interactive dropdowns. Wire them to SQL queries with {{ varname }}.
|
|
107
107
|
# With `type: values` data above they show the UI but don't filter — swap to SQL
|
|
108
|
-
# queries and use `WHERE
|
|
108
|
+
# queries and use `WHERE {{ filter('category', segment) }}` to make them live.
|
|
109
109
|
variables:
|
|
110
110
|
segment:
|
|
111
111
|
label: Segment
|
|
112
112
|
input: select
|
|
113
|
-
default: All
|
|
114
113
|
options:
|
|
115
|
-
static: [
|
|
114
|
+
static: [Software, Services, Hardware]
|
|
116
115
|
|
|
117
116
|
# ── Layout ────────────────────────────────────────────────────────────────────
|
|
118
117
|
# `rows:` stacks content vertically.
|
dataface/agent_api/_paths.py
CHANGED
|
@@ -5,23 +5,67 @@ from __future__ import annotations
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from dataface.core.execute.adapters import
|
|
8
|
+
from dataface.core.execute.adapters import (
|
|
9
|
+
AdapterRegistry as AdapterRegistry,
|
|
10
|
+
build_adapter_registry as build_adapter_registry,
|
|
11
|
+
)
|
|
9
12
|
from dataface.core.project_roots import (
|
|
10
|
-
discover_render_context,
|
|
11
|
-
discovery_boundary_for_face,
|
|
12
|
-
|
|
13
|
+
discover_render_context as discover_render_context,
|
|
14
|
+
discovery_boundary_for_face as discovery_boundary_for_face,
|
|
15
|
+
find_dft_root as find_dft_root,
|
|
13
16
|
)
|
|
14
17
|
from dataface.core.scoped_paths import (
|
|
15
|
-
|
|
16
|
-
project_root_for as project_root_for,
|
|
17
|
-
resolve_scoped_path as resolve_scoped_path,
|
|
18
|
+
resolve_scoped_path as _core_resolve_scoped_path,
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
22
|
+
def resolve_project_dir(project_dir: Path | None) -> Path:
|
|
23
|
+
"""Resolve the caller's `project_dir`, walking up from cwd when omitted.
|
|
24
|
+
|
|
25
|
+
Boundary helper: every agent_api / CLI callsite that today forwards a
|
|
26
|
+
possibly-None `project_dir` into a core helper routes through this so the
|
|
27
|
+
cwd read stays at the agent_api boundary, not inside `dataface/core`.
|
|
28
|
+
"""
|
|
29
|
+
if project_dir is not None:
|
|
30
|
+
return project_dir.resolve()
|
|
31
|
+
cwd = Path.cwd().resolve()
|
|
32
|
+
return find_dft_root(cwd) or cwd
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def resolve_project_dir_from_paths(
|
|
36
|
+
paths: list[Path] | None, project_dir: Path | None
|
|
37
|
+
) -> Path:
|
|
38
|
+
"""Resolve project_dir for a multi-path CLI verb (validate / describe).
|
|
39
|
+
|
|
40
|
+
Explicit `--project-dir` wins. Otherwise, when all supplied paths are
|
|
41
|
+
absolute, walk up from the first path's parent to anchor on the project
|
|
42
|
+
that contains the file. Otherwise fall back to a cwd walk.
|
|
43
|
+
"""
|
|
44
|
+
if project_dir is not None:
|
|
45
|
+
return project_dir.resolve()
|
|
46
|
+
if paths and all(p.is_absolute() for p in paths):
|
|
47
|
+
anchor = paths[0].parent.resolve()
|
|
48
|
+
return find_dft_root(anchor) or anchor
|
|
49
|
+
return resolve_project_dir(None)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def resolve_scoped_path(path: Path, project_dir: Path | None = None) -> Path:
|
|
53
|
+
"""agent_api wrapper: resolves `None → cwd-walked root` at the boundary,
|
|
54
|
+
then delegates to `core.scoped_paths.resolve_scoped_path`.
|
|
55
|
+
|
|
56
|
+
Absolute paths with no explicit `project_dir` resolve directly (no
|
|
57
|
+
containment check) — the caller supplied the path, so no project context is
|
|
58
|
+
needed to validate containment.
|
|
59
|
+
"""
|
|
60
|
+
if path.is_absolute() and project_dir is None:
|
|
61
|
+
return path.resolve()
|
|
62
|
+
return _core_resolve_scoped_path(path, resolve_project_dir(project_dir))
|
|
63
|
+
|
|
64
|
+
|
|
21
65
|
def no_project_hint(project_dir: Path | None) -> str:
|
|
22
66
|
"""Return a hint string when no project marker is found, else empty string."""
|
|
23
67
|
check = project_dir if project_dir is not None else Path.cwd()
|
|
24
|
-
if
|
|
68
|
+
if find_dft_root(check) is not None:
|
|
25
69
|
return ""
|
|
26
70
|
return (
|
|
27
71
|
f" No Dataface project marker found at or above {check}."
|
|
@@ -30,89 +74,93 @@ def no_project_hint(project_dir: Path | None) -> str:
|
|
|
30
74
|
|
|
31
75
|
|
|
32
76
|
@dataclass(frozen=True)
|
|
33
|
-
class
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
Bundles the scoped path, scoped base, and adapter registry produced by
|
|
37
|
-
discovery. CLI/serve callers building this from a face on disk avoid
|
|
38
|
-
duplicating discovery boilerplate; tests can construct one directly.
|
|
39
|
-
"""
|
|
77
|
+
class FaceRenderContext:
|
|
78
|
+
"""Resources resolved from a face path + project root for rendering."""
|
|
40
79
|
|
|
41
|
-
|
|
80
|
+
face_file: Path
|
|
42
81
|
scoped_path: Path | None
|
|
43
|
-
scoped_base: Path
|
|
82
|
+
scoped_base: Path
|
|
83
|
+
project_root: Path
|
|
84
|
+
output_dir: Path
|
|
85
|
+
adapter_registry: AdapterRegistry
|
|
44
86
|
|
|
45
87
|
|
|
46
|
-
def
|
|
88
|
+
def build_face_render_context(
|
|
47
89
|
face_path: Path,
|
|
48
90
|
project_dir: Path | None = None,
|
|
49
91
|
*,
|
|
50
92
|
read_only: bool = True,
|
|
51
|
-
) ->
|
|
52
|
-
"""
|
|
93
|
+
) -> FaceRenderContext:
|
|
94
|
+
"""Resolve a face path, walk for dbt context, and build the adapter registry.
|
|
53
95
|
|
|
54
|
-
|
|
55
|
-
the
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
``project_dir=None`` means "walk freely from the face's parent" — used by
|
|
97
|
+
the CLI when ``--project-dir`` is omitted so a face under a dbt sub-project
|
|
98
|
+
still anchors on that sub-project's root. A given ``project_dir`` is
|
|
99
|
+
authoritative; the walk only contributes the dbt project path.
|
|
58
100
|
"""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
if face_path.is_absolute():
|
|
102
|
+
face_file = face_path.resolve()
|
|
103
|
+
elif ".." in face_path.parts:
|
|
104
|
+
face_file = (Path.cwd() / face_path).resolve()
|
|
62
105
|
else:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
106
|
+
anchor = (
|
|
107
|
+
project_dir.resolve()
|
|
108
|
+
if project_dir is not None
|
|
109
|
+
else resolve_project_dir(None)
|
|
110
|
+
)
|
|
111
|
+
face_file = (anchor / face_path).resolve()
|
|
112
|
+
|
|
113
|
+
walk_root, dbt_project_path = discover_render_context(
|
|
66
114
|
face_file.parent,
|
|
67
115
|
discovery_boundary_for_face(face_file.parent, project_dir),
|
|
68
116
|
)
|
|
69
|
-
if project_dir
|
|
70
|
-
project_root = project_dir.resolve()
|
|
117
|
+
project_root = project_dir.resolve() if project_dir is not None else walk_root
|
|
71
118
|
|
|
72
119
|
try:
|
|
73
120
|
scoped_path: Path | None = face_file.relative_to(project_root)
|
|
74
|
-
scoped_base: Path | None = project_root
|
|
75
121
|
except ValueError:
|
|
76
122
|
scoped_path = face_file
|
|
77
|
-
scoped_base = None
|
|
78
123
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
scoped_path=scoped_path,
|
|
88
|
-
scoped_base=scoped_base,
|
|
124
|
+
return FaceRenderContext(
|
|
125
|
+
face_file=face_file,
|
|
126
|
+
scoped_path=scoped_path,
|
|
127
|
+
scoped_base=project_root,
|
|
128
|
+
project_root=project_root,
|
|
129
|
+
output_dir=project_root,
|
|
130
|
+
adapter_registry=build_adapter_registry(
|
|
131
|
+
project_root, read_only=read_only, dbt_project_path=dbt_project_path
|
|
89
132
|
),
|
|
90
|
-
face_file,
|
|
91
133
|
)
|
|
92
134
|
|
|
93
135
|
|
|
94
|
-
|
|
136
|
+
@dataclass(frozen=True)
|
|
137
|
+
class YamlRenderContext:
|
|
138
|
+
"""Resources resolved from a project root for rendering inline YAML."""
|
|
139
|
+
|
|
140
|
+
project_root: Path
|
|
141
|
+
output_dir: Path
|
|
142
|
+
adapter_registry: AdapterRegistry
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def build_yaml_render_context(
|
|
95
146
|
project_dir: Path | None = None,
|
|
96
147
|
*,
|
|
97
148
|
read_only: bool = True,
|
|
98
|
-
) ->
|
|
99
|
-
"""
|
|
149
|
+
) -> YamlRenderContext:
|
|
150
|
+
"""Walk for dbt context and build the adapter registry for inline YAML.
|
|
100
151
|
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
``project_dir=None`` walks from cwd to discover the project root; a given
|
|
153
|
+
``project_dir`` is authoritative.
|
|
103
154
|
"""
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if project_dir:
|
|
107
|
-
project_root = output_dir
|
|
108
|
-
|
|
109
|
-
adapter_registry = build_adapter_registry(
|
|
110
|
-
project_root,
|
|
111
|
-
read_only=read_only,
|
|
112
|
-
dbt_project_path=dbt_project_path,
|
|
155
|
+
anchor = (
|
|
156
|
+
project_dir.resolve() if project_dir is not None else resolve_project_dir(None)
|
|
113
157
|
)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
158
|
+
walk_root, dbt_project_path = discover_render_context(anchor, None)
|
|
159
|
+
project_root = anchor if project_dir is not None else walk_root
|
|
160
|
+
return YamlRenderContext(
|
|
161
|
+
project_root=project_root,
|
|
162
|
+
output_dir=project_root,
|
|
163
|
+
adapter_registry=build_adapter_registry(
|
|
164
|
+
project_root, read_only=read_only, dbt_project_path=dbt_project_path
|
|
165
|
+
),
|
|
118
166
|
)
|
dataface/agent_api/dashboards.py
CHANGED
|
@@ -108,7 +108,7 @@ class RenderDashboardArgs(BaseModel):
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
def list_dashboards(
|
|
111
|
-
directory: Path
|
|
111
|
+
directory: Path,
|
|
112
112
|
recursive: bool = True,
|
|
113
113
|
) -> ListDashboardsResult:
|
|
114
114
|
"""List all available dashboards in a directory."""
|
|
@@ -197,8 +197,9 @@ def list_dashboards(
|
|
|
197
197
|
|
|
198
198
|
def get_dashboard(
|
|
199
199
|
path: Path,
|
|
200
|
+
*,
|
|
201
|
+
project_dir: Path,
|
|
200
202
|
include_raw: bool = False,
|
|
201
|
-
project_dir: Path | None = None,
|
|
202
203
|
) -> CompiledDashboard:
|
|
203
204
|
"""Get the compiled structure of a dashboard."""
|
|
204
205
|
try:
|
dataface/agent_api/describe.py
CHANGED
|
@@ -7,11 +7,11 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
9
|
|
|
10
|
-
from dataface.core.compile.models.chart.
|
|
10
|
+
from dataface.core.compile.models.chart.normalized import (
|
|
11
11
|
Chart,
|
|
12
12
|
)
|
|
13
|
-
from dataface.core.compile.models.face.
|
|
14
|
-
from dataface.core.compile.models.query.
|
|
13
|
+
from dataface.core.compile.models.face.normalized import Layout
|
|
14
|
+
from dataface.core.compile.models.query.normalized import (
|
|
15
15
|
CsvQuery,
|
|
16
16
|
DbtModelQuery,
|
|
17
17
|
HttpQuery,
|
|
@@ -84,7 +84,11 @@ class DescribeFaceArgs(BaseModel):
|
|
|
84
84
|
|
|
85
85
|
path: Path = Field(description="Path to the dashboard YAML file to describe")
|
|
86
86
|
project_dir: Path | None = Field(
|
|
87
|
-
None,
|
|
87
|
+
default=None,
|
|
88
|
+
description=(
|
|
89
|
+
"Project root for resolving relative paths. Optional on the wire; "
|
|
90
|
+
"the MCP server injects ctx.default_project_dir or cwd when omitted."
|
|
91
|
+
),
|
|
88
92
|
)
|
|
89
93
|
|
|
90
94
|
|
|
@@ -186,7 +190,7 @@ def _encoding_for_chart(chart: Chart) -> dict[str, Any]:
|
|
|
186
190
|
# ---------------------------------------------------------------------------
|
|
187
191
|
|
|
188
192
|
|
|
189
|
-
def describe_face(path: Path, project_dir: Path
|
|
193
|
+
def describe_face(path: Path, *, project_dir: Path) -> DescribeFaceResult:
|
|
190
194
|
"""Describe the structure of a face: queries, charts, variables, layout."""
|
|
191
195
|
from dataface.agent_api._paths import no_project_hint, resolve_scoped_path
|
|
192
196
|
from dataface.core.compile.compiler import compile_file
|
|
@@ -300,7 +304,8 @@ def describe_face(path: Path, project_dir: Path | None = None) -> DescribeFaceRe
|
|
|
300
304
|
|
|
301
305
|
def describe_paths(
|
|
302
306
|
paths: list[Path],
|
|
303
|
-
|
|
307
|
+
*,
|
|
308
|
+
project_dir: Path,
|
|
304
309
|
) -> list[DescribeFaceResult]:
|
|
305
310
|
"""Describe N face files / directories.
|
|
306
311
|
|
|
@@ -310,13 +315,13 @@ def describe_paths(
|
|
|
310
315
|
"""
|
|
311
316
|
out: list[DescribeFaceResult] = []
|
|
312
317
|
for p in paths:
|
|
313
|
-
out.extend(_describe_one_path(p, project_dir
|
|
318
|
+
out.extend(_describe_one_path(p, project_dir))
|
|
314
319
|
return out
|
|
315
320
|
|
|
316
321
|
|
|
317
322
|
def _describe_one_path(
|
|
318
323
|
path: Path,
|
|
319
|
-
project_dir: Path
|
|
324
|
+
project_dir: Path,
|
|
320
325
|
) -> list[DescribeFaceResult]:
|
|
321
326
|
"""Per-argv expansion: file → [one], dir → walk."""
|
|
322
327
|
# WHY: dataface.core.inspect.manifest_utils triggers the inspect package
|
|
@@ -109,7 +109,7 @@ def _duckdb_describe(
|
|
|
109
109
|
sql: str, source: str | None, adapter_registry: AdapterRegistry
|
|
110
110
|
) -> list[DescribeQueryColumn]:
|
|
111
111
|
"""Run DESCRIBE ({sql}) via the registry's read-only SqlAdapter."""
|
|
112
|
-
from dataface.core.compile.models.query.
|
|
112
|
+
from dataface.core.compile.models.query.normalized import SqlQuery
|
|
113
113
|
|
|
114
114
|
result = adapter_registry.execute(SqlQuery(sql=f"DESCRIBE ({sql})", source=source))
|
|
115
115
|
if result.error:
|