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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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."""