dataface 0.3.2.dev111__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/agent_api/docs/yaml-reference.md +33 -133
- dataface/agent_api/serve.py +0 -2
- dataface/agent_api/validate.py +8 -0
- dataface/cli/commands/render.py +6 -0
- dataface/cli/commands/serve.py +0 -6
- dataface/cli/main.py +20 -14
- dataface/core/aliases.py +27 -0
- dataface/core/compile/__init__.py +0 -8
- dataface/core/compile/channel.py +0 -13
- dataface/core/compile/compiler.py +73 -71
- dataface/core/compile/introspection.py +2 -13
- 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/config.py +5 -0
- dataface/core/compile/models/face/authored.py +24 -89
- dataface/core/compile/models/query/authored.py +1 -119
- dataface/core/compile/models/refs.py +0 -2
- dataface/core/compile/models/source.py +73 -159
- dataface/core/compile/models/style/theme/variables.py +0 -3
- dataface/core/compile/normalize_charts.py +4 -152
- dataface/core/compile/normalize_layout.py +20 -284
- dataface/core/compile/normalize_queries.py +3 -506
- dataface/core/compile/normalizer.py +4 -10
- dataface/core/compile/palette.py +19 -7
- dataface/core/compile/validator.py +121 -44
- dataface/core/compile/yaml_error_formatter.py +107 -2
- dataface/core/dashboard.py +3 -1
- dataface/core/defaults/default_config.yml +4 -0
- dataface/core/defaults/themes/_base.yaml +0 -1
- dataface/core/defaults/themes/editorial.yaml +3 -0
- dataface/core/defaults/themes/stark.yaml +8 -0
- dataface/core/diagnostics/__init__.py +10 -0
- dataface/core/diagnostics/codes_compile.py +4 -2
- dataface/core/diagnostics/codes_execute.py +70 -0
- dataface/core/diagnostics/codes_render.py +58 -4
- dataface/core/execute/adapters/__init__.py +1 -1
- dataface/core/execute/cache_keys.py +4 -9
- dataface/core/execute/executor.py +25 -3
- dataface/core/execute/file_source_materializer.py +185 -32
- dataface/core/execute/trivial_local_cache.py +5 -2
- dataface/core/inspect/__init__.py +0 -1
- dataface/core/project.py +6 -2
- dataface/core/render/chart/mark_extents.py +339 -0
- dataface/core/render/chart/rendering.py +97 -34
- dataface/core/render/conditions.py +122 -0
- dataface/core/render/controls.py +60 -187
- dataface/core/render/data_format.py +1 -1
- dataface/core/render/face_resolve.py +7 -0
- dataface/core/render/faces.py +94 -30
- dataface/core/render/layout_sizing.py +46 -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/base.py +18 -0
- dataface/core/render/warnings/layout_min_exceeds_height.py +68 -0
- dataface/core/render/warnings/registry.py +2 -0
- dataface/core/serve/alias_index.py +16 -22
- dataface/data/schemas/board-resolved/board-resolved.schema.json +1 -499
- {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/METADATA +1 -3
- {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/RECORD +69 -67
- 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.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/WHEEL +0 -0
- {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/entry_points.txt +0 -0
- {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/licenses/LICENSE +0 -0
|
@@ -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()
|
|
@@ -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.
|
|
@@ -43,8 +43,6 @@ def infer_query_type_from_keys(query_dict: dict[str, Any]) -> str:
|
|
|
43
43
|
return "values"
|
|
44
44
|
if "metrics" in query_dict or "dimensions" in query_dict:
|
|
45
45
|
return "metricflow"
|
|
46
|
-
if "explore" in query_dict or "merge" in query_dict:
|
|
47
|
-
return "lookml"
|
|
48
46
|
if "url" in query_dict:
|
|
49
47
|
return "http"
|
|
50
48
|
return "sql"
|