Skip to content

Commit 9277846

Browse files
add missing __slots__ to containers and their base classes (#1144) (#1147)
* add missing __slots__ to containers and their base classes (#1144) * Fix flake8 issue
1 parent 4390a2f commit 9277846

37 files changed

+240
-25
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ Versions before `1.0.0` are `0Ver`-based:
55
incremental in minor, bugfixes only are patches.
66
See [0Ver](https://0ver.org/).
77

8+
## 0.17.1
9+
10+
### Bugfixes
11+
12+
- Fixes `__slots__` not being set properly in containers and their base classes
813

914
## 0.17.0
1015

returns/context/requires_context.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ class RequiresContext(
7777
7878
"""
7979

80+
__slots__ = ()
81+
8082
#: This field has an extra 'RequiresContext' just because `mypy` needs it.
8183
_inner_value: Callable[[RequiresContext, _EnvType], _ReturnType]
8284

returns/context/requires_context_future_result.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ class RequiresContextFutureResult(
9595
9696
"""
9797

98+
__slots__ = ()
99+
98100
#: Inner value of `RequiresContext`
99101
#: is just a function that returns `FutureResult`.
100102
#: This field has an extra 'RequiresContext' just because `mypy` needs it.

returns/context/requires_context_ioresult.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class RequiresContextIOResult(
102102
103103
"""
104104

105+
__slots__ = ()
106+
105107
#: Inner value of `RequiresContext`
106108
#: is just a function that returns `IOResult`.
107109
#: This field has an extra 'RequiresContext' just because `mypy` needs it.

returns/context/requires_context_result.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class RequiresContextResult(
9393
9494
"""
9595

96+
__slots__ = ()
97+
9698
#: This field has an extra 'RequiresContext' just because `mypy` needs it.
9799
_inner_value: Callable[
98100
[RequiresContextResult, _EnvType],

returns/contrib/pytest/plugin.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
from contextlib import contextmanager
44
from functools import partial, wraps
55
from types import FrameType
6-
from typing import TYPE_CHECKING, Any, Callable, Iterator, TypeVar, Union
6+
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, TypeVar, Union
77

88
import pytest
99
from typing_extensions import Final, final
1010

1111
if TYPE_CHECKING:
1212
from returns.interfaces.specific.result import ResultLikeN
1313

14-
_ERROR_FIELD: Final = '_error_handled'
1514
_ERROR_HANDLERS: Final = (
1615
'lash',
1716
)
@@ -20,6 +19,17 @@
2019
'alt',
2120
)
2221

22+
# We keep track of errors handled by keeping a mapping of <object id>: object.
23+
# If an error is handled, it is in the mapping.
24+
# If it isn't in the mapping, the error is not handled.
25+
#
26+
# Note only storing object IDs would not work, as objects may be GC'ed
27+
# and their object id assigned to another object.
28+
# Also, the object itself cannot be (in) the key because
29+
# (1) we cannot always assume hashability and
30+
# (2) we need to track the object identity, not its value
31+
_ERRORS_HANDLED: Final[Dict[int, Any]] = {} # noqa: WPS407
32+
2333
_FunctionType = TypeVar('_FunctionType', bound=Callable)
2434
_ReturnsResultType = TypeVar(
2535
'_ReturnsResultType',
@@ -33,8 +43,8 @@ class ReturnsAsserts(object):
3343

3444
__slots__ = ()
3545

36-
def assert_equal(
37-
self,
46+
@staticmethod # noqa: WPS602
47+
def assert_equal( # noqa: WPS602
3848
first,
3949
second,
4050
*,
@@ -45,13 +55,14 @@ def assert_equal(
4555
from returns.primitives.asserts import assert_equal
4656
assert_equal(first, second, deps=deps, backend=backend)
4757

48-
def is_error_handled(self, container) -> bool:
58+
@staticmethod # noqa: WPS602
59+
def is_error_handled(container) -> bool: # noqa: WPS602
4960
"""Ensures that container has its error handled in the end."""
50-
return bool(getattr(container, _ERROR_FIELD, False))
61+
return id(container) in _ERRORS_HANDLED
5162

63+
@staticmethod # noqa: WPS602
5264
@contextmanager
53-
def assert_trace(
54-
self,
65+
def assert_trace( # noqa: WPS602
5566
trace_type: _ReturnsResultType,
5667
function_to_search: _FunctionType,
5768
) -> Iterator[None]:
@@ -76,11 +87,18 @@ def assert_trace(
7687

7788

7889
@pytest.fixture(scope='session')
79-
def returns(_patch_containers) -> ReturnsAsserts: # noqa: WPS442
90+
def returns(_patch_containers) -> ReturnsAsserts:
8091
"""Returns our own class with helpers assertions to check containers."""
8192
return ReturnsAsserts()
8293

8394

95+
@pytest.fixture(autouse=True)
96+
def _clear_errors_handled():
97+
"""Ensures the 'errors handled' registry doesn't leak memory."""
98+
yield
99+
_ERRORS_HANDLED.clear()
100+
101+
84102
def pytest_configure(config) -> None:
85103
"""
86104
Hook to be executed on import.
@@ -182,16 +200,12 @@ def error_handler(cls, original):
182200
if inspect.iscoroutinefunction(original):
183201
async def factory(self, *args, **kwargs):
184202
original_result = await original(self, *args, **kwargs)
185-
object.__setattr__(
186-
original_result, _ERROR_FIELD, True, # noqa: WPS425
187-
)
203+
_ERRORS_HANDLED[id(original_result)] = original_result
188204
return original_result
189205
else:
190206
def factory(self, *args, **kwargs):
191207
original_result = original(self, *args, **kwargs)
192-
object.__setattr__(
193-
original_result, _ERROR_FIELD, True, # noqa: WPS425
194-
)
208+
_ERRORS_HANDLED[id(original_result)] = original_result
195209
return original_result
196210
return wraps(original)(factory)
197211

@@ -200,20 +214,14 @@ def copy_handler(cls, original):
200214
if inspect.iscoroutinefunction(original):
201215
async def factory(self, *args, **kwargs):
202216
original_result = await original(self, *args, **kwargs)
203-
object.__setattr__(
204-
original_result,
205-
_ERROR_FIELD,
206-
getattr(self, _ERROR_FIELD, False),
207-
)
217+
if id(self) in _ERRORS_HANDLED:
218+
_ERRORS_HANDLED[id(original_result)] = original_result
208219
return original_result
209220
else:
210221
def factory(self, *args, **kwargs):
211222
original_result = original(self, *args, **kwargs)
212-
object.__setattr__(
213-
original_result,
214-
_ERROR_FIELD,
215-
getattr(self, _ERROR_FIELD, False),
216-
)
223+
if id(self) in _ERRORS_HANDLED:
224+
_ERRORS_HANDLED[id(original_result)] = original_result
217225
return original_result
218226
return wraps(original)(factory)
219227

returns/future.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class Future(
8585
8686
"""
8787

88+
__slots__ = ()
89+
8890
_inner_value: Awaitable[_ValueType]
8991

9092
def __init__(self, inner_value: Awaitable[_ValueType]) -> None:
@@ -522,6 +524,8 @@ class FutureResult(
522524
523525
"""
524526

527+
__slots__ = ()
528+
525529
_inner_value: Awaitable[Result[_ValueType, _ErrorType]]
526530

527531
def __init__(

returns/interfaces/altable.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ class _LawSpec(LawSpecDef):
3535
https://en.wikibooks.org/wiki/Haskell/The_Functor_class#The_functor_laws
3636
"""
3737

38+
__slots__ = ()
39+
3840
@law_definition
3941
def identity_law(
4042
altable: 'AltableN[_FirstType, _SecondType, _ThirdType]',
@@ -61,6 +63,8 @@ class AltableN(
6163
):
6264
"""Modifies the second type argument with a pure function."""
6365

66+
__slots__ = ()
67+
6468
_laws: ClassVar[Sequence[Law]] = (
6569
Law1(_LawSpec.identity_law),
6670
Law3(_LawSpec.associative_law),

returns/interfaces/applicative.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class _LawSpec(LawSpecDef):
3737
Discussion: https://bit.ly/3jffz3L
3838
"""
3939

40+
__slots__ = ()
41+
4042
@law_definition
4143
def identity_law(
4244
container: 'ApplicativeN[_FirstType, _SecondType, _ThirdType]',
@@ -131,6 +133,8 @@ class ApplicativeN(
131133
132134
"""
133135

136+
__slots__ = ()
137+
134138
_laws: ClassVar[Sequence[Law]] = (
135139
Law1(_LawSpec.identity_law),
136140
Law3(_LawSpec.interchange_law),

returns/interfaces/bimappable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ class BiMappableN(
2121
2222
"""
2323

24+
__slots__ = ()
25+
2426

2527
#: Type alias for kinds with two type arguments.
2628
BiMappable2 = BiMappableN[_FirstType, _SecondType, NoReturn]

0 commit comments

Comments
 (0)