Skip to content

Commit 39f9f0d

Browse files
bryanforbeselprans
authored andcommitted
Refactor typings
* Improve typing of `__init__()` * Update typing of `Map`-producing functions to produce the correct type * Update typing of other methods to more closely align with `Mapping` * Add protocol classes for unexposed data structures * Export protocol classes for ease of use in typed code * Update stub file to pass in mypy strict mode
1 parent 67c5edf commit 39f9f0d

File tree

8 files changed

+170
-80
lines changed

8 files changed

+170
-80
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ jobs:
5151
run: |
5252
pip install -e .[test]
5353
flake8 immutables/ tests/
54+
mypy immutables/
5455
python -m unittest -v tests.suite

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ __pycache__/
2020
/.pytest_cache
2121
/.coverage
2222
/.mypy_cache
23+
/.venv*

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
recursive-include tests *.py
22
recursive-include immutables *.py *.c *.h *.pyi
33
include LICENSE* NOTICE README.rst bench.png
4-
include immutables/py.typed
4+
include mypy.ini immutables/py.typed

immutables/__init__.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
# flake8: noqa
22

3-
try:
3+
import sys
4+
5+
if sys.version_info >= (3, 5, 2):
6+
from typing import TYPE_CHECKING
7+
else:
8+
from typing_extensions import TYPE_CHECKING
9+
10+
if TYPE_CHECKING:
411
from ._map import Map
5-
except ImportError:
6-
from .map import Map
712
else:
8-
import collections.abc as _abc
9-
_abc.Mapping.register(Map)
13+
try:
14+
from ._map import Map
15+
except ImportError:
16+
from .map import Map
17+
else:
18+
import collections.abc as _abc
19+
_abc.Mapping.register(Map)
20+
21+
from ._protocols import MapKeys as MapKeys
22+
from ._protocols import MapValues as MapValues
23+
from ._protocols import MapItems as MapItems
24+
from ._protocols import MapMutation as MapMutation
1025

1126
from ._version import __version__
1227

immutables/_map.pyi

Lines changed: 51 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,73 @@
11
from typing import Any
2+
from typing import Dict
23
from typing import Generic
3-
from typing import Hashable
44
from typing import Iterable
55
from typing import Iterator
66
from typing import Mapping
7-
from typing import MutableMapping
8-
from typing import NoReturn
9-
from typing import overload
7+
from typing import Optional
108
from typing import Tuple
119
from typing import Type
12-
from typing import TypeVar
1310
from typing import Union
11+
from typing import overload
1412

15-
16-
K = TypeVar('K', bound=Hashable)
17-
V = TypeVar('V', bound=Any)
18-
D = TypeVar('D', bound=Any)
19-
20-
21-
class BitmapNode: ...
22-
23-
24-
class MapKeys(Generic[K]):
25-
def __init__(self, c: int, m: BitmapNode) -> None: ...
26-
def __len__(self) -> int: ...
27-
def __iter__(self) -> Iterator[K]: ...
28-
29-
30-
class MapValues(Generic[V]):
31-
def __init__(self, c: int, m: BitmapNode) -> None: ...
32-
def __len__(self) -> int: ...
33-
def __iter__(self) -> Iterator[V]: ...
34-
35-
36-
class MapItems(Generic[K, V]):
37-
def __init__(self, c: int, m: BitmapNode) -> None: ...
38-
def __len__(self) -> int: ...
39-
def __iter__(self) -> Iterator[Tuple[K, V]]: ...
13+
from ._protocols import IterableItems
14+
from ._protocols import MapItems
15+
from ._protocols import MapKeys
16+
from ._protocols import MapMutation
17+
from ._protocols import MapValues
18+
from ._protocols import HT
19+
from ._protocols import KT
20+
from ._protocols import T
21+
from ._protocols import VT_co
4022

4123

42-
class Map(Mapping[K, V]):
24+
class Map(Mapping[KT, VT_co]):
4325
@overload
44-
def __init__(self, **kw: V) -> None: ...
26+
def __init__(self) -> None: ...
27+
@overload
28+
def __init__(self: Map[str, VT_co], **kw: VT_co) -> None: ...
29+
@overload
30+
def __init__(
31+
self, __col: Union[IterableItems[KT, VT_co], Iterable[Tuple[KT, VT_co]]]
32+
) -> None: ...
4533
@overload
4634
def __init__(
47-
self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
35+
self: Map[Union[KT, str], VT_co],
36+
__col: Union[IterableItems[KT, VT_co], Iterable[Tuple[KT, VT_co]]],
37+
**kw: VT_co
4838
) -> None: ...
49-
def __reduce__(self) -> Tuple[Type[Map], Tuple[dict]]: ...
39+
def __reduce__(self) -> Tuple[Type[Map[KT, VT_co]], Tuple[Dict[KT, VT_co]]]: ...
5040
def __len__(self) -> int: ...
5141
def __eq__(self, other: Any) -> bool: ...
5242
@overload
53-
def update(self, **kw: V) -> Map[str, V]: ...
54-
@overload
5543
def update(
56-
self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
57-
) -> Map[K, V]: ...
58-
def mutate(self) -> MapMutation[K, V]: ...
59-
def set(self, key: K, val: V) -> Map[K, V]: ...
60-
def delete(self, key: K) -> Map[K, V]: ...
61-
def get(self, key: K, default: D = ...) -> Union[V, D]: ...
62-
def __getitem__(self, key: K) -> V: ...
63-
def __contains__(self, key: object) -> bool: ...
64-
def __iter__(self) -> Iterator[K]: ...
65-
def keys(self) -> MapKeys[K]: ...
66-
def values(self) -> MapValues[V]: ...
67-
def items(self) -> MapItems[K, V]: ...
68-
def __hash__(self) -> int: ...
69-
def __dump__(self) -> str: ...
70-
def __class_getitem__(cls, item: Any) -> Type[Map]: ...
71-
72-
73-
S = TypeVar('S', bound='MapMutation')
74-
75-
76-
class MapMutation(MutableMapping[K, V]):
77-
def __init__(self, count: int, root: BitmapNode) -> None: ...
78-
def set(self, key: K, val: V) -> None: ...
79-
def __enter__(self: S) -> S: ...
80-
def __exit__(self, *exc: Any): ...
81-
def __iter__(self) -> NoReturn: ...
82-
def __delitem__(self, key: K) -> None: ...
83-
def __setitem__(self, key: K, val: V) -> None: ...
84-
def pop(self, __key: K, __default: D = ...) -> Union[V, D]: ...
85-
def get(self, key: K, default: D = ...) -> Union[V, D]: ...
86-
def __getitem__(self, key: K) -> V: ...
87-
def __contains__(self, key: Any) -> bool: ...
44+
self,
45+
__col: Union[IterableItems[KT, VT_co], Iterable[Tuple[KT, VT_co]]]
46+
) -> Map[KT, VT_co]: ...
8847
@overload
89-
def update(self, **kw: V) -> None: ...
48+
def update(
49+
self: Map[Union[HT, str], Any],
50+
__col: Union[IterableItems[KT, VT_co], Iterable[Tuple[KT, VT_co]]],
51+
**kw: VT_co # type: ignore[misc]
52+
) -> Map[KT, VT_co]: ...
9053
@overload
9154
def update(
92-
self, col: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kw: V
93-
) -> None: ...
94-
def finish(self) -> Map[K, V]: ...
95-
def __len__(self) -> int: ...
96-
def __eq__(self, other: Any) -> bool: ...
55+
self: Map[Union[HT, str], Any],
56+
**kw: VT_co # type: ignore[misc]
57+
) -> Map[KT, VT_co]: ...
58+
def mutate(self) -> MapMutation[KT, VT_co]: ...
59+
def set(self, key: KT, val: VT_co) -> Map[KT, VT_co]: ... # type: ignore[misc]
60+
def delete(self, key: KT) -> Map[KT, VT_co]: ...
61+
@overload
62+
def get(self, key: KT) -> Optional[VT_co]: ...
63+
@overload
64+
def get(self, key: KT, default: Union[VT_co, T]) -> Union[VT_co, T]: ...
65+
def __getitem__(self, key: KT) -> VT_co: ...
66+
def __contains__(self, key: Any) -> bool: ...
67+
def __iter__(self) -> Iterator[KT]: ...
68+
def keys(self) -> MapKeys[KT]: ... # type: ignore[override]
69+
def values(self) -> MapValues[VT_co]: ... # type: ignore[override]
70+
def items(self) -> MapItems[KT, VT_co]: ... # type: ignore[override]
71+
def __hash__(self) -> int: ...
72+
def __dump__(self) -> str: ...
73+
def __class_getitem__(cls, item: Any) -> Type[Map[Any, Any]]: ...

immutables/_protocols.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import sys
2+
from typing import Any
3+
from typing import Hashable
4+
from typing import Iterable
5+
from typing import Iterator
6+
from typing import NoReturn
7+
from typing import Optional
8+
from typing import Tuple
9+
from typing import TypeVar
10+
from typing import Union
11+
from typing import overload
12+
13+
if sys.version_info >= (3, 8):
14+
from typing import Protocol
15+
from typing import TYPE_CHECKING
16+
else:
17+
from typing_extensions import Protocol
18+
from typing_extensions import TYPE_CHECKING
19+
20+
if TYPE_CHECKING:
21+
from ._map import Map
22+
23+
HT = TypeVar('HT', bound=Hashable)
24+
KT = TypeVar('KT', bound=Hashable)
25+
KT_co = TypeVar('KT_co', covariant=True)
26+
MM = TypeVar('MM', bound='MapMutation[Any, Any]')
27+
T = TypeVar('T')
28+
VT = TypeVar('VT')
29+
VT_co = TypeVar('VT_co', covariant=True)
30+
31+
32+
class MapKeys(Protocol[KT_co]):
33+
def __len__(self) -> int: ...
34+
def __iter__(self) -> Iterator[KT_co]: ...
35+
36+
37+
class MapValues(Protocol[VT_co]):
38+
def __len__(self) -> int: ...
39+
def __iter__(self) -> Iterator[VT_co]: ...
40+
41+
42+
class MapItems(Protocol[KT_co, VT_co]):
43+
def __len__(self) -> int: ...
44+
def __iter__(self) -> Iterator[Tuple[KT_co, VT_co]]: ...
45+
46+
47+
class IterableItems(Protocol[KT_co, VT_co]):
48+
def items(self) -> Iterable[Tuple[KT_co, VT_co]]: ...
49+
50+
51+
class MapMutation(Protocol[KT, VT]):
52+
def set(self, key: KT, val: VT) -> None: ...
53+
def __enter__(self: MM) -> MM: ...
54+
def __exit__(self, *exc: Any) -> bool: ...
55+
def __iter__(self) -> NoReturn: ...
56+
def __delitem__(self, key: KT) -> None: ...
57+
def __setitem__(self, key: KT, val: VT) -> None: ...
58+
@overload
59+
def pop(self, __key: KT) -> VT: ...
60+
@overload
61+
def pop(self, __key: KT, __default: T) -> Union[VT, T]: ...
62+
@overload
63+
def get(self, key: KT) -> Optional[VT]: ...
64+
@overload
65+
def get(self, key: KT, default: Union[VT, T]) -> Union[VT, T]: ...
66+
def __getitem__(self, key: KT) -> VT: ...
67+
def __contains__(self, key: object) -> bool: ...
68+
69+
@overload
70+
def update(
71+
self,
72+
__col: Union[IterableItems[KT, VT], Iterable[Tuple[KT, VT]]]
73+
) -> None: ...
74+
75+
@overload
76+
def update(
77+
self: 'MapMutation[Union[HT, str], Any]',
78+
__col: Union[IterableItems[KT, VT], Iterable[Tuple[KT, VT]]],
79+
**kw: VT
80+
) -> None: ...
81+
@overload
82+
def update(self: 'MapMutation[Union[HT, str], Any]', **kw: VT) -> None: ...
83+
def finish(self) -> 'Map[KT, VT]': ...
84+
def __len__(self) -> int: ...
85+
def __eq__(self, other: Any) -> bool: ...

mypy.ini

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[mypy]
2+
incremental = True
3+
strict = True
4+
5+
[mypy-immutables.map]
6+
ignore_errors = True
7+
8+
[mypy-immutables._testutils]
9+
ignore_errors = True

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# (example breakage: https://gitlab.com/pycqa/flake8/issues/427)
1111
'flake8~=3.8.4',
1212
'pycodestyle~=2.6.0',
13+
'mypy>=0.800',
1314
]
1415

1516
EXTRA_DEPENDENCIES = {
@@ -86,5 +87,6 @@
8687
provides=['immutables'],
8788
include_package_data=True,
8889
ext_modules=ext_modules,
90+
install_requires=['typing-extensions>=3.7.4.3;python_version<"3.8"'],
8991
extras_require=EXTRA_DEPENDENCIES,
9092
)

0 commit comments

Comments
 (0)