diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a3fc064f..d4d95220 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.11', '3.12', '3.13', '3.14'] + python-version: ['3.11', '3.12', '3.13', '3.14', '3.15'] steps: - uses: actions/checkout@v7.0.1 diff --git a/.gitignore b/.gitignore index 07980c8a..116b3e71 100644 --- a/.gitignore +++ b/.gitignore @@ -199,7 +199,7 @@ fabric.properties ### Custom ### ex.py experiments/ -.vscode/tags +.vscode/ .pyre .pytype build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4c613c..14f280a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ Versions before `1.0.0` are `0Ver`-based: incremental in minor, bugfixes only are patches. See [0Ver](https://0ver.org/). +## 0.30.0 WIP + +### Features + +- Add `python3.15` support + ## 0.29.1 WIP diff --git a/returns/contrib/mypy/_typeops/analtype.py b/returns/contrib/mypy/_typeops/analtype.py index c6835325..c651f008 100644 --- a/returns/contrib/mypy/_typeops/analtype.py +++ b/returns/contrib/mypy/_typeops/analtype.py @@ -1,5 +1,4 @@ from collections.abc import Sequence -from types import MappingProxyType from typing import Final, Literal, overload from mypy.checkmember import analyze_member_access @@ -9,8 +8,9 @@ from returns.contrib.mypy._structures.args import FuncArg from returns.contrib.mypy._structures.types import CallableContext +from returns.primitives.compat import make_immutable -_KIND_MAPPING: Final = MappingProxyType({ +_KIND_MAPPING: Final = make_immutable({ # We have to replace `ARG_OPT` to `ARG_NAMED`, # because `ARG_OPT` is only used in function defs, not calls. # And `ARG_NAMED` is the same thing for calls. diff --git a/returns/contrib/mypy/_typeops/transform_callable.py b/returns/contrib/mypy/_typeops/transform_callable.py index 059ae652..450cbd57 100644 --- a/returns/contrib/mypy/_typeops/transform_callable.py +++ b/returns/contrib/mypy/_typeops/transform_callable.py @@ -1,4 +1,3 @@ -from types import MappingProxyType from typing import ClassVar, Final, final from mypy.nodes import ( @@ -22,6 +21,7 @@ from mypy.types import Type as MypyType from returns.contrib.mypy._structures.args import FuncArg +from returns.primitives.compat import make_immutable _VARIADIC_KINDS: Final = frozenset((ARG_STAR, ARG_STAR2)) """Kinds of arguments that consume leftover arguments. @@ -33,7 +33,7 @@ _POSITIONAL_KINDS: Final = frozenset((ARG_POS, ARG_OPT)) """Kinds of arguments that can be passed positionally.""" -_KEYWORD_ONLY_KINDS: Final = MappingProxyType({ +_KEYWORD_ONLY_KINDS: Final = make_immutable({ ARG_POS: ARG_NAMED, ARG_OPT: ARG_NAMED_OPT, }) diff --git a/returns/contrib/pytest/plugin.py b/returns/contrib/pytest/plugin.py index f7c4fb9f..cf8413b8 100644 --- a/returns/contrib/pytest/plugin.py +++ b/returns/contrib/pytest/plugin.py @@ -1,18 +1,10 @@ import inspect import sys -from collections.abc import Callable, Generator, Iterator +from collections.abc import Callable, Generator, Iterator, Mapping from contextlib import ExitStack, contextmanager from functools import partial, wraps -from types import FrameType, MappingProxyType -from typing import ( - TYPE_CHECKING, - Any, - Final, - TypeAlias, - TypeVar, - Union, - final, -) +from types import FrameType +from typing import TYPE_CHECKING, Any, TypeAlias, TypeVar, Union, final from unittest import mock import pytest @@ -148,7 +140,7 @@ def _spy_error_handling() -> Generator[_ErrorsHandled]: errs: _ErrorsHandled = {} with ExitStack() as cleanup: for container in _containers_to_patch(): - for method, patch in _ERROR_HANDLING_PATCHERS.items(): + for method, patch in _error_handling_patchers().items(): cleanup.enter_context( mock.patch.object( container, @@ -182,6 +174,16 @@ def _containers_to_patch() -> list: ] +def _error_handling_patchers() -> Mapping[str, Callable]: + from returns.primitives.compat import make_immutable # noqa: PLC0415 + + return make_immutable({ + 'lash': _patched_error_handler, + 'map': _patched_error_copier, + 'alt': _patched_error_copier, + }) + + def _patched_error_handler( original: _FunctionType, errs: _ErrorsHandled, @@ -224,10 +226,3 @@ def sync_wrapper(self, *args, **kwargs): return original_result return wraps(original)(sync_wrapper) # type: ignore - - -_ERROR_HANDLING_PATCHERS: Final = MappingProxyType({ - 'lash': _patched_error_handler, - 'map': _patched_error_copier, - 'alt': _patched_error_copier, -}) diff --git a/returns/primitives/compat.py b/returns/primitives/compat.py new file mode 100644 index 00000000..95bba472 --- /dev/null +++ b/returns/primitives/compat.py @@ -0,0 +1,16 @@ +import types +from collections.abc import Mapping +from typing import TypeVar + +_KT = TypeVar('_KT') +_VT = TypeVar('_VT') + + +def make_immutable( + mutable_dict: Mapping[_KT, _VT], +) -> Mapping[_KT, _VT]: + """Make a dictionary immutable.""" + try: # pragma: >=3.15 cover + return frozendict(mutable_dict) # type: ignore[no-any-return, name-defined, unused-ignore] + except NameError: # pragma: <3.15 cover + return types.MappingProxyType(mutable_dict) diff --git a/setup.cfg b/setup.cfg index ec5a5179..35a1e638 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,6 +54,8 @@ per-file-ignores = # We allow `futures` to do attribute access: returns/future.py: WPS402 returns/_internal/futures/*.py: WPS204, WPS433, WPS437 + # Allow `types.MappingProxyType` in compat code: + returns/primitives/compat.py: WPS483 # We allow a lot of durty hacks in our plugins: returns/contrib/mypy/*.py: S101, WPS201 returns/contrib/pytest/__init__.py: F401