From d497a9870f38e8e69f7a49dc4c80edb15ca15fff Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 28 May 2024 10:51:36 +0200 Subject: [PATCH 1/3] - drop support for Python 3.7 --- .github/workflows/tests.yml | 36 ++++++++++-------------------------- .manylinux-install.sh | 2 -- .meta.toml | 2 +- CHANGES.rst | 2 ++ setup.py | 3 +-- tox.ini | 1 - 6 files changed, 14 insertions(+), 32 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac8fd4d3..e2572525 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,6 @@ jobs: matrix: python-version: - "pypy-3.10" - - "3.7" - "3.8" - "3.9" - "3.10" @@ -106,13 +105,8 @@ jobs: - "3.13" os: [ubuntu-latest, macos-latest, windows-latest] exclude: - - os: macos-latest - python-version: "3.7" - os: macos-latest python-version: "pypy-3.10" - include: - - python-version: "3.7" - os: macos-12 steps: - name: checkout @@ -169,11 +163,10 @@ jobs: pip install -U pip pip install -U "setuptools<69" wheel twine - - name: Build zope.interface (macOS x86_64, Python 3.8+) + - name: Build zope.interface (macOS x86_64) if: > startsWith(runner.os, 'Mac') - && !(startsWith(matrix.python-version, 'pypy') - || matrix.python-version == '3.7') + && !startsWith(matrix.python-version, 'pypy') env: MACOSX_DEPLOYMENT_TARGET: 10.9 _PYTHON_HOST_PLATFORM: macosx-10.9-x86_64 @@ -183,11 +176,10 @@ jobs: # output (pip install uses a random temporary directory, making this difficult). python setup.py build_ext -i python setup.py bdist_wheel - - name: Build zope.interface (macOS arm64, Python 3.8+) + - name: Build zope.interface (macOS arm64) if: > startsWith(runner.os, 'Mac') - && !(startsWith(matrix.python-version, 'pypy') - || matrix.python-version == '3.7') + && !startsWith(matrix.python-version, 'pypy') env: MACOSX_DEPLOYMENT_TARGET: 11.0 _PYTHON_HOST_PLATFORM: macosx-11.0-arm64 @@ -201,7 +193,6 @@ jobs: if: > !startsWith(runner.os, 'Mac') || startsWith(matrix.python-version, 'pypy') - || matrix.python-version == '3.7' run: | # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure # output (pip install uses a random temporary directory, making this difficult). @@ -230,19 +221,18 @@ jobs: startsWith(runner.os, 'Mac') uses: actions/upload-artifact@v4 with: - name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl + # The x86_64 wheel is uploaded with a different name just so it can be + # manually downloaded when desired. The wheel itself *cannot* be tested + # on the GHA runner, which uses arm64 architecture. + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}-x86_64.whl path: dist/*x86_64.whl - name: Upload zope.interface wheel (macOS arm64) if: > startsWith(runner.os, 'Mac') - && !(startsWith(matrix.python-version, 'pypy') - || matrix.python-version == '3.7') + && !startsWith(matrix.python-version, 'pypy') uses: actions/upload-artifact@v4 with: - # The arm64 wheel is uploaded with a different name just so it can be - # manually downloaded when desired. The wheel itself *cannot* be tested - # on the GHA runner, which uses x86_64 architecture. - name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}-arm64.whl + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl path: dist/*arm64.whl - name: Upload zope.interface wheel (all other platforms) if: > @@ -274,7 +264,6 @@ jobs: matrix: python-version: - "pypy-3.10" - - "3.7" - "3.8" - "3.9" - "3.10" @@ -283,13 +272,8 @@ jobs: - "3.13" os: [ubuntu-latest, macos-latest, windows-latest] exclude: - - os: macos-latest - python-version: "3.7" - os: macos-latest python-version: "pypy-3.10" - include: - - python-version: "3.7" - os: macos-12 steps: - name: checkout diff --git a/.manylinux-install.sh b/.manylinux-install.sh index 532c13d2..39697d5e 100755 --- a/.manylinux-install.sh +++ b/.manylinux-install.sh @@ -29,7 +29,6 @@ yum -y install libffi-devel tox_env_map() { case $1 in *"cp313"*) echo 'py313';; - *"cp37"*) echo 'py37';; *"cp38"*) echo 'py38';; *"cp39"*) echo 'py39';; *"cp310"*) echo 'py310';; @@ -45,7 +44,6 @@ for PYBIN in /opt/python/*/bin; do [[ "${PYBIN}" == *"cp313/"* ]] || \ [[ "${PYBIN}" == *"cp311/"* ]] || \ [[ "${PYBIN}" == *"cp312/"* ]] || \ - [[ "${PYBIN}" == *"cp37/"* ]] || \ [[ "${PYBIN}" == *"cp38/"* ]] || \ [[ "${PYBIN}" == *"cp39/"* ]] || \ [[ "${PYBIN}" == *"cp310/"* ]] ; then diff --git a/.meta.toml b/.meta.toml index da5f51fe..01013754 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/config/c-code [meta] template = "c-code" -commit-id = "1c0f31f5" +commit-id = "8d837c89" [python] with-appveyor = true diff --git a/CHANGES.rst b/CHANGES.rst index b3f7fe88..d3be7dab 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ 6.5 (unreleased) ================ +- Drop support for Python 3.7. + 6.4.post2 (2024-05-24) ====================== diff --git a/setup.py b/setup.py index 6f28be53..eb32ad6d 100644 --- a/setup.py +++ b/setup.py @@ -113,7 +113,6 @@ def read(*rnames): "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -135,7 +134,7 @@ def read(*rnames): zip_safe=False, tests_require=tests_require, install_requires=['setuptools'], - python_requires='>=3.7', + python_requires='>=3.8', extras_require={ 'docs': ['Sphinx', 'repoze.sphinx.autointerface', diff --git a/tox.ini b/tox.ini index bab63c8d..89118283 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ minversion = 4.0 envlist = lint - py37,py37-pure py38,py38-pure py39,py39-pure py310,py310-pure From 1b191c2eb562cca698c17cf684ba6fae7406c1f6 Mon Sep 17 00:00:00 2001 From: Tres Seaver Date: Mon, 27 May 2024 14:57:04 -0400 Subject: [PATCH 2/3] tests: doctests pass under 'pypy3' --- docs/verify.rst | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/verify.rst b/docs/verify.rst index bc1be1c6..b93f9912 100644 --- a/docs/verify.rst +++ b/docs/verify.rst @@ -49,7 +49,7 @@ defined. .. doctest:: >>> verify_foo() - The object has failed to implement interface ...IFoo: + The object <...Foo...> has failed to implement interface ...IFoo: Does not declaratively implement the interface The base.IBase.x attribute was not provided The module.IFoo.y attribute was not provided @@ -61,7 +61,7 @@ declaring the correct interface. >>> Foo.x = Foo.y = 42 >>> verify_foo() - The object has failed to implement interface ...IFoo: Does not declaratively implement the interface. + The object <...Foo...> has failed to implement interface ...IFoo: Does not declaratively implement the interface. If we want to only check the structure of the object, without examining its declarations, we can use the ``tentative`` argument. @@ -125,13 +125,13 @@ exception. ... class Foo(object): ... x = 1 >>> verify_foo() - The object has failed to implement interface ...IFoo: The module.IFoo.y attribute was not provided. + The object <...Foo...> has failed to implement interface ...IFoo: The module.IFoo.y attribute was not provided. >>> @implementer(IFoo) ... class Foo(object): ... def __init__(self): ... self.y = 2 >>> verify_foo() - The object has failed to implement interface ...IFoo: The base.IBase.x attribute was not provided. + The object <...Foo...> has failed to implement interface ...IFoo: The base.IBase.x attribute was not provided. If both attributes are missing, an exception is raised reporting both errors. @@ -142,7 +142,7 @@ both errors. ... class Foo(object): ... pass >>> verify_foo() - The object has failed to implement interface ...IFoo: + The object <...Foo ...> has failed to implement interface ...IFoo: The base.IBase.x attribute was not provided The module.IFoo.y attribute was not provided @@ -161,7 +161,7 @@ when trying to get its value, the attribute is considered missing: ... def x(self): ... raise AttributeError >>> verify_foo() - The object has failed to implement interface ...IFoo: The module.IFoo.x attribute was not provided. + The object <...Foo...> has failed to implement interface ...IFoo: The module.IFoo.x attribute was not provided. Any other exception raised by a property will propagate to the caller of @@ -209,7 +209,7 @@ that takes one argument. If we don't provide it, we get an error. ... class Foo(object): ... pass >>> verify_foo() - The object has failed to implement interface ...IFoo: The module.IFoo.simple(arg1) attribute was not provided. + The object <...Foo...> has failed to implement interface ...IFoo: The module.IFoo.simple(arg1) attribute was not provided. Once they exist, they are checked to be callable, and for compatible signatures. @@ -219,7 +219,7 @@ Not being callable is an error. >>> Foo.simple = 42 >>> verify_foo() - The object has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '42' is not a method. + The object <...Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '42' is not a method. Taking too few arguments is an error. (Recall that the ``self`` argument is implicit.) @@ -228,7 +228,7 @@ argument is implicit.) >>> Foo.simple = lambda self: "I take no arguments" >>> verify_foo() - The object has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '()' doesn't allow enough arguments. + The object <...Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '()' doesn't allow enough arguments. Requiring too many arguments is an error. @@ -236,7 +236,7 @@ Requiring too many arguments is an error. >>> Foo.simple = lambda self, a, b: "I require two arguments" >>> verify_foo() - The object has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '(a, b)' requires too many arguments. + The object <...Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.simple(arg1) is violated because '(a, b)' requires too many arguments. Variable arguments can be used to implement the required number, as can arguments with defaults. @@ -263,7 +263,7 @@ variable keyword arguments, the implementation must also accept them. ... class Foo(object): ... def needs_kwargs(self, a=1, b=2): pass >>> verify_foo() - The object has failed to implement interface ...IFoo: The contract of module.IFoo.needs_kwargs(**kwargs) is violated because 'Foo.needs_kwargs(a=1, b=2)' doesn't support keyword arguments. + The object <...Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.needs_kwargs(**kwargs) is violated because 'Foo.needs_kwargs(a=1, b=2)' doesn't support keyword arguments. >>> oname, __name__ = __name__, 'module' >>> class IFoo(Interface): @@ -273,7 +273,7 @@ variable keyword arguments, the implementation must also accept them. ... class Foo(object): ... def needs_varargs(self, **kwargs): pass >>> verify_foo() - The object has failed to implement interface ...IFoo: The contract of module.IFoo.needs_varargs(*args) is violated because 'Foo.needs_varargs(**kwargs)' doesn't support variable arguments. + The object <...Foo...> has failed to implement interface ...IFoo: The contract of module.IFoo.needs_varargs(*args) is violated because 'Foo.needs_varargs(**kwargs)' doesn't support variable arguments. Of course, missing attributes are also found and reported, and the source interface of the missing attribute is included. Similarly, when @@ -294,7 +294,7 @@ the failing method is from a parent class, that is also reported. ... class Foo(Base): ... pass >>> verify_foo() - The object has failed to implement interface ...IFoo: + The object <...Foo...> has failed to implement interface ...IFoo: The contract of base.IBase.method(arg1) is violated because 'Base.method()' doesn't allow enough arguments The module.IFoo.x attribute was not provided From 12da5fdfa01bd18f8c88c1dcfc44b6d40824b537 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 28 May 2024 10:56:32 +0200 Subject: [PATCH 3/3] - run pyupgrade and fix pypy3 doctests (from 2bb0ee6) --- src/zope/interface/common/__init__.py | 4 ++-- src/zope/interface/declarations.py | 6 +++--- src/zope/interface/document.py | 4 ++-- src/zope/interface/exceptions.py | 2 +- src/zope/interface/interface.py | 6 +++--- src/zope/interface/interfaces.py | 2 +- src/zope/interface/registry.py | 2 +- src/zope/interface/ro.py | 2 +- src/zope/interface/tests/odd.py | 2 +- src/zope/interface/tests/test_declarations.py | 4 ++-- src/zope/interface/tests/test_registry.py | 2 +- 11 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/zope/interface/common/__init__.py b/src/zope/interface/common/__init__.py index 4fee7e19..256ddc91 100644 --- a/src/zope/interface/common/__init__.py +++ b/src/zope/interface/common/__init__.py @@ -167,7 +167,7 @@ def __optional_methods_to_docs(attrs): return '' docs = "\n\nThe following methods are optional:\n - " + "\n-".join( - "{}\n{}".format(k, v.__doc__) for k, v in optionals.items() + f"{k}\n{v.__doc__}" for k, v in optionals.items() ) return docs @@ -181,7 +181,7 @@ def ref(c): return "`%s`" % name if mod == '_io': mod = 'io' - return "`{}.{}`".format(mod, name) + return f"`{mod}.{name}`" implementations_doc = "\n - ".join( ref(c) diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py index 7f2384af..02c9f533 100644 --- a/src/zope/interface/declarations.py +++ b/src/zope/interface/declarations.py @@ -348,7 +348,7 @@ def __repr__(self): declared_names = self._argument_names_for_repr(self.declared) if declared_names: declared_names = ', ' + declared_names - return 'classImplements({}{})'.format(name, declared_names) + return f'classImplements({name}{declared_names})' def __reduce__(self): return implementedBy, (self.inherit, ) @@ -772,7 +772,7 @@ def __repr__(self): if len(mod_names) == 1: mod_names = "sys.modules[%r]" % mod_names[0] ordered_names = ( - '{}, '.format(mod_names) + f'{mod_names}, ' ) + ordered_names return "{}({})".format( function_name, @@ -937,7 +937,7 @@ def __repr__(self): # Thus, as our repr, we go with the ``directlyProvides()`` syntax. interfaces = (self._cls, ) + self.__args[2:] ordered_names = self._argument_names_for_repr(interfaces) - return "directlyProvides({})".format(ordered_names) + return f"directlyProvides({ordered_names})" def __reduce__(self): return self.__class__, self.__args diff --git a/src/zope/interface/document.py b/src/zope/interface/document.py index ce3546c5..37250377 100644 --- a/src/zope/interface/document.py +++ b/src/zope/interface/document.py @@ -35,7 +35,7 @@ def asStructuredText(iface, munge=0, rst=False): if rst: def inline_literal(s): - return "``{}``".format(s) + return f"``{s}``" else: def inline_literal(s): return s @@ -76,7 +76,7 @@ def inline_literal(s): level += 1 for name, desc in namesAndDescriptions: if hasattr(desc, 'getSignatureString'): # ugh... - _call = "{}{}".format(desc.getName(), desc.getSignatureString()) + _call = f"{desc.getName()}{desc.getSignatureString()}" item = "{} -- {}".format( inline_literal(_call), desc.getDoc() or 'no documentation' diff --git a/src/zope/interface/exceptions.py b/src/zope/interface/exceptions.py index 1b7fe7d3..b86fb1e0 100644 --- a/src/zope/interface/exceptions.py +++ b/src/zope/interface/exceptions.py @@ -93,7 +93,7 @@ def _str_subject(self): target = self.target if target is self._NOT_GIVEN: return "An object" - return "The object {!r}".format(target) + return f"The object {target!r}" @property def _str_description(self): diff --git a/src/zope/interface/interface.py b/src/zope/interface/interface.py index 81fe5317..575b6381 100644 --- a/src/zope/interface/interface.py +++ b/src/zope/interface/interface.py @@ -793,7 +793,7 @@ def __init__( Specification.__init__(self, bases) self.__attrs = self.__compute_attrs(attrs) - self.__identifier__ = "{}.{}".format(__module__, name) + self.__identifier__ = f"{__module__}.{name}" def __compute_attrs(self, attrs): # Make sure that all recorded attributes (and methods) are of type @@ -938,7 +938,7 @@ def __repr__(self): return self._v_repr except AttributeError: name = str(self) - r = "<{} {}>".format(self.__class__.__name__, name) + r = f"<{self.__class__.__name__} {name}>" self._v_repr = r # pylint:disable=attribute-defined-outside-init return r @@ -946,7 +946,7 @@ def __str__(self): name = self.__name__ m = self.__ibmodule__ if m: - name = '{}.{}'.format(m, name) + name = f'{m}.{name}' return name def _call_conform(self, conform): diff --git a/src/zope/interface/interfaces.py b/src/zope/interface/interfaces.py index 30eb3bdb..454fee89 100644 --- a/src/zope/interface/interfaces.py +++ b/src/zope/interface/interfaces.py @@ -1157,7 +1157,7 @@ class RegistrationEvent(ObjectEvent): """There has been a change in a registration """ def __repr__(self): - return "{} event:\n{!r}".format(self.__class__.__name__, self.object) + return f"{self.__class__.__name__} event:\n{self.object!r}" class IRegistered(IRegistrationEvent): diff --git a/src/zope/interface/registry.py b/src/zope/interface/registry.py index 970ac2ab..37c4ec99 100644 --- a/src/zope/interface/registry.py +++ b/src/zope/interface/registry.py @@ -166,7 +166,7 @@ def __init__(self, name='', bases=()): self._v_utility_registrations_cache = None def __repr__(self): - return "<{} {}>".format(self.__class__.__name__, self.__name__) + return f"<{self.__class__.__name__} {self.__name__}>" def __reduce__(self): # Mimic what a persistent.Persistent object does and elide diff --git a/src/zope/interface/ro.py b/src/zope/interface/ro.py index 5d0b6680..5233e49b 100644 --- a/src/zope/interface/ro.py +++ b/src/zope/interface/ro.py @@ -625,7 +625,7 @@ def __str__(self): max_left = max(len(x) for x in left_lines) max_right = max(len(x) for x in right_lines) - left_title = 'Legacy RO (len={})'.format(len(self.legacy_ro)) + left_title = f'Legacy RO (len={len(self.legacy_ro)})' right_title = 'C3 RO (len={}; inconsistent={})'.format( len(self.c3_ro), diff --git a/src/zope/interface/tests/odd.py b/src/zope/interface/tests/odd.py index bcead56c..b385beb2 100644 --- a/src/zope/interface/tests/odd.py +++ b/src/zope/interface/tests/odd.py @@ -92,7 +92,7 @@ def __getattr__(self, name): raise AttributeError(name) def __repr__(self): # pragma: no cover - return "".format(self.__name__, hex(id(self))) + return f"" MetaClass = MetaMetaClass( diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py index a19a9805..bec87b88 100644 --- a/src/zope/interface/tests/test_declarations.py +++ b/src/zope/interface/tests/test_declarations.py @@ -1357,7 +1357,7 @@ def test__repr__(self): IFoo = InterfaceClass("IFoo") assert IFoo.__name__ == 'IFoo' assert IFoo.__module__ == __name__ - assert repr(IFoo) == ''.format(__name__) + assert repr(IFoo) == f'' IBar = InterfaceClass("IBar") @@ -1968,7 +1968,7 @@ def __call__(self): inst = implementedBy(Callable()) self.assertEqual( repr(inst), - 'classImplements({}.?)'.format(__name__) + f'classImplements({__name__}.?)' ) c = Callable() diff --git a/src/zope/interface/tests/test_registry.py b/src/zope/interface/tests/test_registry.py index 09b7621f..fbe1ed2b 100644 --- a/src/zope/interface/tests/test_registry.py +++ b/src/zope/interface/tests/test_registry.py @@ -2610,7 +2610,7 @@ class UtilityImplementingFoo: for i in range(30): comps.registerUtility( - UtilityImplementingFoo(), IFoo, name='{}'.format(i) + UtilityImplementingFoo(), IFoo, name=f'{i}' ) orig_generation = comps.utilities._generation