diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0c341f496d..854eb67263 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,6 @@ ```python # Your code here - ``` #### Problem description diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 4937f76870..ee31e5cb6a 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -34,7 +34,7 @@ jobs: environment-name: TEST init-shell: bash create-args: >- - python=3.12 + python=3 --file requirements.txt --file requirements-dev.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27ce4ea7f5..a7aa1aa2cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: files: requirements-dev.txt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.12 + rev: v0.12.2 hooks: - id: ruff @@ -28,7 +28,7 @@ repos: language_version: python3 - repo: https://github.com/keewis/blackdoc - rev: v0.3.9 + rev: v0.4.1 hooks: - id: blackdoc @@ -44,6 +44,11 @@ repos: .*\.json | )$ +# - repo: https://github.com/woodruffw/zizmor-pre-commit +# rev: v1.11.0 +# hooks: +# - id: zizmor + ci: autofix_commit_msg: | [pre-commit.ci] auto fixes from pre-commit.com hooks diff --git a/folium/elements.py b/folium/elements.py index f52e8b6fa0..327d92a9a5 100644 --- a/folium/elements.py +++ b/folium/elements.py @@ -1,5 +1,4 @@ from functools import wraps -from typing import List, Tuple from branca.element import ( CssLink, @@ -24,8 +23,8 @@ def inner(self, *args, **kwargs): class JSCSSMixin(MacroElement): """Render links to external Javascript and CSS resources.""" - default_js: List[Tuple[str, str]] = [] - default_css: List[Tuple[str, str]] = [] + default_js: list[tuple[str, str]] = [] + default_css: list[tuple[str, str]] = [] # Since this is typically used as a mixin, we cannot # override the _template member variable here. It would @@ -53,7 +52,7 @@ def add_js_link(self, name: str, url: str): """Add or update JS resource link.""" self._add_link(name, url, self.default_js) - def _add_link(self, name: str, url: str, default_list: List[Tuple[str, str]]): + def _add_link(self, name: str, url: str, default_list: list[tuple[str, str]]): """Modify a css or js link. If `name` does not exist, the link will be appended diff --git a/folium/features.py b/folium/features.py index 8f7e5230c7..0949cc45ac 100644 --- a/folium/features.py +++ b/folium/features.py @@ -7,15 +7,11 @@ import json import operator import warnings +from collections.abc import Iterable, Sequence from typing import ( Any, Callable, - Dict, - Iterable, - List, Optional, - Sequence, - Tuple, Union, get_args, ) @@ -348,7 +344,7 @@ def render(self, **kwargs): name=self.get_name(), ) - embed_mapping: Dict[Optional[int], Callable] = { + embed_mapping = { 1: self._embed_vegalite_v1, 2: self._embed_vegalite_v2, 3: self._embed_vegalite_v3, @@ -851,7 +847,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[Optional[float]]]: """ Computes the bounds of the object itself (not including it's children) in the form [[lat_min, lon_min], [lat_max, lon_max]]. @@ -871,7 +867,7 @@ def render(self, **kwargs): super().render() -TypeStyleMapping = Dict[str, Union[str, List[Union[str, int]]]] +TypeStyleMapping = dict[str, Union[str, list[Union[str, int]]]] class GeoJsonStyleMapper: @@ -1756,9 +1752,9 @@ 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, + icon_size: Optional[tuple[int, int]] = None, + icon_anchor: Optional[tuple[int, int]] = None, + popup_anchor: Optional[tuple[int, int]] = None, class_name: str = "empty", ): super().__init__() @@ -1932,12 +1928,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: Optional[tuple[int, int]] = None, + icon_anchor: Optional[tuple[int, int]] = 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: Optional[tuple[int, int]] = None, + shadow_anchor: Optional[tuple[int, int]] = None, + popup_anchor: Optional[tuple[int, int]] = None, ): super(Icon, self).__init__() self._name = "icon" @@ -2016,7 +2012,7 @@ def __init__( f"Unexpected type for argument `colormap`: {type(colormap)}" ) - out: Dict[str, List[List[List[float]]]] = {} + out: dict[str, list[list[list[float]]]] = {} for (lat1, lng1), (lat2, lng2), color in zip(coords[:-1], coords[1:], colors): out.setdefault(cm(color), []).append([[lat1, lng1], [lat2, lng2]]) for key, val in out.items(): diff --git a/folium/folium.py b/folium/folium.py index d2be974c74..d6f1219bad 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -5,7 +5,8 @@ import time import webbrowser -from typing import Any, List, Optional, Sequence, Union +from collections.abc import Sequence +from typing import Any, Optional, Union from branca.element import Element, Figure @@ -337,7 +338,7 @@ def __init__( **kwargs, ) - self.objects_to_stay_in_front: List[Layer] = [] + self.objects_to_stay_in_front: list[Layer] = [] if isinstance(tiles, TileLayer): self.add_child(tiles) diff --git a/folium/map.py b/folium/map.py index 278b97a1bb..dad78271e8 100644 --- a/folium/map.py +++ b/folium/map.py @@ -5,7 +5,8 @@ import warnings from collections import OrderedDict, defaultdict -from typing import TYPE_CHECKING, DefaultDict, Optional, Sequence, Union, cast +from collections.abc import Sequence +from typing import TYPE_CHECKING, Optional, Union, cast from branca.element import Element, Figure, Html, MacroElement @@ -38,7 +39,7 @@ def __get__(self, obj, owner): class Class(MacroElement): """The root class of the leaflet class hierarchy""" - _includes: DefaultDict[str, dict] = defaultdict(dict) + _includes: defaultdict[str, dict] = defaultdict(dict) @classmethod def include(cls, **kwargs): diff --git a/folium/plugins/timeline.py b/folium/plugins/timeline.py index fce0340247..d370d386df 100644 --- a/folium/plugins/timeline.py +++ b/folium/plugins/timeline.py @@ -1,4 +1,4 @@ -from typing import List, Optional, TextIO, Union +from typing import Optional, TextIO, Union from branca.element import MacroElement @@ -242,7 +242,7 @@ def __init__( """ ) - self.timelines: List[Timeline] = [] + self.timelines: list[Timeline] = [] self.options = remove_empty(**kwargs) def add_timelines(self, *args): diff --git a/folium/utilities.py b/folium/utilities.py index eaf1bc4df8..48cabb715d 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -7,20 +7,14 @@ import re import tempfile import uuid +from collections.abc import Iterable, Iterator, Sequence from contextlib import contextmanager from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Iterable, - Iterator, - List, Literal, Optional, - Sequence, - Tuple, - Type, Union, ) from urllib.parse import urlparse, uses_netloc, uses_params, uses_relative @@ -55,7 +49,7 @@ TypePathOptions = Union[bool, str, float, None] TypeBounds = Sequence[Sequence[float]] -TypeBoundsReturn = List[List[Optional[float]]] +TypeBoundsReturn = list[list[Optional[float]]] TypeContainer = Union[Figure, Div, "Popup"] TypePosition = Literal["bottomright", "bottomleft", "topright", "topleft"] @@ -66,7 +60,7 @@ _VALID_URLS.add("data") -def validate_location(location: Sequence[float]) -> List[float]: +def validate_location(location: Sequence[float]) -> list[float]: """Validate a single lat/lon coordinate pair and convert to a list Validate that location: @@ -126,7 +120,7 @@ def _validate_locations_basics(locations: TypeMultiLine) -> None: raise ValueError("Locations is empty.") -def validate_locations(locations: TypeLine) -> List[List[float]]: +def validate_locations(locations: TypeLine) -> list[list[float]]: """Validate an iterable with lat/lon coordinate pairs.""" locations = if_pandas_df_convert_to_numpy(locations) _validate_locations_basics(locations) @@ -135,7 +129,7 @@ def validate_locations(locations: TypeLine) -> List[List[float]]: def validate_multi_locations( locations: TypeMultiLine, -) -> Union[List[List[float]], List[List[List[float]]]]: +) -> Union[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) _validate_locations_basics(locations) @@ -215,7 +209,7 @@ def _is_url(url: str) -> bool: def mercator_transform( data: Any, - lat_bounds: Tuple[float, float], + lat_bounds: tuple[float, float], origin: str = "upper", height_out: Optional[int] = None, ) -> np.ndarray: @@ -279,7 +273,7 @@ def mercator(x): return out -def iter_coords(obj: Any) -> Iterator[Tuple[float, ...]]: +def iter_coords(obj: Any) -> Iterator[tuple[float, ...]]: """ Returns all the coordinate tuples from a geometry or feature. @@ -313,13 +307,13 @@ def iter_coords(obj: Any) -> Iterator[Tuple[float, ...]]: def get_bounds( locations: Any, lonlat: bool = False, -) -> List[List[Optional[float]]]: +) -> list[list[Optional[float]]]: """ 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[Optional[float]]] = [[None, None], [None, None]] for point in iter_coords(locations): bounds = [ [ @@ -397,22 +391,22 @@ def deep_copy(item_original: Element) -> Element: return item -def get_obj_in_upper_tree(element: Element, cls: Type) -> Element: +def get_obj_in_upper_tree(element: Element, cls: type) -> Element: """Return the first object in the parent tree of class `cls`.""" parent = element._parent if parent is None: raise ValueError(f"The top of the tree was reached without finding a {cls}") if not isinstance(parent, cls): return get_obj_in_upper_tree(parent, cls) - return parent + return parent # type: ignore -def parse_options(**kwargs: TypeJsonValue) -> Dict[str, TypeJsonValueNoNone]: +def parse_options(**kwargs: TypeJsonValue) -> dict[str, TypeJsonValueNoNone]: """Return a dict with lower-camelcase keys and non-None values..""" return {camelize(key): value for key, value in kwargs.items() if value is not None} -def remove_empty(**kwargs: TypeJsonValue) -> Dict[str, TypeJsonValueNoNone]: +def remove_empty(**kwargs: TypeJsonValue) -> dict[str, TypeJsonValueNoNone]: """Return a dict without None values.""" return {key: value for key, value in kwargs.items() if value is not None} diff --git a/folium/vector_layers.py b/folium/vector_layers.py index 066efd6ac2..b46ef599c5 100644 --- a/folium/vector_layers.py +++ b/folium/vector_layers.py @@ -3,7 +3,8 @@ """ -from typing import List, Optional, Sequence, Union +from collections.abc import Sequence +from typing import Optional, Union from branca.element import MacroElement @@ -147,7 +148,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[Optional[float]]]: """Compute the bounds of the object itself.""" return get_bounds(self.locations) @@ -289,7 +290,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[Optional[float]]]: """Compute the bounds of the object itself.""" return get_bounds(self.locations) diff --git a/pyproject.toml b/pyproject.toml index e54ec85c17..d6b14f958a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ lint.select = [ "I", # import sorting "U", # upgrade ] -target-version = "py37" +target-version = "py39" line-length = 120 [lint.per-file-ignores]