From 54e7e48891585562bd936b98a1936bcd50ed024a Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 14:42:51 +0100
Subject: [PATCH 01/44] chore: Add notes for change targets
Need a way to make sense of these and start GH threads:
- 10 total files
- 3 modules named `theme.py`
- 2 modules named `__init__.py`
---
altair/typing/__init__.py | 1 +
altair/typing/theme.py | 1 +
altair/utils/theme.py | 2 ++
altair/vegalite/v5/__init__.py | 2 ++
altair/vegalite/v5/api.py | 4 ++++
altair/vegalite/v5/theme.py | 2 ++
doc/user_guide/customization.rst | 6 ++++++
tests/vegalite/v5/test_api.py | 1 +
tests/vegalite/v5/test_theme.py | 3 +++
tools/generate_api_docs.py | 5 +++++
10 files changed, 27 insertions(+)
diff --git a/altair/typing/__init__.py b/altair/typing/__init__.py
index d80469f35..9ed3afdc2 100644
--- a/altair/typing/__init__.py
+++ b/altair/typing/__init__.py
@@ -51,6 +51,7 @@
"theme",
]
+# TODO: Remove `theme`, `ThemeConfig` imports
from altair.typing import theme
from altair.typing.theme import ThemeConfig
from altair.utils.schemapi import Optional
diff --git a/altair/typing/theme.py b/altair/typing/theme.py
index 17f9a7fd2..c15049fe0 100644
--- a/altair/typing/theme.py
+++ b/altair/typing/theme.py
@@ -1 +1,2 @@
+# TODO: Remove module
from altair.vegalite.v5.schema._config import * # noqa: F403
diff --git a/altair/utils/theme.py b/altair/utils/theme.py
index bbb7247bc..522c40164 100644
--- a/altair/utils/theme.py
+++ b/altair/utils/theme.py
@@ -19,6 +19,8 @@
ThemeType = Plugin[ThemeConfig]
+# NOTE: Parameterising type vars, overriding `enable`
+
# HACK: See for `LiteralString` requirement in `name`
# https://github.com/vega/altair/pull/3526#discussion_r1743350127
diff --git a/altair/vegalite/v5/__init__.py b/altair/vegalite/v5/__init__.py
index a18be6e11..72ff2eec1 100644
--- a/altair/vegalite/v5/__init__.py
+++ b/altair/vegalite/v5/__init__.py
@@ -21,4 +21,6 @@
renderers,
)
from .schema import *
+
+# NOTE: `theme` contents -> `vegalite.v5`
from .theme import register_theme, themes
diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py
index fce8c080d..852252c65 100644
--- a/altair/vegalite/v5/api.py
+++ b/altair/vegalite/v5/api.py
@@ -31,6 +31,8 @@
from .display import VEGA_VERSION, VEGAEMBED_VERSION, VEGALITE_VERSION, renderers
from .schema import SCHEMA_URL, channels, core, mixins
from .schema._typing import Map
+
+# NOTE: Relative themes import
from .theme import themes
if sys.version_info >= (3, 14):
@@ -1901,6 +1903,8 @@ def to_dict( # noqa: C901
vegalite_spec["$schema"] = SCHEMA_URL
# apply theme from theme registry
+
+ # NOTE: Single use of `themes`
if theme := themes.get():
vegalite_spec = utils.update_nested(theme(), vegalite_spec, copy=True)
else:
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index ba5a5ab9a..2bfd236eb 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -53,6 +53,8 @@ def __repr__(self) -> str:
# themes that will be auto-detected. Explicit registration is also
# allowed by the PluginRegistry API.
ENTRY_POINT_GROUP: Final = "altair.vegalite.v5.theme"
+
+# NOTE: `themes` def has an entry point group
themes = ThemeRegistry(entry_point_group=ENTRY_POINT_GROUP)
themes.register(
diff --git a/doc/user_guide/customization.rst b/doc/user_guide/customization.rst
index c068d62e3..1c8d6e96c 100644
--- a/doc/user_guide/customization.rst
+++ b/doc/user_guide/customization.rst
@@ -710,6 +710,9 @@ outside the chart itself; For example, the container may be a ``
`` element
Chart Themes
------------
+..
+ _comment: First mention of alt.themes
+
Altair makes available a theme registry that lets users apply chart configurations
globally within any Python session. This is done via the ``alt.themes`` object.
@@ -838,6 +841,9 @@ fill unless otherwise specified:
If you want to restore the default theme, use:
+..
+ _comment: Last mention of alt.themes
+
.. altair-plot::
:output: none
diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py
index a7d2f1c69..e73b8797e 100644
--- a/tests/vegalite/v5/test_api.py
+++ b/tests/vegalite/v5/test_api.py
@@ -1286,6 +1286,7 @@ def test_LookupData():
def test_themes():
chart = alt.Chart("foo.txt").mark_point()
+ # NOTE: Only other tests using `alt.themes`
with alt.themes.enable("default"):
assert chart.to_dict()["config"] == {
"view": {"continuousWidth": 300, "continuousHeight": 300}
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 97d2fb42e..6cf2bb221 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -5,6 +5,8 @@
import pytest
import altair.vegalite.v5 as alt
+
+# NOTE: Imports assuming existing structure
from altair.typing import ThemeConfig
from altair.vegalite.v5.schema._config import ConfigKwds
from altair.vegalite.v5.schema._typing import is_color_hex
@@ -26,6 +28,7 @@ def chart() -> alt.Chart:
def test_vega_themes(chart) -> None:
for theme in VEGA_THEMES:
+ # NOTE: Assuming this is available in `alt.___`
with alt.themes.enable(theme):
dct = chart.to_dict()
assert dct["usermeta"] == {"embedOptions": {"theme": theme}}
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index dc93aba24..27ab5541e 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -134,6 +134,11 @@ def type_hints() -> list[str]:
return sorted(s for s in iter_objects(alt.typing) if s in alt.typing.__all__)
+# TODO: Currently only the `TypedDict`(s) are visible (only via `alt.typing.___`)
+# Related: https://github.com/vega/altair/issues/3607
+def theme() -> list[str]: ...
+
+
def lowlevel_wrappers() -> list[str]:
objects = sorted(iter_objects(alt.schema.core, restrict_to_subclass=alt.SchemaBase)) # type: ignore[attr-defined]
# The names of these two classes are also used for classes in alt.channels. Due to
From 66c51c308ca8de74850e3844e22216fc475e4034 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 15:15:30 +0100
Subject: [PATCH 02/44] chore: Add TODO in `update_init_file.py`
---
tools/update_init_file.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/tools/update_init_file.py b/tools/update_init_file.py
index c1831093a..104dc9d51 100644
--- a/tools/update_init_file.py
+++ b/tools/update_init_file.py
@@ -126,6 +126,8 @@ def _is_relevant(attr: t.Any, name: str, /) -> bool:
):
return False
elif ismodule(attr):
+ # TODO: Exclude `v5.theme` as it will collide with `alt.theme`
+
# Only include modules which are part of Altair. This excludes built-in
# modules (they do not have a __file__ attribute), standard library,
# and third-party packages.
From 754f665586dd42d6c3348dd0f8d1e001fe756715 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 15:42:46 +0100
Subject: [PATCH 03/44] refactor: Remove `altair.vegalite.v5.theme` from
`alt.__all__`
---
altair/__init__.py | 1 -
tools/update_init_file.py | 9 +++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index d4e20f02f..436a4a976 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -626,7 +626,6 @@
"selection_point",
"sequence",
"sphere",
- "theme",
"themes",
"to_csv",
"to_json",
diff --git a/tools/update_init_file.py b/tools/update_init_file.py
index 104dc9d51..28b2266c5 100644
--- a/tools/update_init_file.py
+++ b/tools/update_init_file.py
@@ -10,7 +10,7 @@
from tools.schemapi.utils import ruff_write_lint_format_str
-_TYPING_CONSTRUCTS = {
+_TYPING_CONSTRUCTS: set[t.Any] = {
te.TypeAlias,
t.TypeVar,
t.cast,
@@ -36,6 +36,8 @@
te.TypeAliasType,
}
+EXCLUDE_MODULES: set[str] = {"altair.vegalite.v5.theme"}
+
def update__all__variable() -> None:
"""
@@ -126,12 +128,11 @@ def _is_relevant(attr: t.Any, name: str, /) -> bool:
):
return False
elif ismodule(attr):
- # TODO: Exclude `v5.theme` as it will collide with `alt.theme`
-
# Only include modules which are part of Altair. This excludes built-in
# modules (they do not have a __file__ attribute), standard library,
# and third-party packages.
- return getattr_static(attr, "__file__", "").startswith(str(Path.cwd()))
+ is_altair = getattr_static(attr, "__file__", "").startswith(str(Path.cwd()))
+ return is_altair and attr.__name__ not in EXCLUDE_MODULES
else:
return True
From 3dd910009e9525c95bd98dcf26a649d6bb75b429 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 28 Sep 2024 15:44:32 +0100
Subject: [PATCH 04/44] refactor: Add `alt.theme.py`
---
altair/theme.py | 1 +
1 file changed, 1 insertion(+)
create mode 100644 altair/theme.py
diff --git a/altair/theme.py b/altair/theme.py
new file mode 100644
index 000000000..9d48db4f9
--- /dev/null
+++ b/altair/theme.py
@@ -0,0 +1 @@
+from __future__ import annotations
From b1be6335afd56c8ea1f6dd0905775a9255268db8 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 13:22:35 +0100
Subject: [PATCH 05/44] build(typing): Generate
`altair.vegalite.v5.schema.__init__.__all__`
https://github.com/vega/altair/pull/3618#discussion_r1780006759
---
altair/vegalite/v5/schema/__init__.py | 569 +++++++++++++++++++++++++-
tools/generate_schema_wrapper.py | 82 +++-
2 files changed, 631 insertions(+), 20 deletions(-)
diff --git a/altair/vegalite/v5/schema/__init__.py b/altair/vegalite/v5/schema/__init__.py
index 1f099eaca..3be8148db 100644
--- a/altair/vegalite/v5/schema/__init__.py
+++ b/altair/vegalite/v5/schema/__init__.py
@@ -1,8 +1,571 @@
-# ruff: noqa
+# ruff: noqa: F403, F405
+# The contents of this file are automatically written by
+# tools/generate_schema_wrapper.py. Do not modify directly.
-from .core import *
-from .channels import *
+from altair.vegalite.v5.schema import channels, core
+from altair.vegalite.v5.schema.channels import *
+from altair.vegalite.v5.schema.core import *
SCHEMA_VERSION = "v5.20.1"
SCHEMA_URL = "https://vega.github.io/schema/vega-lite/v5.20.1.json"
+
+__all__ = [
+ "SCHEMA_URL",
+ "SCHEMA_VERSION",
+ "URI",
+ "X2",
+ "Y2",
+ "Aggregate",
+ "AggregateOp",
+ "AggregateTransform",
+ "AggregatedFieldDef",
+ "Align",
+ "AllSortString",
+ "Angle",
+ "AngleDatum",
+ "AngleValue",
+ "AnyMark",
+ "AnyMarkConfig",
+ "AreaConfig",
+ "ArgmaxDef",
+ "ArgminDef",
+ "AutoSizeParams",
+ "AutosizeType",
+ "Axis",
+ "AxisConfig",
+ "AxisOrient",
+ "AxisResolveMap",
+ "BBox",
+ "BarConfig",
+ "BaseTitleNoValueRefs",
+ "Baseline",
+ "BinExtent",
+ "BinParams",
+ "BinTransform",
+ "BindCheckbox",
+ "BindDirect",
+ "BindInput",
+ "BindRadioSelect",
+ "BindRange",
+ "Binding",
+ "BinnedTimeUnit",
+ "Blend",
+ "BoxPlot",
+ "BoxPlotConfig",
+ "BoxPlotDef",
+ "BrushConfig",
+ "CalculateTransform",
+ "Categorical",
+ "Color",
+ "ColorDatum",
+ "ColorDef",
+ "ColorName",
+ "ColorScheme",
+ "ColorValue",
+ "Column",
+ "CompositeMark",
+ "CompositeMarkDef",
+ "CompositionConfig",
+ "ConcatSpecGenericSpec",
+ "ConditionalAxisColor",
+ "ConditionalAxisLabelAlign",
+ "ConditionalAxisLabelBaseline",
+ "ConditionalAxisLabelFontStyle",
+ "ConditionalAxisLabelFontWeight",
+ "ConditionalAxisNumber",
+ "ConditionalAxisNumberArray",
+ "ConditionalAxisPropertyAlignnull",
+ "ConditionalAxisPropertyColornull",
+ "ConditionalAxisPropertyFontStylenull",
+ "ConditionalAxisPropertyFontWeightnull",
+ "ConditionalAxisPropertyTextBaselinenull",
+ "ConditionalAxisPropertynumberArraynull",
+ "ConditionalAxisPropertynumbernull",
+ "ConditionalAxisPropertystringnull",
+ "ConditionalAxisString",
+ "ConditionalMarkPropFieldOrDatumDef",
+ "ConditionalMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalParameterMarkPropFieldOrDatumDef",
+ "ConditionalParameterMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalParameterStringFieldDef",
+ "ConditionalParameterValueDefGradientstringnullExprRef",
+ "ConditionalParameterValueDefTextExprRef",
+ "ConditionalParameterValueDefnumber",
+ "ConditionalParameterValueDefnumberArrayExprRef",
+ "ConditionalParameterValueDefnumberExprRef",
+ "ConditionalParameterValueDefstringExprRef",
+ "ConditionalParameterValueDefstringnullExprRef",
+ "ConditionalPredicateMarkPropFieldOrDatumDef",
+ "ConditionalPredicateMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalPredicateStringFieldDef",
+ "ConditionalPredicateValueDefAlignnullExprRef",
+ "ConditionalPredicateValueDefColornullExprRef",
+ "ConditionalPredicateValueDefFontStylenullExprRef",
+ "ConditionalPredicateValueDefFontWeightnullExprRef",
+ "ConditionalPredicateValueDefGradientstringnullExprRef",
+ "ConditionalPredicateValueDefTextBaselinenullExprRef",
+ "ConditionalPredicateValueDefTextExprRef",
+ "ConditionalPredicateValueDefnumber",
+ "ConditionalPredicateValueDefnumberArrayExprRef",
+ "ConditionalPredicateValueDefnumberArraynullExprRef",
+ "ConditionalPredicateValueDefnumberExprRef",
+ "ConditionalPredicateValueDefnumbernullExprRef",
+ "ConditionalPredicateValueDefstringExprRef",
+ "ConditionalPredicateValueDefstringnullExprRef",
+ "ConditionalStringFieldDef",
+ "ConditionalValueDefGradientstringnullExprRef",
+ "ConditionalValueDefTextExprRef",
+ "ConditionalValueDefnumber",
+ "ConditionalValueDefnumberArrayExprRef",
+ "ConditionalValueDefnumberExprRef",
+ "ConditionalValueDefstringExprRef",
+ "ConditionalValueDefstringnullExprRef",
+ "Config",
+ "CsvDataFormat",
+ "Cursor",
+ "Cyclical",
+ "Data",
+ "DataFormat",
+ "DataSource",
+ "Datasets",
+ "DateTime",
+ "DatumChannelMixin",
+ "DatumDef",
+ "Day",
+ "DensityTransform",
+ "DerivedStream",
+ "Description",
+ "DescriptionValue",
+ "Detail",
+ "DictInlineDataset",
+ "DictSelectionInit",
+ "DictSelectionInitInterval",
+ "Diverging",
+ "DomainUnionWith",
+ "DsvDataFormat",
+ "Element",
+ "Encoding",
+ "EncodingSortField",
+ "ErrorBand",
+ "ErrorBandConfig",
+ "ErrorBandDef",
+ "ErrorBar",
+ "ErrorBarConfig",
+ "ErrorBarDef",
+ "ErrorBarExtent",
+ "EventStream",
+ "EventType",
+ "Expr",
+ "ExprRef",
+ "ExtentTransform",
+ "Facet",
+ "FacetEncodingFieldDef",
+ "FacetFieldDef",
+ "FacetSpec",
+ "FacetedEncoding",
+ "FacetedUnitSpec",
+ "Feature",
+ "FeatureCollection",
+ "FeatureGeometryGeoJsonProperties",
+ "Field",
+ "FieldChannelMixin",
+ "FieldDefWithoutScale",
+ "FieldEqualPredicate",
+ "FieldGTEPredicate",
+ "FieldGTPredicate",
+ "FieldLTEPredicate",
+ "FieldLTPredicate",
+ "FieldName",
+ "FieldOneOfPredicate",
+ "FieldOrDatumDefWithConditionDatumDefGradientstringnull",
+ "FieldOrDatumDefWithConditionDatumDefnumber",
+ "FieldOrDatumDefWithConditionDatumDefnumberArray",
+ "FieldOrDatumDefWithConditionDatumDefstringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefGradientstringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefTypeForShapestringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefnumber",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefnumberArray",
+ "FieldOrDatumDefWithConditionStringDatumDefText",
+ "FieldOrDatumDefWithConditionStringFieldDefText",
+ "FieldOrDatumDefWithConditionStringFieldDefstring",
+ "FieldRange",
+ "FieldRangePredicate",
+ "FieldValidPredicate",
+ "Fill",
+ "FillDatum",
+ "FillOpacity",
+ "FillOpacityDatum",
+ "FillOpacityValue",
+ "FillValue",
+ "FilterTransform",
+ "Fit",
+ "FlattenTransform",
+ "FoldTransform",
+ "FontStyle",
+ "FontWeight",
+ "FormatConfig",
+ "Generator",
+ "GenericUnitSpecEncodingAnyMark",
+ "GeoJsonFeature",
+ "GeoJsonFeatureCollection",
+ "GeoJsonProperties",
+ "Geometry",
+ "GeometryCollection",
+ "Gradient",
+ "GradientStop",
+ "GraticuleGenerator",
+ "GraticuleParams",
+ "HConcatSpecGenericSpec",
+ "Header",
+ "HeaderConfig",
+ "HexColor",
+ "Href",
+ "HrefValue",
+ "ImputeMethod",
+ "ImputeParams",
+ "ImputeSequence",
+ "ImputeTransform",
+ "InlineData",
+ "InlineDataset",
+ "Interpolate",
+ "IntervalSelectionConfig",
+ "IntervalSelectionConfigWithoutType",
+ "JoinAggregateFieldDef",
+ "JoinAggregateTransform",
+ "JsonDataFormat",
+ "Key",
+ "LabelOverlap",
+ "LatLongDef",
+ "LatLongFieldDef",
+ "Latitude",
+ "Latitude2",
+ "Latitude2Datum",
+ "Latitude2Value",
+ "LatitudeDatum",
+ "LayerRepeatMapping",
+ "LayerRepeatSpec",
+ "LayerSpec",
+ "LayoutAlign",
+ "Legend",
+ "LegendBinding",
+ "LegendConfig",
+ "LegendOrient",
+ "LegendResolveMap",
+ "LegendStreamBinding",
+ "LineConfig",
+ "LineString",
+ "LinearGradient",
+ "LocalMultiTimeUnit",
+ "LocalSingleTimeUnit",
+ "Locale",
+ "LoessTransform",
+ "LogicalAndPredicate",
+ "LogicalNotPredicate",
+ "LogicalOrPredicate",
+ "Longitude",
+ "Longitude2",
+ "Longitude2Datum",
+ "Longitude2Value",
+ "LongitudeDatum",
+ "LookupSelection",
+ "LookupTransform",
+ "Mark",
+ "MarkConfig",
+ "MarkDef",
+ "MarkInvalidDataMode",
+ "MarkPropDefGradientstringnull",
+ "MarkPropDefnumber",
+ "MarkPropDefnumberArray",
+ "MarkPropDefstringnullTypeForShape",
+ "MarkType",
+ "MergedStream",
+ "Month",
+ "MultiLineString",
+ "MultiPoint",
+ "MultiPolygon",
+ "MultiTimeUnit",
+ "NamedData",
+ "NonArgAggregateOp",
+ "NonLayerRepeatSpec",
+ "NonNormalizedSpec",
+ "NumberLocale",
+ "NumericArrayMarkPropDef",
+ "NumericMarkPropDef",
+ "OffsetDef",
+ "Opacity",
+ "OpacityDatum",
+ "OpacityValue",
+ "Order",
+ "OrderFieldDef",
+ "OrderOnlyDef",
+ "OrderValue",
+ "OrderValueDef",
+ "Orient",
+ "Orientation",
+ "OverlayMarkDef",
+ "Padding",
+ "ParameterExtent",
+ "ParameterName",
+ "ParameterPredicate",
+ "Parse",
+ "ParseValue",
+ "PivotTransform",
+ "Point",
+ "PointSelectionConfig",
+ "PointSelectionConfigWithoutType",
+ "PolarDef",
+ "Polygon",
+ "Position",
+ "Position2Def",
+ "PositionDatumDef",
+ "PositionDatumDefBase",
+ "PositionDef",
+ "PositionFieldDef",
+ "PositionFieldDefBase",
+ "PositionValueDef",
+ "Predicate",
+ "PredicateComposition",
+ "PrimitiveValue",
+ "Projection",
+ "ProjectionConfig",
+ "ProjectionType",
+ "QuantileTransform",
+ "RadialGradient",
+ "Radius",
+ "Radius2",
+ "Radius2Datum",
+ "Radius2Value",
+ "RadiusDatum",
+ "RadiusValue",
+ "RangeConfig",
+ "RangeEnum",
+ "RangeRaw",
+ "RangeRawArray",
+ "RangeScheme",
+ "RectConfig",
+ "RegressionTransform",
+ "RelativeBandSize",
+ "RepeatMapping",
+ "RepeatRef",
+ "RepeatSpec",
+ "Resolve",
+ "ResolveMode",
+ "Root",
+ "Row",
+ "RowColLayoutAlign",
+ "RowColboolean",
+ "RowColnumber",
+ "RowColumnEncodingFieldDef",
+ "SampleTransform",
+ "Scale",
+ "ScaleBinParams",
+ "ScaleBins",
+ "ScaleConfig",
+ "ScaleDatumDef",
+ "ScaleFieldDef",
+ "ScaleInterpolateEnum",
+ "ScaleInterpolateParams",
+ "ScaleInvalidDataConfig",
+ "ScaleInvalidDataShowAsValueangle",
+ "ScaleInvalidDataShowAsValuecolor",
+ "ScaleInvalidDataShowAsValuefill",
+ "ScaleInvalidDataShowAsValuefillOpacity",
+ "ScaleInvalidDataShowAsValueopacity",
+ "ScaleInvalidDataShowAsValueradius",
+ "ScaleInvalidDataShowAsValueshape",
+ "ScaleInvalidDataShowAsValuesize",
+ "ScaleInvalidDataShowAsValuestroke",
+ "ScaleInvalidDataShowAsValuestrokeDash",
+ "ScaleInvalidDataShowAsValuestrokeOpacity",
+ "ScaleInvalidDataShowAsValuestrokeWidth",
+ "ScaleInvalidDataShowAsValuetheta",
+ "ScaleInvalidDataShowAsValuex",
+ "ScaleInvalidDataShowAsValuexOffset",
+ "ScaleInvalidDataShowAsValuey",
+ "ScaleInvalidDataShowAsValueyOffset",
+ "ScaleInvalidDataShowAsangle",
+ "ScaleInvalidDataShowAscolor",
+ "ScaleInvalidDataShowAsfill",
+ "ScaleInvalidDataShowAsfillOpacity",
+ "ScaleInvalidDataShowAsopacity",
+ "ScaleInvalidDataShowAsradius",
+ "ScaleInvalidDataShowAsshape",
+ "ScaleInvalidDataShowAssize",
+ "ScaleInvalidDataShowAsstroke",
+ "ScaleInvalidDataShowAsstrokeDash",
+ "ScaleInvalidDataShowAsstrokeOpacity",
+ "ScaleInvalidDataShowAsstrokeWidth",
+ "ScaleInvalidDataShowAstheta",
+ "ScaleInvalidDataShowAsx",
+ "ScaleInvalidDataShowAsxOffset",
+ "ScaleInvalidDataShowAsy",
+ "ScaleInvalidDataShowAsyOffset",
+ "ScaleResolveMap",
+ "ScaleType",
+ "SchemaBase",
+ "SchemeParams",
+ "SecondaryFieldDef",
+ "SelectionConfig",
+ "SelectionInit",
+ "SelectionInitInterval",
+ "SelectionInitIntervalMapping",
+ "SelectionInitMapping",
+ "SelectionParameter",
+ "SelectionResolution",
+ "SelectionType",
+ "SequenceGenerator",
+ "SequenceParams",
+ "SequentialMultiHue",
+ "SequentialSingleHue",
+ "Shape",
+ "ShapeDatum",
+ "ShapeDef",
+ "ShapeValue",
+ "SharedEncoding",
+ "SingleDefUnitChannel",
+ "SingleTimeUnit",
+ "Size",
+ "SizeDatum",
+ "SizeValue",
+ "Sort",
+ "SortArray",
+ "SortByChannel",
+ "SortByChannelDesc",
+ "SortByEncoding",
+ "SortField",
+ "SortOrder",
+ "Spec",
+ "SphereGenerator",
+ "StackOffset",
+ "StackTransform",
+ "StandardType",
+ "Step",
+ "StepFor",
+ "Stream",
+ "StringFieldDef",
+ "StringFieldDefWithCondition",
+ "StringValueDefWithCondition",
+ "Stroke",
+ "StrokeCap",
+ "StrokeDash",
+ "StrokeDashDatum",
+ "StrokeDashValue",
+ "StrokeDatum",
+ "StrokeJoin",
+ "StrokeOpacity",
+ "StrokeOpacityDatum",
+ "StrokeOpacityValue",
+ "StrokeValue",
+ "StrokeWidth",
+ "StrokeWidthDatum",
+ "StrokeWidthValue",
+ "StyleConfigIndex",
+ "SymbolShape",
+ "Text",
+ "TextBaseline",
+ "TextDatum",
+ "TextDef",
+ "TextDirection",
+ "TextValue",
+ "Theta",
+ "Theta2",
+ "Theta2Datum",
+ "Theta2Value",
+ "ThetaDatum",
+ "ThetaValue",
+ "TickConfig",
+ "TickCount",
+ "TimeInterval",
+ "TimeIntervalStep",
+ "TimeLocale",
+ "TimeUnit",
+ "TimeUnitParams",
+ "TimeUnitTransform",
+ "TimeUnitTransformParams",
+ "TitleAnchor",
+ "TitleConfig",
+ "TitleFrame",
+ "TitleOrient",
+ "TitleParams",
+ "Tooltip",
+ "TooltipContent",
+ "TooltipValue",
+ "TopLevelConcatSpec",
+ "TopLevelFacetSpec",
+ "TopLevelHConcatSpec",
+ "TopLevelLayerSpec",
+ "TopLevelParameter",
+ "TopLevelRepeatSpec",
+ "TopLevelSelectionParameter",
+ "TopLevelSpec",
+ "TopLevelUnitSpec",
+ "TopLevelVConcatSpec",
+ "TopoDataFormat",
+ "Transform",
+ "Type",
+ "TypeForShape",
+ "TypedFieldDef",
+ "UnitSpec",
+ "UnitSpecWithFrame",
+ "Url",
+ "UrlData",
+ "UrlValue",
+ "UtcMultiTimeUnit",
+ "UtcSingleTimeUnit",
+ "VConcatSpecGenericSpec",
+ "ValueChannelMixin",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefGradientstringnull",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefTypeForShapestringnull",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefnumber",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefnumberArray",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefstringnull",
+ "ValueDefWithConditionStringFieldDefText",
+ "ValueDefnumber",
+ "ValueDefnumberwidthheightExprRef",
+ "VariableParameter",
+ "Vector2DateTime",
+ "Vector2Vector2number",
+ "Vector2boolean",
+ "Vector2number",
+ "Vector2string",
+ "Vector3number",
+ "Vector7string",
+ "Vector10string",
+ "Vector12string",
+ "VegaLiteSchema",
+ "ViewBackground",
+ "ViewConfig",
+ "WindowEventType",
+ "WindowFieldDef",
+ "WindowOnlyOp",
+ "WindowTransform",
+ "X",
+ "X2Datum",
+ "X2Value",
+ "XDatum",
+ "XError",
+ "XError2",
+ "XError2Value",
+ "XErrorValue",
+ "XOffset",
+ "XOffsetDatum",
+ "XOffsetValue",
+ "XValue",
+ "Y",
+ "Y2Datum",
+ "Y2Value",
+ "YDatum",
+ "YError",
+ "YError2",
+ "YError2Value",
+ "YErrorValue",
+ "YOffset",
+ "YOffsetDatum",
+ "YOffsetValue",
+ "YValue",
+ "channels",
+ "core",
+ "load_schema",
+ "with_property_setters",
+]
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index a0fa08e72..840b43108 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -12,7 +12,16 @@
from itertools import chain
from operator import attrgetter
from pathlib import Path
-from typing import TYPE_CHECKING, Any, Final, Iterable, Iterator, Literal
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Final,
+ Generic,
+ Iterable,
+ Iterator,
+ Literal,
+ TypeVar,
+)
from urllib import request
import vl_convert as vlc
@@ -46,6 +55,8 @@
if TYPE_CHECKING:
from tools.schemapi.codegen import ArgInfo, AttrGetter
+T = TypeVar("T", str, Iterable[str])
+
SCHEMA_VERSION: Final = "v5.20.1"
@@ -458,6 +469,12 @@ class {classname}(DatumChannelMixin, core.{basename}):
)
+class ModuleDef(Generic[T]):
+ def __init__(self, contents: T, all: Iterable[str], /) -> None:
+ self.contents: T = contents
+ self.all: list[str] = list(all)
+
+
def schema_class(*args, **kwargs) -> str:
return SchemaGenerator(*args, **kwargs).schema_class()
@@ -619,7 +636,7 @@ def visit(nodes):
return stack
-def generate_vegalite_schema_wrapper(fp: Path, /) -> str:
+def generate_vegalite_schema_wrapper(fp: Path, /) -> ModuleDef[str]:
"""Generate a schema wrapper at the given path."""
# TODO: generate simple tests for each wrapper
basename = "VegaLiteSchema"
@@ -685,7 +702,7 @@ def generate_vegalite_schema_wrapper(fp: Path, /) -> str:
contents.append(definitions[name].schema_class())
contents.append("") # end with newline
- return "\n".join(contents)
+ return ModuleDef("\n".join(contents), all_)
@dataclass
@@ -717,7 +734,7 @@ def non_field_names(self) -> Iterator[str]:
yield self.value_class_name
-def generate_vegalite_channel_wrappers(fp: Path, /) -> str:
+def generate_vegalite_channel_wrappers(fp: Path, /) -> ModuleDef[list[str]]:
schema = load_schema_with_shorthand_properties(fp)
encoding_def = "FacetedEncoding"
encoding = SchemaInfo(schema["definitions"][encoding_def], rootschema=schema)
@@ -774,7 +791,7 @@ def generate_vegalite_channel_wrappers(fp: Path, /) -> str:
"with_property_setters",
)
it = chain.from_iterable(info.all_names for info in channel_infos.values())
- all_ = list(chain(it, COMPAT_EXPORTS))
+ all_ = sorted(chain(it, COMPAT_EXPORTS))
imports = [
"import sys",
"from typing import Any, overload, Sequence, List, Literal, Union, TYPE_CHECKING, TypedDict",
@@ -786,7 +803,7 @@ def generate_vegalite_channel_wrappers(fp: Path, /) -> str:
"from . import core",
"from ._typing import * # noqa: F403",
]
- contents = [
+ contents: list[str] = [
HEADER,
CHANNEL_MYPY_IGNORE_STATEMENTS,
*imports,
@@ -796,14 +813,14 @@ def generate_vegalite_channel_wrappers(fp: Path, /) -> str:
f"from altair.vegalite.v5.api import {INTO_CONDITION}",
textwrap.indent(import_typing_extensions((3, 11), "Self"), " "),
),
- "\n" f"__all__ = {sorted(all_)}\n",
+ "\n" f"__all__ = {all_}\n",
CHANNEL_MIXINS,
*class_defs,
*generate_encoding_artifacts(
channel_infos, ENCODE_METHOD, facet_encoding=encoding
),
]
- return "\n".join(contents)
+ return ModuleDef(contents, all_)
def generate_vegalite_mark_mixin(fp: Path, /, markdefs: dict[str, str]) -> str:
@@ -1001,6 +1018,32 @@ def generate_vegalite_config_mixin(fp: Path, /) -> str:
return "\n".join(code)
+def generate_schema__init__(
+ version: str,
+ *modules: str,
+ package_name: str = "altair.vegalite.{0}.schema",
+ expand: dict[Path, ModuleDef[Any]] | None = None,
+) -> Iterator[str]:
+ # NOTE: `expand`
+ # - Should run after generating `core`, `channels`
+ # - Only needed for `mypy`, the default works at runtime
+ package_name = package_name.format(version.split(".")[0])
+ yield f"# ruff: noqa: F403, F405\n{HEADER_COMMENT}"
+ yield f"from {package_name} import {', '.join(modules)}"
+ yield from (f"from {package_name}.{mod} import *" for mod in modules)
+ yield f"SCHEMA_VERSION = '{version}'\n"
+ yield f"SCHEMA_URL = {schema_url(version)!r}\n"
+ base_all: list[str] = ["SCHEMA_URL", "SCHEMA_VERSION", *modules]
+ if expand:
+ base_all.extend(
+ chain.from_iterable(v.all for k, v in expand.items() if k.stem in modules)
+ )
+ yield f"__all__ = {base_all}"
+ else:
+ yield f"__all__ = {base_all}"
+ yield from (f"__all__ += {mod}.__all__" for mod in modules)
+
+
def vegalite_main(skip_download: bool = False) -> None:
version = SCHEMA_VERSION
vn = version.split(".")[0]
@@ -1019,27 +1062,32 @@ def vegalite_main(skip_download: bool = False) -> None:
# Generate __init__.py file
outfile = schemapath / "__init__.py"
print(f"Writing {outfile!s}")
- content = [
- "# ruff: noqa\n",
- "from .core import *\nfrom .channels import *\n",
- f"SCHEMA_VERSION = '{version}'\n",
- f"SCHEMA_URL = {schema_url(version)!r}\n",
- ]
- ruff_write_lint_format_str(outfile, content)
+ ruff_write_lint_format_str(
+ outfile, generate_schema__init__(version, "channels", "core")
+ )
TypeAliasTracer.update_aliases(("Map", "Mapping[str, Any]"))
files: dict[Path, str | Iterable[str]] = {}
+ modules: dict[Path, ModuleDef[Any]] = {}
# Generate the core schema wrappers
fp_core = schemapath / "core.py"
print(f"Generating\n {schemafile!s}\n ->{fp_core!s}")
- files[fp_core] = generate_vegalite_schema_wrapper(schemafile)
+ modules[fp_core] = generate_vegalite_schema_wrapper(schemafile)
+ files[fp_core] = modules[fp_core].contents
# Generate the channel wrappers
fp_channels = schemapath / "channels.py"
print(f"Generating\n {schemafile!s}\n ->{fp_channels!s}")
- files[fp_channels] = generate_vegalite_channel_wrappers(schemafile)
+ modules[fp_channels] = generate_vegalite_channel_wrappers(schemafile)
+ files[fp_channels] = modules[fp_channels].contents
+
+ # Expand `schema.__init__.__all__` with new classes
+ ruff_write_lint_format_str(
+ outfile,
+ generate_schema__init__(version, "channels", "core", expand=modules),
+ )
# generate the mark mixin
markdefs = {k: f"{k}Def" for k in ["Mark", "BoxPlot", "ErrorBar", "ErrorBand"]}
From 0bd8f755651a2d00c4695907b6f7eceb87bc0bf2 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:01:53 +0100
Subject: [PATCH 06/44] build: Generate `altair.vegalite.v5.__init__.__all__`
https://github.com/vega/altair/pull/3618#discussion_r1780006759
---
altair/vegalite/v5/__init__.py | 644 ++++++++++++++++++++++++++++++++-
tools/generate_api_docs.py | 2 +-
tools/update_init_file.py | 112 +++++-
3 files changed, 744 insertions(+), 14 deletions(-)
diff --git a/altair/vegalite/v5/__init__.py b/altair/vegalite/v5/__init__.py
index 72ff2eec1..d8d794b2a 100644
--- a/altair/vegalite/v5/__init__.py
+++ b/altair/vegalite/v5/__init__.py
@@ -1,9 +1,9 @@
-# ruff: noqa: F401, F403
+# ruff: noqa: F401, F403, F405
from altair.expr.core import datum
-
-from .api import *
-from .compiler import vegalite_compilers
-from .data import (
+from altair.vegalite.v5 import api, compiler, schema
+from altair.vegalite.v5.api import *
+from altair.vegalite.v5.compiler import vegalite_compilers
+from altair.vegalite.v5.data import (
MaxRowsError,
data_transformers,
default_data_transformer,
@@ -13,14 +13,640 @@
to_json,
to_values,
)
-from .display import (
+from altair.vegalite.v5.display import (
VEGA_VERSION,
VEGAEMBED_VERSION,
VEGALITE_VERSION,
VegaLite,
renderers,
)
-from .schema import *
+from altair.vegalite.v5.schema import *
+
+# The content of __all__ is automatically written by
+# tools/update_init_file.py. Do not modify directly.
-# NOTE: `theme` contents -> `vegalite.v5`
-from .theme import register_theme, themes
+__all__ = [
+ "SCHEMA_URL",
+ "SCHEMA_VERSION",
+ "TOPLEVEL_ONLY_KEYS",
+ "URI",
+ "VEGAEMBED_VERSION",
+ "VEGALITE_VERSION",
+ "VEGA_VERSION",
+ "X2",
+ "Y2",
+ "Aggregate",
+ "AggregateOp",
+ "AggregateTransform",
+ "AggregatedFieldDef",
+ "Align",
+ "AllSortString",
+ "Angle",
+ "AngleDatum",
+ "AngleValue",
+ "AnyMark",
+ "AnyMarkConfig",
+ "AreaConfig",
+ "ArgmaxDef",
+ "ArgminDef",
+ "AutoSizeParams",
+ "AutosizeType",
+ "Axis",
+ "AxisConfig",
+ "AxisOrient",
+ "AxisResolveMap",
+ "BBox",
+ "BarConfig",
+ "BaseTitleNoValueRefs",
+ "Baseline",
+ "Bin",
+ "BinExtent",
+ "BinParams",
+ "BinTransform",
+ "BindCheckbox",
+ "BindDirect",
+ "BindInput",
+ "BindRadioSelect",
+ "BindRange",
+ "Binding",
+ "BinnedTimeUnit",
+ "Blend",
+ "BoxPlot",
+ "BoxPlotConfig",
+ "BoxPlotDef",
+ "BrushConfig",
+ "CalculateTransform",
+ "Categorical",
+ "ChainedWhen",
+ "Chart",
+ "ChartDataType",
+ "Color",
+ "ColorDatum",
+ "ColorDef",
+ "ColorName",
+ "ColorScheme",
+ "ColorValue",
+ "Column",
+ "CompositeMark",
+ "CompositeMarkDef",
+ "CompositionConfig",
+ "ConcatChart",
+ "ConcatSpecGenericSpec",
+ "ConditionalAxisColor",
+ "ConditionalAxisLabelAlign",
+ "ConditionalAxisLabelBaseline",
+ "ConditionalAxisLabelFontStyle",
+ "ConditionalAxisLabelFontWeight",
+ "ConditionalAxisNumber",
+ "ConditionalAxisNumberArray",
+ "ConditionalAxisPropertyAlignnull",
+ "ConditionalAxisPropertyColornull",
+ "ConditionalAxisPropertyFontStylenull",
+ "ConditionalAxisPropertyFontWeightnull",
+ "ConditionalAxisPropertyTextBaselinenull",
+ "ConditionalAxisPropertynumberArraynull",
+ "ConditionalAxisPropertynumbernull",
+ "ConditionalAxisPropertystringnull",
+ "ConditionalAxisString",
+ "ConditionalMarkPropFieldOrDatumDef",
+ "ConditionalMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalParameterMarkPropFieldOrDatumDef",
+ "ConditionalParameterMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalParameterStringFieldDef",
+ "ConditionalParameterValueDefGradientstringnullExprRef",
+ "ConditionalParameterValueDefTextExprRef",
+ "ConditionalParameterValueDefnumber",
+ "ConditionalParameterValueDefnumberArrayExprRef",
+ "ConditionalParameterValueDefnumberExprRef",
+ "ConditionalParameterValueDefstringExprRef",
+ "ConditionalParameterValueDefstringnullExprRef",
+ "ConditionalPredicateMarkPropFieldOrDatumDef",
+ "ConditionalPredicateMarkPropFieldOrDatumDefTypeForShape",
+ "ConditionalPredicateStringFieldDef",
+ "ConditionalPredicateValueDefAlignnullExprRef",
+ "ConditionalPredicateValueDefColornullExprRef",
+ "ConditionalPredicateValueDefFontStylenullExprRef",
+ "ConditionalPredicateValueDefFontWeightnullExprRef",
+ "ConditionalPredicateValueDefGradientstringnullExprRef",
+ "ConditionalPredicateValueDefTextBaselinenullExprRef",
+ "ConditionalPredicateValueDefTextExprRef",
+ "ConditionalPredicateValueDefnumber",
+ "ConditionalPredicateValueDefnumberArrayExprRef",
+ "ConditionalPredicateValueDefnumberArraynullExprRef",
+ "ConditionalPredicateValueDefnumberExprRef",
+ "ConditionalPredicateValueDefnumbernullExprRef",
+ "ConditionalPredicateValueDefstringExprRef",
+ "ConditionalPredicateValueDefstringnullExprRef",
+ "ConditionalStringFieldDef",
+ "ConditionalValueDefGradientstringnullExprRef",
+ "ConditionalValueDefTextExprRef",
+ "ConditionalValueDefnumber",
+ "ConditionalValueDefnumberArrayExprRef",
+ "ConditionalValueDefnumberExprRef",
+ "ConditionalValueDefstringExprRef",
+ "ConditionalValueDefstringnullExprRef",
+ "Config",
+ "CsvDataFormat",
+ "Cursor",
+ "Cyclical",
+ "Data",
+ "DataFormat",
+ "DataSource",
+ "DataType",
+ "Datasets",
+ "DateTime",
+ "DatumChannelMixin",
+ "DatumDef",
+ "Day",
+ "DensityTransform",
+ "DerivedStream",
+ "Description",
+ "DescriptionValue",
+ "Detail",
+ "DictInlineDataset",
+ "DictSelectionInit",
+ "DictSelectionInitInterval",
+ "Diverging",
+ "DomainUnionWith",
+ "DsvDataFormat",
+ "Element",
+ "Encoding",
+ "EncodingSortField",
+ "ErrorBand",
+ "ErrorBandConfig",
+ "ErrorBandDef",
+ "ErrorBar",
+ "ErrorBarConfig",
+ "ErrorBarDef",
+ "ErrorBarExtent",
+ "EventStream",
+ "EventType",
+ "Expr",
+ "ExprRef",
+ "ExtentTransform",
+ "Facet",
+ "FacetChart",
+ "FacetEncodingFieldDef",
+ "FacetFieldDef",
+ "FacetMapping",
+ "FacetSpec",
+ "FacetedEncoding",
+ "FacetedUnitSpec",
+ "Feature",
+ "FeatureCollection",
+ "FeatureGeometryGeoJsonProperties",
+ "Field",
+ "FieldChannelMixin",
+ "FieldDefWithoutScale",
+ "FieldEqualPredicate",
+ "FieldGTEPredicate",
+ "FieldGTPredicate",
+ "FieldLTEPredicate",
+ "FieldLTPredicate",
+ "FieldName",
+ "FieldOneOfPredicate",
+ "FieldOrDatumDefWithConditionDatumDefGradientstringnull",
+ "FieldOrDatumDefWithConditionDatumDefnumber",
+ "FieldOrDatumDefWithConditionDatumDefnumberArray",
+ "FieldOrDatumDefWithConditionDatumDefstringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefGradientstringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefTypeForShapestringnull",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefnumber",
+ "FieldOrDatumDefWithConditionMarkPropFieldDefnumberArray",
+ "FieldOrDatumDefWithConditionStringDatumDefText",
+ "FieldOrDatumDefWithConditionStringFieldDefText",
+ "FieldOrDatumDefWithConditionStringFieldDefstring",
+ "FieldRange",
+ "FieldRangePredicate",
+ "FieldValidPredicate",
+ "Fill",
+ "FillDatum",
+ "FillOpacity",
+ "FillOpacityDatum",
+ "FillOpacityValue",
+ "FillValue",
+ "FilterTransform",
+ "Fit",
+ "FlattenTransform",
+ "FoldTransform",
+ "FontStyle",
+ "FontWeight",
+ "FormatConfig",
+ "Generator",
+ "GenericUnitSpecEncodingAnyMark",
+ "GeoJsonFeature",
+ "GeoJsonFeatureCollection",
+ "GeoJsonProperties",
+ "Geometry",
+ "GeometryCollection",
+ "Gradient",
+ "GradientStop",
+ "GraticuleGenerator",
+ "GraticuleParams",
+ "HConcatChart",
+ "HConcatSpecGenericSpec",
+ "Header",
+ "HeaderConfig",
+ "HexColor",
+ "Href",
+ "HrefValue",
+ "Impute",
+ "ImputeMethod",
+ "ImputeParams",
+ "ImputeSequence",
+ "ImputeTransform",
+ "InlineData",
+ "InlineDataset",
+ "Interpolate",
+ "IntervalSelectionConfig",
+ "IntervalSelectionConfigWithoutType",
+ "JoinAggregateFieldDef",
+ "JoinAggregateTransform",
+ "JsonDataFormat",
+ "Key",
+ "LabelOverlap",
+ "LatLongDef",
+ "LatLongFieldDef",
+ "Latitude",
+ "Latitude2",
+ "Latitude2Datum",
+ "Latitude2Value",
+ "LatitudeDatum",
+ "LayerChart",
+ "LayerRepeatMapping",
+ "LayerRepeatSpec",
+ "LayerSpec",
+ "LayoutAlign",
+ "Legend",
+ "LegendBinding",
+ "LegendConfig",
+ "LegendOrient",
+ "LegendResolveMap",
+ "LegendStreamBinding",
+ "LineConfig",
+ "LineString",
+ "LinearGradient",
+ "LocalMultiTimeUnit",
+ "LocalSingleTimeUnit",
+ "Locale",
+ "LoessTransform",
+ "LogicalAndPredicate",
+ "LogicalNotPredicate",
+ "LogicalOrPredicate",
+ "Longitude",
+ "Longitude2",
+ "Longitude2Datum",
+ "Longitude2Value",
+ "LongitudeDatum",
+ "LookupData",
+ "LookupSelection",
+ "LookupTransform",
+ "Mark",
+ "MarkConfig",
+ "MarkDef",
+ "MarkInvalidDataMode",
+ "MarkPropDefGradientstringnull",
+ "MarkPropDefnumber",
+ "MarkPropDefnumberArray",
+ "MarkPropDefstringnullTypeForShape",
+ "MarkType",
+ "MaxRowsError",
+ "MergedStream",
+ "Month",
+ "MultiLineString",
+ "MultiPoint",
+ "MultiPolygon",
+ "MultiTimeUnit",
+ "NamedData",
+ "NonArgAggregateOp",
+ "NonLayerRepeatSpec",
+ "NonNormalizedSpec",
+ "NumberLocale",
+ "NumericArrayMarkPropDef",
+ "NumericMarkPropDef",
+ "OffsetDef",
+ "Opacity",
+ "OpacityDatum",
+ "OpacityValue",
+ "Order",
+ "OrderFieldDef",
+ "OrderOnlyDef",
+ "OrderValue",
+ "OrderValueDef",
+ "Orient",
+ "Orientation",
+ "OverlayMarkDef",
+ "Padding",
+ "Parameter",
+ "ParameterExpression",
+ "ParameterExtent",
+ "ParameterName",
+ "ParameterPredicate",
+ "Parse",
+ "ParseValue",
+ "PivotTransform",
+ "Point",
+ "PointSelectionConfig",
+ "PointSelectionConfigWithoutType",
+ "PolarDef",
+ "Polygon",
+ "Position",
+ "Position2Def",
+ "PositionDatumDef",
+ "PositionDatumDefBase",
+ "PositionDef",
+ "PositionFieldDef",
+ "PositionFieldDefBase",
+ "PositionValueDef",
+ "Predicate",
+ "PredicateComposition",
+ "PrimitiveValue",
+ "Projection",
+ "ProjectionConfig",
+ "ProjectionType",
+ "QuantileTransform",
+ "RadialGradient",
+ "Radius",
+ "Radius2",
+ "Radius2Datum",
+ "Radius2Value",
+ "RadiusDatum",
+ "RadiusValue",
+ "RangeConfig",
+ "RangeEnum",
+ "RangeRaw",
+ "RangeRawArray",
+ "RangeScheme",
+ "RectConfig",
+ "RegressionTransform",
+ "RelativeBandSize",
+ "RepeatChart",
+ "RepeatMapping",
+ "RepeatRef",
+ "RepeatSpec",
+ "Resolve",
+ "ResolveMode",
+ "Root",
+ "Row",
+ "RowColLayoutAlign",
+ "RowColboolean",
+ "RowColnumber",
+ "RowColumnEncodingFieldDef",
+ "SampleTransform",
+ "Scale",
+ "ScaleBinParams",
+ "ScaleBins",
+ "ScaleConfig",
+ "ScaleDatumDef",
+ "ScaleFieldDef",
+ "ScaleInterpolateEnum",
+ "ScaleInterpolateParams",
+ "ScaleInvalidDataConfig",
+ "ScaleInvalidDataShowAsValueangle",
+ "ScaleInvalidDataShowAsValuecolor",
+ "ScaleInvalidDataShowAsValuefill",
+ "ScaleInvalidDataShowAsValuefillOpacity",
+ "ScaleInvalidDataShowAsValueopacity",
+ "ScaleInvalidDataShowAsValueradius",
+ "ScaleInvalidDataShowAsValueshape",
+ "ScaleInvalidDataShowAsValuesize",
+ "ScaleInvalidDataShowAsValuestroke",
+ "ScaleInvalidDataShowAsValuestrokeDash",
+ "ScaleInvalidDataShowAsValuestrokeOpacity",
+ "ScaleInvalidDataShowAsValuestrokeWidth",
+ "ScaleInvalidDataShowAsValuetheta",
+ "ScaleInvalidDataShowAsValuex",
+ "ScaleInvalidDataShowAsValuexOffset",
+ "ScaleInvalidDataShowAsValuey",
+ "ScaleInvalidDataShowAsValueyOffset",
+ "ScaleInvalidDataShowAsangle",
+ "ScaleInvalidDataShowAscolor",
+ "ScaleInvalidDataShowAsfill",
+ "ScaleInvalidDataShowAsfillOpacity",
+ "ScaleInvalidDataShowAsopacity",
+ "ScaleInvalidDataShowAsradius",
+ "ScaleInvalidDataShowAsshape",
+ "ScaleInvalidDataShowAssize",
+ "ScaleInvalidDataShowAsstroke",
+ "ScaleInvalidDataShowAsstrokeDash",
+ "ScaleInvalidDataShowAsstrokeOpacity",
+ "ScaleInvalidDataShowAsstrokeWidth",
+ "ScaleInvalidDataShowAstheta",
+ "ScaleInvalidDataShowAsx",
+ "ScaleInvalidDataShowAsxOffset",
+ "ScaleInvalidDataShowAsy",
+ "ScaleInvalidDataShowAsyOffset",
+ "ScaleResolveMap",
+ "ScaleType",
+ "SchemaBase",
+ "SchemeParams",
+ "SecondaryFieldDef",
+ "SelectionConfig",
+ "SelectionExpression",
+ "SelectionInit",
+ "SelectionInitInterval",
+ "SelectionInitIntervalMapping",
+ "SelectionInitMapping",
+ "SelectionParameter",
+ "SelectionPredicateComposition",
+ "SelectionResolution",
+ "SelectionType",
+ "SequenceGenerator",
+ "SequenceParams",
+ "SequentialMultiHue",
+ "SequentialSingleHue",
+ "Shape",
+ "ShapeDatum",
+ "ShapeDef",
+ "ShapeValue",
+ "SharedEncoding",
+ "SingleDefUnitChannel",
+ "SingleTimeUnit",
+ "Size",
+ "SizeDatum",
+ "SizeValue",
+ "Sort",
+ "SortArray",
+ "SortByChannel",
+ "SortByChannelDesc",
+ "SortByEncoding",
+ "SortField",
+ "SortOrder",
+ "Spec",
+ "SphereGenerator",
+ "StackOffset",
+ "StackTransform",
+ "StandardType",
+ "Step",
+ "StepFor",
+ "Stream",
+ "StringFieldDef",
+ "StringFieldDefWithCondition",
+ "StringValueDefWithCondition",
+ "Stroke",
+ "StrokeCap",
+ "StrokeDash",
+ "StrokeDashDatum",
+ "StrokeDashValue",
+ "StrokeDatum",
+ "StrokeJoin",
+ "StrokeOpacity",
+ "StrokeOpacityDatum",
+ "StrokeOpacityValue",
+ "StrokeValue",
+ "StrokeWidth",
+ "StrokeWidthDatum",
+ "StrokeWidthValue",
+ "StyleConfigIndex",
+ "SymbolShape",
+ "Text",
+ "TextBaseline",
+ "TextDatum",
+ "TextDef",
+ "TextDirection",
+ "TextValue",
+ "Then",
+ "Theta",
+ "Theta2",
+ "Theta2Datum",
+ "Theta2Value",
+ "ThetaDatum",
+ "ThetaValue",
+ "TickConfig",
+ "TickCount",
+ "TimeInterval",
+ "TimeIntervalStep",
+ "TimeLocale",
+ "TimeUnit",
+ "TimeUnitParams",
+ "TimeUnitTransform",
+ "TimeUnitTransformParams",
+ "Title",
+ "TitleAnchor",
+ "TitleConfig",
+ "TitleFrame",
+ "TitleOrient",
+ "TitleParams",
+ "Tooltip",
+ "TooltipContent",
+ "TooltipValue",
+ "TopLevelConcatSpec",
+ "TopLevelFacetSpec",
+ "TopLevelHConcatSpec",
+ "TopLevelLayerSpec",
+ "TopLevelMixin",
+ "TopLevelParameter",
+ "TopLevelRepeatSpec",
+ "TopLevelSelectionParameter",
+ "TopLevelSpec",
+ "TopLevelUnitSpec",
+ "TopLevelVConcatSpec",
+ "TopoDataFormat",
+ "Transform",
+ "Type",
+ "TypeForShape",
+ "TypedFieldDef",
+ "UnitSpec",
+ "UnitSpecWithFrame",
+ "Url",
+ "UrlData",
+ "UrlValue",
+ "UtcMultiTimeUnit",
+ "UtcSingleTimeUnit",
+ "VConcatChart",
+ "VConcatSpecGenericSpec",
+ "ValueChannelMixin",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefGradientstringnull",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefTypeForShapestringnull",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefnumber",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefnumberArray",
+ "ValueDefWithConditionMarkPropFieldOrDatumDefstringnull",
+ "ValueDefWithConditionStringFieldDefText",
+ "ValueDefnumber",
+ "ValueDefnumberwidthheightExprRef",
+ "VariableParameter",
+ "Vector2DateTime",
+ "Vector2Vector2number",
+ "Vector2boolean",
+ "Vector2number",
+ "Vector2string",
+ "Vector3number",
+ "Vector7string",
+ "Vector10string",
+ "Vector12string",
+ "VegaLite",
+ "VegaLiteSchema",
+ "ViewBackground",
+ "ViewConfig",
+ "When",
+ "WindowEventType",
+ "WindowFieldDef",
+ "WindowOnlyOp",
+ "WindowTransform",
+ "X",
+ "X2Datum",
+ "X2Value",
+ "XDatum",
+ "XError",
+ "XError2",
+ "XError2Value",
+ "XErrorValue",
+ "XOffset",
+ "XOffsetDatum",
+ "XOffsetValue",
+ "XValue",
+ "Y",
+ "Y2Datum",
+ "Y2Value",
+ "YDatum",
+ "YError",
+ "YError2",
+ "YError2Value",
+ "YErrorValue",
+ "YOffset",
+ "YOffsetDatum",
+ "YOffsetValue",
+ "YValue",
+ "api",
+ "binding",
+ "binding_checkbox",
+ "binding_radio",
+ "binding_range",
+ "binding_select",
+ "channels",
+ "check_fields_and_encodings",
+ "compiler",
+ "concat",
+ "condition",
+ "core",
+ "data_transformers",
+ "datum",
+ "default_data_transformer",
+ "graticule",
+ "hconcat",
+ "layer",
+ "limit_rows",
+ "load_schema",
+ "mixins",
+ "param",
+ "renderers",
+ "repeat",
+ "sample",
+ "schema",
+ "selection",
+ "selection_interval",
+ "selection_multi",
+ "selection_point",
+ "selection_single",
+ "sequence",
+ "sphere",
+ "to_csv",
+ "to_json",
+ "to_values",
+ "topo_feature",
+ "value",
+ "vconcat",
+ "vegalite_compilers",
+ "when",
+ "with_property_setters",
+]
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index 27ab5541e..4813f1e4a 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -120,7 +120,7 @@ def api_functions() -> list[str]:
KEEP = set(alt.api.__all__) - set(alt.typing.__all__) # type: ignore[attr-defined]
return sorted(
name
- for name in iter_objects(alt.api, restrict_to_type=types.FunctionType) # type: ignore[attr-defined]
+ for name in iter_objects(alt.api, restrict_to_type=types.FunctionType)
if name in KEEP
)
diff --git a/tools/update_init_file.py b/tools/update_init_file.py
index 28b2266c5..55c41e96e 100644
--- a/tools/update_init_file.py
+++ b/tools/update_init_file.py
@@ -4,9 +4,11 @@
import typing as t
import typing_extensions as te
+from importlib import import_module as _import_module
+from importlib.util import find_spec as _find_spec
from inspect import getattr_static, ismodule
from pathlib import Path
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Iterable, Iterator
from tools.schemapi.utils import ruff_write_lint_format_str
@@ -36,7 +38,7 @@
te.TypeAliasType,
}
-EXCLUDE_MODULES: set[str] = {"altair.vegalite.v5.theme"}
+DYNAMIC_ALL: tuple[te.LiteralString, ...] = ("altair.vegalite.v5",)
def update__all__variable() -> None:
@@ -78,6 +80,10 @@ def update__all__variable() -> None:
# Format file content with ruff
ruff_write_lint_format_str(init_path, new_lines)
+ for source in DYNAMIC_ALL:
+ print(f"Updating dynamic all: {source!r}")
+ update_dynamic__all__(source)
+
def relevant_attributes(namespace: dict[str, t.Any], /) -> list[str]:
"""
@@ -131,11 +137,109 @@ def _is_relevant(attr: t.Any, name: str, /) -> bool:
# Only include modules which are part of Altair. This excludes built-in
# modules (they do not have a __file__ attribute), standard library,
# and third-party packages.
- is_altair = getattr_static(attr, "__file__", "").startswith(str(Path.cwd()))
- return is_altair and attr.__name__ not in EXCLUDE_MODULES
+ return getattr_static(attr, "__file__", "").startswith(str(Path.cwd()))
else:
return True
+def _retrieve_all(name: str, /) -> list[str]:
+ """Import `name` and return a defined ``__all__``."""
+ found = _import_module(name).__all__
+ if not found:
+ msg = (
+ f"Expected to find a populated `__all__` for {name!r},\n"
+ f"but got: {found!r}"
+ )
+ raise AttributeError(msg)
+ return found
+
+
+def normalize_source(src: str | Path, /) -> Path:
+ """
+ Return the ``Path`` representation of a module/package.
+
+ Returned unchanged if already a ``Path``.
+ """
+ if isinstance(src, str):
+ if src.startswith("altair."):
+ if (spec := _find_spec(src)) and (origin := spec.origin):
+ src = origin
+ else:
+ raise ModuleNotFoundError(src, spec)
+ return Path(src)
+ else:
+ return src
+
+
+def extract_lines(fp: Path, /) -> list[str]:
+ """Return all lines in ``fp`` with whitespace stripped."""
+ with Path(fp).open(encoding="utf-8") as f:
+ lines = f.readlines()
+ if not lines:
+ msg = f"Found no content when reading lines for:\n{lines!r}"
+ raise NotImplementedError(msg)
+ return [line.strip() for line in lines]
+
+
+def _normalize_import_lines(lines: Iterable[str]) -> Iterator[str]:
+ """
+ Collapses file content to contain one line per import source.
+
+ Preserves only lines **before** an existing ``__all__``.
+ """
+ it: Iterator[str] = iter(lines)
+ for line in it:
+ if line.endswith("("):
+ line = line.rstrip("( ")
+ for s_line in it:
+ if s_line.endswith(","):
+ line = f"{line} {s_line}"
+ elif s_line.endswith(")"):
+ break
+ else:
+ NotImplementedError(f"Unexpected line:\n{s_line!r}")
+ yield line.rstrip(",")
+ elif line.startswith("__all__"):
+ break
+ else:
+ yield line
+
+
+def process_lines(lines: Iterable[str], /) -> Iterator[str]:
+ """Normalize imports, follow ``*``(s), reconstruct `__all__``."""
+ _all: set[str] = set()
+ for line in _normalize_import_lines(lines):
+ if line.startswith("#") or line == "":
+ yield line
+ elif "import" in line:
+ origin_stmt, members = line.split(" import ", maxsplit=1)
+ if members == "*":
+ _, origin = origin_stmt.split("from ")
+ targets = _retrieve_all(origin)
+ else:
+ targets = members.split(", ")
+ _all.update(targets)
+ yield line
+ else:
+ msg = f"Unexpected line:\n{line!r}"
+ raise NotImplementedError(msg)
+ yield f"__all__ = {sorted(_all)}"
+
+
+def update_dynamic__all__(source: str | Path, /) -> None:
+ """
+ ## Relies on all `*` imports leading to an `__all__`.
+
+ Acceptable `source`:
+
+ "altair.package.subpackage.etc"
+ Path(...)
+
+ """
+ fp = normalize_source(source)
+ content = process_lines(extract_lines(fp))
+ ruff_write_lint_format_str(fp, content)
+
+
if __name__ == "__main__":
update__all__variable()
From c996c95ffedea2c79792817423dca42769624f77 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:03:55 +0100
Subject: [PATCH 07/44] refactor: `utils.theme` -> `vegalite.v5.theme`
---
altair/utils/theme.py | 54 -------------------------------------
altair/vegalite/v5/theme.py | 52 ++++++++++++++++++++++++++---------
2 files changed, 40 insertions(+), 66 deletions(-)
delete mode 100644 altair/utils/theme.py
diff --git a/altair/utils/theme.py b/altair/utils/theme.py
deleted file mode 100644
index 522c40164..000000000
--- a/altair/utils/theme.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""Utilities for registering and working with themes."""
-
-from __future__ import annotations
-
-import sys
-from typing import TYPE_CHECKING
-
-from altair.utils.plugin_registry import Plugin, PluginRegistry
-from altair.vegalite.v5.schema._config import ThemeConfig
-
-if sys.version_info >= (3, 11):
- from typing import LiteralString
-else:
- from typing_extensions import LiteralString
-
-if TYPE_CHECKING:
- from altair.utils.plugin_registry import PluginEnabler
- from altair.vegalite.v5.theme import AltairThemes, VegaThemes
-
-ThemeType = Plugin[ThemeConfig]
-
-# NOTE: Parameterising type vars, overriding `enable`
-
-
-# HACK: See for `LiteralString` requirement in `name`
-# https://github.com/vega/altair/pull/3526#discussion_r1743350127
-class ThemeRegistry(PluginRegistry[ThemeType, ThemeConfig]):
- def enable(
- self, name: LiteralString | AltairThemes | VegaThemes | None = None, **options
- ) -> PluginEnabler:
- """
- Enable a theme by name.
-
- This can be either called directly, or used as a context manager.
-
- Parameters
- ----------
- name : string (optional)
- The name of the theme to enable. If not specified, then use the
- current active name.
- **options :
- Any additional parameters will be passed to the theme as keyword
- arguments
-
- Returns
- -------
- PluginEnabler:
- An object that allows enable() to be used as a context manager
-
- Notes
- -----
- Default `vega` themes can be previewed at https://vega.github.io/vega-themes/
- """
- return super().enable(name, **options)
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index 2bfd236eb..db647a98c 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -2,22 +2,14 @@
from __future__ import annotations
-import sys
-from functools import wraps
-from typing import TYPE_CHECKING, Callable, Final, Literal, get_args
+from typing import TYPE_CHECKING, Any, Final, Literal, get_args
-from altair.utils.theme import ThemeRegistry
+from altair.utils.plugin_registry import Plugin, PluginRegistry
from altair.vegalite.v5.schema._config import ThemeConfig
from altair.vegalite.v5.schema._typing import VegaThemes
-if sys.version_info >= (3, 10):
- from typing import ParamSpec
-else:
- from typing_extensions import ParamSpec
-
-
if TYPE_CHECKING:
- from altair.utils.plugin_registry import Plugin
+ import sys
if sys.version_info >= (3, 11):
from typing import LiteralString
@@ -28,11 +20,47 @@
else:
from typing_extensions import TypeAlias
-P = ParamSpec("P")
+ from altair.utils.plugin_registry import PluginEnabler
+
+
AltairThemes: TypeAlias = Literal["default", "opaque"]
VEGA_THEMES: list[LiteralString] = list(get_args(VegaThemes))
+# HACK: See for `LiteralString` requirement in `name`
+# https://github.com/vega/altair/pull/3526#discussion_r1743350127
+class ThemeRegistry(PluginRegistry[Plugin[ThemeConfig], ThemeConfig]):
+ def enable(
+ self,
+ name: LiteralString | AltairThemes | VegaThemes | None = None,
+ **options: Any,
+ ) -> PluginEnabler:
+ """
+ Enable a theme by name.
+
+ This can be either called directly, or used as a context manager.
+
+ Parameters
+ ----------
+ name : string (optional)
+ The name of the theme to enable. If not specified, then use the
+ current active name.
+ **options :
+ Any additional parameters will be passed to the theme as keyword
+ arguments
+
+ Returns
+ -------
+ PluginEnabler:
+ An object that allows enable() to be used as a context manager
+
+ Notes
+ -----
+ Default `vega` themes can be previewed at https://vega.github.io/vega-themes/
+ """
+ return super().enable(name, **options)
+
+
class VegaTheme:
"""Implementation of a builtin vega theme."""
From c286c382474c9174eedc089602dd0f3b720e0a2c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:05:47 +0100
Subject: [PATCH 08/44] refactor: Remove `alt.typing.theme`
---
altair/typing/__init__.py | 5 -----
altair/typing/theme.py | 2 --
doc/user_guide/api.rst | 2 --
3 files changed, 9 deletions(-)
delete mode 100644 altair/typing/theme.py
diff --git a/altair/typing/__init__.py b/altair/typing/__init__.py
index 9ed3afdc2..cd8cb1489 100644
--- a/altair/typing/__init__.py
+++ b/altair/typing/__init__.py
@@ -46,14 +46,9 @@
"ChartType",
"EncodeKwds",
"Optional",
- "ThemeConfig",
"is_chart_type",
- "theme",
]
-# TODO: Remove `theme`, `ThemeConfig` imports
-from altair.typing import theme
-from altair.typing.theme import ThemeConfig
from altair.utils.schemapi import Optional
from altair.vegalite.v5.api import ChartType, is_chart_type
from altair.vegalite.v5.schema.channels import (
diff --git a/altair/typing/theme.py b/altair/typing/theme.py
deleted file mode 100644
index c15049fe0..000000000
--- a/altair/typing/theme.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# TODO: Remove module
-from altair.vegalite.v5.schema._config import * # noqa: F403
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index 9000bce99..eaa9cb602 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -689,7 +689,5 @@ Typing
ChartType
EncodeKwds
Optional
- ThemeConfig
is_chart_type
- theme
From ef9b846ba774019fb44064129ebb424b6a59b798 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:07:49 +0100
Subject: [PATCH 09/44] refactor: Adds `alt.theme` implementation
---
altair/__init__.py | 5 +-
altair/theme.py | 265 ++++++++++++++++++++++++++++++++
altair/vegalite/v5/api.py | 12 +-
altair/vegalite/v5/theme.py | 72 ---------
tests/vegalite/v5/test_api.py | 9 +-
tests/vegalite/v5/test_theme.py | 24 ++-
tools/generate_api_docs.py | 2 +-
7 files changed, 287 insertions(+), 102 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index 436a4a976..0c9686d07 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -617,7 +617,6 @@
"mixins",
"param",
"parse_shorthand",
- "register_theme",
"renderers",
"repeat",
"sample",
@@ -626,7 +625,7 @@
"selection_point",
"sequence",
"sphere",
- "themes",
+ "theme",
"to_csv",
"to_json",
"to_values",
@@ -652,7 +651,7 @@ def __dir__():
from altair.jupyter import JupyterChart
from altair.expr import expr
from altair.utils import AltairDeprecationWarning, parse_shorthand, Undefined
-from altair import typing
+from altair import typing, theme
def load_ipython_extension(ipython):
diff --git a/altair/theme.py b/altair/theme.py
index 9d48db4f9..b19327b55 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -1 +1,266 @@
+"""Customizing chart configuration defaults."""
+
from __future__ import annotations
+
+from functools import wraps as _wraps
+from typing import TYPE_CHECKING
+
+from altair.vegalite.v5.schema._config import (
+ AreaConfigKwds,
+ AutoSizeParamsKwds,
+ AxisConfigKwds,
+ AxisResolveMapKwds,
+ BarConfigKwds,
+ BindCheckboxKwds,
+ BindDirectKwds,
+ BindInputKwds,
+ BindRadioSelectKwds,
+ BindRangeKwds,
+ BoxPlotConfigKwds,
+ BrushConfigKwds,
+ CompositionConfigKwds,
+ ConfigKwds,
+ DateTimeKwds,
+ DerivedStreamKwds,
+ ErrorBandConfigKwds,
+ ErrorBarConfigKwds,
+ FeatureGeometryGeoJsonPropertiesKwds,
+ FormatConfigKwds,
+ GeoJsonFeatureCollectionKwds,
+ GeoJsonFeatureKwds,
+ GeometryCollectionKwds,
+ GradientStopKwds,
+ HeaderConfigKwds,
+ IntervalSelectionConfigKwds,
+ IntervalSelectionConfigWithoutTypeKwds,
+ LegendConfigKwds,
+ LegendResolveMapKwds,
+ LegendStreamBindingKwds,
+ LinearGradientKwds,
+ LineConfigKwds,
+ LineStringKwds,
+ LocaleKwds,
+ MarkConfigKwds,
+ MergedStreamKwds,
+ MultiLineStringKwds,
+ MultiPointKwds,
+ MultiPolygonKwds,
+ NumberLocaleKwds,
+ OverlayMarkDefKwds,
+ PaddingKwds,
+ PointKwds,
+ PointSelectionConfigKwds,
+ PointSelectionConfigWithoutTypeKwds,
+ PolygonKwds,
+ ProjectionConfigKwds,
+ ProjectionKwds,
+ RadialGradientKwds,
+ RangeConfigKwds,
+ RectConfigKwds,
+ ResolveKwds,
+ RowColKwds,
+ ScaleConfigKwds,
+ ScaleInvalidDataConfigKwds,
+ ScaleResolveMapKwds,
+ SelectionConfigKwds,
+ StepKwds,
+ StyleConfigIndexKwds,
+ ThemeConfig,
+ TickConfigKwds,
+ TimeIntervalStepKwds,
+ TimeLocaleKwds,
+ TitleConfigKwds,
+ TitleParamsKwds,
+ TooltipContentKwds,
+ TopLevelSelectionParameterKwds,
+ VariableParameterKwds,
+ ViewBackgroundKwds,
+ ViewConfigKwds,
+)
+from altair.vegalite.v5.theme import themes
+
+if TYPE_CHECKING:
+ import sys
+ from typing import Callable
+
+ if sys.version_info >= (3, 11):
+ from typing import LiteralString
+ else:
+ from typing_extensions import LiteralString
+ if sys.version_info >= (3, 10):
+ from typing import ParamSpec
+ else:
+ from typing_extensions import ParamSpec
+
+ from altair.utils.plugin_registry import Plugin
+
+ P = ParamSpec("P")
+
+__all__ = [
+ "AreaConfigKwds",
+ "AutoSizeParamsKwds",
+ "AxisConfigKwds",
+ "AxisResolveMapKwds",
+ "BarConfigKwds",
+ "BindCheckboxKwds",
+ "BindDirectKwds",
+ "BindInputKwds",
+ "BindRadioSelectKwds",
+ "BindRangeKwds",
+ "BoxPlotConfigKwds",
+ "BrushConfigKwds",
+ "CompositionConfigKwds",
+ "ConfigKwds",
+ "DateTimeKwds",
+ "DerivedStreamKwds",
+ "ErrorBandConfigKwds",
+ "ErrorBarConfigKwds",
+ "FeatureGeometryGeoJsonPropertiesKwds",
+ "FormatConfigKwds",
+ "GeoJsonFeatureCollectionKwds",
+ "GeoJsonFeatureKwds",
+ "GeometryCollectionKwds",
+ "GradientStopKwds",
+ "HeaderConfigKwds",
+ "IntervalSelectionConfigKwds",
+ "IntervalSelectionConfigWithoutTypeKwds",
+ "LegendConfigKwds",
+ "LegendResolveMapKwds",
+ "LegendStreamBindingKwds",
+ "LineConfigKwds",
+ "LineStringKwds",
+ "LinearGradientKwds",
+ "LocaleKwds",
+ "MarkConfigKwds",
+ "MergedStreamKwds",
+ "MultiLineStringKwds",
+ "MultiPointKwds",
+ "MultiPolygonKwds",
+ "NumberLocaleKwds",
+ "OverlayMarkDefKwds",
+ "PaddingKwds",
+ "PointKwds",
+ "PointSelectionConfigKwds",
+ "PointSelectionConfigWithoutTypeKwds",
+ "PolygonKwds",
+ "ProjectionConfigKwds",
+ "ProjectionKwds",
+ "RadialGradientKwds",
+ "RangeConfigKwds",
+ "RectConfigKwds",
+ "ResolveKwds",
+ "RowColKwds",
+ "ScaleConfigKwds",
+ "ScaleInvalidDataConfigKwds",
+ "ScaleResolveMapKwds",
+ "SelectionConfigKwds",
+ "StepKwds",
+ "StyleConfigIndexKwds",
+ "ThemeConfig",
+ "ThemeConfig",
+ "TickConfigKwds",
+ "TimeIntervalStepKwds",
+ "TimeLocaleKwds",
+ "TitleConfigKwds",
+ "TitleParamsKwds",
+ "TooltipContentKwds",
+ "TopLevelSelectionParameterKwds",
+ "VariableParameterKwds",
+ "ViewBackgroundKwds",
+ "ViewConfigKwds",
+ "enable",
+ "get",
+ "names",
+ "register",
+ "themes",
+ "unregister",
+]
+
+
+def register(
+ name: LiteralString, *, enable: bool
+) -> Callable[[Plugin[ThemeConfig]], Plugin[ThemeConfig]]:
+ """
+ Decorator for registering a theme function.
+
+ Parameters
+ ----------
+ name
+ Unique name assigned in ``alt.theme.themes``.
+ enable
+ Auto-enable the wrapped theme.
+
+ Examples
+ --------
+ Register and enable a theme::
+
+ import altair as alt
+ from altair import theme
+
+
+ @theme.register("param_font_size", enable=True)
+ def custom_theme() -> theme.ThemeConfig:
+ sizes = 12, 14, 16, 18, 20
+ return {
+ "autosize": {"contains": "content", "resize": True},
+ "background": "#F3F2F1",
+ "config": {
+ "axisX": {"labelFontSize": sizes[1], "titleFontSize": sizes[1]},
+ "axisY": {"labelFontSize": sizes[1], "titleFontSize": sizes[1]},
+ "font": "'Lato', 'Segoe UI', Tahoma, Verdana, sans-serif",
+ "headerColumn": {"labelFontSize": sizes[1]},
+ "headerFacet": {"labelFontSize": sizes[1]},
+ "headerRow": {"labelFontSize": sizes[1]},
+ "legend": {"labelFontSize": sizes[0], "titleFontSize": sizes[1]},
+ "text": {"fontSize": sizes[0]},
+ "title": {"fontSize": sizes[-1]},
+ },
+ "height": {"step": 28},
+ "width": 350,
+ }
+
+ Until another theme has been enabled, all charts will use defaults set in ``custom_theme``::
+
+ from vega_datasets import data
+
+ source = data.stocks()
+ lines = (
+ alt.Chart(source, title=alt.Title("Stocks"))
+ .mark_line()
+ .encode(x="date:T", y="price:Q", color="symbol:N")
+ )
+ lines.interactive(bind_y=False)
+
+ """
+
+ # HACK: See for `LiteralString` requirement in `name`
+ # https://github.com/vega/altair/pull/3526#discussion_r1743350127
+ def decorate(func: Plugin[ThemeConfig], /) -> Plugin[ThemeConfig]:
+ themes.register(name, func)
+ if enable:
+ themes.enable(name)
+
+ @_wraps(func)
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> ThemeConfig:
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ return decorate
+
+
+def unregister(name: LiteralString) -> Plugin[ThemeConfig] | None:
+ """
+ Remove and return a previously registered theme.
+
+ Parameters
+ ----------
+ name
+ Unique name assigned in ``alt.theme.themes``.
+ """
+ return themes.register(name, None)
+
+
+enable = themes.enable
+get = themes.get
+names = themes.names
diff --git a/altair/vegalite/v5/api.py b/altair/vegalite/v5/api.py
index 852252c65..7ad31f7c5 100644
--- a/altair/vegalite/v5/api.py
+++ b/altair/vegalite/v5/api.py
@@ -15,7 +15,7 @@
import jsonschema
import narwhals.stable.v1 as nw
-from altair import utils
+from altair import theme, utils
from altair.expr import core as _expr_core
from altair.utils import Optional, SchemaBase, Undefined
from altair.utils._vegafusion_data import (
@@ -32,9 +32,6 @@
from .schema import SCHEMA_URL, channels, core, mixins
from .schema._typing import Map
-# NOTE: Relative themes import
-from .theme import themes
-
if sys.version_info >= (3, 14):
from typing import TypedDict
else:
@@ -1902,11 +1899,8 @@ def to_dict( # noqa: C901
if "$schema" not in vegalite_spec:
vegalite_spec["$schema"] = SCHEMA_URL
- # apply theme from theme registry
-
- # NOTE: Single use of `themes`
- if theme := themes.get():
- vegalite_spec = utils.update_nested(theme(), vegalite_spec, copy=True)
+ if func := theme.get():
+ vegalite_spec = utils.update_nested(func(), vegalite_spec, copy=True)
else:
msg = (
f"Expected a theme to be set but got {None!r}.\n"
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index db647a98c..1e22a6d45 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -104,75 +104,3 @@ def __repr__(self) -> str:
themes.register(theme, VegaTheme(theme))
themes.enable("default")
-
-
-# HACK: See for `LiteralString` requirement in `name`
-# https://github.com/vega/altair/pull/3526#discussion_r1743350127
-def register_theme(
- name: LiteralString, *, enable: bool
-) -> Callable[[Plugin[ThemeConfig]], Plugin[ThemeConfig]]:
- """
- Decorator for registering a theme function.
-
- Parameters
- ----------
- name
- Unique name assigned in ``alt.themes``.
- enable
- Auto-enable the wrapped theme.
-
- Examples
- --------
- Register and enable a theme::
-
- import altair as alt
- from altair.typing import ThemeConfig
-
-
- @alt.register_theme("param_font_size", enable=True)
- def custom_theme() -> ThemeConfig:
- sizes = 12, 14, 16, 18, 20
- return {
- "autosize": {"contains": "content", "resize": True},
- "background": "#F3F2F1",
- "config": {
- "axisX": {"labelFontSize": sizes[1], "titleFontSize": sizes[1]},
- "axisY": {"labelFontSize": sizes[1], "titleFontSize": sizes[1]},
- "font": "'Lato', 'Segoe UI', Tahoma, Verdana, sans-serif",
- "headerColumn": {"labelFontSize": sizes[1]},
- "headerFacet": {"labelFontSize": sizes[1]},
- "headerRow": {"labelFontSize": sizes[1]},
- "legend": {"labelFontSize": sizes[0], "titleFontSize": sizes[1]},
- "text": {"fontSize": sizes[0]},
- "title": {"fontSize": sizes[-1]},
- },
- "height": {"step": 28},
- "width": 350,
- }
-
- Until another theme has been enabled, all charts will use defaults set in ``custom_theme``::
-
- from vega_datasets import data
-
- source = data.stocks()
- lines = (
- alt.Chart(source, title=alt.Title("Stocks"))
- .mark_line()
- .encode(x="date:T", y="price:Q", color="symbol:N")
- )
- lines.interactive(bind_y=False)
-
- """
-
- def decorate(func: Plugin[ThemeConfig], /) -> Plugin[ThemeConfig]:
- themes.register(name, func)
- if enable:
- themes.enable(name)
-
- @wraps(func)
- def wrapper(*args: P.args, **kwargs: P.kwargs) -> ThemeConfig:
- return func(*args, **kwargs)
-
- return wrapper
-
- return decorate
diff --git a/tests/vegalite/v5/test_api.py b/tests/vegalite/v5/test_api.py
index e73b8797e..694ce4282 100644
--- a/tests/vegalite/v5/test_api.py
+++ b/tests/vegalite/v5/test_api.py
@@ -1284,21 +1284,22 @@ def test_LookupData():
def test_themes():
+ from altair import theme
+
chart = alt.Chart("foo.txt").mark_point()
- # NOTE: Only other tests using `alt.themes`
- with alt.themes.enable("default"):
+ with theme.enable("default"):
assert chart.to_dict()["config"] == {
"view": {"continuousWidth": 300, "continuousHeight": 300}
}
- with alt.themes.enable("opaque"):
+ with theme.enable("opaque"):
assert chart.to_dict()["config"] == {
"background": "white",
"view": {"continuousWidth": 300, "continuousHeight": 300},
}
- with alt.themes.enable("none"):
+ with theme.enable("none"):
assert "config" not in chart.to_dict()
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 6cf2bb221..ea480d8a8 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -5,12 +5,10 @@
import pytest
import altair.vegalite.v5 as alt
-
-# NOTE: Imports assuming existing structure
-from altair.typing import ThemeConfig
-from altair.vegalite.v5.schema._config import ConfigKwds
+from altair import theme
+from altair.theme import ConfigKwds, ThemeConfig
from altair.vegalite.v5.schema._typing import is_color_hex
-from altair.vegalite.v5.theme import VEGA_THEMES, register_theme, themes
+from altair.vegalite.v5.theme import VEGA_THEMES
if TYPE_CHECKING:
import sys
@@ -27,23 +25,22 @@ def chart() -> alt.Chart:
def test_vega_themes(chart) -> None:
- for theme in VEGA_THEMES:
- # NOTE: Assuming this is available in `alt.___`
- with alt.themes.enable(theme):
+ for theme_name in VEGA_THEMES:
+ with theme.enable(theme_name):
dct = chart.to_dict()
- assert dct["usermeta"] == {"embedOptions": {"theme": theme}}
+ assert dct["usermeta"] == {"embedOptions": {"theme": theme_name}}
assert dct["config"] == {
"view": {"continuousWidth": 300, "continuousHeight": 300}
}
def test_register_theme_decorator() -> None:
- @register_theme("unique name", enable=True)
+ @theme.register("unique name", enable=True)
def custom_theme() -> ThemeConfig:
return {"height": 400, "width": 700}
- assert themes.active == "unique name"
- registered = themes.get()
+ assert theme.themes.active == "unique name"
+ registered = theme.themes.get()
assert registered is not None
assert registered() == {"height": 400, "width": 700} == custom_theme()
@@ -985,5 +982,6 @@ def test_theme_config(theme_func: Callable[[], ThemeConfig], chart) -> None:
See ``(test_vega_themes|test_register_theme_decorator)`` for comprehensive suite.
"""
name = cast("LiteralString", theme_func.__qualname__)
- register_theme(name, enable=True)
+ theme.register(name, enable=True)(theme_func)
assert chart.to_dict(validate=True)
+ assert theme.get() == theme_func
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index 4813f1e4a..98e8ba3ca 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -136,7 +136,7 @@ def type_hints() -> list[str]:
# TODO: Currently only the `TypedDict`(s) are visible (only via `alt.typing.___`)
# Related: https://github.com/vega/altair/issues/3607
-def theme() -> list[str]: ...
+def theme() -> list[str]: ... # type: ignore[empty-body]
def lowlevel_wrappers() -> list[str]:
From e8d3062dee8fe8a6ea7f852521a1848903319d17 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:16:08 +0100
Subject: [PATCH 10/44] test: Fix `test_common`
Was dependent on `v5` having the registry exported in `__init__`
---
tests/vegalite/test_common.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/tests/vegalite/test_common.py b/tests/vegalite/test_common.py
index 1e3e83daa..69c173b97 100644
--- a/tests/vegalite/test_common.py
+++ b/tests/vegalite/test_common.py
@@ -20,7 +20,10 @@ def basic_spec():
def make_final_spec(alt, basic_spec):
- theme = alt.themes.get()
+ from altair.theme import themes
+
+ theme = themes.get()
+ assert theme
spec = theme()
spec.update(basic_spec)
return spec
@@ -67,10 +70,12 @@ def test_basic_chart_from_dict(alt, basic_spec):
@pytest.mark.parametrize("alt", [v5])
def test_theme_enable(alt, basic_spec):
- active_theme = alt.themes.active
+ from altair.theme import themes
+
+ active_theme = themes.active
try:
- alt.themes.enable("none")
+ themes.enable("none")
chart = alt.Chart.from_dict(basic_spec)
dct = chart.to_dict()
@@ -83,7 +88,7 @@ def test_theme_enable(alt, basic_spec):
assert dct == basic_spec
finally:
# reset the theme to its initial value
- alt.themes.enable(active_theme)
+ themes.enable(active_theme) # pyright: ignore[reportArgumentType]
@pytest.mark.parametrize("alt", [v5])
From f7fe38bd70fadbb4309320567263fd9762bf71e0 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:18:15 +0100
Subject: [PATCH 11/44] chore(typing): Remove now fix ignore comments
https://github.com/vega/altair/pull/3618#discussion_r1780006759
---
tools/generate_api_docs.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index 98e8ba3ca..8a68e1359 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -108,16 +108,16 @@ def iter_objects(
def toplevel_charts() -> list[str]:
- return sorted(iter_objects(alt.api, restrict_to_subclass=alt.TopLevelMixin)) # type: ignore[attr-defined]
+ return sorted(iter_objects(alt.api, restrict_to_subclass=alt.TopLevelMixin))
def encoding_wrappers() -> list[str]:
- return sorted(iter_objects(alt.channels, restrict_to_subclass=alt.SchemaBase)) # type: ignore[attr-defined]
+ return sorted(iter_objects(alt.channels, restrict_to_subclass=alt.SchemaBase))
def api_functions() -> list[str]:
# Exclude `typing` functions/SpecialForm(s)
- KEEP = set(alt.api.__all__) - set(alt.typing.__all__) # type: ignore[attr-defined]
+ KEEP = set(alt.api.__all__) - set(alt.typing.__all__)
return sorted(
name
for name in iter_objects(alt.api, restrict_to_type=types.FunctionType)
@@ -140,7 +140,7 @@ def theme() -> list[str]: ... # type: ignore[empty-body]
def lowlevel_wrappers() -> list[str]:
- objects = sorted(iter_objects(alt.schema.core, restrict_to_subclass=alt.SchemaBase)) # type: ignore[attr-defined]
+ objects = sorted(iter_objects(alt.schema.core, restrict_to_subclass=alt.SchemaBase))
# The names of these two classes are also used for classes in alt.channels. Due to
# how imports are set up, these channel classes overwrite the two low-level classes
# in the top-level Altair namespace. Therefore, they cannot be imported as e.g.
From 0a1c8d306a2db20c7f2e4ffd100b97d4c1707a5d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 18:20:55 +0100
Subject: [PATCH 12/44] fix(typing): Use a bounded `TypeVar` to support
`list[str]`
---
tools/generate_schema_wrapper.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 840b43108..ca2e0db2c 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -55,7 +55,7 @@
if TYPE_CHECKING:
from tools.schemapi.codegen import ArgInfo, AttrGetter
-T = TypeVar("T", str, Iterable[str])
+T = TypeVar("T", bound="str | Iterable[str]")
SCHEMA_VERSION: Final = "v5.20.1"
From 05eca89b4112de46386ac35154ad5f24b192305f Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 19:42:44 +0100
Subject: [PATCH 13/44] refactor: Add deprecation handling
`alt.__init__.__getattr__`
---
altair/__init__.py | 21 +++++++++++++++++++++
tests/utils/test_deprecation.py | 21 +++++++++++++++++++++
2 files changed, 42 insertions(+)
diff --git a/altair/__init__.py b/altair/__init__.py
index 0c9686d07..566d76c22 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
# ruff: noqa
__version__ = "5.5.0dev"
@@ -652,9 +654,28 @@ def __dir__():
from altair.expr import expr
from altair.utils import AltairDeprecationWarning, parse_shorthand, Undefined
from altair import typing, theme
+from typing import Any as _Any
def load_ipython_extension(ipython):
from altair._magics import vegalite
ipython.register_magic_function(vegalite, "cell")
+
+
+def __getattr__(name: str) -> _Any:
+ from altair.utils.deprecation import deprecated_warn
+
+ if name == "themes":
+ deprecated_warn(
+ "Most of the `ThemeRegistry` API is accessible via `altair.theme`.\n"
+ "See the updated User Guide for further details:\n"
+ "https://altair-viz.github.io/user_guide/customization.html#chart-themes",
+ version="5.5.0",
+ alternative="altair.theme.themes",
+ stacklevel=3,
+ )
+ return theme.themes
+ else:
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py
index 3970f4794..471071cc3 100644
--- a/tests/utils/test_deprecation.py
+++ b/tests/utils/test_deprecation.py
@@ -1,3 +1,4 @@
+# ruff: noqa: B018
import re
import pytest
@@ -38,3 +39,23 @@ def test_deprecation_warn():
match=re.compile(r"altair=3321.+this code path is a noop", flags=re.DOTALL),
):
deprecated_warn("this code path is a noop", version="3321", stacklevel=1)
+
+
+def test_deprecated_import():
+ from warnings import catch_warnings, filterwarnings
+
+ import altair as alt
+
+ pattern = re.compile(
+ r"altair=5\.5\.0.+\.theme\.themes instead.+user.guide",
+ flags=re.DOTALL | re.IGNORECASE,
+ )
+ with pytest.warns(AltairDeprecationWarning, match=pattern):
+ alt.themes
+
+ with pytest.warns(AltairDeprecationWarning, match=pattern):
+ from altair import themes # noqa: F401
+
+ with catch_warnings():
+ filterwarnings("ignore", category=AltairDeprecationWarning)
+ assert alt.themes == alt.theme.themes
From 217d7fb181275999ae3f95a6215a6aeb53825353 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 20:51:46 +0100
Subject: [PATCH 14/44] docs: Adds `alt.theme` to API Reference
https://github.com/vega/altair/pull/3618#discussion_r1780130320
---
doc/user_guide/api.rst | 85 ++++++++++++++++++++++++++++++++++++++
tools/generate_api_docs.py | 19 +++++++--
2 files changed, 101 insertions(+), 3 deletions(-)
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index eaa9cb602..d8baa092b 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -164,6 +164,91 @@ API Functions
vconcat
when
+Theme
+-----
+.. currentmodule:: altair.theme
+
+.. autosummary::
+ :toctree: generated/theme/
+ :nosignatures:
+
+ ThemeConfig
+ enable
+ get
+ names
+ register
+ themes
+ unregister
+ AreaConfigKwds
+ AutoSizeParamsKwds
+ AxisConfigKwds
+ AxisResolveMapKwds
+ BarConfigKwds
+ BindCheckboxKwds
+ BindDirectKwds
+ BindInputKwds
+ BindRadioSelectKwds
+ BindRangeKwds
+ BoxPlotConfigKwds
+ BrushConfigKwds
+ CompositionConfigKwds
+ ConfigKwds
+ DateTimeKwds
+ DerivedStreamKwds
+ ErrorBandConfigKwds
+ ErrorBarConfigKwds
+ FeatureGeometryGeoJsonPropertiesKwds
+ FormatConfigKwds
+ GeoJsonFeatureCollectionKwds
+ GeoJsonFeatureKwds
+ GeometryCollectionKwds
+ GradientStopKwds
+ HeaderConfigKwds
+ IntervalSelectionConfigKwds
+ IntervalSelectionConfigWithoutTypeKwds
+ LegendConfigKwds
+ LegendResolveMapKwds
+ LegendStreamBindingKwds
+ LineConfigKwds
+ LineStringKwds
+ LinearGradientKwds
+ LocaleKwds
+ MarkConfigKwds
+ MergedStreamKwds
+ MultiLineStringKwds
+ MultiPointKwds
+ MultiPolygonKwds
+ NumberLocaleKwds
+ OverlayMarkDefKwds
+ PaddingKwds
+ PointKwds
+ PointSelectionConfigKwds
+ PointSelectionConfigWithoutTypeKwds
+ PolygonKwds
+ ProjectionConfigKwds
+ ProjectionKwds
+ RadialGradientKwds
+ RangeConfigKwds
+ RectConfigKwds
+ ResolveKwds
+ RowColKwds
+ ScaleConfigKwds
+ ScaleInvalidDataConfigKwds
+ ScaleResolveMapKwds
+ SelectionConfigKwds
+ StepKwds
+ StyleConfigIndexKwds
+ TickConfigKwds
+ TimeIntervalStepKwds
+ TimeLocaleKwds
+ TitleConfigKwds
+ TitleParamsKwds
+ TooltipContentKwds
+ TopLevelSelectionParameterKwds
+ VariableParameterKwds
+ ViewBackgroundKwds
+ ViewConfigKwds
+
Low-Level Schema Wrappers
-------------------------
.. currentmodule:: altair
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index 8a68e1359..c4cda1639 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -53,6 +53,16 @@
{api_functions}
+Theme
+-----
+.. currentmodule:: altair.theme
+
+.. autosummary::
+ :toctree: generated/theme/
+ :nosignatures:
+
+ {theme_objects}
+
Low-Level Schema Wrappers
-------------------------
.. currentmodule:: altair
@@ -134,9 +144,11 @@ def type_hints() -> list[str]:
return sorted(s for s in iter_objects(alt.typing) if s in alt.typing.__all__)
-# TODO: Currently only the `TypedDict`(s) are visible (only via `alt.typing.___`)
-# Related: https://github.com/vega/altair/issues/3607
-def theme() -> list[str]: ... # type: ignore[empty-body]
+def theme() -> list[str]:
+ return sorted(
+ sorted(s for s in iter_objects(alt.theme) if s in alt.theme.__all__),
+ key=lambda s: s.endswith("Kwds"),
+ )
def lowlevel_wrappers() -> list[str]:
@@ -161,6 +173,7 @@ def write_api_file() -> None:
lowlevel_wrappers=sep.join(lowlevel_wrappers()),
api_classes=sep.join(api_classes()),
typing_objects=sep.join(type_hints()),
+ theme_objects=sep.join(theme()),
),
encoding="utf-8",
)
From 5078f889acd9afb33e9377d1b2df2388e2d69003 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 22:08:26 +0100
Subject: [PATCH 15/44] docs: Add missing `altair` qualifier for `TypedDict`(s)
https://www.sphinx-doc.org/en/master/usage/domains/python.html#cross-referencing-python-objects
---
altair/vegalite/v5/schema/_config.py | 134 +++++++++++++--------------
tools/generate_schema_wrapper.py | 2 +-
2 files changed, 68 insertions(+), 68 deletions(-)
diff --git a/altair/vegalite/v5/schema/_config.py b/altair/vegalite/v5/schema/_config.py
index 80864718d..7ed4ebe32 100644
--- a/altair/vegalite/v5/schema/_config.py
+++ b/altair/vegalite/v5/schema/_config.py
@@ -93,7 +93,7 @@
class AreaConfigKwds(TypedDict, total=False):
"""
- :class:`AreaConfig` ``TypedDict`` wrapper.
+ :class:`altair.AreaConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -567,7 +567,7 @@ class AreaConfigKwds(TypedDict, total=False):
class AutoSizeParamsKwds(TypedDict, total=False):
"""
- :class:`AutoSizeParams` ``TypedDict`` wrapper.
+ :class:`altair.AutoSizeParams` ``TypedDict`` wrapper.
Parameters
----------
@@ -601,7 +601,7 @@ class AutoSizeParamsKwds(TypedDict, total=False):
class AxisConfigKwds(TypedDict, total=False):
"""
- :class:`AxisConfig` ``TypedDict`` wrapper.
+ :class:`altair.AxisConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -1057,7 +1057,7 @@ class AxisConfigKwds(TypedDict, total=False):
class AxisResolveMapKwds(TypedDict, total=False):
"""
- :class:`AxisResolveMap` ``TypedDict`` wrapper.
+ :class:`altair.AxisResolveMap` ``TypedDict`` wrapper.
Parameters
----------
@@ -1073,7 +1073,7 @@ class AxisResolveMapKwds(TypedDict, total=False):
class BarConfigKwds(TypedDict, total=False):
"""
- :class:`BarConfig` ``TypedDict`` wrapper.
+ :class:`altair.BarConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -1544,7 +1544,7 @@ class BarConfigKwds(TypedDict, total=False):
class BindCheckboxKwds(TypedDict, total=False):
"""
- :class:`BindCheckbox` ``TypedDict`` wrapper.
+ :class:`altair.BindCheckbox` ``TypedDict`` wrapper.
Parameters
----------
@@ -1570,7 +1570,7 @@ class BindCheckboxKwds(TypedDict, total=False):
class BindDirectKwds(TypedDict, total=False):
"""
- :class:`BindDirect` ``TypedDict`` wrapper.
+ :class:`altair.BindDirect` ``TypedDict`` wrapper.
Parameters
----------
@@ -1596,7 +1596,7 @@ class BindDirectKwds(TypedDict, total=False):
class BindInputKwds(TypedDict, total=False):
"""
- :class:`BindInput` ``TypedDict`` wrapper.
+ :class:`altair.BindInput` ``TypedDict`` wrapper.
Parameters
----------
@@ -1632,7 +1632,7 @@ class BindInputKwds(TypedDict, total=False):
class BindRadioSelectKwds(TypedDict, total=False):
"""
- :class:`BindRadioSelect` ``TypedDict`` wrapper.
+ :class:`altair.BindRadioSelect` ``TypedDict`` wrapper.
Parameters
----------
@@ -1665,7 +1665,7 @@ class BindRadioSelectKwds(TypedDict, total=False):
class BindRangeKwds(TypedDict, total=False):
"""
- :class:`BindRange` ``TypedDict`` wrapper.
+ :class:`altair.BindRange` ``TypedDict`` wrapper.
Parameters
----------
@@ -1703,7 +1703,7 @@ class BindRangeKwds(TypedDict, total=False):
class BoxPlotConfigKwds(TypedDict, total=False):
"""
- :class:`BoxPlotConfig` ``TypedDict`` wrapper.
+ :class:`altair.BoxPlotConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -1783,7 +1783,7 @@ class BoxPlotConfigKwds(TypedDict, total=False):
class BrushConfigKwds(TypedDict, total=False):
"""
- :class:`BrushConfig` ``TypedDict`` wrapper.
+ :class:`altair.BrushConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -1825,7 +1825,7 @@ class BrushConfigKwds(TypedDict, total=False):
class CompositionConfigKwds(TypedDict, total=False):
"""
- :class:`CompositionConfig` ``TypedDict`` wrapper.
+ :class:`altair.CompositionConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -1858,7 +1858,7 @@ class CompositionConfigKwds(TypedDict, total=False):
class ConfigKwds(TypedDict, total=False):
"""
- :class:`Config` ``TypedDict`` wrapper.
+ :class:`altair.Config` ``TypedDict`` wrapper.
Parameters
----------
@@ -2207,7 +2207,7 @@ class ConfigKwds(TypedDict, total=False):
class DateTimeKwds(TypedDict, total=False):
"""
- :class:`DateTime` ``TypedDict`` wrapper.
+ :class:`altair.DateTime` ``TypedDict`` wrapper.
Parameters
----------
@@ -2255,7 +2255,7 @@ class DateTimeKwds(TypedDict, total=False):
class DerivedStreamKwds(TypedDict, total=False):
"""
- :class:`DerivedStream` ``TypedDict`` wrapper.
+ :class:`altair.DerivedStream` ``TypedDict`` wrapper.
Parameters
----------
@@ -2289,7 +2289,7 @@ class DerivedStreamKwds(TypedDict, total=False):
class ErrorBandConfigKwds(TypedDict, total=False):
"""
- :class:`ErrorBandConfig` ``TypedDict`` wrapper.
+ :class:`altair.ErrorBandConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -2359,7 +2359,7 @@ class ErrorBandConfigKwds(TypedDict, total=False):
class ErrorBarConfigKwds(TypedDict, total=False):
"""
- :class:`ErrorBarConfig` ``TypedDict`` wrapper.
+ :class:`altair.ErrorBarConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -2409,7 +2409,7 @@ class ErrorBarConfigKwds(TypedDict, total=False):
class FeatureGeometryGeoJsonPropertiesKwds(TypedDict, total=False):
"""
- :class:`FeatureGeometryGeoJsonProperties` ``TypedDict`` wrapper.
+ :class:`altair.FeatureGeometryGeoJsonProperties` ``TypedDict`` wrapper.
Parameters
----------
@@ -2444,7 +2444,7 @@ class FeatureGeometryGeoJsonPropertiesKwds(TypedDict, total=False):
class FormatConfigKwds(TypedDict, total=False):
"""
- :class:`FormatConfig` ``TypedDict`` wrapper.
+ :class:`altair.FormatConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -2513,7 +2513,7 @@ class FormatConfigKwds(TypedDict, total=False):
class GeoJsonFeatureKwds(TypedDict, total=False):
"""
- :class:`GeoJsonFeature` ``TypedDict`` wrapper.
+ :class:`altair.GeoJsonFeature` ``TypedDict`` wrapper.
Parameters
----------
@@ -2548,7 +2548,7 @@ class GeoJsonFeatureKwds(TypedDict, total=False):
class GeoJsonFeatureCollectionKwds(TypedDict, total=False):
"""
- :class:`GeoJsonFeatureCollection` ``TypedDict`` wrapper.
+ :class:`altair.GeoJsonFeatureCollection` ``TypedDict`` wrapper.
Parameters
----------
@@ -2568,7 +2568,7 @@ class GeoJsonFeatureCollectionKwds(TypedDict, total=False):
class GeometryCollectionKwds(TypedDict, total=False):
"""
- :class:`GeometryCollection` ``TypedDict`` wrapper.
+ :class:`altair.GeometryCollection` ``TypedDict`` wrapper.
Parameters
----------
@@ -2596,7 +2596,7 @@ class GeometryCollectionKwds(TypedDict, total=False):
class GradientStopKwds(TypedDict, total=False):
"""
- :class:`GradientStop` ``TypedDict`` wrapper.
+ :class:`altair.GradientStop` ``TypedDict`` wrapper.
Parameters
----------
@@ -2612,7 +2612,7 @@ class GradientStopKwds(TypedDict, total=False):
class HeaderConfigKwds(TypedDict, total=False):
"""
- :class:`HeaderConfig` ``TypedDict`` wrapper.
+ :class:`altair.HeaderConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -2787,7 +2787,7 @@ class HeaderConfigKwds(TypedDict, total=False):
class IntervalSelectionConfigKwds(TypedDict, total=False):
"""
- :class:`IntervalSelectionConfig` ``TypedDict`` wrapper.
+ :class:`altair.IntervalSelectionConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -2897,7 +2897,7 @@ class IntervalSelectionConfigKwds(TypedDict, total=False):
class IntervalSelectionConfigWithoutTypeKwds(TypedDict, total=False):
"""
- :class:`IntervalSelectionConfigWithoutType` ``TypedDict`` wrapper.
+ :class:`altair.IntervalSelectionConfigWithoutType` ``TypedDict`` wrapper.
Parameters
----------
@@ -2999,7 +2999,7 @@ class IntervalSelectionConfigWithoutTypeKwds(TypedDict, total=False):
class LegendConfigKwds(TypedDict, total=False):
"""
- :class:`LegendConfig` ``TypedDict`` wrapper.
+ :class:`altair.LegendConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -3354,7 +3354,7 @@ class LegendConfigKwds(TypedDict, total=False):
class LegendResolveMapKwds(TypedDict, total=False):
"""
- :class:`LegendResolveMap` ``TypedDict`` wrapper.
+ :class:`altair.LegendResolveMap` ``TypedDict`` wrapper.
Parameters
----------
@@ -3397,7 +3397,7 @@ class LegendResolveMapKwds(TypedDict, total=False):
class LegendStreamBindingKwds(TypedDict, total=False):
"""
- :class:`LegendStreamBinding` ``TypedDict`` wrapper.
+ :class:`altair.LegendStreamBinding` ``TypedDict`` wrapper.
Parameters
----------
@@ -3410,7 +3410,7 @@ class LegendStreamBindingKwds(TypedDict, total=False):
class LineConfigKwds(TypedDict, total=False):
"""
- :class:`LineConfig` ``TypedDict`` wrapper.
+ :class:`altair.LineConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -3873,7 +3873,7 @@ class LineConfigKwds(TypedDict, total=False):
class LineStringKwds(TypedDict, total=False):
"""
- :class:`LineString` ``TypedDict`` wrapper.
+ :class:`altair.LineString` ``TypedDict`` wrapper.
Parameters
----------
@@ -3893,7 +3893,7 @@ class LineStringKwds(TypedDict, total=False):
class LinearGradientKwds(TypedDict, total=False):
"""
- :class:`LinearGradient` ``TypedDict`` wrapper.
+ :class:`altair.LinearGradient` ``TypedDict`` wrapper.
Parameters
----------
@@ -3932,7 +3932,7 @@ class LinearGradientKwds(TypedDict, total=False):
class LocaleKwds(TypedDict, total=False):
"""
- :class:`Locale` ``TypedDict`` wrapper.
+ :class:`altair.Locale` ``TypedDict`` wrapper.
Parameters
----------
@@ -3948,7 +3948,7 @@ class LocaleKwds(TypedDict, total=False):
class MarkConfigKwds(TypedDict, total=False):
"""
- :class:`MarkConfig` ``TypedDict`` wrapper.
+ :class:`altair.MarkConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -4396,7 +4396,7 @@ class MarkConfigKwds(TypedDict, total=False):
class MergedStreamKwds(TypedDict, total=False):
"""
- :class:`MergedStream` ``TypedDict`` wrapper.
+ :class:`altair.MergedStream` ``TypedDict`` wrapper.
Parameters
----------
@@ -4430,7 +4430,7 @@ class MergedStreamKwds(TypedDict, total=False):
class MultiLineStringKwds(TypedDict, total=False):
"""
- :class:`MultiLineString` ``TypedDict`` wrapper.
+ :class:`altair.MultiLineString` ``TypedDict`` wrapper.
Parameters
----------
@@ -4450,7 +4450,7 @@ class MultiLineStringKwds(TypedDict, total=False):
class MultiPointKwds(TypedDict, total=False):
"""
- :class:`MultiPoint` ``TypedDict`` wrapper.
+ :class:`altair.MultiPoint` ``TypedDict`` wrapper.
Parameters
----------
@@ -4470,7 +4470,7 @@ class MultiPointKwds(TypedDict, total=False):
class MultiPolygonKwds(TypedDict, total=False):
"""
- :class:`MultiPolygon` ``TypedDict`` wrapper.
+ :class:`altair.MultiPolygon` ``TypedDict`` wrapper.
Parameters
----------
@@ -4490,7 +4490,7 @@ class MultiPolygonKwds(TypedDict, total=False):
class NumberLocaleKwds(TypedDict, total=False):
"""
- :class:`NumberLocale` ``TypedDict`` wrapper.
+ :class:`altair.NumberLocale` ``TypedDict`` wrapper.
Parameters
----------
@@ -4524,7 +4524,7 @@ class NumberLocaleKwds(TypedDict, total=False):
class OverlayMarkDefKwds(TypedDict, total=False):
"""
- :class:`OverlayMarkDef` ``TypedDict`` wrapper.
+ :class:`altair.OverlayMarkDef` ``TypedDict`` wrapper.
Parameters
----------
@@ -5014,7 +5014,7 @@ class OverlayMarkDefKwds(TypedDict, total=False):
class PointKwds(TypedDict, total=False):
"""
- :class:`Point` ``TypedDict`` wrapper.
+ :class:`altair.Point` ``TypedDict`` wrapper.
Parameters
----------
@@ -5038,7 +5038,7 @@ class PointKwds(TypedDict, total=False):
class PointSelectionConfigKwds(TypedDict, total=False):
"""
- :class:`PointSelectionConfig` ``TypedDict`` wrapper.
+ :class:`altair.PointSelectionConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -5146,7 +5146,7 @@ class PointSelectionConfigKwds(TypedDict, total=False):
class PointSelectionConfigWithoutTypeKwds(TypedDict, total=False):
"""
- :class:`PointSelectionConfigWithoutType` ``TypedDict`` wrapper.
+ :class:`altair.PointSelectionConfigWithoutType` ``TypedDict`` wrapper.
Parameters
----------
@@ -5246,7 +5246,7 @@ class PointSelectionConfigWithoutTypeKwds(TypedDict, total=False):
class PolygonKwds(TypedDict, total=False):
"""
- :class:`Polygon` ``TypedDict`` wrapper.
+ :class:`altair.Polygon` ``TypedDict`` wrapper.
Parameters
----------
@@ -5266,7 +5266,7 @@ class PolygonKwds(TypedDict, total=False):
class ProjectionKwds(TypedDict, total=False):
"""
- :class:`Projection` ``TypedDict`` wrapper.
+ :class:`altair.Projection` ``TypedDict`` wrapper.
Parameters
----------
@@ -5409,7 +5409,7 @@ class ProjectionKwds(TypedDict, total=False):
class ProjectionConfigKwds(TypedDict, total=False):
"""
- :class:`ProjectionConfig` ``TypedDict`` wrapper.
+ :class:`altair.ProjectionConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -5552,7 +5552,7 @@ class ProjectionConfigKwds(TypedDict, total=False):
class RadialGradientKwds(TypedDict, total=False):
"""
- :class:`RadialGradient` ``TypedDict`` wrapper.
+ :class:`altair.RadialGradient` ``TypedDict`` wrapper.
Parameters
----------
@@ -5607,7 +5607,7 @@ class RadialGradientKwds(TypedDict, total=False):
class RangeConfigKwds(TypedDict, total=False):
"""
- :class:`RangeConfig` ``TypedDict`` wrapper.
+ :class:`altair.RangeConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -5661,7 +5661,7 @@ class RangeConfigKwds(TypedDict, total=False):
class RectConfigKwds(TypedDict, total=False):
"""
- :class:`RectConfig` ``TypedDict`` wrapper.
+ :class:`altair.RectConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -6127,7 +6127,7 @@ class RectConfigKwds(TypedDict, total=False):
class ResolveKwds(TypedDict, total=False):
"""
- :class:`Resolve` ``TypedDict`` wrapper.
+ :class:`altair.Resolve` ``TypedDict`` wrapper.
Parameters
----------
@@ -6146,7 +6146,7 @@ class ResolveKwds(TypedDict, total=False):
class ScaleConfigKwds(TypedDict, total=False):
"""
- :class:`ScaleConfig` ``TypedDict`` wrapper.
+ :class:`altair.ScaleConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -6329,7 +6329,7 @@ class ScaleConfigKwds(TypedDict, total=False):
class ScaleInvalidDataConfigKwds(TypedDict, total=False):
"""
- :class:`ScaleInvalidDataConfig` ``TypedDict`` wrapper.
+ :class:`altair.ScaleInvalidDataConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -6399,7 +6399,7 @@ class ScaleInvalidDataConfigKwds(TypedDict, total=False):
class ScaleResolveMapKwds(TypedDict, total=False):
"""
- :class:`ScaleResolveMap` ``TypedDict`` wrapper.
+ :class:`altair.ScaleResolveMap` ``TypedDict`` wrapper.
Parameters
----------
@@ -6460,7 +6460,7 @@ class ScaleResolveMapKwds(TypedDict, total=False):
class SelectionConfigKwds(TypedDict, total=False):
"""
- :class:`SelectionConfig` ``TypedDict`` wrapper.
+ :class:`altair.SelectionConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -6488,7 +6488,7 @@ class SelectionConfigKwds(TypedDict, total=False):
class StepKwds(TypedDict, closed=True, total=False): # type: ignore[call-arg]
"""
- :class:`Step` ``TypedDict`` wrapper.
+ :class:`altair.Step` ``TypedDict`` wrapper.
Parameters
----------
@@ -6513,7 +6513,7 @@ class StepKwds(TypedDict, closed=True, total=False): # type: ignore[call-arg]
class StyleConfigIndexKwds(TypedDict, closed=True, total=False): # type: ignore[call-arg]
"""
- :class:`StyleConfigIndex` ``TypedDict`` wrapper.
+ :class:`altair.StyleConfigIndex` ``TypedDict`` wrapper.
Parameters
----------
@@ -6580,7 +6580,7 @@ class StyleConfigIndexKwds(TypedDict, closed=True, total=False): # type: ignore
class TickConfigKwds(TypedDict, total=False):
"""
- :class:`TickConfig` ``TypedDict`` wrapper.
+ :class:`altair.TickConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -7039,7 +7039,7 @@ class TickConfigKwds(TypedDict, total=False):
class TimeIntervalStepKwds(TypedDict, total=False):
"""
- :class:`TimeIntervalStep` ``TypedDict`` wrapper.
+ :class:`altair.TimeIntervalStep` ``TypedDict`` wrapper.
Parameters
----------
@@ -7055,7 +7055,7 @@ class TimeIntervalStepKwds(TypedDict, total=False):
class TimeLocaleKwds(TypedDict, total=False):
"""
- :class:`TimeLocale` ``TypedDict`` wrapper.
+ :class:`altair.TimeLocale` ``TypedDict`` wrapper.
Parameters
----------
@@ -7089,7 +7089,7 @@ class TimeLocaleKwds(TypedDict, total=False):
class TitleConfigKwds(TypedDict, total=False):
"""
- :class:`TitleConfig` ``TypedDict`` wrapper.
+ :class:`altair.TitleConfig` ``TypedDict`` wrapper.
Parameters
----------
@@ -7197,7 +7197,7 @@ class TitleConfigKwds(TypedDict, total=False):
class TitleParamsKwds(TypedDict, total=False):
"""
- :class:`TitleParams` ``TypedDict`` wrapper.
+ :class:`altair.TitleParams` ``TypedDict`` wrapper.
Parameters
----------
@@ -7328,7 +7328,7 @@ class TitleParamsKwds(TypedDict, total=False):
class TooltipContentKwds(TypedDict, total=False):
"""
- :class:`TooltipContent` ``TypedDict`` wrapper.
+ :class:`altair.TooltipContent` ``TypedDict`` wrapper.
Parameters
----------
@@ -7341,7 +7341,7 @@ class TooltipContentKwds(TypedDict, total=False):
class TopLevelSelectionParameterKwds(TypedDict, total=False):
"""
- :class:`TopLevelSelectionParameter` ``TypedDict`` wrapper.
+ :class:`altair.TopLevelSelectionParameter` ``TypedDict`` wrapper.
Parameters
----------
@@ -7403,7 +7403,7 @@ class TopLevelSelectionParameterKwds(TypedDict, total=False):
class VariableParameterKwds(TypedDict, total=False):
"""
- :class:`VariableParameter` ``TypedDict`` wrapper.
+ :class:`altair.VariableParameter` ``TypedDict`` wrapper.
Parameters
----------
@@ -7448,7 +7448,7 @@ class VariableParameterKwds(TypedDict, total=False):
class ViewBackgroundKwds(TypedDict, total=False):
"""
- :class:`ViewBackground` ``TypedDict`` wrapper.
+ :class:`altair.ViewBackground` ``TypedDict`` wrapper.
Parameters
----------
@@ -7526,7 +7526,7 @@ class ViewBackgroundKwds(TypedDict, total=False):
class ViewConfigKwds(TypedDict, total=False):
"""
- :class:`ViewConfig` ``TypedDict`` wrapper.
+ :class:`altair.ViewConfig` ``TypedDict`` wrapper.
Parameters
----------
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index ca2e0db2c..f6db7811d 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -934,7 +934,7 @@ def generate_typed_dict(
name=name,
metaclass_kwds=metaclass_kwds,
comment=comment,
- summary=summary or f"{rst_syntax_for_class(info.title)} ``TypedDict`` wrapper.",
+ summary=(summary or f":class:`altair.{info.title}` ``TypedDict`` wrapper."),
doc=doc,
td_args=args,
)
From 2339116cb80afcce84c5071b834dcf8126fca3ec Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 29 Sep 2024 22:27:16 +0100
Subject: [PATCH 16/44] fix(typing): Preserve generics in `PluginEnabler`
---
altair/utils/plugin_registry.py | 16 ++++++++++------
altair/vegalite/v5/theme.py | 2 +-
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/altair/utils/plugin_registry.py b/altair/utils/plugin_registry.py
index b2723396a..b4505fd9f 100644
--- a/altair/utils/plugin_registry.py
+++ b/altair/utils/plugin_registry.py
@@ -39,7 +39,7 @@ def __str__(self):
return f"No {self.name!r} entry point found in group {self.group!r}"
-class PluginEnabler:
+class PluginEnabler(Generic[PluginT, R]):
"""
Context manager for enabling plugins.
@@ -51,21 +51,23 @@ class PluginEnabler:
# plugins back to original state
"""
- def __init__(self, registry: PluginRegistry, name: str, **options):
- self.registry: PluginRegistry = registry
+ def __init__(
+ self, registry: PluginRegistry[PluginT, R], name: str, **options: Any
+ ) -> None:
+ self.registry: PluginRegistry[PluginT, R] = registry
self.name: str = name
self.options: dict[str, Any] = options
self.original_state: dict[str, Any] = registry._get_state()
self.registry._enable(name, **options)
- def __enter__(self) -> PluginEnabler:
+ def __enter__(self) -> PluginEnabler[PluginT, R]:
return self
def __exit__(self, typ: type, value: Exception, traceback: TracebackType) -> None:
self.registry._set_state(self.original_state)
def __repr__(self) -> str:
- return f"{self.registry.__class__.__name__}.enable({self.name!r})"
+ return f"{type(self.registry).__name__}.enable({self.name!r})"
class PluginRegistry(Generic[PluginT, R]):
@@ -211,7 +213,9 @@ def _enable(self, name: str, **options) -> None:
self._global_settings[key] = options.pop(key)
self._options = options
- def enable(self, name: str | None = None, **options) -> PluginEnabler:
+ def enable(
+ self, name: str | None = None, **options: Any
+ ) -> PluginEnabler[PluginT, R]:
"""
Enable a plugin by name.
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index 1e22a6d45..a4daf0df2 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -34,7 +34,7 @@ def enable(
self,
name: LiteralString | AltairThemes | VegaThemes | None = None,
**options: Any,
- ) -> PluginEnabler:
+ ) -> PluginEnabler[Plugin[ThemeConfig], ThemeConfig]:
"""
Enable a theme by name.
From 48e44c0e4adbc740f7fe3c7aa35cf02072e7802c Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 15:54:28 +0100
Subject: [PATCH 17/44] refactor: Refine and fully document
`generate_schema__init__`
- Require fewer parameters
- Remove need for format string
- Adds general `path_to_module_str` utility
https://github.com/vega/altair/pull/3618#discussion_r1780132853
---
tools/generate_schema_wrapper.py | 63 ++++++++++++++++++++++++++------
1 file changed, 51 insertions(+), 12 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index f6db7811d..4254e8174 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -1019,20 +1019,47 @@ def generate_vegalite_config_mixin(fp: Path, /) -> str:
def generate_schema__init__(
- version: str,
*modules: str,
- package_name: str = "altair.vegalite.{0}.schema",
+ package: str,
expand: dict[Path, ModuleDef[Any]] | None = None,
) -> Iterator[str]:
- # NOTE: `expand`
- # - Should run after generating `core`, `channels`
- # - Only needed for `mypy`, the default works at runtime
- package_name = package_name.format(version.split(".")[0])
+ """
+ Generate schema subpackage init contents.
+
+ Parameters
+ ----------
+ *modules
+ Module names to expose, in addition to their members::
+
+ ...schema.__init__.__all__ = [
+ ...,
+ module_1.__name__,
+ module_1.__all__,
+ module_2.__name__,
+ module_2.__all__,
+ ...,
+ ]
+ package
+ Absolute, dotted path for `schema`, e.g::
+
+ "altair.vegalite.v5.schema"
+ expand
+ Required for 2nd-pass, which explicitly defines the new ``__all__``, using newly generated names.
+
+ .. note::
+ The default `import idiom`_ works at runtime, and for ``pyright`` - but not ``mypy``.
+ See `issue`_.
+
+ .. _import idiom:
+ https://typing.readthedocs.io/en/latest/spec/distributing.html#library-interface-public-and-private-symbols
+ .. _issue:
+ https://github.com/python/mypy/issues/15300
+ """
yield f"# ruff: noqa: F403, F405\n{HEADER_COMMENT}"
- yield f"from {package_name} import {', '.join(modules)}"
- yield from (f"from {package_name}.{mod} import *" for mod in modules)
- yield f"SCHEMA_VERSION = '{version}'\n"
- yield f"SCHEMA_URL = {schema_url(version)!r}\n"
+ yield f"from {package} import {', '.join(modules)}"
+ yield from (f"from {package}.{mod} import *" for mod in modules)
+ yield f"SCHEMA_VERSION = {SCHEMA_VERSION!r}\n"
+ yield f"SCHEMA_URL = {schema_url()!r}\n"
base_all: list[str] = ["SCHEMA_URL", "SCHEMA_VERSION", *modules]
if expand:
base_all.extend(
@@ -1044,6 +1071,17 @@ def generate_schema__init__(
yield from (f"__all__ += {mod}.__all__" for mod in modules)
+def path_to_module_str(
+ fp: Path,
+ /,
+ root: Literal["altair", "doc", "sphinxext", "tests", "tools"] = "altair",
+) -> str:
+ idx = fp.parts.index(root)
+ start = idx + 1 if root == "altair" else idx
+ end = -2 if fp.stem == "__init__" else -1
+ return ".".join(fp.parts[start:end])
+
+
def vegalite_main(skip_download: bool = False) -> None:
version = SCHEMA_VERSION
vn = version.split(".")[0]
@@ -1061,9 +1099,10 @@ def vegalite_main(skip_download: bool = False) -> None:
# Generate __init__.py file
outfile = schemapath / "__init__.py"
+ pkg_schema = path_to_module_str(outfile)
print(f"Writing {outfile!s}")
ruff_write_lint_format_str(
- outfile, generate_schema__init__(version, "channels", "core")
+ outfile, generate_schema__init__("channels", "core", package=pkg_schema)
)
TypeAliasTracer.update_aliases(("Map", "Mapping[str, Any]"))
@@ -1086,7 +1125,7 @@ def vegalite_main(skip_download: bool = False) -> None:
# Expand `schema.__init__.__all__` with new classes
ruff_write_lint_format_str(
outfile,
- generate_schema__init__(version, "channels", "core", expand=modules),
+ generate_schema__init__("channels", "core", package=pkg_schema, expand=modules),
)
# generate the mark mixin
From a7dbaca89f8c68f3a4ff2e71d47d2702a61180a9 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 15:57:11 +0100
Subject: [PATCH 18/44] refactor: Use new functions for part of
`update__all__variable`
The rest is still different enough that I'm not planning to further generalize between the two approaches
---
tools/update_init_file.py | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/tools/update_init_file.py b/tools/update_init_file.py
index 55c41e96e..17fe41ce9 100644
--- a/tools/update_init_file.py
+++ b/tools/update_init_file.py
@@ -51,11 +51,8 @@ def update__all__variable() -> None:
# Read existing file content
import altair as alt
- encoding = "utf-8"
- init_path = Path(alt.__file__)
- with init_path.open(encoding=encoding) as f:
- lines = f.readlines()
- lines = [line.strip("\n") for line in lines]
+ init_path = normalize_source("altair")
+ lines = extract_lines(init_path, strip_chars="\n")
# Find first and last line of the definition of __all__
first_definition_line = None
@@ -81,7 +78,7 @@ def update__all__variable() -> None:
ruff_write_lint_format_str(init_path, new_lines)
for source in DYNAMIC_ALL:
- print(f"Updating dynamic all: {source!r}")
+ print(f"Updating `__all__`\n " f"{source!r}\n ->{normalize_source(source)!s}")
update_dynamic__all__(source)
@@ -161,7 +158,7 @@ def normalize_source(src: str | Path, /) -> Path:
Returned unchanged if already a ``Path``.
"""
if isinstance(src, str):
- if src.startswith("altair."):
+ if src == "altair" or src.startswith("altair."):
if (spec := _find_spec(src)) and (origin := spec.origin):
src = origin
else:
@@ -171,14 +168,14 @@ def normalize_source(src: str | Path, /) -> Path:
return src
-def extract_lines(fp: Path, /) -> list[str]:
+def extract_lines(fp: Path, /, strip_chars: str | None = None) -> list[str]:
"""Return all lines in ``fp`` with whitespace stripped."""
with Path(fp).open(encoding="utf-8") as f:
lines = f.readlines()
if not lines:
msg = f"Found no content when reading lines for:\n{lines!r}"
raise NotImplementedError(msg)
- return [line.strip() for line in lines]
+ return [line.strip(strip_chars) for line in lines]
def _normalize_import_lines(lines: Iterable[str]) -> Iterator[str]:
From a0947f8ab88d4ceef8ab89bff958a7b115dea658 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 16:22:55 +0100
Subject: [PATCH 19/44] fix: Don't consider suffix a part in
`path_to_module_str`
https://github.com/vega/altair/actions/runs/11108757298/job/30862467326?pr=3618
---
tools/generate_schema_wrapper.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 4254e8174..1dddb4eeb 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -1078,8 +1078,8 @@ def path_to_module_str(
) -> str:
idx = fp.parts.index(root)
start = idx + 1 if root == "altair" else idx
- end = -2 if fp.stem == "__init__" else -1
- return ".".join(fp.parts[start:end])
+ parents = fp.parts[start:-1]
+ return ".".join(parents if fp.stem == "__init__" else (*parents, fp.stem))
def vegalite_main(skip_download: bool = False) -> None:
From 1e9f6393cb4483eb68ddb33ecceb0f4b9e8ac78b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 16:37:13 +0100
Subject: [PATCH 20/44] fix: Account for GH runner path
https://github.com/vega/altair/actions/runs/11109202680/job/30863988787?pr=3618
---
tools/generate_schema_wrapper.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
index 1dddb4eeb..c23789b72 100644
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -1076,8 +1076,10 @@ def path_to_module_str(
/,
root: Literal["altair", "doc", "sphinxext", "tests", "tools"] = "altair",
) -> str:
+ # NOTE: GH runner has 3x altair, local is 2x
+ # - Needs to be the last occurence
idx = fp.parts.index(root)
- start = idx + 1 if root == "altair" else idx
+ start = idx + fp.parts.count(root) - 1 if root == "altair" else idx
parents = fp.parts[start:-1]
return ".".join(parents if fp.stem == "__init__" else (*parents, fp.stem))
From d895447509ae3c9f419eb4a634376f87d6c7332b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 18:43:14 +0100
Subject: [PATCH 21/44] docs: Add link targets for API Reference sections
I've paired these with the `toctree` name, except for `api`.
Since that would be `api-api`
---
doc/user_guide/api.rst | 14 ++++++++++++++
tools/generate_api_docs.py | 14 ++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index d8baa092b..d9e972436 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -9,6 +9,8 @@ Please refer to the `full user guide
`_ for
further details, as this low-level documentation may not be enough to give
full guidelines on their use.
+.. _api-toplevel:
+
Top-Level Objects
-----------------
.. currentmodule:: altair
@@ -26,6 +28,8 @@ Top-Level Objects
TopLevelMixin
VConcatChart
+.. _api-channels:
+
Encoding Channels
-----------------
.. currentmodule:: altair
@@ -134,6 +138,8 @@ Encoding Channels
YOffsetValue
YValue
+.. _api-functions:
+
API Functions
-------------
.. currentmodule:: altair
@@ -164,6 +170,8 @@ API Functions
vconcat
when
+.. _api-theme:
+
Theme
-----
.. currentmodule:: altair.theme
@@ -249,6 +257,8 @@ Theme
ViewBackgroundKwds
ViewConfigKwds
+.. _api-core:
+
Low-Level Schema Wrappers
-------------------------
.. currentmodule:: altair
@@ -710,6 +720,8 @@ Low-Level Schema Wrappers
WindowOnlyOp
WindowTransform
+.. _api-cls:
+
API Utility Classes
-------------------
.. currentmodule:: altair
@@ -723,6 +735,8 @@ API Utility Classes
Then
ChainedWhen
+.. _api-typing:
+
Typing
------
.. currentmodule:: altair.typing
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index c4cda1639..d21b7ff5a 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -23,6 +23,8 @@
further details, as this low-level documentation may not be enough to give
full guidelines on their use.
+.. _api-toplevel:
+
Top-Level Objects
-----------------
.. currentmodule:: altair
@@ -33,6 +35,8 @@
{toplevel_charts}
+.. _api-channels:
+
Encoding Channels
-----------------
.. currentmodule:: altair
@@ -43,6 +47,8 @@
{encoding_wrappers}
+.. _api-functions:
+
API Functions
-------------
.. currentmodule:: altair
@@ -53,6 +59,8 @@
{api_functions}
+.. _api-theme:
+
Theme
-----
.. currentmodule:: altair.theme
@@ -63,6 +71,8 @@
{theme_objects}
+.. _api-core:
+
Low-Level Schema Wrappers
-------------------------
.. currentmodule:: altair
@@ -73,6 +83,8 @@
{lowlevel_wrappers}
+.. _api-cls:
+
API Utility Classes
-------------------
.. currentmodule:: altair
@@ -83,6 +95,8 @@
{api_classes}
+.. _api-typing:
+
Typing
------
.. currentmodule:: altair.typing
From a9538bc21665a0db98a7e8ec41b4d39b693e12de Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 18:55:14 +0100
Subject: [PATCH 22/44] docs: Customize theme toctree order
---
doc/user_guide/api.rst | 2 +-
tools/generate_api_docs.py | 9 +++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index d9e972436..0564bf088 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -180,13 +180,13 @@ Theme
:toctree: generated/theme/
:nosignatures:
- ThemeConfig
enable
get
names
register
themes
unregister
+ ThemeConfig
AreaConfigKwds
AutoSizeParamsKwds
AxisConfigKwds
diff --git a/tools/generate_api_docs.py b/tools/generate_api_docs.py
index d21b7ff5a..12331f871 100644
--- a/tools/generate_api_docs.py
+++ b/tools/generate_api_docs.py
@@ -159,10 +159,11 @@ def type_hints() -> list[str]:
def theme() -> list[str]:
- return sorted(
- sorted(s for s in iter_objects(alt.theme) if s in alt.theme.__all__),
- key=lambda s: s.endswith("Kwds"),
- )
+ sort_1 = sorted(s for s in iter_objects(alt.theme) if s in alt.theme.__all__)
+ # Display functions before `TypedDict`, but show `ThemeConfig` before `Kwds`
+ sort_2 = sorted(sort_1, key=lambda s: s.endswith("Kwds"))
+ sort_3 = sorted(sort_2, key=lambda s: not s.islower())
+ return sort_3
def lowlevel_wrappers() -> list[str]:
From e11449322d14775cdf087ce40f590acd9b232f1d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 20:55:07 +0100
Subject: [PATCH 23/44] docs: Update User Guide section
https://github.com/vega/altair/issues/3607
---
doc/user_guide/customization.rst | 58 +++++++++++++++-----------------
1 file changed, 27 insertions(+), 31 deletions(-)
diff --git a/doc/user_guide/customization.rst b/doc/user_guide/customization.rst
index 1c8d6e96c..8f5f19b79 100644
--- a/doc/user_guide/customization.rst
+++ b/doc/user_guide/customization.rst
@@ -710,18 +710,20 @@ outside the chart itself; For example, the container may be a ```` element
Chart Themes
------------
-..
- _comment: First mention of alt.themes
+.. note::
+
+ This material was changed considerably with the release of Altair ``5.5.0``.
Altair makes available a theme registry that lets users apply chart configurations
-globally within any Python session. This is done via the ``alt.themes`` object.
+globally within any Python session. For most use cases we have dedicated :ref:`helper functions
`, but
+the registry may also be accessed directly via :obj:`altair.theme.themes`.
The themes registry consists of functions which define a specification dictionary
that will be added to every created chart.
For example, the default theme configures the default size of a single chart:
>>> import altair as alt
- >>> default = alt.themes.get()
+ >>> default = alt.theme.get()
>>> default()
{'config': {'view': {'continuousWidth': 300, 'continuousHeight': 300}}}
@@ -750,14 +752,14 @@ The rendered chart will then reflect these configurations:
Changing the Theme
~~~~~~~~~~~~~~~~~~
If you would like to enable any other theme for the length of your Python session,
-you can call ``alt.themes.enable(theme_name)``.
+you can call :func:`altair.theme.enable`.
For example, Altair includes a theme in which the chart background is opaque
rather than transparent:
.. altair-plot::
:output: repr
- alt.themes.enable('opaque')
+ alt.theme.enable('opaque')
chart.to_dict()
.. altair-plot::
@@ -771,7 +773,7 @@ theme named ``'none'``:
.. altair-plot::
:output: repr
- alt.themes.enable('none')
+ alt.theme.enable('none')
chart.to_dict()
.. altair-plot::
@@ -787,9 +789,14 @@ If you would like to use any theme just for a single chart, you can use the
.. altair-plot::
:output: none
- with alt.themes.enable('default'):
+ with alt.theme.enable('default'):
spec = chart.to_json()
+.. note::
+ The above requires that a conversion/saving operation occurs during the ``with`` block,
+ such as :meth:`~Chart.to_dict`, :meth:`~Chart.to_json`, :meth:`~Chart.save`.
+ See https://github.com/vega/altair/issues/3586
+
Currently Altair does not offer many built-in themes, but we plan to add
more options in the future.
@@ -797,10 +804,11 @@ See `Vega Theme Test`_ for an interactive demo of themes inherited from `Vega Th
Defining a Custom Theme
~~~~~~~~~~~~~~~~~~~~~~~
-The theme registry also allows defining and registering custom themes.
A theme is simply a function that returns a dictionary of default values
-to be added to the chart specification at rendering time, which is then
-registered and activated.
+to be added to the chart specification at rendering time.
+
+Using :func:`altair.theme.register`, we can both register and enable a theme
+at the site of the function definition.
For example, here we define a theme in which all marks are drawn with black
fill unless otherwise specified:
@@ -810,26 +818,17 @@ fill unless otherwise specified:
import altair as alt
from vega_datasets import data
- # define the theme by returning the dictionary of configurations
- def black_marks():
+ # define, register and enable theme
+
+ @alt.theme.register("black_marks", enable=True)
+ def black_marks() -> alt.theme.ThemeConfig:
return {
- 'config': {
- 'view': {
- 'height': 300,
- 'width': 300,
- },
- 'mark': {
- 'color': 'black',
- 'fill': 'black'
- }
+ "config": {
+ "view": {"continuousWidth": 300, "continuousHeight": 300},
+ "mark": {"color": "black", "fill": "black"},
}
}
- # register the custom theme under a chosen name
- alt.themes.register('black_marks', black_marks)
-
- # enable the newly registered theme
- alt.themes.enable('black_marks')
# draw the chart
cars = data.cars.url
@@ -841,13 +840,10 @@ fill unless otherwise specified:
If you want to restore the default theme, use:
-..
- _comment: Last mention of alt.themes
-
.. altair-plot::
:output: none
- alt.themes.enable('default')
+ alt.theme.enable('default')
For more ideas on themes, see the `Vega Themes`_ repository.
From c8033b2cdb7939eda9b5a2d839d008a5fa8c59ba Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 21:14:34 +0100
Subject: [PATCH 24/44] docs: Provide API ref link in warning
https://github.com/vega/altair/pull/3618#discussion_r1780150958
---
altair/__init__.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index 566d76c22..e68cb4978 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -670,7 +670,8 @@ def __getattr__(name: str) -> _Any:
deprecated_warn(
"Most of the `ThemeRegistry` API is accessible via `altair.theme`.\n"
"See the updated User Guide for further details:\n"
- "https://altair-viz.github.io/user_guide/customization.html#chart-themes",
+ " https://altair-viz.github.io/user_guide/api.html#theme\n"
+ " https://altair-viz.github.io/user_guide/customization.html#chart-themes",
version="5.5.0",
alternative="altair.theme.themes",
stacklevel=3,
From ef0f263bb4e13682159b9fe2f5fdff3160e41058 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Mon, 30 Sep 2024 21:24:01 +0100
Subject: [PATCH 25/44] refactor: Remove unused `__future__` import
I added this while experimenting with a more complex `__getattr__`, but don't need it for anything in this version
---
altair/__init__.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index e68cb4978..c226c1484 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
# ruff: noqa
__version__ = "5.5.0dev"
From 58da996dbe19f3750159713fa0b73ef44c8455f1 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:38:16 +0100
Subject: [PATCH 26/44] feat: Support `alt.theme.(active|options)`
https://github.com/vega/altair/issues/3610#issuecomment-2385432948
---
altair/theme.py | 30 +++++++++++++++++++++++++++---
doc/user_guide/api.rst | 2 ++
2 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/altair/theme.py b/altair/theme.py
index b19327b55..a572a5c40 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -3,7 +3,8 @@
from __future__ import annotations
from functools import wraps as _wraps
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
+from typing import overload as _overload
from altair.vegalite.v5.schema._config import (
AreaConfigKwds,
@@ -81,7 +82,7 @@
if TYPE_CHECKING:
import sys
- from typing import Callable
+ from typing import Any, Callable, Literal
if sys.version_info >= (3, 11):
from typing import LiteralString
@@ -157,7 +158,6 @@
"StepKwds",
"StyleConfigIndexKwds",
"ThemeConfig",
- "ThemeConfig",
"TickConfigKwds",
"TimeIntervalStepKwds",
"TimeLocaleKwds",
@@ -168,9 +168,11 @@
"VariableParameterKwds",
"ViewBackgroundKwds",
"ViewConfigKwds",
+ "active",
"enable",
"get",
"names",
+ "options",
"register",
"themes",
"unregister",
@@ -264,3 +266,25 @@ def unregister(name: LiteralString) -> Plugin[ThemeConfig] | None:
enable = themes.enable
get = themes.get
names = themes.names
+active: str
+"""Return the name of the currently active theme."""
+options: dict[str, Any]
+"""Return the current themes options dictionary."""
+
+
+def __dir__() -> list[str]:
+ return __all__
+
+
+@_overload
+def __getattr__(name: Literal["active"]) -> str: ...
+@_overload
+def __getattr__(name: Literal["options"]) -> dict[str, Any]: ...
+def __getattr__(name: Literal["active", "options"]) -> str | dict[str, Any]:
+ if name == "active":
+ return themes.active
+ elif name == "options":
+ return themes.options
+ else:
+ msg = f"module {__name__!r} has no attribute {name!r}"
+ raise AttributeError(msg)
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index 0564bf088..0cb91f6ae 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -180,9 +180,11 @@ Theme
:toctree: generated/theme/
:nosignatures:
+ active
enable
get
names
+ options
register
themes
unregister
From bc507a95ce11d8f196cda7f52daa3d1c4b09def0 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:56:19 +0100
Subject: [PATCH 27/44] fix(typing): Partial resolve `mypy` `__getattr__`
Didn't have any issues with `pyright`.
Adapted from https://stackoverflow.com/a/78369397
https://github.com/vega/altair/actions/runs/11123634307/job/30907482239?pr=3618
---
altair/theme.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/altair/theme.py b/altair/theme.py
index a572a5c40..6a335fa28 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -277,10 +277,10 @@ def __dir__() -> list[str]:
@_overload
-def __getattr__(name: Literal["active"]) -> str: ...
+def __getattr__(name: Literal["active"]) -> str: ... # type: ignore[misc]
@_overload
-def __getattr__(name: Literal["options"]) -> dict[str, Any]: ...
-def __getattr__(name: Literal["active", "options"]) -> str | dict[str, Any]:
+def __getattr__(name: Literal["options"]) -> dict[str, Any]: ... # type: ignore[misc]
+def __getattr__(name: str) -> Any:
if name == "active":
return themes.active
elif name == "options":
From e0f47212245228220e084654a54dbd3786fe2bae Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 12:47:26 +0100
Subject: [PATCH 28/44] test: Rename test to `test_theme_register_decorator`
Now reflects the updated function name
---
tests/vegalite/v5/test_theme.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index ea480d8a8..fd3b4b83e 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -34,7 +34,7 @@ def test_vega_themes(chart) -> None:
}
-def test_register_theme_decorator() -> None:
+def test_theme_register_decorator() -> None:
@theme.register("unique name", enable=True)
def custom_theme() -> ThemeConfig:
return {"height": 400, "width": 700}
From 4e8def023691167c60155848d7c8fc6b048d4bd8 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:23:48 +0100
Subject: [PATCH 29/44] test: Add some more `(theme|themes)` equal checks
---
tests/vegalite/v5/test_theme.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index fd3b4b83e..978d74093 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -39,9 +39,10 @@ def test_theme_register_decorator() -> None:
def custom_theme() -> ThemeConfig:
return {"height": 400, "width": 700}
- assert theme.themes.active == "unique name"
+ assert theme.themes.active == "unique name" == theme.active
registered = theme.themes.get()
assert registered is not None
+ assert registered == theme.get()
assert registered() == {"height": 400, "width": 700} == custom_theme()
From 9bd031628c302157481272ecf726dece28993f2d Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:25:00 +0100
Subject: [PATCH 30/44] test: Add `test_theme_unregister`
The commented out assertion may be added later after resolving https://github.com/vega/altair/issues/3619
---
tests/vegalite/v5/test_theme.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 978d74093..7d4155bfc 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -46,6 +46,20 @@ def custom_theme() -> ThemeConfig:
assert registered() == {"height": 400, "width": 700} == custom_theme()
+def test_theme_unregister() -> None:
+ @theme.register("big square", enable=True)
+ def custom_theme() -> ThemeConfig:
+ return {"height": 1000, "width": 1000}
+
+ assert theme.active == "big square"
+ fn = theme.unregister("big square")
+ assert fn is not None
+ assert fn() == custom_theme()
+ assert theme.active == theme.themes.active
+ # BUG: https://github.com/vega/altair/issues/3619
+ # assert theme.active != "big square"
+
+
@pytest.mark.parametrize(
("color_code", "valid"),
[
From 0d95b44ed9027400f4bc8f1176d2f22b648e3cb3 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:41:53 +0100
Subject: [PATCH 31/44] feat: Raise instead of returning `None` in `unregister`
---
altair/theme.py | 18 ++++++++++++++++--
tests/vegalite/v5/test_theme.py | 6 +++++-
2 files changed, 21 insertions(+), 3 deletions(-)
diff --git a/altair/theme.py b/altair/theme.py
index 6a335fa28..ce0d7ba31 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -251,7 +251,7 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> ThemeConfig:
return decorate
-def unregister(name: LiteralString) -> Plugin[ThemeConfig] | None:
+def unregister(name: LiteralString) -> Plugin[ThemeConfig]:
"""
Remove and return a previously registered theme.
@@ -259,8 +259,22 @@ def unregister(name: LiteralString) -> Plugin[ThemeConfig] | None:
----------
name
Unique name assigned in ``alt.theme.themes``.
+
+ Raises
+ ------
+ TypeError
+ When ``name`` has not been registered.
"""
- return themes.register(name, None)
+ plugin = themes.register(name, None)
+ if plugin is None:
+ msg = (
+ f"Found no theme named {name!r} in registry.\n"
+ f"Registered themes:\n"
+ f"{names()!r}"
+ )
+ raise TypeError(msg)
+ else:
+ return plugin
enable = themes.enable
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 7d4155bfc..20366fe9a 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -53,12 +53,16 @@ def custom_theme() -> ThemeConfig:
assert theme.active == "big square"
fn = theme.unregister("big square")
- assert fn is not None
assert fn() == custom_theme()
assert theme.active == theme.themes.active
# BUG: https://github.com/vega/altair/issues/3619
# assert theme.active != "big square"
+ with pytest.raises(
+ TypeError, match=r"Found no theme named 'big square' in registry."
+ ):
+ theme.unregister("big square")
+
@pytest.mark.parametrize(
("color_code", "valid"),
From 3e6bde8e1f1b0ce29ce26e59e6c8daaeea5a5430 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Tue, 1 Oct 2024 17:50:59 +0100
Subject: [PATCH 32/44] docs: Update remaining `ThemeRegistry` methods
---
altair/vegalite/v5/theme.py | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index a4daf0df2..d57d88b4d 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -10,6 +10,7 @@
if TYPE_CHECKING:
import sys
+ from functools import partial
if sys.version_info >= (3, 11):
from typing import LiteralString
@@ -60,6 +61,14 @@ def enable(
"""
return super().enable(name, **options)
+ def get(self) -> partial[ThemeConfig] | Plugin[ThemeConfig] | None:
+ """Return the currently active theme."""
+ return super().get()
+
+ def names(self) -> list[str]:
+ """Return the names of the registered and entry points themes."""
+ return super().names()
+
class VegaTheme:
"""Implementation of a builtin vega theme."""
From 7c49b519d8365a0220c128a2792b0b51f6042b38 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 11 Oct 2024 15:45:25 +0100
Subject: [PATCH 33/44] test: Adds `test_theme_remote_lambda`
Related https://github.com/vega/altair/pull/3618#issuecomment-2407414605
---
tests/vegalite/v5/test_theme.py | 39 +++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 20366fe9a..484d6d252 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -9,6 +9,7 @@
from altair.theme import ConfigKwds, ThemeConfig
from altair.vegalite.v5.schema._typing import is_color_hex
from altair.vegalite.v5.theme import VEGA_THEMES
+from tests import slow
if TYPE_CHECKING:
import sys
@@ -34,6 +35,44 @@ def test_vega_themes(chart) -> None:
}
+@slow
+def test_theme_remote_lambda() -> None:
+ """
+ Compatibility test for ``lambda`` usage in `dash-vega-components`_.
+
+ A ``lambda`` here is to fetch the remote resource **once**, wrapping the result in a function.
+
+ .. _dash-vega-components:
+ https://github.com/vega/dash-vega-components/blob/c3e8cae873580bc7a52bc01daea1f27a7df02b8b/example_app.py#L13-L17
+ """
+ import altair as alt # noqa: I001
+ from urllib.request import urlopen
+ import json
+
+ URL = "https://gist.githubusercontent.com/binste/b4042fa76a89d72d45cbbb9355ec6906/raw/e36f79d722bcd9dd954389b1753a2d4a18113227/altair_theme.json"
+ with urlopen(URL) as response:
+ custom_theme = json.load(response)
+
+ alt.theme.register("remote_binste", enable=True)(lambda: custom_theme)
+ assert alt.theme.active == "remote_binste"
+
+ # NOTE: A decorator-compatible way to define an "anonymous" function
+ @alt.theme.register("remote_binste_2", enable=True)
+ def _():
+ return custom_theme
+
+ assert alt.theme.active == "remote_binste_2"
+
+ decorated_theme = alt.theme.get()
+ alt.theme.enable("remote_binste")
+ assert alt.theme.active == "remote_binste"
+ lambda_theme = alt.theme.get()
+
+ assert decorated_theme
+ assert lambda_theme
+ assert decorated_theme() == lambda_theme()
+
+
def test_theme_register_decorator() -> None:
@theme.register("unique name", enable=True)
def custom_theme() -> ThemeConfig:
From a018165b7a32508b33c5bda30bc585de6f021cde Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 12 Oct 2024 19:24:08 +0100
Subject: [PATCH 34/44] docs: Suggest `alt.theme` instead of `alt.theme.themes`
https://github.com/vega/altair/pull/3618#issuecomment-2408550858
---
altair/__init__.py | 2 +-
tests/utils/test_deprecation.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index c226c1484..3b49f05d4 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -671,7 +671,7 @@ def __getattr__(name: str) -> _Any:
" https://altair-viz.github.io/user_guide/api.html#theme\n"
" https://altair-viz.github.io/user_guide/customization.html#chart-themes",
version="5.5.0",
- alternative="altair.theme.themes",
+ alternative="altair.theme",
stacklevel=3,
)
return theme.themes
diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py
index 471071cc3..cbc13dc5d 100644
--- a/tests/utils/test_deprecation.py
+++ b/tests/utils/test_deprecation.py
@@ -47,7 +47,7 @@ def test_deprecated_import():
import altair as alt
pattern = re.compile(
- r"altair=5\.5\.0.+\.theme\.themes instead.+user.guide",
+ r"altair=5\.5\.0.+\.theme instead.+user.guide",
flags=re.DOTALL | re.IGNORECASE,
)
with pytest.warns(AltairDeprecationWarning, match=pattern):
From 2b3eb3533b64a116205c0ac3c1268ac46064f677 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 18 Oct 2024 13:48:10 +0100
Subject: [PATCH 35/44] refactor: Rename `theme.themes` -> `theme._themes`
https://github.com/vega/altair/pull/3618#issuecomment-2408550858, https://github.com/vega/altair/pull/3618#issuecomment-2422290958, https://github.com/vega/altair/issues/3610#issuecomment-2385487732
---
altair/__init__.py | 2 +-
altair/theme.py | 21 ++++++++++-----------
doc/user_guide/api.rst | 1 -
tests/utils/test_deprecation.py | 2 +-
tests/vegalite/test_common.py | 12 ++++++------
tests/vegalite/v5/test_theme.py | 6 +++---
6 files changed, 21 insertions(+), 23 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index 3b49f05d4..d9adb730f 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -674,7 +674,7 @@ def __getattr__(name: str) -> _Any:
alternative="altair.theme",
stacklevel=3,
)
- return theme.themes
+ return theme._themes
else:
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)
diff --git a/altair/theme.py b/altair/theme.py
index ce0d7ba31..feacc1a91 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -78,7 +78,7 @@
ViewBackgroundKwds,
ViewConfigKwds,
)
-from altair.vegalite.v5.theme import themes
+from altair.vegalite.v5.theme import themes as _themes
if TYPE_CHECKING:
import sys
@@ -174,7 +174,6 @@
"names",
"options",
"register",
- "themes",
"unregister",
]
@@ -238,9 +237,9 @@ def custom_theme() -> theme.ThemeConfig:
# HACK: See for `LiteralString` requirement in `name`
# https://github.com/vega/altair/pull/3526#discussion_r1743350127
def decorate(func: Plugin[ThemeConfig], /) -> Plugin[ThemeConfig]:
- themes.register(name, func)
+ _themes.register(name, func)
if enable:
- themes.enable(name)
+ _themes.enable(name)
@_wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ThemeConfig:
@@ -258,14 +257,14 @@ def unregister(name: LiteralString) -> Plugin[ThemeConfig]:
Parameters
----------
name
- Unique name assigned in ``alt.theme.themes``.
+ Unique name assigned during ``alt.theme.register``.
Raises
------
TypeError
When ``name`` has not been registered.
"""
- plugin = themes.register(name, None)
+ plugin = _themes.register(name, None)
if plugin is None:
msg = (
f"Found no theme named {name!r} in registry.\n"
@@ -277,9 +276,9 @@ def unregister(name: LiteralString) -> Plugin[ThemeConfig]:
return plugin
-enable = themes.enable
-get = themes.get
-names = themes.names
+enable = _themes.enable
+get = _themes.get
+names = _themes.names
active: str
"""Return the name of the currently active theme."""
options: dict[str, Any]
@@ -296,9 +295,9 @@ def __getattr__(name: Literal["active"]) -> str: ... # type: ignore[misc]
def __getattr__(name: Literal["options"]) -> dict[str, Any]: ... # type: ignore[misc]
def __getattr__(name: str) -> Any:
if name == "active":
- return themes.active
+ return _themes.active
elif name == "options":
- return themes.options
+ return _themes.options
else:
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)
diff --git a/doc/user_guide/api.rst b/doc/user_guide/api.rst
index 0cb91f6ae..d3c08f547 100644
--- a/doc/user_guide/api.rst
+++ b/doc/user_guide/api.rst
@@ -186,7 +186,6 @@ Theme
names
options
register
- themes
unregister
ThemeConfig
AreaConfigKwds
diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py
index cbc13dc5d..d1410f13c 100644
--- a/tests/utils/test_deprecation.py
+++ b/tests/utils/test_deprecation.py
@@ -58,4 +58,4 @@ def test_deprecated_import():
with catch_warnings():
filterwarnings("ignore", category=AltairDeprecationWarning)
- assert alt.themes == alt.theme.themes
+ assert alt.themes == alt.theme._themes
diff --git a/tests/vegalite/test_common.py b/tests/vegalite/test_common.py
index 69c173b97..0ea5b049b 100644
--- a/tests/vegalite/test_common.py
+++ b/tests/vegalite/test_common.py
@@ -20,9 +20,9 @@ def basic_spec():
def make_final_spec(alt, basic_spec):
- from altair.theme import themes
+ from altair.theme import _themes
- theme = themes.get()
+ theme = _themes.get()
assert theme
spec = theme()
spec.update(basic_spec)
@@ -70,12 +70,12 @@ def test_basic_chart_from_dict(alt, basic_spec):
@pytest.mark.parametrize("alt", [v5])
def test_theme_enable(alt, basic_spec):
- from altair.theme import themes
+ from altair.theme import _themes
- active_theme = themes.active
+ active_theme = _themes.active
try:
- themes.enable("none")
+ _themes.enable("none")
chart = alt.Chart.from_dict(basic_spec)
dct = chart.to_dict()
@@ -88,7 +88,7 @@ def test_theme_enable(alt, basic_spec):
assert dct == basic_spec
finally:
# reset the theme to its initial value
- themes.enable(active_theme) # pyright: ignore[reportArgumentType]
+ _themes.enable(active_theme) # pyright: ignore[reportArgumentType]
@pytest.mark.parametrize("alt", [v5])
diff --git a/tests/vegalite/v5/test_theme.py b/tests/vegalite/v5/test_theme.py
index 484d6d252..da7c134ba 100644
--- a/tests/vegalite/v5/test_theme.py
+++ b/tests/vegalite/v5/test_theme.py
@@ -78,8 +78,8 @@ def test_theme_register_decorator() -> None:
def custom_theme() -> ThemeConfig:
return {"height": 400, "width": 700}
- assert theme.themes.active == "unique name" == theme.active
- registered = theme.themes.get()
+ assert theme._themes.active == "unique name" == theme.active
+ registered = theme._themes.get()
assert registered is not None
assert registered == theme.get()
assert registered() == {"height": 400, "width": 700} == custom_theme()
@@ -93,7 +93,7 @@ def custom_theme() -> ThemeConfig:
assert theme.active == "big square"
fn = theme.unregister("big square")
assert fn() == custom_theme()
- assert theme.active == theme.themes.active
+ assert theme.active == theme._themes.active
# BUG: https://github.com/vega/altair/issues/3619
# assert theme.active != "big square"
From 10872b6807207aca7e99ed0a7bcd67e28297de8b Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 18 Oct 2024 13:55:12 +0100
Subject: [PATCH 36/44] docs: Update `@theme.register`
Rather than saying exactly where `name` is **stored**, showing a place it is **used** with an example
---
altair/theme.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/altair/theme.py b/altair/theme.py
index feacc1a91..8c0162a5d 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -187,7 +187,7 @@ def register(
Parameters
----------
name
- Unique name assigned in ``alt.theme.themes``.
+ Unique name assigned in registry.
enable
Auto-enable the wrapped theme.
@@ -220,7 +220,12 @@ def custom_theme() -> theme.ThemeConfig:
"width": 350,
}
- Until another theme has been enabled, all charts will use defaults set in ``custom_theme``::
+ We can then see the ``name`` parameter displayed when checking::
+
+ theme.active
+ "param_font_size"
+
+ Until another theme has been enabled, all charts will use defaults set in ``custom_theme()``::
from vega_datasets import data
From 1127534701ec5b6f285dfb8c7697b58aceb3bcc4 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 18 Oct 2024 14:30:10 +0100
Subject: [PATCH 37/44] docs: Update `customization.rst`
- Replaced references to `altair.theme.themes`
- Tried to improve the flow, without entirely removing the term "registry"
---
doc/user_guide/customization.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/doc/user_guide/customization.rst b/doc/user_guide/customization.rst
index 52e967f77..71db5336f 100644
--- a/doc/user_guide/customization.rst
+++ b/doc/user_guide/customization.rst
@@ -715,10 +715,10 @@ Chart Themes
This material was changed considerably with the release of Altair ``5.5.0``.
Altair makes available a theme registry that lets users apply chart configurations
-globally within any Python session. For most use cases we have dedicated :ref:`helper functions `, but
-the registry may also be accessed directly via :obj:`altair.theme.themes`.
+globally within any Python session.
+The :mod:`altair.theme` module provides :ref:`helper functions ` to interact with the registry.
-The themes registry consists of functions which define a specification dictionary
+Each theme in the registry is a function which define a specification dictionary
that will be added to every created chart.
For example, the default theme configures the default size of a single chart:
From cb56c82e30697c6a4df95f1de7cc82ad02b47321 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 18 Oct 2024 20:43:10 +0100
Subject: [PATCH 38/44] feat: Add support displaying warning once
https://github.com/vega/altair/pull/3618#issuecomment-2408550858, https://github.com/vega/altair/pull/3618#issuecomment-2422290958, https://github.com/vega/altair/pull/3618#issuecomment-2422962007
---
altair/__init__.py | 1 +
altair/utils/deprecation.py | 43 +++++++++++++++++++++++++++++++--
tests/utils/test_deprecation.py | 13 ++++++----
3 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index d9adb730f..41b7f1acd 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -673,6 +673,7 @@ def __getattr__(name: str) -> _Any:
version="5.5.0",
alternative="altair.theme",
stacklevel=3,
+ action="once",
)
return theme._themes
else:
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index f11dd1a41..b89b8519b 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -1,8 +1,9 @@
from __future__ import annotations
import sys
+import threading
import warnings
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Literal
if sys.version_info >= (3, 13):
from warnings import deprecated as _deprecated
@@ -81,6 +82,7 @@ def deprecated_warn(
alternative: LiteralString | None = None,
category: type[AltairDeprecationWarning] = AltairDeprecationWarning,
stacklevel: int = 2,
+ action: Literal["once"] | None = None,
) -> None:
"""
Indicate that the current code path is deprecated.
@@ -112,4 +114,41 @@ def deprecated_warn(
[warnings.warn](https://docs.python.org/3/library/warnings.html#warnings.warn)
"""
msg = _format_message(version, alternative, message)
- warnings.warn(msg, category=category, stacklevel=stacklevel)
+ if action is None:
+ warnings.warn(msg, category=category, stacklevel=stacklevel)
+ elif action == "once":
+ _warn_once(msg, category=category, stacklevel=stacklevel)
+ else:
+ raise NotImplementedError(action)
+
+
+class _WarningsMonitor:
+ def __init__(self) -> None:
+ self._warned: dict[LiteralString, Literal[True]] = {}
+ self._lock = threading.Lock()
+
+ def __contains__(self, key: LiteralString, /) -> bool:
+ with self._lock:
+ return key in self._warned
+
+ def hit(self, key: LiteralString, /) -> None:
+ with self._lock:
+ self._warned[key] = True
+
+ def clear(self) -> None:
+ with self._lock:
+ self._warned.clear()
+
+
+_warnings_monitor = _WarningsMonitor()
+
+
+def _warn_once(
+ msg: LiteralString, /, *, category: type[AltairDeprecationWarning], stacklevel: int
+) -> None:
+ global _warnings_monitor
+ if msg in _warnings_monitor:
+ return
+ else:
+ _warnings_monitor.hit(msg)
+ warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
diff --git a/tests/utils/test_deprecation.py b/tests/utils/test_deprecation.py
index d1410f13c..3449142aa 100644
--- a/tests/utils/test_deprecation.py
+++ b/tests/utils/test_deprecation.py
@@ -5,6 +5,7 @@
from altair.utils.deprecation import (
AltairDeprecationWarning,
+ _warnings_monitor,
deprecated,
deprecated_warn,
)
@@ -42,8 +43,6 @@ def test_deprecation_warn():
def test_deprecated_import():
- from warnings import catch_warnings, filterwarnings
-
import altair as alt
pattern = re.compile(
@@ -53,9 +52,13 @@ def test_deprecated_import():
with pytest.warns(AltairDeprecationWarning, match=pattern):
alt.themes
+ # NOTE: Tests that second access does not trigger a warning
+ assert alt.themes
+ # Then reset cache
+ _warnings_monitor.clear()
+
with pytest.warns(AltairDeprecationWarning, match=pattern):
from altair import themes # noqa: F401
- with catch_warnings():
- filterwarnings("ignore", category=AltairDeprecationWarning)
- assert alt.themes == alt.theme._themes
+ assert alt.themes == alt.theme._themes
+ _warnings_monitor.clear()
From 097722838dccf2195f3270ca9dbef6d551238294 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Fri, 18 Oct 2024 20:47:23 +0100
Subject: [PATCH 39/44] docs: Adds `utils.deprecation.__all__`
---
altair/utils/deprecation.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index b89b8519b..b6252c071 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -17,6 +17,8 @@
else:
from typing_extensions import LiteralString
+__all__ = ["AltairDeprecationWarning", "deprecated", "deprecated_warn"]
+
class AltairDeprecationWarning(DeprecationWarning): ...
From 5a1dc6893134966635999fb7f687316f1f10b5dd Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 19 Oct 2024 15:04:52 +0100
Subject: [PATCH 40/44] docs: Add examples to deprecation message
https://github.com/vega/altair/pull/3618#issuecomment-2408133664, https://github.com/vega/altair/pull/3618#issuecomment-2422290958
---
altair/__init__.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index 41b7f1acd..0b58ef43e 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -666,7 +666,21 @@ def __getattr__(name: str) -> _Any:
if name == "themes":
deprecated_warn(
- "Most of the `ThemeRegistry` API is accessible via `altair.theme`.\n"
+ "Most cases require only the following change:\n\n"
+ " # Deprecated\n"
+ " alt.themes.enable('quartz')\n\n"
+ " # Updated\n"
+ " alt.theme.enable('quartz')\n\n"
+ "If your code registers a theme, make the following change:\n\n"
+ " # Deprecated\n"
+ " def custom_theme():\n"
+ " return {'height': 400, 'width': 700}\n"
+ " alt.themes.register('theme_name', custom_theme)\n"
+ " alt.themes.enable('theme_name')\n\n"
+ " # Updated\n"
+ " @alt.theme.register('theme_name', enable=True)\n"
+ " def custom_theme() -> alt.theme.ThemeConfig:\n"
+ " return {'height': 400, 'width': 700}\n\n"
"See the updated User Guide for further details:\n"
" https://altair-viz.github.io/user_guide/api.html#theme\n"
" https://altair-viz.github.io/user_guide/customization.html#chart-themes",
From 22d7df2597704defca3360c1ca00eab18efdca55 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 19 Oct 2024 17:16:07 +0100
Subject: [PATCH 41/44] docs: Prefix `" "` to all deprecation warnings
https://github.com/vega/altair/pull/3618#issuecomment-2424025777, https://github.com/vega/altair/pull/3618#issuecomment-2424029590
---
altair/utils/deprecation.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index b6252c071..065dd661b 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -29,7 +29,7 @@ def _format_message(
message: LiteralString | None,
/,
) -> LiteralString:
- output = f"Deprecated in `altair={version}`."
+ output = f"\nDeprecated in `altair={version}`."
if alternative:
output = f"{output} Use {alternative} instead."
return f"{output}\n{message}" if message else output
From 25cbdc8119c867f7c74f52568245735083078c3f Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sat, 19 Oct 2024 18:26:21 +0100
Subject: [PATCH 42/44] docs(typing): Adds static-only deprecation for
`themes.register`
https://github.com/vega/altair/pull/3618#issuecomment-2423991968
---
altair/__init__.py | 3 +--
altair/theme.py | 17 +++++++++++++++--
altair/utils/deprecation.py | 31 ++++++++++++++++++++++++++++++-
altair/vegalite/v5/theme.py | 10 ++++++++++
4 files changed, 56 insertions(+), 5 deletions(-)
diff --git a/altair/__init__.py b/altair/__init__.py
index 0b58ef43e..cbc2c3b52 100644
--- a/altair/__init__.py
+++ b/altair/__init__.py
@@ -652,7 +652,6 @@ def __dir__():
from altair.expr import expr
from altair.utils import AltairDeprecationWarning, parse_shorthand, Undefined
from altair import typing, theme
-from typing import Any as _Any
def load_ipython_extension(ipython):
@@ -661,7 +660,7 @@ def load_ipython_extension(ipython):
ipython.register_magic_function(vegalite, "cell")
-def __getattr__(name: str) -> _Any:
+def __getattr__(name: str):
from altair.utils.deprecation import deprecated_warn
if name == "themes":
diff --git a/altair/theme.py b/altair/theme.py
index 8c0162a5d..e77a95eb8 100644
--- a/altair/theme.py
+++ b/altair/theme.py
@@ -242,7 +242,7 @@ def custom_theme() -> theme.ThemeConfig:
# HACK: See for `LiteralString` requirement in `name`
# https://github.com/vega/altair/pull/3526#discussion_r1743350127
def decorate(func: Plugin[ThemeConfig], /) -> Plugin[ThemeConfig]:
- _themes.register(name, func)
+ _register(name, func)
if enable:
_themes.enable(name)
@@ -269,7 +269,7 @@ def unregister(name: LiteralString) -> Plugin[ThemeConfig]:
TypeError
When ``name`` has not been registered.
"""
- plugin = _themes.register(name, None)
+ plugin = _register(name, None)
if plugin is None:
msg = (
f"Found no theme named {name!r} in registry.\n"
@@ -306,3 +306,16 @@ def __getattr__(name: str) -> Any:
else:
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)
+
+
+def _register(
+ name: LiteralString, fn: Plugin[ThemeConfig] | None, /
+) -> Plugin[ThemeConfig] | None:
+ if fn is None:
+ return _themes._plugins.pop(name, None)
+ elif _themes.plugin_type(fn):
+ _themes._plugins[name] = fn
+ return fn
+ else:
+ msg = f"{type(fn).__name__!r} is not a callable theme\n\n{fn!r}"
+ raise TypeError(msg)
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index 065dd661b..f32d6c183 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -17,7 +17,12 @@
else:
from typing_extensions import LiteralString
-__all__ = ["AltairDeprecationWarning", "deprecated", "deprecated_warn"]
+__all__ = [
+ "AltairDeprecationWarning",
+ "deprecated",
+ "deprecated_static_only",
+ "deprecated_warn",
+]
class AltairDeprecationWarning(DeprecationWarning): ...
@@ -124,6 +129,30 @@ def deprecated_warn(
raise NotImplementedError(action)
+deprecated_static_only = _deprecated
+"""
+Using this decorator **exactly as described**, ensures the message is displayed to a static type checker.
+
+**BE CAREFUL USING THIS**.
+
+See screenshots in `comment`_ for motivation.
+
+Every use should look like::
+
+ @deprecated_static_only(
+ "Deprecated in `altair=5.5.0`. Use altair.other instead.",
+ category=None,
+ )
+ def old_function(*args): ...
+
+If a runtime warning is desired, use `@alt.utils.deprecated` instead.
+
+.. _comment:
+ https://github.com/vega/altair/pull/3618#issuecomment-2423991968
+---
+"""
+
+
class _WarningsMonitor:
def __init__(self) -> None:
self._warned: dict[LiteralString, Literal[True]] = {}
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index d57d88b4d..7dc5fc142 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -4,6 +4,7 @@
from typing import TYPE_CHECKING, Any, Final, Literal, get_args
+from altair.utils.deprecation import deprecated_static_only
from altair.utils.plugin_registry import Plugin, PluginRegistry
from altair.vegalite.v5.schema._config import ThemeConfig
from altair.vegalite.v5.schema._typing import VegaThemes
@@ -69,6 +70,15 @@ def names(self) -> list[str]:
"""Return the names of the registered and entry points themes."""
return super().names()
+ @deprecated_static_only(
+ "Deprecated in `altair=5.5.0`. Use @altair.theme.register instead.",
+ category=None,
+ )
+ def register(
+ self, name: str, value: Plugin[ThemeConfig] | None
+ ) -> Plugin[ThemeConfig] | None:
+ return super().register(name, value)
+
class VegaTheme:
"""Implementation of a builtin vega theme."""
From a0497e0a5461274cceb06ffd26bf92f9d2ce60fe Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 20 Oct 2024 11:47:42 +0100
Subject: [PATCH 43/44] docs: "Deprecated in" -> "Deprecated since"
https://github.com/vega/altair/pull/3618#issuecomment-2424199204, https://github.com/vega/altair/pull/3618#issuecomment-2424214358
---
altair/utils/deprecation.py | 4 ++--
altair/vegalite/v5/theme.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index f32d6c183..b0b2c857e 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -34,7 +34,7 @@ def _format_message(
message: LiteralString | None,
/,
) -> LiteralString:
- output = f"\nDeprecated in `altair={version}`."
+ output = f"\nDeprecated since `altair={version}`."
if alternative:
output = f"{output} Use {alternative} instead."
return f"{output}\n{message}" if message else output
@@ -140,7 +140,7 @@ def deprecated_warn(
Every use should look like::
@deprecated_static_only(
- "Deprecated in `altair=5.5.0`. Use altair.other instead.",
+ "Deprecated since `altair=5.5.0`. Use altair.other instead.",
category=None,
)
def old_function(*args): ...
diff --git a/altair/vegalite/v5/theme.py b/altair/vegalite/v5/theme.py
index 7dc5fc142..77f480829 100644
--- a/altair/vegalite/v5/theme.py
+++ b/altair/vegalite/v5/theme.py
@@ -71,7 +71,7 @@ def names(self) -> list[str]:
return super().names()
@deprecated_static_only(
- "Deprecated in `altair=5.5.0`. Use @altair.theme.register instead.",
+ "Deprecated since `altair=5.5.0`. Use @altair.theme.register instead.",
category=None,
)
def register(
From b84fd5f9a86c39c56f7d9df1ef872e029461f866 Mon Sep 17 00:00:00 2001
From: dangotbanned <125183946+dangotbanned@users.noreply.github.com>
Date: Sun, 20 Oct 2024 12:09:25 +0100
Subject: [PATCH 44/44] docs: Add more specific constraints to
`@deprecated_static_only`
The rules here are not the same as the rules for [`LiteralString`](https://typing.readthedocs.io/en/latest/spec/literal.html#literalstring)
Hoping to avoid any confusion if anyone uses this later
---
altair/utils/deprecation.py | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/altair/utils/deprecation.py b/altair/utils/deprecation.py
index b0b2c857e..2b07a229e 100644
--- a/altair/utils/deprecation.py
+++ b/altair/utils/deprecation.py
@@ -131,7 +131,7 @@ def deprecated_warn(
deprecated_static_only = _deprecated
"""
-Using this decorator **exactly as described**, ensures the message is displayed to a static type checker.
+Using this decorator **exactly as described**, ensures ``message`` is displayed to a static type checker.
**BE CAREFUL USING THIS**.
@@ -147,6 +147,17 @@ def old_function(*args): ...
If a runtime warning is desired, use `@alt.utils.deprecated` instead.
+Parameters
+----------
+message : LiteralString
+ - **Not** a variable
+ - **Not** use placeholders
+ - **Not** use concatenation
+ - **Do not use anything that could be considered dynamic**
+
+category : None
+ You **need** to explicitly pass ``None``
+
.. _comment:
https://github.com/vega/altair/pull/3618#issuecomment-2423991968
---