From 9a38c9f45a2e9d2a306deedce2e243b5e7a83ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 8 Mar 2024 00:40:11 +0100 Subject: [PATCH 1/7] Fix classmethod tests with Python 3.13+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/GrahamDumpleton/wrapt/issues/259 The failures were: =================================== FAILURES =================================== _____________ TestCallingOuterClassMethod.test_class_call_function _____________ self = def test_class_call_function(self): # Test calling classmethod. Prior to Python 3.9, the instance # and class passed to the wrapper will both be None because our # decorator is surrounded by the classmethod decorator. The # classmethod decorator doesn't bind the method and treats it # like a normal function, explicitly passing the class as the # first argument with the actual arguments following that. This # was only finally fixed in Python 3.9. For more details see: # https://bugs.python.org/issue19072 _args = (1, 2) _kwargs = {'one': 1, 'two': 2} @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): if PYXY < (3, 9): self.assertEqual(instance, None) self.assertEqual(args, (Class,)+_args) else: self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) self.assertEqual(wrapped.__module__, _function.__module__) self.assertEqual(wrapped.__name__, _function.__name__) return wrapped(*args, **kwargs) @_decorator def _function(*args, **kwargs): return args, kwargs class Class(object): @classmethod @_decorator def _function(cls, *args, **kwargs): return (args, kwargs) > result = Class._function(*_args, **_kwargs) tests/test_outer_classmethod.py:160: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ tests/test_outer_classmethod.py:141: in _decorator self.assertEqual(instance, Class) E AssertionError: None != ___________ TestCallingOuterClassMethod.test_instance_call_function ____________ self = def test_instance_call_function(self): # Test calling classmethod via class instance. Prior to Python # 3.9, the instance and class passed to the wrapper will both be # None because our decorator is surrounded by the classmethod # decorator. The classmethod decorator doesn't bind the method # and treats it like a normal function, explicitly passing the # class as the first argument with the actual arguments # following that. This was only finally fixed in Python 3.9. For # more details see: https://bugs.python.org/issue19072 _args = (1, 2) _kwargs = {'one': 1, 'two': 2} @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): if PYXY < (3, 9): self.assertEqual(instance, None) self.assertEqual(args, (Class,)+_args) else: self.assertEqual(instance, Class) self.assertEqual(args, _args) self.assertEqual(kwargs, _kwargs) self.assertEqual(wrapped.__module__, _function.__module__) self.assertEqual(wrapped.__name__, _function.__name__) return wrapped(*args, **kwargs) @_decorator def _function(*args, **kwargs): return args, kwargs class Class(object): @classmethod @_decorator def _function(cls, *args, **kwargs): return (args, kwargs) > result = Class()._function(*_args, **_kwargs) tests/test_outer_classmethod.py:202: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ tests/test_outer_classmethod.py:183: in _decorator self.assertEqual(instance, Class) E AssertionError: None != _____________ TestSynchronized.test_synchronized_outer_classmethod _____________ self = def test_synchronized_outer_classmethod(self): # Prior to Python 3.9 this isn't detected as a class method # call, as the classmethod decorator doesn't bind the wrapped # function to the class before calling and just calls it direct, # explicitly passing the class as first argument. For more # details see: https://bugs.python.org/issue19072 if PYXY < (3, 9): _lock0 = getattr(C4.function2, '_synchronized_lock', None) else: _lock0 = getattr(C4, '_synchronized_lock', None) self.assertEqual(_lock0, None) c4.function2() if PYXY < (3, 9): _lock1 = getattr(C4.function2, '_synchronized_lock', None) else: _lock1 = getattr(C4, '_synchronized_lock', None) > self.assertNotEqual(_lock1, None) E AssertionError: None == None tests/test_synchronized_lock.py:181: AssertionError ----------------------------- Captured stdout call ----------------------------- function2 =========================== short test summary info ============================ FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_class_call_function FAILED tests/test_outer_classmethod.py::TestCallingOuterClassMethod::test_instance_call_function FAILED tests/test_synchronized_lock.py::TestSynchronized::test_synchronized_outer_classmethod ======================== 3 failed, 435 passed in 0.83s ========================= To fix the same failures on Python 3.9, they were adjusted in the past. For details see https://github.com/GrahamDumpleton/wrapt/issues/160 However, Python 3.13 reverted the change from 3.9, so this adds an upper bound for the conditionals. To make the conditionals easier to read, the if-else branches were switched. Signed-off-by: Filipe Laíns --- tests/test_outer_classmethod.py | 18 ++++++++++-------- tests/test_synchronized_lock.py | 26 ++++++++++++++------------ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/tests/test_outer_classmethod.py b/tests/test_outer_classmethod.py index ab807646..c08d34a5 100644 --- a/tests/test_outer_classmethod.py +++ b/tests/test_outer_classmethod.py @@ -128,18 +128,20 @@ def test_class_call_function(self): # first argument with the actual arguments following that. This # was only finally fixed in Python 3.9. For more details see: # https://bugs.python.org/issue19072 + # Starting with Python 3.13 the old behavior is back. + # For more details see https://github.com/python/cpython/issues/89519 _args = (1, 2) _kwargs = {'one': 1, 'two': 2} @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - if PYXY < (3, 9): - self.assertEqual(instance, None) - self.assertEqual(args, (Class,)+_args) - else: + if (3, 9) <= PYXY < (3, 13): self.assertEqual(instance, Class) self.assertEqual(args, _args) + else: + self.assertEqual(instance, None) + self.assertEqual(args, (Class,)+_args) self.assertEqual(kwargs, _kwargs) self.assertEqual(wrapped.__module__, _function.__module__) @@ -176,12 +178,12 @@ def test_instance_call_function(self): @wrapt.decorator def _decorator(wrapped, instance, args, kwargs): - if PYXY < (3, 9): - self.assertEqual(instance, None) - self.assertEqual(args, (Class,)+_args) - else: + if (3, 9) <= PYXY < (3, 13): self.assertEqual(instance, Class) self.assertEqual(args, _args) + else: + self.assertEqual(instance, None) + self.assertEqual(args, (Class,)+_args) self.assertEqual(kwargs, _kwargs) self.assertEqual(wrapped.__module__, _function.__module__) diff --git a/tests/test_synchronized_lock.py b/tests/test_synchronized_lock.py index 0e43f7af..7c41aa5a 100644 --- a/tests/test_synchronized_lock.py +++ b/tests/test_synchronized_lock.py @@ -165,36 +165,38 @@ def test_synchronized_outer_classmethod(self): # function to the class before calling and just calls it direct, # explicitly passing the class as first argument. For more # details see: https://bugs.python.org/issue19072 + # Starting with Python 3.13 the old behavior is back. + # For more details see https://github.com/python/cpython/issues/89519 - if PYXY < (3, 9): - _lock0 = getattr(C4.function2, '_synchronized_lock', None) - else: + if (3, 9) <= PYXY < (3, 13): _lock0 = getattr(C4, '_synchronized_lock', None) + else: + _lock0 = getattr(C4.function2, '_synchronized_lock', None) self.assertEqual(_lock0, None) c4.function2() - if PYXY < (3, 9): - _lock1 = getattr(C4.function2, '_synchronized_lock', None) - else: + if (3, 9) <= PYXY < (3, 13): _lock1 = getattr(C4, '_synchronized_lock', None) + else: + _lock1 = getattr(C4.function2, '_synchronized_lock', None) self.assertNotEqual(_lock1, None) C4.function2() - if PYXY < (3, 9): - _lock2 = getattr(C4.function2, '_synchronized_lock', None) - else: + if (3, 9) <= PYXY < (3, 13): _lock2 = getattr(C4, '_synchronized_lock', None) + else: + _lock2 = getattr(C4.function2, '_synchronized_lock', None) self.assertNotEqual(_lock2, None) self.assertEqual(_lock2, _lock1) C4.function2() - if PYXY < (3, 9): - _lock3 = getattr(C4.function2, '_synchronized_lock', None) - else: + if (3, 9) <= PYXY < (3, 13): _lock3 = getattr(C4, '_synchronized_lock', None) + else: + _lock3 = getattr(C4.function2, '_synchronized_lock', None) self.assertNotEqual(_lock3, None) self.assertEqual(_lock3, _lock2) From cd882990fee9296383226257c08e3bd7b24cf194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Fri, 20 Sep 2024 00:44:45 +0100 Subject: [PATCH 2/7] tox: fix install_command for non py311 environments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9c282c93..c0ebb67a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,7 +92,7 @@ deps = coverage pytest install_command = - py311,py311-{without,install,disable}-extensions: python -m pip install --no-binary coverage {opts} {packages} + python -m pip install --no-binary coverage {opts} {packages} commands = python -m coverage run --rcfile {toxinidir}/setup.cfg -m pytest -v {posargs} {toxinidir}/tests setenv = From 226eddcc8265c78d863785cb433c781d1b7f8d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Fri, 20 Sep 2024 00:45:11 +0100 Subject: [PATCH 3/7] tox: add Python 3.13 and 3.14 environments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe Laíns --- setup.cfg | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index c0ebb67a..47e3bd2c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,8 +70,8 @@ norecursedirs = .tox venv [tox:tox] envlist = - py{36,37,38,39,310,311,312} - py{36,37,38,39,310,311,312}-{without,install,disable}-extensions, + py{36,37,38,39,310,311,312,313,314} + py{36,37,38,39,310,311,312,313,314}-{without,install,disable}-extensions, pypy-without-extensions [gh-actions] @@ -83,6 +83,8 @@ python = 3.10: py310, py310-without-extensions, py310-install-extensions, py310-disable-extensions 3.11: py311, py311-without-extensions, py311-install-extensions, py311-disable-extensions 3.12: py312, py312-without-extensions, py312-install-extensions, py312-disable-extensions + 3.13: py313, py313-without-extensions, py313-install-extensions, py313-disable-extensions + 3.14: py314, py314-without-extensions, py314-install-extensions, py314-disable-extensions pypy-3.8: pypy-without-extensions pypy-3.9: pypy-without-extensions pypy-3.10: pypy-without-extensions From d19eeb7f8a3f88f6dc51608caa0df2e150b541c5 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Fri, 20 Sep 2024 10:25:27 +1000 Subject: [PATCH 4/7] Drop 3.6/3.7 from test matrix and make 3.8 minimum Python version. --- setup.cfg | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/setup.cfg b/setup.cfg index 47e3bd2c..65029715 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,13 +17,12 @@ classifiers = Development Status :: 5 - Production/Stable License :: OSI Approved :: BSD License Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 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 project_urls = @@ -33,7 +32,7 @@ project_urls = [options] zip_safe = false -python_requires = >= 3.6 +python_requires = >= 3.8 packages = find: package_dir = =src @@ -70,14 +69,12 @@ norecursedirs = .tox venv [tox:tox] envlist = - py{36,37,38,39,310,311,312,313,314} - py{36,37,38,39,310,311,312,313,314}-{without,install,disable}-extensions, + py{38,39,310,311,312,313,314} + py{38,39,310,311,312,313,314}-{without,install,disable}-extensions, pypy-without-extensions [gh-actions] python = - 3.6: py36, py36-without-extensions, py36-install-extensions, py36-disable-extensions - 3.7: py37, py37-without-extensions, py37-install-extensions, py37-disable-extensions 3.8: py38, py38-without-extensions, py38-install-extensions, py38-disable-extensions 3.9: py39, py39-without-extensions, py39-install-extensions, py39-disable-extensions 3.10: py310, py310-without-extensions, py310-install-extensions, py310-disable-extensions From 665eb83e1e71f796ca6378ba8e85c00673e9577c Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Fri, 20 Sep 2024 10:44:55 +1000 Subject: [PATCH 5/7] Update GitHub actions tests to drop 3.6/3.7 and add 3.13-dev. --- .github/workflows/main.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2aa76d97..a2fa567e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,13 +19,14 @@ jobs: python-version: # - 2.7 # - 3.5 - - 3.6 - - 3.7 + # - 3.6 + # - 3.7 - 3.8 - 3.9 - "3.10" - 3.11 - 3.12 + - 3.13-dev # - pypy-2.7 - pypy-3.8 - pypy-3.9 @@ -93,13 +94,14 @@ jobs: python-version: # - 2.7 # - 3.5 - - 3.6 - - 3.7 + # - 3.6 + # - 3.7 - 3.8 - 3.9 - "3.10" - 3.11 - 3.12 + - 3.13-dev # - pypy-2.7 - pypy-3.8 - pypy-3.9 @@ -155,13 +157,14 @@ jobs: - windows-latest python-version: # - 3.5 - - 3.6 - - 3.7 + # - 3.6 + # - 3.7 - 3.8 - 3.9 - "3.10" - 3.11 - 3.12 + - 3.13-dev # - pypy-2.7 - pypy-3.8 - pypy-3.9 From 7b57c1c618f1a0919beab206f4b3b9f63d4f9f9b Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Fri, 20 Sep 2024 10:53:45 +1000 Subject: [PATCH 6/7] Disable coveralls as not using it and don't understand it. --- .github/workflows/main.yml | 60 +++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a2fa567e..14ed06f6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -275,33 +275,33 @@ jobs: name: dist path: dist/*.whl - coveralls: - name: Generate code coverage report - if: ${{ false }} # disable for now - needs: - - test_linux - - test_macos - # - test_windows_py27 - - test_windows - runs-on: ubuntu-20.04 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Setup Python 3.9 - uses: actions/setup-python@v4 - with: - python-version: 3.9 - - name: Install coverage package - run: python -m pip install -U coverage - - name: Download partial coverage reports - uses: actions/download-artifact@v3 - with: - name: coverage - - name: Combine coverage - run: python -m coverage combine - - name: Report coverage - run: python -m coverage report - - name: Export coverage to XML - run: python -m coverage xml - - name: Upload coverage statistics to Coveralls - uses: AndreMiras/coveralls-python-action@develop + # coveralls: + # name: Generate code coverage report + # if: ${{ false }} # disable for now + # needs: + # - test_linux + # - test_macos + # # - test_windows_py27 + # - test_windows + # runs-on: ubuntu-20.04 + # steps: + # - name: Checkout code + # uses: actions/checkout@v3 + # - name: Setup Python 3.9 + # uses: actions/setup-python@v4 + # with: + # python-version: 3.9 + # - name: Install coverage package + # run: python -m pip install -U coverage + # - name: Download partial coverage reports + # uses: actions/download-artifact@v3 + # with: + # name: coverage + # - name: Combine coverage + # run: python -m coverage combine + # - name: Report coverage + # run: python -m coverage report + # - name: Export coverage to XML + # run: python -m coverage xml + # - name: Upload coverage statistics to Coveralls + # uses: AndreMiras/coveralls-python-action@develop From 9f42d9e03ac4fbab7a74ec7b04e2d6577007dc25 Mon Sep 17 00:00:00 2001 From: Graham Dumpleton Date: Fri, 20 Sep 2024 10:54:02 +1000 Subject: [PATCH 7/7] Update CI package for building wheels. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 14ed06f6..5521fe7e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -262,7 +262,7 @@ jobs: if: ${{ matrix.arch == 'aarch64' }} uses: docker/setup-qemu-action@v2 - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + uses: pypa/cibuildwheel@v2.21.1 with: output-dir: dist env: