1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
From e441c384427fdc6bc0bc26fbb0aba70ececacac0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tin=20Tvrtkovi=C4=87?= <tinchester@gmail.com>
Date: Sun, 2 Jun 2024 00:20:03 +0200
Subject: [PATCH 1/6] Python 3.13 support
---
.github/workflows/main.yml | 2 +-
pyproject.toml | 1 +
src/cattrs/dispatch.py | 3 +--
tox.ini | 4 +++-
4 files changed, 6 insertions(+), 4 deletions(-)
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
- python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.10"]
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.10"]
fail-fast: false
steps:
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,7 @@ authors = [
]
dependencies = [
"attrs>=23.1.0",
- "typing-extensions>=4.1.0, !=4.6.3; python_version < '3.11'",
+ "typing-extensions>=4.12.2",
"exceptiongroup>=1.1.1; python_version < '3.11'",
]
requires-python = ">=3.8"
@@ -59,6 +59,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Typing :: Typed",
@@ -77,7 +78,7 @@ ujson = [
"ujson>=5.7.0",
]
orjson = [
- "orjson>=3.9.2; implementation_name == \"cpython\"",
+ "orjson>=3.10.7; implementation_name == \"cpython\"",
]
msgpack = [
"msgpack>=1.0.5",
--- a/src/cattrs/dispatch.py
+++ b/src/cattrs/dispatch.py
@@ -91,9 +91,9 @@ class MultiStrategyDispatch(Generic[Hook
MultiStrategyDispatch uses a combination of exact-match dispatch,
singledispatch, and FunctionDispatch.
- :param converter: A converter to be used for factories that require converters.
:param fallback_factory: A hook factory to be called when a hook cannot be
produced.
+ :param converter: A converter to be used for factories that require converters.
.. versionchanged:: 23.2.0
Fallbacks are now factories.
@@ -103,7 +103,6 @@ class MultiStrategyDispatch(Generic[Hook
"""
_fallback_factory: HookFactory[Hook]
- _converter: BaseConverter
_direct_dispatch: dict[TargetType, Hook]
_function_dispatch: FunctionDispatch
_single_dispatch: Any
--- a/tox.ini
+++ b/tox.ini
@@ -6,10 +6,12 @@ python =
3.10: py310
3.11: py311, docs
3.12: py312, lint
+ 3.13: py313
pypy-3: pypy3
+
[tox]
-envlist = pypy3, py38, py39, py310, py311, py312, lint, docs
+envlist = pypy3, py38, py39, py310, py311, py312, py313, lint, docs
isolated_build = true
skipsdist = true
@@ -42,6 +44,15 @@ setenv =
COVERAGE_PROCESS_START={toxinidir}/pyproject.toml
COVERAGE_CORE=sysmon
+[testenv:py313]
+setenv =
+ PDM_IGNORE_SAVED_PYTHON="1"
+ COVERAGE_PROCESS_START={toxinidir}/pyproject.toml
+ COVERAGE_CORE=sysmon
+commands_pre =
+ pdm sync -G ujson,msgpack,pyyaml,tomlkit,cbor2,bson,orjson,test
+ python -c 'import pathlib; pathlib.Path("{env_site_packages_dir}/cov.pth").write_text("import coverage; coverage.process_startup()")'
+
[testenv:pypy3]
setenv =
FAST = 1
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -37,3 +37,5 @@ if sys.version_info < (3, 12):
collect_ignore_glob.append("*_695.py")
if platform.python_implementation() == "PyPy":
collect_ignore_glob.append("*_cpython.py")
+if sys.version_info >= (3, 13): # Remove when msgspec supports 3.13.
+ collect_ignore_glob.append("*test_msgspec_cpython.py")
--- a/tests/test_preconf.py
+++ b/tests/test_preconf.py
@@ -4,7 +4,7 @@ from enum import Enum, IntEnum, unique
from json import dumps as json_dumps
from json import loads as json_loads
from platform import python_implementation
-from typing import Any, Dict, List, NamedTuple, NewType, Tuple, Union
+from typing import Any, Dict, Final, List, NamedTuple, NewType, Tuple, Union
import pytest
from attrs import define
@@ -699,7 +699,10 @@ def test_cbor2_unions(union_and_val: tup
assert converter.structure(val, type) == val
-@pytest.mark.skipif(python_implementation() == "PyPy", reason="no msgspec on PyPy")
+NO_MSGSPEC: Final = python_implementation() == "PyPy" or sys.version_info[:2] >= (3, 13)
+
+
+@pytest.mark.skipif(NO_MSGSPEC, reason="msgspec not available")
@given(everythings(allow_inf=False))
def test_msgspec_json_converter(everything: Everything):
from cattrs.preconf.msgspec import make_converter as msgspec_make_converter
@@ -709,7 +712,7 @@ def test_msgspec_json_converter(everythi
assert converter.loads(raw, Everything) == everything
-@pytest.mark.skipif(python_implementation() == "PyPy", reason="no msgspec on PyPy")
+@pytest.mark.skipif(NO_MSGSPEC, reason="msgspec not available")
@given(everythings(allow_inf=False))
def test_msgspec_json_unstruct_collection_overrides(everything: Everything):
"""Ensure collection overrides work."""
@@ -724,7 +727,7 @@ def test_msgspec_json_unstruct_collectio
assert raw["a_frozenset"] == sorted(raw["a_frozenset"])
-@pytest.mark.skipif(python_implementation() == "PyPy", reason="no msgspec on PyPy")
+@pytest.mark.skipif(NO_MSGSPEC, reason="msgspec not available")
@given(
union_and_val=native_unions(
include_datetimes=False,
--- a/src/cattrs/gen/__init__.py
+++ b/src/cattrs/gen/__init__.py
@@ -14,6 +14,7 @@ from typing import (
)
from attrs import NOTHING, Attribute, Factory, resolve_types
+from typing_extensions import NoDefault
from .._compat import (
ANIES,
@@ -1029,6 +1030,9 @@ def iterable_unstructure_factory(
"""A hook factory for unstructuring iterables.
:param unstructure_to: Force unstructuring to this type, if provided.
+
+ .. versionchanged:: 24.2.0
+ `typing.NoDefault` is now correctly handled as `Any`.
"""
handler = converter.unstructure
@@ -1039,6 +1043,8 @@ def iterable_unstructure_factory(
type_arg = cl.__args__[0]
if isinstance(type_arg, TypeVar):
type_arg = getattr(type_arg, "__default__", Any)
+ if type_arg is NoDefault:
+ type_arg = Any
handler = converter.get_unstructure_hook(type_arg, cache_result=False)
if handler == identity:
# Save ourselves the trouble of iterating over it all.
|