Skip to content

Commit 2ee6a10

Browse files
committed
Update Python version in release automation workflow
1 parent ea37adc commit 2ee6a10

File tree

9 files changed

+42
-70
lines changed

9 files changed

+42
-70
lines changed

.github/workflows/release-tags.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88

99
env:
10-
python-version: "3.11"
10+
python-version: "3.14"
1111

1212
jobs:
1313
build:

src/emsarray/cli/commands/plot.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,28 @@
33
import logging
44
from collections.abc import Callable
55
from pathlib import Path
6-
from typing import Any, TypeVar
6+
from typing import Any, overload
77

88
import emsarray
99
from emsarray.cli import BaseCommand, CommandException
1010

11-
T = TypeVar('T')
12-
1311
logger = logging.getLogger(__name__)
1412

1513

16-
def key_value(arg: str, value_type: Callable = str) -> dict[str, T]:
14+
@overload
15+
def key_value(arg: str) -> dict[str, str]: ...
16+
@overload
17+
def key_value[T](arg: str, value_type: Callable[[str], T]) -> dict[str, T]: ...
18+
def key_value[T](arg: str, value_type: Callable[[str], T] | None = None) -> dict[str, T] | dict[str, str]:
1719
try:
1820
name, value = arg.split("=", 2)
1921
except ValueError:
2022
raise argparse.ArgumentTypeError(
2123
"Coordinate / dimension indexes must be given as `name=value` pairs")
22-
return {name: value_type(value)}
24+
if value_type is None:
25+
return {name: value}
26+
else:
27+
return {name: value_type(value)}
2328

2429

2530
class UpdateDict(argparse.Action):

src/emsarray/compat/shapely.py

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import warnings
21
from collections.abc import Iterable
3-
from typing import Generic, TypeVar, cast
2+
from typing import cast
43

54
import numpy
6-
import shapely
7-
from packaging.version import parse
8-
from shapely.errors import ShapelyDeprecationWarning
95
from shapely.geometry.base import BaseGeometry
106
from shapely.strtree import STRtree
117

12-
shapely_version = parse(shapely.__version__)
13-
v2 = parse("2")
14-
v1_8_3 = parse("1.8.3")
158

16-
T = TypeVar("T")
17-
18-
19-
class SpatialIndex(Generic[T]):
9+
class SpatialIndex[T]:
2010
"""
2111
A wrapper around a shapely STRtree that can associate metadata with
2212
geometries.
@@ -34,32 +24,18 @@ def __init__(
3424
items: numpy.ndarray | Iterable[tuple[BaseGeometry, T]],
3525
):
3626
self.items = numpy.array(items, dtype=self.dtype)
37-
38-
if shapely_version >= v2:
39-
self.index = STRtree(self.items['geom'])
40-
elif shapely_version >= v1_8_3:
41-
with warnings.catch_warnings():
42-
warnings.simplefilter("ignore", ShapelyDeprecationWarning)
43-
self.index = STRtree(geoms=self.items['geom'])
44-
else:
45-
self.index = STRtree(geoms=self.items['geom'])
27+
self.index = STRtree(self.items['geom'])
4628

4729
def query(
4830
self,
4931
geom: BaseGeometry,
5032
) -> numpy.ndarray:
51-
if shapely_version >= v2:
52-
indexes = self.index.query(geom)
53-
else:
54-
indexes = self.index._query(geom)
33+
indexes = self.index.query(geom)
5534
return cast(numpy.ndarray, self.items.take(indexes))
5635

5736
def nearest(
5837
self,
5938
geom: BaseGeometry,
6039
) -> numpy.ndarray:
61-
if shapely_version >= v2:
62-
indexes = self.index.nearest(geom)
63-
else:
64-
indexes = self.index._nearest(geom)
40+
indexes = self.index.nearest(geom)
6541
return cast(numpy.ndarray, self.items.take(indexes))

src/emsarray/conventions/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
Refer to each Convention implementation for details.
1515
"""
1616
from ._base import (
17-
Convention, DimensionConvention, GridKind, Index, SpatialIndexItem,
17+
Convention, DimensionConvention, SpatialIndexItem,
1818
Specificity
1919
)
2020
from ._registry import get_dataset_convention, register_convention
@@ -25,7 +25,7 @@
2525
from .ugrid import UGrid
2626

2727
__all__ = [
28-
"Convention", "DimensionConvention", "GridKind", "Index",
28+
"Convention", "DimensionConvention",
2929
"SpatialIndexItem", "Specificity",
3030
"get_dataset_convention", "register_convention",
3131
"open_dataset",

src/emsarray/conventions/_base.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import warnings
77
from collections.abc import Callable, Hashable, Iterable, Sequence
88
from functools import cached_property
9-
from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, cast
9+
from typing import TYPE_CHECKING, Any, Literal, cast
1010

1111
import numpy
1212
import shapely
@@ -61,17 +61,17 @@
6161
#:
6262
#: More esoteric cases involving datasets with a potentially unbounded numbers of grids
6363
#: can use a type that supports this instead.
64-
GridKind = TypeVar("GridKind")
64+
# GridKind = TypeVar("GridKind")
6565

6666
#: An :ref:`index <indexing>` to a specific point on a grid in this convention.
6767
#: For conventions with :ref:`multiple grids <grids>` (e.g. cells, edges, and nodes),
6868
#: this should be a tuple whos first element is :data:`.GridKind`.
6969
#: For conventions with a single grid, :data:`.GridKind` is not required.
70-
Index = TypeVar("Index")
70+
# Index = TypeVar("Index")
7171

7272

7373
@dataclasses.dataclass
74-
class SpatialIndexItem(Generic[Index]):
74+
class SpatialIndexItem[Index]:
7575
"""Information about an item in the :class:`~shapely.strtree.STRtree`
7676
spatial index for a dataset.
7777
@@ -125,7 +125,7 @@ class Specificity(enum.IntEnum):
125125
HIGH = 30
126126

127127

128-
class Convention(abc.ABC, Generic[GridKind, Index]):
128+
class Convention[GridKind, Index](abc.ABC):
129129
"""
130130
Each supported geometry convention represents data differently.
131131
The :class:`Convention` class abstracts these differences away,
@@ -2002,7 +2002,7 @@ def hash_geometry(self, hash: "hashlib._Hash") -> None:
20022002
hash_attributes(hash, data_array.attrs)
20032003

20042004

2005-
class DimensionConvention(Convention[GridKind, Index]):
2005+
class DimensionConvention[GridKind, Index](Convention[GridKind, Index]):
20062006
"""
20072007
A Convention subclass where different grid kinds
20082008
are always defined on unique sets of dimension.

src/emsarray/conventions/grid.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from collections.abc import Hashable, Sequence
1010
from contextlib import suppress
1111
from functools import cached_property
12-
from typing import Generic, TypeVar, cast
12+
from typing import cast
1313

1414
import numpy
1515
import xarray
@@ -183,10 +183,7 @@ def size(self) -> int:
183183
return int(numpy.prod(self.shape))
184184

185185

186-
Topology = TypeVar('Topology', bound=CFGridTopology)
187-
188-
189-
class CFGrid(Generic[Topology], DimensionConvention[CFGridKind, CFGridIndex]):
186+
class CFGrid[Topology: CFGridTopology](DimensionConvention[CFGridKind, CFGridIndex]):
190187
"""
191188
A base class for CF grid datasets.
192189
There are two concrete subclasses: :class:`CFGrid1D` and :class:`CFGrid2D`.

src/emsarray/operations/geometry.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import pathlib
88
from collections.abc import Generator, Iterable, Iterator
99
from contextlib import contextmanager
10-
from typing import IO, Any, Generic, TypeVar
10+
from typing import IO, Any
1111

1212
import geojson
1313
import shapefile
@@ -16,10 +16,8 @@
1616

1717
from emsarray.types import Pathish
1818

19-
T = TypeVar('T')
2019

21-
22-
class _dumpable_iterator(Generic[T], list):
20+
class _dumpable_iterator[T](list):
2321
"""
2422
Wrap an iterator / generator so it can be used in `json.dumps()`.
2523
No guarantees that it works for anything else!

src/emsarray/transect.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dataclasses
22
from collections.abc import Callable, Iterable
33
from functools import cached_property
4-
from typing import Any, Generic, cast
4+
from typing import Any, cast
55

66
import cfunits
77
import numpy
@@ -16,7 +16,7 @@
1616
from matplotlib.figure import Figure
1717
from matplotlib.ticker import EngFormatter, Formatter
1818

19-
from emsarray.conventions import Convention, Index
19+
from emsarray.conventions import Convention
2020
from emsarray.plot import _requires_plot, make_plot_title
2121
from emsarray.types import DataArrayOrName, Landmark
2222
from emsarray.utils import move_dimensions_to_end, name_to_data_array
@@ -86,7 +86,7 @@ class TransectPoint:
8686

8787

8888
@dataclasses.dataclass
89-
class TransectSegment(Generic[Index]):
89+
class TransectSegment[Index]:
9090
"""
9191
A TransectSegment holds information about each intersecting segment of the
9292
transect path and the dataset cells.

src/emsarray/utils.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
Callable, Hashable, Iterable, Mapping, MutableMapping, Sequence
1919
)
2020
from types import TracebackType
21-
from typing import Any, Literal, TypeVar, cast
21+
from typing import Any, Literal, cast
2222

2323
import cftime
2424
import netCDF4
@@ -35,10 +35,6 @@
3535
DEFAULT_CALENDAR = 'proleptic_gregorian'
3636

3737

38-
_T = TypeVar("_T")
39-
_Exception = TypeVar("_Exception", bound=BaseException)
40-
41-
4238
class PerfTimer:
4339
__slots__ = ('_start', '_stop', 'running')
4440

@@ -56,10 +52,10 @@ def __enter__(self) -> 'PerfTimer':
5652
self._start = time.perf_counter()
5753
return self
5854

59-
def __exit__(
55+
def __exit__[E: BaseException](
6056
self,
61-
exc_type: type[_Exception] | None,
62-
exc_value: _Exception | None,
57+
exc_type: type[E] | None,
58+
exc_value: E | None,
6359
traceback: TracebackType
6460
) -> bool | None:
6561
self._stop = time.perf_counter()
@@ -75,7 +71,7 @@ def elapsed(self) -> float:
7571
return self._stop - self._start
7672

7773

78-
def timed_func(fn: Callable[..., _T]) -> Callable[..., _T]:
74+
def timed_func[F: Callable](fn: F) -> F:
7975
"""
8076
Log the execution time of the decorated function.
8177
Logs "Calling ``<func.__qualname__>``" before the wrapped function is called,
@@ -101,13 +97,13 @@ def polygons(self):
10197
fn_logger = logging.getLogger(fn.__module__)
10298

10399
@functools.wraps(fn)
104-
def wrapper(*args: Any, **kwargs: Any) -> _T:
100+
def wrapper(*args, **kwargs): # type: ignore
105101
fn_logger.debug("Calling %s", fn.__qualname__)
106102
with PerfTimer() as timer:
107103
value = fn(*args, **kwargs)
108104
fn_logger.debug("Completed %s in %fs", fn.__qualname__, timer.elapsed)
109105
return value
110-
return wrapper
106+
return cast(F, wrapper)
111107

112108

113109
def to_netcdf_with_fixes(
@@ -376,7 +372,7 @@ def extract_vars(
376372
return dataset.drop_vars(drop_vars)
377373

378374

379-
def pairwise(iterable: Iterable[_T]) -> Iterable[tuple[_T, _T]]:
375+
def pairwise[T](iterable: Iterable[T]) -> Iterable[tuple[T, T]]:
380376
"""
381377
Iterate over values in an iterator in pairs.
382378
@@ -734,15 +730,15 @@ def __init__(self, extra: str) -> None:
734730
self.extra = extra
735731

736732

737-
def requires_extra(
733+
def requires_extra[T](
738734
extra: str,
739735
import_error: ImportError | None,
740736
exception_class: type[RequiresExtraException] = RequiresExtraException,
741-
) -> Callable[[_T], _T]:
737+
) -> Callable[[T], T]:
742738
if import_error is None:
743739
return lambda fn: fn
744740

745-
def error_decorator(fn: _T) -> _T:
741+
def error_decorator(fn: T) -> T:
746742
@functools.wraps(fn) # type: ignore
747743
def error(*args: Any, **kwargs: Any) -> Any:
748744
raise exception_class(extra) from import_error

0 commit comments

Comments
 (0)