diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b86e086..c2dd52d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -122,15 +122,6 @@ jobs: # to save the cache. So it must come before the thing we want to use # the cache. ### - - name: Get pip cache epoch - id: pip-cache-epoch - # ``actions/cache`` never replaces an entry: "if the provided key - # matches an existing cache, a new cache is not created". A corrupt - # entry would therefore break every run that restores it until GitHub - # evicts it. Rotating the key weekly abandons such an entry instead. - run: | - echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" - - name: Get pip cache dir id: pip-cache run: | @@ -140,19 +131,30 @@ jobs: uses: actions/cache@v5 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }}-${{ steps.pip-cache-epoch.outputs.week }} - + key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }} - name: Install Build Dependencies (3.15) if: startsWith(matrix.python-version, '3.15') run: | - python -m pip install -U pip - pip install -U "setuptools >= 78.1.1,< 82" wheel twine cffi pycparser + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } + retry python -m pip install -U pip + retry pip install -U "setuptools >= 78.1.1,< 82" wheel twine cffi pycparser - name: Install Build Dependencies if: ${{ !startsWith(matrix.python-version, '3.15') }} run: | - python -m pip install -U pip - pip install -U "setuptools >= 78.1.1,< 82" wheel twine + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } + retry python -m pip install -U pip + retry pip install -U "setuptools >= 78.1.1,< 82" wheel twine - name: Build AccessControl (macOS x86_64) if: > @@ -200,16 +202,28 @@ jobs: - name: Install AccessControl and dependencies (3.15) if: startsWith(matrix.python-version, '3.15') run: | + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } # Install to collect dependencies into the (pip) cache. # Use "--pre" here because dependencies with support for this future # Python release may only be available as pre-releases - pip install --pre .[test] + retry pip install --pre .[test] - name: Install AccessControl and dependencies if: ${{ !startsWith(matrix.python-version, '3.15') }} run: | + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } # Install to collect dependencies into the (pip) cache. - python -m pip install -U pip "setuptools >= 78.1.1,< 82" - pip install .[test] + retry python -m pip install -U pip "setuptools >= 78.1.1,< 82" + retry pip install .[test] - name: Check AccessControl build run: | @@ -290,15 +304,6 @@ jobs: # to save the cache. So it must come before the thing we want to use # the cache. ### - - name: Get pip cache epoch - id: pip-cache-epoch - # ``actions/cache`` never replaces an entry: "if the provided key - # matches an existing cache, a new cache is not created". A corrupt - # entry would therefore break every run that restores it until GitHub - # evicts it. Rotating the key weekly abandons such an entry instead. - run: | - echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" - - name: Get pip cache dir id: pip-cache run: | @@ -308,8 +313,7 @@ jobs: uses: actions/cache@v5 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }}-${{ steps.pip-cache-epoch.outputs.week }} - + key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }} - name: Download AccessControl wheel (Linux/macOS) if: "!startsWith(runner.os, 'Windows')" uses: actions/download-artifact@v8 @@ -326,10 +330,16 @@ jobs: - name: Install AccessControl ${{ matrix.python-version }} if: startsWith(matrix.python-version, '3.15') run: | - pip install -U wheel "setuptools >= 78.1.1,< 82" + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } + retry pip install -U wheel "setuptools >= 78.1.1,< 82" # coverage might have a wheel on PyPI for a future python version which is # not ABI compatible with the current one, so build it from sdist: - pip install -U --no-binary :all: coverage[toml] + retry pip install -U --no-binary :all: coverage[toml] # Unzip into src/ so that testrunner can find the .so files # when we ask it to load tests from that directory. This # might also save some build time? @@ -337,19 +347,25 @@ jobs: unzip -n dist/*.whl -d src # Use "--pre" here because dependencies with support for this future # Python release may only be available as pre-releases - pip install --pre -e .[test] + retry pip install --pre -e .[test] - name: Install AccessControl if: ${{ !startsWith(matrix.python-version, '3.15') }} run: | - pip install -U wheel "setuptools >= 78.1.1,< 82" - pip install -U coverage[toml] - pip install -U 'cffi; platform_python_implementation == "CPython"' + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } + retry pip install -U wheel "setuptools >= 78.1.1,< 82" + retry pip install -U coverage[toml] + retry pip install -U 'cffi; platform_python_implementation == "CPython"' # Unzip into src/ so that testrunner can find the .so files # when we ask it to load tests from that directory. This # might also save some build time? ls -l dist/ unzip -n dist/*.whl -d src - pip install -e .[test] + retry pip install -e .[test] - name: Run tests with C extensions if: ${{ !startsWith(matrix.python-version, 'pypy') }} run: | @@ -404,15 +420,6 @@ jobs: # to save the cache. So it must come before the thing we want to use # the cache. ### - - name: Get pip cache epoch - id: pip-cache-epoch - # ``actions/cache`` never replaces an entry: "if the provided key - # matches an existing cache, a new cache is not created". A corrupt - # entry would therefore break every run that restores it until GitHub - # evicts it. Rotating the key weekly abandons such an entry instead. - run: | - echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" - - name: Get pip cache dir id: pip-cache run: | @@ -422,8 +429,7 @@ jobs: uses: actions/cache@v5 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }}-${{ steps.pip-cache-epoch.outputs.week }} - + key: ${{ runner.os }}-${{ runner.arch }}-pip-${{ matrix.python-version }} - name: Download AccessControl wheel uses: actions/download-artifact@v8 with: @@ -431,9 +437,15 @@ jobs: path: dist/ - name: Install AccessControl run: | - pip install -U wheel - pip install -U tox - pip install -U "`ls dist/*.whl`[docs]" + # PyPI downloads are occasionally cut off mid-transfer, which makes + # pip fail with ``IncompleteRead``. pip does not retry a truncated + # download, so retry the complete command. Purge pip's cache before + # the last attempt: should a truncated download ever get *cached*, + # it would fail every attempt in the same way. + retry() { for i in 1 2; do "$@" && return 0; echo "Attempt $i failed, retrying in 15s ..."; sleep 15; done; pip cache purge || true; "$@"; } + retry pip install -U wheel + retry pip install -U tox + retry pip install -U "`ls dist/*.whl`[docs]" - name: Run release check env: ZOPE_INTERFACE_STRICT_IRO: 1 @@ -472,15 +484,6 @@ jobs: # to save the cache. So it must come before the thing we want to use # the cache. ### - - name: Get pip cache epoch - id: pip-cache-epoch - # ``actions/cache`` never replaces an entry: "if the provided key - # matches an existing cache, a new cache is not created". A corrupt - # entry would therefore break every run that restores it until GitHub - # evicts it. Rotating the key weekly abandons such an entry instead. - run: | - echo "week=$(date -u +%G-%V)" >> "$GITHUB_OUTPUT" - - name: Get pip cache dir id: pip-cache run: | @@ -490,8 +493,7 @@ jobs: uses: actions/cache@v5 with: path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip_manylinux-${{ matrix.image }}-${{ matrix.python-version }}-${{ steps.pip-cache-epoch.outputs.week }} - + key: ${{ runner.os }}-pip_manylinux-${{ matrix.image }}-${{ matrix.python-version }} - name: Update pip run: python -m pip install -U pip diff --git a/.meta.toml b/.meta.toml index 25ecd17..d15d904 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/src/zope/meta/c-code [meta] template = "c-code" -commit-id = "9333ca19" +commit-id = "7cab96bb" [python] with-windows = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59609e1..fd0f5d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,15 @@ # https://github.com/zopefoundation/meta/tree/master/src/zope/meta/c-code minimum_pre_commit_version: '3.6' repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace - repo: https://github.com/pycqa/isort rev: "8.0.1" hooks: @@ -27,3 +36,7 @@ repos: - id: flake8 additional_dependencies: - flake8-debugger == 4.1.2 + - repo: https://github.com/sphinx-contrib/sphinx-lint + rev: v1.0.2 + hooks: + - id: sphinx-lint diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 79859e0..b8581dd 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -1 +1 @@ -Zope Foundation and Contributors \ No newline at end of file +Zope Foundation and Contributors diff --git a/docs/AccessControl.rst b/docs/AccessControl.rst index e5b4389..6402f43 100644 --- a/docs/AccessControl.rst +++ b/docs/AccessControl.rst @@ -30,15 +30,15 @@ Types of access A "type of access" is a named grouping of 0 or more of the permissions defined by an object. All objects have one predefined -type of access called Full Access (all permissions defined by that -object). A user who has the special role "Manager" always has Full -Access to all objects at or below the level in the object hierarchy +type of access called Full Access (all permissions defined by that +object). A user who has the special role "Manager" always has Full +Access to all objects at or below the level in the object hierarchy at which the user is defined. New types of access may be defined as combinations of the various permissions defined by a given object. These new types of access may be defined by the programmer, or by -users at runtime. +users at runtime. @@ -50,13 +50,13 @@ A role is a name that ties users (authentication of identity) to permissions (authorization for that identity) in the system. Roles may be defined in any Folder (or Folderish) object in the system. Sub folders can make use of roles defined higher in the -hierarchy. These roles can be assigned to users. All users, -including non-authenticated users have the built-in role of -"Anonymous". +hierarchy. These roles can be assigned to users. All users, +including non-authenticated users have the built-in role of +"Anonymous". -Principia objects allow the association of defined roles -with a single "type of access" each, in the context of that -object. A single role is associated with one and only one +Principia objects allow the association of defined roles +with a single "type of access" each, in the context of that +object. A single role is associated with one and only one type of access in the context of a given object. @@ -191,14 +191,14 @@ Example: ) The developer may also predefine useful types of access, by -specifying an __ac_types__ attribute. This should be a tuple of -tuples, where each tuple represents a type of access and contains -a string name as its first element and a list of permission names +specifying an __ac_types__ attribute. This should be a tuple of +tuples, where each tuple represents a type of access and contains +a string name as its first element and a list of permission names as its second element. By default, only "Full Access" is defined (by the RoleManager mixin). If you wish to override __ac_types__ to provide convenient types of -access, you must always be sure to define "Full Access" as containing +access, you must always be sure to define "Full Access" as containing the names of all possible permissions for your object. Example: diff --git a/src/AccessControl/meta.zcml b/src/AccessControl/meta.zcml index f567115..a999d1e 100644 --- a/src/AccessControl/meta.zcml +++ b/src/AccessControl/meta.zcml @@ -40,7 +40,7 @@ name="permission" schema="zope.security.zcml.IPermissionDirective" handler=".security.PermissionDirective"/> - +