dataface 0.1.6.dev476__py3-none-any.whl → 0.1.6.dev558__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 +1 -0
- dataface/agent_api/describe.py +1 -1
- dataface/agent_api/describe_query.py +2 -2
- dataface/agent_api/docs/yaml-reference.md +25 -11
- dataface/agent_api/project_session.py +8 -1
- dataface/agent_api/render_face.py +12 -5
- dataface/agent_api/validate.py +53 -2
- dataface/ai/skills/dashboard-build/SKILL.md +2 -0
- dataface/ai/skills/dashboard-design/SKILL.md +4 -0
- dataface/ai/skills/two-by-two-grid-overview/SKILL.md +4 -3
- dataface/cli/commands/serve.py +31 -6
- dataface/core/compile/__init__.py +2 -4
- dataface/core/compile/compiler.py +75 -40
- dataface/core/compile/config.py +154 -224
- dataface/core/compile/data_table_attachment.py +47 -40
- dataface/core/compile/inherit_resolver.py +1 -1
- dataface/core/compile/introspection.py +1 -8
- dataface/core/compile/merge.py +767 -0
- dataface/core/compile/meta.py +22 -55
- dataface/core/compile/models/chart/authored.py +7 -6
- dataface/core/compile/models/chart/resolved.py +4 -2
- dataface/core/compile/models/config.py +21 -126
- dataface/core/compile/models/face/authored.py +132 -68
- dataface/core/compile/models/face/patch.py +28 -0
- dataface/core/compile/models/factories.py +38 -18
- dataface/core/compile/models/markers.py +43 -31
- dataface/core/compile/models/primitives.py +6 -6
- dataface/core/compile/models/source.py +4 -4
- dataface/core/compile/models/style/authored.py +6 -5
- dataface/core/compile/models/style/resolved.py +23 -87
- dataface/core/compile/models/style/theme.py +87 -53
- dataface/core/compile/normalize_charts.py +38 -4
- dataface/core/compile/normalize_layout.py +39 -41
- dataface/core/compile/normalize_queries.py +6 -6
- dataface/core/compile/normalizer.py +43 -52
- dataface/core/compile/sizing.py +31 -32
- dataface/core/compile/sources.py +3 -2
- dataface/core/compile/style_cascade.py +28 -86
- dataface/core/compile/typography.py +15 -8
- dataface/core/compile/vega_config.py +3 -3
- dataface/core/compile/yaml_error_formatter.py +35 -5
- dataface/core/dashboard.py +4 -4
- dataface/core/defaults/default_config.yml +17 -30
- dataface/core/defaults/themes/_base.yaml +10 -1
- dataface/core/errors/__init__.py +2 -0
- dataface/core/errors/codes_compile.py +27 -0
- dataface/core/execute/adapters/__init__.py +2 -0
- dataface/core/execute/adapters/adapter_registry.py +68 -24
- dataface/core/execute/adapters/base.py +13 -0
- dataface/core/execute/adapters/dbt_adapter.py +3 -2
- dataface/core/execute/adapters/duckdb_adapter.py +503 -0
- dataface/core/execute/adapters/sql_adapter.py +56 -468
- dataface/core/execute/source_resolver.py +69 -4
- dataface/core/inspect/renderer.py +2 -2
- dataface/core/project.py +45 -11
- dataface/core/registered_views/render_pipeline.py +1 -1
- dataface/core/render/board_links.py +37 -18
- dataface/core/render/chart/kpi.py +23 -4
- dataface/core/render/chart/pipeline.py +1 -2
- dataface/core/render/chart/render_single.py +11 -4
- dataface/core/render/chart/rendering.py +4 -8
- dataface/core/render/chart/standard_renderer.py +447 -123
- dataface/core/render/chart/table.py +44 -12
- dataface/core/render/chart/time_unit_detect.py +4 -4
- dataface/core/render/chrome_css.py +7 -3
- dataface/core/render/face_api.py +8 -5
- dataface/core/render/faces.py +31 -31
- dataface/core/render/renderer.py +4 -5
- dataface/core/resolve_face.py +14 -16
- dataface/core/serve/server.py +4 -4
- dataface/core/validate.py +4 -4
- dataface/integrations/markdown.py +2 -2
- {dataface-0.1.6.dev476.dist-info → dataface-0.1.6.dev558.dist-info}/METADATA +1 -1
- {dataface-0.1.6.dev476.dist-info → dataface-0.1.6.dev558.dist-info}/RECORD +77 -75
- dataface/core/serve/bootstrap.py +0 -69
- {dataface-0.1.6.dev476.dist-info → dataface-0.1.6.dev558.dist-info}/WHEEL +0 -0
- {dataface-0.1.6.dev476.dist-info → dataface-0.1.6.dev558.dist-info}/entry_points.txt +0 -0
- {dataface-0.1.6.dev476.dist-info → dataface-0.1.6.dev558.dist-info}/licenses/LICENSE +0 -0
dataface/DATAFACE_SYNTAX.md
CHANGED
|
@@ -1119,6 +1119,7 @@ The registry is being filled out incrementally — some compile-time and variabl
|
|
|
1119
1119
|
- **`DF-COMPILE-SOURCE-REQUIRED`** — a SQL query has no `source:` and no default is configured. Set the source on the query (`source: my_db`), at the face level (`source:` or `sources.default`), or project-wide via `sources.default` in `dataface.yml`.
|
|
1120
1120
|
- **`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.
|
|
1121
1121
|
- **`DF-COMPILE-EXTRA-FIELD`** — the face YAML contains a field not recognised by the schema. Remove it or check the schema for supported keys.
|
|
1122
|
+
- **`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`.
|
|
1122
1123
|
- **`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.
|
|
1123
1124
|
|
|
1124
1125
|
### Render errors
|
dataface/agent_api/describe.py
CHANGED
|
@@ -356,7 +356,7 @@ def _describe_one_path(
|
|
|
356
356
|
for pf in project.iter_faces(under=under, recursive=True)
|
|
357
357
|
if pf.is_yaml
|
|
358
358
|
and not pf.is_private
|
|
359
|
-
and not pf.
|
|
359
|
+
and not pf.parent.file(INSPECT_TEMPLATE_MANIFEST).exists()
|
|
360
360
|
)
|
|
361
361
|
if not faces:
|
|
362
362
|
return [
|
|
@@ -75,7 +75,7 @@ def describe_query(
|
|
|
75
75
|
cfg = adapter_registry.resolve_source_config(source)
|
|
76
76
|
|
|
77
77
|
if cfg.get("type") == "duckdb":
|
|
78
|
-
# Run DESCRIBE via the registry's existing read-only
|
|
78
|
+
# Run DESCRIBE via the registry's existing read-only DuckDBAdapter so
|
|
79
79
|
# we don't open a writable dbt-duckdb connection that would conflict
|
|
80
80
|
# with dft serve.
|
|
81
81
|
cols = _duckdb_describe(sql, source, adapter_registry)
|
|
@@ -110,7 +110,7 @@ def describe_query(
|
|
|
110
110
|
def _duckdb_describe(
|
|
111
111
|
sql: str, source: str | None, adapter_registry: AdapterRegistry
|
|
112
112
|
) -> list[DescribeQueryColumn]:
|
|
113
|
-
"""Run DESCRIBE ({sql}) via the registry's read-only
|
|
113
|
+
"""Run DESCRIBE ({sql}) via the registry's read-only DuckDBAdapter."""
|
|
114
114
|
from dataface.core.compile.models.query.normalized import SqlQuery
|
|
115
115
|
|
|
116
116
|
result = adapter_registry.execute(SqlQuery(sql=f"DESCRIBE ({sql})", source=source))
|
|
@@ -30,7 +30,7 @@ AuthoredFace (dataface) definition from YAML.
|
|
|
30
30
|
| `width` | str \| int | ✓ | Width when nested (e.g., '50%', '400px', or an integer in pixels). |
|
|
31
31
|
| `height` | str \| int | ✓ | Height when nested (e.g., '300px' or an integer in pixels). |
|
|
32
32
|
| `visible` | bool \| str \| [SingleRowBoolProbe](#singlerowboolprobe) | ✓ | Controls whether this layout item is rendered. Accepts a bool, variable name, Jinja expression, or {query, column} probe. |
|
|
33
|
-
| `
|
|
33
|
+
| `extends` | str \| list[str] | ✓ | Face name(s) or relative path(s) this face inherits from, low to high priority. |
|
|
34
34
|
| `auto_link` | bool | ✓ | When True, table charts with no explicit link: automatically link each row to its canonical /data/<source>/<schema>/<table>/detail/ page. Default off. Explicit link: always wins; set link: ~ to suppress per chart. |
|
|
35
35
|
|
|
36
36
|
<a id="sourcessection"></a>
|
|
@@ -1149,6 +1149,7 @@ Authored overlay for ChartsStyle. Registry of all chart-type styles plus shared
|
|
|
1149
1149
|
| `color` | str \| [StyleColorConfig](#stylecolorconfig) | ✓ | Chart-local static mark color (CSS string) or gradient scale config; None uses the theme palette. |
|
|
1150
1150
|
| `title` | [TitleStyle](#titlestyle) | ✓ | Chart-level title style override; None inherits the theme title style. |
|
|
1151
1151
|
| `dashes` | list[list[int]] | ✓ | Ordered list of Vega-Lite strokeDash arrays for line-family categorical encoding; None disables dash emission. |
|
|
1152
|
+
| `default_width` | float | ✓ | Default chart width in pixels when no explicit width is set. |
|
|
1152
1153
|
| `default_chart_height` | float | ✓ | Fallback chart height in pixels when aspect-ratio sizing is unavailable. |
|
|
1153
1154
|
| `default_table_height` | float | ✓ | Placeholder table height in pixels; replaced by data-aware row-count sizing at render time. |
|
|
1154
1155
|
| `label_usable_ratio` | float | ✓ | Fraction of chart width usable for axis labels (0–1); labels are tilted when full labels exceed this width. |
|
|
@@ -1222,15 +1223,19 @@ Authored overlay for PageStyle. Page-level (outer HTML canvas) styling.
|
|
|
1222
1223
|
|
|
1223
1224
|
<a id="footerstyle"></a>
|
|
1224
1225
|
## FooterStyle
|
|
1225
|
-
Authored overlay for FooterStyle.
|
|
1226
|
+
Authored overlay for FooterStyle. Page footer chrome: visibility, attribution text, font, and rule.
|
|
1226
1227
|
|
|
1227
1228
|
| Field | Type | Optional | Description |
|
|
1228
1229
|
|-------|------|:--------:|-------------|
|
|
1229
1230
|
| `visible` | bool | ✓ | Show the footer attribution line. |
|
|
1231
|
+
| `text` | str | ✓ | Attribution text shown in the footer. |
|
|
1232
|
+
| `font` | [FontStyle](#fontstyle) | ✓ | Footer text font style (size and color required). |
|
|
1233
|
+
| `y_offset` | float | ✓ | Vertical offset from bottom edge in pixels. |
|
|
1234
|
+
| `rule` | [FooterRule](#footerrule) | ✓ | Hairline rule above footer text; null disables the rule. |
|
|
1230
1235
|
|
|
1231
1236
|
<a id="timestampstyle"></a>
|
|
1232
1237
|
## TimestampStyle
|
|
1233
|
-
Authored overlay for TimestampStyle. Authored timestamp chrome: visibility, placement, strftime format, and
|
|
1238
|
+
Authored overlay for TimestampStyle. Authored timestamp chrome: visibility, placement, strftime format, font, and y-offset.
|
|
1234
1239
|
|
|
1235
1240
|
| Field | Type | Optional | Description |
|
|
1236
1241
|
|-------|------|:--------:|-------------|
|
|
@@ -1238,6 +1243,7 @@ Authored overlay for TimestampStyle. Authored timestamp chrome: visibility, plac
|
|
|
1238
1243
|
| `position` | enum: "top", "footer" | ✓ | Timestamp row: top page chrome or footer baseline. |
|
|
1239
1244
|
| `align` | enum: "left", "right" | ✓ | Timestamp horizontal alignment within its row. |
|
|
1240
1245
|
| `format` | str | ✓ | strftime format string for the render timestamp. |
|
|
1246
|
+
| `y` | float | ✓ | Y-coordinate for top-positioned timestamp in pixels. |
|
|
1241
1247
|
| `font` | [FontStyle](#fontstyle) | ✓ | Timestamp font style overrides (size, color, weight, ...). |
|
|
1242
1248
|
|
|
1243
1249
|
<a id="spacingvalues"></a>
|
|
@@ -1279,7 +1285,7 @@ A data_table row that reads a column's raw per-x value.
|
|
|
1279
1285
|
| Field | Type | Optional | Description |
|
|
1280
1286
|
|-------|------|:--------:|-------------|
|
|
1281
1287
|
| `source` | str | | Query column the row reads from (per-x raw value). |
|
|
1282
|
-
| `format` | str | ✓ | D3
|
|
1288
|
+
| `format` | str \| [FormatConfig](#formatconfig) | ✓ | D3 format string or format config object. Optional; inherits the chart measure format when omitted and source matches chart.y. |
|
|
1283
1289
|
| `label` | str | ✓ | Left-stub row label. Optional. |
|
|
1284
1290
|
|
|
1285
1291
|
<a id="chartdatatableaggregate"></a>
|
|
@@ -1290,7 +1296,7 @@ A data_table row that reads an aggregate of a column grouped by x.
|
|
|
1290
1296
|
|-------|------|:--------:|-------------|
|
|
1291
1297
|
| `aggregate` | enum: "sum", "avg", "min", "max", "median", "count", "count_distinct" | | Aggregate operation applied per x-group. One of: sum, avg, min, max, median, count, count_distinct. Exact names only — no aliases (spec G4). |
|
|
1292
1298
|
| `source` | str | | Query column being aggregated. Always required alongside `aggregate:` (spec G2). |
|
|
1293
|
-
| `format` | str | ✓ | D3
|
|
1299
|
+
| `format` | str \| [FormatConfig](#formatconfig) | ✓ | D3 format string or format config object for the aggregated value. Optional. |
|
|
1294
1300
|
| `label` | str | ✓ | Column header label override for this row. |
|
|
1295
1301
|
|
|
1296
1302
|
<a id="chartdatatableperseries"></a>
|
|
@@ -1302,11 +1308,11 @@ A data_table entry that expands into one row per color: series.
|
|
|
1302
1308
|
| `per_series` | str | | Query column the row reads from (per-x, per-series value). |
|
|
1303
1309
|
| `by_measure` | bool | ✓ | When True, expand one row reading the named measure field directly (no color: groupby). Required for multi-y charts where each measure is its own y-field rather than a color-encoded series. |
|
|
1304
1310
|
| `label` | str | ✓ | Row label displayed in the strip's label gutter. When None and by_measure=True, the per_series field name is used. Has no effect when by_measure=False (series name is the label). |
|
|
1305
|
-
| `format` | str | ✓ | D3
|
|
1311
|
+
| `format` | str \| [FormatConfig](#formatconfig) | ✓ | D3 format string or format config object. Optional. |
|
|
1306
1312
|
|
|
1307
1313
|
<a id="fontstyle"></a>
|
|
1308
1314
|
## FontStyle
|
|
1309
|
-
Text appearance.
|
|
1315
|
+
Text appearance. Merged as a unit.
|
|
1310
1316
|
|
|
1311
1317
|
| Field | Type | Optional | Description |
|
|
1312
1318
|
|-------|------|:--------:|-------------|
|
|
@@ -1445,7 +1451,6 @@ Authored overlay for DataTableStyle. Attached data_table style. Lives at style.c
|
|
|
1445
1451
|
| `divider` | [RuleStyle](#rulestyle) | ✓ | Rule at the boundary between the chart plot and the data strip. For position='bottom': rule sits above the strip (below the axis). For position='top': rule sits below the strip rows (above the plot top). |
|
|
1446
1452
|
| `row` | [DataTableRowStyle](#datatablerowstyle) | ✓ | Data_table row padding and rule style. |
|
|
1447
1453
|
| `label` | [DataTableLabelStyle](#datatablelabelstyle) | ✓ | Row label (series name) style. |
|
|
1448
|
-
| `number_align` | enum: "left", "right", "decimal" | ✓ | Alignment of numeric values in data_table cells. |
|
|
1449
1454
|
| `padding_top` | float | ✓ | Padding above the topmost strip row in pixels. For position='bottom': gap between axis labels and the first row. For position='top': space above the topmost row (outer edge of strip). |
|
|
1450
1455
|
| `padding_bottom` | float | ✓ | Padding below the last strip row in pixels. For position='bottom': space below the last row. For position='top': gap between the last row and the plot top edge. |
|
|
1451
1456
|
| `label_max_lines` | int | ✓ | Number of x-axis label lines to reserve in the axis gap (only used for position='bottom'; ignored for position='top'). Typically 1 or 2. |
|
|
@@ -2049,6 +2054,15 @@ Authored overlay for InputStyle.
|
|
|
2049
2054
|
| `widths` | [InputWidths](#inputwidths) | ✓ | Per-input-type default widths. |
|
|
2050
2055
|
| `range` | [RangeDefaults](#rangedefaults) | ✓ | Range input default min/max/step values. |
|
|
2051
2056
|
|
|
2057
|
+
<a id="footerrule"></a>
|
|
2058
|
+
## FooterRule
|
|
2059
|
+
Authored overlay for FooterRule. Hairline rule above the footer attribution text. None = no rule.
|
|
2060
|
+
|
|
2061
|
+
| Field | Type | Optional | Description |
|
|
2062
|
+
|-------|------|:--------:|-------------|
|
|
2063
|
+
| `color` | str | ✓ | Rule stroke color. |
|
|
2064
|
+
| `stroke_width` | float | ✓ | Rule stroke width in pixels. |
|
|
2065
|
+
|
|
2052
2066
|
<a id="legendlabelstyle"></a>
|
|
2053
2067
|
## LegendLabelStyle
|
|
2054
2068
|
Authored overlay for LegendLabelStyle.
|
|
@@ -2237,7 +2251,7 @@ Authored overlay for LineMarkStyle. Line mark stroke, interpolation, and halo. P
|
|
|
2237
2251
|
|
|
2238
2252
|
<a id="pointmarkstyle"></a>
|
|
2239
2253
|
## PointMarkStyle
|
|
2240
|
-
Authored overlay for PointMarkStyle. Point mark style (data-point markers on line/area
|
|
2254
|
+
Authored overlay for PointMarkStyle. Point mark style (data-point markers on scatter/point/line/area charts).
|
|
2241
2255
|
|
|
2242
2256
|
| Field | Type | Optional | Description |
|
|
2243
2257
|
|-------|------|:--------:|-------------|
|
|
@@ -2248,7 +2262,7 @@ Authored overlay for PointMarkStyle. Point mark style (data-point markers on lin
|
|
|
2248
2262
|
| `filled` | bool | ✓ | Whether points are filled; None uses VL default. |
|
|
2249
2263
|
| `fill` | str | ✓ | Point interior fill color; only applied when filled=false. |
|
|
2250
2264
|
| `stroke_width` | float | ✓ | Stroke width in pixels for hollow point rings; None uses VL default. |
|
|
2251
|
-
| `labels` | [PointLabelsStyle](#pointlabelsstyle) | ✓ | Value label style for point marks. |
|
|
2265
|
+
| `labels` | [PointLabelsStyle](#pointlabelsstyle) | ✓ | Value label style for point marks. On line charts, setting marks.point.labels is an alias for marks.line.labels. |
|
|
2252
2266
|
|
|
2253
2267
|
<a id="rulemarkstyle"></a>
|
|
2254
2268
|
## RuleMarkStyle
|
|
@@ -2540,7 +2554,7 @@ Authored overlay for BarLabelsStyle. Bar mark value-label config. Extends MarkLa
|
|
|
2540
2554
|
| `dx` | int | ✓ | Horizontal pixel offset for value labels; overrides the position default. |
|
|
2541
2555
|
| `dy` | int | ✓ | Vertical pixel offset for value labels; overrides the position default. |
|
|
2542
2556
|
| `font` | [FontStyle](#fontstyle) | ✓ | Value label font style (color, size, family, etc.). |
|
|
2543
|
-
| `position` | enum: "
|
|
2557
|
+
| `position` | enum: "above", "top", "middle", "middle_aligned", "bottom" | ✓ | Label position relative to the bar. 'above' places labels above the bar top (outside). 'top' places labels just inside the top edge. 'middle' centers labels vertically in the bar. 'middle_aligned' centers all labels at a common height (mean of bar heights / 2). 'bottom' places labels just inside the bottom edge. |
|
|
2544
2558
|
|
|
2545
2559
|
<a id="strokestyle"></a>
|
|
2546
2560
|
## StrokeStyle
|
|
@@ -30,6 +30,7 @@ from dataface.agent_api.validate import (
|
|
|
30
30
|
from dataface.core import dashboard as _core_dashboard
|
|
31
31
|
from dataface.core.compile.config import (
|
|
32
32
|
ProjectSourcesConfig,
|
|
33
|
+
get_export_config,
|
|
33
34
|
)
|
|
34
35
|
from dataface.core.execute.adapters.adapter_registry import (
|
|
35
36
|
AdapterRegistry,
|
|
@@ -43,12 +44,12 @@ from dataface.core.inspect.query_validator import (
|
|
|
43
44
|
)
|
|
44
45
|
from dataface.core.project import Project
|
|
45
46
|
from dataface.core.project_roots import find_project_root
|
|
47
|
+
from dataface.core.render.board_links import LinkContext
|
|
46
48
|
|
|
47
49
|
if TYPE_CHECKING:
|
|
48
50
|
from dataface.core.dashboard import RenderedDashboard, RenderFormat
|
|
49
51
|
from dataface.core.execute.source_resolver import SourceResolver
|
|
50
52
|
from dataface.core.inspect.query_validator import RelationshipContext
|
|
51
|
-
from dataface.core.render.board_links import LinkContext
|
|
52
53
|
|
|
53
54
|
# Probe at module load time — single find_spec call per process.
|
|
54
55
|
_SUPER_SCHEMA_AVAILABLE: bool = (
|
|
@@ -416,6 +417,12 @@ class ProjectSession:
|
|
|
416
417
|
link_context: LinkContext | None = None,
|
|
417
418
|
**render_options: Any,
|
|
418
419
|
) -> RenderedDashboard:
|
|
420
|
+
# When the caller does not supply a link_context, derive origin from the
|
|
421
|
+
# project's public_url config so dft render exports carry fully-qualified links.
|
|
422
|
+
if link_context is None:
|
|
423
|
+
public_url = get_export_config(self.project).public_url
|
|
424
|
+
if public_url:
|
|
425
|
+
link_context = LinkContext(runtime="serve", origin=public_url)
|
|
419
426
|
return _core_dashboard.render_dashboard(
|
|
420
427
|
path=path,
|
|
421
428
|
yaml_content=yaml_content,
|
|
@@ -8,13 +8,15 @@ from pathlib import Path
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
10
|
from dataface.agent_api.project_session import ProjectSession
|
|
11
|
+
from dataface.core.compile.config import get_export_config
|
|
11
12
|
from dataface.core.compile.markdown import (
|
|
12
13
|
MARKDOWN_NOT_FACE_MESSAGE,
|
|
13
14
|
is_markdown_face,
|
|
14
15
|
markdown_to_yaml,
|
|
15
16
|
)
|
|
16
17
|
from dataface.core.execute.cache_backend import QueryResultCache
|
|
17
|
-
from dataface.core.project import Project,
|
|
18
|
+
from dataface.core.project import Project, ProjectDirectory
|
|
19
|
+
from dataface.core.render.board_links import LinkContext
|
|
18
20
|
from dataface.core.render.face_api import compile_and_render
|
|
19
21
|
|
|
20
22
|
|
|
@@ -76,21 +78,26 @@ def render_face(
|
|
|
76
78
|
# project-relative handle; nested sub-file refs are unsupported in that
|
|
77
79
|
# mode (relative reads still resolve via face_dir below).
|
|
78
80
|
try:
|
|
79
|
-
|
|
81
|
+
base_dir: ProjectDirectory | None = p.directory_for_path(face_file.parent)
|
|
80
82
|
except ValueError:
|
|
81
|
-
|
|
83
|
+
base_dir = None
|
|
84
|
+
extra = dict(options)
|
|
85
|
+
if "link_context" not in extra:
|
|
86
|
+
public_url = get_export_config(p).public_url
|
|
87
|
+
if public_url:
|
|
88
|
+
extra["link_context"] = LinkContext(runtime="serve", origin=public_url)
|
|
82
89
|
return compile_and_render(
|
|
83
90
|
yaml_content,
|
|
84
91
|
p,
|
|
85
92
|
adapter_registry=project_session.adapter_registry,
|
|
86
93
|
cache=cache,
|
|
87
94
|
warnings_ignore=warnings_ignore,
|
|
88
|
-
|
|
95
|
+
base_dir=base_dir,
|
|
89
96
|
face_dir=face_file.parent,
|
|
90
97
|
format=format,
|
|
91
98
|
variables=variables,
|
|
92
99
|
use_cache=use_cache,
|
|
93
|
-
**
|
|
100
|
+
**extra,
|
|
94
101
|
)
|
|
95
102
|
finally:
|
|
96
103
|
project_session.close()
|
dataface/agent_api/validate.py
CHANGED
|
@@ -15,13 +15,20 @@ from dataface.agent_api._paths import (
|
|
|
15
15
|
resolve_scoped_path,
|
|
16
16
|
)
|
|
17
17
|
from dataface.core.compile.errors import DatafaceError
|
|
18
|
-
from dataface.core.errors import
|
|
18
|
+
from dataface.core.errors import (
|
|
19
|
+
DF_COMPILE_META_SCHEMA,
|
|
20
|
+
DF_UNKNOWN_INTERNAL,
|
|
21
|
+
StructuredError,
|
|
22
|
+
)
|
|
19
23
|
from dataface.core.project import Project
|
|
20
24
|
from dataface.core.render.warnings.base import RenderWarning
|
|
21
25
|
|
|
22
26
|
if TYPE_CHECKING:
|
|
23
27
|
from dataface.agent_api.project_session import ProjectSession
|
|
24
28
|
|
|
29
|
+
# Filenames treated as cascade fragments (FacePatch), not standalone compilable faces.
|
|
30
|
+
_META_FILENAMES: frozenset[str] = frozenset({"meta.yaml", "meta.yml"})
|
|
31
|
+
|
|
25
32
|
|
|
26
33
|
class ValidateDashboardArgs(BaseModel):
|
|
27
34
|
"""Validate a face YAML file without executing queries.
|
|
@@ -116,7 +123,8 @@ def _validate_one_path(
|
|
|
116
123
|
for pf in project.iter_faces(under=under, recursive=True)
|
|
117
124
|
if pf.is_yaml
|
|
118
125
|
and not pf.is_private
|
|
119
|
-
and
|
|
126
|
+
and Path(pf.relpath).name not in _META_FILENAMES
|
|
127
|
+
and not pf.parent.file(INSPECT_TEMPLATE_MANIFEST).exists()
|
|
120
128
|
)
|
|
121
129
|
if not faces:
|
|
122
130
|
return [
|
|
@@ -134,6 +142,46 @@ def _validate_one_path(
|
|
|
134
142
|
return [validate(f, project=project) for f in faces]
|
|
135
143
|
|
|
136
144
|
|
|
145
|
+
def _validate_meta_file(path: Path) -> ValidateResult:
|
|
146
|
+
"""Validate a meta.yaml as a FacePatch fragment — no layout required."""
|
|
147
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
148
|
+
|
|
149
|
+
from dataface.core.compile.errors import CompilationError
|
|
150
|
+
from dataface.core.compile.meta import load_meta_file
|
|
151
|
+
from dataface.core.compile.models.face.patch import FacePatch
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
meta_data, _ = load_meta_file(path)
|
|
155
|
+
except CompilationError as exc:
|
|
156
|
+
return ValidateResult(
|
|
157
|
+
success=False,
|
|
158
|
+
path=path,
|
|
159
|
+
errors=[
|
|
160
|
+
DatafaceError.from_code(
|
|
161
|
+
DF_COMPILE_META_SCHEMA, message=str(exc)
|
|
162
|
+
).to_structured(file=str(path))
|
|
163
|
+
],
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
FacePatch.model_validate(meta_data)
|
|
168
|
+
except PydanticValidationError as exc:
|
|
169
|
+
errors = [
|
|
170
|
+
DatafaceError.from_code(
|
|
171
|
+
DF_COMPILE_META_SCHEMA,
|
|
172
|
+
message=(
|
|
173
|
+
f"{' → '.join(str(loc) for loc in e['loc'])}: {e['msg']}"
|
|
174
|
+
if e["loc"]
|
|
175
|
+
else e["msg"]
|
|
176
|
+
),
|
|
177
|
+
).to_structured(file=str(path))
|
|
178
|
+
for e in exc.errors()
|
|
179
|
+
]
|
|
180
|
+
return ValidateResult(success=False, path=path, errors=errors)
|
|
181
|
+
|
|
182
|
+
return ValidateResult(success=True, path=path)
|
|
183
|
+
|
|
184
|
+
|
|
137
185
|
def validate(path: Path, *, project: Project) -> ValidateResult:
|
|
138
186
|
"""Fast YAML schema + cross-reference validation. No warehouse, no execute."""
|
|
139
187
|
from dataface.core.compile.compiler import compile_file
|
|
@@ -164,6 +212,9 @@ def validate(path: Path, *, project: Project) -> ValidateResult:
|
|
|
164
212
|
],
|
|
165
213
|
)
|
|
166
214
|
|
|
215
|
+
if resolved.name in _META_FILENAMES:
|
|
216
|
+
return _validate_meta_file(resolved)
|
|
217
|
+
|
|
167
218
|
try:
|
|
168
219
|
result = compile_file(project.file_for_path(resolved), project=project)
|
|
169
220
|
except OSError as exc:
|
|
@@ -121,6 +121,8 @@ rows:
|
|
|
121
121
|
- cols: [by_region, by_product]
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
Don't put a `title:` on every row. A section heading over already-labeled charts is repetition, not structure — group with proximity instead. Reach for a row `title:` only when the dashboard is becoming more of a narrative — when there's accompanying `text:` prose, or the heading genuinely says something the charts don't (a shared scope like "Last 30 days", a real mode boundary). A title with no text alongside it usually means the title and the sectioning weren't needed — drop it. (This is dashboard guidance — in a **report**, sections are good: narrative `## …` headings in `text:` blocks carry the prose between charts — see `{{ s_skill_design_report }}`.)
|
|
125
|
+
|
|
124
126
|
### Step 6 — Save the Final YAML (when saving applies)
|
|
125
127
|
|
|
126
128
|
Skip this step when you are previewing ephemerally — see "Previewing vs. saving a face" above; your host decides the default. On hosts that offer the user a Save control over the rendered preview (the Cloud chat, for example), saving is the user's click — don't write the file yourself. When the host has no such control and saving applies, write the YAML to a **new** face under `faces/` (never silently fold into an existing one) with your normal file-edit mechanism, then:
|
|
@@ -107,6 +107,8 @@ Most important information first, following Western reading pattern (top-left
|
|
|
107
107
|
- Related metrics grouped by proximity
|
|
108
108
|
- Consistent styling throughout
|
|
109
109
|
|
|
110
|
+
**Section titles — use sparingly.** A `title:` on a `rows:`/`cols:`/`grid` item renders a heading above that group. On a dense, chart-only dashboard it is almost always noise: the KPIs and chart titles inside already say what each group is, so a row header like "Core Metrics", "Trendlines", or "Overview" just repeats them. Group with proximity and whitespace, not labels. Reach for a section title only when the dashboard is becoming more of a narrative — when there's accompanying `text:` prose that introduces the section, or when the heading genuinely says something the charts don't (a scoping qualifier the charts share like "Last 30 days" or "North America", a real mode boundary the layout alone wouldn't signal). A title with no text alongside it is the tell: usually it means neither the title nor the sectioning was needed — delete it and let the charts speak. This is guidance for designing **dashboards** — in a **report**, sections are good: there the narrative is the point, and `## …` headings in `text:` blocks carry the prose that introduces each section.
|
|
111
|
+
|
|
110
112
|
### 5. Color & Visual Design
|
|
111
113
|
|
|
112
114
|
- **Gray is default** — muted everything, ONE accent color for emphasis
|
|
@@ -170,6 +172,7 @@ Before delivering:
|
|
|
170
172
|
| Pie chart with 7 segments | Use a bar chart instead |
|
|
171
173
|
| Decorative color | Color must encode data or meaning |
|
|
172
174
|
| Generic titles | Titles should state what the chart answers |
|
|
175
|
+
| Section title over every row | Drop it — labeled charts don't need a heading repeating them. Reserve titles for reports or a real scoping/mode boundary |
|
|
173
176
|
| Scrolling dashboard | Reduce charts or split dashboards |
|
|
174
177
|
|
|
175
178
|
## Rationalizations to Resist
|
|
@@ -180,3 +183,4 @@ Before delivering:
|
|
|
180
183
|
| "Pie chart is fine for 6 categories" | It's not — humans compare angles poorly. Use bar. |
|
|
181
184
|
| "The legend explains the colors" | Direct labels are always better than legends. |
|
|
182
185
|
| "More charts = more value" | More charts = more noise. Each must earn its place. |
|
|
186
|
+
| "Section titles organize the dashboard" | They organize a *report*. On a dense dashboard the charts are already labeled — a heading per row is repetition, not structure. |
|
|
@@ -37,8 +37,7 @@ default.
|
|
|
37
37
|
|
|
38
38
|
```yaml
|
|
39
39
|
rows:
|
|
40
|
-
-
|
|
41
|
-
cols:
|
|
40
|
+
- cols:
|
|
42
41
|
- revenue_trend # top-left
|
|
43
42
|
- cost_trend # top-right
|
|
44
43
|
- cols:
|
|
@@ -46,6 +45,8 @@ rows:
|
|
|
46
45
|
- by_product # bottom-right
|
|
47
46
|
```
|
|
48
47
|
|
|
48
|
+
No section title — the four labeled charts are self-explanatory, and a heading over them just repeats their titles.
|
|
49
|
+
|
|
49
50
|
Each chart is defined as usual in `charts:`. The 2×2 is purely a layout
|
|
50
51
|
decision — swap in any chart type per cell.
|
|
51
52
|
|
|
@@ -57,7 +58,7 @@ See `examples/two-by-two-grid-overview.yml` for the inline-data worked example.
|
|
|
57
58
|
|---|---|---|
|
|
58
59
|
| 2×3 (six cells) | Add a third `cols:` row | Six equally-weighted metrics |
|
|
59
60
|
| Asymmetric | `width: "60%"` on one cell | Slightly more emphasis on one chart |
|
|
60
|
-
| Section title | `title:` on a `rows:` item |
|
|
61
|
+
| Section title | `title:` on a `rows:` item | Rarely needed — only when a heading adds something the charts don't (a shared scope, a mode boundary). Default to omitting it |
|
|
61
62
|
| Grid layout | `grid: columns: 24` + `col_span: 12` | Finer column control |
|
|
62
63
|
|
|
63
64
|
## Common pitfalls
|
dataface/cli/commands/serve.py
CHANGED
|
@@ -46,21 +46,46 @@ def serve_command(
|
|
|
46
46
|
Returns:
|
|
47
47
|
None (exits with code 0 on success, 1 on errors)
|
|
48
48
|
"""
|
|
49
|
+
from dataface.core.compile.config import list_built_in_themes, load_config
|
|
50
|
+
from dataface.core.errors import DF_SERVE_INVALID_DEFAULT_THEME
|
|
49
51
|
from dataface.core.project import Project
|
|
50
52
|
from dataface.core.project_roots import find_dft_root, infer_dialect_from_dbt
|
|
51
|
-
from dataface.core.serve.bootstrap import apply_default_theme
|
|
52
53
|
from dataface.core.serve.port import resolve_port
|
|
53
54
|
from dataface.core.serve.server import create_server
|
|
54
55
|
|
|
55
56
|
# Resolve the project root: --project-dir if given, else discover from cwd.
|
|
56
57
|
project_dir = project_dir or find_dft_root() or Path.cwd()
|
|
57
58
|
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
core_project = Project(project_dir)
|
|
60
|
+
|
|
61
|
+
# Validate DFT_DEFAULT_THEME at startup — fail fast before serving any request.
|
|
62
|
+
# The env var is read per-compilation by get_default_theme_name(); validation
|
|
63
|
+
# here ensures the user sees a clear error at startup rather than on first render.
|
|
64
|
+
env_theme = os.environ.get(
|
|
65
|
+
"DFT_DEFAULT_THEME"
|
|
66
|
+
) # noqa: TID251 — DFT_DEFAULT_THEME knob
|
|
67
|
+
# Empty string is treated as unset (matches get_default_theme_name, which
|
|
68
|
+
# falls back to the shipped default) — only a non-empty unknown name errors.
|
|
69
|
+
if env_theme:
|
|
70
|
+
available = list_built_in_themes()
|
|
71
|
+
if env_theme not in available:
|
|
72
|
+
err = DatafaceError.from_code(
|
|
73
|
+
DF_SERVE_INVALID_DEFAULT_THEME,
|
|
74
|
+
source="DFT_DEFAULT_THEME env var",
|
|
75
|
+
theme=env_theme,
|
|
76
|
+
available=", ".join(available),
|
|
77
|
+
)
|
|
78
|
+
print_structured_errors([err.to_structured()])
|
|
79
|
+
raise typer.Exit(1) from None
|
|
80
|
+
|
|
81
|
+
# Validate dataface.yml wholesale: stray keys (style:, board:, theme:, etc.) are a
|
|
82
|
+
# hard error here at startup rather than silently ignored.
|
|
83
|
+
from pydantic import ValidationError as _PydanticError
|
|
84
|
+
|
|
60
85
|
try:
|
|
61
|
-
|
|
62
|
-
except
|
|
63
|
-
|
|
86
|
+
load_config(core_project)
|
|
87
|
+
except _PydanticError as e:
|
|
88
|
+
typer.echo(f"dataface.yml validation error: {e}", err=True)
|
|
64
89
|
raise typer.Exit(1) from None
|
|
65
90
|
|
|
66
91
|
# Resolve port: --port > DFT_PORT > dataface.yml server.port > hash(project_dir)
|
|
@@ -43,10 +43,9 @@ from dataface.core.compile.errors import (
|
|
|
43
43
|
)
|
|
44
44
|
from dataface.core.compile.meta import (
|
|
45
45
|
MetaLintConfig,
|
|
46
|
-
apply_meta_to_face,
|
|
47
46
|
find_meta_files,
|
|
48
47
|
load_meta_file,
|
|
49
|
-
|
|
48
|
+
resolve_meta_lint,
|
|
50
49
|
)
|
|
51
50
|
from dataface.core.compile.models.chart.authored import (
|
|
52
51
|
AreaChart,
|
|
@@ -208,10 +207,9 @@ __all__ = [
|
|
|
208
207
|
"get_project_warnings_ignore",
|
|
209
208
|
# Meta configuration
|
|
210
209
|
"MetaLintConfig",
|
|
211
|
-
"apply_meta_to_face",
|
|
212
210
|
"find_meta_files",
|
|
213
211
|
"load_meta_file",
|
|
214
|
-
"
|
|
212
|
+
"resolve_meta_lint",
|
|
215
213
|
# Type guards
|
|
216
214
|
"is_face",
|
|
217
215
|
"is_chart",
|