From bfd605d08896e5ecdf9791b0f748decfbb143d70 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Fri, 24 Jun 2022 09:20:13 +0200 Subject: [PATCH] Add support for Python 3.11 --- setup.py | 1 + src/attr/_compat.py | 26 +++++++++++--------------- tests/test_annotations.py | 13 +++++++++---- tests/test_make.py | 8 ++++++-- tests/test_slots.py | 18 ++++++++++++------ 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index 00e7b01..32ba64b 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ CLASSIFIERS = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/src/attr/_compat.py b/src/attr/_compat.py index dc0cb02..b5c6ed6 100644 --- a/src/attr/_compat.py +++ b/src/attr/_compat.py @@ -181,12 +181,8 @@ def make_set_closure_cell(): # Convert this code object to a code object that sets the # function's first _freevar_ (not cellvar) to the argument. if sys.version_info >= (3, 8): - # CPython 3.8+ has an incompatible CodeType signature - # (added a posonlyargcount argument) but also added - # CodeType.replace() to do this without counting parameters. - set_first_freevar_code = co.replace( - co_cellvars=co.co_freevars, co_freevars=co.co_cellvars - ) + def set_closure_cell(cell, value): + cell.cell_contents = value else: args = [co.co_argcount] if not PY2: @@ -211,15 +207,15 @@ def make_set_closure_cell(): ) set_first_freevar_code = types.CodeType(*args) - def set_closure_cell(cell, value): - # Create a function using the set_first_freevar_code, - # whose first closure cell is `cell`. Calling it will - # change the value of that cell. - setter = types.FunctionType( - set_first_freevar_code, {}, "setter", (), (cell,) - ) - # And call it to set the cell. - setter(value) + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) # Make sure it works on this interpreter: def make_func_with_cell(): diff --git a/tests/test_annotations.py b/tests/test_annotations.py index a201ebf..014bea5 100644 --- a/tests/test_annotations.py +++ b/tests/test_annotations.py @@ -94,6 +94,10 @@ class TestAnnotations: assert 1 == len(attr.fields(C)) assert_init_annotations(C, x=typing.List[int]) + @pytest.mark.skipif( + sys.version_info[:2] < (3, 11), + reason="Incompatible behavior on older Pythons", + ) @pytest.mark.parametrize("slots", [True, False]) def test_auto_attribs(self, slots): """ @@ -149,7 +153,7 @@ class TestAnnotations: x=typing.List[int], y=int, z=int, - foo=typing.Optional[typing.Any], + foo=typing.Any, ) @pytest.mark.parametrize("slots", [True, False]) @@ -384,8 +388,9 @@ class TestAnnotations: assert attr.converters.optional(noop).__annotations__ == {} - @pytest.mark.xfail( - sys.version_info[:2] == (3, 6), reason="Does not work on 3.6." + @pytest.mark.skipif( + sys.version_info[:2] < (3, 11), + reason="Incompatible behavior on older Pythons", ) @pytest.mark.parametrize("slots", [True, False]) def test_annotations_strings(self, slots): @@ -417,7 +422,7 @@ class TestAnnotations: x=typing.List[int], y=int, z=int, - foo=typing.Optional[typing.Any], + foo=typing.Any, ) @pytest.mark.parametrize("slots", [True, False]) diff --git a/tests/test_make.py b/tests/test_make.py index 729d3a7..71a50a5 100644 --- a/tests/test_make.py +++ b/tests/test_make.py @@ -2312,7 +2312,9 @@ class TestAutoDetect: def __getstate__(self): return ("hi",) - assert None is getattr(C(), "__setstate__", None) + assert getattr(object, "__setstate__", None) is getattr( + C, "__setstate__", None + ) @attr.s(slots=slots, auto_detect=True) class C(object): @@ -2328,7 +2330,9 @@ class TestAutoDetect: i.__setstate__(()) assert True is i.called - assert None is getattr(C(), "__getstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C, "__getstate__", None + ) @pytest.mark.skipif(PY310, reason="Pre-3.10 only.") def test_match_args_pre_310(self): diff --git a/tests/test_slots.py b/tests/test_slots.py index baf9a40..3da80cc 100644 --- a/tests/test_slots.py +++ b/tests/test_slots.py @@ -665,10 +665,12 @@ class TestPickle(object): As long as getstate_setstate is None, nothing is done to dict classes. """ - i = C1(1, 2) - - assert None is getattr(i, "__getstate__", None) - assert None is getattr(i, "__setstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C1, "__getstate__", None + ) + assert getattr(object, "__setstate__", None) is getattr( + C1, "__setstate__", None + ) def test_no_getstate_setstate_if_option_false(self): """ @@ -681,8 +683,12 @@ class TestPickle(object): i = C(42) - assert None is getattr(i, "__getstate__", None) - assert None is getattr(i, "__setstate__", None) + assert getattr(object, "__getstate__", None) is getattr( + C, "__getstate__", None + ) + assert getattr(object, "__setstate__", None) is getattr( + C, "__setstate__", None + ) @pytest.mark.parametrize("cls", [C2(1), C2Slots(1)]) def test_getstate_set_state_force_true(self, cls): -- 2.36.1