Skip to content

Commit 8501775

Browse files
authored
Merge pull request #204 from csiro-coasts/pep-695-generics
Use PEP 695 generics
2 parents 0aaf3fa + 6a4143b commit 8501775

File tree

11 files changed

+78
-78
lines changed

11 files changed

+78
-78
lines changed

docs/api/conventions/interface.rst

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Convention interface
77
.. contents::
88
:local:
99

10+
.. currentmodule:: emsarray.conventions
11+
1012
Each supported convention implements
1113
the :class:`~emsarray.conventions.Convention` interface.
1214

@@ -25,8 +27,37 @@ the :class:`~emsarray.conventions.Convention` interface.
2527
.. autoclass:: emsarray.conventions.SpatialIndexItem
2628
:members:
2729

28-
.. autodata:: emsarray.conventions._base.GridKind
29-
.. autodata:: emsarray.conventions._base.Index
30+
.. type:: GridKind
31+
32+
Some type that can enumerate the different :ref:`grid types <grids>`
33+
present in a dataset.
34+
This can be an :class:`enum.Enum` listing each different kind of grid.
35+
36+
:type:`Index` values will be included in the feature properties
37+
of exported geometry from :mod:`emsarray.operations.geometry`.
38+
If the index type includes the grid kind,
39+
the grid kind needs to be JSON serializable.
40+
The easiest way to achieve this is to make your GridKind type subclass :class:`str`:
41+
42+
.. code-block:: python
43+
44+
class MyGridKind(str, enum.Enum):
45+
face = 'face'
46+
edge = 'edge'
47+
node = 'node'
48+
49+
For cases where the convention only supports a single grid,
50+
a singleton enum can be used.
51+
52+
More esoteric cases involving datasets with a potentially unbounded numbers of grids
53+
can use a type that supports this instead.
54+
55+
.. type:: Index
56+
57+
An :ref:`index <indexing>` to a specific point on a grid in this convention.
58+
For conventions with :ref:`multiple grids <grids>` (e.g. cells, edges, and nodes),
59+
this should be a tuple whos first element is :type:`.GridKind`.
60+
For conventions with a single grid, :type:`.GridKind` is not required.
3061

3162
.. autoclass:: emsarray.conventions.Specificity
3263
:members:

docs/concepts/grids.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ from one face to another.
1919
These edges represent another grid.
2020
Some conventions also define variables on face vertices, called *nodes*.
2121
Nodes represent a third grid.
22-
This is represented by the :data:`~.conventions._base.GridKind` type variable.
22+
This is represented by the :type:`~.conventions.GridKind` type variable.
2323

2424
Each of the faces, edges, and nodes define an area, line, or point.
2525
These areas, lines, or points exist at some geographic location.

docs/concepts/indexing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ As each geometry convention may define a different number of grids,
1616
each convention has a different method of indexing data in these grids.
1717
These are the *convention native indexes*.
1818
Each :class:`~.conventions.Convention` implementation
19-
has its own :data:`~.conventions._base.Index` type.
19+
has its own :type:`~.conventions.Index` type.
2020

2121
:mod:`CF grid datasets <.conventions.grid>` have only one grid - faces.
2222
Each face can be indexed using two numbers *x* and *y*.

docs/releases/development.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,9 @@ Next release (in development)
1818
``spatial_index()``, ``get_grid_kind_and_size()``,
1919
and ``NonIntersectingPoints.indices``
2020
(:pr:`202`).
21+
* Use `PEP 695 <https://peps.python.org/pep-0695/>`_ style type parameters.
22+
This drops the `Index` and `GridKind` type variables
23+
which were exported in `emsarray.conventions`,
24+
which is a backwards incompatible change
25+
but is difficult to add meaningful backwards compatible support
26+
(:issue:`109`, :pr:`203`)

src/emsarray/cli/commands/plot.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,30 @@
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]: ... # noqa: E704
16+
@overload
17+
def key_value[T](arg: str, value_type: Callable[[str], T]) -> dict[str, T]: ... # noqa: E704
18+
19+
20+
def key_value[T](arg: str, value_type: Callable[[str], T] | None = None) -> dict[str, T] | dict[str, str]:
1721
try:
1822
name, value = arg.split("=", 2)
1923
except ValueError:
2024
raise argparse.ArgumentTypeError(
2125
"Coordinate / dimension indexes must be given as `name=value` pairs")
22-
return {name: value_type(value)}
26+
if value_type is None:
27+
return {name: value}
28+
else:
29+
return {name: value_type(value)}
2330

2431

2532
class UpdateDict(argparse.Action):

src/emsarray/conventions/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
Refer to each Convention implementation for details.
1515
"""
1616
from ._base import (
17-
Convention, DimensionConvention, GridKind, Index, SpatialIndexItem,
18-
Specificity
17+
Convention, DimensionConvention, SpatialIndexItem, Specificity
1918
)
2019
from ._registry import get_dataset_convention, register_convention
2120
from ._utils import open_dataset
@@ -25,7 +24,7 @@
2524
from .ugrid import UGrid
2625

2726
__all__ = [
28-
"Convention", "DimensionConvention", "GridKind", "Index",
27+
"Convention", "DimensionConvention",
2928
"SpatialIndexItem", "Specificity",
3029
"get_dataset_convention", "register_convention",
3130
"open_dataset",

src/emsarray/conventions/_base.py

Lines changed: 4 additions & 35 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
@@ -38,39 +38,8 @@
3838
logger = logging.getLogger(__name__)
3939

4040

41-
#: Some type that can enumerate the different :ref:`grid types <grids>`
42-
#: present in a dataset.
43-
#: This can be an :class:`enum.Enum` listing each different kind of grid.
44-
#:
45-
#: :data:`Index` values will be included in the feature properties
46-
#: of exported geometry from :mod:`emsarray.operations.geometry`.
47-
#: If the index type includes the grid kind,
48-
#: the grid kind needs to be JSON serializable.
49-
#: The easiest way to achieve this is to make your GridKind type subclass :class:`str`:
50-
#:
51-
#: .. code-block:: python
52-
#:
53-
#: class MyGridKind(str, enum.Enum):
54-
#: face = 'face'
55-
#: edge = 'edge'
56-
#: node = 'node'
57-
#:
58-
#: For cases where the convention only supports a single grid,
59-
#: a singleton enum can be used.
60-
#:
61-
#: More esoteric cases involving datasets with a potentially unbounded numbers of grids
62-
#: can use a type that supports this instead.
63-
GridKind = TypeVar("GridKind")
64-
65-
#: An :ref:`index <indexing>` to a specific point on a grid in this convention.
66-
#: For conventions with :ref:`multiple grids <grids>` (e.g. cells, edges, and nodes),
67-
#: this should be a tuple whos first element is :data:`.GridKind`.
68-
#: For conventions with a single grid, :data:`.GridKind` is not required.
69-
Index = TypeVar("Index")
70-
71-
7241
@dataclasses.dataclass
73-
class SpatialIndexItem(Generic[Index]):
42+
class SpatialIndexItem[Index]:
7443
"""Information about an item in the :class:`~shapely.strtree.STRtree`
7544
spatial index for a dataset.
7645
@@ -124,7 +93,7 @@ class Specificity(enum.IntEnum):
12493
HIGH = 30
12594

12695

127-
class Convention(abc.ABC, Generic[GridKind, Index]):
96+
class Convention[GridKind, Index](abc.ABC):
12897
"""
12998
Each supported geometry convention represents data differently.
13099
The :class:`Convention` class abstracts these differences away,
@@ -1749,7 +1718,7 @@ def hash_geometry(self, hash: "hashlib._Hash") -> None:
17491718
hash_attributes(hash, data_array.attrs)
17501719

17511720

1752-
class DimensionConvention(Convention[GridKind, Index]):
1721+
class DimensionConvention[GridKind, Index](Convention[GridKind, Index]):
17531722
"""
17541723
A Convention subclass where different grid kinds
17551724
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: 4 additions & 7 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:
9090
"""
9191
A TransectSegment holds information about each intersecting segment of the
9292
transect path and the dataset cells.
@@ -96,7 +96,6 @@ class TransectSegment(Generic[Index]):
9696
intersection: shapely.LineString
9797
start_distance: float
9898
end_distance: float
99-
index: Index
10099
linear_index: int
101100
polygon: shapely.Polygon
102101

@@ -281,7 +280,7 @@ def points(
281280
return points
282281

283282
@cached_property
284-
def segments(self) -> list[TransectSegment[Index]]:
283+
def segments(self) -> list[TransectSegment]:
285284
"""
286285
A list of :class:`.TransectSegmens` for each intersecting segment of the transect line and the dataset geometry.
287286
Segments are listed in order from the start of the line to the end of the line.
@@ -293,7 +292,6 @@ def segments(self) -> list[TransectSegment[Index]]:
293292

294293
for linear_index in intersecting_indexes:
295294
polygon = self.convention.polygons[linear_index]
296-
index = self.convention.wind_index(linear_index)
297295
for intersection in self._intersect_polygon(polygon):
298296
# The line will have two ends.
299297
# The intersection starts and ends at these points.
@@ -314,7 +312,6 @@ def segments(self) -> list[TransectSegment[Index]]:
314312
intersection=intersection,
315313
start_distance=start[1],
316314
end_distance=end[1],
317-
index=index,
318315
linear_index=linear_index,
319316
polygon=polygon,
320317
))

0 commit comments

Comments
 (0)