Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add missing __slots__ to containers and their base classes (#1144) #1147

Merged
merged 2 commits into from
Nov 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Versions before `1.0.0` are `0Ver`-based:
incremental in minor, bugfixes only are patches.
See [0Ver](https://0ver.org/).

## 0.17.1

### Bugfixes

- Fixes `__slots__` not being set properly in containers and their base classes

## 0.17.0

Expand Down
2 changes: 2 additions & 0 deletions returns/context/requires_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class RequiresContext(

"""

__slots__ = ()

#: This field has an extra 'RequiresContext' just because `mypy` needs it.
_inner_value: Callable[[RequiresContext, _EnvType], _ReturnType]

Expand Down
2 changes: 2 additions & 0 deletions returns/context/requires_context_future_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class RequiresContextFutureResult(

"""

__slots__ = ()

#: Inner value of `RequiresContext`
#: is just a function that returns `FutureResult`.
#: This field has an extra 'RequiresContext' just because `mypy` needs it.
Expand Down
2 changes: 2 additions & 0 deletions returns/context/requires_context_ioresult.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class RequiresContextIOResult(

"""

__slots__ = ()

#: Inner value of `RequiresContext`
#: is just a function that returns `IOResult`.
#: This field has an extra 'RequiresContext' just because `mypy` needs it.
Expand Down
2 changes: 2 additions & 0 deletions returns/context/requires_context_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ class RequiresContextResult(

"""

__slots__ = ()

#: This field has an extra 'RequiresContext' just because `mypy` needs it.
_inner_value: Callable[
[RequiresContextResult, _EnvType],
Expand Down
58 changes: 33 additions & 25 deletions returns/contrib/pytest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
from contextlib import contextmanager
from functools import partial, wraps
from types import FrameType
from typing import TYPE_CHECKING, Any, Callable, Iterator, TypeVar, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, TypeVar, Union

import pytest
from typing_extensions import Final, final

if TYPE_CHECKING:
from returns.interfaces.specific.result import ResultLikeN

_ERROR_FIELD: Final = '_error_handled'
_ERROR_HANDLERS: Final = (
'lash',
)
Expand All @@ -20,6 +19,17 @@
'alt',
)

# We keep track of errors handled by keeping a mapping of <object id>: object.
# If an error is handled, it is in the mapping.
# If it isn't in the mapping, the error is not handled.
#
# Note only storing object IDs would not work, as objects may be GC'ed
# and their object id assigned to another object.
# Also, the object itself cannot be (in) the key because
# (1) we cannot always assume hashability and
# (2) we need to track the object identity, not its value
_ERRORS_HANDLED: Final[Dict[int, Any]] = {} # noqa: WPS407

_FunctionType = TypeVar('_FunctionType', bound=Callable)
_ReturnsResultType = TypeVar(
'_ReturnsResultType',
Expand All @@ -33,8 +43,8 @@ class ReturnsAsserts(object):

__slots__ = ()

def assert_equal(
self,
@staticmethod # noqa: WPS602
def assert_equal( # noqa: WPS602
first,
second,
*,
Expand All @@ -45,13 +55,14 @@ def assert_equal(
from returns.primitives.asserts import assert_equal
assert_equal(first, second, deps=deps, backend=backend)

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

@staticmethod # noqa: WPS602
@contextmanager
def assert_trace(
self,
def assert_trace( # noqa: WPS602
trace_type: _ReturnsResultType,
function_to_search: _FunctionType,
) -> Iterator[None]:
Expand All @@ -76,11 +87,18 @@ def assert_trace(


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


@pytest.fixture(autouse=True)
def _clear_errors_handled():
"""Ensures the 'errors handled' registry doesn't leak memory."""
yield
_ERRORS_HANDLED.clear()


def pytest_configure(config) -> None:
"""
Hook to be executed on import.
Expand Down Expand Up @@ -182,16 +200,12 @@ def error_handler(cls, original):
if inspect.iscoroutinefunction(original):
async def factory(self, *args, **kwargs):
original_result = await original(self, *args, **kwargs)
object.__setattr__(
original_result, _ERROR_FIELD, True, # noqa: WPS425
)
_ERRORS_HANDLED[id(original_result)] = original_result
return original_result
else:
def factory(self, *args, **kwargs):
original_result = original(self, *args, **kwargs)
object.__setattr__(
original_result, _ERROR_FIELD, True, # noqa: WPS425
)
_ERRORS_HANDLED[id(original_result)] = original_result
return original_result
return wraps(original)(factory)

Expand All @@ -200,20 +214,14 @@ def copy_handler(cls, original):
if inspect.iscoroutinefunction(original):
async def factory(self, *args, **kwargs):
original_result = await original(self, *args, **kwargs)
object.__setattr__(
original_result,
_ERROR_FIELD,
getattr(self, _ERROR_FIELD, False),
)
if id(self) in _ERRORS_HANDLED:
_ERRORS_HANDLED[id(original_result)] = original_result
return original_result
else:
def factory(self, *args, **kwargs):
original_result = original(self, *args, **kwargs)
object.__setattr__(
original_result,
_ERROR_FIELD,
getattr(self, _ERROR_FIELD, False),
)
if id(self) in _ERRORS_HANDLED:
_ERRORS_HANDLED[id(original_result)] = original_result
return original_result
return wraps(original)(factory)

Expand Down
4 changes: 4 additions & 0 deletions returns/future.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class Future(

"""

__slots__ = ()

_inner_value: Awaitable[_ValueType]

def __init__(self, inner_value: Awaitable[_ValueType]) -> None:
Expand Down Expand Up @@ -522,6 +524,8 @@ class FutureResult(

"""

__slots__ = ()

_inner_value: Awaitable[Result[_ValueType, _ErrorType]]

def __init__(
Expand Down
4 changes: 4 additions & 0 deletions returns/interfaces/altable.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class _LawSpec(LawSpecDef):
https://en.wikibooks.org/wiki/Haskell/The_Functor_class#The_functor_laws
"""

__slots__ = ()

@law_definition
def identity_law(
altable: 'AltableN[_FirstType, _SecondType, _ThirdType]',
Expand All @@ -61,6 +63,8 @@ class AltableN(
):
"""Modifies the second type argument with a pure function."""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law1(_LawSpec.identity_law),
Law3(_LawSpec.associative_law),
Expand Down
4 changes: 4 additions & 0 deletions returns/interfaces/applicative.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class _LawSpec(LawSpecDef):
Discussion: https://bit.ly/3jffz3L
"""

__slots__ = ()

@law_definition
def identity_law(
container: 'ApplicativeN[_FirstType, _SecondType, _ThirdType]',
Expand Down Expand Up @@ -131,6 +133,8 @@ class ApplicativeN(

"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law1(_LawSpec.identity_law),
Law3(_LawSpec.interchange_law),
Expand Down
2 changes: 2 additions & 0 deletions returns/interfaces/bimappable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class BiMappableN(

"""

__slots__ = ()


#: Type alias for kinds with two type arguments.
BiMappable2 = BiMappableN[_FirstType, _SecondType, NoReturn]
Expand Down
2 changes: 2 additions & 0 deletions returns/interfaces/bindable.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class BindableN(Generic[_FirstType, _SecondType, _ThirdType]):
works with the first type argument.
"""

__slots__ = ()

@abstractmethod
def bind(
self: _BindableType,
Expand Down
4 changes: 4 additions & 0 deletions returns/interfaces/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class _LawSpec(LawSpecDef):
Good explanation: https://bit.ly/2Qsi5re
"""

__slots__ = ()

@law_definition
def left_identity_law(
raw_value: _FirstType,
Expand Down Expand Up @@ -111,6 +113,8 @@ class ContainerN(

"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law3(_LawSpec.left_identity_law),
Law1(_LawSpec.right_identity_law),
Expand Down
4 changes: 4 additions & 0 deletions returns/interfaces/equable.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class _LawSpec(LawSpecDef):
Description: https://bit.ly/34D40iT
"""

__slots__ = ()

@law_definition
def reflexive_law(
first: _EqualType,
Expand Down Expand Up @@ -62,6 +64,8 @@ class Equable(Lawful['Equable']):

"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law1(_LawSpec.reflexive_law),
Law2(_LawSpec.symmetry_law),
Expand Down
12 changes: 12 additions & 0 deletions returns/interfaces/failable.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class _FailableLawSpec(LawSpecDef):
We need to be sure that ``.lash`` won't lash success types.
"""

__slots__ = ()

@law_definition
def lash_short_circuit_law(
raw_value: _FirstType,
Expand Down Expand Up @@ -65,6 +67,8 @@ class FailableN(
Use ``SingleFailableN`` and ``DiverseFailableN`` instead.
"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law3(_FailableLawSpec.lash_short_circuit_law),
)
Expand All @@ -86,6 +90,8 @@ class _SingleFailableLawSpec(LawSpecDef):
works correctly for ``empty`` property.
"""

__slots__ = ()

@law_definition
def map_short_circuit_law(
container: 'SingleFailableN[_FirstType, _SecondType, _ThirdType]',
Expand Down Expand Up @@ -133,6 +139,8 @@ class SingleFailableN(
Like ``Maybe`` types where the only failed value is ``Nothing``.
"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law2(_SingleFailableLawSpec.map_short_circuit_law),
Law2(_SingleFailableLawSpec.bind_short_circuit_law),
Expand Down Expand Up @@ -163,6 +171,8 @@ class _DiverseFailableLawSpec(LawSpecDef):
works correctly for both success and failure types.
"""

__slots__ = ()

@law_definition
def map_short_circuit_law(
raw_value: _SecondType,
Expand Down Expand Up @@ -231,6 +241,8 @@ class DiverseFailableN(
Like ``Result`` types.
"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law3(_DiverseFailableLawSpec.map_short_circuit_law),
Law3(_DiverseFailableLawSpec.bind_short_circuit_law),
Expand Down
2 changes: 2 additions & 0 deletions returns/interfaces/lashable.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class LashableN(Generic[_FirstType, _SecondType, _ThirdType]):
works with the second type value.
"""

__slots__ = ()

@abstractmethod
def lash(
self: _LashableType,
Expand Down
4 changes: 4 additions & 0 deletions returns/interfaces/mappable.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class _LawSpec(LawSpecDef):
https://en.wikibooks.org/wiki/Haskell/The_Functor_class#The_functor_laws
"""

__slots__ = ()

@law_definition
def identity_law(
mappable: 'MappableN[_FirstType, _SecondType, _ThirdType]',
Expand Down Expand Up @@ -69,6 +71,8 @@ class MappableN(

"""

__slots__ = ()

_laws: ClassVar[Sequence[Law]] = (
Law1(_LawSpec.identity_law),
Law3(_LawSpec.associative_law),
Expand Down
6 changes: 6 additions & 0 deletions returns/interfaces/specific/future.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class FutureLikeN(io.IOLikeN[_FirstType, _SecondType, _ThirdType]):
But at the time this is not a real ``Future`` and cannot be awaited.
"""

__slots__ = ()

@abstractmethod
def bind_future(
self: _FutureLikeType,
Expand Down Expand Up @@ -103,6 +105,8 @@ class AwaitableFutureN(Generic[_FirstType, _SecondType, _ThirdType]):
Should not be used directly. Use ``FutureBasedN`` instead.
"""

__slots__ = ()

@abstractmethod
def __await__(self: _AsyncFutureType) -> Generator[
Any, Any, io.IOLikeN[_FirstType, _SecondType, _ThirdType],
Expand Down Expand Up @@ -136,6 +140,8 @@ class FutureBasedN(
They can be awaited.
"""

__slots__ = ()


#: Type alias for kinds with one type argument.
FutureBased1 = FutureBasedN[_FirstType, NoReturn, NoReturn]
Expand Down
Loading