dataface 0.3.2.dev22__py3-none-any.whl → 0.3.2.dev111__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 (185) hide show
  1. dataface/__init__.py +70 -23
  2. dataface/agent_api/__init__.py +162 -68
  3. dataface/agent_api/_paths.py +9 -3
  4. dataface/agent_api/docs/yaml-reference.md +240 -106
  5. dataface/agent_api/init.py +1 -1
  6. dataface/agent_api/project_session.py +2 -1
  7. dataface/agent_api/serve.py +115 -0
  8. dataface/ai/agent.py +28 -2
  9. dataface/cli/_project.py +100 -1
  10. dataface/cli/commands/board_artifact.py +5 -2
  11. dataface/cli/commands/describe.py +7 -3
  12. dataface/cli/commands/docs.py +12 -3
  13. dataface/cli/commands/init.py +7 -14
  14. dataface/cli/commands/inspect.py +6 -1
  15. dataface/cli/commands/mcp_init.py +23 -43
  16. dataface/cli/commands/migrate.py +8 -3
  17. dataface/cli/commands/query.py +11 -8
  18. dataface/cli/commands/render.py +20 -9
  19. dataface/cli/commands/search.py +8 -2
  20. dataface/cli/commands/serve.py +26 -76
  21. dataface/cli/commands/skills_init.py +2 -19
  22. dataface/cli/commands/validate.py +7 -2
  23. dataface/cli/filesystem_project.py +5 -4
  24. dataface/cli/main.py +2 -19
  25. dataface/core/__init__.py +73 -28
  26. dataface/core/{compile/colors.py → colors.py} +20 -5
  27. dataface/core/compile/compiler.py +97 -34
  28. dataface/core/compile/data_table.py +7 -2
  29. dataface/core/compile/enrich.py +18 -44
  30. dataface/core/compile/face_warnings.py +46 -8
  31. dataface/core/compile/format.py +47 -1
  32. dataface/core/compile/inherit_graph.py +3 -3
  33. dataface/core/compile/inherit_registry.yaml +1 -1
  34. dataface/core/compile/migrations.py +112 -9
  35. dataface/core/compile/models/chart/normalized/kpi.py +0 -4
  36. dataface/core/compile/models/chart/resolved/_base.py +28 -10
  37. dataface/core/compile/models/chart/resolved/callout.py +2 -3
  38. dataface/core/compile/models/chart/resolved/kpi.py +7 -3
  39. dataface/core/compile/models/chart/resolved/table.py +16 -2
  40. dataface/core/compile/models/markers.py +1 -1
  41. dataface/core/compile/models/primitives.py +175 -2
  42. dataface/core/compile/models/source.py +5 -5
  43. dataface/core/compile/models/style/authored/__init__.py +32 -8
  44. dataface/core/compile/models/style/authored/_base.py +126 -69
  45. dataface/core/compile/models/style/authored/table.py +61 -3
  46. dataface/core/compile/models/style/resolved/__init__.py +12 -2
  47. dataface/core/compile/models/style/resolved/_base.py +142 -57
  48. dataface/core/compile/models/style/resolved/_cartesian.py +6 -11
  49. dataface/core/compile/models/style/resolved/_cascade.py +141 -54
  50. dataface/core/compile/models/style/resolved/area.py +1 -1
  51. dataface/core/compile/models/style/resolved/bar.py +1 -1
  52. dataface/core/compile/models/style/resolved/line.py +1 -1
  53. dataface/core/compile/models/style/resolved/scatter.py +3 -2
  54. dataface/core/compile/models/style/theme/__init__.py +36 -14
  55. dataface/core/compile/models/style/theme/_chart_base.py +17 -9
  56. dataface/core/compile/models/style/theme/axis.py +432 -312
  57. dataface/core/compile/models/style/theme/charts.py +10 -6
  58. dataface/core/compile/models/style/theme/kpi.py +2 -1
  59. dataface/core/compile/models/style/theme/page.py +5 -2
  60. dataface/core/compile/models/style/theme/table.py +2 -2
  61. dataface/core/compile/models/vega_lite/config.py +1 -1
  62. dataface/core/compile/normalize_charts.py +6 -2
  63. dataface/core/compile/normalize_queries.py +21 -7
  64. dataface/core/compile/resolve/__init__.py +2 -0
  65. dataface/core/compile/resolve/_axes.py +147 -44
  66. dataface/core/compile/resolve/_channels.py +1 -1
  67. dataface/core/compile/resolve/_dispatch.py +1 -1
  68. dataface/core/compile/resolve/_domain.py +49 -32
  69. dataface/core/compile/resolve/_kwargs.py +39 -12
  70. dataface/core/compile/resolve/_marks.py +8 -3
  71. dataface/core/compile/resolve/_palette.py +7 -3
  72. dataface/core/compile/resolve/_table.py +124 -33
  73. dataface/core/compile/resolve/area.py +48 -18
  74. dataface/core/compile/resolve/bar.py +245 -64
  75. dataface/core/compile/resolve/geo.py +10 -3
  76. dataface/core/compile/resolve/heatmap.py +16 -8
  77. dataface/core/compile/resolve/line.py +35 -12
  78. dataface/core/compile/resolve/pie.py +4 -1
  79. dataface/core/compile/resolve/pie_attachment.py +1 -1
  80. dataface/core/compile/resolve/scatter.py +25 -13
  81. dataface/core/compile/resolve/simple.py +25 -2
  82. dataface/core/compile/schema_renderers/prompt.py +1 -1
  83. dataface/core/compile/schema_renderers/textmate_grammar.py +8 -1
  84. dataface/core/compile/source_map.py +211 -28
  85. dataface/core/compile/style_cascade.py +126 -55
  86. dataface/core/compile/yaml_error_formatter.py +1 -1
  87. dataface/core/connections.py +10 -3
  88. dataface/core/dashboard.py +24 -18
  89. dataface/core/defaults/themes/_base.yaml +49 -39
  90. dataface/core/defaults/themes/cream.yaml +4 -4
  91. dataface/core/defaults/themes/diagnostics-title-baseline.yaml +1 -1
  92. dataface/core/defaults/themes/diagnostics-title-color-extreme.yaml +1 -1
  93. dataface/core/defaults/themes/diagnostics-title-font-extreme.yaml +1 -1
  94. dataface/core/defaults/themes/diagnostics-title-size-extreme.yaml +1 -1
  95. dataface/core/defaults/themes/diagnostics-title-weight-extreme.yaml +1 -1
  96. dataface/core/defaults/themes/editorial.yaml +6 -6
  97. dataface/core/defaults/themes/neon.yaml +15 -8
  98. dataface/core/defaults/themes/plain.yaml +6 -4
  99. dataface/core/defaults/themes/stark.yaml +16 -7
  100. dataface/core/defaults/themes/vivid.yaml +15 -8
  101. dataface/core/diagnostics/__init__.py +2 -2
  102. dataface/core/diagnostics/codes_compile.py +13 -31
  103. dataface/core/diagnostics/codes_execute.py +79 -0
  104. dataface/core/diagnostics/codes_render.py +87 -28
  105. dataface/core/diagnostics/diagnostic.py +30 -0
  106. dataface/core/diagnostics/hints.py +19 -0
  107. dataface/core/dialects/base.py +17 -0
  108. dataface/core/dialects/postgres.py +14 -0
  109. dataface/core/dialects/redshift.py +5 -0
  110. dataface/core/execute/__init__.py +59 -14
  111. dataface/core/execute/adapters/dbt_adapter.py +4 -1
  112. dataface/core/execute/adapters/dbt_adapter_factory.py +19 -0
  113. dataface/core/execute/adapters/dbt_utils.py +84 -20
  114. dataface/core/execute/adapters/postgres_connection_manager.py +48 -0
  115. dataface/core/execute/adapters/sql_adapter.py +72 -4
  116. dataface/core/execute/dbt_jinja.py +65 -10
  117. dataface/core/execute/executor.py +69 -16
  118. dataface/core/font_measure.py +28 -0
  119. dataface/core/fonts.py +8 -5
  120. dataface/core/inspect/__init__.py +46 -6
  121. dataface/core/project.py +13 -1
  122. dataface/core/render/chart/data_table_attachment.py +14 -12
  123. dataface/core/render/chart/emitters/_cartesian.py +21 -18
  124. dataface/core/render/chart/emitters/_channels.py +56 -8
  125. dataface/core/render/chart/emitters/_label_overlap.py +18 -15
  126. dataface/core/render/chart/emitters/_measured_label_padding.py +4 -4
  127. dataface/core/render/chart/emitters/area.py +3 -3
  128. dataface/core/render/chart/emitters/bar.py +60 -35
  129. dataface/core/render/chart/emitters/geo.py +8 -13
  130. dataface/core/render/chart/emitters/heatmap.py +20 -26
  131. dataface/core/render/chart/emitters/line.py +2 -2
  132. dataface/core/render/chart/emitters/scatter.py +7 -30
  133. dataface/core/render/chart/features/baseline.py +22 -9
  134. dataface/core/render/chart/features/endpoint_labels.py +55 -32
  135. dataface/core/render/chart/features/mirror_axis.py +12 -9
  136. dataface/core/render/chart/features/value_labels.py +1 -1
  137. dataface/core/render/chart/kpi.py +11 -90
  138. dataface/core/render/chart/presentation.py +6 -8
  139. dataface/core/render/chart/rendering.py +3 -12
  140. dataface/core/render/chart/session.py +10 -0
  141. dataface/core/render/chart/spec.py +3 -0
  142. dataface/core/render/chart/spec_builders.py +18 -18
  143. dataface/core/render/chart/table.py +81 -36
  144. dataface/core/render/chart/table_support.py +2 -53
  145. dataface/core/render/chart/time_unit_detect.py +3 -2
  146. dataface/core/render/chart/translate.py +82 -44
  147. dataface/core/render/chart/type_inference.py +45 -25
  148. dataface/core/render/chart/validation.py +91 -0
  149. dataface/core/render/chart/vega_lite.py +30 -11
  150. dataface/core/render/chart/vl_field_maps.py +111 -76
  151. dataface/core/render/converters/chart.py +16 -6
  152. dataface/core/render/faces.py +3 -2
  153. dataface/core/render/fonts/DFTSerifOldstyleProportional-Regular.woff2 +0 -0
  154. dataface/core/render/fonts/DFTSerifOldstyleTabular-Regular.woff2 +0 -0
  155. dataface/core/render/fonts/SOURCE_CODE_PRO_LICENSE.txt +93 -93
  156. dataface/core/render/fonts/_font_face.css +10 -0
  157. dataface/core/render/layout_sizing.py +267 -238
  158. dataface/core/render/numeral_expr.py +80 -0
  159. dataface/core/render/warnings/bar_band_width_too_narrow.py +64 -6
  160. dataface/core/render/warnings/layered_chart_shared_y_axis_scale_mismatch.py +25 -3
  161. dataface/core/render/warnings/likely_currency_or_percent_missing_formatter.py +12 -6
  162. dataface/core/render/warnings/palette_unsupported.py +6 -0
  163. dataface/core/render/warnings/pie_dominant_segment.py +1 -0
  164. dataface/core/render/warnings/pie_too_many_segments.py +1 -0
  165. dataface/core/render/warnings/point_map_out_of_projection.py +1 -0
  166. dataface/core/render/warnings/query_returned_zero_rows.py +1 -0
  167. dataface/core/render/warnings/redundant_encoding.py +20 -1
  168. dataface/core/render/warnings/registry.py +7 -3
  169. dataface/core/render/warnings/temporal_single_point.py +1 -0
  170. dataface/core/render/warnings/too_many_color_categories.py +1 -0
  171. dataface/core/render/warnings/too_many_x_categories.py +18 -2
  172. dataface/core/render/warnings/value_labels_crowd_width.py +9 -0
  173. dataface/core/render/warnings/y_encoding_mostly_null.py +1 -0
  174. dataface/core/render_format.py +18 -0
  175. dataface/core/text/format_d3.py +56 -0
  176. dataface/core/text/numeral_scale.py +218 -0
  177. dataface/core/utils.py +103 -10
  178. dataface/data/schemas/board-resolved/board-resolved.schema.json +9487 -7978
  179. dataface/data/schemas/yaml/0.3.2.json +1 -0
  180. dataface/data/schemas/yaml/manifest.json +7 -0
  181. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/METADATA +1 -1
  182. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/RECORD +185 -177
  183. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/WHEEL +0 -0
  184. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/entry_points.txt +0 -0
  185. {dataface-0.3.2.dev22.dist-info → dataface-0.3.2.dev111.dist-info}/licenses/LICENSE +0 -0
@@ -4,12 +4,13 @@ Includes the top-level ``StylePatch`` (the all-encompassing authored overlay)
4
4
  and the cross-cutting axis/scale/title/padding/tooltip/legend/mark patches
5
5
  that chart-family patch files import.
6
6
 
7
- Some patches here (``AxisStylePatch``, ``DataTableStylePatch``,
8
- ``PaddingStylePatch``, ``TooltipStylePatch``, ``TableChartStylePatch``) are
9
- built early and injected back into ``style.theme``'s module globals so that
10
- Pydantic can resolve forward references in theme chart-style classes — see
11
- the injection block below. ``theme.py`` must not import from this package,
12
- so injection is the only DAG-safe path.
7
+ Some patches here (``BaseAxisStylePatch``, ``AxisXStylePatch``,
8
+ ``AxisYStylePatch``, ``DataTableStylePatch``, ``PaddingStylePatch``,
9
+ ``TooltipStylePatch``, ``TableChartStylePatch``) are built early and injected
10
+ back into ``style.theme``'s module globals so that Pydantic can resolve forward
11
+ references in theme chart-style classes — see the injection block below.
12
+ ``theme.py`` must not import from this package, so injection is the only
13
+ DAG-safe path.
13
14
  """
14
15
 
15
16
  from __future__ import annotations
@@ -34,16 +35,23 @@ from dataface.core.compile.models.style.theme import (
34
35
  AreaChartStyle,
35
36
  AreaLayerStyle,
36
37
  AreaMarkStyle,
37
- AxisDomainStyle,
38
- AxisElementStyle,
39
- AxisGridStyle,
40
- AxisStyle,
38
+ AxisLabelStyle,
39
+ AxisLineStyle,
41
40
  AxisTicksStyle,
41
+ AxisTitleStyle,
42
+ AxisXStyle,
43
+ AxisYStyle,
44
+ BandAxisStyle,
42
45
  BarChartStyle,
43
46
  BarLayerStyle,
44
47
  BarMarkStyle,
48
+ BaseAxisGridStyle,
49
+ BaseAxisStyle,
50
+ BaseScaleStyle,
45
51
  ChartsStyle,
46
52
  DataTableStyle as DataTableStyle,
53
+ DimensionLabelStyle,
54
+ DimensionTicksStyle,
47
55
  GeoshapeChartStyle,
48
56
  GlobalMarksStyle,
49
57
  HeatmapChartStyle,
@@ -53,11 +61,13 @@ from dataface.core.compile.models.style.theme import (
53
61
  LineChartStyle,
54
62
  LineLayerStyle,
55
63
  LineMarkStyle,
64
+ MeasureGridStyle,
56
65
  PaddingStyle,
57
66
  PieChartStyle,
58
67
  PointMapChartStyle,
59
68
  PointMarkStyle,
60
- ScaleStyle,
69
+ QuantitativeAxisStyle,
70
+ ScaleContinuousStyle,
61
71
  ScatterChartStyle,
62
72
  ScatterLayerStyle,
63
73
  Style,
@@ -65,73 +75,104 @@ from dataface.core.compile.models.style.theme import (
65
75
  TitleStyle,
66
76
  TooltipStyle,
67
77
  TotalStyle,
78
+ XScaleStyle,
68
79
  _CartesianChartStyle,
69
80
  _GeoChartStyle,
70
81
  _RadialChartStyle,
71
82
  coerce_gap,
72
83
  )
73
84
 
74
- # Patch type for scale style — generated (and registered) BEFORE AxisStylePatch
75
- # so the recursive AxisStyle build reuses this exact class for its ``scale``
76
- # field (identity matters: tests and AxisStylePatch.scale must agree).
77
- # The custom base carries ScaleDomainValidationMixin so chart-local authored
78
- # patches enforce the same domain shape/type-consistency rules as theme-stage
79
- # ScaleStyle — build_patch_model_ext only carries over validators that live
80
- # on the patch model's base class.
85
+ # ── Scale group patches ───────────────────────────────────────────────────────
86
+ # Generated first (continuous is embedded in BaseScaleStyle, which is
87
+ # embedded in BaseAxisStyle). Order: leaf group → BaseScaleStyle → XScaleStyle.
88
+
89
+ # ScaleContinuousStylePatch carries ScaleDomainValidationMixin (domain shape
90
+ # check) via build_patch_model_ext, but NOT _validate_log_pow_symlog_params —
91
+ # that validator is defined directly on ScaleContinuousStyle (not on a mixin),
92
+ # so build_patch_model_ext naturally excludes it from the patch. A theme layer
93
+ # can set type:log and a chart-local patch can set only log.base; the
94
+ # cross-field check fires once, on the fully-merged result via
95
+ # ScaleContinuousStyle.model_validate.
81
96
  if TYPE_CHECKING:
82
97
 
83
- class ScaleStylePatch(ScaleStyle):
98
+ class ScaleContinuousStylePatch(ScaleContinuousStyle):
84
99
  pass
85
100
 
86
101
  else:
87
102
  from dataface.core.compile.models.style.theme import ScaleDomainValidationMixin
88
103
 
89
- class _ScaleStylePatchBase(_PatchBase, ScaleDomainValidationMixin):
90
- """Base for ScaleStylePatch: extra="forbid" from _PatchBase plus the
91
- authored scale.domain validation from ScaleDomainValidationMixin."""
104
+ class _ScaleContinuousStylePatchBase(_PatchBase, ScaleDomainValidationMixin):
105
+ """Base for ScaleContinuousStylePatch: extra="forbid" from _PatchBase
106
+ plus the authored scale.domain validation from ScaleDomainValidationMixin."""
92
107
 
93
- ScaleStylePatch = register_patch(ScaleStyle)(
94
- build_patch_model_ext(ScaleStyle, base_cls=_ScaleStylePatchBase)
108
+ ScaleContinuousStylePatch = register_patch(ScaleContinuousStyle)(
109
+ build_patch_model_ext(
110
+ ScaleContinuousStyle, base_cls=_ScaleContinuousStylePatchBase
111
+ )
95
112
  )
96
113
 
114
+ BaseScaleStylePatch = build_patch_model(BaseScaleStyle)
115
+ XScaleStylePatch = build_patch_model(XScaleStyle)
116
+
117
+ # ── Axis ticks patches ─────────────────────────────────────────────────────────
118
+ # Generated (and registered) BEFORE axis variant patches so the recursive
119
+ # axis-variant build reuses these exact classes for their ``ticks`` field.
120
+ # Neither patch carries the step/time_unit cadence validator — same
121
+ # ScaleContinuousStyle convention above: the check is defined directly on
122
+ # DimensionTicksStyle (mode="after"), not a mixin, and build_patch_model_ext
123
+ # only carries over validators living on the patch's own base_cls, so it does
124
+ # NOT leak into either generated patch. It fires once, on the fully-merged
125
+ # theme-tier result, since a theme may set time_unit and a chart-local patch
126
+ # layer may set only step.
127
+ AxisTicksStylePatch = build_patch_model(AxisTicksStyle)
128
+ DimensionTicksStylePatch = build_patch_model(DimensionTicksStyle)
129
+
130
+
131
+ # ── Axis leaf patches ─────────────────────────────────────────────────────────
132
+ # Generated before axis variant patches so the recursive variant builds reuse
133
+ # these exact classes for their sub-object fields.
134
+ AxisGridZeroStylePatch: type # declared for forward reference clarity
135
+ from dataface.core.compile.models.style.theme.axis import (
136
+ AxisGridZeroStyle, # noqa: PLC0415, E402
137
+ )
97
138
 
98
- # Patch type for axis ticks style — generated (and registered) BEFORE
99
- # AxisStylePatch so the recursive AxisStyle build reuses this exact class for
100
- # its ``ticks`` field. The custom base carries
101
- # AxisTicksCadenceValidationMixin so chart-local authored patches enforce the
102
- # same step/interval/count cadence rules as theme-stage AxisTicksStyle —
103
- # build_patch_model_ext only carries over validators that live on the patch
104
- # model's base class.
105
- if TYPE_CHECKING:
139
+ AxisGridZeroStylePatch = build_patch_model(AxisGridZeroStyle)
140
+ BaseAxisGridStylePatch = build_patch_model(BaseAxisGridStyle)
141
+ MeasureGridStylePatch = build_patch_model(MeasureGridStyle)
142
+ AxisLineStylePatch = build_patch_model(AxisLineStyle)
143
+ AxisTitleStylePatch = build_patch_model(AxisTitleStyle)
144
+ AxisLabelStylePatch = build_patch_model(AxisLabelStyle)
145
+ DimensionLabelStylePatch = build_patch_model(DimensionLabelStyle)
106
146
 
107
- class AxisTicksStylePatch(AxisTicksStyle):
108
- pass
109
147
 
110
- else:
111
- from dataface.core.compile.models.style.theme import (
112
- AxisTicksCadenceValidationMixin,
113
- )
148
+ # ── Axis variant patches ──────────────────────────────────────────────────────
149
+ # Five axis-variant patches, each typed to the matching authored slot so extra
150
+ # fields on the wrong variant raise at validation time.
151
+ # TYPE_CHECKING stubs give mypy proper class definitions — at runtime
152
+ # build_patch_model generates the actual Pydantic model.
153
+ if TYPE_CHECKING:
114
154
 
115
- class _AxisTicksStylePatchBase(_PatchBase, AxisTicksCadenceValidationMixin):
116
- """Base for AxisTicksStylePatch: extra="forbid" from _PatchBase plus
117
- the step/interval/count cadence validation from
118
- AxisTicksCadenceValidationMixin."""
155
+ class BaseAxisStylePatch(BaseAxisStyle):
156
+ pass
119
157
 
120
- AxisTicksStylePatch = register_patch(AxisTicksStyle)(
121
- build_patch_model_ext(AxisTicksStyle, base_cls=_AxisTicksStylePatchBase)
122
- )
158
+ class AxisXStylePatch(AxisXStyle):
159
+ pass
123
160
 
161
+ class AxisYStylePatch(AxisYStyle):
162
+ pass
124
163
 
125
- # Patch type for axis style — generated before theme chart-type classes
126
- # so _CartesianChartStyle can reference it as a forward-ref field.
127
- if TYPE_CHECKING:
164
+ class QuantitativeAxisStylePatch(QuantitativeAxisStyle):
165
+ pass
128
166
 
129
- class AxisStylePatch(AxisStyle):
167
+ class BandAxisStylePatch(BandAxisStyle):
130
168
  pass
131
169
 
132
170
  else:
133
- AxisStylePatch = build_patch_model(AxisStyle)
134
-
171
+ BaseAxisStylePatch = build_patch_model(BaseAxisStyle)
172
+ AxisXStylePatch = build_patch_model(AxisXStyle)
173
+ AxisYStylePatch = build_patch_model(AxisYStyle)
174
+ QuantitativeAxisStylePatch = build_patch_model(QuantitativeAxisStyle)
175
+ BandAxisStylePatch = build_patch_model(BandAxisStyle)
135
176
 
136
177
  if TYPE_CHECKING:
137
178
 
@@ -200,19 +241,29 @@ class EndpointLabelsConfig(BaseModel):
200
241
  # can resolve forward references in _CartesianChartStyle, BarChartStyle, etc.
201
242
  # when build_patch_model(Style) triggers schema building for those classes.
202
243
  # theme.py must not import from authored/, so injection is the only DAG-safe path.
244
+ # setattr avoids type: ignore[attr-defined] on a dynamically-typed module object.
203
245
  import sys as _sys
204
246
 
205
247
  _theme_mod = _sys.modules["dataface.core.compile.models.style.theme"]
206
- _theme_mod.AxisStylePatch = AxisStylePatch # type: ignore[attr-defined]
207
- _theme_mod.DataTableStylePatch = DataTableStylePatch # type: ignore[attr-defined]
208
- _theme_mod.PaddingStylePatch = PaddingStylePatch # type: ignore[attr-defined]
209
- _theme_mod.TableChartStylePatch = TableChartStylePatch # type: ignore[attr-defined]
210
- _theme_mod.TooltipStylePatch = TooltipStylePatch # type: ignore[attr-defined]
211
- _theme_mod.EndpointLabelsConfig = EndpointLabelsConfig # type: ignore[attr-defined]
212
- _theme_mod.PaginationConfig = PaginationConfig # type: ignore[attr-defined]
213
- _theme_mod.TableColumnDefaultsConfig = TableColumnDefaultsConfig # type: ignore[attr-defined]
214
- _theme_mod.TableColumnConfig = TableColumnConfig # type: ignore[attr-defined]
215
- del _theme_mod
248
+ for _name, _obj in (
249
+ ("BaseAxisStylePatch", BaseAxisStylePatch),
250
+ ("AxisXStylePatch", AxisXStylePatch),
251
+ ("AxisYStylePatch", AxisYStylePatch),
252
+ ("QuantitativeAxisStylePatch", QuantitativeAxisStylePatch),
253
+ ("BandAxisStylePatch", BandAxisStylePatch),
254
+ ("BaseScaleStylePatch", BaseScaleStylePatch),
255
+ ("XScaleStylePatch", XScaleStylePatch),
256
+ ("DataTableStylePatch", DataTableStylePatch),
257
+ ("PaddingStylePatch", PaddingStylePatch),
258
+ ("TableChartStylePatch", TableChartStylePatch),
259
+ ("TooltipStylePatch", TooltipStylePatch),
260
+ ("EndpointLabelsConfig", EndpointLabelsConfig),
261
+ ("PaginationConfig", PaginationConfig),
262
+ ("TableColumnDefaultsConfig", TableColumnDefaultsConfig),
263
+ ("TableColumnConfig", TableColumnConfig),
264
+ ):
265
+ setattr(_theme_mod, _name, _obj)
266
+ del _theme_mod, _name, _obj
216
267
 
217
268
  # Rebuild theme chart classes that forward-ref authored types.
218
269
  # Must run BEFORE build_patch_model(BarChartStyle) etc. so that model_fields
@@ -220,7 +271,13 @@ del _theme_mod
220
271
  # ForwardRefs. The patch factory uses field annotations to determine recursive
221
272
  # types; a ForwardRef for EndpointLabelsConfig produces the wrong patch.
222
273
  _authored_ns: dict[str, object] = {
223
- "AxisStylePatch": AxisStylePatch,
274
+ "BaseAxisStylePatch": BaseAxisStylePatch,
275
+ "AxisXStylePatch": AxisXStylePatch,
276
+ "AxisYStylePatch": AxisYStylePatch,
277
+ "QuantitativeAxisStylePatch": QuantitativeAxisStylePatch,
278
+ "BandAxisStylePatch": BandAxisStylePatch,
279
+ "BaseScaleStylePatch": BaseScaleStylePatch,
280
+ "XScaleStylePatch": XScaleStylePatch,
224
281
  "DataTableStylePatch": DataTableStylePatch,
225
282
  "PaddingStylePatch": PaddingStylePatch,
226
283
  "TableChartStylePatch": TableChartStylePatch,
@@ -270,11 +327,11 @@ class StylePatch(_StylePatchBase): # type: ignore[misc,valid-type]
270
327
  return coerce_gap(v)
271
328
 
272
329
 
273
- AxisGridStylePatch = build_patch_model(AxisGridStyle)
274
- AxisDomainStylePatch = build_patch_model(AxisDomainStyle)
330
+ # AxisGridStylePatch / AxisDomainStylePatch / AxisElementStylePatch are built
331
+ # earlier (before the axis variant patches); they are not rebuilt here.
275
332
  # AxisTicksStylePatch is built explicitly above (with the cadence validator
276
333
  # base_cls) — not rebuilt here.
277
- AxisElementStylePatch = build_patch_model(AxisElementStyle)
334
+ AxisGridStylePatch = BaseAxisGridStylePatch # backwards-compat alias
278
335
  if TYPE_CHECKING:
279
336
 
280
337
  class PointMarkStylePatch(PointMarkStyle):
@@ -286,9 +343,9 @@ else:
286
343
  TotalStylePatch = build_patch_model(TotalStyle)
287
344
  GlobalMarksStylePatch = build_patch_model(GlobalMarksStyle)
288
345
 
289
- # TYPE_CHECKING stub mirrors the AxisStylePatch pattern — gives mypy a proper
290
- # class (with accurate field names from LegendStyle) while the runtime uses
291
- # the dynamically created patch model from build_patch_model().
346
+ # TYPE_CHECKING stub mirrors the variant-axis-patch pattern — gives mypy a
347
+ # proper class (with accurate field names from LegendStyle) while the runtime
348
+ # uses the dynamically created patch model from build_patch_model().
292
349
  if TYPE_CHECKING:
293
350
 
294
351
  class LegendStylePatch(LegendStyle):
@@ -322,7 +379,7 @@ else:
322
379
  # Per-family layer style patches — generated from thin required wrappers in style/theme/.
323
380
  # build_patch_model recurses: LineLayerStyle{marks: LineChartMarksStyle} →
324
381
  # LineLayerStylePatch{marks: LineChartMarksStylePatch | None}.
325
- # TYPE_CHECKING stubs mirror the AxisStylePatch / LegendStylePatch pattern so
382
+ # TYPE_CHECKING stubs mirror the axis-variant-patch / LegendStylePatch pattern so
326
383
  # mypy sees a valid class while the runtime uses the dynamically-created model.
327
384
  if TYPE_CHECKING:
328
385
 
@@ -322,9 +322,16 @@ class TableColumnConfig(BaseModel):
322
322
 
323
323
  @field_validator("spark", mode="before")
324
324
  @classmethod
325
- def _reject_renamed_away_spark_shorthand(cls, value: Any) -> Any:
326
- if isinstance(value, str) and value in _SPARK_RENAMED_AWAY:
327
- raise ValueError(_SPARK_RENAMED_AWAY[value])
325
+ def _normalize_spark_shorthand(cls, value: Any) -> Any:
326
+ """Normalize ``spark: <type>`` string shorthand to ``{"type": <type>}``.
327
+
328
+ Runs before union validation so ``.spark`` is always ``SparkConfig | None``
329
+ after construction — resolve and render never branch on the raw string form.
330
+ """
331
+ if isinstance(value, str):
332
+ if value in _SPARK_RENAMED_AWAY:
333
+ raise ValueError(_SPARK_RENAMED_AWAY[value])
334
+ return {"type": value}
328
335
  return value
329
336
 
330
337
  @model_validator(mode="after")
@@ -351,6 +358,57 @@ class TableColumnConfig(BaseModel):
351
358
  )
352
359
 
353
360
 
361
+ _SIZING_FIELDS = frozenset({"width", "max_width"})
362
+
363
+
364
+ def fill_table_column_defaults(
365
+ source: TableColumnConfig | None,
366
+ defaults: TableColumnDefaultsConfig | None,
367
+ *,
368
+ fallback_label: str | None = None,
369
+ link_override: str | None = None,
370
+ ) -> TableColumnConfig:
371
+ """Build one final ``TableColumnConfig`` from an optional explicit source
372
+ and table-level ``column_defaults``, constructed once (never
373
+ ``model_copy(update=...)``).
374
+
375
+ Precedence per field: ``source``'s own value > ``defaults`` (fills only
376
+ ``None`` fields) > ``fallback_label`` for ``label`` / ``link_override``
377
+ for ``link``, each used only when neither of the above set one.
378
+ ``width``/``max_width`` are one sizing slot — a source that claims either
379
+ is never filled from the other by ``defaults``.
380
+
381
+ The single constructor this repo uses both for a resolved chart's own
382
+ columns (``compile/resolve/_table.py``) and for a table renderer's two
383
+ render-native column key spaces — pivot leaf columns and transpose's
384
+ ``__metric__``/``__value__`` pair — whose key space doesn't exist until
385
+ a render-time data transform runs, so ``column_defaults`` reaches them
386
+ here instead of at resolve.
387
+ """
388
+ sizing_claimed = source is not None and (
389
+ source.width is not None or source.max_width is not None
390
+ )
391
+ defaults_fields = (
392
+ frozenset(type(defaults).model_fields) if defaults is not None else frozenset()
393
+ )
394
+ fields: dict[str, Any] = {}
395
+ for name in TableColumnConfig.model_fields:
396
+ value = getattr(source, name) if source is not None else None
397
+ if (
398
+ defaults is not None
399
+ and value is None
400
+ and name in defaults_fields
401
+ and not (name in _SIZING_FIELDS and sizing_claimed)
402
+ ):
403
+ value = getattr(defaults, name)
404
+ fields[name] = value
405
+ if fields["label"] is None and fallback_label is not None:
406
+ fields["label"] = fallback_label
407
+ if fields["link"] is None and link_override is not None:
408
+ fields["link"] = link_override
409
+ return TableColumnConfig(**fields)
410
+
411
+
354
412
  if TYPE_CHECKING:
355
413
 
356
414
  class SparkStylePatch(SparkStyle):
@@ -35,16 +35,21 @@ logic, not a model definition — it lives at
35
35
  """
36
36
 
37
37
  from dataface.core.compile.models.style.resolved._base import (
38
- ResolvedAxisDomainStyle,
39
38
  ResolvedAxisElementStyle,
40
39
  ResolvedAxisGridStyle,
41
40
  ResolvedAxisGridZeroStyle,
42
41
  ResolvedAxisLabelOverlapConfig,
42
+ ResolvedAxisLineStyle,
43
43
  ResolvedAxisStyle,
44
44
  ResolvedAxisTicksStyle,
45
45
  ResolvedChartsStyle,
46
46
  ResolvedLegendElementStyle,
47
47
  ResolvedLegendStyle,
48
+ ResolvedScaleContinuousStyle,
49
+ ResolvedScaleLogStyle,
50
+ ResolvedScalePowStyle,
51
+ ResolvedScaleStyle,
52
+ ResolvedScaleSymlogStyle,
48
53
  ResolvedStyle,
49
54
  _VLConfig,
50
55
  effective_padding,
@@ -95,11 +100,11 @@ from dataface.core.compile.models.style.resolved.table import ResolvedTableStyle
95
100
 
96
101
  __all__ = [
97
102
  # _base
98
- "ResolvedAxisDomainStyle",
99
103
  "ResolvedAxisElementStyle",
100
104
  "ResolvedAxisGridStyle",
101
105
  "ResolvedAxisGridZeroStyle",
102
106
  "ResolvedAxisLabelOverlapConfig",
107
+ "ResolvedAxisLineStyle",
103
108
  "ResolvedAxisStyle",
104
109
  "ResolvedAxisTicksStyle",
105
110
  "ResolvedCalloutElementStyle",
@@ -107,6 +112,11 @@ __all__ = [
107
112
  "ResolvedChartsStyle",
108
113
  "ResolvedLegendElementStyle",
109
114
  "ResolvedLegendStyle",
115
+ "ResolvedScaleContinuousStyle",
116
+ "ResolvedScaleLogStyle",
117
+ "ResolvedScalePowStyle",
118
+ "ResolvedScaleStyle",
119
+ "ResolvedScaleSymlogStyle",
110
120
  "ResolvedStyle",
111
121
  "_VLConfig",
112
122
  # _base (public function)