Repository: zopefoundation/zope.interface (language: python)

Issue:
.meta.toml or the config template is out of sync with actual files
I ran the config-package.py from zopefoundation/meta and discovered that it makes unexpected changes:
- the entire `docs:` section gets removed from .github/workflows/tests.yml
- a `setenv = PURE_PYTHON=1` shows up in one of the tox testenv sections
- flake8 gets enabled unexpectedly, fixed in https://github.com/zopefoundation/meta/pull/111

and then the tool runs tox and half of the environments fail with mysterious `ModuleNotFoundError: No module named 'zope.testing'` errors.

This should not be happening.  It should be safe to re-generate the CI files from the template at any time without breaking anything.

I don't currently have the time/energy to look into this.


Discussion / hints:

Fix patch:
diff --git a/.editorconfig b/.editorconfig
index 27282ae5..9d3c4f28 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -28,7 +28,7 @@ max_line_length = off
 # 4 space indentation
 indent_size = 4
 
-[*.{yml,zpt,pt,dtml}]
+[*.{yml,zpt,pt,dtml,zcml}]
 # 2 space indentation
 indent_size = 2
 
diff --git a/.gitignore b/.gitignore
index 24ccc4ec..20b21282 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
 # Generated from:
 # https://github.com/zopefoundation/meta/tree/master/config/c-code
+*.dll
 *.egg-info/
 *.profraw
 *.pyc
 *.pyo
+*.so
 .coverage
 .coverage.*
 .eggs/
diff --git a/.manylinux-install.sh b/.manylinux-install.sh
index 92873063..3b477360 100755
--- a/.manylinux-install.sh
+++ b/.manylinux-install.sh
@@ -1,4 +1,6 @@
 #!/usr/bin/env bash
+# Generated from:
+# https://github.com/zopefoundation/meta/tree/master/config/c-code
 
 set -e -x
 
@@ -21,14 +23,19 @@ fi
 ls -ld /cache
 ls -ld /cache/pip
 
+# We need some libraries because we build wheels from scratch:
+yum -y install libffi-devel
+
 # Compile wheels
 for PYBIN in /opt/python/*/bin; do
-    if [[ "${PYBIN}" == *"cp27"* ]] || \
+    if \
+       [[ "${PYBIN}" == *"cp27"* ]] || \
        [[ "${PYBIN}" == *"cp35"* ]] || \
        [[ "${PYBIN}" == *"cp36"* ]] || \
        [[ "${PYBIN}" == *"cp37"* ]] || \
        [[ "${PYBIN}" == *"cp38"* ]] || \
-       [[ "${PYBIN}" == *"cp39"* ]]; then
+       [[ "${PYBIN}" == *"cp39"* ]] || \
+       [[ "${PYBIN}" == *"cp310"* ]] ; then
         "${PYBIN}/pip" install -e /io/
         "${PYBIN}/pip" wheel /io/ -w wheelhouse/
         if [ `uname -m` == 'aarch64' ]; then
diff --git a/.manylinux.sh b/.manylinux.sh
index 520d68e6..ea4ef418 100755
--- a/.manylinux.sh
+++ b/.manylinux.sh
@@ -1,4 +1,6 @@
 #!/usr/bin/env bash
+# Generated from:
+# https://github.com/zopefoundation/meta/tree/master/config/c-code
 
 set -e -x
 
diff --git a/.meta.toml b/.meta.toml
index 77e115fd..5ff51220 100644
--- a/.meta.toml
+++ b/.meta.toml
@@ -2,44 +2,30 @@
 # https://github.com/zopefoundation/meta/tree/master/config/c-code
 [meta]
 template = "c-code"
-commit-id = "3ddccc7a1430600364f48f37e4f24a275e1bbb85"
+commit-id = "23625f0c67c2171441dbe2069830d3b3fdb17757"
 
 [python]
 with-appveyor = true
 with-pypy = true
 with-legacy-python = true
-with-docs = false
-with-sphinx-doctests = false
+with-sphinx-doctests = true
+with-windows = false
+with-future-python = false
+with-docs = true
 
 [tox]
 use-flake8 = false
-additional-envlist = [
-    "docs",
-    ]
 testenv-commands = [
-    "coverage run -p -m unittest discover -s src",
+    "coverage run -p -m unittest discover -s src {posargs}",
     ]
 testenv-deps = [
     ]
 testenv-setenv = [
     "ZOPE_INTERFACE_STRICT_IRO=1",
     ]
-testenv-additional = [
-    "",
-    "[testenv:docs]",
-    "basepython = python3",
-    "# Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it:",
-    "deps =",
-    "    Sphinx < 4",
-    "    coverage",
-    "extras = docs",
-    "commands =",
-    "    sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html",
-    "    coverage run -p -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest",
-    ]
 coverage-command = "coverage combine"
 coverage-additional = [
-    "depends = py27,py27-pure,py35,py35-pure,py36,py36-pure,py37,py37-pure,py38,py38-pure,py39,py39-pure,pypy,pypy3,docs",
+    "depends = py27,py27-pure,py35,py35-pure,py36,py36-pure,py37,py37-pure,py38,py38-pure,py39,py39-pure,py310,py310-pure,pypy,pypy3,docs",
     "parallel_show_output = true",
     ]
 
@@ -102,19 +88,6 @@ global-env-vars = [
     "  TWINE_PASSWORD:",
     "    secure: aoZC/+rvJKg8B5GMGIxd1X2q2bz7SMl8G3810BID9U8PXFqM0FbWaK9fZ9qcU0UyG2xJsK56Fb6+L6g27I0Lg8UFNhlU1zLAuMSgJQbHsqawFgSY067IdJB68pp34d/oEyxMrJvAKENHH77Fe4KGDssLlk5WnnYS3DA9b66p5imP+1DTtkq5/gMtoG4nZTBtVos7J2kkYTQ5t4BjzTQxPMC3bStNnvuuB0orX4AoCyTrOR1wdZFiNKLzbVnrJCNn24t/n3kG9WrxbnKlrbOm4A==",
     ]
-build-script = [
-    "- python -W ignore setup.py -q bdist_wheel",
-    ]
 test-steps = [
     "- python -m unittest discover -s src",
     ]
-additional-lines = [
-    "artifacts:",
-    "  - path: 'dist\\*.whl'",
-    "    name: wheel",
-    "",
-    "deploy_script:",
-    "  - ps: if ($env:APPVEYOR_REPO_TAG -eq $TRUE) { pip install twine; twine upload --skip-existing dist/* }",
-    "",
-    "deploy: on",
-    ]
diff --git a/CHANGES.rst b/CHANGES.rst
index 3b4ce94b..c6508164 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,9 +2,11 @@
  Changes
 =========
 
-5.4.1 (unreleased)
+5.5.0 (unreleased)
 ==================
 
+- Add support for Python 3.10.
+
 - Add missing Trove classifier showing support for Python 3.9.
 
 - Add some more entries to ``zope.interface.interfaces.__all__``.
diff --git a/MANIFEST.in b/MANIFEST.in
index 7f39b4ea..19b3bbd5 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,6 +7,11 @@ include tox.ini
 include appveyor.yml
 include .coveragerc
 
+recursive-include docs *.py
+recursive-include docs *.rst
+recursive-include docs *.txt
+recursive-include docs Makefile
+
 recursive-include src *.py
 include *.cmd
 include *.sh
diff --git a/appveyor.yml b/appveyor.yml
index 87316b9d..40460ef3 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,10 +1,9 @@
 # Generated from:
 # https://github.com/zopefoundation/meta/tree/master/config/c-code
 environment:
-
   # Currently the builds use @mgedmin's Appveyor account.  The PyPI token
   # belongs to zope.wheelbuilder, which is managed by @mgedmin and @dataflake.
-
+  
   global:
     TWINE_USERNAME: __token__
     TWINE_PASSWORD:
@@ -23,6 +22,8 @@ environment:
     - python: 38-x64
     - python: 39
     - python: 39-x64
+    - python: 310
+    - python: 310-x64
 
 install:
   - "SET PYTHONVERSION=%PYTHON%"
@@ -47,14 +48,11 @@ build_script:
 
 test_script:
   - python -m unittest discover -s src
-
-on_success:
-  - echo Build succesful!
 artifacts:
   - path: 'dist\*.whl'
     name: wheel
 
 deploy_script:
-  - ps: if ($env:APPVEYOR_REPO_TAG -eq $TRUE) { pip install twine; twine upload --skip-existing dist/* }
+  - ps: if ($env:APPVEYOR_REPO_TAG -eq $TRUE) { pip install twine; twine upload --skip-existing dist\*.whl }
 
 deploy: on
diff --git a/setup.cfg b/setup.cfg
index d3ac42aa..ce0d540e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,6 +3,9 @@
 [bdist_wheel]
 universal = 1
 
+[zest.releaser]
+create-wheel = no
+
 [flake8]
 doctests = 1
 
@@ -10,6 +13,8 @@ doctests = 1
 ignore =
     .editorconfig
     .meta.toml
+    docs/_build/html/_sources/*
+    docs/_build/doctest/*
     docs/_build/doctest/output.txt
     docs/_build/html/_sources/*
     docs/_build/html/_sources/api/*
diff --git a/setup.py b/setup.py
index b8b8b4a0..d92ab8ff 100644
--- a/setup.py
+++ b/setup.py
@@ -100,7 +100,7 @@ def read(*rnames):
         )
 
 setup(name='zope.interface',
-      version='5.4.1.dev0',
+      version='5.5.0.dev0',
       url='https://github.com/zopefoundation/zope.interface',
       license='ZPL 2.1',
       description='Interfaces for Python',
@@ -121,6 +121,7 @@ def read(*rnames):
           "Programming Language :: Python :: 3.7",
           "Programming Language :: Python :: 3.8",
           "Programming Language :: Python :: 3.9",
+          "Programming Language :: Python :: 3.10",
           "Programming Language :: Python :: Implementation :: CPython",
           "Programming Language :: Python :: Implementation :: PyPy",
           "Framework :: Zope :: 3",
diff --git a/tox.ini b/tox.ini
index 026d5676..f614f3f6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,10 +10,11 @@ envlist =
     py37,py37-pure
     py38,py38-pure
     py39,py39-pure
+    py310,py310-pure
     pypy
     pypy3
-    coverage
     docs
+    coverage
 
 [testenv]
 usedevelop = true
@@ -23,20 +24,11 @@ setenv =
     !pure-!pypy-!pypy3: PURE_PYTHON=0
     ZOPE_INTERFACE_STRICT_IRO=1
 commands =
-    coverage run -p -m unittest discover -s src
+    coverage run -p -m unittest discover -s src {posargs}
+    !py27-!pypy: sphinx-build -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest
 extras =
     test
-
-[testenv:docs]
-basepython = python3
-# Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it:
-deps =
-    Sphinx < 4
-    coverage
-extras = docs
-commands =
-    sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
-    coverage run -p -m sphinx -b doctest -d docs/_build/doctrees docs docs/_build/doctest
+    docs
 
 [testenv:coverage]
 basepython = python3
@@ -44,12 +36,14 @@ allowlist_externals =
     mkdir
 deps =
     coverage
+setenv =
+    PURE_PYTHON=1
 commands =
     mkdir -p {toxinidir}/parts/htmlcov
     coverage combine
     coverage html -i
     coverage report -i -m --fail-under=99
-depends = py27,py27-pure,py35,py35-pure,py36,py36-pure,py37,py37-pure,py38,py38-pure,py39,py39-pure,pypy,pypy3,docs
+depends = py27,py27-pure,py35,py35-pure,py36,py36-pure,py37,py37-pure,py38,py38-pure,py39,py39-pure,py310,py310-pure,pypy,pypy3,docs
 parallel_show_output = true
 
 [testenv:lint]
@@ -57,7 +51,16 @@ basepython = python3
 skip_install = true
 deps =
     check-manifest
-    check-python-versions
+    check-python-versions >= 0.19.1
+    wheel
 commands =
     check-manifest
     check-python-versions
+
+[testenv:docs]
+basepython = python3
+skip_install = false
+commands_pre =
+commands =
+    sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
+    sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6ef90c06..af8c46f4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -92,24 +92,26 @@ jobs:
     # with `test`, and `docs` must use a subset.
     runs-on: ${{ matrix.os }}
     strategy:
+      fail-fast: false
       matrix:
         python-version:
-          - 2.7
-          - 3.5
-          - pypy-2.7
-          - pypy-3.6
-          - 3.6
-          - 3.7
-          - 3.8
-          - 3.9
+          - "2.7"
+          - "3.5"
+          - "pypy-2.7"
+          - "pypy-3.7"
+          - "3.6"
+          - "3.7"
+          - "3.8"
+          - "3.9"
+          - "3.10"
         os: [ubuntu-20.04, macos-latest]
         exclude:
           - os: macos-latest
-            python-version: pypy-2.7
+            python-version: "pypy-2.7"
           - os: macos-latest
-            python-version: pypy-3.6
+            python-version: "pypy-3.7"
           - os: macos-latest
-            python-version: 3.5
+            python-version: "3.5"
 
     steps:
       - name: checkout
@@ -167,7 +169,7 @@ jobs:
         # We cannot 'uses: pypa/gh-action-pypi-publish@v1.4.1' because
         # that's apparently a container action, and those don't run on
         # the Mac.
-        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && startsWith(runner.os, 'Mac')
+        if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && startsWith(runner.os, 'Mac') && !startsWith(matrix.python-version, 'pypy')
         env:
           TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
         run: |
@@ -177,24 +179,26 @@ jobs:
     needs: build-package
     runs-on: ${{ matrix.os }}
     strategy:
+      fail-fast: false
       matrix:
         python-version:
-          - 2.7
-          - 3.5
-          - pypy-2.7
-          - pypy-3.6
-          - 3.6
-          - 3.7
-          - 3.8
-          - 3.9
+          - "2.7"
+          - "3.5"
+          - "pypy-2.7"
+          - "pypy-3.7"
+          - "3.6"
+          - "3.7"
+          - "3.8"
+          - "3.9"
+          - "3.10"
         os: [ubuntu-20.04, macos-latest]
         exclude:
           - os: macos-latest
-            python-version: pypy-2.7
+            python-version: "pypy-2.7"
           - os: macos-latest
-            python-version: pypy-3.6
+            python-version: "pypy-3.7"
           - os: macos-latest
-            python-version: 3.5
+            python-version: "3.5"
 
     steps:
       - name: checkout
@@ -270,7 +274,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.9]
+        python-version: ["3.9"]
         os: [ubuntu-20.04]
 
     steps:
@@ -309,8 +313,6 @@ jobs:
           pip install -U wheel
           pip install -U coverage
           pip install -U  "`ls dist/zope.interface-*.whl`[docs]"
-          # Until repoze.sphinx.autointerface supports Sphinx 4.x we cannot use it:
-          pip install -U "Sphinx < 4"
       - name: Build docs
         env:
           ZOPE_INTERFACE_STRICT_IRO: 1
@@ -318,13 +320,12 @@ jobs:
           sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html
           sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest
 
-
   lint:
     needs: build-package
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.9]
+        python-version: ["3.9"]
         os: [ubuntu-20.04]
 
     steps:
@@ -376,7 +377,7 @@ jobs:
     # We use a regular Python matrix entry to share as much code as possible.
     strategy:
       matrix:
-        python-version: [3.9]
+        python-version: ["3.9"]
         image: [manylinux2010_x86_64, manylinux2010_i686, manylinux2014_aarch64]
 
     steps:
@@ -415,13 +416,13 @@ jobs:
         # The 2010 image is the most recent spec that comes with Python 2.7,
         # and only up through the tag 2021-02-06-3d322a5
         env:
-          DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }}:2021-02-06-3d322a5
+          DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }}
         run: |
           bash .manylinux.sh
       - name: Build zope.interface (i686)
         if: matrix.image == 'manylinux2010_i686'
         env:
-          DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }}:2021-02-06-3d322a5
+          DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }}
           PRE_CMD: linux32
         run: |
           bash .manylinux.sh
diff --git a/src/zope/interface/common/tests/__init__.py b/src/zope/interface/common/tests/__init__.py
index b285ad77..b3bd5bea 100644
--- a/src/zope/interface/common/tests/__init__.py
+++ b/src/zope/interface/common/tests/__init__.py
@@ -81,16 +81,17 @@ def test_ro(self, stdlib_class=stdlib_class, iface=iface):
                 sro = implements.__sro__
                 self.assertIs(sro[-1], Interface)
 
-                # Check that we got the strict C3 resolution order, unless we
-                # know we cannot. Note that 'Interface' is virtual base that doesn't
-                # necessarily appear at the same place in the calculated SRO as in the
-                # final SRO.
-                strict = stdlib_class not in self.NON_STRICT_RO
-                isro = ro.ro(implements, strict=strict)
-                isro.remove(Interface)
-                isro.append(Interface)
-
-                self.assertEqual(tuple(isro), sro)
+                if stdlib_class not in self.UNVERIFIABLE_RO:
+                    # Check that we got the strict C3 resolution order, unless
+                    # we know we cannot. Note that 'Interface' is virtual base
+                    # that doesn't necessarily appear at the same place in the
+                    # calculated SRO as in the final SRO.
+                    strict = stdlib_class not in self.NON_STRICT_RO
+                    isro = ro.ro(implements, strict=strict)
+                    isro.remove(Interface)
+                    isro.append(Interface)
+
+                    self.assertEqual(tuple(isro), sro)
 
             name = 'test_auto_ro_' + suffix
             test_ro.__name__ = name
@@ -101,6 +102,7 @@ class VerifyClassMixin(unittest.TestCase):
     verifier = staticmethod(verifyClass)
     UNVERIFIABLE = ()
     NON_STRICT_RO = ()
+    UNVERIFIABLE_RO = ()
 
     def _adjust_object_before_verify(self, iface, x):
         return x
diff --git a/src/zope/interface/common/tests/test_collections.py b/src/zope/interface/common/tests/test_collections.py
index 81eea0ea..152626b8 100644
--- a/src/zope/interface/common/tests/test_collections.py
+++ b/src/zope/interface/common/tests/test_collections.py
@@ -11,6 +11,7 @@
 ##############################################################################
 
 
+import array
 import unittest
 try:
     import collections.abc as abc
@@ -93,6 +94,10 @@ def test_non_iterable_UserDict(self):
         # It's imported because...? Coverage imports it, but why do we have it without
         # coverage?
         'Row',
+        # In Python 3.10 ``array.array`` appears as ``IMutableSequence`` but it
+        # does not provide a ``clear()`` method and it cannot be instantiated
+        # using ``array.array()``.
+        array.array,
     }
 
     if PYPY:
@@ -121,6 +126,14 @@ def test_non_iterable_UserDict(self):
         })
         NON_STRICT_RO = {
         }
+    else:
+        UNVERIFIABLE_RO = {
+            # ``array.array`` fails the ``test_auto_ro_*`` tests with and
+            # without strict RO but only on Windows (AppVeyor) on Python 3.10.0
+            # (in older versions ``array.array`` does not appear as
+            # ``IMutableSequence``).
+            array.array,
+        }
 
 add_abc_interface_tests(TestVerifyClass, collections.ISet.__module__)
 
@@ -158,3 +171,11 @@ class TestVerifyObject(VerifyObjectMixin,
         CONSTRUCTORS.update({
             collections.IValuesView: {}.viewvalues,
         })
+    else:
+        UNVERIFIABLE_RO = {
+            # ``array.array`` fails the ``test_auto_ro_*`` tests with and
+            # without strict RO but only on Windows (AppVeyor) on Python 3.10.0
+            # (in older versions ``array.array`` does not appear as
+            # ``IMutableSequence``).
+            array.array,
+        }