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
@@ -18,9 +18,6 @@ from dataface.core.compile.models.chart.authored import (
18
18
  from dataface.core.compile.models.chart.normalized import Chart
19
19
  from dataface.core.compile.models.face.authored import (
20
20
  AuthoredFace,
21
- ForeachConfig,
22
- ForeachItem,
23
- ForeachQuery,
24
21
  GridLayout,
25
22
  LayoutType,
26
23
  TabLayout,
@@ -36,7 +33,6 @@ from dataface.core.compile.normalize_charts import (
36
33
  generate_inline_chart_id,
37
34
  normalize_chart,
38
35
  )
39
- from dataface.core.compile.palette import palette as resolve_palette
40
36
  from dataface.core.utils import UniqueKeyLoader
41
37
 
42
38
 
@@ -75,7 +71,6 @@ def build_unified_layout(
75
71
  theme: str | None = None,
76
72
  parent_variables: dict[str, Any] | None = None,
77
73
  *,
78
- _resolve_jinja: bool = False,
79
74
  resolved_style: ResolvedStyle,
80
75
  parent_level: int = 0,
81
76
  sources: dict[str, Any],
@@ -125,7 +120,6 @@ def build_unified_layout(
125
120
  theme,
126
121
  used_chart_ids,
127
122
  parent_variables,
128
- _resolve_jinja=_resolve_jinja,
129
123
  parent_level=parent_level,
130
124
  cache_root=cache_root,
131
125
  sources=sources,
@@ -148,7 +142,6 @@ def build_unified_layout(
148
142
  theme,
149
143
  used_chart_ids,
150
144
  parent_variables,
151
- _resolve_jinja=_resolve_jinja,
152
145
  parent_level=parent_level,
153
146
  cache_root=cache_root,
154
147
  sources=sources,
@@ -171,7 +164,6 @@ def build_unified_layout(
171
164
  theme,
172
165
  used_chart_ids,
173
166
  parent_variables,
174
- _resolve_jinja=_resolve_jinja,
175
167
  parent_level=parent_level,
176
168
  cache_root=cache_root,
177
169
  sources=sources,
@@ -197,7 +189,6 @@ def build_unified_layout(
197
189
  face_cache,
198
190
  chart_registry,
199
191
  theme,
200
- _resolve_jinja=_resolve_jinja,
201
192
  resolved_style=resolved_style,
202
193
  parent_level=parent_level,
203
194
  cache_root=cache_root,
@@ -257,7 +248,6 @@ def build_unified_layout(
257
248
  theme,
258
249
  used_chart_ids,
259
250
  parent_variables,
260
- _resolve_jinja=_resolve_jinja,
261
251
  parent_level=parent_level,
262
252
  cache_root=cache_root,
263
253
  sources=sources,
@@ -284,7 +274,6 @@ def _resolve_layout_items(
284
274
  theme: str | None = None,
285
275
  used_chart_ids: set[str] | None = None,
286
276
  parent_variables: dict[str, Any] | None = None,
287
- _resolve_jinja: bool = False,
288
277
  parent_level: int = 0,
289
278
  *,
290
279
  sources: dict[str, Any],
@@ -320,50 +309,26 @@ def _resolve_layout_items(
320
309
  local_chart_ids = local_chart_ids or set()
321
310
 
322
311
  for idx, item in enumerate(items):
323
- # Handle foreach construct
324
- if isinstance(item, ForeachItem):
325
- foreach_items = _resolve_foreach_item(
326
- item.foreach,
327
- charts,
328
- query_registry,
329
- face_id,
330
- depth,
331
- f"{prefix}{idx}",
332
- base_dir,
333
- default_source,
334
- face_cache,
335
- chart_registry,
336
- theme,
337
- used_chart_ids,
338
- parent_variables,
339
- parent_level=parent_level,
340
- cache_root=cache_root,
341
- sources=sources,
342
- local_chart_ids=local_chart_ids,
343
- )
344
- resolved.extend(foreach_items)
345
- else:
346
- layout_item = _resolve_single_item(
347
- item,
348
- charts,
349
- query_registry,
350
- face_id,
351
- depth,
352
- f"{prefix}{idx}",
353
- base_dir,
354
- default_source,
355
- face_cache,
356
- chart_registry,
357
- theme,
358
- used_chart_ids,
359
- parent_variables,
360
- _resolve_jinja=_resolve_jinja,
361
- parent_level=parent_level,
362
- cache_root=cache_root,
363
- sources=sources,
364
- local_chart_ids=local_chart_ids,
365
- )
366
- resolved.append(layout_item)
312
+ layout_item = _resolve_single_item(
313
+ item,
314
+ charts,
315
+ query_registry,
316
+ face_id,
317
+ depth,
318
+ f"{prefix}{idx}",
319
+ base_dir,
320
+ default_source,
321
+ face_cache,
322
+ chart_registry,
323
+ theme,
324
+ used_chart_ids,
325
+ parent_variables,
326
+ parent_level=parent_level,
327
+ cache_root=cache_root,
328
+ sources=sources,
329
+ local_chart_ids=local_chart_ids,
330
+ )
331
+ resolved.append(layout_item)
367
332
 
368
333
  return resolved
369
334
 
@@ -382,7 +347,6 @@ def _resolve_single_item(
382
347
  theme: str | None = None,
383
348
  used_chart_ids: set[str] | None = None,
384
349
  parent_variables: dict[str, Any] | None = None,
385
- _resolve_jinja: bool = False,
386
350
  parent_level: int = 0,
387
351
  *,
388
352
  sources: dict[str, Any],
@@ -426,7 +390,6 @@ def _resolve_single_item(
426
390
  chart_registry,
427
391
  theme,
428
392
  parent_variables,
429
- _resolve_jinja=_resolve_jinja,
430
393
  parent_level=parent_level,
431
394
  cache_root=cache_root,
432
395
  sources=sources,
@@ -466,13 +429,6 @@ def _resolve_single_item(
466
429
  if isinstance(item, AuthoredFace):
467
430
  from dataface.core.compile.normalizer import normalize_face
468
431
 
469
- # In foreach context, resolve Jinja templates baked into the typed face.
470
- # Pydantic parsed templates as literal strings; dump to dict, resolve, re-validate.
471
- if _resolve_jinja and parent_variables:
472
- face_dict = item.model_dump(exclude_none=True, by_alias=False)
473
- face_dict = _resolve_dict_templates(face_dict, parent_variables)
474
- item = AuthoredFace.model_validate(face_dict)
475
-
476
432
  details = item.details
477
433
  user_width = str(item.width) if item.width is not None else None
478
434
  layout_height = str(item.height) if item.height is not None else None
@@ -491,7 +447,6 @@ def _resolve_single_item(
491
447
  "sources": sources,
492
448
  "theme": theme,
493
449
  "variables": parent_variables,
494
- "_resolve_jinja": _resolve_jinja,
495
450
  },
496
451
  query_registry=query_registry,
497
452
  chart_registry=chart_registry,
@@ -550,9 +505,6 @@ def _resolve_single_item(
550
505
 
551
506
 
552
507
  # Layout keys that should not have templates resolved (processed by layout resolution)
553
- _LAYOUT_KEYS = frozenset({"rows", "cols", "grid", "tabs"})
554
-
555
-
556
508
  def _layout_source_path(item_id: str) -> str:
557
509
  match = re.fullmatch(r"(row|col|grid)(\d+)", item_id)
558
510
  if not match:
@@ -561,54 +513,6 @@ def _layout_source_path(item_id: str) -> str:
561
513
  return f"{key}[{match.group(2)}]"
562
514
 
563
515
 
564
- def _resolve_dict_templates(
565
- data: dict[str, Any],
566
- variables: dict[str, Any],
567
- ) -> dict[str, Any]:
568
- """Resolve Jinja templates recursively in dict values.
569
-
570
- Used to resolve loop variables and inherited variables in nested face
571
- definitions created by foreach (compile-time static expansion).
572
-
573
- Args:
574
- data: Dict with potentially templated string values
575
- variables: Variables for Jinja resolution
576
-
577
- Returns:
578
- Dict with resolved string values
579
- """
580
- from dataface.core.compile.jinja import resolve_jinja_template
581
-
582
- result: dict[str, Any] = {}
583
- for key, value in data.items():
584
- if isinstance(value, str):
585
- # Resolve Jinja templates in strings
586
- result[key] = resolve_jinja_template(value, variables, strict=False)
587
- elif isinstance(value, dict):
588
- # Recurse into nested dicts
589
- result[key] = _resolve_dict_templates(value, variables)
590
- elif isinstance(value, list):
591
- # Don't recurse into layout items - those are processed by _resolve_layout_items
592
- if key in _LAYOUT_KEYS:
593
- result[key] = value
594
- else:
595
- result[key] = [
596
- (
597
- _resolve_dict_templates(v, variables)
598
- if isinstance(v, dict)
599
- else (
600
- resolve_jinja_template(v, variables, strict=False)
601
- if isinstance(v, str)
602
- else v
603
- )
604
- )
605
- for v in value
606
- ]
607
- else:
608
- result[key] = value
609
- return result
610
-
611
-
612
516
  def _resolve_board_file_import(
613
517
  file_path: str,
614
518
  query_registry: dict[str, AnyQuery],
@@ -621,7 +525,6 @@ def _resolve_board_file_import(
621
525
  chart_registry: dict[str, Any] | None = None,
622
526
  theme: str | None = None,
623
527
  parent_variables: dict[str, Any] | None = None,
624
- _resolve_jinja: bool = False,
625
528
  parent_level: int = 0,
626
529
  *,
627
530
  sources: dict[str, Any],
@@ -647,8 +550,6 @@ def _resolve_board_file_import(
647
550
  theme: Vega-Lite theme to inherit
648
551
  used_chart_ids: Set of already-used chart IDs for uniqueness
649
552
  parent_variables: Variables from parent scope for template resolution
650
- _resolve_jinja: If True, resolve Jinja templates in the YAML content
651
- (foreach creates static faces; all templates baked at compile time).
652
553
  If False, skip YAML content resolution so interactive variables
653
554
  remain as templates and are resolved at render time.
654
555
 
@@ -686,14 +587,6 @@ def _resolve_board_file_import(
686
587
  # Load and parse the YAML file
687
588
  try:
688
589
  yaml_content = nested_path.read_text()
689
- # Only resolve Jinja templates in the YAML content when inside a foreach.
690
- # Foreach creates static compile-time faces, so all templates (loop vars +
691
- # interactive defaults) must be baked in. For regular imports, leave
692
- # templates intact so they are resolved dynamically at render time.
693
- if _resolve_jinja:
694
- yaml_content = resolve_jinja_template(
695
- yaml_content, parent_variables, strict=False
696
- )
697
590
  face_data = yaml.load(yaml_content, Loader=UniqueKeyLoader)
698
591
  except yaml.YAMLError as e:
699
592
  raise CompilationError(
@@ -723,7 +616,6 @@ def _resolve_board_file_import(
723
616
  "sources": sources,
724
617
  "theme": theme,
725
618
  "variables": parent_variables, # Pass variables to child
726
- "_resolve_jinja": _resolve_jinja, # Propagate foreach context to children
727
619
  }
728
620
 
729
621
  # Compile the nested face
@@ -741,158 +633,6 @@ def _resolve_board_file_import(
741
633
  return LayoutItem(type="face", face=compiled_nested)
742
634
 
743
635
 
744
- def _resolve_foreach_item(
745
- foreach_config: ForeachConfig,
746
- charts: dict[str, Chart],
747
- query_registry: dict[str, AnyQuery],
748
- face_id: str,
749
- depth: int,
750
- item_id: str,
751
- base_dir: ProjectDirectory | None = None,
752
- default_source: str | None = None,
753
- face_cache: CachePatch = INHERIT_CACHE,
754
- chart_registry: dict[str, Any] | None = None,
755
- theme: str | None = None,
756
- used_chart_ids: set[str] | None = None,
757
- parent_variables: dict[str, Any] | None = None,
758
- parent_level: int = 0,
759
- *,
760
- sources: dict[str, Any],
761
- cache_root: CachePatch | None = None,
762
- local_chart_ids: set[str] | None = None,
763
- ) -> list[LayoutItem]:
764
- """Resolve a foreach construct that iterates over inline data.
765
-
766
- foreach allows dynamically generating layout items at compile time.
767
- Each row from the inline data becomes available as a loop variable,
768
- accessible via dot notation (e.g., {{ col.name }}).
769
-
770
- Example YAML:
771
- rows:
772
- - foreach:
773
- query:
774
- data:
775
- - name: "revenue"
776
- type: "numeric"
777
- - name: "date"
778
- type: "date"
779
- as: col
780
- items:
781
- - 'partials/{{ col.type }}.yml'
782
-
783
- Note: At compile time, foreach only works with inline data (query.data).
784
- Query references are not executed during compilation.
785
-
786
- Args:
787
- foreach_config: Validated ForeachConfig with query, as_, and items.
788
- charts: Available charts
789
- query_registry: Query registry
790
- face_id: Parent face ID
791
- depth: Nesting depth
792
- item_id: ID prefix for generated items
793
- base_dir: ProjectDirectory handle for resolving external file references
794
- default_source: Default source for inline queries
795
- face_cache: The declaring dashboard's `cache:` layer, inherited
796
- by every query normalized below it
797
- chart_registry: Raw chart definitions for nested face normalization
798
- theme: Vega-Lite theme to inherit
799
- used_chart_ids: Set of already-used chart IDs for uniqueness
800
- parent_variables: Variables from parent scope
801
-
802
- Returns:
803
- List of LayoutItems generated from the foreach iteration
804
- """
805
- query_config = foreach_config.query
806
- loop_var = foreach_config.as_
807
- item_templates = foreach_config.items
808
- parent_variables = parent_variables or {}
809
-
810
- # Extract inline data from query config
811
- # At compile time, foreach only works with inline data
812
- static_data = _get_foreach_data(query_config)
813
- if not static_data:
814
- return []
815
-
816
- # Generate layout items for each row
817
- resolved_items: list[LayoutItem] = []
818
- for idx, row in enumerate(static_data):
819
- loop_context = _build_loop_context(row, loop_var, parent_variables)
820
-
821
- for template_idx, template_item in enumerate(item_templates):
822
- layout_item = _resolve_single_item(
823
- template_item,
824
- charts,
825
- query_registry,
826
- face_id,
827
- depth,
828
- f"{item_id}_foreach{idx}_{template_idx}",
829
- base_dir,
830
- default_source,
831
- face_cache,
832
- chart_registry,
833
- theme,
834
- used_chart_ids,
835
- loop_context,
836
- _resolve_jinja=True,
837
- parent_level=parent_level,
838
- cache_root=cache_root,
839
- sources=sources,
840
- local_chart_ids=local_chart_ids,
841
- )
842
- resolved_items.append(layout_item)
843
-
844
- return resolved_items
845
-
846
-
847
- def _get_foreach_data(query_config: ForeachQuery) -> list[dict[str, Any]] | None:
848
- """Extract data for foreach iteration from a ForeachQuery.
849
-
850
- Args:
851
- query_config: Validated ForeachQuery with optional data, static_data,
852
- or palette.
853
-
854
- Returns:
855
- List of row dicts for iteration, or None if no data.
856
- """
857
- if query_config.palette is not None:
858
- stops = resolve_palette(query_config.palette)
859
- return [{"n": i, "hex": hex_value} for i, hex_value in enumerate(stops, 1)]
860
- data = query_config.data or query_config.static_data
861
- if isinstance(data, list):
862
- return data
863
- return None
864
-
865
-
866
- def _build_loop_context(
867
- row: Any,
868
- loop_var: str,
869
- parent_variables: dict[str, Any],
870
- ) -> dict[str, Any]:
871
- """Build the variable context for a foreach iteration.
872
-
873
- The loop variable is accessible via dot notation: {{ col.field }}
874
- This keeps variable scoping explicit and avoids shadowing parent variables.
875
-
876
- Args:
877
- row: Current row data (dict or scalar)
878
- loop_var: Name of the loop variable
879
- parent_variables: Variables inherited from parent scope
880
-
881
- Returns:
882
- Combined context dict for Jinja resolution
883
- """
884
- loop_context = dict(parent_variables)
885
-
886
- if isinstance(row, dict):
887
- # Row is a dict - accessible as {{ loop_var.field }}
888
- loop_context[loop_var] = row
889
- else:
890
- # Scalar value - wrap in dict with 'value' key
891
- loop_context[loop_var] = {"value": row}
892
-
893
- return loop_context
894
-
895
-
896
636
  def _resolve_grid_items(
897
637
  grid: GridLayout,
898
638
  charts: dict[str, Chart],
@@ -906,7 +646,6 @@ def _resolve_grid_items(
906
646
  theme: str | None = None,
907
647
  used_chart_ids: set[str] | None = None,
908
648
  parent_variables: dict[str, Any] | None = None,
909
- _resolve_jinja: bool = False,
910
649
  parent_level: int = 0,
911
650
  *,
912
651
  sources: dict[str, Any],
@@ -960,7 +699,6 @@ def _resolve_grid_items(
960
699
  theme,
961
700
  used_chart_ids,
962
701
  parent_variables,
963
- _resolve_jinja=_resolve_jinja,
964
702
  parent_level=parent_level,
965
703
  cache_root=cache_root,
966
704
  sources=sources,
@@ -1011,7 +749,6 @@ def _resolve_tab_items(
1011
749
  chart_registry: dict[str, Any] | None = None,
1012
750
  theme: str | None = None,
1013
751
  *,
1014
- _resolve_jinja: bool = False,
1015
752
  resolved_style: ResolvedStyle,
1016
753
  parent_level: int = 0,
1017
754
  sources: dict[str, Any],
@@ -1061,7 +798,6 @@ def _resolve_tab_items(
1061
798
  "face_cache": face_cache,
1062
799
  "sources": sources,
1063
800
  "theme": theme,
1064
- "_resolve_jinja": _resolve_jinja,
1065
801
  },
1066
802
  query_registry=query_registry,
1067
803
  chart_registry=chart_registry,