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.
Files changed (73) hide show
  1. dataface/DATAFACE_SYNTAX.md +15 -4
  2. dataface/agent_api/docs/yaml-reference.md +33 -133
  3. dataface/agent_api/serve.py +0 -2
  4. dataface/agent_api/validate.py +8 -0
  5. dataface/cli/commands/render.py +6 -0
  6. dataface/cli/commands/serve.py +0 -6
  7. dataface/cli/main.py +20 -14
  8. dataface/core/aliases.py +27 -0
  9. dataface/core/compile/__init__.py +0 -8
  10. dataface/core/compile/channel.py +0 -13
  11. dataface/core/compile/compiler.py +73 -71
  12. dataface/core/compile/introspection.py +2 -13
  13. dataface/core/compile/models/chart/authored/_base.py +1 -1
  14. dataface/core/compile/models/chart/normalized/__init__.py +2 -0
  15. dataface/core/compile/models/chart/normalized/_base.py +26 -0
  16. dataface/core/compile/models/config.py +5 -0
  17. dataface/core/compile/models/face/authored.py +24 -89
  18. dataface/core/compile/models/query/authored.py +1 -119
  19. dataface/core/compile/models/refs.py +0 -2
  20. dataface/core/compile/models/source.py +73 -159
  21. dataface/core/compile/models/style/theme/variables.py +0 -3
  22. dataface/core/compile/normalize_charts.py +4 -152
  23. dataface/core/compile/normalize_layout.py +20 -284
  24. dataface/core/compile/normalize_queries.py +3 -506
  25. dataface/core/compile/normalizer.py +4 -10
  26. dataface/core/compile/palette.py +19 -7
  27. dataface/core/compile/validator.py +121 -44
  28. dataface/core/compile/yaml_error_formatter.py +107 -2
  29. dataface/core/dashboard.py +3 -1
  30. dataface/core/defaults/default_config.yml +4 -0
  31. dataface/core/defaults/themes/_base.yaml +0 -1
  32. dataface/core/defaults/themes/editorial.yaml +3 -0
  33. dataface/core/defaults/themes/stark.yaml +8 -0
  34. dataface/core/diagnostics/__init__.py +10 -0
  35. dataface/core/diagnostics/codes_compile.py +4 -2
  36. dataface/core/diagnostics/codes_execute.py +70 -0
  37. dataface/core/diagnostics/codes_render.py +58 -4
  38. dataface/core/execute/adapters/__init__.py +1 -1
  39. dataface/core/execute/cache_keys.py +4 -9
  40. dataface/core/execute/executor.py +25 -3
  41. dataface/core/execute/file_source_materializer.py +185 -32
  42. dataface/core/execute/trivial_local_cache.py +5 -2
  43. dataface/core/inspect/__init__.py +0 -1
  44. dataface/core/project.py +6 -2
  45. dataface/core/render/chart/mark_extents.py +339 -0
  46. dataface/core/render/chart/rendering.py +97 -34
  47. dataface/core/render/conditions.py +122 -0
  48. dataface/core/render/controls.py +60 -187
  49. dataface/core/render/data_format.py +1 -1
  50. dataface/core/render/face_resolve.py +7 -0
  51. dataface/core/render/faces.py +94 -30
  52. dataface/core/render/layout_sizing.py +46 -0
  53. dataface/core/render/renderer.py +14 -1
  54. dataface/core/render/sizing.py +42 -105
  55. dataface/core/render/svg_utils.py +60 -14
  56. dataface/core/render/terminal.py +1 -1
  57. dataface/core/render/variables_layout.py +211 -0
  58. dataface/core/render/variables_resolve.py +376 -0
  59. dataface/core/render/variables_strip.py +291 -368
  60. dataface/core/render/warnings/base.py +18 -0
  61. dataface/core/render/warnings/layout_min_exceeds_height.py +68 -0
  62. dataface/core/render/warnings/registry.py +2 -0
  63. dataface/core/serve/alias_index.py +16 -22
  64. dataface/data/schemas/board-resolved/board-resolved.schema.json +1 -499
  65. {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/METADATA +1 -3
  66. {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/RECORD +69 -67
  67. dataface/core/inspect/partials/categorical.yml +0 -38
  68. dataface/core/inspect/partials/date.yml +0 -38
  69. dataface/core/inspect/partials/numeric.yml +0 -53
  70. dataface/core/inspect/templates/charts.yml +0 -94
  71. {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/WHEEL +0 -0
  72. {dataface-0.3.2.dev111.dist-info → dataface-0.3.2.dev150.dist-info}/entry_points.txt +0 -0
  73. {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 | ForeachItem | dict[str, 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 (ForeachConfig.items references ForeachItem and
727
- # AuthoredFace; GridItem.item and TabItem.rows/cols also reference types defined
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"