diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5b51155..1962dd1 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,10 +1,8 @@ [bumpversion] -current_version = 0.3.0 +current_version = 0.5.1 commit = True tag = True -[bumpversion:file:__pkginfo__.py] - [bumpversion:file:README.rst] [bumpversion:file:doc-source/index.rst] @@ -20,3 +18,9 @@ search = : str = "{current_version}" replace = : str = "{new_version}" [bumpversion:file:pyproject.toml] +search = version = "{current_version}" +replace = version = "{new_version}" + +[bumpversion:file:.github/workflows/conda_ci.yml] +search = ={current_version}=py_1 +replace = ={new_version}=py_1 diff --git a/.dependabot/config.yml b/.dependabot/config.yml deleted file mode 100644 index 4584924..0000000 --- a/.dependabot/config.yml +++ /dev/null @@ -1,9 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. ---- -version: 1 -update_configs: -- package_manager: python - directory: / - update_schedule: weekly - default_reviewers: - - domdfcoding diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ffd3569..35cc847 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -44,7 +44,7 @@ If possible, please include a small, self-contained reproduction. * flake8-encodings: ## Installation source - + ## Other Additional Information: diff --git a/.github/actions_build_conda.sh b/.github/actions_build_conda.sh deleted file mode 100755 index 1acebbe..0000000 --- a/.github/actions_build_conda.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# This file is managed by 'repo_helper'. Don't edit it directly. - -set -e -x - -python -m repo_helper make-recipe || exit 1 - -# Switch to miniconda -source "/home/runner/miniconda/etc/profile.d/conda.sh" -hash -r -conda activate base -conda config --set always_yes yes --set changeps1 no -conda update -q conda -conda install conda-build -conda install anaconda-client -conda info -a - -conda config --add channels conda-forge || exit 1 -conda config --add channels domdfcoding || exit 1 - -conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist --skip-existing - -exit 0 diff --git a/.github/actions_deploy_conda.sh b/.github/actions_deploy_conda.sh deleted file mode 100755 index 3aa7e0b..0000000 --- a/.github/actions_deploy_conda.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# This file is managed by 'repo_helper'. Don't edit it directly. - -set -e -x - -# Switch to miniconda -source "/home/runner/miniconda/etc/profile.d/conda.sh" -hash -r -conda activate base -conda config --set always_yes yes --set changeps1 no -conda update -q conda -conda install anaconda-client -conda info -a - -for f in conda/dist/noarch/flake8-encodings-*.tar.bz2; do - [ -e "$f" ] || continue - echo "$f" - conda install "$f" || exit 1 - echo "Deploying to Anaconda.org..." - anaconda -t "$ANACONDA_TOKEN" upload "$f" || exit 1 - echo "Successfully deployed to Anaconda.org." -done - -exit 0 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e769ad3..454225a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,5 +6,6 @@ updates: directory: / schedule: interval: weekly + open-pull-requests-limit: 0 reviewers: - domdfcoding diff --git a/.github/milestones.py b/.github/milestones.py new file mode 100755 index 0000000..5e868dc --- /dev/null +++ b/.github/milestones.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# stdlib +import os +import sys + +# 3rd party +from github3 import GitHub +from github3.repos import Repository +from packaging.version import InvalidVersion, Version + +latest_tag = os.environ["GITHUB_REF_NAME"] + +try: + current_version = Version(latest_tag) +except InvalidVersion: + sys.exit() + +gh: GitHub = GitHub(token=os.environ["GITHUB_TOKEN"]) +repo: Repository = gh.repository(*os.environ["GITHUB_REPOSITORY"].split('/', 1)) + +for milestone in repo.milestones(state="open"): + try: + milestone_version = Version(milestone.title) + except InvalidVersion: + continue + if milestone_version == current_version: + sys.exit(not milestone.update(state="closed")) diff --git a/.github/stale.yml b/.github/stale.yml index bb7ca3f..bb7fa62 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -7,7 +7,7 @@ daysUntilStale: 180 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 180 +daysUntilClose: false # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] @@ -28,13 +28,13 @@ exemptMilestones: false exemptAssignees: false # Label to use when marking as stale -staleLabel: wontfix +staleLabel: stale # Comment to post when marking as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. +markComment: false +# This issue has been automatically marked as stale because it has not had +# recent activity. It will be closed if no further activity occurs. Thank you +# for your contributions. # Comment to post when removing the stale label. # unmarkComment: > diff --git a/.github/workflows/cleanup.yml b/.github/workflows/cleanup.yml deleted file mode 100644 index 741c0bd..0000000 --- a/.github/workflows/cleanup.yml +++ /dev/null @@ -1,14 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. ---- -name: Artefact Cleaner -on: - schedule: - - cron: 0 9 1 * * -jobs: - Clean: - runs-on: ubuntu-latest - steps: - - name: cleanup - uses: glassechidna/artifact-cleaner@v2 - with: - minimumAge: 1000000.0 diff --git a/.github/workflows/conda_ci.yml b/.github/workflows/conda_ci.yml index c626f22..9b3fbc7 100644 --- a/.github/workflows/conda_ci.yml +++ b/.github/workflows/conda_ci.yml @@ -6,38 +6,63 @@ on: push: branches: ["master"] +permissions: + contents: read + jobs: tests: name: "Conda" - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 + defaults: + run: + shell: bash -l {0} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" + with: + python-version: "3.11" + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2.1.1 with: - python-version: "3.8" + activate-environment: env + conda-build-version: 3.28.4 + miniconda-version: py311_24.1.2-0 + python-version: "3.11" + miniforge-variant: Mambaforge - name: Install dependencies 🔧 run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade repo_helper + python -m pip install --upgrade "whey-conda" "whey" # $CONDA is an environment variable pointing to the root of the miniconda directory - $CONDA/bin/conda update -q conda - $CONDA/bin/conda install conda-build=3.21.0 - + $CONDA/bin/conda update -n base conda $CONDA/bin/conda config --add channels conda-forge $CONDA/bin/conda config --add channels domdfcoding - - name: "Build and install package" + - name: "Build and index channel" run: | - # This mess is only necessary because conda won't fix it themselves - # https://github.com/conda/conda/issues/1884 - - python -m repo_helper build --conda --out-dir conda-bld/noarch + python -m whey --builder whey_conda --out-dir conda-bld/noarch $CONDA/bin/conda index ./conda-bld || exit 1 - $CONDA/bin/conda install -c file://$(pwd)/conda-bld flake8-encodings -y || exit 1 + + - name: "Search for package" + run: | + $CONDA/bin/conda search -c file://$(pwd)/conda-bld flake8-encodings + $CONDA/bin/conda search -c file://$(pwd)/conda-bld --override-channels flake8-encodings + + - name: "Install package" + run: | + $CONDA/bin/conda install -c file://$(pwd)/conda-bld flake8-encodings=0.5.1=py_1 -y || exit 1 + + - name: "Run Tests" + run: | + rm -rf flake8_encodings + $CONDA/bin/conda install pytest coincidence || exit 1 + pip install -r tests/requirements.txt + pytest tests/ diff --git a/.github/workflows/docs_test_action.yml b/.github/workflows/docs_test_action.yml index 43c2775..8f60ba5 100644 --- a/.github/workflows/docs_test_action.yml +++ b/.github/workflows/docs_test_action.yml @@ -2,17 +2,36 @@ --- name: "Docs Check" on: - - push + push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: docs: runs-on: ubuntu-latest steps: - name: Checkout 🛎️ - uses: "actions/checkout@v1" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!tests/**' + - name: Install and Build 🔧 - uses: ammaraskar/sphinx-action@master + uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1 + if: steps.changes.outputs.code == 'true' with: - pre-build-command: apt-get update && apt-get install gcc python3-dev git pandoc -y && python -m pip install tox + pre-build-command: python -m pip install tox docs-folder: "doc-source/" build-command: "tox -e docs -- -W " diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 29303d6..5e67c5c 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -4,27 +4,47 @@ name: Flake8 on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: Run: name: "Flake8" - runs-on: "ubuntu-18.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + if: steps.changes.outputs.code == 'true' + uses: "actions/setup-python@v5" with: - python-version: "3.8" + python-version: "3.9" - name: Install dependencies 🔧 + if: steps.changes.outputs.code == 'true' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install tox + python -m pip install tox~=3.0 - name: "Run Flake8" - run: "python -m tox -e lint -- --format github" + if: steps.changes.outputs.code == 'true' + run: "python -m tox -e lint -s false -- --format github" diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index e10cf9b..4c22a52 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -4,6 +4,14 @@ name: mypy on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + pull_request: + +permissions: + contents: read jobs: Run: @@ -12,24 +20,35 @@ jobs: strategy: matrix: - os: ['ubuntu-20.04', 'windows-2019'] + os: ['ubuntu-22.04', 'windows-2022'] fail-fast: false steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + if: steps.changes.outputs.code == 'true' + uses: "actions/setup-python@v5" with: - python-version: "3.6" + python-version: "3.9" - name: Install dependencies 🔧 run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run mypy" - run: "python -m tox -e mypy" + if: steps.changes.outputs.code == 'true' + run: "python -m tox -e mypy -s false" diff --git a/.github/workflows/octocheese.yml b/.github/workflows/octocheese.yml index 8e54454..fea7cd2 100644 --- a/.github/workflows/octocheese.yml +++ b/.github/workflows/octocheese.yml @@ -3,10 +3,8 @@ name: "GitHub Releases" on: - push: - branches: ["master"] schedule: - - cron: 0 12 * * 2,4,6 + - cron: 0 12 * * * jobs: Run: diff --git a/.github/workflows/python_ci.yml b/.github/workflows/python_ci.yml index 0c1bdda..ca858d8 100644 --- a/.github/workflows/python_ci.yml +++ b/.github/workflows/python_ci.yml @@ -4,49 +4,78 @@ name: Windows on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "windows-2019 / Python ${{ matrix.config.python-version }}" - runs-on: "windows-2019" + name: "windows-2022 / Python ${{ matrix.config.python-version }}" + runs-on: "windows-2022" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.10", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: True} + - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9-v7.3.15", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.github/workflows/python_ci_linux.yml b/.github/workflows/python_ci_linux.yml index 7830816..01edd6b 100644 --- a/.github/workflows/python_ci_linux.yml +++ b/.github/workflows/python_ci_linux.yml @@ -4,64 +4,94 @@ name: Linux on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + tags: + - '*' + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "ubuntu-20.04 / Python ${{ matrix.config.python-version }}" - runs-on: "ubuntu-20.04" + name: "ubuntu-22.04 / Python ${{ matrix.config.python-version }}" + runs-on: "ubuntu-22.04" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.10", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: True} + - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 python -m pip install --upgrade coverage_pyver_pragma - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true Coverage: needs: tests - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: python-version: 3.8 @@ -71,26 +101,32 @@ jobs: python -m pip install --upgrade "coveralls>=3.0.0" coverage_pyver_pragma - name: "Download Coverage 🪂" - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: path: coverage - name: Display structure of downloaded files + id: show run: ls -R working-directory: coverage + continue-on-error: true - name: Combine Coverage 👷 + if: ${{ steps.show.outcome != 'failure' }} run: | shopt -s globstar python -m coverage combine coverage/**/.coverage - name: "Upload Combined Coverage Artefact 🚀" - uses: actions/upload-artifact@v2 + if: ${{ steps.show.outcome != 'failure' }} + uses: actions/upload-artifact@v4 with: name: "combined-coverage" path: .coverage + include-hidden-files: true - name: "Upload Combined Coverage to Coveralls" + if: ${{ steps.show.outcome != 'failure' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | @@ -99,29 +135,29 @@ jobs: Deploy: needs: tests - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" if: startsWith(github.ref, 'refs/tags/') - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" + if: startsWith(github.ref, 'refs/tags/') with: python-version: 3.8 - if: startsWith(github.ref, 'refs/tags/') - name: Install dependencies 🔧 + if: startsWith(github.ref, 'refs/tags/') run: | python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox - if: startsWith(github.ref, 'refs/tags/') + python -m pip install --upgrade tox~=3.0 - name: Build distributions 📦 + if: startsWith(github.ref, 'refs/tags/') run: | tox -e build - if: startsWith(github.ref, 'refs/tags/') - name: Upload distribution to PyPI 🚀 if: startsWith(github.ref, 'refs/tags/') @@ -131,37 +167,73 @@ jobs: password: ${{ secrets.PYPI_TOKEN }} skip_existing: true + - name: Close milestone 🚪 + if: startsWith(github.ref, 'refs/tags/') + run: | + python -m pip install --upgrade github3.py packaging + python .github/milestones.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + Conda: needs: deploy - runs-on: "ubuntu-20.04" + runs-on: ubuntu-22.04 if: startsWith(github.ref, 'refs/tags/') || (startsWith(github.event.head_commit.message, 'Bump version') != true) steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + uses: "actions/setup-python@v5" with: - python-version: 3.8 + python-version: 3.11 + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2.1.1 + with: + activate-environment: env + conda-build-version: 3.28.4 + miniconda-version: py311_24.1.2-0 + python-version: "3.11" + miniforge-variant: Mambaforge - name: Install dependencies 🔧 run: | + python -VV + python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade repo_helper - - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - bash miniconda.sh -b -p $HOME/miniconda - - - name: Build Conda 📦 + python -m pip install --upgrade "mkrecipe" "whey" + # $CONDA is an environment variable pointing to the root of the miniconda directory + $CONDA/bin/conda config --set always_yes yes --set changeps1 no + $CONDA/bin/conda update -n base conda + $CONDA/bin/conda info -a + $CONDA/bin/conda install conda-forge::py-lief=0.14.1 + $CONDA/bin/conda config --add channels conda-forge + $CONDA/bin/conda config --add channels domdfcoding + + $CONDA/bin/conda config --remove channels defaults + + - name: Build Conda Package 📦 run: | - chmod +x .github/actions_build_conda.sh - bash .github/actions_build_conda.sh + python -m mkrecipe --type wheel || exit 1 + $CONDA/bin/conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist - - name: Deploy Conda 🚀 + - name: Deploy Conda Package 🚀 + if: startsWith(github.ref, 'refs/tags/') run: | - chmod +x .github/actions_deploy_conda.sh - bash .github/actions_deploy_conda.sh + $CONDA/bin/conda config --set always_yes yes --set changeps1 no + $CONDA/bin/conda install anaconda-client + $CONDA/bin/conda info -a + + for f in conda/dist/noarch/flake8-encodings-*.tar.bz2; do + [ -e "$f" ] || continue + echo "$f" + conda install "$f" || exit 1 + echo "Deploying to Anaconda.org..." + $CONDA/bin/anaconda -t "$ANACONDA_TOKEN" upload "$f" || exit 1 + echo "Successfully deployed to Anaconda.org." + done env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} diff --git a/.github/workflows/python_ci_macos.yml b/.github/workflows/python_ci_macos.yml index e9e2bc1..280a25b 100644 --- a/.github/workflows/python_ci_macos.yml +++ b/.github/workflows/python_ci_macos.yml @@ -4,49 +4,78 @@ name: macOS on: push: + branches-ignore: + - 'repo-helper-update' + - 'pre-commit-ci-update-config' + - 'imgbot' + + pull_request: + +permissions: + actions: write + issues: write + contents: read jobs: tests: - name: "macos-latest / Python ${{ matrix.config.python-version }}" - runs-on: "macos-latest" + name: "macos-${{ matrix.config.os-ver }} / Python ${{ matrix.config.python-version }}" + runs-on: "macos-${{ matrix.config.os-ver }}" continue-on-error: ${{ matrix.config.experimental }} env: - USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10.0-alpha.6,pypy-3.6,pypy-3.7' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - - {python-version: "3.6", testenvs: "py36,build", experimental: False} - - {python-version: "3.7", testenvs: "py37,build", experimental: False} - - {python-version: "3.8", testenvs: "py38,build", experimental: False} - - {python-version: "3.9", testenvs: "py39,build", experimental: False} - - {python-version: "3.10.0-alpha.6", testenvs: "py310-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} + - {python-version: "3.7", os-ver: "13", testenvs: "py37,build", experimental: False} + - {python-version: "3.8", os-ver: "14", testenvs: "py38,build", experimental: False} + - {python-version: "3.9", os-ver: "14", testenvs: "py39,build", experimental: False} + - {python-version: "3.10", os-ver: "14", testenvs: "py310,build", experimental: False} + - {python-version: "3.11", os-ver: "14", testenvs: "py311,build", experimental: False} + - {python-version: "3.12", os-ver: "14", testenvs: "py312,build", experimental: False} + - {python-version: "3.13", os-ver: "14", testenvs: "py313,build", experimental: True} + - {python-version: "pypy-3.7", os-ver: "13", testenvs: "pypy37,build", experimental: False} + - {python-version: "pypy-3.8", os-ver: "14", testenvs: "pypy38,build", experimental: False} + - {python-version: "pypy-3.9", os-ver: "14", testenvs: "pypy39,build", experimental: True} steps: - name: Checkout 🛎️ - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" + + - name: Check for changed files + if: startsWith(github.ref, 'refs/tags/') != true + uses: dorny/paths-filter@v2 + id: changes + with: + list-files: "json" + filters: | + code: + - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python 🐍 - uses: "actions/setup-python@v2" + id: setup-python + if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} + uses: "actions/setup-python@v5" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies 🔧 + if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel - python -m pip install --upgrade tox virtualenv + python -m pip install --upgrade tox~=3.0 virtualenv!=20.16.0 - name: "Run Tests for Python ${{ matrix.config.python-version }}" - run: python -m tox -e "${{ matrix.config.testenvs }}" + if: steps.setup-python.outcome == 'success' + run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage 🚀" - uses: actions/upload-artifact@v2 - if: ${{ always() }} + uses: actions/upload-artifact@v4 + if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0328fc9..5e5cd22 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,15 @@ exclude: ^$ +ci: + autoupdate_schedule: quarterly + repos: + - repo: https://github.com/repo-helper/pyproject-parser + rev: v0.13.0 + hooks: + - id: reformat-pyproject + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: @@ -25,28 +33,28 @@ repos: - id: end-of-file-fixer - repo: https://github.com/domdfcoding/pre-commit-hooks - rev: v0.2.1 + rev: v0.4.0 hooks: - id: requirements-txt-sorter args: - --allow-git - id: check-docstring-first - exclude: ^(doc-source/conf|__pkginfo__|make_conda_recipe|setup|tests/.*)\.py$ + exclude: ^(doc-source/conf|__pkginfo__|setup|tests/.*)\.py$ - id: bind-requirements - - repo: https://github.com/domdfcoding/flake8-dunder-all - rev: v0.1.7 + - repo: https://github.com/python-formate/flake8-dunder-all + rev: v0.4.1 hooks: - id: ensure-dunder-all files: ^flake8_encodings/.*\.py$ - repo: https://github.com/domdfcoding/flake2lint - rev: v0.4.0 + rev: v0.4.3 hooks: - id: flake2lint - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.8.0 + rev: v1.10.0 hooks: - id: python-no-eval - id: rst-backticks @@ -54,7 +62,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/asottile/pyupgrade - rev: v2.11.0 + rev: v2.12.0 hooks: - id: pyupgrade args: @@ -62,19 +70,24 @@ repos: - --keep-runtime-typing - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.9 + rev: v1.5.1 hooks: - id: remove-crlf - id: forbid-crlf - - repo: https://github.com/repo-helper/formate - rev: v0.4.3 + - repo: https://github.com/python-formate/snippet-fmt + rev: v0.1.5 + hooks: + - id: snippet-fmt + + - repo: https://github.com/python-formate/formate + rev: v0.8.0 hooks: - id: formate - exclude: ^(doc-source/conf|__pkginfo__|make_conda_recipe|setup)\.(_)?py$ + exclude: ^(doc-source/conf|__pkginfo__|setup)\.(_)?py$ - - repo: https://github.com/domdfcoding/dep_checker - rev: v0.6.0 + - repo: https://github.com/python-coincidence/dep_checker + rev: v0.8.0 hooks: - id: dep_checker args: diff --git a/.pylintrc b/.pylintrc index a21206a..81ecba0 100644 --- a/.pylintrc +++ b/.pylintrc @@ -66,7 +66,7 @@ confidence= # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=all -enable=assert-on-tuple,astroid-error,bad-except-order,bad-inline-option,bad-option-value,bad-reversed-sequence,bare-except,binary-op-exception,boolean-datetime,catching-non-exception,cell-var-from-loop,confusing-with-statement,consider-merging-isinstance,consider-using-enumerate,consider-using-ternary,continue-in-finally,cyclic-import,deprecated-pragma,django-not-available,duplicate-except,duplicate-key,eval-used,exec-used,expression-not-assigned,fatal,file-ignored,fixme,global-at-module-level,global-statement,global-variable-not-assigned,global-variable-undefined,http-response-with-content-type-json,http-response-with-json-dumps,invalid-all-object,invalid-characters-in-docstring,len-as-condition,literal-comparison,locally-disabled,locally-enabled,lost-exception,lowercase-l-suffix,misplaced-bare-raise,missing-kwoa,mixed-line-endings,model-has-unicode,model-missing-unicode,model-no-explicit-unicode,model-unicode-not-callable,multiple-imports,new-db-field-with-default,non-ascii-bytes-literals,nonexistent-operator,not-in-loop,notimplemented-raised,overlapping-except,parse-error,pointless-statement,pointless-string-statement,raising-bad-type,raising-non-exception,raw-checker-failed,redefine-in-handler,redefined-argument-from-local,redefined-builtin,redundant-content-type-for-json-response,reimported,relative-import,return-outside-function,simplifiable-if-statement,singleton-comparison,syntax-error,trailing-comma-tuple,trailing-newlines,unbalanced-tuple-unpacking,undefined-all-variable,undefined-loop-variable,unexpected-line-ending-format,unidiomatic-typecheck,unnecessary-lambda,unnecessary-pass,unnecessary-semicolon,unneeded-not,unpacking-non-sequence,unreachable,unrecognized-inline-option,used-before-assignment,useless-else-on-loop,using-constant-test,wildcard-import,yield-outside-function,useless-return +enable=assert-on-tuple,astroid-error,bad-except-order,bad-inline-option,bad-option-value,bad-reversed-sequence,bare-except,binary-op-exception,boolean-datetime,catching-non-exception,cell-var-from-loop,confusing-with-statement,consider-merging-isinstance,consider-using-enumerate,consider-using-ternary,continue-in-finally,deprecated-pragma,django-not-available,duplicate-except,duplicate-key,eval-used,exec-used,expression-not-assigned,fatal,file-ignored,fixme,global-at-module-level,global-statement,global-variable-not-assigned,global-variable-undefined,http-response-with-content-type-json,http-response-with-json-dumps,invalid-all-object,invalid-characters-in-docstring,len-as-condition,literal-comparison,locally-disabled,locally-enabled,lost-exception,lowercase-l-suffix,misplaced-bare-raise,missing-kwoa,mixed-line-endings,model-has-unicode,model-missing-unicode,model-no-explicit-unicode,model-unicode-not-callable,multiple-imports,new-db-field-with-default,non-ascii-bytes-literals,nonexistent-operator,not-in-loop,notimplemented-raised,overlapping-except,parse-error,pointless-statement,pointless-string-statement,raising-bad-type,raising-non-exception,raw-checker-failed,redefine-in-handler,redefined-argument-from-local,redefined-builtin,redundant-content-type-for-json-response,reimported,relative-import,return-outside-function,simplifiable-if-statement,singleton-comparison,syntax-error,trailing-comma-tuple,trailing-newlines,unbalanced-tuple-unpacking,undefined-all-variable,undefined-loop-variable,unexpected-line-ending-format,unidiomatic-typecheck,unnecessary-lambda,unnecessary-pass,unnecessary-semicolon,unneeded-not,unpacking-non-sequence,unreachable,unrecognized-inline-option,used-before-assignment,useless-else-on-loop,using-constant-test,wildcard-import,yield-outside-function,useless-return [REPORTS] diff --git a/.readthedocs.yml b/.readthedocs.yml index 41d2e57..2dee4b5 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,9 +9,16 @@ formats: - pdf - htmlzip python: - version: 3.8 install: - requirements: requirements.txt - requirements: doc-source/requirements.txt - - method: pip - path: . +build: + os: ubuntu-22.04 + tools: + python: '3.9' + jobs: + post_create_environment: + - pip install .[classes] + post_install: + - pip install sphinxcontrib-applehelp==1.0.4 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.1 + sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 diff --git a/LICENSE b/LICENSE index e1de807..74bb4a1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2021 Dominic Davis-Foster +Copyright (c) 2021-2022 Dominic Davis-Foster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 164b4f4..81f8207 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ flake8-encodings * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - - |codefactor| |actions_flake8| |actions_mypy| |pre_commit_ci| + - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| @@ -34,40 +34,40 @@ flake8-encodings :target: https://flake8-encodings.readthedocs.io/en/latest :alt: Documentation Build Status -.. |docs_check| image:: https://github.com/domdfcoding/flake8-encodings/workflows/Docs%20Check/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22Docs+Check%22 +.. |docs_check| image:: https://github.com/python-formate/flake8-encodings/workflows/Docs%20Check/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22Docs+Check%22 :alt: Docs Check Status -.. |actions_linux| image:: https://github.com/domdfcoding/flake8-encodings/workflows/Linux/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22Linux%22 +.. |actions_linux| image:: https://github.com/python-formate/flake8-encodings/workflows/Linux/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22Linux%22 :alt: Linux Test Status -.. |actions_windows| image:: https://github.com/domdfcoding/flake8-encodings/workflows/Windows/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22Windows%22 +.. |actions_windows| image:: https://github.com/python-formate/flake8-encodings/workflows/Windows/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22Windows%22 :alt: Windows Test Status -.. |actions_macos| image:: https://github.com/domdfcoding/flake8-encodings/workflows/macOS/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22macOS%22 +.. |actions_macos| image:: https://github.com/python-formate/flake8-encodings/workflows/macOS/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22macOS%22 :alt: macOS Test Status -.. |actions_flake8| image:: https://github.com/domdfcoding/flake8-encodings/workflows/Flake8/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22Flake8%22 +.. |actions_flake8| image:: https://github.com/python-formate/flake8-encodings/workflows/Flake8/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22Flake8%22 :alt: Flake8 Status -.. |actions_mypy| image:: https://github.com/domdfcoding/flake8-encodings/workflows/mypy/badge.svg - :target: https://github.com/domdfcoding/flake8-encodings/actions?query=workflow%3A%22mypy%22 +.. |actions_mypy| image:: https://github.com/python-formate/flake8-encodings/workflows/mypy/badge.svg + :target: https://github.com/python-formate/flake8-encodings/actions?query=workflow%3A%22mypy%22 :alt: mypy status -.. |requires| image:: https://requires.io/github/domdfcoding/flake8-encodings/requirements.svg?branch=master - :target: https://requires.io/github/domdfcoding/flake8-encodings/requirements/?branch=master +.. |requires| image:: https://dependency-dash.repo-helper.uk/github/python-formate/flake8-encodings/badge.svg + :target: https://dependency-dash.repo-helper.uk/github/python-formate/flake8-encodings/ :alt: Requirements Status -.. |coveralls| image:: https://img.shields.io/coveralls/github/domdfcoding/flake8-encodings/master?logo=coveralls - :target: https://coveralls.io/github/domdfcoding/flake8-encodings?branch=master +.. |coveralls| image:: https://img.shields.io/coveralls/github/python-formate/flake8-encodings/master?logo=coveralls + :target: https://coveralls.io/github/python-formate/flake8-encodings?branch=master :alt: Coverage -.. |codefactor| image:: https://img.shields.io/codefactor/grade/github/domdfcoding/flake8-encodings?logo=codefactor - :target: https://www.codefactor.io/repository/github/domdfcoding/flake8-encodings +.. |codefactor| image:: https://img.shields.io/codefactor/grade/github/python-formate/flake8-encodings?logo=codefactor + :target: https://www.codefactor.io/repository/github/python-formate/flake8-encodings :alt: CodeFactor Grade .. |pypi-version| image:: https://img.shields.io/pypi/v/flake8-encodings @@ -94,32 +94,28 @@ flake8-encodings :target: https://anaconda.org/domdfcoding/flake8-encodings :alt: Conda - Platform -.. |license| image:: https://img.shields.io/github/license/domdfcoding/flake8-encodings - :target: https://github.com/domdfcoding/flake8-encodings/blob/master/LICENSE +.. |license| image:: https://img.shields.io/github/license/python-formate/flake8-encodings + :target: https://github.com/python-formate/flake8-encodings/blob/master/LICENSE :alt: License -.. |language| image:: https://img.shields.io/github/languages/top/domdfcoding/flake8-encodings +.. |language| image:: https://img.shields.io/github/languages/top/python-formate/flake8-encodings :alt: GitHub top language -.. |commits-since| image:: https://img.shields.io/github/commits-since/domdfcoding/flake8-encodings/v0.3.0 - :target: https://github.com/domdfcoding/flake8-encodings/pulse +.. |commits-since| image:: https://img.shields.io/github/commits-since/python-formate/flake8-encodings/v0.5.1 + :target: https://github.com/python-formate/flake8-encodings/pulse :alt: GitHub commits since tagged version -.. |commits-latest| image:: https://img.shields.io/github/last-commit/domdfcoding/flake8-encodings - :target: https://github.com/domdfcoding/flake8-encodings/commit/master +.. |commits-latest| image:: https://img.shields.io/github/last-commit/python-formate/flake8-encodings + :target: https://github.com/python-formate/flake8-encodings/commit/master :alt: GitHub last commit -.. |maintained| image:: https://img.shields.io/maintenance/yes/2021 +.. |maintained| image:: https://img.shields.io/maintenance/yes/2025 :alt: Maintenance .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/flake8-encodings :target: https://pypi.org/project/flake8-encodings/ :alt: PyPI - Downloads -.. |pre_commit_ci| image:: https://results.pre-commit.ci/badge/github/domdfcoding/flake8-encodings/master.svg - :target: https://results.pre-commit.ci/latest/github/domdfcoding/flake8-encodings/master - :alt: pre-commit.ci status - .. end shields Installation @@ -141,8 +137,8 @@ To install with ``conda``: .. code-block:: bash - $ conda config --add channels http://conda.anaconda.org/conda-forge - $ conda config --add channels http://conda.anaconda.org/domdfcoding + $ conda config --add channels https://conda.anaconda.org/conda-forge + $ conda config --add channels https://conda.anaconda.org/domdfcoding * Then install @@ -152,6 +148,20 @@ To install with ``conda``: .. end installation + +In version 0.5.1 and above the functionality for checking classes +(``configparser.ConfigParser`` and ``pathlib.Path`` for now) +requires the ``classes`` extra to be installed: + +.. code-block:: bash + + $ python3 -m pip install flake8-encodings[classes] + +The checks for classes are slower and CPU intensive, +so only enable them if you use the classes in question. + + + Motivation ------------- diff --git a/__pkginfo__.py b/__pkginfo__.py index 0900244..2a320ae 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -1,32 +1,5 @@ # This file is managed by 'repo_helper'. Don't edit it directly. -# Copyright © 2020 Dominic Davis-Foster -# -# This file is distributed under the same license terms as the program it came with. -# There will probably be a file called LICEN[S/C]E in the same directory as this file. -# -# In any case, this program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# -# This script based on https://github.com/rocky/python-uncompyle6/blob/master/__pkginfo__.py -# -# stdlib -import pathlib +__all__ = ["extras_require"] -__all__ = [ - "__copyright__", - "__version__", - "repo_root", - "install_requires", - "extras_require", - ] - -__copyright__ = """ -2020-2021 Dominic Davis-Foster -""" - -__version__ = "0.3.0" -repo_root = pathlib.Path(__file__).parent -install_requires = (repo_root / "requirements.txt").read_text(encoding="utf-8").split('\n') -extras_require = {} +extras_require = {"classes": ["jedi>=0.18.0"], "all": ["jedi>=0.18.0"]} diff --git a/doc-source/Source.rst b/doc-source/Source.rst index 1df773f..598d75c 100644 --- a/doc-source/Source.rst +++ b/doc-source/Source.rst @@ -3,28 +3,31 @@ Downloading source code ========================= The ``flake8-encodings`` source code is available on GitHub, -and can be accessed from the following URL: https://github.com/domdfcoding/flake8-encodings +and can be accessed from the following URL: https://github.com/python-formate/flake8-encodings If you have ``git`` installed, you can clone the repository with the following command: -.. code-block:: bash +.. prompt:: bash + + git clone https://github.com/python-formate/flake8-encodings + +.. parsed-literal:: - $ git clone https://github.com/domdfcoding/flake8-encodings" - > Cloning into 'flake8-encodings'... - > remote: Enumerating objects: 47, done. - > remote: Counting objects: 100% (47/47), done. - > remote: Compressing objects: 100% (41/41), done. - > remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 - > Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. - > Resolving deltas: 100% (66/66), done. + Cloning into 'flake8-encodings'... + remote: Enumerating objects: 47, done. + remote: Counting objects: 100% (47/47), done. + remote: Compressing objects: 100% (41/41), done. + remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 + Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. + Resolving deltas: 100% (66/66), done. | Alternatively, the code can be downloaded in a 'zip' file by clicking: | :guilabel:`Clone or download` --> :guilabel:`Download Zip` .. figure:: git_download.png - :alt: Downloading a 'zip' file of the source code. + :alt: Downloading a 'zip' file of the source code. - Downloading a 'zip' file of the source code + Downloading a 'zip' file of the source code Building from source diff --git a/doc-source/_static/style.css b/doc-source/_static/style.css index 644e48b..a6b8e4d 100644 --- a/doc-source/_static/style.css +++ b/doc-source/_static/style.css @@ -8,3 +8,12 @@ div.highlight { .field-list dt, dl.simple dt { margin-top: 0.5rem; } + +div.versionchanged ul, div.versionremoved ul { + margin-left: 20px; + margin-top: 0; +} + +.longtable.autosummary { + width: 100%; +} diff --git a/doc-source/_templates/sidebar/navigation.html b/doc-source/_templates/sidebar/navigation.html deleted file mode 100644 index 36b61cc..0000000 --- a/doc-source/_templates/sidebar/navigation.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/doc-source/conf.py b/doc-source/conf.py index 055485e..8eb3017 100644 --- a/doc-source/conf.py +++ b/doc-source/conf.py @@ -7,69 +7,34 @@ import re import sys -sys.path.append(os.path.abspath('.')) -sys.path.append(os.path.abspath("..")) +# 3rd party +from sphinx_pyproject import SphinxConfig -# this package -from __pkginfo__ import __version__ +sys.path.append('.') -github_username = "domdfcoding" -github_repository = "flake8-encodings" -github_url = f"https://github.com/{github_username}/{github_repository}" +config = SphinxConfig(globalns=globals()) +project = config["project"] +author = config["author"] +documentation_summary = config.description + +github_url = "https://github.com/{github_username}/{github_repository}".format_map(config) rst_prolog = f""".. |pkgname| replace:: flake8-encodings .. |pkgname2| replace:: ``flake8-encodings`` .. |browse_github| replace:: `Browse the GitHub Repository <{github_url}>`__ """ -author = "Dominic Davis-Foster" -project = "flake8-encodings".replace('_', '-') slug = re.sub(r'\W+', '-', project.lower()) -release = version = __version__ -copyright = "2020-2021 Dominic Davis-Foster" # pylint: disable=redefined-builtin -language = "en" -package_root = "flake8_encodings" - -extensions = [ - "sphinx_toolbox", - "sphinx_toolbox.more_autodoc", - "sphinx_toolbox.more_autosummary", - "sphinx_toolbox.documentation_summary", - "sphinx_toolbox.tweaks.param_dash", - "sphinx_toolbox.tweaks.latex_toc", - "sphinx.ext.intersphinx", - "sphinx.ext.mathjax", - "sphinxcontrib.httpdomain", - "sphinxcontrib.extras_require", - "sphinx.ext.todo", - "sphinxemoji.sphinxemoji", - "notfound.extension", - "sphinx_copybutton", - "sphinxcontrib.default_values", - "sphinxcontrib.toctree_plus", - "sphinx_debuginfo", - "seed_intersphinx_mapping", - "sphinx_toolbox.pre_commit", - "sphinx_toolbox.flake8", - ] - -sphinxemoji_style = "twemoji" -todo_include_todos = bool(os.environ.get("SHOW_TODOS", 0)) -gitstamp_fmt = "%d %b %Y" - -templates_path = ["_templates"] -html_static_path = ["_static"] -source_suffix = ".rst" -master_doc = "index" -suppress_warnings = ["image.nonlocal_uri"] -pygments_style = "default" +release = version = config.version + +sphinx_builder = os.environ.get("SPHINX_BUILDER", "html").lower() +todo_include_todos = int(os.environ.get("SHOW_TODOS", 0)) and sphinx_builder != "latex" intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "sphinx": ("https://www.sphinx-doc.org/en/stable/", None), } -html_theme = "furo" html_theme_options = { "light_css_variables": { "toc-title-font-size": "12pt", @@ -82,8 +47,6 @@ "admonition-font-size": "12pt", }, } -html_theme_path = ["../.."] -html_show_sourcelink = True # True will show link to source html_context = {} htmlhelp_basename = slug @@ -92,53 +55,55 @@ man_pages = [("index", slug, project, [author], 1)] texinfo_documents = [("index", slug, project, author, slug, project, "Miscellaneous")] -toctree_plus_types = { - "class", - "function", - "method", - "data", - "enum", - "flag", - "confval", - "directive", - "role", - "confval", - "protocol", - "typeddict", - "namedtuple", - "exception", - } +toctree_plus_types = set(config["toctree_plus_types"]) -add_module_names = False -hide_none_rtype = True -all_typevars = True -overloads_location = "bottom" -documentation_summary = "A Flake8 plugin to identify incorrect use of encodings." - -autodoc_exclude_members = [ # Exclude "standard" methods. - "__dict__", - "__class__", - "__dir__", - "__weakref__", - "__module__", - "__annotations__", - "__orig_bases__", - "__parameters__", - "__subclasshook__", - "__init_subclass__", - "__attrs_attrs__", - "__init__", - "__new__", - "__getnewargs__", - "__abstractmethods__", - "__hash__", - ] autodoc_default_options = { "members": None, # Include all members (methods). "special-members": None, "autosummary": None, "show-inheritance": None, - "exclude-members": ','.join(autodoc_exclude_members), + "exclude-members": ','.join(config["autodoc_exclude_members"]), } +latex_elements = { + "printindex": "\\begin{flushleft}\n\\printindex\n\\end{flushleft}", + "tableofcontents": "\\pdfbookmark[0]{\\contentsname}{toc}\\sphinxtableofcontents", + } + + +# Fix for pathlib issue with sphinxemoji on Python 3.9 and Sphinx 4.x +def copy_asset_files(app, exc): + # 3rd party + from domdf_python_tools.compat import importlib_resources + from sphinx.util.fileutil import copy_asset + + if exc: + return + + asset_files = ["twemoji.js", "twemoji.css"] + for path in asset_files: + path_str = os.fspath(importlib_resources.files("sphinxemoji") / path) + copy_asset(path_str, os.path.join(app.outdir, "_static")) + + +def setup(app): + # 3rd party + from sphinx_toolbox.latex import better_header_layout + from sphinxemoji import sphinxemoji + + app.connect("config-inited", lambda app, config: better_header_layout(config)) + app.connect("build-finished", copy_asset_files) + app.add_js_file("https://unpkg.com/twemoji@latest/dist/twemoji.min.js") + app.add_js_file("twemoji.js") + app.add_css_file("twemoji.css") + app.add_transform(sphinxemoji.EmojiSubstitutions) + + +needspace_amount = r"5\baselineskip" +favicons = [{ + "rel": "icon", + "href": "https://python-formate.github.io/assets/formate.ico", + "sizes": "48x48", + "type": "image/vnd.microsoft.icon" + }] nitpicky = True diff --git a/doc-source/contributing.rst b/doc-source/contributing.rst deleted file mode 100644 index ec6d84a..0000000 --- a/doc-source/contributing.rst +++ /dev/null @@ -1,73 +0,0 @@ -============== -Contributing -============== - -.. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md - -``flake8-encodings`` uses `tox `_ to automate testing and packaging, -and `pre-commit `_ to maintain code quality. - -Install ``pre-commit`` with ``pip`` and install the git hook: - -.. prompt:: bash - - python -m pip install pre-commit - pre-commit install - - -Coding style --------------- - -`formate `_ is used for code formatting. - -It can be run manually via ``pre-commit``: - -.. prompt:: bash - - pre-commit run formate -a - - -Or, to run the complete autoformatting suite: - -.. prompt:: bash - - pre-commit run -a - - -Automated tests -------------------- - -Tests are run with ``tox`` and ``pytest``. -To run tests for a specific Python version, such as Python 3.6: - -.. prompt:: bash - - tox -e py36 - - -To run tests for all Python versions, simply run: - -.. prompt:: bash - - tox - - -Type Annotations -------------------- - -Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: - -.. prompt:: bash - - tox -e mypy - - - -Build documentation locally ------------------------------- - -The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: - -.. prompt:: bash - - tox -e docs diff --git a/doc-source/index.rst b/doc-source/index.rst index ec0eca1..b436d89 100644 --- a/doc-source/index.rst +++ b/doc-source/index.rst @@ -5,10 +5,13 @@ flake8-encodings .. start short_desc .. documentation-summary:: + :meta: .. end short_desc -.. seealso:: :pep:`597` -- Add optional EncodingWarning +.. only:: html + + .. seealso:: :pep:`597` -- Add optional EncodingWarning .. start shields @@ -29,7 +32,7 @@ flake8-encodings * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - - |codefactor| |actions_flake8| |actions_mypy| |pre_commit_ci| + - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| @@ -61,7 +64,8 @@ flake8-encodings :workflow: mypy :alt: mypy status - .. |requires| requires-io-shield:: + .. |requires| image:: https://dependency-dash.repo-helper.uk/github/python-formate/flake8-encodings/badge.svg + :target: https://dependency-dash.repo-helper.uk/github/python-formate/flake8-encodings/ :alt: Requirements Status .. |coveralls| coveralls-shield:: @@ -107,14 +111,14 @@ flake8-encodings :alt: GitHub top language .. |commits-since| github-shield:: - :commits-since: v0.3.0 + :commits-since: v0.5.1 :alt: GitHub commits since tagged version .. |commits-latest| github-shield:: :last-commit: :alt: GitHub last commit - .. |maintained| maintained-shield:: 2021 + .. |maintained| maintained-shield:: 2025 :alt: Maintenance .. |pypi-downloads| pypi-shield:: @@ -122,9 +126,6 @@ flake8-encodings :downloads: month :alt: PyPI - Downloads - .. |pre_commit_ci| pre-commit-ci-shield:: - :alt: pre-commit.ci status - .. end shields Installation @@ -140,6 +141,19 @@ Installation .. end installation +.. latex:vspace:: 20px + +In version 0.5.1 and above the functionality for checking classes +(:class:`~configparser.ConfigParser` and :class:`~.pathlib.Path` for now) +requires the ``classes`` extra to be installed: + +.. prompt:: bash + + python3 -m pip install flake8-encodings[classes] + +The checks for classes are slower and CPU intensive, +so only enable them if you use the classes in question. + Motivation ------------- @@ -169,6 +183,9 @@ which can be used in conjunction with this tool to identify issues at runtime. Contents ------------- +.. html-section:: + + .. toctree:: :hidden: @@ -179,8 +196,16 @@ Contents :glob: usage - contributing Source + license + +.. sidebar-links:: + :caption: Links + :github: + :pypi: flake8-encodings + + Contributing Guide + .. start links @@ -188,7 +213,7 @@ Contents View the :ref:`Function Index ` or browse the `Source Code <_modules/index.html>`__. - `Browse the GitHub Repository `__ + :github:repo:`Browse the GitHub Repository ` .. end links @@ -196,6 +221,8 @@ Contents Footnotes ------------- +.. html-section:: + .. [1] "Packages can't be installed when encoding is not UTF-8" (https://github.com/methane/pep597-pypi-ascii) diff --git a/doc-source/license.rst b/doc-source/license.rst new file mode 100644 index 0000000..37033a2 --- /dev/null +++ b/doc-source/license.rst @@ -0,0 +1,10 @@ +========= +License +========= + +``flake8-encodings`` is licensed under the :choosealicense:`MIT` + +.. license-info:: MIT + +.. license:: + :py: flake8-encodings diff --git a/doc-source/requirements.txt b/doc-source/requirements.txt index a350580..3a34638 100644 --- a/doc-source/requirements.txt +++ b/doc-source/requirements.txt @@ -1,18 +1,21 @@ -autodocsumm>=0.2.0 -default-values>=0.4.2 -extras-require>=0.2.0 -furo>=2020.11.19b18 -pyyaml>=5.3.1 -repo-helper-sphinx-theme>=0.0.2 -seed-intersphinx-mapping>=0.3.1 +default-values>=0.6.0 +extras-require>=0.5.0 +furo==2021.06.18b36 +html-section>=0.3.0 +seed-intersphinx-mapping>=1.2.2 sphinx>=3.0.3 sphinx-copybutton>=0.2.12 -sphinx-debuginfo>=0.1.0 -sphinx-notfound-page>=0.5 -sphinx-prompt>=1.1.0 -sphinx-tabs>=1.1.13 -sphinx-toolbox>=2.2.0 -sphinxcontrib-httpdomain>=1.7.0 +sphinx-debuginfo>=0.2.2 +sphinx-favicon>=0.2 +sphinx-licenseinfo>=0.3.1 +sphinx-notfound-page>=0.7.1 +sphinx-pyproject>=0.1.0 +sphinx-toolbox>=3.5.0 +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 sphinxemoji>=0.1.6 -tabulate>=0.8.7 -toctree-plus>=0.1.0 +toctree-plus>=0.6.1 diff --git a/doc-source/usage.rst b/doc-source/usage.rst index 57d4853..e118e94 100644 --- a/doc-source/usage.rst +++ b/doc-source/usage.rst @@ -8,7 +8,7 @@ This library provides the Flake8 plugin ``flake8-encodings`` to identify incorr Flake8 codes -------------- -**ENC00X**: checks for :func:`open`, :func:`builtins.open ` and :func:`io.open`. +:bold-title:`ENC00X`: checks for :func:`open`, :func:`builtins.open ` and :func:`io.open`. .. flake8-codes:: flake8_encodings @@ -19,7 +19,8 @@ Flake8 codes ``ENC003`` and ``ENC004`` are used in cases where the encoding is omitted (or is explicitly :py:obj:`None`) but the mode cannot be determined. The file might be opened in binary mode, in which case the encoding argument is ignored, or in text mode, in which case an encoding should be given. -**ENC01X**: checks for :meth:`configparser.ConfigParser.read`. + +:bold-title:`ENC01X`: checks for :meth:`configparser.ConfigParser.read`. .. flake8-codes:: flake8_encodings @@ -27,8 +28,10 @@ Flake8 codes ENC012 .. versionadded:: 0.2.0 +.. versionchanged:: 0.4.0 These codes now require the ``classes`` extra to be installed [1]_. + -**ENC02X**: checks for :meth:`pathlib.Path.open`, :meth:`read_text() ` and :meth:`write_text() `. +:bold-title:`ENC02X`: checks for :meth:`pathlib.Path.open`, :meth:`read_text() ` and :meth:`write_text() `. .. flake8-codes:: flake8_encodings @@ -40,12 +43,17 @@ Flake8 codes ENC026 .. versionadded:: 0.3.0 +.. versionchanged:: 0.4.0 These codes now require the ``classes`` extra to be installed [1]_. + +.. [1] Install using ``python3 -m pip install flake8-encodings[classes]`` + Examples ^^^^^^^^^^ .. code-block:: python + # stdlib import configparser open("README.rst").read() # ENC001 no encoding specified for 'open'. @@ -54,9 +62,10 @@ Examples open("README.rst", mode="rb", encoding=None).read() # OK - def foo(mode: str = "r"): + def foo(mode: str = 'r'): open("README.rst", mode=mode).read() # ENC003 no encoding specified for 'open' with unknown mode. - open("README.rst", mode=mode, encoding=None).read() # ENC004 'encoding=None' used for 'open' with unknown mode. + open("README.rst", mode=mode, + encoding=None).read() # ENC004 'encoding=None' used for 'open' with unknown mode. def load_config(filename: str): @@ -64,12 +73,13 @@ Examples cfg.read(filename) # ENC011 # cfg.read(filename, encoding=None) # ENC012 + def manipulate_file(filename): path = pathlib.Path(filename) path.write_text("Hello world") # ENC025 - with path.open("a") as fp: # ENC021 + with path.open('a') as fp: # ENC021 f.write("\nHello everyone") print(path.read_text(encoding=None)) # ENC024 @@ -83,4 +93,4 @@ See `pre-commit `_ for instructions Sample ``.pre-commit-config.yaml``: -.. pre-commit:flake8:: 0.3.0 +.. pre-commit:flake8:: 0.5.1 diff --git a/flake8_encodings/__init__.py b/flake8_encodings/__init__.py index 8840f17..76d0317 100644 --- a/flake8_encodings/__init__.py +++ b/flake8_encodings/__init__.py @@ -37,23 +37,29 @@ # stdlib import ast import configparser +import pathlib +import sys import tempfile -from typing import Iterator, List, Optional, Tuple, Type +from typing import TYPE_CHECKING, Callable, Iterator, List, Optional, Tuple, Type # 3rd party import flake8_helper -import jedi # type: ignore from astatine import get_attribute_name, kwargs_from_node from domdf_python_tools.paths import PathPlus from domdf_python_tools.typing import PathLike +if TYPE_CHECKING: + # 3rd party + from jedi import Script # type: ignore[import-untyped] + from jedi.api.classes import Name # type: ignore[import-untyped] + __author__: str = "Dominic Davis-Foster" __copyright__: str = "2020-2021 Dominic Davis-Foster" __license__: str = "MIT License" -__version__: str = "0.3.0" +__version__: str = "0.5.1" __email__: str = "dominic@davis-foster.co.uk" -__all__ = ["Visitor", "Plugin"] +__all__ = ["Visitor", "ClassVisitor", "Plugin"] ENC001 = "ENC001 no encoding specified for 'open'." ENC002 = "ENC002 'encoding=None' used for 'open'." @@ -70,7 +76,10 @@ ENC025 = "ENC025 no encoding specified for 'pathlib.Path.write_text'." ENC026 = "ENC026 'encoding=None' used for 'pathlib.Path.write_text'." -jedi.settings.fast_parser = False +_configparser_read = configparser.ConfigParser().read +_pathlib_open = pathlib.Path().open +_pathlib_read_text = pathlib.Path().read_text +_pathlib_write_text = pathlib.Path().write_text def mode_is_binary(mode: ast.AST) -> Optional[bool]: @@ -84,45 +93,38 @@ def mode_is_binary(mode: ast.AST) -> Optional[bool]: if isinstance(mode, ast.Constant): # pragma: no cover (") - self.jedi_script = jedi.Script('') +if sys.version_info < (3, 12): # pragma: no cover (py312+) + _constant_nameconstant = (ast.Constant, ast.NameConstant) + _skip_312_deprecations = False +else: # pragma: no cover ( None: """ Check the call represented by the given AST node is using encodings correctly. This function checks :func:`open`, :func:`builtins.open ` and :func:`io.open`. - .. versionchanged:: 0.2.0 - - Renamed from ``check_encoding`` + .. versionchanged:: 0.2.0 Renamed from ``check_encoding`` """ kwargs = kwargs_from_node(node, open) @@ -144,13 +146,85 @@ def check_open_encoding(self, node: ast.Call): if "encoding" not in kwargs: self.report_error(node, ENC003 if unknown_mode else ENC001) - elif isinstance(kwargs["encoding"], (ast.Constant, ast.NameConstant)): + elif isinstance(kwargs["encoding"], _constant_nameconstant): if kwargs["encoding"].value is None: self.report_error(node, ENC004 if unknown_mode else ENC002) check_encoding = check_open_encoding # deprecated - def check_configparser_encoding(self, node: ast.Call): + def visit_Call(self, node: ast.Call) -> None: # noqa: D102 + + if isinstance(node.func, ast.Name): + + if node.func.id == "open": + # print(node.func.id) + self.check_encoding(node) + return + + elif isinstance(node.func, ast.Attribute): + if isinstance(node.func.value, ast.Name): + + if node.func.value.id in {"builtins", "io"} and node.func.attr == "open": + self.check_open_encoding(node) + return + + if not _skip_312_deprecations: # pragma: no cover (py312+) + if isinstance(node.func.value, ast.Str): # pragma: no cover + # Attribute on a string + return self.generic_visit(node) + + if isinstance(node.func.value, ast.BinOp): # pragma: no cover + # TODO + # Expressions such as (tmp_pathplus / "code.py").write_text(example_source) + return self.generic_visit(node) + + elif isinstance(node.func.value, ast.Subscript): # pragma: no cover + # TODO + # Expressions such as my_list[0].run() + return self.generic_visit(node) + + self.generic_visit(node) + + +class ClassVisitor(Visitor): # pragma: no cover (py313+) + """ + AST visitor to identify incorrect use of encodings, + with support for :class:`pathlib.Path` and :class:`configparser.ConfigParser`. + + .. versionadded:: 0.4.0 + """ # noqa: D400 + + def __init__(self): + try: + # 3rd party + import jedi + except ImportError as e: + exc = e.__class__("This class requires 'jedi' to be installed but it could not be imported.") + exc.__traceback__ = e.__traceback__ + raise exc from None + + super().__init__() + self.filename = PathPlus("") + self.jedi_script = jedi.Script('') + + def first_visit(self, node: ast.AST, filename: PathPlus) -> None: + """ + Like :meth:`ast.NodeVisitor.visit`, but configures type inference. + + .. versionadded:: 0.2.0 + + :param node: + :param filename: The path to Python source file the AST node was generated from. + """ + + # 3rd party + import jedi # nodep + + self.filename = PathPlus(filename) + self.jedi_script = jedi.Script(self.filename.read_text(), path=self.filename) + self.visit(node) + + def check_configparser_encoding(self, node: ast.Call) -> None: """ Check the call represented by the given AST node is using encodings correctly. @@ -159,16 +233,16 @@ def check_configparser_encoding(self, node: ast.Call): .. versionadded:: 0.2.0 """ - kwargs = kwargs_from_node(node, configparser.ConfigParser.read) + kwargs = kwargs_from_node(node, _configparser_read) if "encoding" not in kwargs: self.report_error(node, ENC011) - elif isinstance(kwargs["encoding"], (ast.Constant, ast.NameConstant)): + elif isinstance(kwargs["encoding"], _constant_nameconstant): if kwargs["encoding"].value is None: self.report_error(node, ENC012) - def check_pathlib_encoding(self, node: ast.Call, method_name: str): + def check_pathlib_encoding(self, node: ast.Call, method_name: str) -> None: """ Check the call represented by the given AST node is using encodings correctly. @@ -178,29 +252,46 @@ def check_pathlib_encoding(self, node: ast.Call, method_name: str): .. versionadded:: 0.3.0 """ + function: Callable + if method_name == "open": no_encoding = ENC021 encoding_none = ENC022 + function = _pathlib_open elif method_name == "read_text": no_encoding = ENC023 encoding_none = ENC024 + function = _pathlib_read_text elif method_name == "write_text": no_encoding = ENC025 encoding_none = ENC026 + function = _pathlib_write_text else: # pragma: no cover # Not a method we understand return - kwargs = kwargs_from_node(node, configparser.ConfigParser.read) + kwargs = kwargs_from_node(node, function) + + unknown_mode = False + + if "mode" in kwargs: + is_binary = mode_is_binary(kwargs["mode"]) + + if is_binary: + return + elif is_binary is None: # pragma: no cover + # TODO: unknown mode + unknown_mode = True + return if "encoding" not in kwargs: self.report_error(node, no_encoding) - elif isinstance(kwargs["encoding"], (ast.Constant, ast.NameConstant)): + elif isinstance(kwargs["encoding"], _constant_nameconstant): if kwargs["encoding"].value is None: self.report_error(node, encoding_none) - def visit_Call(self, node: ast.Call): # noqa: D102 + def visit_Call(self, node: ast.Call) -> None: # noqa: D102 if isinstance(node.func, ast.Name): @@ -216,22 +307,32 @@ def visit_Call(self, node: ast.Call): # noqa: D102 self.check_open_encoding(node) return - if isinstance(node.func.value, ast.Str): # pragma: no cover - # Attribute on a string - return self.generic_visit(node) + if not _skip_312_deprecations: # pragma: no cover (py312+) + if isinstance(node.func.value, ast.Str): # pragma: no cover + # Attribute on a string + return self.generic_visit(node) - elif isinstance(node.func.value, ast.BinOp): # pragma: no cover + if isinstance(node.func.value, ast.BinOp): # pragma: no cover # TODO # Expressions such as (tmp_pathplus / "code.py").write_text(example_source) return self.generic_visit(node) + elif isinstance(node.func.value, ast.Subscript): # pragma: no cover + # TODO + # Expressions such as my_list[0].run() + return self.generic_visit(node) + elif self.filename.as_posix() == "": # no jedi source (run with .visit() or from memory) return self.generic_visit(node) else: - inferred_types = get_inferred_types(self.jedi_script, node) - method_name = tuple(get_attribute_name(node.func))[-1] + + try: + inferred_types = get_inferred_types(self.jedi_script, node) + method_name = tuple(get_attribute_name(node.func))[-1] + except NotImplementedError: # pragma: no cover + return self.generic_visit(node) for class_name in inferred_types: if is_configparser_read(class_name, method_name): @@ -260,21 +361,34 @@ def __init__(self, tree: ast.AST, filename: PathLike): def run(self) -> Iterator[Tuple[int, int, str, Type["Plugin"]]]: # noqa: D102 - original_cache_dir = jedi.settings.cache_directory + try: # pragma: no cover (py313+) + # 3rd party + import jedi + + # jedi.settings.fast_parser = False - with tempfile.TemporaryDirectory() as cache_directory: - jedi.settings.cache_directory = cache_directory + original_cache_dir = jedi.settings.cache_directory + with tempfile.TemporaryDirectory() as cache_directory: + jedi.settings.cache_directory = cache_directory + + class_visitor = ClassVisitor() + class_visitor.first_visit(self._tree, self.filename) + + for line, col, msg in class_visitor.errors: + yield line, col, msg, type(self) + + jedi.settings.cache_directory = original_cache_dir + + except ImportError: visitor = Visitor() - visitor.first_visit(self._tree, self.filename) + visitor.visit(self._tree) for line, col, msg in visitor.errors: yield line, col, msg, type(self) - jedi.settings.cache_directory = original_cache_dir - -def is_configparser_read(class_name: str, method_name: str) -> bool: +def is_configparser_read(class_name: str, method_name: str) -> bool: # pragma: no cover (py313+) """ Returns :py:obj:`True` if method is :meth:`configparser.ConfigParser.read` or :meth:`configparser.RawConfigParser.read`. @@ -294,7 +408,7 @@ def is_configparser_read(class_name: str, method_name: str) -> bool: return True -def is_pathlib_method(class_name: str, method_name: str) -> bool: +def is_pathlib_method(class_name: str, method_name: str) -> bool: # pragma: no cover (py313+) """ Returns :py:obj:`True` if method is :meth:`pathlib.Path.open`, :meth:`read_text() ` or :meth:`write_text() `. @@ -314,7 +428,7 @@ def is_pathlib_method(class_name: str, method_name: str) -> bool: return True -def get_inferred_types(jedi_script: jedi.Script, node: ast.Call) -> List[str]: +def get_inferred_types(jedi_script: "Script", node: ast.Call) -> List[str]: # pragma: no cover (py313+) """ Returns a list of types inferred by ``jedi`` for the given call node. @@ -325,11 +439,11 @@ def get_inferred_types(jedi_script: jedi.Script, node: ast.Call) -> List[str]: attr_names = tuple(get_attribute_name(node.func)) inferred_types = set() - inferred_name: jedi.api.classes.Name + inferred_name: "Name" for inferred_name in jedi_script.infer(node.lineno, node.func.col_offset): inferred_types.add(inferred_name.full_name) for inferred_name in jedi_script.infer(node.lineno, node.func.col_offset + len('.'.join(attr_names[:-1]))): inferred_types.add(inferred_name.full_name) - return sorted(inferred_types) + return sorted(filter(None, inferred_types)) diff --git a/formate.toml b/formate.toml index 74725fb..a2e27c5 100644 --- a/formate.toml +++ b/formate.toml @@ -6,21 +6,17 @@ noqa-reformat = 60 ellipsis-reformat = 70 squish_stubs = 80 -[config] -indent = "\t" -line_length = 115 - [hooks.yapf] priority = 30 -[hooks.isort] -priority = 50 - [hooks.yapf.kwargs] yapf_style = ".style.yapf" +[hooks.isort] +priority = 50 + [hooks.isort.kwargs] -indent = "\t\t" +indent = " " multi_line_output = 8 import_heading_stdlib = "stdlib" import_heading_thirdparty = "3rd party" @@ -43,6 +39,7 @@ known_third_party = [ "flake8", "flake8_helper", "github", + "importlib_metadata", "jedi", "pytest", "pytest_cov", @@ -51,4 +48,8 @@ known_third_party = [ "pytest_timeout", "requests", ] -known_first_party = "flake8_encodings" +known_first_party = [ "flake8_encodings",] + +[config] +indent = " " +line_length = 115 diff --git a/justfile b/justfile new file mode 100644 index 0000000..e8ed871 --- /dev/null +++ b/justfile @@ -0,0 +1,22 @@ +default: lint + +pdf-docs: latex-docs + make -C doc-source/build/latex/ + +latex-docs: + SPHINX_BUILDER=latex tox -e docs + +unused-imports: + tox -e lint -- --select F401 + +incomplete-defs: + tox -e lint -- --select MAN + +vdiff: + git diff $(repo-helper show version -q)..HEAD + +bare-ignore: + greppy '# type:? *ignore(?!\[|\w)' -s + +lint: unused-imports incomplete-defs bare-ignore + tox -n qa diff --git a/pyproject.toml b/pyproject.toml index 61ef0f4..4858e2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,26 +4,32 @@ build-backend = "whey" [project] name = "flake8-encodings" -version = "0.3.0" +version = "0.5.1" description = "A Flake8 plugin to identify incorrect use of encodings." readme = "README.rst" -keywords = [ "flake8", "encodings", "pep597", "unicode",] +keywords = [ "encodings", "flake8", "pep597", "unicode",] dynamic = [ "requires-python", "classifiers", "dependencies",] -[[project.authors]] -email = "dominic@davis-foster.co.uk" -name = "Dominic Davis-Foster" - - [project.license] file = "LICENSE" +[[project.authors]] +name = "Dominic Davis-Foster" +email = "dominic@davis-foster.co.uk" + [project.urls] -Homepage = "https://github.com/domdfcoding/flake8-encodings" -"Issue Tracker" = "https://github.com/domdfcoding/flake8-encodings/issues" -"Source Code" = "https://github.com/domdfcoding/flake8-encodings" +Homepage = "https://github.com/python-formate/flake8-encodings" +"Issue Tracker" = "https://github.com/python-formate/flake8-encodings/issues" +"Source Code" = "https://github.com/python-formate/flake8-encodings" Documentation = "https://flake8-encodings.readthedocs.io/en/latest" +[project.entry-points."flake8.extension"] +ENC0 = "flake8_encodings:Plugin" + +[project.optional-dependencies] +classes = [ "jedi>=0.18.0",] +all = [ "jedi>=0.18.0",] + [tool.whey] base-classifiers = [ "Development Status :: 4 - Beta", @@ -32,11 +38,124 @@ base-classifiers = [ "Topic :: Utilities", "Typing :: Typed", ] -python-versions = [ "3.6", "3.7", "3.8", "3.9",] +python-versions = [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13",] python-implementations = [ "CPython", "PyPy",] platforms = [ "Windows", "macOS", "Linux",] license-key = "MIT" package = "flake8_encodings" -[project.entry-points."flake8.extension"] -ENC0 = "flake8_encodings:Plugin" +[tool.mkrecipe] +conda-channels = [ "conda-forge", "domdfcoding",] +extras = "all" + +[tool.sphinx-pyproject] +github_username = "python-formate" +github_repository = "flake8-encodings" +author = "Dominic Davis-Foster" +project = "flake8-encodings" +copyright = "2020-2022 Dominic Davis-Foster" +language = "en" +package_root = "flake8_encodings" +extensions = [ + "sphinx_toolbox", + "sphinx_toolbox.more_autodoc", + "sphinx_toolbox.more_autosummary", + "sphinx_toolbox.documentation_summary", + "sphinx_toolbox.tweaks.param_dash", + "sphinxcontrib.toctree_plus", + "sphinx_toolbox.tweaks.latex_layout", + "sphinx_toolbox.tweaks.latex_toc", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinxcontrib.extras_require", + "sphinx.ext.todo", + "notfound.extension", + "sphinx_copybutton", + "sphinxcontrib.default_values", + "sphinx_debuginfo", + "sphinx_licenseinfo", + "seed_intersphinx_mapping", + "html_section", + "sphinx_toolbox.pre_commit", + "sphinx_toolbox.flake8", + "sphinx_favicon", +] +gitstamp_fmt = "%d %b %Y" +templates_path = [ "_templates",] +html_static_path = [ "_static",] +source_suffix = ".rst" +master_doc = "index" +suppress_warnings = [ "image.nonlocal_uri",] +pygments_style = "default" +html_theme = "furo" +html_theme_path = [ "../..",] +html_show_sourcelink = true +toctree_plus_types = [ + "class", + "confval", + "data", + "directive", + "enum", + "exception", + "flag", + "function", + "namedtuple", + "protocol", + "role", + "typeddict", +] +add_module_names = false +hide_none_rtype = true +all_typevars = true +overloads_location = "bottom" +html_codeblock_linenos_style = "table" +autodoc_exclude_members = [ + "__dict__", + "__class__", + "__dir__", + "__weakref__", + "__module__", + "__annotations__", + "__orig_bases__", + "__parameters__", + "__subclasshook__", + "__init_subclass__", + "__attrs_attrs__", + "__init__", + "__new__", + "__getnewargs__", + "__abstractmethods__", + "__hash__", +] + +[tool.mypy] +python_version = "3.9" +namespace_packages = true +check_untyped_defs = true +warn_unused_ignores = true +no_implicit_optional = true +show_error_codes = true + +[tool.snippet-fmt] +directives = [ "code-block",] + +[tool.snippet-fmt.languages.python] +reformat = true + +[tool.snippet-fmt.languages.TOML] +reformat = true + +[tool.snippet-fmt.languages.ini] + +[tool.snippet-fmt.languages.json] + +[tool.dependency-dash."requirements.txt"] +order = 10 + +[tool.dependency-dash."tests/requirements.txt"] +order = 20 +include = false + +[tool.dependency-dash."doc-source/requirements.txt"] +order = 30 +include = false diff --git a/repo_helper.yml b/repo_helper.yml index 101e4ab..cfba98e 100644 --- a/repo_helper.yml +++ b/repo_helper.yml @@ -1,32 +1,40 @@ # Configuration for 'repo_helper' (https://github.com/domdfcoding/repo_helper) --- modname: flake8-encodings -copyright_years: "2020-2021" +copyright_years: "2020-2022" author: "Dominic Davis-Foster" email: "dominic@davis-foster.co.uk" -version: "0.3.0" -username: "domdfcoding" +version: "0.5.1" +username: "python-formate" +assignee: "domdfcoding" +primary_conda_channel: "domdfcoding" license: 'MIT' short_desc: "A Flake8 plugin to identify incorrect use of encodings." -min_coverage: 100 +min_coverage: 94 use_whey: true -python_deploy_version: 3.6 +mypy_version: 1.16 +python_deploy_version: 3.9 docs_fail_on_warning: true -mypy_version: "0.812" +tox_testenv_extras: classes +sphinx_html_theme: furo conda_channels: - conda-forge # Versions to run tests for python_versions: - - '3.6' - - '3.7' - - '3.8' - - '3.9' - - 3.10-dev - - pypy36 - - pypy37 + 3.7: + 3.8: + 3.9: + "3.10": + 3.11: + 3.12: + 3.13: + experimental: true + pypy37: + pypy38: + pypy39: classifiers: - 'Development Status :: 4 - Beta' @@ -37,14 +45,12 @@ classifiers: extra_sphinx_extensions: - sphinx_toolbox.pre_commit - sphinx_toolbox.flake8 + - sphinx_favicon entry_points: flake8.extension: - ENC0=flake8_encodings:Plugin -sphinx_html_theme: furo -standalone_contrib_guide: true - keywords: - flake8 - encodings @@ -52,4 +58,17 @@ keywords: - unicode sphinx_conf_epilogue: + - needspace_amount = r"5\baselineskip" + - 'favicons = [{"rel": "icon", "href": "https://python-formate.github.io/assets/formate.ico", "sizes": "48x48", "type": "image/vnd.microsoft.icon"}]' - nitpicky = True + +extras_require: + classes: + - jedi>=0.18.0 + +tox_unmanaged: + - testenv + - testenv:py313 + +exclude_files: + - contributing diff --git a/requirements.txt b/requirements.txt index 9d1945b..f6dcd87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ astatine>=0.3.1 -domdf-python-tools>=1.8.0 -flake8>=3.7 +domdf-python-tools>=2.8.1 +flake8>=3.8.4 flake8-helper>=0.1.1 -jedi>=0.18.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 18c9c04..0000000 --- a/setup.cfg +++ /dev/null @@ -1,16 +0,0 @@ -# This file is managed by 'repo_helper'. -# You may add new sections, but any changes made to the following sections will be lost: -# * metadata -# * options -# * options.packages.find -# * mypy -# * options.entry_points - -[mypy] -python_version = 3.6 -namespace_packages = True -check_untyped_defs = True -warn_unused_ignores = True - -[options.entry_points] -flake8.extension = ENC0=flake8_encodings:Plugin diff --git a/tests/example_source.py b/tests/example_source.py index 4d9de64..1a07428 100644 --- a/tests/example_source.py +++ b/tests/example_source.py @@ -44,6 +44,9 @@ def paths_open(): pathlib.WindowsPath("README.md").open(encoding="UTF-8") pathlib.PosixPath("README.md").as_posix() + with pathlib.Path("foo.txt").open("w") as fp: + fp.write("Hello World") + def paths_read_text(): pathlib.Path("foo.txt").read_text() pathlib.Path("LICENSE").read_text(encoding=None) @@ -74,4 +77,7 @@ def paths_assigned(): else: pyproject_file.write_text("foo", encoding="UTF-8") +def paths_open_binary(): + with pathlib.Path("foo.txt").open("wb", encoding="UTF-8") as fp: + fp.write(b"Hello World") """ diff --git a/tests/requirements.txt b/tests/requirements.txt index 6a87f52..9f6f4ab 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,10 +1,11 @@ -coincidence>=0.1.0 +coincidence>=0.2.0 coverage>=5.1 coverage-pyver-pragma>=0.2.1 domdf-python-tools[testing]>=2.0.1 +importlib-metadata>=3.6.0 iniconfig!=1.1.0,>=1.0.1 pytest>=6.0.0 pytest-cov>=2.8.1 -pytest-randomly>=3.3.1 +pytest-randomly>=3.7.0 pytest-rerunfailures>=9.0 pytest-timeout>=1.4.2 diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 45a47f3..e940c0f 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -2,6 +2,7 @@ import ast # 3rd party +import pytest from coincidence.regressions import AdvancedDataRegressionFixture from domdf_python_tools.paths import PathPlus @@ -9,8 +10,28 @@ from flake8_encodings import Plugin from tests.example_source import example_source +try: + # 3rd party + import jedi # type: ignore[import-untyped] # noqa: F401 + has_jedi = True +except ImportError: + has_jedi = False -def test_plugin(tmp_pathplus: PathPlus, advanced_data_regression: AdvancedDataRegressionFixture): +skip_reason = "Output differs depending on jedi availability" + + +@pytest.mark.parametrize( + "has_jedi", + [ + pytest.param(True, id="has_jedi", marks=pytest.mark.skipif(not has_jedi, reason=skip_reason)), + pytest.param(False, id="no_jedi", marks=pytest.mark.skipif(has_jedi, reason=skip_reason)), + ] + ) +def test_plugin( + tmp_pathplus: PathPlus, + advanced_data_regression: AdvancedDataRegressionFixture, + has_jedi: bool, + ): (tmp_pathplus / "code.py").write_text(example_source) plugin = Plugin(ast.parse(example_source), filename=str(tmp_pathplus / "code.py")) diff --git a/tests/test_plugin_/test_plugin.yml b/tests/test_plugin_/test_plugin_has_jedi_.yml similarity index 54% rename from tests/test_plugin_/test_plugin.yml rename to tests/test_plugin_/test_plugin_has_jedi_.yml index 1193dbd..3724434 100644 --- a/tests/test_plugin_/test_plugin.yml +++ b/tests/test_plugin_/test_plugin_has_jedi_.yml @@ -9,12 +9,13 @@ - '38:1: ENC022 ''encoding=None'' used for ''pathlib.Path.open''.' - '42:1: ENC021 no encoding specified for ''pathlib.Path.open''.' - '43:1: ENC022 ''encoding=None'' used for ''pathlib.Path.open''.' -- '48:1: ENC023 no encoding specified for ''pathlib.Path.read_text''.' -- '49:1: ENC024 ''encoding=None'' used for ''pathlib.Path.read_text''.' -- '53:7: ENC023 no encoding specified for ''pathlib.Path.read_text''.' -- '54:1: ENC024 ''encoding=None'' used for ''pathlib.Path.read_text''.' -- '59:1: ENC025 no encoding specified for ''pathlib.Path.write_text''.' -- '60:1: ENC026 ''encoding=None'' used for ''pathlib.Path.write_text''.' -- '64:1: ENC025 no encoding specified for ''pathlib.Path.write_text''.' -- '65:1: ENC026 ''encoding=None'' used for ''pathlib.Path.write_text''.' -- '73:2: ENC023 no encoding specified for ''pathlib.Path.read_text''.' +- '47:6: ENC021 no encoding specified for ''pathlib.Path.open''.' +- '51:1: ENC023 no encoding specified for ''pathlib.Path.read_text''.' +- '52:1: ENC024 ''encoding=None'' used for ''pathlib.Path.read_text''.' +- '56:7: ENC023 no encoding specified for ''pathlib.Path.read_text''.' +- '57:1: ENC024 ''encoding=None'' used for ''pathlib.Path.read_text''.' +- '62:1: ENC025 no encoding specified for ''pathlib.Path.write_text''.' +- '63:1: ENC026 ''encoding=None'' used for ''pathlib.Path.write_text''.' +- '67:1: ENC025 no encoding specified for ''pathlib.Path.write_text''.' +- '68:1: ENC026 ''encoding=None'' used for ''pathlib.Path.write_text''.' +- '76:2: ENC023 no encoding specified for ''pathlib.Path.read_text''.' diff --git a/tests/test_plugin_/test_plugin_no_jedi_.yml b/tests/test_plugin_/test_plugin_no_jedi_.yml new file mode 100644 index 0000000..1852d01 --- /dev/null +++ b/tests/test_plugin_/test_plugin_no_jedi_.yml @@ -0,0 +1,5 @@ +- '6:9: ENC001 no encoding specified for ''open''.' +- '11:10: ENC001 no encoding specified for ''open''.' +- '12:10: ENC001 no encoding specified for ''open''.' +- '13:10: ENC002 ''encoding=None'' used for ''open''.' +- '23:11: ENC003 no encoding specified for ''open'' with unknown mode.' diff --git a/tests/test_visitor.py b/tests/test_visitor.py index 3975a58..3d21116 100644 --- a/tests/test_visitor.py +++ b/tests/test_visitor.py @@ -2,13 +2,21 @@ import ast # 3rd party +import pytest from coincidence.regressions import AdvancedDataRegressionFixture from domdf_python_tools.paths import PathPlus # this package -from flake8_encodings import Visitor +from flake8_encodings import ClassVisitor, Visitor from tests.example_source import example_source +try: + # 3rd party + import jedi # type: ignore[import-untyped] # noqa: F401 + has_jedi = True +except ImportError: + has_jedi = False + def test_visitor(advanced_data_regression: AdvancedDataRegressionFixture): visitor = Visitor() @@ -17,9 +25,34 @@ def test_visitor(advanced_data_regression: AdvancedDataRegressionFixture): def test_visitor_with_jedi(tmp_pathplus: PathPlus, advanced_data_regression: AdvancedDataRegressionFixture): - visitor = Visitor() + pytest.importorskip("jedi") + + visitor = ClassVisitor() (tmp_pathplus / "code.py").write_text(example_source) visitor.first_visit(ast.parse(example_source), filename=tmp_pathplus / "code.py") advanced_data_regression.check(visitor.errors) + + +def test_visitor_with_jedi_visit_method( + tmp_pathplus: PathPlus, advanced_data_regression: AdvancedDataRegressionFixture + ): + pytest.importorskip("jedi") + + visitor = ClassVisitor() + + (tmp_pathplus / "code.py").write_text(example_source) + + visitor.visit(ast.parse(example_source)) + advanced_data_regression.check(visitor.errors) + + +def test_classvisitor_importerror(): + if has_jedi: + pytest.skip(reason="Requires that jedi isn't installed") + + with pytest.raises( + ImportError, match="This class requires 'jedi' to be installed but it could not be imported." + ): + ClassVisitor() diff --git a/tests/test_visitor_/test_visitor_with_jedi.yml b/tests/test_visitor_/test_visitor_with_jedi.yml index 2ba49ef..323aedb 100644 --- a/tests/test_visitor_/test_visitor_with_jedi.yml +++ b/tests/test_visitor_/test_visitor_with_jedi.yml @@ -31,30 +31,33 @@ - - 43 - 1 - ENC022 'encoding=None' used for 'pathlib.Path.open'. -- - 48 +- - 47 + - 6 + - ENC021 no encoding specified for 'pathlib.Path.open'. +- - 51 - 1 - ENC023 no encoding specified for 'pathlib.Path.read_text'. -- - 49 +- - 52 - 1 - ENC024 'encoding=None' used for 'pathlib.Path.read_text'. -- - 53 +- - 56 - 7 - ENC023 no encoding specified for 'pathlib.Path.read_text'. -- - 54 +- - 57 - 1 - ENC024 'encoding=None' used for 'pathlib.Path.read_text'. -- - 59 +- - 62 - 1 - ENC025 no encoding specified for 'pathlib.Path.write_text'. -- - 60 +- - 63 - 1 - ENC026 'encoding=None' used for 'pathlib.Path.write_text'. -- - 64 +- - 67 - 1 - ENC025 no encoding specified for 'pathlib.Path.write_text'. -- - 65 +- - 68 - 1 - ENC026 'encoding=None' used for 'pathlib.Path.write_text'. -- - 73 +- - 76 - 2 - ENC023 no encoding specified for 'pathlib.Path.read_text'. diff --git a/tests/test_visitor_/test_visitor_with_jedi_visit_method.yml b/tests/test_visitor_/test_visitor_with_jedi_visit_method.yml new file mode 100644 index 0000000..e69b377 --- /dev/null +++ b/tests/test_visitor_/test_visitor_with_jedi_visit_method.yml @@ -0,0 +1,15 @@ +- - 6 + - 9 + - ENC001 no encoding specified for 'open'. +- - 11 + - 10 + - ENC001 no encoding specified for 'open'. +- - 12 + - 10 + - ENC001 no encoding specified for 'open'. +- - 13 + - 10 + - ENC002 'encoding=None' used for 'open'. +- - 23 + - 11 + - ENC003 no encoding specified for 'open' with unknown mode. diff --git a/tox.ini b/tox.ini index 7068ac4..cab0e44 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,14 @@ # You may add new sections, but any changes made to the following sections will be lost: # * tox # * envlists -# * testenv +# * testenv:.package +# * testenv:py313-dev +# * testenv:py312-dev +# * testenv:py312 # * testenv:docs # * testenv:build # * testenv:lint +# * testenv:perflint # * testenv:mypy # * testenv:pyup # * testenv:coverage @@ -16,95 +20,138 @@ # * pytest [tox] -envlist = py36, py37, py38, py39, py310-dev, pypy36, pypy37, mypy, build +envlist = + py37 + py38 + py39 + py310 + py311 + py312 + py313 + pypy37 + pypy38 + pypy39 + mypy + build skip_missing_interpreters = True isolated_build = True requires = - pip>=20.3.3 + pip>=21,!=22.2 tox-envlist>=0.2.1 - tox-pip-version>=0.0.7 + tox~=3.0 + virtualenv!=20.16.0 [envlists] -test = py36, py37, py38, py39, py310-dev, pypy36, pypy37 +test = py37, py38, py39, py310, py311, py312, py313, pypy37, pypy38, pypy39 qa = mypy, lint -cov = py36, coverage +cov = py39, coverage -[testenv] -setenv = PYTHONDEVMODE = 1 -deps = -r{toxinidir}/tests/requirements.txt -commands = - python --version - python -m pytest --cov=flake8_encodings -r aR tests/ {posargs} +[testenv:.package] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + +[testenv:py312] +download = True +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 [testenv:docs] setenv = SHOW_TODOS = 1 +passenv = SPHINX_BUILDER basepython = python3.8 -pip_version = pip>=21 changedir = {toxinidir}/doc-source +extras = classes deps = -r{toxinidir}/doc-source/requirements.txt -commands = sphinx-build -M html . ./build {posargs} +commands = sphinx-build -M {env:SPHINX_BUILDER:html} . ./build {posargs} [testenv:build] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + PIP_PREFER_BINARY=1 + UNSAFE_PYO3_SKIP_VERSION_CHECK=1 skip_install = True changedir = {toxinidir} deps = build[virtualenv]>=0.3.1 check-wheel-contents>=0.1.0 twine>=3.2.0 + cryptography<40; implementation_name == "pypy" and python_version <= "3.7" commands = python -m build --sdist --wheel "{toxinidir}" twine check dist/*.tar.gz dist/*.whl check-wheel-contents dist/ [testenv:lint] -basepython = python3.6 +basepython = python3.9 changedir = {toxinidir} ignore_errors = True skip_install = False deps = - flake8 >=3.8.2 - flake8-2020 >= 1.6.0 + flake8>=3.8.2,<5 + flake8-2020>=1.6.0 flake8-builtins>=1.5.3 flake8-docstrings>=1.5.0 flake8-dunder-all>=0.1.1 flake8-encodings>=0.1.0 flake8-github-actions>=0.1.0 - flake8-pyi>=20.10.0 - flake8-pytest-style>=1.3.0 + flake8-noqa>=1.1.0,<=1.2.2 + flake8-pyi>=20.10.0,<=22.8.0 + flake8-pytest-style>=1.3.0,<2 + flake8-quotes>=3.3.0 flake8-slots>=0.1.0 flake8-sphinx-links>=0.0.4 flake8-strftime>=0.1.1 flake8-typing-imports>=1.10.0 - git+https://github.com/domdfcoding/flake8-quotes.git git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git git+https://github.com/domdfcoding/flake8-rst-docstrings.git - pydocstyle>=6.0.0 + git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods + git+https://github.com/python-formate/flake8-missing-annotations.git + git+https://github.com/domdfcoding/pydocstyle.git@stub-functions pygments>=2.7.1 + importlib_metadata<4.5.0; python_version<'3.8' commands = python3 -m flake8_rst_docstrings_sphinx flake8_encodings tests --allow-toolbox {posargs} +[testenv:perflint] +basepython = python3.9 +changedir = {toxinidir} +ignore_errors = True +skip_install = True +deps = perflint +commands = python3 -m perflint flake8_encodings {posargs} + [testenv:mypy] -basepython = python3.6 +basepython = python3.9 ignore_errors = True changedir = {toxinidir} +extras = classes deps = - mypy==0.812 + mypy==1.16 -r{toxinidir}/tests/requirements.txt -r{toxinidir}/stubs.txt commands = mypy flake8_encodings tests {posargs} [testenv:pyup] -basepython = python3.6 +basepython = python3.9 skip_install = True ignore_errors = True changedir = {toxinidir} deps = pyupgrade-directories +extras = classes commands = pyup_dirs flake8_encodings tests --py36-plus --recursive [testenv:coverage] -basepython = python3.6 +basepython = python3.9 skip_install = True ignore_errors = True whitelist_externals = /bin/bash +passenv = + COV_PYTHON_VERSION + COV_PLATFORM + COV_PYTHON_IMPLEMENTATION + * changedir = {toxinidir} deps = coverage>=5 @@ -116,12 +163,15 @@ commands = [flake8] max-line-length = 120 -select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT005 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 A003 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 +select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 ENC011 ENC012 ENC021 ENC022 ENC023 ENC024 ENC025 ENC026 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 NQA001 NQA002 NQA003 NQA004 NQA005 NQA102 NQA103 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 extend-exclude = doc-source,old,build,dist,__pkginfo__.py,setup.py,venv rst-directives = TODO envvar extras-require + license + license-info +rst-roles = choosealicense per-file-ignores = tests/*: D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 */*.pyi: E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 @@ -130,20 +180,25 @@ inline-quotes = " multiline-quotes = """ docstring-quotes = """ count = True -min_python_version = 3.6.1 +min_python_version = 3.7 +unused-arguments-ignore-abstract-functions = True +unused-arguments-ignore-overload-functions = True +unused-arguments-ignore-magic-methods = True +unused-arguments-ignore-variadic-names = True [coverage:run] plugins = coverage_pyver_pragma [coverage:report] -fail_under = 100 +fail_under = 94 +show_missing = True exclude_lines = raise AssertionError raise NotImplementedError if 0: if False: - if TYPE_CHECKING: - if typing.TYPE_CHECKING: + if TYPE_CHECKING + if typing.TYPE_CHECKING if __name__ == .__main__.: [check-wheel-contents] @@ -154,6 +209,35 @@ package = flake8_encodings [pytest] addopts = --color yes --durations 25 timeout = 300 +filterwarnings = + error + ignore:can't resolve package from __spec__ or __package__, falling back on __name__ and __path__:ImportWarning + +[testenv:py313] +download = True +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + UNSAFE_PYO3_SKIP_VERSION_CHECK=1 +commands = + python --version + python -m pip uninstall jedi -y + python -m pytest --cov=flake8_encodings -r aR tests/ --cov-append {posargs} + +[testenv] +setenv = + PYTHONDEVMODE=1 + PIP_DISABLE_PIP_VERSION_CHECK=1 + SETUPTOOLS_USE_DISTUTILS=stdlib +deps = -r{toxinidir}/tests/requirements.txt +ignore_errors = True +commands = + python --version + python -m pip install jedi + python -m pytest --cov=flake8_encodings -r aR tests/ {posargs} + python -m pip uninstall jedi -y + python -m pytest --cov=flake8_encodings -r aR tests/ --cov-append {posargs} + [dep_checker] allowed_unused = flake8