From 0f2d9e86a8f4b99e0528f037bb4aa79183949116 Mon Sep 17 00:00:00 2001 From: jorenham Date: Wed, 21 Oct 2026 15:24:27 +0200 Subject: [PATCH 1/2] Drop support for Python <3.11 (EOL) --- .github/workflows/test_code.yml | 2 +- .github/workflows/test_selenium.yml | 2 +- pyproject.toml | 4 ++-- setup.py | 4 +--- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test_code.yml b/.github/workflows/test_code.yml index 138f300651..455622c3e6 100644 --- a/.github/workflows/test_code.yml +++ b/.github/workflows/test_code.yml @@ -20,7 +20,7 @@ jobs: strategy: matrix: os: [ ubuntu-latest, windows-latest, macos-latest ] - python-version: [ "3.10", "3.14" ] + python-version: [ "3.11", "3.14" ] fail-fast: false defaults: run: diff --git a/.github/workflows/test_selenium.yml b/.github/workflows/test_selenium.yml index dd494460cd..4d6b14a461 100644 --- a/.github/workflows/test_selenium.yml +++ b/.github/workflows/test_selenium.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.9", "3.14" ] + python-version: [ "3.11", "3.14" ] fail-fast: false defaults: run: diff --git a/pyproject.toml b/pyproject.toml index 51c532f0c4..ea6d1ec914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dynamic = [ "dependencies", "optional-dependencies", ] -requires-python = ">=3.9" +requires-python = ">=3.11" [tool.interrogate] ignore-init-method = true @@ -39,7 +39,7 @@ lint.select = [ "I", # import sorting "U", # upgrade ] -target-version = "py39" +target-version = "py311" line-length = 120 [lint.per-file-ignores] diff --git a/setup.py b/setup.py index a2cf34823a..e3383c4df2 100644 --- a/setup.py +++ b/setup.py @@ -53,8 +53,6 @@ def walk_subpkg(name): url="https://github.com/python-visualization/folium", keywords="data visualization", classifiers=[ - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -66,7 +64,7 @@ def walk_subpkg(name): platforms="any", packages=packages, package_data=package_data, - python_requires=">=3.9", + python_requires=">=3.11", extras_require={"testing": ["pytest"]}, install_requires=install_requires, zip_safe=False, From 088624f67101fb7f67c9537d8ac91b8a2ee5ecb6 Mon Sep 17 00:00:00 2001 From: jorenham Date: Wed, 21 Oct 2026 15:31:34 +0200 Subject: [PATCH 2/2] fix new ruff (pyupgrade) errors using `ruff check --fix` (mostly) --- docs/conf.py | 2 +- folium/features.py | 117 +++++++++--------- folium/folium.py | 36 +++--- folium/map.py | 30 ++--- folium/plugins/geocoder.py | 4 +- .../plugins/overlapping_marker_spiderfier.py | 4 +- folium/plugins/realtime.py | 10 +- folium/plugins/timeline.py | 10 +- folium/plugins/treelayercontrol.py | 6 +- folium/plugins/vectorgrid_protobuf.py | 6 +- folium/raster_layers.py | 23 ++-- folium/template.py | 3 +- folium/utilities.py | 43 +++---- folium/vector_layers.py | 31 +++-- 14 files changed, 153 insertions(+), 172 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c40af8b500..19fcdfd776 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -49,7 +49,7 @@ # General information about the project. import datetime -year = datetime.datetime.now(tz=datetime.timezone.utc).date().year +year = datetime.datetime.now(tz=datetime.UTC).date().year project = "Folium" copyright = f"2013-{year}, Rob Story" diff --git a/folium/features.py b/folium/features.py index 587f5e053b..813efceb3f 100644 --- a/folium/features.py +++ b/folium/features.py @@ -7,10 +7,9 @@ import json import operator import warnings -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from typing import ( Any, - Callable, Optional, Union, get_args, @@ -103,8 +102,8 @@ def __init__( number_of_sides: int = 4, rotation: int = 0, radius: int = 15, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, **kwargs: TypePathOptions, ): super().__init__(location, popup=popup, tooltip=tooltip) @@ -159,10 +158,10 @@ class Vega(JSCSSMixin): def __init__( self, data: Any, - width: Union[int, str, None] = None, - height: Union[int, str, None] = None, - left: Union[int, str] = "0%", - top: Union[int, str] = "0%", + width: int | str | None = None, + height: int | str | None = None, + left: int | str = "0%", + top: int | str = "0%", position: str = "relative", ): super().__init__() @@ -264,10 +263,10 @@ class VegaLite(MacroElement): def __init__( self, data: Any, - width: Union[int, str, None] = None, - height: Union[int, str, None] = None, - left: Union[int, str] = "0%", - top: Union[int, str] = "0%", + width: int | str | None = None, + height: int | str | None = None, + left: int | str = "0%", + top: int | str = "0%", position: str = "relative", ): super(self.__class__, self).__init__() @@ -338,7 +337,7 @@ def render(self, **kwargs): embed_vegalite(figure=figure, parent=parent) @property - def vegalite_major_version(self) -> Optional[int]: + def vegalite_major_version(self) -> int | None: if "$schema" not in self.data: return None @@ -678,26 +677,26 @@ class GeoJson(Layer): def __init__( self, data: Any, - style_function: Optional[Callable] = None, - highlight_function: Optional[Callable] = None, + style_function: Callable | None = None, + highlight_function: Callable | None = None, popup_keep_highlighted: bool = False, - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, - smooth_factor: Optional[float] = None, + smooth_factor: float | None = None, tooltip: Union[str, Tooltip, "GeoJsonTooltip", None] = None, embed: bool = True, popup: Optional["GeoJsonPopup"] = None, zoom_on_click: bool = False, - on_each_feature: Optional[JsCode] = None, - marker: Union[Circle, CircleMarker, Marker, None] = None, + on_each_feature: JsCode | None = None, + marker: Circle | CircleMarker | Marker | None = None, **kwargs: Any, ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "GeoJson" self.embed = embed - self.embed_link: Optional[str] = None + self.embed_link: str | None = None self.json = None self.parent_map = None self.smooth_factor = smooth_factor @@ -845,7 +844,7 @@ def find_identifier(self) -> str: "field to your geojson data or set `embed=True`. " ) - def _get_self_bounds(self) -> list[list[Optional[float]]]: + def _get_self_bounds(self) -> list[list[float | None]]: """ Computes the bounds of the object itself (not including it's children) in the form [[lat_min, lon_min], [lat_max, lon_max]]. @@ -865,7 +864,7 @@ def render(self, **kwargs): super().render() -TypeStyleMapping = dict[str, Union[str, list[Union[str, int]]]] +TypeStyleMapping = dict[str, str | list[str | int]] class GeoJsonStyleMapper: @@ -912,7 +911,7 @@ def _create_mapping(self, func: Callable, switch: str) -> TypeStyleMapping: self._set_default_key(mapping) return mapping - def get_feature_id(self, feature: dict) -> Union[str, int]: + def get_feature_id(self, feature: dict) -> str | int: """Return a value identifying the feature.""" prefix = "feature.properties[" if self.feature_identifier.startswith(prefix): @@ -1025,13 +1024,13 @@ def __init__( self, data: Any, object_path: str, - style_function: Optional[Callable] = None, - name: Optional[str] = None, + style_function: Callable | None = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, - smooth_factor: Optional[float] = None, - tooltip: Union[str, Tooltip, None] = None, + smooth_factor: float | None = None, + tooltip: str | Tooltip | None = None, ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "TopoJson" @@ -1161,10 +1160,10 @@ class GeoJsonDetail(MacroElement): def __init__( self, fields: Sequence[str], - aliases: Optional[Sequence[str]] = None, + aliases: Sequence[str] | None = None, labels: bool = True, localize: bool = False, - style: Optional[str] = None, + style: str | None = None, class_name: str = "geojsondetail", ): super().__init__() @@ -1311,10 +1310,10 @@ class GeoJsonTooltip(GeoJsonDetail): def __init__( self, fields: Sequence[str], - aliases: Optional[Sequence[str]] = None, + aliases: Sequence[str] | None = None, labels: bool = True, localize: bool = False, - style: Optional[str] = None, + style: str | None = None, class_name: str = "foliumtooltip", sticky: bool = True, **kwargs: TypeJsonValue, @@ -1379,7 +1378,7 @@ class GeoJsonPopup(GeoJsonDetail): def __init__( self, fields: Sequence[str], - aliases: Optional[Sequence[str]] = None, + aliases: Sequence[str] | None = None, labels: bool = True, style: str = "margin: auto;", class_name: str = "foliumpopup", @@ -1520,24 +1519,24 @@ class Choropleth(FeatureGroup): def __init__( self, geo_data: Any, - data: Optional[Any] = None, - columns: Optional[Sequence[Any]] = None, - key_on: Optional[str] = None, - bins: Union[int, Sequence[float]] = 6, - fill_color: Optional[str] = None, + data: Any | None = None, + columns: Sequence[Any] | None = None, + key_on: str | None = None, + bins: int | Sequence[float] = 6, + fill_color: str | None = None, nan_fill_color: str = "black", fill_opacity: float = 0.6, - nan_fill_opacity: Optional[float] = None, + nan_fill_opacity: float | None = None, line_color: str = "black", line_weight: float = 1, line_opacity: float = 1, - name: Optional[str] = None, + name: str | None = None, legend_name: str = "", overlay: bool = True, control: bool = True, show: bool = True, - topojson: Optional[str] = None, - smooth_factor: Optional[float] = None, + topojson: str | None = None, + smooth_factor: float | None = None, highlight: bool = False, use_jenks: bool = False, **kwargs, @@ -1668,7 +1667,7 @@ def highlight_function(x): return {"weight": line_weight + 2, "fillOpacity": fill_opacity + 0.2} if topojson: - self.geojson: Union[TopoJson, GeoJson] = TopoJson( + self.geojson: TopoJson | GeoJson = TopoJson( geo_data, topojson, style_function=style_function, @@ -1687,7 +1686,7 @@ def highlight_function(x): self.add_child(self.color_scale) @classmethod - def _get_by_key(cls, obj: Union[dict, list], key: str) -> Union[float, str, None]: + def _get_by_key(cls, obj: dict | list, key: str) -> float | str | None: key_parts = key.split(".") first_key_part = key_parts[0] if first_key_part.isdigit(): @@ -1748,10 +1747,10 @@ class DivIcon(MacroElement): def __init__( self, - html: Optional[str] = None, - icon_size: Optional[tuple[int, int]] = None, - icon_anchor: Optional[tuple[int, int]] = None, - popup_anchor: Optional[tuple[int, int]] = None, + html: str | None = None, + icon_size: tuple[int, int] | None = None, + icon_anchor: tuple[int, int] | None = None, + popup_anchor: tuple[int, int] | None = None, class_name: str = "empty", ): super().__init__() @@ -1825,7 +1824,7 @@ class ClickForMarker(MacroElement): {% endmacro %} """) # noqa - def __init__(self, popup: Union[IFrame, Html, str, None] = None): + def __init__(self, popup: IFrame | Html | str | None = None): super().__init__() self._name = "ClickForMarker" @@ -1866,7 +1865,7 @@ class ClickForLatLng(MacroElement): {% endmacro %} """) # noqa - def __init__(self, format_str: Optional[str] = None, alert: bool = True): + def __init__(self, format_str: str | None = None, alert: bool = True): super().__init__() self._name = "ClickForLatLng" self.format_str = format_str or 'lat + "," + lng' @@ -1917,12 +1916,12 @@ class CustomIcon(Icon): def __init__( self, icon_image: Any, - icon_size: Optional[tuple[int, int]] = None, - icon_anchor: Optional[tuple[int, int]] = None, + icon_size: tuple[int, int] | None = None, + icon_anchor: tuple[int, int] | None = None, shadow_image: Any = None, - shadow_size: Optional[tuple[int, int]] = None, - shadow_anchor: Optional[tuple[int, int]] = None, - popup_anchor: Optional[tuple[int, int]] = None, + shadow_size: tuple[int, int] | None = None, + shadow_anchor: tuple[int, int] | None = None, + popup_anchor: tuple[int, int] | None = None, ): super(Icon, self).__init__() self._name = "icon" @@ -1970,10 +1969,10 @@ def __init__( self, positions: TypeLine, colors: Iterable[float], - colormap: Union[ColorMap, Sequence[Any], None] = None, + colormap: ColorMap | Sequence[Any] | None = None, nb_steps: int = 12, - weight: Optional[int] = None, - opacity: Optional[float] = None, + weight: int | None = None, + opacity: float | None = None, **kwargs: Any, ): super().__init__(**kwargs) @@ -2045,9 +2044,9 @@ class Control(JSCSSMixin, Class): def __init__( self, - control: Optional[str] = None, + control: str | None = None, *args, - position: Optional[TypePosition] = None, + position: TypePosition | None = None, **kwargs, ): super().__init__() diff --git a/folium/folium.py b/folium/folium.py index c22b83fbd5..335ba7cd92 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -7,7 +7,7 @@ import webbrowser from collections.abc import Sequence from importlib.resources import files as _resource_files -from typing import Any, Optional, Union +from typing import Any from branca.element import Element, Figure @@ -273,16 +273,16 @@ class Map(JSCSSMixin, Evented): def __init__( self, - location: Optional[Sequence[float]] = None, - width: Union[str, float] = "100%", - height: Union[str, float] = "100%", - left: Union[str, float] = "0%", - top: Union[str, float] = "0%", + location: Sequence[float] | None = None, + width: str | float = "100%", + height: str | float = "100%", + left: str | float = "0%", + top: str | float = "0%", position: str = "relative", - tiles: Union[str, TileLayer, None] = "OpenStreetMap", - attr: Optional[str] = None, - min_zoom: Optional[int] = None, - max_zoom: Optional[int] = None, + tiles: str | TileLayer | None = "OpenStreetMap", + attr: str | None = None, + min_zoom: int | None = None, + max_zoom: int | None = None, zoom_start: int = 10, min_lat: float = -90, max_lat: float = 90, @@ -295,14 +295,14 @@ def __init__( no_touch: bool = False, disable_3d: bool = False, png_enabled: bool = False, - zoom_control: Union[bool, str] = True, + zoom_control: bool | str = True, font_size: str = "1rem", **kwargs: TypeJsonValue, ): super().__init__() self._name = "Map" - self._png_image: Optional[bytes] = None + self._png_image: bytes | None = None self.png_enabled = png_enabled if location is None: @@ -374,7 +374,7 @@ def _repr_html_(self, **kwargs) -> str: return out def _to_png( - self, delay: int = 3, driver: Any = None, size: Optional[Sequence[int]] = None + self, delay: int = 3, driver: Any = None, size: Sequence[int] | None = None ) -> bytes: """Export the HTML to byte representation of a PNG image. @@ -426,7 +426,7 @@ def _to_png( self._png_image = png return self._png_image - def _repr_png_(self) -> Optional[bytes]: + def _repr_png_(self) -> bytes | None: """Displays the PNG Map in a Jupyter notebook.""" # The notebook calls all _repr_*_ by default. # We don't want that here b/c this one is quite slow. @@ -452,10 +452,10 @@ def show_in_browser(self) -> None: def fit_bounds( self, bounds: TypeBounds, - padding_top_left: Optional[Sequence[float]] = None, - padding_bottom_right: Optional[Sequence[float]] = None, - padding: Optional[Sequence[float]] = None, - max_zoom: Optional[int] = None, + padding_top_left: Sequence[float] | None = None, + padding_bottom_right: Sequence[float] | None = None, + padding: Sequence[float] | None = None, + max_zoom: int | None = None, ) -> None: """Fit the map to contain a bounding box with the maximum zoom level possible. diff --git a/folium/map.py b/folium/map.py index 62bdf50ad8..53694b692a 100644 --- a/folium/map.py +++ b/folium/map.py @@ -6,7 +6,7 @@ import warnings from collections import OrderedDict, defaultdict from collections.abc import Sequence -from typing import TYPE_CHECKING, Optional, Union, cast +from typing import TYPE_CHECKING, Union, cast from branca.element import Element, Figure, Html, MacroElement @@ -116,7 +116,7 @@ class Layer(Evented): def __init__( self, - name: Optional[str] = None, + name: str | None = None, overlay: bool = False, control: bool = True, show: bool = True, @@ -174,7 +174,7 @@ class FeatureGroup(Layer): def __init__( self, - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, @@ -225,7 +225,7 @@ class LayerGroup(Layer): def __init__( self, - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, @@ -537,10 +537,10 @@ def render(self, **kwargs): def __init__( self, - location: Optional[Sequence[float]] = None, + location: Sequence[float] | None = None, popup: Union["Popup", str, None] = None, tooltip: Union["Tooltip", str, None] = None, - icon: Optional[Union[Icon, "CustomIcon", "DivIcon"]] = None, + icon: Union[Icon, "CustomIcon", "DivIcon"] | None = None, draggable: bool = False, **kwargs: TypeJsonValue, ): @@ -636,9 +636,9 @@ class Popup(MacroElement): def __init__( self, - html: Union[str, Element, None] = None, + html: str | Element | None = None, parse_html: bool = False, - max_width: Union[str, int] = "100%", + max_width: str | int = "100%", show: bool = False, sticky: bool = False, lazy: bool = False, @@ -721,7 +721,7 @@ class Tooltip(MacroElement): def __init__( self, text: str, - style: Optional[str] = None, + style: str | None = None, sticky: bool = True, **kwargs: TypeJsonValue, ): @@ -774,10 +774,10 @@ class FitBounds(MacroElement): def __init__( self, bounds: TypeBounds, - padding_top_left: Optional[Sequence[float]] = None, - padding_bottom_right: Optional[Sequence[float]] = None, - padding: Optional[Sequence[float]] = None, - max_zoom: Optional[int] = None, + padding_top_left: Sequence[float] | None = None, + padding_bottom_right: Sequence[float] | None = None, + padding: Sequence[float] | None = None, + max_zoom: int | None = None, ): super().__init__() self._name = "FitBounds" @@ -828,7 +828,7 @@ class FitOverlays(MacroElement): def __init__( self, padding: int = 0, - max_zoom: Optional[int] = None, + max_zoom: int | None = None, fly: bool = False, fit_on_map_load: bool = True, ): @@ -877,7 +877,7 @@ class CustomPane(MacroElement): def __init__( self, name: str, - z_index: Union[int, str] = 625, + z_index: int | str = 625, pointer_events: bool = False, ): super().__init__() diff --git a/folium/plugins/geocoder.py b/folium/plugins/geocoder.py index 17fcb6633e..52ba060616 100644 --- a/folium/plugins/geocoder.py +++ b/folium/plugins/geocoder.py @@ -1,5 +1,3 @@ -from typing import Optional - from branca.element import MacroElement from folium.elements import JSCSSMixin @@ -73,7 +71,7 @@ def __init__( collapsed: bool = False, position: str = "topright", add_marker: bool = True, - zoom: Optional[int] = 11, + zoom: int | None = 11, provider: str = "nominatim", provider_options: dict = {}, **kwargs, diff --git a/folium/plugins/overlapping_marker_spiderfier.py b/folium/plugins/overlapping_marker_spiderfier.py index 219510ecf7..49cf5e7053 100644 --- a/folium/plugins/overlapping_marker_spiderfier.py +++ b/folium/plugins/overlapping_marker_spiderfier.py @@ -1,5 +1,3 @@ -from typing import Optional - from jinja2 import Template from folium.elements import Element, JSCSSMixin, MacroElement @@ -86,7 +84,7 @@ def __init__( ) def add_to( - self, parent: Element, name: Optional[str] = None, index: Optional[int] = None + self, parent: Element, name: str | None = None, index: int | None = None ) -> Element: self._parent = parent self.markers = self._get_all_markers(parent) diff --git a/folium/plugins/realtime.py b/folium/plugins/realtime.py index d6e6b6b447..d953bb3cb5 100644 --- a/folium/plugins/realtime.py +++ b/folium/plugins/realtime.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from folium.elements import JSCSSMixin from folium.features import GeoJson from folium.map import FeatureGroup @@ -91,13 +89,13 @@ class Realtime(JSCSSMixin, FeatureGroup): def __init__( self, - source: Union[str, dict, JsCode], + source: str | dict | JsCode, start: bool = True, interval: int = 60000, - get_feature_id: Union[JsCode, str, None] = None, - update_feature: Union[JsCode, str, None] = None, + get_feature_id: JsCode | str | None = None, + update_feature: JsCode | str | None = None, remove_missing: bool = False, - container: Optional[Union[FeatureGroup, GeoJson]] = None, + container: FeatureGroup | GeoJson | None = None, **kwargs, ): super().__init__() diff --git a/folium/plugins/timeline.py b/folium/plugins/timeline.py index 4d701d3a85..a48ca36e56 100644 --- a/folium/plugins/timeline.py +++ b/folium/plugins/timeline.py @@ -1,4 +1,4 @@ -from typing import Optional, TextIO, Union +from typing import TextIO from branca.element import MacroElement @@ -103,8 +103,8 @@ class Timeline(GeoJson): def __init__( self, - data: Union[dict, str, TextIO], - get_interval: Optional[JsCode] = None, + data: dict | str | TextIO, + get_interval: JsCode | None = None, **kwargs, ): super().__init__(data) @@ -204,8 +204,8 @@ def __init__( # arguments relevant to both interval and timestamp mode auto_play: bool = True, date_options: str = "YYYY-MM-DD HH:mm:ss", - start: Optional[Union[str, int, float]] = None, - end: Optional[Union[str, int, float]] = None, + start: str | int | float | None = None, + end: str | int | float | None = None, enable_playback: bool = True, enable_keyboard_controls: bool = False, show_ticks: bool = True, diff --git a/folium/plugins/treelayercontrol.py b/folium/plugins/treelayercontrol.py index a2e225a49e..69d2ed949c 100644 --- a/folium/plugins/treelayercontrol.py +++ b/folium/plugins/treelayercontrol.py @@ -1,5 +1,3 @@ -from typing import Union - from branca.element import MacroElement from folium.elements import JSCSSMixin @@ -134,8 +132,8 @@ class TreeLayerControl(JSCSSMixin, MacroElement): def __init__( self, - base_tree: Union[dict, list, None] = None, - overlay_tree: Union[dict, list, None] = None, + base_tree: dict | list | None = None, + overlay_tree: dict | list | None = None, closed_symbol: str = "+", opened_symbol: str = "-", space_symbol: str = " ", diff --git a/folium/plugins/vectorgrid_protobuf.py b/folium/plugins/vectorgrid_protobuf.py index 73184863bb..3438541a38 100644 --- a/folium/plugins/vectorgrid_protobuf.py +++ b/folium/plugins/vectorgrid_protobuf.py @@ -1,5 +1,3 @@ -from typing import Optional, Union - from folium.elements import JSCSSMixin from folium.map import Layer from folium.template import Template @@ -124,8 +122,8 @@ class VectorGridProtobuf(JSCSSMixin, Layer): def __init__( self, url: str, - name: Optional[str] = None, - options: Union[str, dict, None] = None, + name: str | None = None, + options: str | dict | None = None, overlay: bool = True, control: bool = True, show: bool = True, diff --git a/folium/raster_layers.py b/folium/raster_layers.py index 8bef5e9673..e5b2ba869c 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -3,7 +3,8 @@ """ -from typing import Any, Callable, Optional, Union +from collections.abc import Callable +from typing import Any import xyzservices @@ -91,13 +92,13 @@ class TileLayer(Layer): def __init__( self, - tiles: Union[str, xyzservices.TileProvider] = "OpenStreetMap", - min_zoom: Optional[int] = None, - max_zoom: Optional[int] = None, - max_native_zoom: Optional[int] = None, - attr: Optional[str] = None, + tiles: str | xyzservices.TileProvider = "OpenStreetMap", + min_zoom: int | None = None, + max_zoom: int | None = None, + max_native_zoom: int | None = None, + attr: str | None = None, detect_retina: bool = False, - name: Optional[str] = None, + name: str | None = None, overlay: bool = False, control: bool = True, show: bool = True, @@ -208,7 +209,7 @@ def __init__( transparent: bool = False, version: str = "1.1.1", attr: str = "", - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, @@ -312,10 +313,10 @@ def __init__( image: Any, bounds: TypeBounds, origin: str = "upper", - colormap: Optional[Callable] = None, + colormap: Callable | None = None, mercator_project: bool = False, pixelated: bool = True, - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, @@ -385,7 +386,7 @@ def __init__( bounds: TypeBounds, autoplay: bool = True, loop: bool = True, - name: Optional[str] = None, + name: str | None = None, overlay: bool = True, control: bool = True, show: bool = True, diff --git a/folium/template.py b/folium/template.py index 1d3cb6aa0d..b76c9fc1b2 100644 --- a/folium/template.py +++ b/folium/template.py @@ -1,5 +1,4 @@ import json -from typing import Union import jinja2 from branca.element import Element @@ -7,7 +6,7 @@ from folium.utilities import JsCode, TypeJsonValue, camelize -def tojavascript(obj: Union[str, JsCode, dict, list, Element]) -> str: +def tojavascript(obj: str | JsCode | dict | list | Element) -> str: if isinstance(obj, JsCode): return obj.js_code elif isinstance(obj, Element): diff --git a/folium/utilities.py b/folium/utilities.py index e9807ad91d..008aabf78f 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -6,16 +6,9 @@ import os import tempfile import uuid -from collections.abc import Iterable, Iterator, Sequence +from collections.abc import Callable, Iterable, Iterator, Sequence from contextlib import contextmanager -from typing import ( - TYPE_CHECKING, - Any, - Callable, - Literal, - Optional, - Union, -) +from typing import TYPE_CHECKING, Any, Literal, TypeAlias from urllib.parse import urlparse, uses_netloc, uses_params, uses_relative import numpy as np @@ -39,19 +32,19 @@ from .features import Popup -TypeLine = Iterable[Sequence[float]] -TypeMultiLine = Union[TypeLine, Iterable[TypeLine]] +TypeLine: TypeAlias = Iterable[Sequence[float]] +TypeMultiLine: TypeAlias = TypeLine | Iterable[TypeLine] -TypeJsonValueNoNone = Union[str, float, bool, Sequence, dict] -TypeJsonValue = Union[TypeJsonValueNoNone, None] +TypeJsonValueNoNone: TypeAlias = str | float | bool | Sequence | dict +TypeJsonValue: TypeAlias = TypeJsonValueNoNone | None -TypePathOptions = Union[bool, str, float, None] +TypePathOptions: TypeAlias = bool | str | float | None -TypeBounds = Sequence[Sequence[float]] -TypeBoundsReturn = list[list[Optional[float]]] +TypeBounds: TypeAlias = Sequence[Sequence[float]] +TypeBoundsReturn: TypeAlias = list[list[float | None]] -TypeContainer = Union[Figure, Div, "Popup"] -TypePosition = Literal["bottomright", "bottomleft", "topright", "topleft"] +TypeContainer: TypeAlias = "Figure | Div | Popup" +TypePosition: TypeAlias = Literal["bottomright", "bottomleft", "topright", "topleft"] _VALID_URLS = set(uses_relative + uses_netloc + uses_params) @@ -130,7 +123,7 @@ def validate_locations(locations: TypeLine) -> list[list[float]]: def validate_multi_locations( locations: TypeMultiLine, -) -> Union[list[list[float]], list[list[list[float]]]]: +) -> list[list[float]] | list[list[list[float]]]: """Validate an iterable with possibly nested lists of coordinate pairs.""" locations = if_pandas_df_convert_to_numpy(locations) if isinstance(locations, Iterator): @@ -163,7 +156,7 @@ def if_pandas_df_convert_to_numpy(obj: Any) -> Any: def image_to_url( image: Any, - colormap: Optional[Callable] = None, + colormap: Callable | None = None, origin: str = "upper", ) -> str: """ @@ -217,7 +210,7 @@ def mercator_transform( data: Any, lat_bounds: tuple[float, float], origin: str = "upper", - height_out: Optional[int] = None, + height_out: int | None = None, ) -> np.ndarray: """ Transforms an image computed in (longitude,latitude) coordinates into @@ -305,13 +298,13 @@ def iter_coords(obj: Any) -> Iterator[tuple[float, ...]]: def get_bounds( locations: Any, lonlat: bool = False, -) -> list[list[Optional[float]]]: +) -> list[list[float | None]]: """ Computes the bounds of the object in the form [[lat_min, lon_min], [lat_max, lon_max]] """ - bounds: list[list[Optional[float]]] = [[None, None], [None, None]] + bounds: list[list[float | None]] = [[None, None], [None, None]] for point in iter_coords(locations): bounds = [ [ @@ -435,7 +428,7 @@ def get_and_assert_figure_root(obj: Element) -> Figure: class JsCode: """Wrapper around Javascript code.""" - def __init__(self, js_code: Union[str, "JsCode"]): + def __init__(self, js_code: "str | JsCode"): if isinstance(js_code, JsCode): self.js_code: str = js_code.js_code else: @@ -445,7 +438,7 @@ def __str__(self): return self.js_code -def parse_font_size(value: Union[str, int, float]) -> str: +def parse_font_size(value: str | int | float) -> str: """Parse a font size value, if number set as px""" if isinstance(value, (int, float)): return f"{value}px" diff --git a/folium/vector_layers.py b/folium/vector_layers.py index 7a822c57fb..d218634ba3 100644 --- a/folium/vector_layers.py +++ b/folium/vector_layers.py @@ -4,7 +4,6 @@ """ from collections.abc import Sequence -from typing import Optional, Union from branca.element import MacroElement @@ -22,7 +21,7 @@ def path_options( - line: bool = False, radius: Optional[float] = None, **kwargs: TypePathOptions + line: bool = False, radius: float | None = None, **kwargs: TypePathOptions ): """ Contains options and constants shared between vector overlays @@ -141,8 +140,8 @@ class BaseMultiLocation(MacroElement): def __init__( self, locations: TypeMultiLine, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, ): super().__init__() self.locations = validate_multi_locations(locations) @@ -153,7 +152,7 @@ def __init__( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) - def _get_self_bounds(self) -> list[list[Optional[float]]]: + def _get_self_bounds(self) -> list[list[float | None]]: """Compute the bounds of the object itself.""" return get_bounds(self.locations) @@ -233,8 +232,8 @@ class Polygon(BaseMultiLocation): def __init__( self, locations: TypeMultiLine, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, **kwargs: TypePathOptions, ): super().__init__(locations, popup=popup, tooltip=tooltip) @@ -273,8 +272,8 @@ class Rectangle(MacroElement): def __init__( self, bounds: TypeLine, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, **kwargs: TypePathOptions, ): super().__init__() @@ -289,7 +288,7 @@ def __init__( tooltip if isinstance(tooltip, Tooltip) else Tooltip(str(tooltip)) ) - def _get_self_bounds(self) -> list[list[Optional[float]]]: + def _get_self_bounds(self) -> list[list[float | None]]: """Compute the bounds of the object itself.""" return get_bounds(self.locations) @@ -330,10 +329,10 @@ class Circle(Marker): def __init__( self, - location: Optional[Sequence[float]] = None, + location: Sequence[float] | None = None, radius: float = 50, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, **kwargs: TypePathOptions, ): super().__init__(location, popup=popup, tooltip=tooltip) @@ -374,10 +373,10 @@ class CircleMarker(Marker): def __init__( self, - location: Optional[Sequence[float]] = None, + location: Sequence[float] | None = None, radius: float = 10, - popup: Union[Popup, str, None] = None, - tooltip: Union[Tooltip, str, None] = None, + popup: Popup | str | None = None, + tooltip: Tooltip | str | None = None, **kwargs: TypePathOptions, ): super().__init__(location, popup=popup, tooltip=tooltip)