dataface 0.3.2.dev22__py3-none-any.whl → 0.3.2.dev150__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 -4
- dataface/__init__.py +70 -23
- dataface/agent_api/__init__.py +162 -68
- dataface/agent_api/_paths.py +9 -3
- dataface/agent_api/docs/yaml-reference.md +273 -239
- dataface/agent_api/init.py +1 -1
- dataface/agent_api/project_session.py +2 -1
- dataface/agent_api/serve.py +113 -0
- dataface/agent_api/validate.py +8 -0
- dataface/ai/agent.py +28 -2
- dataface/cli/_project.py +100 -1
- dataface/cli/commands/board_artifact.py +5 -2
- dataface/cli/commands/describe.py +7 -3
- dataface/cli/commands/docs.py +12 -3
- dataface/cli/commands/init.py +7 -14
- dataface/cli/commands/inspect.py +6 -1
- dataface/cli/commands/mcp_init.py +23 -43
- dataface/cli/commands/migrate.py +8 -3
- dataface/cli/commands/query.py +11 -8
- dataface/cli/commands/render.py +26 -9
- dataface/cli/commands/search.py +8 -2
- dataface/cli/commands/serve.py +25 -81
- dataface/cli/commands/skills_init.py +2 -19
- dataface/cli/commands/validate.py +7 -2
- dataface/cli/filesystem_project.py +5 -4
- dataface/cli/main.py +22 -33
- dataface/core/__init__.py +73 -28
- dataface/core/aliases.py +27 -0
- dataface/core/{compile/colors.py → colors.py} +20 -5
- dataface/core/compile/__init__.py +0 -8
- dataface/core/compile/channel.py +0 -13
- dataface/core/compile/compiler.py +161 -96
- dataface/core/compile/data_table.py +7 -2
- dataface/core/compile/enrich.py +18 -44
- dataface/core/compile/face_warnings.py +46 -8
- dataface/core/compile/format.py +47 -1
- dataface/core/compile/inherit_graph.py +3 -3
- dataface/core/compile/inherit_registry.yaml +1 -1
- dataface/core/compile/introspection.py +2 -13
- dataface/core/compile/migrations.py +112 -9
- dataface/core/compile/models/chart/authored/_base.py +1 -1
- dataface/core/compile/models/chart/normalized/__init__.py +2 -0
- dataface/core/compile/models/chart/normalized/_base.py +26 -0
- dataface/core/compile/models/chart/normalized/kpi.py +0 -4
- dataface/core/compile/models/chart/resolved/_base.py +28 -10
- dataface/core/compile/models/chart/resolved/callout.py +2 -3
- dataface/core/compile/models/chart/resolved/kpi.py +7 -3
- dataface/core/compile/models/chart/resolved/table.py +16 -2
- dataface/core/compile/models/config.py +5 -0
- dataface/core/compile/models/face/authored.py +24 -89
- dataface/core/compile/models/markers.py +1 -1
- dataface/core/compile/models/primitives.py +175 -2
- dataface/core/compile/models/query/authored.py +1 -119
- dataface/core/compile/models/refs.py +0 -2
- dataface/core/compile/models/source.py +78 -164
- dataface/core/compile/models/style/authored/__init__.py +32 -8
- dataface/core/compile/models/style/authored/_base.py +126 -69
- dataface/core/compile/models/style/authored/table.py +61 -3
- dataface/core/compile/models/style/resolved/__init__.py +12 -2
- dataface/core/compile/models/style/resolved/_base.py +142 -57
- dataface/core/compile/models/style/resolved/_cartesian.py +6 -11
- dataface/core/compile/models/style/resolved/_cascade.py +141 -54
- dataface/core/compile/models/style/resolved/area.py +1 -1
- dataface/core/compile/models/style/resolved/bar.py +1 -1
- dataface/core/compile/models/style/resolved/line.py +1 -1
- dataface/core/compile/models/style/resolved/scatter.py +3 -2
- dataface/core/compile/models/style/theme/__init__.py +36 -14
- dataface/core/compile/models/style/theme/_chart_base.py +17 -9
- dataface/core/compile/models/style/theme/axis.py +432 -312
- dataface/core/compile/models/style/theme/charts.py +10 -6
- dataface/core/compile/models/style/theme/kpi.py +2 -1
- dataface/core/compile/models/style/theme/page.py +5 -2
- dataface/core/compile/models/style/theme/table.py +2 -2
- dataface/core/compile/models/style/theme/variables.py +0 -3
- dataface/core/compile/models/vega_lite/config.py +1 -1
- dataface/core/compile/normalize_charts.py +10 -154
- dataface/core/compile/normalize_layout.py +20 -284
- dataface/core/compile/normalize_queries.py +24 -513
- dataface/core/compile/normalizer.py +4 -10
- dataface/core/compile/palette.py +19 -7
- dataface/core/compile/resolve/__init__.py +2 -0
- dataface/core/compile/resolve/_axes.py +147 -44
- dataface/core/compile/resolve/_channels.py +1 -1
- dataface/core/compile/resolve/_dispatch.py +1 -1
- dataface/core/compile/resolve/_domain.py +49 -32
- dataface/core/compile/resolve/_kwargs.py +39 -12
- dataface/core/compile/resolve/_marks.py +8 -3
- dataface/core/compile/resolve/_palette.py +7 -3
- dataface/core/compile/resolve/_table.py +124 -33
- dataface/core/compile/resolve/area.py +48 -18
- dataface/core/compile/resolve/bar.py +245 -64
- dataface/core/compile/resolve/geo.py +10 -3
- dataface/core/compile/resolve/heatmap.py +16 -8
- dataface/core/compile/resolve/line.py +35 -12
- dataface/core/compile/resolve/pie.py +4 -1
- dataface/core/compile/resolve/pie_attachment.py +1 -1
- dataface/core/compile/resolve/scatter.py +25 -13
- dataface/core/compile/resolve/simple.py +25 -2
- dataface/core/compile/schema_renderers/prompt.py +1 -1
- dataface/core/compile/schema_renderers/textmate_grammar.py +8 -1
- dataface/core/compile/source_map.py +211 -28
- dataface/core/compile/style_cascade.py +126 -55
- dataface/core/compile/validator.py +121 -44
- dataface/core/compile/yaml_error_formatter.py +108 -3
- dataface/core/connections.py +10 -3
- dataface/core/dashboard.py +27 -19
- dataface/core/defaults/default_config.yml +4 -0
- dataface/core/defaults/themes/_base.yaml +49 -40
- dataface/core/defaults/themes/cream.yaml +4 -4
- dataface/core/defaults/themes/diagnostics-title-baseline.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +1 -1
- dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +1 -1
- dataface/core/defaults/themes/editorial.yaml +9 -6
- dataface/core/defaults/themes/neon.yaml +15 -8
- dataface/core/defaults/themes/plain.yaml +6 -4
- dataface/core/defaults/themes/stark.yaml +24 -7
- dataface/core/defaults/themes/vivid.yaml +15 -8
- dataface/core/diagnostics/__init__.py +12 -2
- dataface/core/diagnostics/codes_compile.py +17 -33
- dataface/core/diagnostics/codes_execute.py +149 -0
- dataface/core/diagnostics/codes_render.py +144 -31
- dataface/core/diagnostics/diagnostic.py +30 -0
- dataface/core/diagnostics/hints.py +19 -0
- dataface/core/dialects/base.py +17 -0
- dataface/core/dialects/postgres.py +14 -0
- dataface/core/dialects/redshift.py +5 -0
- dataface/core/execute/__init__.py +59 -14
- dataface/core/execute/adapters/__init__.py +1 -1
- dataface/core/execute/adapters/dbt_adapter.py +4 -1
- dataface/core/execute/adapters/dbt_adapter_factory.py +19 -0
- dataface/core/execute/adapters/dbt_utils.py +84 -20
- dataface/core/execute/adapters/postgres_connection_manager.py +48 -0
- dataface/core/execute/adapters/sql_adapter.py +72 -4
- dataface/core/execute/cache_keys.py +4 -9
- dataface/core/execute/dbt_jinja.py +65 -10
- dataface/core/execute/executor.py +94 -19
- dataface/core/execute/file_source_materializer.py +185 -32
- dataface/core/execute/trivial_local_cache.py +5 -2
- dataface/core/font_measure.py +28 -0
- dataface/core/fonts.py +8 -5
- dataface/core/inspect/__init__.py +46 -7
- dataface/core/project.py +19 -3
- dataface/core/render/chart/data_table_attachment.py +14 -12
- dataface/core/render/chart/emitters/_cartesian.py +21 -18
- dataface/core/render/chart/emitters/_channels.py +56 -8
- dataface/core/render/chart/emitters/_label_overlap.py +18 -15
- dataface/core/render/chart/emitters/_measured_label_padding.py +4 -4
- dataface/core/render/chart/emitters/area.py +3 -3
- dataface/core/render/chart/emitters/bar.py +60 -35
- dataface/core/render/chart/emitters/geo.py +8 -13
- dataface/core/render/chart/emitters/heatmap.py +20 -26
- dataface/core/render/chart/emitters/line.py +2 -2
- dataface/core/render/chart/emitters/scatter.py +7 -30
- dataface/core/render/chart/features/baseline.py +22 -9
- dataface/core/render/chart/features/endpoint_labels.py +55 -32
- dataface/core/render/chart/features/mirror_axis.py +12 -9
- dataface/core/render/chart/features/value_labels.py +1 -1
- dataface/core/render/chart/kpi.py +11 -90
- dataface/core/render/chart/mark_extents.py +339 -0
- dataface/core/render/chart/presentation.py +6 -8
- dataface/core/render/chart/rendering.py +100 -46
- dataface/core/render/chart/session.py +10 -0
- dataface/core/render/chart/spec.py +3 -0
- dataface/core/render/chart/spec_builders.py +18 -18
- dataface/core/render/chart/table.py +81 -36
- dataface/core/render/chart/table_support.py +2 -53
- dataface/core/render/chart/time_unit_detect.py +3 -2
- dataface/core/render/chart/translate.py +82 -44
- dataface/core/render/chart/type_inference.py +45 -25
- dataface/core/render/chart/validation.py +91 -0
- dataface/core/render/chart/vega_lite.py +30 -11
- dataface/core/render/chart/vl_field_maps.py +111 -76
- dataface/core/render/conditions.py +122 -0
- dataface/core/render/controls.py +60 -187
- dataface/core/render/converters/chart.py +16 -6
- dataface/core/render/data_format.py +1 -1
- dataface/core/render/face_resolve.py +7 -0
- dataface/core/render/faces.py +97 -32
- dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.woff2 +0 -0
- dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.woff2 +0 -0
- dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -93
- dataface/core/render/fonts/_font_face.css +10 -0
- dataface/core/render/layout_sizing.py +313 -238
- dataface/core/render/numeral_expr.py +80 -0
- dataface/core/render/renderer.py +14 -1
- dataface/core/render/sizing.py +42 -105
- dataface/core/render/svg_utils.py +60 -14
- dataface/core/render/terminal.py +1 -1
- dataface/core/render/variables_layout.py +211 -0
- dataface/core/render/variables_resolve.py +376 -0
- dataface/core/render/variables_strip.py +291 -368
- dataface/core/render/warnings/bar_band_width_too_narrow.py +64 -6
- dataface/core/render/warnings/base.py +18 -0
- dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +25 -3
- dataface/core/render/warnings/layout_min_exceeds_height.py +68 -0
- dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +12 -6
- dataface/core/render/warnings/palette_unsupported.py +6 -0
- dataface/core/render/warnings/pie_dominant_segment.py +1 -0
- dataface/core/render/warnings/pie_too_many_segments.py +1 -0
- dataface/core/render/warnings/point_map_out_of_projection.py +1 -0
- dataface/core/render/warnings/query_returned_zero_rows.py +1 -0
- dataface/core/render/warnings/redundant_encoding.py +20 -1
- dataface/core/render/warnings/registry.py +9 -3
- dataface/core/render/warnings/temporal_single_point.py +1 -0
- dataface/core/render/warnings/too_many_color_categories.py +1 -0
- dataface/core/render/warnings/too_many_x_categories.py +18 -2
- dataface/core/render/warnings/value_labels_crowd_width.py +9 -0
- dataface/core/render/warnings/y_encoding_mostly_null.py +1 -0
- dataface/core/render_format.py +18 -0
- dataface/core/serve/alias_index.py +16 -22
- dataface/core/text/format_d3.py +56 -0
- dataface/core/text/numeral_scale.py +218 -0
- dataface/core/utils.py +103 -10
- dataface/data/schemas/board-resolved/board-resolved.schema.json +8589 -7578
- dataface/data/schemas/yaml/0.3.2.json +1 -0
- dataface/data/schemas/yaml/manifest.json +7 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev150.dist-info}/METADATA +1 -3
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev150.dist-info}/RECORD +223 -213
- dataface/core/inspect/partials/categorical.yml +0 -38
- dataface/core/inspect/partials/date.yml +0 -38
- dataface/core/inspect/partials/numeric.yml +0 -53
- dataface/core/inspect/templates/charts.yml +0 -94
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev150.dist-info}/WHEEL +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev150.dist-info}/entry_points.txt +0 -0
- {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev150.dist-info}/licenses/LICENSE +0 -0
|
@@ -29,7 +29,7 @@ from dataface.core.compile.models.query.normalized import AnyQuery
|
|
|
29
29
|
from dataface.core.compile.models.style.resolved import (
|
|
30
30
|
ResolvedLegendStyle,
|
|
31
31
|
)
|
|
32
|
-
from dataface.core.compile.models.style.theme.board import PaddingStyle
|
|
32
|
+
from dataface.core.compile.models.style.theme.board import PaddingStyle, TitleStyle
|
|
33
33
|
from dataface.core.compile.models.vega_lite.contracts import Projection
|
|
34
34
|
|
|
35
35
|
|
|
@@ -94,23 +94,41 @@ class _BaseResolvedChartFields(BaseModel):
|
|
|
94
94
|
legend: ResolvedLegendStyle = Field(
|
|
95
95
|
description="Baked board-level legend style from the style cascade.",
|
|
96
96
|
)
|
|
97
|
-
layout_padding: PaddingStyle
|
|
98
|
-
default=None,
|
|
97
|
+
layout_padding: PaddingStyle = Field(
|
|
99
98
|
description=(
|
|
100
|
-
"Effective per-chart layout padding baked in at
|
|
101
|
-
"Combines board.charts.padding with any
|
|
102
|
-
"override.
|
|
103
|
-
"(e.g. unit tests). render_layout_item falls back to board-level padding "
|
|
104
|
-
"when None."
|
|
99
|
+
"Effective per-chart layout padding, baked in at construction time by "
|
|
100
|
+
"every family resolver. Combines board.charts.padding with any "
|
|
101
|
+
"chart-local style.<family>.padding override."
|
|
105
102
|
),
|
|
106
103
|
)
|
|
107
104
|
|
|
108
105
|
|
|
109
106
|
class _SharedResolvedChartFields(_BaseResolvedChartFields):
|
|
110
|
-
"""Adds title/subtitle for all families except KPI and Callout."""
|
|
107
|
+
"""Adds title/subtitle/background for all families except KPI and Callout."""
|
|
111
108
|
|
|
112
|
-
title: str | None = Field(
|
|
109
|
+
title: str | None = Field(
|
|
110
|
+
default=None,
|
|
111
|
+
description=(
|
|
112
|
+
"Jinja-resolved chart title, raw (no title-case transform). Also "
|
|
113
|
+
"backs non-VL consumers (e.g. the data-chart-title wire attribute) "
|
|
114
|
+
"that have always emitted the authored text as-is."
|
|
115
|
+
),
|
|
116
|
+
)
|
|
113
117
|
subtitle: str | None = Field(default=None, description="Chart subtitle.")
|
|
118
|
+
background: str = Field(
|
|
119
|
+
description=(
|
|
120
|
+
"Effective chart background: chart-local style.<family>.background "
|
|
121
|
+
"when authored, otherwise the board/face background."
|
|
122
|
+
),
|
|
123
|
+
)
|
|
124
|
+
title_style: TitleStyle = Field(
|
|
125
|
+
description=(
|
|
126
|
+
"Effective title style — chart-local style.<family>.title merged "
|
|
127
|
+
"onto the board title style. Render-time VL assembly reads "
|
|
128
|
+
"title_style.font.case to case-transform `title` (VL presentation "
|
|
129
|
+
"only), and title_style.overflow for title-wrap mode."
|
|
130
|
+
),
|
|
131
|
+
)
|
|
114
132
|
|
|
115
133
|
|
|
116
134
|
class _CartesianResolvedChartFields(_SharedResolvedChartFields):
|
|
@@ -74,10 +74,9 @@ class ResolvedCalloutChart(BaseModel):
|
|
|
74
74
|
default_factory=dict,
|
|
75
75
|
description="Always empty — callout has no data channels.",
|
|
76
76
|
)
|
|
77
|
-
layout_padding: PaddingStyle
|
|
78
|
-
default=None,
|
|
77
|
+
layout_padding: PaddingStyle = Field(
|
|
79
78
|
description=(
|
|
80
|
-
"Effective per-chart layout padding baked in at
|
|
79
|
+
"Effective per-chart layout padding, baked in at construction time. "
|
|
81
80
|
"Structural parity with _BaseResolvedChartFields so render_layout_item "
|
|
82
81
|
"can access chart.layout_padding without isinstance guards."
|
|
83
82
|
),
|
|
@@ -7,10 +7,10 @@ from typing import Literal
|
|
|
7
7
|
from pydantic import Field
|
|
8
8
|
|
|
9
9
|
from dataface.core.compile.models.chart.authored import KpiSupportConfig
|
|
10
|
+
from dataface.core.compile.models.primitives import FormatConfig
|
|
10
11
|
from dataface.core.compile.models.style.resolved import ResolvedKpiStyle
|
|
11
12
|
|
|
12
13
|
from ._base import _BaseResolvedChartFields
|
|
13
|
-
from ._layer import FormatState
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class ResolvedKpiChart(_BaseResolvedChartFields):
|
|
@@ -50,9 +50,13 @@ class ResolvedKpiChart(_BaseResolvedChartFields):
|
|
|
50
50
|
default="stacked",
|
|
51
51
|
description="Layout variant — dispatches to a distinct SVG emit path.",
|
|
52
52
|
)
|
|
53
|
-
format:
|
|
53
|
+
format: FormatConfig | None = Field(
|
|
54
54
|
default=None,
|
|
55
|
-
description=
|
|
55
|
+
description=(
|
|
56
|
+
"Final headline value format — resolve() has already applied the "
|
|
57
|
+
"narrative-notation and SI-compaction defaults against this "
|
|
58
|
+
"chart's row; render consumes it as-is."
|
|
59
|
+
),
|
|
56
60
|
)
|
|
57
61
|
style: ResolvedKpiStyle = Field(
|
|
58
62
|
description="KPI family style slice.",
|
|
@@ -39,7 +39,15 @@ class ResolvedTableChart(_SharedResolvedChartFields):
|
|
|
39
39
|
# not from source_chart.style (which no longer exists on Resolved*Chart).
|
|
40
40
|
columns: dict[str, TableColumnConfig] | None = Field(
|
|
41
41
|
default=None,
|
|
42
|
-
description=
|
|
42
|
+
description=(
|
|
43
|
+
"Final per-column display config (width, format, label, …), keyed by "
|
|
44
|
+
"column name. Complete for every column resolve can see: defaults, "
|
|
45
|
+
"explicit overrides, and runtime FK links are already merged. Excludes "
|
|
46
|
+
"pivot measure columns (see `column_defaults`) — their real key space "
|
|
47
|
+
"only exists after render's pivot transform runs. None means no "
|
|
48
|
+
"column-level config or runtime link facts apply — render falls back "
|
|
49
|
+
"to plain query columns."
|
|
50
|
+
),
|
|
43
51
|
)
|
|
44
52
|
header_overflow: Literal["clip", "truncate", "wrap-two", "wrap"] | None = Field(
|
|
45
53
|
default=None,
|
|
@@ -47,7 +55,13 @@ class ResolvedTableChart(_SharedResolvedChartFields):
|
|
|
47
55
|
)
|
|
48
56
|
column_defaults: TableColumnDefaultsConfig | None = Field(
|
|
49
57
|
default=None,
|
|
50
|
-
description=
|
|
58
|
+
description=(
|
|
59
|
+
"Table-level column defaults, kept only for render's two render-native "
|
|
60
|
+
"column key spaces — pivot leaf/measure columns and transpose's "
|
|
61
|
+
"__metric__/__value__ pair — neither of which exists until a "
|
|
62
|
+
"render-time data transform runs. Every other column (explicit or "
|
|
63
|
+
"query-inferred) already has defaults merged into `columns`."
|
|
64
|
+
),
|
|
51
65
|
)
|
|
52
66
|
style: ResolvedTableStyle = Field(
|
|
53
67
|
description="Table family style slice.",
|
|
@@ -217,6 +217,11 @@ class ExecutionConfig(ConfigNode):
|
|
|
217
217
|
max_query_duration_seconds: int = Field(
|
|
218
218
|
gt=0, description="Maximum seconds a single query may run (must be > 0)."
|
|
219
219
|
)
|
|
220
|
+
# Hard cap on files matched per glob pattern in file sources. Error is raised
|
|
221
|
+
# before any file is read so a runaway glob fails fast. Override in dataface.yml.
|
|
222
|
+
max_glob_file_count: int = Field(
|
|
223
|
+
gt=0, description="Maximum files a single glob may match (must be > 0)."
|
|
224
|
+
)
|
|
220
225
|
# sqlglot uses different dialect names than Dataface's public-facing dialect strings.
|
|
221
226
|
# This mapping normalizes Dataface names to sqlglot equivalents before parsing.
|
|
222
227
|
dialect_aliases: dict[str, str]
|
|
@@ -22,6 +22,7 @@ from pydantic import (
|
|
|
22
22
|
model_validator,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
+
from dataface.core.aliases import normalize_alias_url
|
|
25
26
|
from dataface.core.compile.models.cache import CachePatch
|
|
26
27
|
from dataface.core.compile.models.chart.authored import AuthoredChart
|
|
27
28
|
from dataface.core.compile.models.markers import Merge, Strategy
|
|
@@ -119,74 +120,6 @@ def _check_layout_item(v: Any) -> Any:
|
|
|
119
120
|
return _reject_chartref_dict(v)
|
|
120
121
|
|
|
121
122
|
|
|
122
|
-
class ForeachQuery(BaseModel):
|
|
123
|
-
"""Inline data source for compile-time foreach loops."""
|
|
124
|
-
|
|
125
|
-
model_config = ConfigDict(extra="forbid")
|
|
126
|
-
|
|
127
|
-
data: list[dict[str, Any]] | None = Field(
|
|
128
|
-
default=None,
|
|
129
|
-
description="Inline row data for compile-time iteration.",
|
|
130
|
-
)
|
|
131
|
-
static_data: list[dict[str, Any]] | None = Field(
|
|
132
|
-
default=None,
|
|
133
|
-
description="Alias for data; inline row data for compile-time iteration.",
|
|
134
|
-
)
|
|
135
|
-
palette: str | None = Field(
|
|
136
|
-
default=None,
|
|
137
|
-
description=(
|
|
138
|
-
"Palette name to iterate its live stops from the palette YAML source "
|
|
139
|
-
"of truth (one row per stop: {n: 1-indexed position, hex: sRGB hex}). "
|
|
140
|
-
"Accepts the same 'name:steps' / 'name_r' shorthand as palette()."
|
|
141
|
-
),
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
@model_validator(mode="after")
|
|
145
|
-
def _reject_palette_with_inline_data(self) -> ForeachQuery:
|
|
146
|
-
"""palette: is a distinct data source; combining it with data:/static_data:
|
|
147
|
-
is ambiguous about which one drives the loop."""
|
|
148
|
-
if self.palette is not None and (
|
|
149
|
-
self.data is not None or self.static_data is not None
|
|
150
|
-
):
|
|
151
|
-
raise ValueError(
|
|
152
|
-
"foreach.query.palette is mutually exclusive with data/static_data"
|
|
153
|
-
)
|
|
154
|
-
return self
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class ForeachConfig(BaseModel):
|
|
158
|
-
"""foreach loop configuration.
|
|
159
|
-
|
|
160
|
-
`as` is a Python keyword so the field is declared as `as_` with an alias.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
164
|
-
|
|
165
|
-
query: ForeachQuery = Field(
|
|
166
|
-
description="Inline data source providing the rows to iterate over."
|
|
167
|
-
)
|
|
168
|
-
as_: str = Field(
|
|
169
|
-
alias="as",
|
|
170
|
-
description="Variable name bound to each row (accessible as {{ as.field }}).",
|
|
171
|
-
)
|
|
172
|
-
items: Annotated[
|
|
173
|
-
list[
|
|
174
|
-
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
175
|
-
],
|
|
176
|
-
BeforeValidator(_check_layout_list),
|
|
177
|
-
] = Field(description="Layout item templates resolved once per data row.")
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
class ForeachItem(BaseModel):
|
|
181
|
-
"""Top-level foreach construct in a layout list."""
|
|
182
|
-
|
|
183
|
-
model_config = ConfigDict(extra="forbid")
|
|
184
|
-
|
|
185
|
-
foreach: ForeachConfig = Field(
|
|
186
|
-
description="foreach loop configuration with query, as, and items."
|
|
187
|
-
)
|
|
188
|
-
|
|
189
|
-
|
|
190
123
|
class GridItem(BaseModel):
|
|
191
124
|
"""Grid layout item with position and span.
|
|
192
125
|
|
|
@@ -209,7 +142,7 @@ class GridItem(BaseModel):
|
|
|
209
142
|
model_config = ConfigDict(extra="forbid")
|
|
210
143
|
|
|
211
144
|
item: Annotated[
|
|
212
|
-
str | AuthoredFace | AuthoredChart |
|
|
145
|
+
str | AuthoredFace | AuthoredChart | dict[str, AuthoredChart],
|
|
213
146
|
BeforeValidator(_check_layout_item),
|
|
214
147
|
] = Field(
|
|
215
148
|
description="Chart name or inline chart/face definition to place in this grid cell."
|
|
@@ -288,17 +221,11 @@ class TabItem(BaseModel):
|
|
|
288
221
|
|
|
289
222
|
# Layout fields (tab can contain nested layouts)
|
|
290
223
|
rows: Annotated[
|
|
291
|
-
list[
|
|
292
|
-
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
293
|
-
]
|
|
294
|
-
| None,
|
|
224
|
+
list[str | AuthoredFace | AuthoredChart | dict[str, AuthoredChart]] | None,
|
|
295
225
|
BeforeValidator(_check_layout_list),
|
|
296
226
|
] = Field(default=None, description="Vertical stack layout for this tab's content.")
|
|
297
227
|
cols: Annotated[
|
|
298
|
-
list[
|
|
299
|
-
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
300
|
-
]
|
|
301
|
-
| None,
|
|
228
|
+
list[str | AuthoredFace | AuthoredChart | dict[str, AuthoredChart]] | None,
|
|
302
229
|
BeforeValidator(_check_layout_list),
|
|
303
230
|
] = Field(default=None, description="Horizontal layout for this tab's content.")
|
|
304
231
|
grid: GridLayout | None = Field(
|
|
@@ -510,6 +437,7 @@ class AuthoredFace(_FaceDesugarMixin):
|
|
|
510
437
|
),
|
|
511
438
|
)
|
|
512
439
|
)
|
|
440
|
+
|
|
513
441
|
text: Annotated[str | None, Merge(Strategy.OVERRIDE)] = Field(
|
|
514
442
|
default=None, description="Markdown text content for text-only sections."
|
|
515
443
|
)
|
|
@@ -573,10 +501,7 @@ class AuthoredFace(_FaceDesugarMixin):
|
|
|
573
501
|
|
|
574
502
|
# Layout (exactly one should be present)
|
|
575
503
|
rows: Annotated[
|
|
576
|
-
list[
|
|
577
|
-
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
578
|
-
]
|
|
579
|
-
| None,
|
|
504
|
+
list[str | AuthoredFace | AuthoredChart | dict[str, AuthoredChart]] | None,
|
|
580
505
|
BeforeValidator(_check_layout_list),
|
|
581
506
|
Merge(Strategy.APPEND, nested=Strategy.CHILD),
|
|
582
507
|
] = Field(
|
|
@@ -584,10 +509,7 @@ class AuthoredFace(_FaceDesugarMixin):
|
|
|
584
509
|
description="Vertical stack layout: list of chart names or inline chart/face definitions.",
|
|
585
510
|
)
|
|
586
511
|
cols: Annotated[
|
|
587
|
-
list[
|
|
588
|
-
str | AuthoredFace | AuthoredChart | ForeachItem | dict[str, AuthoredChart]
|
|
589
|
-
]
|
|
590
|
-
| None,
|
|
512
|
+
list[str | AuthoredFace | AuthoredChart | dict[str, AuthoredChart]] | None,
|
|
591
513
|
BeforeValidator(_check_layout_list),
|
|
592
514
|
Merge(Strategy.APPEND, nested=Strategy.CHILD),
|
|
593
515
|
] = Field(
|
|
@@ -697,6 +619,21 @@ class AuthoredFace(_FaceDesugarMixin):
|
|
|
697
619
|
)
|
|
698
620
|
return data
|
|
699
621
|
|
|
622
|
+
@model_validator(mode="after")
|
|
623
|
+
def validate_aliases(self) -> AuthoredFace:
|
|
624
|
+
"""Every alias must be absolute, per ``aliases``' own description.
|
|
625
|
+
|
|
626
|
+
Checked through ``normalize_alias_url`` so the authored surface and the
|
|
627
|
+
redirect index cannot disagree about what a well-formed alias is; the
|
|
628
|
+
normalized value is discarded, since the face stores what the author
|
|
629
|
+
wrote.
|
|
630
|
+
"""
|
|
631
|
+
if self.aliases is None:
|
|
632
|
+
return self
|
|
633
|
+
for alias in self.aliases:
|
|
634
|
+
normalize_alias_url(alias)
|
|
635
|
+
return self
|
|
636
|
+
|
|
700
637
|
@model_validator(mode="after")
|
|
701
638
|
def validate_layout(self) -> AuthoredFace:
|
|
702
639
|
"""Ensure at least one layout type or content is defined."""
|
|
@@ -723,10 +660,8 @@ class AuthoredFace(_FaceDesugarMixin):
|
|
|
723
660
|
return self.source
|
|
724
661
|
|
|
725
662
|
|
|
726
|
-
# Resolve forward references (
|
|
727
|
-
#
|
|
728
|
-
# later in the file — rebuild after all classes are in scope)
|
|
729
|
-
ForeachConfig.model_rebuild()
|
|
663
|
+
# Resolve forward references (GridItem.item and TabItem.rows/cols reference types
|
|
664
|
+
# defined later in the file — rebuild after all classes are in scope)
|
|
730
665
|
GridItem.model_rebuild()
|
|
731
666
|
TabItem.model_rebuild()
|
|
732
667
|
AuthoredFace.model_rebuild()
|
|
@@ -104,7 +104,7 @@ class SkipInheritSlots:
|
|
|
104
104
|
``_ChartStyleBase`` must not inherit from the root cascade.
|
|
105
105
|
|
|
106
106
|
**On a leaf scalar field**: prevents the leaf from being added to the graph
|
|
107
|
-
via slot expansion. Use on
|
|
107
|
+
via slot expansion. Use on axis style fields that the style resolver does
|
|
108
108
|
not cascade — e.g. ``grid.dash``, ``ticks.count``, ``scale``, ``position`` —
|
|
109
109
|
so the inherit graph only declares what the resolver actually does.
|
|
110
110
|
|
|
@@ -8,12 +8,13 @@ cannot live in either without creating a circular import.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import re
|
|
11
|
-
from typing import Annotated, Any, Literal
|
|
11
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
12
12
|
|
|
13
13
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
14
14
|
|
|
15
15
|
from dataface.core.compile.models.factories import (
|
|
16
16
|
_PatchBase,
|
|
17
|
+
build_patch_model_ext,
|
|
17
18
|
register_as_own_patch,
|
|
18
19
|
register_patch,
|
|
19
20
|
)
|
|
@@ -410,7 +411,158 @@ class FontColorStrokeStyle(StrokeStyle):
|
|
|
410
411
|
)
|
|
411
412
|
|
|
412
413
|
|
|
413
|
-
|
|
414
|
+
# Vega/Vega-Lite's built-in named color schemes (https://vega.github.io/vega/docs/schemes/),
|
|
415
|
+
# the only strings ``ScaleTargetConfig.palette`` forwards straight through to VL's
|
|
416
|
+
# ``scale.scheme``. Sampled-discrete shorthand like "blues-9" is not a real scheme
|
|
417
|
+
# name — VL fails deep inside rendering ("Cannot read properties of undefined")
|
|
418
|
+
# rather than rejecting it up front, so this is validated at compile time instead.
|
|
419
|
+
#
|
|
420
|
+
# Empirically verified against this repo's pinned vl-convert (not hand-copied from
|
|
421
|
+
# docs alone): every name below was round-tripped through
|
|
422
|
+
# ``vlc.vegalite_to_svg`` with a quantitative ``scale.scheme`` encoding and
|
|
423
|
+
# confirmed to render — see ``test_vega_scheme_names_parity.py``, which re-runs
|
|
424
|
+
# that sweep and fails if this set and the bundled renderer ever diverge.
|
|
425
|
+
VEGA_SCHEME_NAMES: frozenset[str] = frozenset(
|
|
426
|
+
{
|
|
427
|
+
# Categorical
|
|
428
|
+
"accent",
|
|
429
|
+
"category10",
|
|
430
|
+
"category20",
|
|
431
|
+
"category20b",
|
|
432
|
+
"category20c",
|
|
433
|
+
"dark2",
|
|
434
|
+
"observable10",
|
|
435
|
+
"paired",
|
|
436
|
+
"pastel1",
|
|
437
|
+
"pastel2",
|
|
438
|
+
"set1",
|
|
439
|
+
"set2",
|
|
440
|
+
"set3",
|
|
441
|
+
"tableau10",
|
|
442
|
+
"tableau20",
|
|
443
|
+
# Sequential (single-hue)
|
|
444
|
+
"blues",
|
|
445
|
+
"tealblues",
|
|
446
|
+
"teals",
|
|
447
|
+
"greens",
|
|
448
|
+
"browns",
|
|
449
|
+
"greys",
|
|
450
|
+
"oranges",
|
|
451
|
+
"purples",
|
|
452
|
+
"reds",
|
|
453
|
+
"warmgreys",
|
|
454
|
+
# Sequential (multi-hue)
|
|
455
|
+
"viridis",
|
|
456
|
+
"magma",
|
|
457
|
+
"inferno",
|
|
458
|
+
"plasma",
|
|
459
|
+
"cividis",
|
|
460
|
+
"turbo",
|
|
461
|
+
"bluegreen",
|
|
462
|
+
"bluepurple",
|
|
463
|
+
"goldgreen",
|
|
464
|
+
"goldorange",
|
|
465
|
+
"goldred",
|
|
466
|
+
"greenblue",
|
|
467
|
+
"orangered",
|
|
468
|
+
"purplebluegreen",
|
|
469
|
+
"purpleblue",
|
|
470
|
+
"purplered",
|
|
471
|
+
"redpurple",
|
|
472
|
+
"yellowgreenblue",
|
|
473
|
+
"yellowgreen",
|
|
474
|
+
"yelloworangebrown",
|
|
475
|
+
"yelloworangered",
|
|
476
|
+
# Diverging
|
|
477
|
+
"blueorange",
|
|
478
|
+
"brownbluegreen",
|
|
479
|
+
"purplegreen",
|
|
480
|
+
"pinkyellowgreen",
|
|
481
|
+
"purpleorange",
|
|
482
|
+
"redblue",
|
|
483
|
+
"redgrey",
|
|
484
|
+
"redyellowblue",
|
|
485
|
+
"redyellowgreen",
|
|
486
|
+
"spectral",
|
|
487
|
+
# Cyclical
|
|
488
|
+
"rainbow",
|
|
489
|
+
"sinebow",
|
|
490
|
+
# For dark backgrounds
|
|
491
|
+
"darkblue",
|
|
492
|
+
"darkgold",
|
|
493
|
+
"darkgreen",
|
|
494
|
+
"darkmulti",
|
|
495
|
+
"darkred",
|
|
496
|
+
# For light backgrounds
|
|
497
|
+
"lightgreyred",
|
|
498
|
+
"lightgreyteal",
|
|
499
|
+
"lightmulti",
|
|
500
|
+
"lightorange",
|
|
501
|
+
"lighttealblue",
|
|
502
|
+
}
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class ScaleTargetPaletteValidationMixin(BaseModel):
|
|
507
|
+
"""Shared authored-input validator for ``ScaleTargetConfig.palette``.
|
|
508
|
+
|
|
509
|
+
Inherited by both ``ScaleTargetConfig`` and ``ScaleTargetConfigPatch`` so
|
|
510
|
+
chart-local authored patches reject a bad palette at the compile boundary
|
|
511
|
+
(where ``normalize_face()`` wraps the resulting ``pydantic.ValidationError``
|
|
512
|
+
into a proper ``ERR-VALIDATION-FIELD`` diagnostic) instead of only at
|
|
513
|
+
resolve time, where the raw pydantic error leaks past Dataface's error
|
|
514
|
+
formatting — ``build_patch_model_ext`` only carries over validators that
|
|
515
|
+
live on the patch model's base class (mirrors ``ScaleDomainValidationMixin``).
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
model_config = ConfigDict(extra="forbid")
|
|
519
|
+
|
|
520
|
+
@model_validator(mode="before")
|
|
521
|
+
@classmethod
|
|
522
|
+
def _validate_named_palette(cls, data: object) -> object:
|
|
523
|
+
"""A string palette is either a literal Vega scheme name (forwarded
|
|
524
|
+
as-is to VL's ``scale.scheme`` by geo/heatmap gradient rendering) or a
|
|
525
|
+
Dataface named palette (resolved to hex stops downstream by
|
|
526
|
+
``resolve_palette_stops``). Reject anything that resolves as neither.
|
|
527
|
+
|
|
528
|
+
A dash-suffixed name whose prefix is a real scheme (``"blues-9"``,
|
|
529
|
+
VL's sampled-discrete shorthand, which Dataface does not forward) gets
|
|
530
|
+
a specific hint naming the continuous scheme instead of the generic
|
|
531
|
+
unknown-palette message — that shape is the exact mistake this
|
|
532
|
+
validator exists to catch: an author reaching for N discrete buckets
|
|
533
|
+
that Dataface doesn't support yet, not a random typo.
|
|
534
|
+
|
|
535
|
+
``mode="before"`` on the raw dict (not ``field_validator``) so this
|
|
536
|
+
runs unchanged on both ``ScaleTargetConfig`` and the generated
|
|
537
|
+
``ScaleTargetConfigPatch`` — a field-scoped validator would need the
|
|
538
|
+
field to exist on this mixin itself, which a patch base never does.
|
|
539
|
+
"""
|
|
540
|
+
if not isinstance(data, dict):
|
|
541
|
+
return data
|
|
542
|
+
value = data.get("palette")
|
|
543
|
+
if not isinstance(value, str) or value in VEGA_SCHEME_NAMES:
|
|
544
|
+
return data
|
|
545
|
+
from dataface.core.compile.palette import ( # noqa: PLC0415
|
|
546
|
+
UnknownPaletteError,
|
|
547
|
+
palette as resolve_palette,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
try:
|
|
551
|
+
resolve_palette(value)
|
|
552
|
+
except UnknownPaletteError as exc:
|
|
553
|
+
base, _, suffix = value.rpartition("-")
|
|
554
|
+
if base in VEGA_SCHEME_NAMES and suffix.isdigit():
|
|
555
|
+
raise ValueError(
|
|
556
|
+
f"palette {value!r} requests a {suffix}-step discrete "
|
|
557
|
+
f"variant of the '{base}' scheme. Dataface does not support "
|
|
558
|
+
"bucketed/quantized color scales yet — use the continuous "
|
|
559
|
+
f"scheme '{base}' instead."
|
|
560
|
+
) from None
|
|
561
|
+
raise ValueError(str(exc)) from None
|
|
562
|
+
return data
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
class ScaleTargetConfig(ScaleTargetPaletteValidationMixin):
|
|
414
566
|
"""Scale configuration for a single style target (background or color).
|
|
415
567
|
|
|
416
568
|
Used as a chart channel annotation (``ColumnScaleConfig``) and as the
|
|
@@ -447,6 +599,27 @@ class ScaleTargetConfig(BaseModel):
|
|
|
447
599
|
)
|
|
448
600
|
|
|
449
601
|
|
|
602
|
+
# Patch type for ScaleTargetConfig — generated (and registered) here, right after
|
|
603
|
+
# the compiled class, so every later recursive build_patch_model_ext call that
|
|
604
|
+
# encounters ScaleTargetConfig as a nested field (StaticGradientColorStyle.gradient,
|
|
605
|
+
# ColumnScaleConfig.color/background) reuses this exact registered class instead of
|
|
606
|
+
# a vanilla auto-generated one with no palette validation.
|
|
607
|
+
if TYPE_CHECKING:
|
|
608
|
+
|
|
609
|
+
class ScaleTargetConfigPatch(ScaleTargetConfig):
|
|
610
|
+
pass
|
|
611
|
+
|
|
612
|
+
else:
|
|
613
|
+
|
|
614
|
+
class _ScaleTargetConfigPatchBase(_PatchBase, ScaleTargetPaletteValidationMixin):
|
|
615
|
+
"""Base for ScaleTargetConfigPatch: extra="forbid" from _PatchBase plus
|
|
616
|
+
the palette validation from ScaleTargetPaletteValidationMixin."""
|
|
617
|
+
|
|
618
|
+
ScaleTargetConfigPatch = register_patch(ScaleTargetConfig)(
|
|
619
|
+
build_patch_model_ext(ScaleTargetConfig, base_cls=_ScaleTargetConfigPatchBase)
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
|
|
450
623
|
class CategoricalColorStyle(BaseModel):
|
|
451
624
|
"""Categorical palette config: per-series colors and single-series ink list."""
|
|
452
625
|
|
|
@@ -287,114 +287,6 @@ class AuthoredSchemaQuery(_BaseQueryFields):
|
|
|
287
287
|
)
|
|
288
288
|
|
|
289
289
|
|
|
290
|
-
class _AuthoredLookmlQueryFields(_BaseQueryFields):
|
|
291
|
-
"""Fields shared by both LookML authoring shapes.
|
|
292
|
-
|
|
293
|
-
``source:`` names the LookML *model* (declared under a warehouse source's
|
|
294
|
-
``models:`` map), not a connection. Optional install:
|
|
295
|
-
``pip install dataface[lookml]``.
|
|
296
|
-
"""
|
|
297
|
-
|
|
298
|
-
type: Literal["lookml"] = Field(
|
|
299
|
-
default="lookml",
|
|
300
|
-
description="Query adapter type.",
|
|
301
|
-
)
|
|
302
|
-
fields: list[str] | None = Field(
|
|
303
|
-
default=None,
|
|
304
|
-
description=(
|
|
305
|
-
"Qualified 'view.field' refs to select. "
|
|
306
|
-
"Optional at authoring time: collected from chart channels when absent."
|
|
307
|
-
),
|
|
308
|
-
)
|
|
309
|
-
sorts: list[str] | None = Field(
|
|
310
|
-
default=None,
|
|
311
|
-
description=(
|
|
312
|
-
"Looker sort clauses, e.g. "
|
|
313
|
-
"['view.field', 'view.other desc']. Lowered to ORDER BY via likeml."
|
|
314
|
-
),
|
|
315
|
-
)
|
|
316
|
-
pivots: list[str] | None = Field(
|
|
317
|
-
default=None,
|
|
318
|
-
description="Qualified 'view.field' refs to pivot on.",
|
|
319
|
-
)
|
|
320
|
-
pivots_unwrapped: bool | None = Field(
|
|
321
|
-
default=None,
|
|
322
|
-
description=(
|
|
323
|
-
"Which of Looker's two pivot SQL shapes to emit: "
|
|
324
|
-
"False/None = wrapped (5-layer DENSE_RANK), True = plain GROUP BY."
|
|
325
|
-
),
|
|
326
|
-
)
|
|
327
|
-
total: bool | None = Field(
|
|
328
|
-
default=None,
|
|
329
|
-
description="Looker grand-totals flag.",
|
|
330
|
-
)
|
|
331
|
-
subtotals: list[str] | None = Field(
|
|
332
|
-
default=None,
|
|
333
|
-
description="Dimension refs to subtotal via GROUP BY ROLLUP.",
|
|
334
|
-
)
|
|
335
|
-
filter_expression: str | None = Field(
|
|
336
|
-
default=None,
|
|
337
|
-
description=(
|
|
338
|
-
"Looker query-level free-form boolean filter over ${view.field} refs."
|
|
339
|
-
),
|
|
340
|
-
)
|
|
341
|
-
dynamic_fields: list[dict[str, Any]] | None = Field(
|
|
342
|
-
default=None,
|
|
343
|
-
description=(
|
|
344
|
-
"Looker dynamic fields: custom measures / custom "
|
|
345
|
-
"dimensions synthesized into SQL. Table calculations compose as a derived "
|
|
346
|
-
"wrapper query, not here."
|
|
347
|
-
),
|
|
348
|
-
)
|
|
349
|
-
limit: int | None = Field(
|
|
350
|
-
default=None,
|
|
351
|
-
description="Maximum number of rows returned.",
|
|
352
|
-
)
|
|
353
|
-
column_limit: int | None = Field(
|
|
354
|
-
default=None,
|
|
355
|
-
description="Looker column_limit — caps wrapped-pivot columns.",
|
|
356
|
-
)
|
|
357
|
-
filters: dict[str, Any] | None = Field(
|
|
358
|
-
default=None,
|
|
359
|
-
description=(
|
|
360
|
-
"RAW Looker filter expressions keyed by 'view.field' "
|
|
361
|
-
'(e.g. "13 months", "gold") — translated to SQL predicates by likeml.'
|
|
362
|
-
),
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
class AuthoredLookmlQuery(_AuthoredLookmlQueryFields):
|
|
367
|
-
"""LookML query rooted at one explore."""
|
|
368
|
-
|
|
369
|
-
explore: Annotated[
|
|
370
|
-
str,
|
|
371
|
-
Field(
|
|
372
|
-
description=(
|
|
373
|
-
"LookML explore name. Presence of this key infers type: lookml. "
|
|
374
|
-
"Use a merge query instead to combine multiple sources."
|
|
375
|
-
)
|
|
376
|
-
),
|
|
377
|
-
]
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
class AuthoredLookmlMergeQuery(_AuthoredLookmlQueryFields):
|
|
381
|
-
"""LookML query that merges multiple sources."""
|
|
382
|
-
|
|
383
|
-
merge: Annotated[
|
|
384
|
-
list[dict[str, Any]],
|
|
385
|
-
Field(
|
|
386
|
-
description=(
|
|
387
|
-
"LookML merge sources — a list of source specs. "
|
|
388
|
-
"Each is either an explore spec ({explore, fields, filters?, merge_on?}) "
|
|
389
|
-
"or a query reference ({query, columns, merge_on?}) for a derived source "
|
|
390
|
-
"such as a table-calc wrapper. First is the primary; each secondary's "
|
|
391
|
-
"`merge_on` maps its key to the primary's. Presence of this key infers "
|
|
392
|
-
"type: lookml. Compiled to a FULL OUTER JOIN via likeml."
|
|
393
|
-
)
|
|
394
|
-
),
|
|
395
|
-
]
|
|
396
|
-
|
|
397
|
-
|
|
398
290
|
def _discriminate_authored_query(v: Any) -> str | None:
|
|
399
291
|
"""Custom discriminator: return the type tag for the AuthoredQuery union.
|
|
400
292
|
|
|
@@ -419,14 +311,6 @@ def _discriminate_authored_query(v: Any) -> str | None:
|
|
|
419
311
|
return (
|
|
420
312
|
"values_rows" if isinstance(v, dict) and "rows" in v else "values_compact"
|
|
421
313
|
)
|
|
422
|
-
if t == "lookml":
|
|
423
|
-
if isinstance(v, AuthoredLookmlMergeQuery):
|
|
424
|
-
return "lookml_merge"
|
|
425
|
-
if isinstance(v, AuthoredLookmlQuery):
|
|
426
|
-
return "lookml_explore"
|
|
427
|
-
return (
|
|
428
|
-
"lookml_merge" if isinstance(v, dict) and "merge" in v else "lookml_explore"
|
|
429
|
-
)
|
|
430
314
|
return t
|
|
431
315
|
|
|
432
316
|
|
|
@@ -436,9 +320,7 @@ AuthoredQuery = Annotated[
|
|
|
436
320
|
| Annotated[AuthoredHttpQuery, Tag("http")]
|
|
437
321
|
| Annotated[AuthoredValuesQuery, Tag("values_rows")]
|
|
438
322
|
| Annotated[AuthoredCompactValuesQuery, Tag("values_compact")]
|
|
439
|
-
| Annotated[AuthoredSchemaQuery, Tag("schema")]
|
|
440
|
-
| Annotated[AuthoredLookmlQuery, Tag("lookml_explore")]
|
|
441
|
-
| Annotated[AuthoredLookmlMergeQuery, Tag("lookml_merge")],
|
|
323
|
+
| Annotated[AuthoredSchemaQuery, Tag("schema")],
|
|
442
324
|
Discriminator(_discriminate_authored_query),
|
|
443
325
|
]
|
|
444
326
|
"""Discriminated union over the authored query types.
|