From 0095308e343ebd013b53a124ddb53a7a09e36496 Mon Sep 17 00:00:00 2001 From: Khabib Shakhbanov <136262760+Khabib73@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:52:32 +0300 Subject: [PATCH 1/7] Update test.yml --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2564d006c2887f5b0ff03fa41a7fb3a91cdc8650 Mon Sep 17 00:00:00 2001 From: Khabib <136262760+Khabib73@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:20:39 +0300 Subject: [PATCH 2/7] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4c613c..29c58177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ See [0Ver](https://0ver.org/). another awaitable (instead of being coroutine functions themselves) type-check correctly +### Features + +- Add `python3.15` support + ## 0.29.0 From b7611389830a4316ed8be99a699e2d90f98aa085 Mon Sep 17 00:00:00 2001 From: Khabib <136262760+Khabib73@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:33:29 +0300 Subject: [PATCH 3/7] Fix WPS --- .gitignore | 2 +- CHANGELOG.md | 10 +++--- returns/contrib/mypy/_typeops/analtype.py | 4 +-- .../mypy/_typeops/transform_callable.py | 4 +-- returns/contrib/pytest/plugin.py | 32 ++++++++----------- returns/primitives/compat.py | 20 ++++++++++++ 6 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 returns/primitives/compat.py 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 29c58177..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 @@ -15,10 +21,6 @@ See [0Ver](https://0ver.org/). another awaitable (instead of being coroutine functions themselves) type-check correctly -### Features - -- Add `python3.15` support - ## 0.29.0 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..5bb3ed98 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,15 @@ 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 +225,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..47c00068 --- /dev/null +++ b/returns/primitives/compat.py @@ -0,0 +1,20 @@ +import sys +import types +from collections.abc import Mapping +from typing import Final, TypeVar + +_KT = TypeVar('_KT') +_VT = TypeVar('_VT') + +# This indicates that we are running on python3.15+ +PY315: Final = sys.version_info >= (3, 15) + + +def make_immutable( + mutable_dict: Mapping[_KT, _VT], +) -> Mapping[_KT, _VT]: + """Make a dictionary immutable.""" + try: + return frozendict(mutable_dict) + except NameError: + return types.MappingProxyType(mutable_dict) From 6d74f8424264bbe3e9f1a751991d600a8953ce0c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 05:33:46 +0000 Subject: [PATCH 4/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- returns/contrib/pytest/plugin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/returns/contrib/pytest/plugin.py b/returns/contrib/pytest/plugin.py index 5bb3ed98..cf8413b8 100644 --- a/returns/contrib/pytest/plugin.py +++ b/returns/contrib/pytest/plugin.py @@ -183,6 +183,7 @@ def _error_handling_patchers() -> Mapping[str, Callable]: 'alt': _patched_error_copier, }) + def _patched_error_handler( original: _FunctionType, errs: _ErrorsHandled, From e1fc8174b53cccef01627705392a88d65716c20d Mon Sep 17 00:00:00 2001 From: Khabib <136262760+Khabib73@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:44:44 +0300 Subject: [PATCH 5/7] fix mypy --- returns/primitives/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/returns/primitives/compat.py b/returns/primitives/compat.py index 47c00068..91d25e1f 100644 --- a/returns/primitives/compat.py +++ b/returns/primitives/compat.py @@ -15,6 +15,6 @@ def make_immutable( ) -> Mapping[_KT, _VT]: """Make a dictionary immutable.""" try: - return frozendict(mutable_dict) + return frozendict(mutable_dict) # type: ignore[no-any-return, name-defined, unused-ignore] except NameError: return types.MappingProxyType(mutable_dict) From e97732e2e0196fe00e715464ae1f7eedd66ad062 Mon Sep 17 00:00:00 2001 From: Khabib <136262760+Khabib73@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:47:30 +0300 Subject: [PATCH 6/7] Allow WPS483 --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) 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 From 436e5f2902529f786b77b7aae4f1b2070a1f63d6 Mon Sep 17 00:00:00 2001 From: Khabib <136262760+Khabib73@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:53:31 +0300 Subject: [PATCH 7/7] Fix cov --- returns/primitives/compat.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/returns/primitives/compat.py b/returns/primitives/compat.py index 91d25e1f..95bba472 100644 --- a/returns/primitives/compat.py +++ b/returns/primitives/compat.py @@ -1,20 +1,16 @@ -import sys import types from collections.abc import Mapping -from typing import Final, TypeVar +from typing import TypeVar _KT = TypeVar('_KT') _VT = TypeVar('_VT') -# This indicates that we are running on python3.15+ -PY315: Final = sys.version_info >= (3, 15) - def make_immutable( mutable_dict: Mapping[_KT, _VT], ) -> Mapping[_KT, _VT]: """Make a dictionary immutable.""" - try: + try: # pragma: >=3.15 cover return frozendict(mutable_dict) # type: ignore[no-any-return, name-defined, unused-ignore] - except NameError: + except NameError: # pragma: <3.15 cover return types.MappingProxyType(mutable_dict)