diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6001e213..b460192b 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -18,7 +18,3 @@ replace = : str = "{new_version}" 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/.github/workflows/conda_ci.yml b/.github/workflows/conda_ci.yml deleted file mode 100644 index 57ba7b59..00000000 --- a/.github/workflows/conda_ci.yml +++ /dev/null @@ -1,68 +0,0 @@ -# This file is managed by 'repo_helper'. Don't edit it directly. ---- -name: Conda Tests - -on: - push: - branches: ["master"] - -permissions: - contents: read - -jobs: - tests: - name: "Conda" - runs-on: ubuntu-22.04 - defaults: - run: - shell: bash -l {0} - - steps: - - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" - - - name: Setup Python 🐍 - uses: "actions/setup-python@v5" - with: - 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 "whey-conda" "hatch-requirements-txt" - # $CONDA is an environment variable pointing to the root of the miniconda directory - $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 index channel" - run: | - python -m whey --builder whey_conda --out-dir conda-bld/noarch - $CONDA/bin/conda index ./conda-bld || exit 1 - - - name: "Search for package" - run: | - $CONDA/bin/conda search -c file://$(pwd)/conda-bld domdf_python_tools - $CONDA/bin/conda search -c file://$(pwd)/conda-bld --override-channels domdf_python_tools - - - name: "Install package" - run: | - $CONDA/bin/conda install -c file://$(pwd)/conda-bld domdf_python_tools=3.10.0=py_1 -y || exit 1 - - - name: "Run Tests" - run: | - rm -rf domdf_python_tools - $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 8f60ba5a..f47c729d 100644 --- a/.github/workflows/docs_test_action.yml +++ b/.github/workflows/docs_test_action.yml @@ -9,18 +9,17 @@ on: - 'imgbot' pull_request: -permissions: - contents: read - jobs: docs: + permissions: + contents: read runs-on: ubuntu-latest steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -29,7 +28,7 @@ jobs: - '!tests/**' - name: Install and Build πŸ”§ - uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1 + uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1-py39 if: steps.changes.outputs.code == 'true' with: pre-build-command: python -m pip install tox diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 0a8c0c3e..1f5be866 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -10,20 +10,19 @@ on: - 'imgbot' pull_request: -permissions: - contents: read - jobs: Run: name: "Flake8" - runs-on: "ubuntu-20.04" + permissions: + contents: read + runs-on: "ubuntu-22.04" steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -33,9 +32,9 @@ jobs: - name: Setup Python 🐍 if: steps.changes.outputs.code == 'true' - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: - python-version: "3.8" + python-version: "3.9" - name: Install dependencies πŸ”§ if: steps.changes.outputs.code == 'true' diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 10c6f300..2b5e4f86 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -10,25 +10,24 @@ on: - 'imgbot' pull_request: -permissions: - contents: read - jobs: Run: + permissions: + contents: read name: "mypy / ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: matrix: - os: ['ubuntu-20.04', 'windows-2019'] + os: ['ubuntu-22.04', 'windows-2022'] fail-fast: false steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -38,9 +37,9 @@ jobs: - name: Setup Python 🐍 if: steps.changes.outputs.code == 'true' - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: - python-version: "3.8" + python-version: "3.9" - name: Install dependencies πŸ”§ run: | diff --git a/.github/workflows/octocheese.yml b/.github/workflows/octocheese.yml index fd77cd01..3bc6e0c2 100644 --- a/.github/workflows/octocheese.yml +++ b/.github/workflows/octocheese.yml @@ -8,11 +8,13 @@ on: jobs: Run: + permissions: + contents: write runs-on: ubuntu-latest steps: - uses: domdfcoding/octocheese@master with: - pypi_name: "domdf_python_tools" + pypi_name: "domdf-python-tools" env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} if: startsWith(github.ref, 'refs/tags/') != true diff --git a/.github/workflows/python_ci.yml b/.github/workflows/python_ci.yml index 4505aef6..de523821 100644 --- a/.github/workflows/python_ci.yml +++ b/.github/workflows/python_ci.yml @@ -11,44 +11,41 @@ on: pull_request: -permissions: - actions: write - issues: write - contents: read - jobs: tests: - name: "windows-2019 / Python ${{ matrix.config.python-version }}" - runs-on: "windows-2019" + permissions: + actions: write + contents: read + 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,3.11,3.12,3.13,pypy-3.6,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' 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", 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-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: False} + - {python-version: "3.14", testenvs: "py314,build", experimental: False} - {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} - - {python-version: "pypy-3.10-v7.3.15", testenvs: "pypy310,build", experimental: True} + - {python-version: "pypy-3.8", testenvs: "pypy38", experimental: False} + - {python-version: "pypy-3.9-v7.3.16", testenvs: "pypy39", experimental: True} + - {python-version: "pypy-3.10-v7.3.19", testenvs: "pypy310", experimental: True} steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -59,7 +56,7 @@ jobs: - name: Setup Python 🐍 id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: python-version: "${{ matrix.config.python-version }}" @@ -81,3 +78,4 @@ jobs: with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.github/workflows/python_ci_alt_linux.yml b/.github/workflows/python_ci_alt_linux.yml index 6aa867fd..8401ea3d 100644 --- a/.github/workflows/python_ci_alt_linux.yml +++ b/.github/workflows/python_ci_alt_linux.yml @@ -20,7 +20,7 @@ permissions: jobs: tests: name: "alt-linux / Python ${{ matrix.config.python-version }}" - runs-on: "ubuntu-20.04" + runs-on: "ubuntu-22.04" container: image: ghcr.io/domdfcoding/alt-linux-python:latest continue-on-error: ${{ matrix.config.experimental }} diff --git a/.github/workflows/python_ci_linux.yml b/.github/workflows/python_ci_linux.yml index de8bd506..a0a591d5 100644 --- a/.github/workflows/python_ci_linux.yml +++ b/.github/workflows/python_ci_linux.yml @@ -12,44 +12,41 @@ on: - '*' 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" + permissions: + actions: write + contents: read + 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,3.11,3.12,3.13,pypy-3.6,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' 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", 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-dev,build", experimental: True} - - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} + - {python-version: "3.13", testenvs: "py313,build", experimental: False} + - {python-version: "3.14", testenvs: "py314,build", experimental: False} - {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} + - {python-version: "pypy-3.9", testenvs: "pypy39", experimental: True} - {python-version: "pypy-3.10", testenvs: "pypy310,build", experimental: True} steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -60,7 +57,7 @@ jobs: - name: Setup Python 🐍 id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: python-version: "${{ matrix.config.python-version }}" @@ -83,17 +80,21 @@ jobs: with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true Coverage: needs: tests - runs-on: "ubuntu-20.04" + permissions: + actions: write + contents: read + runs-on: "ubuntu-22.04" steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Setup Python 🐍 - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: python-version: 3.8 @@ -125,6 +126,7 @@ jobs: with: name: "combined-coverage" path: .coverage + include-hidden-files: true - name: "Upload Combined Coverage to Coveralls" if: ${{ steps.show.outcome != 'failure' }} @@ -135,15 +137,18 @@ jobs: Deploy: needs: tests - - runs-on: "ubuntu-20.04" + permissions: + actions: write + issues: write + contents: read + runs-on: "ubuntu-22.04" steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" if: startsWith(github.ref, 'refs/tags/') - name: Setup Python 🐍 - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" if: startsWith(github.ref, 'refs/tags/') with: python-version: 3.8 @@ -162,11 +167,11 @@ jobs: - name: Upload distribution to PyPI πŸš€ if: startsWith(github.ref, 'refs/tags/') - uses: pypa/gh-action-pypi-publish@v1.4.2 + uses: pypa/gh-action-pypi-publish@v1.13.0 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} - skip_existing: true + skip-existing: true - name: Close milestone πŸšͺ if: startsWith(github.ref, 'refs/tags/') @@ -175,65 +180,3 @@ jobs: python .github/milestones.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - - Conda: - needs: deploy - 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@v4" - - - name: Setup Python 🐍 - uses: "actions/setup-python@v5" - with: - 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 "mkrecipe" "hatch-requirements-txt" - # $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 config --add channels conda-forge - $CONDA/bin/conda config --add channels domdfcoding - - $CONDA/bin/conda config --remove channels defaults - - - name: Build Conda Package πŸ“¦ - run: | - python -m mkrecipe --type wheel || exit 1 - $CONDA/bin/conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist - - - name: Deploy Conda Package πŸš€ - if: startsWith(github.ref, 'refs/tags/') - run: | - $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/domdf_python_tools-*.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 d008b4ea..a6b734da 100644 --- a/.github/workflows/python_ci_macos.yml +++ b/.github/workflows/python_ci_macos.yml @@ -11,43 +11,41 @@ on: pull_request: -permissions: - actions: write - issues: write - contents: read - jobs: tests: - name: "macos-13 / Python ${{ matrix.config.python-version }}" - runs-on: "macos-13" + permissions: + actions: write + contents: read + 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,3.11,3.12,3.13,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' + USING_COVERAGE: '3.7,3.8,3.9,3.10,3.11,3.12,3.13,3.14,pypy-3.7,pypy-3.8,pypy-3.9,pypy-3.10' 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", 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-dev,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} - - {python-version: "pypy-3.10", testenvs: "pypy310,build", experimental: True} + - {python-version: "3.7", os-ver: "15-intel", 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: False} + - {python-version: "3.14", os-ver: "14", testenvs: "py314,build", experimental: False} + - {python-version: "pypy-3.7", os-ver: "15-intel", 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", experimental: True} + - {python-version: "pypy-3.10", os-ver: "14", testenvs: "pypy310,build", experimental: True} steps: - name: Checkout πŸ›ŽοΈ - uses: "actions/checkout@v4" + uses: "actions/checkout@v6" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true - uses: dorny/paths-filter@v2 + uses: dorny/paths-filter@v4 id: changes with: list-files: "json" @@ -58,7 +56,7 @@ jobs: - name: Setup Python 🐍 id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} - uses: "actions/setup-python@v5" + uses: "actions/setup-python@v6" with: python-version: "${{ matrix.config.python-version }}" @@ -80,3 +78,4 @@ jobs: with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage + include-hidden-files: true diff --git a/.gitignore b/.gitignore index b3401223..ce4c4d12 100644 --- a/.gitignore +++ b/.gitignore @@ -47,7 +47,6 @@ docs/_build/ doc/build target/ .ipynb_checkpoints -.python-version celerybeat-schedule celerybeat.pid *.sage.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4912f4d..bf00d0b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,10 +5,11 @@ exclude: ^domdf_python_tools/compat/importlib_resources.py$ ci: autoupdate_schedule: quarterly + skip: [taplo-lint] repos: - repo: https://github.com/repo-helper/pyproject-parser - rev: v0.11.1 + rev: v0.14.0 hooks: - id: reformat-pyproject @@ -18,11 +19,9 @@ repos: - id: check-added-large-files - id: check-ast - id: fix-byte-order-marker - - id: check-byte-order-marker - id: check-case-conflict - id: check-executables-have-shebangs - id: check-json - - id: check-toml - id: check-yaml - id: check-merge-conflict - id: check-symlinks @@ -32,8 +31,14 @@ repos: - id: mixed-line-ending - id: end-of-file-fixer + - repo: https://github.com/domdfcoding/taplo-pre-commit + rev: v0.10.0 + hooks: + - id: taplo-lint + args: [] + - repo: https://github.com/domdfcoding/pre-commit-hooks - rev: v0.4.0 + rev: v0.7.0 hooks: - id: requirements-txt-sorter args: @@ -41,9 +46,12 @@ repos: - id: check-docstring-first exclude: ^(doc-source/conf|__pkginfo__|setup|tests/.*)\.py$ - id: bind-requirements + args: + - --python-min + - '3.7' - repo: https://github.com/python-formate/flake8-dunder-all - rev: v0.4.1 + rev: v0.5.0 hooks: - id: ensure-dunder-all files: ^domdf_python_tools/.*\.py$ @@ -61,12 +69,12 @@ repos: - id: rst-directive-colons - id: rst-inline-touching-normal - - repo: https://github.com/asottile/pyupgrade - rev: v2.12.0 + - repo: https://github.com/python-formate/pyupgrade + rev: bbe3007 hooks: - id: pyupgrade args: - - --py36-plus + - --py37-plus - --keep-runtime-typing - repo: https://github.com/Lucas-C/pre-commit-hooks @@ -76,18 +84,25 @@ repos: - id: forbid-crlf - repo: https://github.com/python-formate/snippet-fmt - rev: v0.1.5 + rev: v0.3.1 hooks: - id: snippet-fmt + additional_dependencies: + - formate-trailing-commas>=0.1.1 - repo: https://github.com/python-formate/formate - rev: v0.8.0 + rev: v1.2.1 hooks: - id: formate exclude: ^(doc-source/conf|__pkginfo__|setup)\.(_)?py$ + additional_dependencies: + - formate-trailing-commas>=0.1.1 + types_or: + - python + - pyi - repo: https://github.com/python-coincidence/dep_checker - rev: v0.8.0 + rev: v0.9.0 hooks: - id: dep_checker args: diff --git a/.readthedocs.yml b/.readthedocs.yml index 35b5c858..e73aa4ab 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,7 +13,7 @@ python: - requirements: requirements.txt - requirements: doc-source/requirements.txt build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: python: '3.9' jobs: diff --git a/.style.yapf b/.style.yapf index e64be7cb..9378f04c 100644 --- a/.style.yapf +++ b/.style.yapf @@ -312,7 +312,7 @@ split_before_dict_set_generator=True # # foo = ('This is a really long string: {}, {}, {}, {}' # .format(a, b, c, d)) -split_before_dot=False +split_before_dot=True # Split after the opening paren which surrounds an expression if it doesn't # fit on a single line. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 4c3b5a2c..35c62f8f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -38,11 +38,11 @@ Automated tests ------------------- Tests are run with ``tox`` and ``pytest``. -To run tests for a specific Python version, such as Python 3.6: +To run tests for a specific Python version, such as Python 3.10: .. code-block:: bash - $ tox -e py36 + $ tox -e py310 To run tests for all Python versions, simply run: diff --git a/README.rst b/README.rst index 641fd161..a5037a49 100644 --- a/README.rst +++ b/README.rst @@ -69,28 +69,28 @@ domdf_python_tools :target: https://www.codefactor.io/repository/github/domdfcoding/domdf_python_tools :alt: CodeFactor Grade -.. |pypi-version| image:: https://img.shields.io/pypi/v/domdf_python_tools - :target: https://pypi.org/project/domdf_python_tools/ +.. |pypi-version| image:: https://img.shields.io/pypi/v/domdf-python-tools + :target: https://pypi.org/project/domdf-python-tools/ :alt: PyPI - Package Version -.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/domdf_python_tools?logo=python&logoColor=white - :target: https://pypi.org/project/domdf_python_tools/ +.. |supported-versions| image:: https://img.shields.io/pypi/pyversions/domdf-python-tools?logo=python&logoColor=white + :target: https://pypi.org/project/domdf-python-tools/ :alt: PyPI - Supported Python Versions -.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/domdf_python_tools - :target: https://pypi.org/project/domdf_python_tools/ +.. |supported-implementations| image:: https://img.shields.io/pypi/implementation/domdf-python-tools + :target: https://pypi.org/project/domdf-python-tools/ :alt: PyPI - Supported Implementations -.. |wheel| image:: https://img.shields.io/pypi/wheel/domdf_python_tools - :target: https://pypi.org/project/domdf_python_tools/ +.. |wheel| image:: https://img.shields.io/pypi/wheel/domdf-python-tools + :target: https://pypi.org/project/domdf-python-tools/ :alt: PyPI - Wheel -.. |conda-version| image:: https://img.shields.io/conda/v/domdfcoding/domdf_python_tools?logo=anaconda - :target: https://anaconda.org/domdfcoding/domdf_python_tools +.. |conda-version| image:: https://img.shields.io/conda/v/conda-forge/domdf-python-tools?logo=anaconda + :target: https://anaconda.org/conda-forge/domdf-python-tools :alt: Conda - Package Version -.. |conda-platform| image:: https://img.shields.io/conda/pn/domdfcoding/domdf_python_tools?label=conda%7Cplatform - :target: https://anaconda.org/domdfcoding/domdf_python_tools +.. |conda-platform| image:: https://img.shields.io/conda/pn/conda-forge/domdf-python-tools?label=conda%7Cplatform + :target: https://anaconda.org/conda-forge/domdf-python-tools :alt: Conda - Platform .. |license| image:: https://img.shields.io/github/license/domdfcoding/domdf_python_tools @@ -108,11 +108,11 @@ domdf_python_tools :target: https://github.com/domdfcoding/domdf_python_tools/commit/master :alt: GitHub last commit -.. |maintained| image:: https://img.shields.io/maintenance/yes/2025 +.. |maintained| image:: https://img.shields.io/maintenance/yes/2026 :alt: Maintenance -.. |pypi-downloads| image:: https://img.shields.io/pypi/dm/domdf_python_tools - :target: https://pypi.org/project/domdf_python_tools/ +.. |pypi-downloads| image:: https://img.shields.io/pypi/dm/domdf-python-tools + :target: https://pypistats.org/packages/domdf-python-tools :alt: PyPI - Downloads .. end shields @@ -130,21 +130,12 @@ To install with ``pip``: .. code-block:: bash - $ python -m pip install domdf_python_tools + $ python -m pip install domdf-python-tools To install with ``conda``: - * First add the required channels - - .. code-block:: bash - - $ conda config --add channels https://conda.anaconda.org/conda-forge - $ conda config --add channels https://conda.anaconda.org/domdfcoding - - * Then install - - .. code-block:: bash +.. code-block:: bash - $ conda install domdf_python_tools + $ conda install -c conda-forge domdf-python-tools .. end installation diff --git a/doc-source/api/bases.rst b/doc-source/api/bases.rst index 2669e005..6c77ecc7 100644 --- a/doc-source/api/bases.rst +++ b/doc-source/api/bases.rst @@ -63,7 +63,7 @@ from :class:`NamedList` rather than from :func:`~.namedlist`. For example, do th .. code-block:: python >>> class ShoppingList(NamedList): - ... pass + ... pass >>> and not this: diff --git a/doc-source/contributing.rst b/doc-source/contributing.rst index f155af05..d3e8a02b 100644 --- a/doc-source/contributing.rst +++ b/doc-source/contributing.rst @@ -37,11 +37,11 @@ Automated tests ------------------- Tests are run with ``tox`` and ``pytest``. -To run tests for a specific Python version, such as Python 3.6: +To run tests for a specific Python version, such as Python 3.10: .. prompt:: bash - tox -e py36 + tox -e py310 To run tests for all Python versions, simply run: diff --git a/doc-source/index.rst b/doc-source/index.rst index 12f8540e..772bb33a 100644 --- a/doc-source/index.rst +++ b/doc-source/index.rst @@ -72,31 +72,31 @@ domdf_python_tools :alt: CodeFactor Grade .. |pypi-version| pypi-shield:: - :project: domdf_python_tools + :project: domdf-python-tools :version: :alt: PyPI - Package Version .. |supported-versions| pypi-shield:: - :project: domdf_python_tools + :project: domdf-python-tools :py-versions: :alt: PyPI - Supported Python Versions .. |supported-implementations| pypi-shield:: - :project: domdf_python_tools + :project: domdf-python-tools :implementations: :alt: PyPI - Supported Implementations .. |wheel| pypi-shield:: - :project: domdf_python_tools + :project: domdf-python-tools :wheel: :alt: PyPI - Wheel - .. |conda-version| image:: https://img.shields.io/conda/v/domdfcoding/domdf_python_tools?logo=anaconda - :target: https://anaconda.org/domdfcoding/domdf_python_tools + .. |conda-version| image:: https://img.shields.io/conda/v/conda-forge/domdf-python-tools?logo=anaconda + :target: https://anaconda.org/conda-forge/domdf-python-tools :alt: Conda - Package Version - .. |conda-platform| image:: https://img.shields.io/conda/pn/domdfcoding/domdf_python_tools?label=conda%7Cplatform - :target: https://anaconda.org/domdfcoding/domdf_python_tools + .. |conda-platform| image:: https://img.shields.io/conda/pn/conda-forge/domdf-python-tools?label=conda%7Cplatform + :target: https://anaconda.org/conda-forge/domdf-python-tools :alt: Conda - Platform .. |license| github-shield:: @@ -115,11 +115,11 @@ domdf_python_tools :last-commit: :alt: GitHub last commit - .. |maintained| maintained-shield:: 2025 + .. |maintained| maintained-shield:: 2026 :alt: Maintenance .. |pypi-downloads| pypi-shield:: - :project: domdf_python_tools + :project: domdf-python-tools :downloads: month :alt: PyPI - Downloads @@ -140,9 +140,11 @@ Installation .. installation:: domdf_python_tools :pypi: + :pypi-name: domdf-python-tools :github: :anaconda: - :conda-channels: conda-forge, domdfcoding + :conda-name: domdf-python-tools + :conda-channels: conda-forge .. end installation diff --git a/doc-source/license.rst b/doc-source/license.rst index 42e3d409..bc0b0dce 100644 --- a/doc-source/license.rst +++ b/doc-source/license.rst @@ -7,4 +7,4 @@ License .. license-info:: MIT .. license:: - :py: domdf_python_tools + :py: domdf-python-tools diff --git a/doc-source/requirements.txt b/doc-source/requirements.txt index 254a6964..cae1d8b5 100644 --- a/doc-source/requirements.txt +++ b/doc-source/requirements.txt @@ -1,4 +1,5 @@ default-values>=0.6.0 +docutils<0.22 domdf-sphinx-theme>=0.3.0 extras-require>=0.5.0 html-section>=0.3.0 @@ -6,7 +7,9 @@ pandas>=1.1.2 pytest>=6.2.0 pytest-regressions>=2.0.2 pytz>=2019.1 +roman>=4.0 seed-intersphinx-mapping>=1.2.2 +setuptools<81 sphinx>=3.0.3 sphinx-autofixture>=0.2.1 sphinx-copybutton>=0.2.12 diff --git a/domdf_python_tools/bases.py b/domdf_python_tools/bases.py index d25b380e..cf59ebff 100644 --- a/domdf_python_tools/bases.py +++ b/domdf_python_tools/bases.py @@ -80,7 +80,7 @@ @prettify_docstrings -class Dictable(Iterable[Tuple[str, _V]]): +class Dictable(Iterable[Tuple[str, _V]]): # noqa: PRM002 """ The basic structure of a class that can be converted into a dictionary. """ @@ -116,10 +116,10 @@ def __deepcopy__(self, memodict={}): @property @abstractmethod - def __dict__(self): + def __dict__(self): # type: ignore[override] return dict() # pragma: no cover (abc) - def __eq__(self, other) -> bool: + def __eq__(self, other) -> bool: # noqa: MAN001 if isinstance(other, self.__class__): return is_match_with(other.__dict__, self.__dict__) @@ -266,6 +266,8 @@ def __copy__(self): def append(self, item: _T) -> None: """ Append ``item`` to the end of the :class:`~.domdf_python_tools.bases.UserList`. + + :param item: """ self.data.append(item) @@ -273,6 +275,9 @@ def append(self, item: _T) -> None: def insert(self, i: int, item: _T) -> None: """ Insert ``item`` at position ``i`` in the :class:`~.domdf_python_tools.bases.UserList`. + + :param i: + :param item: """ self.data.insert(i, item) @@ -281,6 +286,8 @@ def pop(self, i: int = -1) -> _T: """ Removes and returns the item at index ``i``. + :param i: + :raises IndexError: if list is empty or index is out of range. """ @@ -315,7 +322,7 @@ def copy(self: _S) -> _S: return self.__class__(self) - def count(self, item: _T) -> int: + def count(self, item: _T) -> int: # noqa: PRM002 """ Returns the number of occurrences of ``item`` in the :class:`~.domdf_python_tools.bases.UserList`. """ @@ -323,11 +330,11 @@ def count(self, item: _T) -> int: return self.data.count(item) def index(self, item: _T, *args: Any) -> int: - """ + r""" Returns the index of the fist element matching ``item``. :param item: - :param args: + :param \*args: :raises ValueError: if the item is not present. """ @@ -341,7 +348,7 @@ def reverse(self) -> None: self.data.reverse() - def sort(self, *, key=None, reverse: bool = False) -> None: + def sort(self, *, key=None, reverse: bool = False) -> None: # noqa: PRM002 """ Sort the list in ascending order and return :py:obj:`None`. @@ -422,7 +429,7 @@ def __sub__(self: _F, other: float) -> _F: def __mul__(self: _F, other: float) -> _F: return self.__class__(float(self).__mul__(other)) - def __floordiv__(self: _F, other: float) -> _F: # type: ignore[override] + def __floordiv__(self: _F, other: float) -> _F: return self.__class__(float(self).__floordiv__(other)) def __truediv__(self: _F, other: float) -> _F: @@ -446,7 +453,7 @@ def __rsub__(self: _F, other: float) -> _F: def __rmul__(self: _F, other: float) -> _F: return self.__class__(float(self).__rmul__(other)) - def __rfloordiv__(self: _F, other: float) -> _F: # type: ignore[override] + def __rfloordiv__(self: _F, other: float) -> _F: return self.__class__(float(self).__rfloordiv__(other)) def __rtruediv__(self: _F, other: float) -> _F: @@ -456,7 +463,7 @@ def __rmod__(self: _F, other: float) -> _F: return self.__class__(float(self).__rmod__(other)) def __rdivmod__(self: _F, other: float) -> Tuple[_F, _F]: - return tuple(self.__class__(x) for x in float(self).__rdivmod__(other)) # type: ignore + return tuple(self.__class__(x) for x in float(self).__rdivmod__(other)) # type: ignore[return-value] def __rpow__(self: _F, other: float, mod=None) -> _F: return self.__class__(float(self).__rpow__(other, mod)) @@ -471,7 +478,13 @@ def __trunc__(self) -> int: return float(self).__trunc__() - def __round__(self, ndigits: Optional[int] = None) -> Union[int, float]: # type: ignore + @overload + def __round__(self, ndigits: None = ...) -> int: ... + + @overload + def __round__(self, ndigits: int) -> float: ... + + def __round__(self, ndigits: Optional[int] = None) -> Union[int, float]: """ Round the :class:`~.UserFloat` to ``ndigits`` decimal places, defaulting to ``0``. @@ -538,7 +551,7 @@ def __float__(self) -> float: def __abs__(self: _F) -> _F: return self.__class__(float(self).__abs__()) - def __hash__(self) -> int: + def __hash__(self) -> int: # type: ignore[override] return float(self).__hash__() def __repr__(self) -> str: @@ -622,7 +635,7 @@ def replace(self: _LU, what: _T, with_: _T) -> _LU: return self - def sort( # type: ignore + def sort( # type: ignore[override] # noqa: PRM002 self: _LU, *, key=None, @@ -643,25 +656,25 @@ def sort( # type: ignore super().sort(key=key, reverse=reverse) return self - def reverse(self: _LU) -> _LU: # type: ignore # noqa: D102 + def reverse(self: _LU) -> _LU: # type: ignore[override] # noqa: D102 super().reverse() return self - def append( # type: ignore # noqa: D102 + def append( # type: ignore[override] # noqa: D102 self: _LU, item: _T, ) -> _LU: super().append(item) return self - def extend( # type: ignore # noqa: D102 + def extend( # type: ignore[override] # noqa: D102 self: _LU, other: Iterable[_T], ) -> _LU: super().extend(other) return self - def insert( # type: ignore # noqa: D102 + def insert( # type: ignore[override] # noqa: D102 self: _LU, i: int, item: _T, @@ -669,13 +682,13 @@ def insert( # type: ignore # noqa: D102 super().insert(i, item) return self - def remove( # type: ignore # noqa: D102 + def remove( # type: ignore[override] # noqa: D102 self: _LU, item: _T, ) -> _LU: super().remove(item) return self - def clear(self: _LU) -> _LU: # type: ignore # noqa: D102 + def clear(self: _LU) -> _LU: # type: ignore[override] # noqa: D102 super().clear() return self diff --git a/domdf_python_tools/dates.py b/domdf_python_tools/dates.py index 22dc0833..71575b4a 100644 --- a/domdf_python_tools/dates.py +++ b/domdf_python_tools/dates.py @@ -147,7 +147,7 @@ def utc_timestamp_to_datetime( if sys.version_info <= (3, 7, 2): # pragma: no cover (py37+) MonthsType = OrderedDict else: # pragma: no cover ( str: month = int(month) except ValueError: try: - return months[month.capitalize()[:3]] # type: ignore + assert not isinstance(month, int) + return months[month.capitalize()[:3]] except KeyError: raise ValueError(error_text) @@ -382,7 +383,7 @@ def is_bst(the_date: Union[time.struct_time, datetime.date]) -> bool: '{word_join(_pytz_functions)}' require pytz (https://pypi.org/project/pytz/), but it could not be imported. The error was: {e}. - """ + """, ) else: @@ -393,7 +394,7 @@ class SelfWrapper(ModuleType): def __getattr__(self, name): if name in _pytz_functions: raise ImportError( - f"{name!r} requires pytz (https://pypi.org/project/pytz/), but it could not be imported." + f"{name!r} requires pytz (https://pypi.org/project/pytz/), but it could not be imported.", ) else: return getattr(_actual_module, name) diff --git a/domdf_python_tools/delegators.py b/domdf_python_tools/delegators.py index 3244bc56..d8fd724f 100644 --- a/domdf_python_tools/delegators.py +++ b/domdf_python_tools/delegators.py @@ -82,9 +82,9 @@ def _f(f: _C) -> _C: if param in to_params: del to_params[param] - f.__signature__ = from_sig.replace( # type: ignore - parameters=[*from_params.values(), *to_params.values()] - ) + f.__signature__ = from_sig.replace( # type: ignore[attr-defined] + parameters=[*from_params.values(), *to_params.values()], + ) f.__annotations__ = {**to_annotations, **from_annotations} return f @@ -116,13 +116,13 @@ def _f(f: _C) -> _C: from_params = dict(from_sig.parameters) if tuple(from_params.keys()) == ("args", "kwargs"): - f.__signature__ = to_sig # type: ignore + f.__signature__ = to_sig # type: ignore[attr-defined] copy_annotations(f) elif tuple(from_params.keys()) == ("self", "args", "kwargs"): - f.__signature__ = from_sig.replace( # type: ignore - parameters=[from_params["self"], *to_sig.parameters.values()] - ) + f.__signature__ = from_sig.replace( # type: ignore[attr-defined] + parameters=[from_params["self"], *to_sig.parameters.values()], + ) copy_annotations(f) diff --git a/domdf_python_tools/doctools.py b/domdf_python_tools/doctools.py index 6ae998cc..048523fc 100644 --- a/domdf_python_tools/doctools.py +++ b/domdf_python_tools/doctools.py @@ -216,8 +216,8 @@ def sphinxify_docstring() -> Callable[[_F], _F]: .. code-block:: python intersphinx_mapping = { - "python": ("https://docs.python.org/3/", None), - } + "python": ("https://docs.python.org/3/", None), + } """ def wrapper(target: _F) -> _F: diff --git a/domdf_python_tools/getters.py b/domdf_python_tools/getters.py index 32f9884b..f77ae072 100644 --- a/domdf_python_tools/getters.py +++ b/domdf_python_tools/getters.py @@ -55,9 +55,9 @@ class attrgetter: .. code-block:: python >>> from pathlib import Path - >>> attrgetter(0, 'name')([Path("dir/code.py")]) + >>> attrgetter(0, "name")([Path("dir/code.py")]) 'code.py' - >>> attrgetter(2, 'parent.name')([Path("dir/coincidence.py"), Path("dir/wheel.py"), Path("dir/operator.py")]) + >>> attrgetter(2, "parent.name")([Path("dir/coincidence.py"), Path("dir/wheel.py"), Path("dir/operator.py")]) 'dir' .. seealso:: :func:`operator.attrgetter` and :func:`operator.itemgetter` @@ -110,14 +110,14 @@ class itemgetter: .. code-block:: python - >>> itemgetter(0, 1)(['ABCDEFG']) + >>> itemgetter(0, 1)(["ABCDEFG"]) 'B' - >>> itemgetter(1, 2)(['ABC', 'DEF']) + >>> itemgetter(1, 2)(["ABC", "DEF"]) 'F' - >>> itemgetter(0, slice(2, None))(['ABCDEFG']) + >>> itemgetter(0, slice(2, None))(["ABCDEFG"]) 'CDEFG' - >>> army = [dict(rank='captain', name='Blackadder'), dict(rank='Private', name='Baldrick')] - >>> itemgetter(0, 'rank')(army) + >>> army = [dict(rank="captain", name="Blackadder"), dict(rank="Private", name="Baldrick")] + >>> itemgetter(0, "rank")(army) 'captain' .. seealso:: :func:`operator.itemgetter` @@ -158,11 +158,11 @@ class methodcaller: .. code-block:: python >>> from datetime import date - >>> methodcaller(0, 'upper')(["hello", "world"]) + >>> methodcaller(0, "upper")(["hello", "world"]) 'HELLO' - >>> methodcaller(1, 'center', 9, "=")(["hello", "world"]) + >>> methodcaller(1, "center", 9, '=')(["hello", "world"]) '==world==' - >>> methodcaller(0, 'replace', year=2019)([date(2021, 7, 6)]) + >>> methodcaller(0, "replace", year=2019)([date(2021, 7, 6)]) datetime.date(2019, 7, 6) .. seealso:: :func:`operator.methodcaller` and :func:`operator.itemgetter` diff --git a/domdf_python_tools/import_tools.py b/domdf_python_tools/import_tools.py index fc631183..45b12234 100644 --- a/domdf_python_tools/import_tools.py +++ b/domdf_python_tools/import_tools.py @@ -53,7 +53,7 @@ "discover_in_module", "discover_entry_points", "discover_entry_points_by_name", - "iter_submodules" + "iter_submodules", ] diff --git a/domdf_python_tools/iterative.py b/domdf_python_tools/iterative.py index b3cf970e..76f6b897 100644 --- a/domdf_python_tools/iterative.py +++ b/domdf_python_tools/iterative.py @@ -306,9 +306,9 @@ def groupfloats( .. code-block:: python >>> list(groupfloats( - ... [170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], - ... step=0.05, - ... )) + ... [170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], + ... step=0.05, + ... )) [(170.0, 170.05, 170.1, 170.15), (171.05, 171.1, 171.15, 171.2)] >>> list(groupfloats([1, 2, 3, 4, 5, 7, 8, 9, 10])) [(1, 2, 3, 4, 5), (7, 8, 9, 10)] @@ -501,4 +501,4 @@ def __init_subclass__(cls, **kwargs): count.__qualname__ = count.__name__ = "count" - return count() # type: ignore + return count() # type: ignore[return-value] diff --git a/domdf_python_tools/pagesizes/classes.py b/domdf_python_tools/pagesizes/classes.py index b2c56cc1..8f048bea 100644 --- a/domdf_python_tools/pagesizes/classes.py +++ b/domdf_python_tools/pagesizes/classes.py @@ -110,6 +110,8 @@ def from_pt(cls, size: Tuple[float, float]): def from_size(cls, size: Tuple[AnyNumber, AnyNumber]) -> "BaseSize": """ Create a :class:`~domdf_python_tools.pagesizes.classes.BaseSize` object from a tuple. + + :param size: """ return cls(*size) diff --git a/domdf_python_tools/pagesizes/units.py b/domdf_python_tools/pagesizes/units.py index 0a7f2d86..58aa29f9 100644 --- a/domdf_python_tools/pagesizes/units.py +++ b/domdf_python_tools/pagesizes/units.py @@ -39,7 +39,7 @@ # stdlib import math from decimal import ROUND_HALF_UP, Decimal -from typing import SupportsFloat, Union +from typing import NoReturn, SupportsFloat, Union # this package from domdf_python_tools.doctools import prettify_docstrings @@ -80,7 +80,7 @@ class Unit(float): .. code-block:: python - >>> (3*mm) + (7*mm) + >>> (3 * mm) + (7 * mm) When adding different :class:`~domdf_python_tools.pagesizes.units.Unit` objects, @@ -88,18 +88,18 @@ class Unit(float): .. code-block:: python - >>> (2.54*cm) + inch + >>> (2.54 * cm) + inch - >>> inch + (2.54*cm) + >>> inch + (2.54 * cm) :class:`~domdf_python_tools.pagesizes.units.Unit` objects can also be added to :class:`float` and :class:`int` objects: .. code-block:: python - >>> (3*cm) + 7 + >>> (3 * cm) + 7 - >>> 7 + (3*cm) + >>> 7 + (3 * cm) @@ -109,15 +109,15 @@ class Unit(float): .. code-block:: python - >>> (17*mm) - (7*mm) + >>> (17 * mm) - (7 * mm) - >>> (2.54*cm) - inch + >>> (2.54 * cm) - inch - >>> inch - (2.54*cm) + >>> inch - (2.54 * cm) - >>> (17*cm) - 7 + >>> (17 * cm) - 7 - >>> 17 - (7*cm) + >>> 17 - (7 * cm) @@ -128,11 +128,11 @@ class Unit(float): .. code-block:: python - >>> (3*mm) * 3 + >>> (3 * mm) * 3 - >>> 3 * (3*mm) + >>> 3 * (3 * mm) - >>> 3.5 * (3*mm) + >>> 3.5 * (3 * mm) Multiplication works either way round. @@ -142,7 +142,7 @@ class Unit(float): .. code-block:: python - >>> inch * (7*cm) + >>> inch * (7 * cm) Traceback (most recent call last): NotImplementedError: Multiplying a unit by another unit is not allowed. @@ -153,16 +153,16 @@ class Unit(float): .. code-block:: python - >>> (3*mm) / 3 + >>> (3 * mm) / 3 - >>> (10*mm) / 2.5 + >>> (10 * mm) / 2.5 Dividing by another unit results in a :exc:`NotImplementedError`: .. code-block:: python - >>> inch / (7*cm) + >>> inch / (7 * cm) Traceback (most recent call last): NotImplementedError: Dividing a unit by another unit is not allowed. @@ -171,7 +171,7 @@ class Unit(float): .. code-block:: python - >>> 3 / (3*mm) + >>> 3 / (3 * mm) Traceback (most recent call last): NotImplementedError: Dividing by a unit is not allowed. @@ -188,7 +188,7 @@ class Unit(float): .. code-block:: python - >>> (3*mm) % 2.5 + >>> (3 * mm) % 2.5 Dividing by a unit, or modulo division of two units, is not officially supported. @@ -199,12 +199,12 @@ class Unit(float): name: str = "pt" _in_pt: float = 1 - def __repr__(self): + def __repr__(self) -> str: value = _rounders(float(self), "0.000") as_pt = _rounders(self.as_pt(), "0.000") return f"" - def __str__(self): + def __str__(self) -> str: value = _rounders(float(self), "0.000") as_pt = _rounders(self.as_pt(), "0.000") return f"" @@ -251,10 +251,10 @@ def __mod__(self, other: Union[float, "Unit"]) -> "Unit": return self.__class__(super().__mod__(other)) - def __pow__(self, power, modulo=None): + def __pow__(self, power, modulo=None) -> NoReturn: # noqa: MAN001 raise NotImplementedError("Powers are not supported for units.") - def __rtruediv__(self, other): + def __rtruediv__(self, other) -> NoReturn: # noqa: MAN001 raise NotImplementedError("Dividing by a unit is not allowed.") __rdiv__ = __rtruediv__ diff --git a/domdf_python_tools/paths.py b/domdf_python_tools/paths.py index f7c6b7b3..6ca924c9 100644 --- a/domdf_python_tools/paths.py +++ b/domdf_python_tools/paths.py @@ -56,6 +56,7 @@ import sys import tempfile import urllib.parse +import warnings from collections import defaultdict, deque from operator import methodcaller from typing import ( @@ -142,7 +143,7 @@ """ -def append(var: str, filename: PathLike, **kwargs) -> int: +def append(var: str, filename: PathLike, **kwargs) -> int: # noqa: PRM002 """ Append ``var`` to the file ``filename`` in the current directory. @@ -204,7 +205,7 @@ def copytree( return dst -def delete(filename: PathLike, **kwargs): +def delete(filename: PathLike, **kwargs): # noqa: PRM002 """ Delete the file in the current directory. @@ -260,7 +261,7 @@ def parent_path(path: PathLike) -> pathlib.Path: return path.parent -def read(filename: PathLike, **kwargs) -> str: +def read(filename: PathLike, **kwargs) -> str: # noqa: PRM002 """ Read a file in the current directory (in text mode). @@ -307,7 +308,7 @@ def relpath(path: PathLike, relative_to: Optional[PathLike] = None) -> pathlib.P return abs_path -def write(var: str, filename: PathLike, **kwargs) -> None: +def write(var: str, filename: PathLike, **kwargs) -> None: # noqa: PRM002 """ Write a variable to file in the current directory. @@ -388,7 +389,7 @@ class PathPlus(pathlib.Path): __slots__ = () if sys.version_info < (3, 11): - _accessor = pathlib._normal_accessor # type: ignore + _accessor = pathlib._normal_accessor # type: ignore[attr-defined] _closed = False def _init(self, *args, **kwargs): @@ -396,11 +397,11 @@ def _init(self, *args, **kwargs): @classmethod def _from_parts(cls, args, init=True): - return super()._from_parts(args) # type: ignore + return super()._from_parts(args) # type: ignore[misc] def __new__(cls: Type[_PP], *args, **kwargs) -> _PP: # noqa: D102 if cls is PathPlus: - cls = WindowsPathPlus if os.name == "nt" else PosixPathPlus # type: ignore + cls = WindowsPathPlus if os.name == "nt" else PosixPathPlus # type: ignore[assignment] return super().__new__(cls, *args, **kwargs) @@ -517,7 +518,7 @@ def write_lines( encoding: Optional[str] = "UTF-8", errors: Optional[str] = None, *, - trailing_whitespace: bool = False + trailing_whitespace: bool = False, ) -> None: """ Write the given list of lines to the file without trailing whitespace. @@ -532,6 +533,12 @@ def write_lines( .. versionchanged:: 2.4.0 Added the ``trailing_whitespace`` option. """ + if isinstance(data, str): + warnings.warn( + "Passing a string to PathPlus.write_lines writes each character to its own line.\n" + "That probably isn't what you intended.", + ) + if trailing_whitespace: data = list(data) if data[-1].strip(): @@ -585,7 +592,7 @@ def open( # type: ignore # noqa: A003 # pylint: disable=redefined-builtin encoding: Optional[str] = "UTF-8", errors: Optional[str] = None, newline: Optional[str] = NEWLINE_DEFAULT, - ) -> IO[Any]: + ) -> IO[Any]: """ Open the file pointed by this path and return a file object, as the built-in :func:`open` function does. @@ -630,7 +637,7 @@ def dump_json( data: Any, encoding: Optional[str] = "UTF-8", errors: Optional[str] = None, - json_library: JsonLibrary = json, # type: ignore + json_library: JsonLibrary = json, # type: ignore[assignment] *, compress: bool = False, **kwargs, @@ -674,7 +681,7 @@ def load_json( self, encoding: Optional[str] = "UTF-8", errors: Optional[str] = None, - json_library: JsonLibrary = json, # type: ignore + json_library: JsonLibrary = json, # type: ignore[assignment] *, decompress: bool = False, **kwargs, @@ -783,6 +790,8 @@ def unlink(self, missing_ok: bool = False) -> None: If the path is a directory, use :meth:`~domdf_python_tools.paths.PathPlus.rmdir()` instead. + :param missing_ok: + .. versionadded:: 0.3.8 for Python 3.8 and above .. versionadded:: 0.11.0 for Python 3.6 and Python 3.7 """ @@ -1118,6 +1127,10 @@ class TemporaryPathPlus(tempfile.TemporaryDirectory): Unlike :func:`tempfile.TemporaryDirectory` this class is based around a :class:`~.PathPlus` object. + :param suffix: A str suffix for the directory name. + :param prefix: A str prefix for the directory name. + :param dir: A directory to create this temp dir in. + .. versionadded:: 2.4.0 .. autosummary-widths:: 6/16 """ @@ -1163,12 +1176,12 @@ def __enter__(self) -> PathPlus: def sort_paths(*paths: PathLike) -> List[PathPlus]: - """ + r""" Sort the ``paths`` by directory, then by file. .. versionadded:: 2.6.0 - :param paths: + :param \*paths: """ directories: Dict[str, List[PathPlus]] = defaultdict(list) @@ -1252,10 +1265,10 @@ def phase4(self) -> None: # noqa: D102 "left_only": filecmp.dircmp.phase1, "right_only": filecmp.dircmp.phase1, "left_list": filecmp.dircmp.phase0, - "right_list": filecmp.dircmp.phase0 + "right_list": filecmp.dircmp.phase0, } - methodmap = _methodmap # type: ignore + methodmap = _methodmap # type: ignore[assignment] def compare_dirs(a: PathLike, b: PathLike) -> bool: diff --git a/domdf_python_tools/pretty_print.py b/domdf_python_tools/pretty_print.py index d205744e..9fe8d166 100644 --- a/domdf_python_tools/pretty_print.py +++ b/domdf_python_tools/pretty_print.py @@ -39,20 +39,20 @@ # stdlib import sys from io import StringIO -from typing import IO, Any, Callable, Iterator, MutableMapping, Optional, Tuple, Type, TypeVar +from typing import IO, Any, Callable, ClassVar, Iterator, MutableMapping, Optional, Tuple, Type, TypeVar try: # pragma: no cover # 3rd party from pprint36 import PrettyPrinter - from pprint36._pprint import _safe_key # type: ignore + from pprint36._pprint import _safe_key # type: ignore[attr-defined] supports_sort_dicts = True except ImportError: # stdlib - from pprint import PrettyPrinter, _safe_key # type: ignore + from pprint import PrettyPrinter, _safe_key # type: ignore[attr-defined] supports_sort_dicts = sys.version_info >= (3, 8) @@ -106,8 +106,8 @@ def __init__( _dispatch: MutableMapping[Callable, Callable] _indent_per_level: int - _format_items: Callable[[PrettyPrinter, Any, Any, Any, Any, Any, Any], None] - _dispatch = dict(PrettyPrinter._dispatch) # type: ignore + _format_items: ClassVar[Callable[[PrettyPrinter, Any, Any, Any, Any, Any, Any], None]] + _dispatch = dict(PrettyPrinter._dispatch) # type: ignore[attr-defined] def _make_open(self, char: str, indent: int, obj): if self._indent_per_level > 1: @@ -115,13 +115,13 @@ def _make_open(self, char: str, indent: int, obj): else: the_indent = ' ' * (indent + self._indent_per_level) - if obj and not self._compact: # type: ignore + if obj and not self._compact: # type: ignore[attr-defined] return f"{char}\n{the_indent}" else: return char def _make_close(self, char: str, indent: int, obj): - if obj and not self._compact: # type: ignore + if obj and not self._compact: # type: ignore[attr-defined] return f",\n{' ' * (indent + self._indent_per_level)}{char}" else: return char @@ -143,14 +143,14 @@ def _pprint_dict( write((self._indent_per_level - 1) * ' ') if obj: - self._format_dict_items( # type: ignore - obj.items(), - stream, - indent, - allowance + 1, - context, - level, - ) + self._format_dict_items( # type: ignore[attr-defined] + obj.items(), + stream, + indent, + allowance + 1, + context, + level, + ) write(self._make_close('}', indent, obj)) @@ -243,14 +243,14 @@ def _format_attribute_items(self, items, stream, indent, allowance, context, lev last = i == last_index write(key) write('=') - self._format( # type: ignore + self._format( # type: ignore[attr-defined] ent, stream, indent + len(key) + 2, allowance if last else 1, context, level, - ) + ) if not last: write(delimnl) @@ -263,7 +263,7 @@ def simple_repr(*attributes: str, show_module: bool = False, **kwargs): r""" Adds a simple ``__repr__`` method to the decorated class. - :param attributes: The attributes to include in the ``__repr__``. + :param \*attributes: The attributes to include in the ``__repr__``. :param show_module: Whether to show the name of the module in the ``__repr__``. :param \*\*kwargs: Keyword arguments passed on to :class:`pprint.PrettyPrinter`. """ @@ -284,7 +284,7 @@ def __repr__(self) -> str: __repr__.__name__ = "__repr__" __repr__.__module__ = obj.__module__ __repr__.__qualname__ = f"{obj.__module__}.__repr__" - obj.__repr__ = __repr__ # type: ignore + obj.__repr__ = __repr__ # type: ignore[assignment] return obj diff --git a/domdf_python_tools/stringlist.py b/domdf_python_tools/stringlist.py index 4b0c173a..516398de 100644 --- a/domdf_python_tools/stringlist.py +++ b/domdf_python_tools/stringlist.py @@ -108,7 +108,7 @@ def __repr__(self) -> str: return f"{type(self).__name__}(size={self.size}, type={self.type!r})" - def __eq__(self, other): + def __eq__(self, other) -> bool: # noqa: MAN001 if isinstance(other, Indent): return other.size == self.size and other.type == self.type elif isinstance(other, str): @@ -252,7 +252,7 @@ def __setitem__(self, index: Union[SupportsIndex, slice], line: Union[String, It for lline, idx in zip( reversed(line), reversed(range(index.start or 0, index.stop + 1, index.step or 1)), - ): + ): self[idx] = lline else: line = cast(String, line) @@ -304,7 +304,7 @@ def blankline(self, ensure_single: bool = False): self.append('') - def set_indent_size(self, size: int = 0): + def set_indent_size(self, size: int = 0) -> None: """ Sets the size of the indent to insert at the beginning of new lines. @@ -313,7 +313,7 @@ def set_indent_size(self, size: int = 0): self.indent.size = int(size) - def set_indent_type(self, indent_type: str = '\t'): + def set_indent_type(self, indent_type: str = '\t') -> None: """ Sets the type of the indent to insert at the beginning of new lines. @@ -322,7 +322,7 @@ def set_indent_type(self, indent_type: str = '\t'): self.indent.type = str(indent_type) - def set_indent(self, indent: Union[String, Indent], size: int = 0): + def set_indent(self, indent: Union[String, Indent], size: int = 0) -> None: """ Sets the indent to insert at the beginning of new lines. @@ -386,7 +386,7 @@ def __bytes__(self) -> bytes: return str(self).encode("UTF-8") - def __eq__(self, other) -> bool: + def __eq__(self, other) -> bool: # noqa: PRM002 """ Returns whether the other object is equal to this :class:`~domdf_python_tools.stringlist.StringList`. """ @@ -405,13 +405,13 @@ def with_indent(self, indent: Union[String, Indent], size: int = 0): >>> sl = StringList() >>> with sl.with_indent(" ", 1): - ... sl.append("Hello World") + ... sl.append("Hello World") :param indent: The :class:`~.Indent` to use within the ``with`` block, or the indent type. :param size: If ``indent`` is an indent type, the indent size to use within the ``with`` block. """ - original_indent: Tuple[int, str] = tuple(self.indent) # type: ignore + original_indent: Tuple[int, str] = tuple(self.indent) # type: ignore[assignment] try: self.set_indent(indent, size) @@ -428,7 +428,7 @@ def with_indent_size(self, size: int = 0): >>> sl = StringList() >>> with sl.with_indent_size(1): - ... sl.append("Hello World") + ... sl.append("Hello World") :param size: The indent size to use within the ``with`` block. """ @@ -450,7 +450,7 @@ def with_indent_type(self, indent_type: str = '\t'): >>> sl = StringList() >>> with sl.with_indent_type(" "): - ... sl.append("Hello World") + ... sl.append("Hello World") :param indent_type: The type of indent to use within the ``with`` block. """ @@ -467,6 +467,8 @@ def splitlines(self, keepends: bool = False) -> List[str]: """ Analagous to :meth:`str.splitlines`. + :param keepends: + .. versionadded:: 3.8.0 """ diff --git a/domdf_python_tools/terminal.py b/domdf_python_tools/terminal.py index a8a09f8d..a0c0ac3f 100644 --- a/domdf_python_tools/terminal.py +++ b/domdf_python_tools/terminal.py @@ -170,26 +170,22 @@ def __init__(self, indent: str = ' ' * 2): if frame is None: # pragma: no cover raise ValueError("Unable to obtain the frame of the caller.") else: - self.parent_frame = inspect.currentframe().f_back # type: ignore # TODO + self.parent_frame = inspect.currentframe().f_back # type: ignore[union-attr] # TODO def __enter__(self): """ Called when entering the context manager. """ - self.locals_on_entry = self.parent_frame.f_locals.copy() # type: ignore + self.locals_on_entry = self.parent_frame.f_locals.copy() # type: ignore[union-attr] - def __exit__(self, *args, **kwargs): + def __exit__(self, *args, **kwargs): # noqa: PRM002 """ Called when exiting the context manager. """ - new_locals = { - k: v - for k, - v in self.parent_frame.f_locals.items() # type: ignore - if k not in self.locals_on_entry - } + f_locals = self.parent_frame.f_locals # type: ignore[union-attr] + new_locals = {k: v for k, v in f_locals.items() if k not in self.locals_on_entry} print(textwrap.indent(pprint.pformat(new_locals), self.indent)) diff --git a/domdf_python_tools/typing.py b/domdf_python_tools/typing.py index efa533af..c32d7efa 100644 --- a/domdf_python_tools/typing.py +++ b/domdf_python_tools/typing.py @@ -109,7 +109,7 @@ def check_membership(obj: Any, type_: Union[Type, object]) -> bool: such as a :class:`typing.List`, :py:data:`typing.Union` or :py:class:`typing.Sequence`. """ - return isinstance(obj, type_.__args__) # type: ignore + return isinstance(obj, type_.__args__) # type: ignore[union-attr] class JsonLibrary(Protocol): @@ -134,7 +134,7 @@ def dumps( sort_keys: bool = ..., **kwds: Any, ) -> str: - """ + r""" Serialize ``obj`` to a JSON formatted :class:`str`. :param obj: @@ -147,7 +147,7 @@ def dumps( :param separators: :param default: :param sort_keys: - :param kwds: + :param \*\*kwds: """ @staticmethod @@ -162,7 +162,7 @@ def loads( object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ..., **kwds: Any, ) -> Any: - """ + r""" Deserialize ``s`` to a Python object. :param s: @@ -172,7 +172,7 @@ def loads( :param parse_int: :param parse_constant: :param object_pairs_hook: - :param kwds: + :param \*\*kwds: :rtype: @@ -225,7 +225,7 @@ def head(self: "HasHead", n: int = 5) -> "HasHead": :return: The first n rows of the caller object. """ - def to_string(self, *args, **kwargs) -> Optional[str]: + def to_string(self, *args, **kwargs) -> Optional[str]: # noqa: PRM002 """ Render the object to a console-friendly tabular output. """ @@ -254,7 +254,7 @@ class SupportsLessThan(Protocol): .. versionadded:: 3.0.0 """ - def __lt__(self, __other: Any) -> bool: + def __lt__(self, __other: Any) -> bool: # noqa: PRM002 """ Return ``self < value``. """ @@ -267,7 +267,7 @@ class SupportsLessEqual(Protocol): .. versionadded:: 3.0.0 """ - def __le__(self, __other: Any) -> bool: + def __le__(self, __other: Any) -> bool: # noqa: PRM002 """ Return ``self <= value``. """ @@ -280,7 +280,7 @@ class SupportsGreaterThan(Protocol): .. versionadded:: 3.0.0 """ - def __gt__(self, __other: Any) -> bool: + def __gt__(self, __other: Any) -> bool: # noqa: PRM002 """ Return ``self > value``. """ @@ -293,7 +293,7 @@ class SupportsGreaterEqual(Protocol): .. versionadded:: 3.0.0 """ - def __ge__(self, __other: Any) -> bool: + def __ge__(self, __other: Any) -> bool: # noqa: PRM002 """ Return ``self >= value``. """ diff --git a/domdf_python_tools/utils.py b/domdf_python_tools/utils.py index 8207d809..533fd9c3 100644 --- a/domdf_python_tools/utils.py +++ b/domdf_python_tools/utils.py @@ -139,7 +139,7 @@ SPACE_PLACEHOLDER = '␣' -def cmp(x, y) -> int: +def cmp(x, y) -> int: # noqa: PRM002 """ Implementation of ``cmp`` for Python 3. @@ -278,6 +278,8 @@ def strtobool(val: Union[str, int]) -> bool: :py:obj:`False` values are ``'n'``, ``'no'``, ``'f'``, ``'false'``, ``'off'``, ``'0'``, and ``0``. + :param val: + :raises: :py:exc:`ValueError` if ``val`` is anything else. """ @@ -431,7 +433,7 @@ def head(obj: Union[Tuple, List, "DataFrame", "Series", String, HasHead], n: int if len(obj) <= n: return repr(obj) else: - head_of_namedtuple = {k: v for k, v in zip(obj._fields[:n], obj[:n])} # type: ignore + head_of_namedtuple = {k: v for k, v in zip(obj._fields[:n], obj[:n])} repr_fmt = '(' + ", ".join(f"{k}={v!r}" for k, v in head_of_namedtuple.items()) + f", {etc})" return obj.__class__.__name__ + repr_fmt @@ -444,11 +446,11 @@ def head(obj: Union[Tuple, List, "DataFrame", "Series", String, HasHead], n: int elif isinstance(obj, HasHead): return obj.head(n).to_string() - elif len(obj) <= n: # type: ignore + elif len(obj) <= n: # type: ignore[arg-type] return str(obj) else: - return str(obj[:n]) + etc # type: ignore + return str(obj[:n]) + etc # type: ignore[index] def magnitude(x: float) -> int: @@ -553,7 +555,7 @@ def divide(string: str, sep: str) -> Tuple[str, str]: raise ValueError(f"{sep!r} not in {string!r}") parts = string.split(sep, 1) - return tuple(parts) # type: ignore + return tuple(parts) # type: ignore[return-value] def redivide(string: str, pat: Union[str, Pattern]) -> Tuple[str, str]: @@ -577,7 +579,7 @@ def redivide(string: str, pat: Union[str, Pattern]) -> Tuple[str, str]: raise ValueError(f"{pat!r} has no matches in {string!r}") parts = pat.split(string, 1) - return tuple(parts) # type: ignore + return tuple(parts) # type: ignore[return-value] @overload diff --git a/domdf_python_tools/versions.py b/domdf_python_tools/versions.py index 2ebc152f..a2d74eac 100644 --- a/domdf_python_tools/versions.py +++ b/domdf_python_tools/versions.py @@ -78,19 +78,19 @@ class Version(Tuple[int, int, int]): """ @property # type: ignore - def major(self): # noqa: D102 + def major(self) -> int: # noqa: D102 return self[0] @property # type: ignore - def minor(self): # noqa: D102 + def minor(self) -> int: # noqa: D102 return self[1] @property # type: ignore - def patch(self): # noqa: D102 + def patch(self) -> int: # noqa: D102 return self[2] - def __new__(cls: Type[_V], major=0, minor=0, patch=0) -> _V: # noqa: D102 - t: _V = super().__new__(cls, (int(major), int(minor), int(patch))) # type: ignore + def __new__(cls: Type[_V], major=0, minor=0, patch=0) -> _V: # noqa: D102,MAN001 + t: _V = super().__new__(cls, (int(major), int(minor), int(patch))) return t @@ -123,14 +123,14 @@ def __int__(self) -> int: return self.major - def __getnewargs__(self): + def __getnewargs__(self) -> Tuple[int, int, int]: """ Return Version as a plain tuple. Used by copy and pickle. """ - return tuple(self) + return tuple(self) # type: ignore[return-value] - def __eq__(self, other) -> bool: + def __eq__(self, other) -> bool: # noqa: MAN001,PRM002 """ Returns whether this version is equal to the other version. @@ -145,7 +145,7 @@ def __eq__(self, other) -> bool: shortest = min(len(self), (len(other))) return self[:shortest] == other[:shortest] - def __gt__(self, other) -> bool: + def __gt__(self, other) -> bool: # noqa: MAN001,PRM002 """ Returns whether this version is greater than the other version. @@ -159,7 +159,7 @@ def __gt__(self, other) -> bool: else: return tuple(self) > other - def __lt__(self, other) -> bool: + def __lt__(self, other) -> bool: # noqa: MAN001,PRM002 """ Returns whether this version is less than the other version. @@ -173,7 +173,7 @@ def __lt__(self, other) -> bool: else: return tuple(self) < other - def __ge__(self, other) -> bool: + def __ge__(self, other) -> bool: # noqa: MAN001,PRM002 """ Returns whether this version is greater than or equal to the other version. @@ -187,7 +187,7 @@ def __ge__(self, other) -> bool: else: return tuple(self)[:len(other)] >= other - def __le__(self, other) -> bool: + def __le__(self, other) -> bool: # noqa: MAN001,PRM002 """ Returns whether this version is less than or equal to the other version. @@ -256,12 +256,12 @@ def _asdict(self) -> Dict[str, int]: } def _replace(self: _V, **kwargs) -> _V: - """ + r""" Return a new instance of the named tuple replacing specified fields with new values. .. versionadded:: 1.4.0 - :param kwargs: + :param \*\*kwargs: """ return self.__class__(**{**self._asdict(), **kwargs}) @@ -303,7 +303,7 @@ def _iter_float(version_float: float) -> Generator[int, None, None]: return _iter_string(str(version_float)) -def _prep_for_eq(other: Union[str, float, Version], ) -> Tuple[int, ...]: +def _prep_for_eq(other: Union[str, float, Version]) -> Tuple[int, ...]: # noqa: PRM002 """ Prepare 'other' for use in ``__eq__``, ``__le__``, ``__ge__``, ``__gt__``, and ``__lt__``. """ diff --git a/domdf_python_tools/words.py b/domdf_python_tools/words.py index d8bc36cf..dd74787d 100644 --- a/domdf_python_tools/words.py +++ b/domdf_python_tools/words.py @@ -39,7 +39,6 @@ # stdlib import functools import random -import re import sys from gettext import ngettext from reprlib import recursive_repr @@ -171,14 +170,13 @@ def alpha_sort( alphabet_ = list(alphabet) - try: - return sorted(iterable, key=lambda attr: [alphabet_.index(letter) for letter in attr], reverse=reverse) - except ValueError as e: - m = re.match(r"'(.*)' is not in list", str(e)) - if m: - raise ValueError(f"The character {m.group(1)!r} was not found in the alphabet.") from None - else: # pragma: no cover - raise e + def _alphabet_index(letter: str) -> int: + try: + return alphabet_.index(letter) + except ValueError: + raise ValueError(f"The character {letter!r} was not found in the alphabet.") from None + + return sorted(iterable, key=lambda attr: [_alphabet_index(letter) for letter in attr], reverse=reverse) class Font(Dict[str, str]): @@ -218,7 +216,7 @@ def __call__(self, text: str) -> str: return ''.join(self[char] for char in text) - def get(self, char: str, default: Optional[str] = None) -> str: # type: ignore + def get(self, char: str, default: Optional[str] = None) -> str: # type: ignore[override] """ Returns the given character in this font. @@ -595,13 +593,15 @@ class Plural(functools.partial): def __init__(self, singular: str, plural: str): pass - def __call__(self, n: int) -> str: # type: ignore + def __call__(self, n: int) -> str: """ Returns either the singular or plural form of the word depending on the value of ``n``. :param n: """ + return '' + # if PYPY: # pragma: no cover (!PyPy) if PYPY and sys.version_info < (3, 9): # pragma: no cover (!PyPy) @@ -613,7 +613,7 @@ def __new__(cls, singular: str, plural: str): # noqa: D102 return functools.partial.__new__(cls, ngettext, singular, plural) @recursive_repr() - def __repr__(self): + def __repr__(self) -> str: qualname = type(self).__qualname__ args: List[str] = [] args.extend(repr(x) for x in self.args) @@ -653,9 +653,9 @@ class PluralPhrase(NamedTuple): .. code-block:: python >>> phrase = PluralPhrase( - ... "The proposed {} {} to ...", - ... (Plural("change", "changes"), Plural("is", "are")) - ... ) + ... "The proposed {} {} to ...", + ... (Plural("change", "changes"), Plural("is", "are")), + ... ) >>> phrase(1) 'The proposed change is to ...' >>> phrase(2) @@ -679,7 +679,7 @@ class PluralPhrase(NamedTuple): template: str words: Tuple[Plural, ...] - def __call__(self, n: int) -> str: # noqa: TYP004 # TODO + def __call__(self, n: int) -> str: """ Construct the phrase based on the value of ``n``. diff --git a/formate.toml b/formate.toml index 95093bce..20307c54 100644 --- a/formate.toml +++ b/formate.toml @@ -5,6 +5,7 @@ reformat-generics = 40 noqa-reformat = 60 ellipsis-reformat = 70 squish_stubs = 80 +newline_after_equals = 90 [hooks.yapf] priority = 30 @@ -12,6 +13,12 @@ priority = 30 [hooks.yapf.kwargs] yapf_style = ".style.yapf" +[hooks.trailing_commas] +priority = 21 + +[hooks.trailing_commas.kwargs] +format_ImportFrom = false + [hooks.isort] priority = 50 @@ -27,31 +34,6 @@ lines_between_types = 0 use_parentheses = true remove_redundant_aliases = true default_section = "THIRDPARTY" -known_third_party = [ - "click", - "coincidence", - "consolekit", - "coverage", - "coverage_pyver_pragma", - "faker", - "flake8", - "funcy", - "github", - "importlib_metadata", - "importlib_resources", - "natsort", - "pandas", - "pydash", - "pytest", - "pytest_cov", - "pytest_randomly", - "pytest_regressions", - "pytest_rerunfailures", - "pytest_timeout", - "pytz", - "requests", - "typing_extensions", -] known_first_party = [ "domdf_python_tools",] [config] diff --git a/justfile b/justfile index e8ed871a..4307956e 100644 --- a/justfile +++ b/justfile @@ -12,6 +12,9 @@ unused-imports: incomplete-defs: tox -e lint -- --select MAN +commas: + tox -e lint -- --select C810,C812,C813,C814,C815,C816 + vdiff: git diff $(repo-helper show version -q)..HEAD @@ -20,3 +23,8 @@ bare-ignore: lint: unused-imports incomplete-defs bare-ignore tox -n qa + +uncomm: + git status -uall --ignored + +# Custom commands can be added below this comment diff --git a/pyproject.toml b/pyproject.toml index 335c68b1..f0a9cd88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,11 +3,11 @@ requires = [ "hatch-requirements-txt",] build-backend = "hatchling.build" [project] -name = "domdf_python_tools" +name = "domdf-python-tools" version = "3.10.0" description = "Helpful functions for Pythonβ€‚πŸβ€‚πŸ› οΈ" readme = "README.rst" -requires-python = ">=3.6" +requires-python = ">=3.7" keywords = [ "utilities",] classifiers = [ "Development Status :: 5 - Production/Stable", @@ -16,13 +16,14 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules", @@ -48,11 +49,6 @@ dates = [ "pytz>=2019.1",] testing = [] all = [ "pytz>=2019.1",] -[tool.mkrecipe] -conda-channels = [ "conda-forge", "domdfcoding",] -extras = [ "dates",] -license-key = "MIT" - [tool.sphinx-pyproject] github_username = "domdfcoding" github_repository = "domdf_python_tools" @@ -142,14 +138,15 @@ base-classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed", ] -python-versions = [ "3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12",] +python-versions = [ "3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14",] python-implementations = [ "CPython", "PyPy",] platforms = [ "Windows", "macOS", "Linux",] license-key = "MIT" additional-files = [ "include domdf_python_tools/google-10000-english-no-swears.txt",] +package = "domdf_python_tools" [tool.mypy] -python_version = "3.8" +python_version = "3.9" namespace_packages = true check_untyped_defs = true warn_unused_ignores = true diff --git a/repo_helper.yml b/repo_helper.yml index 4c9b41d1..2b407161 100644 --- a/repo_helper.yml +++ b/repo_helper.yml @@ -8,27 +8,23 @@ username: "domdfcoding" license: 'MIT' short_desc: 'Helpful functions for Pythonβ€‚πŸβ€‚πŸ› οΈ' -python_deploy_version: 3.8 -requires_python: 3.6 min_coverage: 95 tox_testenv_extras: all pre_commit_exclude: "^domdf_python_tools/compat/importlib_resources.py$" docs_fail_on_warning: true use_hatch: true - -conda_channels: - - conda-forge +on_conda_forge: true +enable_conda: false python_versions: - 3.6: 3.7: 3.8: 3.9: "3.10": "3.11": "3.12": - "3.13-dev": - pypy36: + "3.13": + "3.14": pypy37: pypy38: pypy39: diff --git a/tests/discover_demo_module/__init__.py b/tests/discover_demo_module/__init__.py index abee116f..6a52ec9f 100644 --- a/tests/discover_demo_module/__init__.py +++ b/tests/discover_demo_module/__init__.py @@ -1,2 +1,2 @@ def foo_in_init() -> str: - pass + return '' diff --git a/tests/list_tests.py b/tests/list_tests.py index 2c6f7050..49c5d781 100644 --- a/tests/list_tests.py +++ b/tests/list_tests.py @@ -104,7 +104,7 @@ def test_reversed(self): assert list(reversed(self.type2test())) == self.type2test() # Bug 3689: make sure list-reversed-iterator doesn't have __len__ with pytest.raises(TypeError): - len(reversed([1, 2, 3])) # type: ignore + len(reversed([1, 2, 3])) # type: ignore[arg-type] def test_setitem(self): a = self.type2test([0, 1]) diff --git a/tests/mypy_test.py b/tests/mypy_test.py index a72f85b2..deea8d97 100644 --- a/tests/mypy_test.py +++ b/tests/mypy_test.py @@ -8,13 +8,11 @@ class MyDictable(Dictable): def __init__(self, foo: str, bar: int): - super().__init__() - self.foo: str = foo self.bar: float = float(bar) @property - def __dict__(self): + def __dict__(self): # type: ignore[override] return dict(foo=self.foo, bar=self.bar) diff --git a/tests/seq_tests.py b/tests/seq_tests.py index 07754900..c981bb20 100644 --- a/tests/seq_tests.py +++ b/tests/seq_tests.py @@ -12,7 +12,7 @@ import pickle import sys from itertools import chain -from typing import Any, List +from typing import Any, Callable, Iterator, List, Tuple, Type # 3rd party import pytest @@ -180,16 +180,16 @@ def itermulti(seqn): return chain(map(lambda x: x, iterfunc(IterGen(Sequence(seqn))))) -class LyingTuple(tuple): +class LyingTuple(Tuple[int]): __slots__ = () - def __iter__(self): + def __iter__(self) -> Iterator[int]: yield 1 -class LyingList(list): +class LyingList(List[int]): - def __iter__(self): + def __iter__(self) -> Iterator[int]: yield 1 @@ -197,13 +197,13 @@ class CommonTest: # The type to be tested type2test: type - def assertEqual(self, left, right): + def assertEqual(self, left: Any, right: Any): assert left == right - def assertNotEqual(self, left, right): + def assertNotEqual(self, left: Any, right: Any): assert left != right - def assertRaises(self, what, func, *args): + def assertRaises(self, what: Type[Exception], func: Callable, *args): with pytest.raises(what): func(*args) @@ -226,13 +226,13 @@ def test_constructors(self): class OtherSeq: - def __init__(self, initseq): + def __init__(self, initseq) -> None: self.__data = initseq - def __len__(self): + def __len__(self) -> int: return len(self.__data) - def __getitem__(self, i): + def __getitem__(self, i): # noqa: MAN001,MAN002 return self.__data[i] s = OtherSeq(u0) @@ -244,7 +244,7 @@ def __getitem__(self, i): assert len(vv) == len(s2) # Create from various iteratables - for s2 in ("123", '', range(1000), ("do", 1.2), range(2000, 2200, 5)): # type: ignore + for s2 in ("123", '', range(1000), ("do", 1.2), range(2000, 2200, 5)): # type: ignore[assignment] for g in (Sequence, IterFunc, IterGen, itermulti, iterfunc): assert self.type2test(g(s2)) == self.type2test(s2) assert self.type2test(IterFuncStop(s2)) == self.type2test() @@ -405,7 +405,7 @@ def test_addmul(self): assert u2 + u2 + u2 == u2 * 3 assert u2 + u2 + u2 == 3 * u2 - class subclass(self.type2test): # type: ignore + class subclass(self.type2test): # type: ignore[name-defined] pass u3 = subclass([0, 1]) @@ -434,7 +434,7 @@ def test_imul(self): def test_getitemoverwriteiter(self): # Verify that __getitem__ overrides are not recognized by __iter__ - class T(self.type2test): # type: ignore + class T(self.type2test): # type: ignore[name-defined] def __getitem__(self, key: Any) -> str: return str(key) + "!!!" @@ -500,7 +500,7 @@ class BadExc(Exception): class BadCmp: - def __eq__(self, other): + def __eq__(self, other) -> bool: if other == 2: raise BadExc() return False @@ -540,7 +540,7 @@ class BadExc(Exception): class BadCmp: - def __eq__(self, other): + def __eq__(self, other) -> bool: if other == 2: raise BadExc() return False diff --git a/tests/test_bases.py b/tests/test_bases.py index 1125ffb1..ff4eec6f 100644 --- a/tests/test_bases.py +++ b/tests/test_bases.py @@ -10,7 +10,7 @@ import copy import pickle # nosec: B101 from numbers import Number, Real -from typing import no_type_check +from typing import Any, Dict, Optional, no_type_check # 3rd party import pytest @@ -22,15 +22,13 @@ class Person(Dictable): - def __init__(self, name, age, occupation=None): - super().__init__() - + def __init__(self, name: str, age: int, occupation: Optional[str] = None): self.name = str(name) self.age = int(age) self.occupation = occupation @property - def __dict__(self): + def __dict__(self) -> Dict[str, Any]: # type: ignore[override] return dict( name=self.name, age=self.age, @@ -40,13 +38,13 @@ def __dict__(self): class Child(Person): - def __init__(self, name, age, school): + def __init__(self, name: str, age: int, school: str): super().__init__(name, age, "Student") self.school = "school" @property - def __dict__(self): + def __dict__(self) -> Dict[str, Any]: # type: ignore[override] class_dict = super().__dict__ class_dict["School"] = self.school return class_dict @@ -59,12 +57,13 @@ def alice(): class TestDictable: - def test_creation(self, alice): + @no_type_check + def test_creation(self, alice: Dictable): assert alice.name == "Alice" assert alice.age == 20 assert alice.occupation == "IRC Lurker" - def test_str(self, alice: object): + def test_str(self, alice: Dictable): assert str(alice).startswith(" int: - pass + return 0 def test_delegate_kwargs(): @@ -84,7 +84,7 @@ class F: @delegates(f) def g(self, *args, **kwargs) -> str: - pass + return '' sig = inspect.signature(F.g) assert list(sig.parameters.keys()) == ["self", 'a', 'b', 'c', 'd', 'e', 'f', 'g'] diff --git a/tests/test_dir_comparator.py b/tests/test_dir_comparator.py index 0a98baae..4c773e0d 100644 --- a/tests/test_dir_comparator.py +++ b/tests/test_dir_comparator.py @@ -12,6 +12,7 @@ import shutil from contextlib import redirect_stdout from io import StringIO +from typing import Sequence # 3rd party import pytest @@ -66,17 +67,17 @@ def test_default_ignores(self): assert ".hg" in filecmp.DEFAULT_IGNORES # @pytest.mark.parametrize() - def test_cmpfiles(self, comparator_tmpdir): + def test_cmpfiles(self, comparator_tmpdir: ComparatorTmpdirData): assert filecmp.cmpfiles( comparator_tmpdir.dir, comparator_tmpdir.dir, ["file"], - ) == (["file"], [], []), "Comparing directory to itself fails" + ) == (["file"], [], []), "Comparing directory to itself fails" assert filecmp.cmpfiles( comparator_tmpdir.dir, comparator_tmpdir.dir_same, ["file"], - ) == (["file"], [], []), "Comparing directory to same fails" + ) == (["file"], [], []), "Comparing directory to same fails" # Try it with shallow=False assert filecmp.cmpfiles( @@ -84,13 +85,13 @@ def test_cmpfiles(self, comparator_tmpdir): comparator_tmpdir.dir, ["file"], shallow=False, - ) == (["file"], [], []), "Comparing directory to itself fails" + ) == (["file"], [], []), "Comparing directory to itself fails" assert filecmp.cmpfiles( comparator_tmpdir.dir, comparator_tmpdir.dir_same, ["file"], shallow=False, - ), "Comparing directory to same fails" + ), "Comparing directory to same fails" # Add different file2 with open(os.path.join(comparator_tmpdir.dir, "file2"), 'w', encoding="UTF-8") as output: @@ -100,16 +101,16 @@ def test_cmpfiles(self, comparator_tmpdir): comparator_tmpdir.dir, comparator_tmpdir.dir_same, ["file", "file2"], - ) != (["file"], ["file2"], []), "Comparing mismatched directories fails" + ) != (["file"], ["file2"], []), "Comparing mismatched directories fails" - def _assert_lists(self, actual, expected): + def _assert_lists(self, actual: Sequence, expected: Sequence): """ Assert that two lists are equal, up to ordering. """ assert sorted(actual) == sorted(expected) - def test_dircmp(self, comparator_tmpdir): + def test_dircmp(self, comparator_tmpdir: ComparatorTmpdirData): # Check attributes for comparison of two identical directories left_dir, right_dir = comparator_tmpdir.dir, comparator_tmpdir.dir_same d = DirComparator(left_dir, right_dir) @@ -195,7 +196,7 @@ def test_dircmp(self, comparator_tmpdir): ] self._assert_report(d.report, expected_report) - def test_dircmp_subdirs_type(self, comparator_tmpdir): + def test_dircmp_subdirs_type(self, comparator_tmpdir: ComparatorTmpdirData): """ Check that dircmp.subdirs respects subclassing. """ @@ -209,7 +210,7 @@ class MyDirCmp(DirComparator): sub_dcmp = sub_dirs["subdir"] assert type(sub_dcmp) == MyDirCmp # pylint: disable=unidiomatic-typecheck - def test_report_partial_closure(self, comparator_tmpdir): + def test_report_partial_closure(self, comparator_tmpdir: ComparatorTmpdirData): left_dir, right_dir = comparator_tmpdir.dir, comparator_tmpdir.dir_same d = DirComparator(left_dir, right_dir) left_subdir = os.path.join(left_dir, "subdir") @@ -223,7 +224,7 @@ def test_report_partial_closure(self, comparator_tmpdir): ] self._assert_report(d.report_partial_closure, expected_report) - def test_report_full_closure(self, comparator_tmpdir): + def test_report_full_closure(self, comparator_tmpdir: ComparatorTmpdirData): left_dir, right_dir = comparator_tmpdir.dir, comparator_tmpdir.dir_same d = DirComparator(left_dir, right_dir) left_subdir = os.path.join(left_dir, "subdir") diff --git a/tests/test_doctools.py b/tests/test_doctools.py index 55358b41..72850cd5 100644 --- a/tests/test_doctools.py +++ b/tests/test_doctools.py @@ -112,12 +112,12 @@ def __init__(self): "pate, brandy and with a fried egg on top and spam." ) - @doctools.is_documented_by(Cafe.menu) # type: ignore + @doctools.is_documented_by(Cafe.menu) # type: ignore[prop-decorator] @property def menu(self): return super().menu + [self._todays_special] - @doctools.is_documented_by(Cafe.opening_hours) # type: ignore + @doctools.is_documented_by(Cafe.opening_hours) # type: ignore[prop-decorator] @property def opening_hours(self): return f"""Open Monday-Saturday {self._opens_at}am - {self._closes_at}pm @@ -194,7 +194,7 @@ def function_in_class_with_same_args(self, a, b, c, d): (False, ''), (0, ''), ([], ''), - ] + ], ) def test_deindent_string(docstring, expects): assert doctools.deindent_string(docstring) == expects @@ -209,17 +209,14 @@ def test_decorators(): assert SpamCafe.opening_hours.__doc__ == Cafe.opening_hours.__doc__ # set_opening_hours and ceil should have extra text at the beginning - assert SpamCafe.set_opening_hours.__doc__.startswith( # type: ignore - "I will not buy this record, it is scratched." - ) - assert (doctools.deindent_string(SpamCafe.set_opening_hours.__doc__ - )).endswith(doctools.deindent_string(Cafe.set_opening_hours.__doc__)) + assert (SpamCafe.set_opening_hours.__doc__ or '').startswith("I will not buy this record, it is scratched.") + doc = (doctools.deindent_string(SpamCafe.set_opening_hours.__doc__)) + assert doc.endswith(doctools.deindent_string(Cafe.set_opening_hours.__doc__)) # Dedented both strings to be sure of equivalence - assert SpamCafe.ceil.__doc__.startswith( # type: ignore - "I don't know why the cafe has a ceil function, but we'd better document it properly.", - ) - assert doctools.deindent_string(SpamCafe.ceil.__doc__ - ).rstrip().endswith(doctools.deindent_string(math.ceil.__doc__).rstrip()) + expected = "I don't know why the cafe has a ceil function, but we'd better document it properly." + assert (SpamCafe.ceil.__doc__ or '').startswith(expected) + doc = doctools.deindent_string(SpamCafe.ceil.__doc__).rstrip() + assert doc.endswith(doctools.deindent_string(math.ceil.__doc__).rstrip()) # Dedented both strings to be sure of equivalence # Functions @@ -228,18 +225,25 @@ def test_decorators(): if PEP_563: assert undocumented_function.__annotations__ == { - 'a': "float", 'b': "float", 'c': "float", 'd': "int", "return": "float" + 'a': "float", + 'b': "float", + 'c': "float", + 'd': "int", + "return": "float", } else: assert undocumented_function.__annotations__ == { - 'a': float, 'b': float, 'c': float, 'd': int, "return": float + 'a': float, + 'b': float, + 'c': float, + 'd': int, + "return": float, } - assert partially_documented_function.__doc__.startswith( # type: ignore - "This function works like ``documented_function`` except it returns the result telepathically.", - ) - assert (doctools.deindent_string(partially_documented_function.__doc__ - )).endswith(doctools.deindent_string(documented_function.__doc__)) + expected = "This function works like ``documented_function`` except it returns the result telepathically." + assert (partially_documented_function.__doc__ or '').startswith(expected) + doc = (doctools.deindent_string(partially_documented_function.__doc__)) + assert doc.endswith(doctools.deindent_string(documented_function.__doc__)) # Dedented both strings to be sure of equivalence assert DummyClass.function_in_class_with_same_args.__doc__ == documented_function.__doc__ assert DummyClass.function_in_class_with_same_args.__name__ == "function_in_class_with_same_args" @@ -247,7 +251,7 @@ def test_decorators(): def test_document_object_from_another(): - def funA(): + def funA() -> None: pass doctools.document_object_from_another(funA, str) @@ -513,12 +517,12 @@ def test_prettify_with_method(): class F(Iterable): pass - assert prettify_docstrings(F).__getitem__.__doc__ != "Return ``self[key]``." # type: ignore + assert prettify_docstrings(F).__getitem__.__doc__ != "Return ``self[key]``." # type: ignore[misc] class G(Dictable): pass - assert prettify_docstrings(G).__getitem__.__doc__ != "Return ``self[key]``." # type: ignore + assert prettify_docstrings(G).__getitem__.__doc__ != "Return ``self[key]``." # type: ignore[misc] def test_prettify_namedtuple(): diff --git a/tests/test_getters.py b/tests/test_getters.py index dd433626..0f5c2d50 100644 --- a/tests/test_getters.py +++ b/tests/test_getters.py @@ -12,7 +12,7 @@ # 3rd party import pytest -from funcy.funcs import rpartial # type: ignore +from funcy.funcs import rpartial # type: ignore[import-untyped] # this package import domdf_python_tools @@ -29,10 +29,10 @@ class A: pass a = A() - a.name = "john" # type: ignore + a.name = "john" # type: ignore[attr-defined] b = A() - b.name = "graham" # type: ignore + b.name = "graham" # type: ignore[attr-defined] f = attrgetter(0, "name") assert f([a, b]) == "john" @@ -41,13 +41,13 @@ class A: assert f([a, b]) == "graham" with pytest.raises(TypeError, match=r"__call__\(\) missing 1 required positional argument: 'obj'"): - f() # type: ignore + f() # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) takes 2 positional arguments but 3 were given"): - f(a, "cleese") # type: ignore + f(a, "cleese") # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) got an unexpected keyword argument 'surname'"): - f(a, surname="cleese") # type: ignore + f(a, surname="cleese") # type: ignore[call-arg] f = attrgetter(0, "rank") @@ -78,15 +78,15 @@ def __getattr__(self, name): # recursive gets a = A() - a.name = "john" # type: ignore - a.child = A() # type: ignore - a.child.name = "thomas" # type: ignore + a.name = "john" # type: ignore[attr-defined] + a.child = A() # type: ignore[attr-defined] + a.child.name = "thomas" # type: ignore[attr-defined] f = attrgetter(3, "child.name") assert f([1, 2, 3, a]) == "thomas" with pytest.raises(AttributeError, match="'A' object has no attribute 'child'"): - f([1, 2, 3, a.child]) # type: ignore + f([1, 2, 3, a.child]) # type: ignore[attr-defined] f = attrgetter(1, "child.name") @@ -105,8 +105,8 @@ def __getattr__(self, name): with pytest.raises(AttributeError, match="'A' object has no attribute ''"): f([a]) - a.child.child = A() # type: ignore - a.child.child.name = "johnson" # type: ignore + a.child.child = A() # type: ignore[attr-defined] + a.child.child.name = "johnson" # type: ignore[attr-defined] f = attrgetter(0, "child.child.name") assert f([a]) == "johnson" @@ -118,12 +118,12 @@ class A: pass a = A() - a.x = 'X' # type: ignore - a.y = 'Y' # type: ignore - a.z = 'Z' # type: ignore - a.t = A() # type: ignore - a.t.u = A() # type: ignore - a.t.u.v = 'V' # type: ignore + a.x = 'X' # type: ignore[attr-defined] + a.y = 'Y' # type: ignore[attr-defined] + a.z = 'Z' # type: ignore[attr-defined] + a.t = A() # type: ignore[attr-defined] + a.t.u = A() # type: ignore[attr-defined] + a.t.u.v = 'V' # type: ignore[attr-defined] f = attrgetter(0, 'x') f2 = copy(f, proto) @@ -150,13 +150,13 @@ def test_itemgetter(self): assert f([1, 2, a]) == 'C' with pytest.raises(TypeError, match=r"__call__\(\) missing 1 required positional argument: 'obj'"): - f() # type: ignore + f() # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) takes 2 positional arguments but 3 were given"): - f(a, 3) # type: ignore + f(a, 3) # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) got an unexpected keyword argument 'size'"): - f(a, size=3) # type: ignore + f(a, size=3) # type: ignore[call-arg] f = itemgetter(1, 10) @@ -186,16 +186,16 @@ def __getitem__(self, name): TypeError, match=r"__init__\(\) missing 2 required positional arguments: 'idx' and 'item'", ): - itemgetter() # type: ignore + itemgetter() # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__init__\(\) missing 1 required positional argument: 'item'"): - itemgetter(1) # type: ignore + itemgetter(1) # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__init__\(\) missing 1 required positional argument: 'item'"): - itemgetter("abc") # type: ignore + itemgetter("abc") # type: ignore[arg-type,call-arg] with pytest.raises(TypeError, match="'idx' must be an integer"): - itemgetter("abc", 2) # type: ignore + itemgetter("abc", 2) # type: ignore[arg-type] d = dict(key="val") @@ -250,19 +250,19 @@ def test_methodcaller(self): TypeError, match=r"__init__\(\) missing 2 required positional arguments: '_idx' and '_name'", ): - methodcaller() # type: ignore + methodcaller() # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__init__\(\) missing 1 required positional argument: '_name'"): - methodcaller(12) # type: ignore + methodcaller(12) # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__init__\(\) missing 1 required positional argument: '_name'"): - methodcaller("name") # type: ignore + methodcaller("name") # type: ignore[arg-type,call-arg] with pytest.raises(TypeError, match="'_idx' must be an integer"): - methodcaller("name", 12) # type: ignore + methodcaller("name", 12) # type: ignore[arg-type] with pytest.raises(TypeError, match="method name must be a string"): - methodcaller(0, 12) # type: ignore + methodcaller(0, 12) # type: ignore[arg-type] f = methodcaller(1, "foo") @@ -293,19 +293,19 @@ def baz(*args, **kwds): # noqa: MAN002 assert f([1, a]) == 3 with pytest.raises(TypeError, match=r"__call__\(\) missing 1 required positional argument: 'obj'"): - f() # type: ignore + f() # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) takes 2 positional arguments but 3 were given"): - f(a, 3) # type: ignore + f(a, 3) # type: ignore[call-arg] with pytest.raises(TypeError, match=r"__call__\(\) got an unexpected keyword argument 'spam'"): - f(a, spam=3) # type: ignore + f(a, spam=3) # type: ignore[call-arg] f = methodcaller(0, "bar") assert f([a]) == 42 with pytest.raises(TypeError, match=r"__call__\(\) takes 2 positional arguments but 3 were given"): - f([a], [a]) # type: ignore + f([a], [a]) # type: ignore[call-arg] f = methodcaller(0, "bar", f=5) assert f([a]) == 5 @@ -354,15 +354,12 @@ def baz(*args, **kwds): def test_repr(self): assert repr(methodcaller(0, "lower")) == "domdf_python_tools.getters.methodcaller(0, 'lower')" assert repr(methodcaller(1, "__iter__")) == "domdf_python_tools.getters.methodcaller(1, '__iter__')" - assert repr( - methodcaller(1, "__iter__", "arg1") - ) == "domdf_python_tools.getters.methodcaller(1, '__iter__', 'arg1')" - assert repr( - methodcaller(1, "__iter__", kw1="kwarg1") - ) == "domdf_python_tools.getters.methodcaller(1, '__iter__', kw1='kwarg1')" - assert repr( - methodcaller(1, "__iter__", "arg1", "arg2", kw1="kwarg1", kw2="kwarg2") - ) == "domdf_python_tools.getters.methodcaller(1, '__iter__', 'arg1', 'arg2', kw1='kwarg1', kw2='kwarg2')" + expected = "domdf_python_tools.getters.methodcaller(1, '__iter__', 'arg1')" + assert repr(methodcaller(1, "__iter__", "arg1")) == expected + expected = "domdf_python_tools.getters.methodcaller(1, '__iter__', kw1='kwarg1')" + assert repr(methodcaller(1, "__iter__", kw1="kwarg1")) == expected + expected = "domdf_python_tools.getters.methodcaller(1, '__iter__', 'arg1', 'arg2', kw1='kwarg1', kw2='kwarg2')" + assert repr(methodcaller(1, "__iter__", "arg1", "arg2", kw1="kwarg1", kw2="kwarg2")) == expected evaluate(repr(methodcaller(0, "lower"))) evaluate(repr(methodcaller(1, "__iter__"))) diff --git a/tests/test_import_tools.py b/tests/test_import_tools.py index 71fcc984..35b1479f 100644 --- a/tests/test_import_tools.py +++ b/tests/test_import_tools.py @@ -4,6 +4,7 @@ import re import sys from contextlib import contextmanager +from typing import Any, ContextManager # 3rd party import pytest @@ -22,7 +23,7 @@ sys.path.append("tests") # 3rd party -import discover_demo_module # type: ignore # noqa: E402 +import discover_demo_module # type: ignore[import-not-found] # noqa: E402 def test_discover(): @@ -39,7 +40,8 @@ def test_discover(): def test_discover_function_only(): # Alphabetical order regardless of order in the module. assert discover( - discover_demo_module, match_func=inspect.isfunction + discover_demo_module, + match_func=inspect.isfunction, ) == [ discover_demo_module.foo_in_init, discover_demo_module.submodule_a.bar, @@ -50,7 +52,8 @@ def test_discover_function_only(): def test_discover_class_only(): # Alphabetical order regardless of order in the module. assert discover( - discover_demo_module, match_func=inspect.isclass + discover_demo_module, + match_func=inspect.isclass, ) == [ discover_demo_module.submodule_b.Alice, discover_demo_module.submodule_b.Bob, @@ -59,7 +62,7 @@ def test_discover_class_only(): def test_discover_hasattr(): - def match_func(obj): + def match_func(obj: Any): return hasattr(obj, "foo") assert discover(discover_demo_module, match_func=match_func) == [] @@ -102,7 +105,7 @@ def raises_attribute_error(obj, **kwargs): pytest.param(HasPath, haspath_error, id="HasPath"), ], ) -def test_discover_errors(obj, expects): +def test_discover_errors(obj: Any, expects: ContextManager): with expects: discover(obj) @@ -114,14 +117,16 @@ def test_discover_entry_points(advanced_data_regression: AdvancedDataRegressionF def test_discover_entry_points_by_name_object_match_func(advanced_data_regression: AdvancedDataRegressionFixture): entry_points = discover_entry_points_by_name( - "flake8.extension", object_match_func=lambda f: f.__name__.startswith("break") + "flake8.extension", + object_match_func=lambda f: f.__name__.startswith("break"), ) advanced_data_regression.check({k: v.__name__ for k, v in entry_points.items()}) def test_discover_entry_points_by_name_name_match_func(advanced_data_regression: AdvancedDataRegressionFixture): entry_points = discover_entry_points_by_name( - "flake8.extension", name_match_func=lambda n: n.startswith("pycodestyle.") + "flake8.extension", + name_match_func=lambda n: n.startswith("pycodestyle."), ) advanced_data_regression.check({k: v.__name__ for k, v in entry_points.items()}) @@ -134,46 +139,46 @@ def test_discover_entry_points_by_name_name_match_func(advanced_data_regression: 3.7, marks=[ only_version(3.7, reason="Output differs on Python 3.7"), - not_pypy("Output differs on PyPy") - ] + not_pypy("Output differs on PyPy"), + ], ), pytest.param( "3.7-pypy", marks=[ only_version(3.7, reason="Output differs on Python 3.7"), - only_pypy("Output differs on PyPy") - ] + only_pypy("Output differs on PyPy"), + ], ), pytest.param( 3.8, marks=[ only_version(3.8, reason="Output differs on Python 3.8"), - not_pypy("Output differs on PyPy 3.8") - ] + not_pypy("Output differs on PyPy 3.8"), + ], ), pytest.param( "3.8_pypy", marks=[ only_version(3.8, reason="Output differs on Python 3.8"), - only_pypy("Output differs on PyPy 3.8") - ] + only_pypy("Output differs on PyPy 3.8"), + ], ), pytest.param( 3.9, marks=[ only_version(3.9, reason="Output differs on Python 3.9"), - not_pypy("Output differs on PyPy 3.9") - ] + not_pypy("Output differs on PyPy 3.9"), + ], ), pytest.param( "3.9_pypy", marks=[ only_version(3.9, reason="Output differs on Python 3.9"), - only_pypy("Output differs on PyPy 3.9") - ] + only_pypy("Output differs on PyPy 3.9"), + ], ), pytest.param("3.10", marks=only_version("3.10", reason="Output differs on Python 3.10")), - ] + ], ) @@ -242,7 +247,7 @@ def freedesktop_os_release(): [ pytest.param('', marks=pytest.mark.skipif(on_alt_linux, reason="Not for ALT Linux")), pytest.param("altlinux", marks=pytest.mark.skipif(not on_alt_linux, reason="Only for ALT Linux")), - ] + ], ) def test_iter_submodules_asyncio( platform, diff --git a/tests/test_iterative.py b/tests/test_iterative.py index ca7b3524..37290a25 100644 --- a/tests/test_iterative.py +++ b/tests/test_iterative.py @@ -21,7 +21,7 @@ from itertools import islice from random import shuffle from types import GeneratorType -from typing import Any, Iterable, List, Optional, Sequence, Tuple, TypeVar +from typing import Any, Iterable, List, MutableSequence, Optional, Sequence, Tuple, TypeVar # 3rd party import pytest @@ -138,7 +138,7 @@ def test_len(capsys): ([((1, 2), (3, 4)), ((5, 6), (7, 8))], [1, 2, 3, 4, 5, 6, 7, 8]), ((((1, 2), (3, 4)), ((5, 6), (7, 8))), [1, 2, 3, 4, 5, 6, 7, 8]), ((("12", "34"), ("56", "78")), ['1', '2', '3', '4', '5', '6', '7', '8']), - ] + ], ) def test_double_chain(value, expects): assert list(double_chain(value)) == expects @@ -164,9 +164,9 @@ def test_make_tree(advanced_file_regression: AdvancedFileRegressionFixture): ], ], "domdf_python_tools==2.2.0", - ]) + ]), ), - advanced_file_regression + advanced_file_regression, ) @@ -177,9 +177,9 @@ def test_make_tree(advanced_file_regression: AdvancedFileRegressionFixture): ["abc", "def", ["ghi", "jkl", "mno", "pqr"]], ["abc", "def", "ghi", "jkl", ["mno", "pqr"]], ["abc", "def", "ghi", "jkl", "mno", "pqr"], - ] + ], ) -def test_flatten(data, advanced_data_regression: AdvancedDataRegressionFixture): +def test_flatten(data: List, advanced_data_regression: AdvancedDataRegressionFixture): advanced_data_regression.check(list(flatten(data))) @@ -191,9 +191,9 @@ def test_flatten(data, advanced_data_regression: AdvancedDataRegressionFixture): pytest.param(['1', '3', '5', '7', '9'], id="numerical_strings"), pytest.param(["1.2", "3.4", "5.6", "7.8", "9.0"], id="float strings"), pytest.param(["0.9", "0.12.4", '1', "2.5"], id="versions"), - ] + ], ) -def test_natmin(data): +def test_natmin(data: MutableSequence): orig_data = data[:] for _ in range(5): shuffle(data) @@ -208,9 +208,9 @@ def test_natmin(data): pytest.param(['1', '3', '5', '7', '9'], id="numerical_strings"), pytest.param(["1.2", "3.4", "5.6", "7.8", "9.0"], id="float strings"), pytest.param(["0.9", "0.12.4", '1', "2.5"], id="versions"), - ] + ], ) -def test_natmax(data): +def test_natmax(data: MutableSequence): orig_data = data[:] for _ in range(5): shuffle(data) @@ -233,9 +233,8 @@ def test_groupfloats(): def test_ranges_from_iterable(): expects = [(170.0, 170.15), (171.05, 171.2)] - assert list( - ranges_from_iterable([170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], step=0.05) - ) == expects + ranges = ranges_from_iterable([170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], step=0.05) + assert list(ranges) == expects expects = [(1, 5), (7, 10)] assert list(ranges_from_iterable([1, 2, 3, 4, 5, 7, 8, 9, 10])) == expects @@ -255,7 +254,7 @@ def _extend_param(sequence: str, expects: Any): _extend_param("abcde", "abcde"), pytest.param(('a', 'b', 'c', 'd', 'e'), "abcde", id="tuple"), pytest.param(['a', 'b', 'c', 'd', 'e'], "abcde", id="list"), - ] + ], ) def test_extend(sequence: Sequence[str], expects: str): assert ''.join(extend(sequence, 4)) == expects @@ -271,7 +270,7 @@ def test_extend(sequence: Sequence[str], expects: str): _extend_param("abcde", "abcde"), pytest.param(('a', 'b', 'c', 'd', 'e'), "abcde", id="tuple"), pytest.param(['a', 'b', 'c', 'd', 'e'], "abcde", id="list"), - ] + ], ) def test_extend_with(sequence: Sequence[str], expects: str): assert ''.join(extend_with(sequence, 4, 'z')) == expects diff --git a/tests/test_namedlist.py b/tests/test_namedlist.py index 81bdac2d..edb398cb 100644 --- a/tests/test_namedlist.py +++ b/tests/test_namedlist.py @@ -121,7 +121,7 @@ class TestNamedListFunction(NamedListTest): class TestNamedlistSubclassFunction: - class ShoppingList(namedlist()): # type: ignore + class ShoppingList(namedlist()): # type: ignore[misc] pass shopping_list = ShoppingList(["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"]) diff --git a/tests/test_paths.py b/tests/test_paths.py index 22e411be..4639250a 100644 --- a/tests/test_paths.py +++ b/tests/test_paths.py @@ -20,7 +20,7 @@ # 3rd party import pytest from coincidence.regressions import AdvancedDataRegressionFixture -from coincidence.selectors import not_pypy, not_windows, only_windows +from coincidence.selectors import max_version, not_pypy, not_windows, only_windows # this package from domdf_python_tools import paths @@ -35,9 +35,10 @@ sort_paths, traverse_to_file ) +from domdf_python_tools.typing import PathLike -def test_maybe_make(tmp_pathplus): +def test_maybe_make(tmp_pathplus: PathPlus): test_dir = tmp_pathplus / "maybe_make" assert test_dir.exists() is False @@ -64,7 +65,7 @@ def test_maybe_make(tmp_pathplus): assert test_dir.is_file() -def test_maybe_make_pathplus(tmp_pathplus): +def test_maybe_make_pathplus(tmp_pathplus: PathPlus): test_dir = tmp_pathplus / "maybe_make" assert test_dir.exists() is False @@ -91,7 +92,7 @@ def test_maybe_make_pathplus(tmp_pathplus): assert test_dir.is_file() -def test_maybe_make_string(tmp_pathplus): +def test_maybe_make_string(tmp_pathplus: PathPlus): test_dir = tmp_pathplus / "maybe_make" assert test_dir.exists() is False @@ -118,7 +119,7 @@ def test_maybe_make_string(tmp_pathplus): assert test_dir.is_file() -def test_maybe_make_parents(tmp_pathplus): +def test_maybe_make_parents(tmp_pathplus: PathPlus): test_dir = tmp_pathplus / "maybe_make" / "child1" / "child2" assert test_dir.exists() is False @@ -134,7 +135,7 @@ def test_maybe_make_parents(tmp_pathplus): assert test_dir.exists() -def test_maybe_make_parents_pathplus(tmp_pathplus): +def test_maybe_make_parents_pathplus(tmp_pathplus: PathPlus): test_dir = tmp_pathplus / "maybe_make" / "child1" / "child2" assert test_dir.exists() is False @@ -150,7 +151,7 @@ def test_maybe_make_parents_pathplus(tmp_pathplus): assert test_dir.exists() -def test_parent_path(tmp_pathplus): +def test_parent_path(tmp_pathplus: PathPlus): dir1 = tmp_pathplus / "dir1" dir2 = dir1 / "dir2" dir3 = dir2 / "dir3" @@ -172,7 +173,7 @@ def test_parent_path(tmp_pathplus): (None, pathlib.Path("/home/username/Documents/letter.doc")), ], ) -def test_relpath(relto, relpath): +def test_relpath(relto: PathLike, relpath: PathLike): path = "/home/username/Documents/letter.doc" assert paths.relpath(path, relative_to=relto) == pathlib.Path(relpath) assert isinstance(paths.relpath(path, relative_to=relto), pathlib.Path) @@ -186,13 +187,13 @@ def test_relpath(relto, relpath): ("c:/users/username/Documents", "letter.doc"), ( pathlib.Path("c:/users/username/Documents/games/chess.py"), - "c:/users/username/Documents/letter.doc" + "c:/users/username/Documents/letter.doc", ), (pathlib.Path("c:/users/username/Documents"), "letter.doc"), (None, pathlib.Path("c:/users/username/Documents/letter.doc")), ], ) -def test_relpath_windows(relto, relpath): +def test_relpath_windows(relto: PathLike, relpath: PathLike): path = "c:/users/username/Documents/letter.doc" assert paths.relpath(path, relative_to=relto) == pathlib.Path(relpath) assert isinstance(paths.relpath(path, relative_to=relto), pathlib.Path) @@ -245,7 +246,7 @@ def teardown_class(cls): pathlib.Path(file).unlink() -def test_clean_writer(tmp_pathplus): +def test_clean_writer(tmp_pathplus: PathPlus): tempfile = tmp_pathplus / "tmpfile.txt" test_string = '\n'.join([ @@ -287,36 +288,45 @@ def test_clean_writer(tmp_pathplus): @pytest.mark.parametrize( "input_string, output_string", - [([ - "Top line", - " ", - "Line with whitespace ", - "Line with tabs\t\t\t\t ", - "No newline at end of file", - ], [ - "Top line", - '', - "Line with whitespace", - "Line with tabs", - "No newline at end of file", - '', - ]), - ([ - "Top line", - " ", - "Line with whitespace ", - "Line with tabs\t\t\t\t ", - "Too many newlines\n\n\n\n\n\n\n" - ], [ - "Top line", - '', - "Line with whitespace", - "Line with tabs", - "Too many newlines", - '', - ]), ([], [''])] + [ + ( + [ + "Top line", + " ", + "Line with whitespace ", + "Line with tabs\t\t\t\t ", + "No newline at end of file", + ], + [ + "Top line", + '', + "Line with whitespace", + "Line with tabs", + "No newline at end of file", + '', + ], + ), + ( + [ + "Top line", + " ", + "Line with whitespace ", + "Line with tabs\t\t\t\t ", + "Too many newlines\n\n\n\n\n\n\n", + ], + [ + "Top line", + '', + "Line with whitespace", + "Line with tabs", + "Too many newlines", + '', + ], + ), + ([], ['']), + ], ) -def test_pathplus_write_clean(tmp_pathplus, input_string, output_string): +def test_pathplus_write_clean(tmp_pathplus: PathPlus, input_string: str, output_string: str): tempfile = tmp_pathplus / "tmpfile.txt" tempfile.write_clean('\n'.join(input_string)) @@ -360,7 +370,7 @@ def test_instantiate_wrong_platform(): paths.WindowsPathPlus() -def test_copytree(tmp_pathplus): +def test_copytree(tmp_pathplus: PathPlus): srcdir = tmp_pathplus / "src" srcdir.mkdir() @@ -411,7 +421,7 @@ def test_copytree(tmp_pathplus): assert (destdir / 'c' / "c.txt").is_file() -def test_copytree_exists(tmp_pathplus): +def test_copytree_exists(tmp_pathplus: PathPlus): srcdir = tmp_pathplus / "src" srcdir.mkdir() @@ -465,7 +475,7 @@ def test_copytree_exists(tmp_pathplus): condition=(sys.version_info < (3, 6, 9) and platform.python_implementation() == "PyPy"), reason="Fails with unrelated error on PyPy 7.1.1 / 3.6.1", ) -def test_copytree_exists_stdlib(tmp_pathplus): +def test_copytree_exists_stdlib(tmp_pathplus: PathPlus): srcdir = tmp_pathplus / "src" srcdir.mkdir() @@ -499,7 +509,7 @@ def test_copytree_exists_stdlib(tmp_pathplus): shutil.copytree(srcdir, destdir) -def test_write_lines(tmp_pathplus): +def test_write_lines(tmp_pathplus: PathPlus): tmp_file = tmp_pathplus / "test.txt" contents = [ @@ -521,6 +531,14 @@ def test_write_lines(tmp_pathplus): content = tmp_file.read_text() assert content == "this\nis\na\nlist\nof\nwords\nto\nwrite\nto\nthe\nfile\n" + with pytest.warns( + UserWarning, + match="Passing a string to PathPlus.write_lines writes each character to its own line.", + ): + tmp_file.write_lines("abcdefg") + + assert tmp_file.read_text() == "a\nb\nc\nd\ne\nf\ng\n" + def test_write_lines_trailing_whitespace(tmp_pathplus: PathPlus): tmp_file = tmp_pathplus / "test.txt" @@ -567,7 +585,7 @@ def test_read_lines(tmp_pathplus: PathPlus): assert tmp_file.read_lines() == expected -def test_dump_json(tmpdir): +def test_dump_json(tmpdir: str): tmpdir_p = PathPlus(tmpdir) tmp_file = tmpdir_p / "test.txt" @@ -587,7 +605,7 @@ def test_dump_json(tmpdir): """) -def test_dump_json_gzip(tmpdir): +def test_dump_json_gzip(tmpdir: str): tmpdir_p = PathPlus(tmpdir) tmp_file = tmpdir_p / "test.txt" @@ -599,7 +617,7 @@ def test_dump_json_gzip(tmpdir): assert tmp_file.load_json(decompress=True) == {"key": "value", "int": 1234, "float": 12.34} -def test_load_json(tmpdir): +def test_load_json(tmpdir: str): tmpdir_p = PathPlus(tmpdir) tmp_file = tmpdir_p / "test.txt" @@ -608,12 +626,13 @@ def test_load_json(tmpdir): assert tmp_file.load_json() == {"key": "value", "int": 1234, "float": 12.34} - tmp_file.write_text(dedent("""\ + json_string = dedent("""\ { "key": "value", "int": 1234, "float": 12.34 - }""")) + }""") + tmp_file.write_text(json_string) assert tmp_file.load_json() == {"key": "value", "int": 1234, "float": 12.34} @@ -642,7 +661,7 @@ def test_in_directory(tmp_pathplus: PathPlus): ("foo/foo.yml", "foo"), ("foo/bar/foo.yml", "foo/bar"), ("foo/bar/baz/foo.yml", "foo/bar/baz"), - ] + ], ) def test_traverse_to_file(tmp_pathplus: PathPlus, location: str, expected: str): (tmp_pathplus / location).parent.maybe_make(parents=True) @@ -711,7 +730,13 @@ def test_iterchildren_match(advanced_data_regression: AdvancedDataRegressionFixt child_paths = sorted(p.relative_to(repo_path).as_posix() for p in children) for exclude_filename in { - ".coverage", "pathtype_demo.py", "dist", "htmlcov", "conda", ".idea", "mutdef.py" + ".coverage", + "pathtype_demo.py", + "dist", + "htmlcov", + "conda", + ".idea", + "mutdef.py", }: if exclude_filename in child_paths: child_paths.remove(exclude_filename) @@ -812,7 +837,7 @@ def test_iterchildren_no_exclusions(tmp_pathplus: PathPlus): ( "/home/domdf/Python/01 GitHub Repos/03 Libraries/domdf_python_tools/**/*.py", "/home/domdf/Python/01 GitHub Repos/03 Libraries/domdf_python_tools/domdf_python_tools/pagesizes/units.py", - True + True, ), ("domdf_python_tools/**/*.py", "domdf_python_tools/domdf_python_tools/pagesizes/units.py", True), ("**/*.py", ".pre-commit-config.yaml", False), @@ -831,7 +856,7 @@ def test_iterchildren_no_exclusions(tmp_pathplus: PathPlus): ("**/.tox/*", "foo/bar/.tox/build", True), ("**/.tox/**", "foo/bar/.tox/build", True), ("**/.tox/**", "foo/bar/.tox/build/baz", True), - ] + ], ) def test_matchglob(pattern: str, filename: str, match: bool): assert matchglob(filename, pattern) is match @@ -918,14 +943,21 @@ def test_sort_paths(): "c:/a/bΓ©", "//some/share/", "//some/share/a/b.c", - "//some/share/a/b%#cΓ©" + "//some/share/a/b%#cΓ©", ] else: _from_uri_paths = ['/', "/home/domdf/β˜ƒ.txt", "/a/b.c", "/a/b%#c"] @pytest.mark.parametrize("path", _from_uri_paths) -@pytest.mark.parametrize("left_type", [pathlib.PurePath, pathlib.Path, PathPlus]) +@pytest.mark.parametrize( + "left_type", + [ + pytest.param(pathlib.PurePath, marks=max_version((3, 13))), + pathlib.Path, + PathPlus, + ], + ) def test_pathplus_from_uri(path: str, left_type: Type): assert PathPlus.from_uri(left_type(path).as_uri()).as_posix() == path @@ -962,7 +994,7 @@ def test_write_text_line_endings(tmp_pathplus: PathPlus): @pytest.fixture() -def move_example_file(tmp_pathplus) -> PathPlus: +def move_example_file(tmp_pathplus: PathPlus) -> PathPlus: src_file = tmp_pathplus / "tmpdir/foo" src_file.parent.maybe_make(parents=True) src_file.write_bytes(b"spam") diff --git a/tests/test_paths_stdlib.py b/tests/test_paths_stdlib.py index 82d0a85c..b90877ea 100644 --- a/tests/test_paths_stdlib.py +++ b/tests/test_paths_stdlib.py @@ -24,13 +24,14 @@ # this package from domdf_python_tools.compat import PYPY from domdf_python_tools.paths import PathPlus, PosixPathPlus, WindowsPathPlus +from domdf_python_tools.typing import PathLike try: # stdlib import grp import pwd except ImportError: - grp = pwd = None # type: ignore + grp = pwd = None # type: ignore[assignment] if sys.version_info[:2] >= (3, 10): # stdlib @@ -63,11 +64,11 @@ def BASE(tmp_pathplus: PathPlus) -> Iterator[PathPlus]: if os.name == "nt": # Workaround for http://bugs.python.org/issue13772. - def dirlink(src, dest): + def dirlink(src: PathLike, dest: PathLike): os.symlink(src, dest, target_is_directory=True) else: - def dirlink(src, dest): + def dirlink(src: PathLike, dest: PathLike): os.symlink(src, dest) os.mkdir(join("dirA")) @@ -100,17 +101,13 @@ def dirlink(src, dest): shutil.rmtree(top_dir) -def assertEqualNormCase(path_a, path_b): - assert (os.path.normcase(path_a) == os.path.normcase(path_b)) - - if os.name == "nt": # Workaround for http://bugs.python.org/issue13772. - def dirlink(src, dest): + def dirlink(src: PathLike, dest: PathLike) -> None: os.symlink(src, dest, target_is_directory=True) else: - def dirlink(src, dest): + def dirlink(src: PathLike, dest: PathLike) -> None: os.symlink(src, dest) @@ -129,6 +126,9 @@ def test_stat(BASE: PathPlus): @pytest.mark.skipif(not hasattr(socket, "AF_UNIX"), reason="Unix sockets required") def test_is_socket_true(BASE: PathPlus): + if sys.platform == "win32": + return + P = PathPlus(BASE, "mysock") sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) try: @@ -148,7 +148,7 @@ def test_cwd(): p = PathPlus.cwd() q = PathPlus(os.getcwd()) assert (p == q) - assertEqualNormCase(str(p), str(q)) + assert (os.path.normcase(str(p)) == os.path.normcase(str(q))) assert (type(p) is type(q)) assert (p.is_absolute()) @@ -159,7 +159,7 @@ def test_read_write_text(BASE: PathPlus): assert ((p / "fileA").read_text(encoding="utf-8", errors="ignore") == "bcdefg") # Check that trying to write bytes does not truncate the file. with pytest.raises(TypeError): - (p / "fileA").write_text(b"somebytes") # type: ignore + (p / "fileA").write_text(b"somebytes") # type: ignore[arg-type] assert ((p / "fileA").read_text(encoding="latin-1") == "Γ€bcdefg") @@ -437,7 +437,7 @@ def test_mkdir_concurrent_parent_creation(BASE: PathPlus): real_mkdir = os.mkdir - def my_mkdir(path, mode=0o777): + def my_mkdir(path: PathLike, mode: int = 0o777): path = str(path) # Emulate another process that would create the directory # just before we try to create it ourselves. We do it diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 530cfcb2..256ef374 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -13,7 +13,7 @@ import random import types from textwrap import dedent -from typing import no_type_check +from typing import Any, no_type_check # 3rd party import pytest @@ -32,13 +32,13 @@ class list2(list): class list3(list): - def __repr__(self): + def __repr__(self) -> str: return list.__repr__(self) class list_custom_repr(list): - def __repr__(self): + def __repr__(self) -> str: return '*' * len(list.__repr__(self)) @@ -49,14 +49,14 @@ class tuple2(tuple): class tuple3(tuple): __slots__ = () - def __repr__(self): + def __repr__(self) -> str: return tuple.__repr__(self) class tuple_custom_repr(tuple): __slots__ = () - def __repr__(self): + def __repr__(self) -> str: return '*' * len(tuple.__repr__(self)) @@ -66,13 +66,13 @@ class set2(set): class set3(set): - def __repr__(self): + def __repr__(self) -> str: return set.__repr__(self) class set_custom_repr(set): - def __repr__(self): + def __repr__(self) -> str: return '*' * len(set.__repr__(self)) @@ -82,13 +82,13 @@ class frozenset2(frozenset): class frozenset3(frozenset): - def __repr__(self): + def __repr__(self) -> str: return frozenset.__repr__(self) class frozenset_custom_repr(frozenset): - def __repr__(self): + def __repr__(self) -> str: return '*' * len(frozenset.__repr__(self)) @@ -98,19 +98,19 @@ class dict2(dict): class dict3(dict): - def __repr__(self): + def __repr__(self) -> str: return dict.__repr__(self) class dict_custom_repr(dict): - def __repr__(self): + def __repr__(self) -> str: return '*' * len(dict.__repr__(self)) class Unorderable: - def __repr__(self): + def __repr__(self) -> str: return str(id(self)) @@ -120,25 +120,25 @@ class Orderable: def __init__(self, hash): # noqa: A002 # pylint: disable=redefined-builtin self._hash = hash - def __lt__(self, other): + def __lt__(self, other) -> bool: return False - def __gt__(self, other): + def __gt__(self, other) -> bool: return self != other - def __le__(self, other): + def __le__(self, other) -> bool: return self == other - def __ge__(self, other): + def __ge__(self, other) -> bool: return True - def __eq__(self, other): + def __eq__(self, other) -> bool: return self is other - def __ne__(self, other): + def __ne__(self, other) -> bool: return self is not other - def __hash__(self): + def __hash__(self) -> int: return self._hash @@ -175,7 +175,7 @@ def test_list(self): 'tomato', 'cherry', 'blackcurrant', - ]""" + ]""", ) @no_type_check @@ -212,7 +212,7 @@ def test_init(self): ..., list(range(100)), list(range(200)), - ] + ], ) def test_basic(self, safe): # Verify .isrecursive() and .isreadable() w/o recursion @@ -328,7 +328,7 @@ def test_basic_line_wrap(self): "controldesk_runtime_us": 0, "main_code_runtime_us": 0, "read_io_runtime_us": 0, - "write_io_runtime_us": 43690 + "write_io_runtime_us": 43690, } exp = """\ { @@ -417,7 +417,7 @@ def test_width(self): assert FancyPrinter(width=16).pformat(o) == expected assert FancyPrinter(width=25).pformat(o) == expected assert FancyPrinter(width=14).pformat( - o + o, ) == """\ [ [ @@ -506,7 +506,7 @@ def test_mapping_proxy(self): d = dict(zip(words, itertools.count())) m = types.MappingProxyType(d) assert FancyPrinter().pformat( - m + m, ) == """\ mappingproxy({ 'the': 0, @@ -522,7 +522,7 @@ def test_mapping_proxy(self): d = collections.OrderedDict(zip(words, itertools.count())) m = types.MappingProxyType(d) assert FancyPrinter().pformat( - m + m, ) == """\ mappingproxy(OrderedDict([ ('the', 0), @@ -556,9 +556,9 @@ def test_subclassing(self, advanced_file_regression: AdvancedFileRegressionFixtu pytest.param(set(range(7)), 20, id="case_1"), pytest.param(set2(range(7)), 20, id="case_2"), pytest.param(set3(range(7)), 20, id="case_3"), - ] + ], ) - def test_set_reprs(self, value, width, advanced_file_regression: AdvancedFileRegressionFixture): + def test_set_reprs(self, value, width: int, advanced_file_regression: AdvancedFileRegressionFixture): assert FancyPrinter().pformat(set()) == "set()" assert FancyPrinter().pformat(set(range(3))) == "{0, 1, 2}" advanced_file_regression.check(FancyPrinter(width=width).pformat(value)) @@ -569,9 +569,14 @@ def test_set_reprs(self, value, width, advanced_file_regression: AdvancedFileReg pytest.param(frozenset(range(7)), 20, id="case_1"), pytest.param(frozenset2(range(7)), 20, id="case_2"), pytest.param(frozenset3(range(7)), 20, id="case_3"), - ] + ], ) - def test_frozenset_reprs(self, value, width, advanced_file_regression: AdvancedFileRegressionFixture): + def test_frozenset_reprs( + self, + value: frozenset, + width: int, + advanced_file_regression: AdvancedFileRegressionFixture, + ): assert FancyPrinter().pformat(frozenset()) == "frozenset()" assert FancyPrinter().pformat(frozenset(range(3))) == "frozenset({0, 1, 2})" advanced_file_regression.check(FancyPrinter(width=width).pformat(value)) @@ -601,14 +606,14 @@ def test_sort_unorderable_values(self): assert clean(FancyPrinter().pformat(set(keys))) == '{' + ','.join(map(repr, skeys)) + ",}" assert clean(FancyPrinter().pformat(frozenset(keys))) == "frozenset({" + ','.join(map(repr, skeys)) + ",})" - assert clean(FancyPrinter().pformat(dict.fromkeys(keys)) - ) == '{' + ','.join("%r:None" % k for k in keys) + ",}" + expected = '{' + ','.join("%r:None" % k for k in keys) + ",}" + assert clean(FancyPrinter().pformat(dict.fromkeys(keys))) == expected # Issue 10017: TypeError on user-defined types as dict keys. assert FancyPrinter().pformat({Unorderable: 0, 1: 0}) == "{1: 0, " + repr(Unorderable) + ": 0}" # Issue 14998: TypeError on tuples with NoneTypes as dict keys. - keys = [(1, ), (None, )] # type: ignore + keys = [(1, ), (None, )] # type: ignore[list-item] assert FancyPrinter().pformat(dict.fromkeys(keys, 0)) == "{%r: 0, %r: 0}" % tuple(sorted(keys, key=id)) def test_sort_orderable_and_unorderable_values(self): @@ -618,8 +623,8 @@ def test_sort_orderable_and_unorderable_values(self): # self-test assert a < b assert str(type(b)) < str(type(a)) - assert sorted([b, a]) == [a, b] # type: ignore - assert sorted([a, b]) == [a, b] # type: ignore + assert sorted([b, a]) == [a, b] # type: ignore[type-var] + assert sorted([a, b]) == [a, b] # type: ignore[type-var] # set assert FancyPrinter(width=1).pformat({b, a}) == f"{{\n {a!r},\n {b!r},\n }}" assert FancyPrinter(width=1).pformat({a, b}) == f"{{\n {a!r},\n {b!r},\n }}" @@ -630,8 +635,9 @@ def test_sort_orderable_and_unorderable_values(self): def test_str_wrap(self): # pprint tries to wrap strings intelligently fox = "the quick brown fox jumped over a lazy dog" - assert FancyPrinter(width=19 - ).pformat(fox) == """\ + assert FancyPrinter( + width=19, + ).pformat(fox) == """\ ('the quick brown ' 'fox jumped over ' 'a lazy dog')""" @@ -651,13 +657,13 @@ def test_str_wrap(self): special = "Portons dix bons \"whiskys\"\nΓ  l'avocat goujat\t qui fumait au zoo" assert FancyPrinter(width=68).pformat(special) == repr(special) assert FancyPrinter(width=31).pformat( - special + special, ) == """\ ('Portons dix bons "whiskys"\\n' "Γ  l'avocat goujat\\t qui " 'fumait au zoo')""" assert FancyPrinter(width=20).pformat( - special + special, ) == """\ ('Portons dix bons ' '"whiskys"\\n' @@ -731,16 +737,16 @@ def test_compact(self): 14, 15], [], [0], [0, 1], [0, 1, 2], [0, 1, 2, 3], [0, 1, 2, 3, 4]]""" - assert FancyPrinter(width=47, compact=True).pformat(o, ) == expected + assert FancyPrinter(width=47, compact=True).pformat(o) == expected def test_compact_width(self): levels = 20 number = 10 o = [0] * number for i in range(levels - 1): - o = [o] # type: ignore + o = [o] # type: ignore[list-item] for w in range(levels * 2 + 1, levels + 3 * number - 1): - lines = FancyPrinter(width=w, compact=True).pformat(o, ).splitlines() + lines = FancyPrinter(width=w, compact=True).pformat(o).splitlines() maxwidth = max(map(len, lines)) assert maxwidth <= w maxwidth > w - 3 # pylint: disable=pointless-statement @@ -764,31 +770,31 @@ def test_bytes_wrap(self): special = bytes(range(16)) assert FancyPrinter(width=61).pformat(special) == repr(special) assert FancyPrinter(width=48).pformat( - special + special, ) == """\ (b'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""" assert FancyPrinter(width=32).pformat( - special + special, ) == """\ (b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""" assert FancyPrinter(width=1).pformat( - special + special, ) == """\ (b'\\x00\\x01\\x02\\x03' b'\\x04\\x05\\x06\\x07' b'\\x08\\t\\n\\x0b' b'\\x0c\\r\\x0e\\x0f')""" - assert FancyPrinter(width=21).pformat({'a': 1, 'b': letters, 'c': 2} == """\ + assert FancyPrinter(width=21).pformat({'a': 1, 'b': letters, 'c': 2}) == """\ { 'a': 1, 'b': b'abcdefghijkl' b'mnopqrstuvwx' b'yz', 'c': 2, - }""") + }""" assert FancyPrinter(width=20).pformat({'a': 1, 'b': letters, 'c': 2}) == """\ { 'a': 1, @@ -852,9 +858,9 @@ def test_bytes_wrap(self): ), pytest.param([[[[[bytearray(b"abcdefghijklmnopqrstuvwxyz")]]]]], 37, id="case_11"), pytest.param([[[[[bytearray(range(16))]]]]], 50, id="case_12"), - ] + ], ) - def test_bytearray_wrap(self, value, width, advanced_file_regression: AdvancedFileRegressionFixture): + def test_bytearray_wrap(self, value, width: int, advanced_file_regression: AdvancedFileRegressionFixture): advanced_file_regression.check(FancyPrinter(width=width).pformat(value)) def test_default_dict(self, advanced_file_regression: AdvancedFileRegressionFixture): @@ -892,7 +898,7 @@ def test_deque(self): words = "the quick brown fox jumped over a lazy dog".split() d = collections.deque(zip(words, itertools.count())) assert FancyPrinter().pformat( - d + d, ) == """\ deque([('the', 0), ('quick', 1), @@ -905,7 +911,7 @@ def test_deque(self): ('dog', 8)])""" d = collections.deque(zip(words, itertools.count()), maxlen=7) assert FancyPrinter().pformat( - d + d, ) == """\ deque([('brown', 2), ('fox', 3), @@ -930,6 +936,12 @@ def test_user_list(self, advanced_file_regression: AdvancedFileRegressionFixture d = collections.UserList(zip(words, itertools.count())) advanced_file_regression.check(FancyPrinter().pformat(d)) + _sl_str_param = StringList([ + "('the quick brown '", + " 'fox jumped over '", + " 'a lazy dog')", + ]) + @pytest.mark.parametrize( "value, width, expects", [ @@ -937,27 +949,25 @@ def test_user_list(self, advanced_file_regression: AdvancedFileRegressionFixture ( collections.UserString("the quick brown fox jumped over a lazy dog"), 20, - str(StringList([ - "('the quick brown '", - " 'fox jumped over '", - " 'a lazy dog')", - ])) + str(_sl_str_param), + ), + ( + {1: collections.UserString("the quick brown fox jumped over a lazy dog")}, + 20, + str( + StringList([ + '{', + " 1: 'the quick '", + " 'brown fox '", + " 'jumped over a '", + " 'lazy dog',", + " }", + ]), + ), ), - ({1: collections.UserString("the quick brown fox jumped over a lazy dog")}, - 20, - str( - StringList([ - '{', - " 1: 'the quick '", - " 'brown fox '", - " 'jumped over a '", - " 'lazy dog',", - " }" - ]) - )), - ] + ], ) - def test_user_string(self, value, width, expects): + def test_user_string(self, value: Any, width: int, expects: str): assert FancyPrinter(width=width).pformat(value) == expects diff --git a/tests/test_stringlist.py b/tests/test_stringlist.py index 42e95b3e..1700b52f 100644 --- a/tests/test_stringlist.py +++ b/tests/test_stringlist.py @@ -2,7 +2,7 @@ import pickle import textwrap from textwrap import dedent -from typing import no_type_check +from typing import List, Tuple, no_type_check # 3rd party import pytest @@ -24,7 +24,7 @@ def test_creation(self): assert StringList("1\n") == ['1', ''] with pytest.raises(TypeError, match="'int' object is not iterable"): - StringList(1) # type: ignore + StringList(1) # type: ignore[arg-type] def test_append(self): sl = StringList() @@ -64,7 +64,22 @@ def test_insert(self): sl.insert(100, "end") assert sl == [ - "1234", "foo", "bar", '', "a line", '', '', '', "hello", "world", '', '', '', "1234", "baz", "end" + "1234", + "foo", + "bar", + '', + "a line", + '', + '', + '', + "hello", + "world", + '', + '', + '', + "1234", + "baz", + "end", ] def test_setitem(self): @@ -215,7 +230,7 @@ def bar(self, listicle: List[Item]): def __repr__(self) -> str: return "Foo()" - """ + """, ) sl = StringList() @@ -458,10 +473,10 @@ def test_size(self): indent.size = 1 assert indent.size == 1 - indent.size = '2' # type: ignore + indent.size = '2' # type: ignore[assignment] assert indent.size == 2 - indent.size = 3.0 # type: ignore + indent.size = 3.0 # type: ignore[assignment] assert indent.size == 3 def test_type(self): @@ -473,7 +488,7 @@ def test_type(self): indent.type = ' ' assert indent.type == ' ' - indent.type = 1 # type: ignore + indent.type = 1 # type: ignore[assignment] assert indent.type == '1' indent.type = ">>> " @@ -551,17 +566,17 @@ def test_delimitedlist(): ("abc\ndef\r\nghi\n\r", [("abc", '\n'), ("def", "\r\n"), ("ghi", '\n'), ('', '\r')]), ("\nabc\ndef\r\nghi\n\r", [('', '\n'), ("abc", '\n'), ("def", "\r\n"), ("ghi", '\n'), ('', '\r')]), ("abcdef", [("abcdef", '')]), - ] + ], ) @joinlines_splitlines_param -def test_splitlines(string, lines): +def test_splitlines(string: str, lines: List[str]): assert splitlines(string) == lines @joinlines_splitlines_param -def test_joinlines(string, lines): +def test_joinlines(string: str, lines: List[Tuple[str, str]]): assert string == joinlines(lines) diff --git a/tests/test_typing.py b/tests/test_typing.py index 8809883a..6815d527 100644 --- a/tests/test_typing.py +++ b/tests/test_typing.py @@ -9,7 +9,7 @@ # stdlib import os import pathlib -from typing import Dict, List, Sequence, Set, Tuple, Union +from typing import Any, Dict, List, Sequence, Set, Tuple, Union # 3rd party import pytest @@ -35,7 +35,7 @@ (12.34, Dict[int, float]), ], ) -def test_check_membership_true(obj, type_): +def test_check_membership_true(obj: Any, type_): # todo: Positions for Tuple and Dict assert check_membership(obj, type_) @@ -57,26 +57,26 @@ def test_check_membership_true(obj, type_): (12.34, Dict[int, str]), ], ) -def test_check_membership_false(obj, type_): +def test_check_membership_false(obj: Any, type_): # todo: Positions for Tuple and Dict assert not check_membership(obj, type_) class MyPathLike(os.PathLike): - def __init__(self, directory, filename): + def __init__(self, directory: PathLike, filename: PathLike): self.directory = str(directory) self.filename = str(filename) - def __fspath__(self): - os.path.join(self.directory, self.filename) + def __fspath__(self) -> str: + return os.path.join(self.directory, self.filename) class MyStr(str): __slots__ = () -class MyPath(type(pathlib.Path())): # type: ignore +class MyPath(type(pathlib.Path())): # type: ignore[misc] pass @@ -97,19 +97,20 @@ class MyPath(type(pathlib.Path())): # type: ignore MyPathLike('.', "test_typing.py"), ], ) -def test_pathlike_true(obj): +def test_pathlike_true(obj: PathLike): assert check_membership(obj, PathLike) @pytest.mark.parametrize( - "obj", [ + "obj", + [ 1234, 12.34, [1, 2, 3, 4, 5], {1, 2, 3, 4, 5}, (1, 2, 3, 4, 5), {'a': 1, 'b': 2}, - ] + ], ) -def test_pathlike_false(obj): +def test_pathlike_false(obj: Any): assert not check_membership(obj, PathLike) diff --git a/tests/test_userlist.py b/tests/test_userlist.py index 77bf9ea1..337ba0f6 100644 --- a/tests/test_userlist.py +++ b/tests/test_userlist.py @@ -25,7 +25,7 @@ class TestList(list_tests.CommonTest): def test_getslice(self): super().test_getslice() l = [0, 1, 2, 3, 4] - u = self.type2test(l) # type: ignore + u = self.type2test(l) # type: ignore[call-arg] for i in range(-3, 6): assert u[:i] == l[:i] assert u[i:] == l[i:] @@ -34,7 +34,7 @@ def test_getslice(self): def test_slice_type(self): l = [0, 1, 2, 3, 4] - u = self.type2test(l) # type: ignore + u = self.type2test(l) # type: ignore[call-arg] assert u[:] != u.__class__ assert u[:] == u @@ -42,7 +42,7 @@ def test_slice_type(self): def test_iadd(self): super().test_iadd() u = [0, 1] - u += self.type2test([0, 1]) # type: ignore + u += self.type2test([0, 1]) # type: ignore[call-arg] assert u == [0, 1, 0, 1] @no_type_check diff --git a/tests/test_utils.py b/tests/test_utils.py index 768bd836..2cd0498c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -13,6 +13,7 @@ import re import sys from collections import namedtuple +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union # 3rd party import click @@ -60,7 +61,7 @@ class TestList2Str: (['a', 2, pathlib.Path("foo.txt")], "a,2,foo.txt"), ], ) - def test_list2str(self, value, expects): + def test_list2str(self, value: Any, expects: str): str_representation = list2str(value) assert isinstance(str_representation, str) assert str_representation == expects @@ -74,7 +75,7 @@ def test_list2str(self, value, expects): (['a', 2, pathlib.Path("foo.txt")], "a;2;foo.txt"), ], ) - def test_list2str_semicolon(self, value, expects): + def test_list2str_semicolon(self, value: Any, expects: str): str_representation = list2str(value, sep=';') assert isinstance(str_representation, str) assert str_representation == expects @@ -85,7 +86,7 @@ class CustomRepr: def __init__(self): pass - def __repr__(self): + def __repr__(self) -> str: return "This is my custom __repr__!" @@ -98,7 +99,7 @@ def __init__(self): no_repr_instance = NoRepr() -def get_mem_addr(obj): +def get_mem_addr(obj: object): if sys.platform == "win32" and platform.python_implementation() != "PyPy": return f"0x0*{hex(id(obj))[2:].upper()}" else: @@ -116,7 +117,7 @@ def get_mem_addr(obj): (no_repr_instance, f""), ], ) -def test_printr(obj, expects, capsys): +def test_printr(obj: Any, expects: str, capsys): printr(obj) captured = capsys.readouterr() @@ -124,7 +125,7 @@ def test_printr(obj, expects, capsys): assert re.match(expects, stdout[0]) -if sys.version_info >= (3, 13): +if sys.version_info[:2] == (3, 13): pure_posix_path_expected = "" else: pure_posix_path_expected = "" @@ -141,7 +142,7 @@ def test_printr(obj, expects, capsys): (no_repr_instance, ""), ], ) -def test_printt(obj, expects, capsys): +def test_printt(obj: Any, expects: str, capsys): printt(obj) captured = capsys.readouterr() @@ -160,7 +161,7 @@ def test_printt(obj, expects, capsys): (no_repr_instance, f""), ], ) -def test_stderr_writer(obj, expects, capsys): +def test_stderr_writer(obj: Any, expects: str, capsys): stderr_writer(obj) captured = capsys.readouterr() @@ -188,7 +189,7 @@ def test_str2tuple(self, value, expects): ("1; 2; 3", (1, 2, 3)), # tests with semicolon ], ) - def test_str2tuple_semicolon(self, value, expects): + def test_str2tuple_semicolon(self, value: str, expects: Tuple[int, ...]): assert isinstance(str2tuple(value, sep=';'), tuple) assert str2tuple(value, sep=';') == expects @@ -196,7 +197,7 @@ def test_str2tuple_semicolon(self, value, expects): class TestStrToBool: @coincidence.testing_boolean_values(extra_truthy=[50, -1]) - def test_strtobool(self, boolean_string, expected_boolean): + def test_strtobool(self, boolean_string, expected_boolean: bool): assert strtobool(boolean_string) == expected_boolean @pytest.mark.parametrize( @@ -210,7 +211,7 @@ def test_strtobool(self, boolean_string, expected_boolean): (0.0, AttributeError), ], ) - def test_strtobool_errors(self, obj, expects): + def test_strtobool_errors(self, obj: Any, expects: Type[Exception]): with pytest.raises(expects): strtobool(obj) @@ -236,7 +237,7 @@ def test_strtobool_errors(self, obj, expects): ("Hello World", "'Hello World'"), ], ) -def test_enquote_value(obj, expects): +def test_enquote_value(obj: Any, expects: Any): assert enquote_value(obj) == expects @@ -268,7 +269,7 @@ def test_cmp(): assert cmp(20, 20) == 0 -def demo_function(arg1, arg2, arg3): +def demo_function(arg1, arg2, arg3): # noqa: MAN001,MAN002 pass @@ -281,29 +282,45 @@ def demo_function(arg1, arg2, arg3): ((1, 2, 3), ("arg1", "arg2", "arg3"), {}, {"arg1": 1, "arg2": 2, "arg3": 3}), ((1, 2, 3), ("arg1", "arg2", "arg3"), None, {"arg1": 1, "arg2": 2, "arg3": 3}), ((1, 2, 3), ("arg1", "arg2", "arg3"), {"arg4": 4}, {"arg1": 1, "arg2": 2, "arg3": 3, "arg4": 4}), - ((1, 2, 3), demo_function, None, { - "arg1": 1, - "arg2": 2, - "arg3": 3, - }), - ((cwd, "wb", -1, "UTF-8"), - pathlib.Path.open, - None, { - "self": cwd, - "mode": "wb", - "buffering": -1, - "encoding": "UTF-8", - }), - (("wb", -1, "UTF-8"), - pathlib.Path().open, - None, { - "mode": "wb", - "buffering": -1, - "encoding": "UTF-8", - }), - ] + ( + (1, 2, 3), + demo_function, + None, + { + "arg1": 1, + "arg2": 2, + "arg3": 3, + }, + ), + ( + (cwd, "wb", -1, "UTF-8"), + pathlib.Path.open, + None, + { + "self": cwd, + "mode": "wb", + "buffering": -1, + "encoding": "UTF-8", + }, + ), + ( + ("wb", -1, "UTF-8"), + pathlib.Path().open, + None, + { + "mode": "wb", + "buffering": -1, + "encoding": "UTF-8", + }, + ), + ], ) -def test_posargs2kwargs(args, posarg_names, kwargs, expects): +def test_posargs2kwargs( + args: Sequence, + posarg_names: Union[str, Callable], + kwargs: Optional[Dict[str, Any]], + expects: Dict[str, Any], + ): assert posargs2kwargs(args, posarg_names, kwargs) == expects @@ -351,41 +368,43 @@ def test_protocol_pandas(self): @pytest.mark.parametrize( "args, expects", [ - ((foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), ), - "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, ...)"), - ((foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 13), - "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11, l=12, m=13)"), - ] + ( + (foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), ), + "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, ...)", + ), + ( + (foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 13), + "foo(a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9, j=10, k=11, l=12, m=13)", + ), + ], ) - def test_namedtuple(self, args, expects): + def test_namedtuple(self, args: Sequence, expects: str): assert head(*args) == expects @pytest.mark.parametrize( "args, expects", [ (((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), ), "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)"), - (( - (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), - 13, + ( + ((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), 13), + "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)", ), - "(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)"), - ] + ], ) - def test_tuple(self, args, expects): + def test_tuple(self, args: Sequence, expects: str): assert head(*args) == expects @pytest.mark.parametrize( "args, expects", [ (([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], ), "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]"), - (( - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], - 13, + ( + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13], 13), + "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]", ), - "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]"), - ] + ], ) - def test_list(self, args, expects): + def test_list(self, args: Sequence, expects: str): assert head(*args) == expects def test_data_frame(self): @@ -396,7 +415,7 @@ def test_data_frame(self): columns=["Name", "Age", "Occupation"], ) assert head( - df + df, ) == """ Name Age Occupation 0 Bob 20 Apprentice 1 Alice 23 Secretary @@ -451,7 +470,7 @@ def test_trim_precision(): ("don't", "\"don't\""), ("Here's a single quote \"", "\"Here's a single quote \\\"\""), (enquote_value('β˜ƒ'), "\"'β˜ƒ'\""), - ] + ], ) def test_double_repr_string(value: str, expects: str): assert double_repr_string(value) == expects @@ -488,7 +507,7 @@ def test_redirect_output_combine(): ("hello = world", " = "), ("hello: world", ':'), ("hello: world", ": "), - ] + ], ) def test_divide(string: str, sep: str, advanced_data_regression: AdvancedDataRegressionFixture): data = dict(divide(e, sep) for e in [string, string, string]) @@ -509,7 +528,7 @@ def test_divide_errors(): ("hello = world", '='), ("hello: world", r":\s?"), ("hello: world", r"\s?:\s?"), - ] + ], ) def test_redivide(string: str, sep: str, advanced_data_regression: AdvancedDataRegressionFixture): data = dict(redivide(e, sep) for e in [string, string, string]) @@ -530,9 +549,9 @@ def test_redivide_errors(): (("foo", "bar"), ["bar", "foo"]), (("foo", "foo", "bar"), ["bar", "foo"]), (("foo", "bar", "bar"), ["bar", "foo"]), - ] + ], ) -def test_unique_sorted(values, expected): +def test_unique_sorted(values: Sequence[str], expected: List[str]): assert unique_sorted(values) == expected @@ -545,7 +564,7 @@ def test_unique_sorted(values, expected): ("\b\u000b", "^H^K"), ("\u001a", "^Z^?"), ('\x81', "M+A"), - ] + ], ) def test_replace_nonprinting(the_string: str, expected: str): assert replace_nonprinting(the_string) == expected diff --git a/tests/test_versions.py b/tests/test_versions.py index deaa405c..96a33ff2 100644 --- a/tests/test_versions.py +++ b/tests/test_versions.py @@ -3,7 +3,7 @@ import pickle import platform import sys -from typing import Any +from typing import Any, Sequence, Tuple # 3rd party import pytest @@ -28,7 +28,7 @@ ("15", (15, )), ], ) -def test_iter_string(string, expects): +def test_iter_string(string: str, expects: Tuple[int, ...]): assert tuple(_iter_string(string)) == expects @@ -47,7 +47,7 @@ def test_iter_string(string, expects): ("15", (15, )), ], ) -def test_iter_float(float_, expects): +def test_iter_float(float_: float, expects: Version): assert tuple(_iter_float(float_)) == expects @@ -70,39 +70,31 @@ def test_iter_float(float_, expects): (('1', '5', '1'), (1, 5, 1)), (('1', '5', '1', '2', '3', '4', '5'), (1, 5, 1, 2, 3, 4, 5)), (("15", ), (15, )), - ([ - '1', - ], (1, )), + (['1'], (1, )), (['1', '0'], (1, 0)), (['1', '5'], (1, 5)), (['1', '5', '1'], (1, 5, 1)), (['1', '5', '1', '2', '3', '4', '5'], (1, 5, 1, 2, 3, 4, 5)), - ([ - "15", - ], (15, )), + (["15"], (15, )), ((1, ), (1, )), ((1, 0), (1, 0)), ((1, 5), (1, 5)), ((1, 5, 1), (1, 5, 1)), ((1, 5, 1, 2, 3, 4, 5), (1, 5, 1, 2, 3, 4, 5)), ((15, ), (15, )), - ([ - 1, - ], (1, )), + ([1], (1, )), ([1, 0], (1, 0)), ([1, 5], (1, 5)), ([1, 5, 1], (1, 5, 1)), ([1, 5, 1, 2, 3, 4, 5], (1, 5, 1, 2, 3, 4, 5)), - ([ - 15, - ], (15, )), + ([15], (15, )), (Version(1, 2, 3), (1, 2, 3)), (Version(1, 2), (1, 2, 0)), (Version(1), (1, 0, 0)), ], ) -def test_prep_for_eq(other, expects): - assert _prep_for_eq(other) == expects +def test_prep_for_eq(other: Sequence, expects: Tuple[int, ...]): + assert _prep_for_eq(other) == expects # type: ignore[arg-type] @pytest.mark.parametrize( @@ -115,7 +107,7 @@ def test_prep_for_eq(other, expects): (2.0, Version(2)), ], ) -def test_from_float(float_, expects): +def test_from_float(float_: float, expects: Version): assert Version.from_float(float_) == expects @@ -133,7 +125,7 @@ def test_from_float(float_, expects): ("1.5.2", Version(1, 5, 2)), ], ) -def test_from_str(string, expects): +def test_from_str(string: str, expects: Version): assert Version.from_str(string) == expects @@ -145,50 +137,42 @@ def test_from_str(string, expects): (('1', '5'), Version(1, 5)), (('1', '5', '1'), Version(1, 5, 1)), (("15", ), Version(15)), - ([ - '1', - ], Version(1)), + (['1'], Version(1)), (['1', '0'], Version(1, 0)), (['1', '5'], Version(1, 5)), (['1', '5', '1'], Version(1, 5, 1)), - ([ - "15", - ], Version(15)), + (["15"], Version(15)), ((1, ), Version(1)), ((1, 0), Version(1, 0)), ((1, 5), Version(1, 5)), ((1, 5, 1), Version(1, 5, 1)), ((15, ), Version(15)), - ([ - 1, - ], Version(1)), + ([1], Version(1)), ([1, 0], Version(1, 0)), ([1, 5], Version(1, 5)), ([1, 5, 1], Version(1, 5, 1)), - ([ - 15, - ], Version(15)), + ([15], Version(15)), ], ) -def test_from_tuple(tuple_, expects): - assert Version.from_tuple(tuple_) == expects +def test_from_tuple(tuple_: Sequence, expects: Version): + assert Version.from_tuple(tuple_) == expects # type: ignore[arg-type] def test_too_many_values(): with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 5 were given"): Version.from_str("1.2.3.4") with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 5 were given"): - Version(1, 2, 3, 4) # type: ignore + Version(1, 2, 3, 4) # type: ignore[call-arg] with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 5 were given"): - Version('1', '2', '3', '4') # type: ignore + Version('1', '2', '3', '4') # type: ignore[call-arg] # with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 8 were given"): - # Version.from_tuple(("1", "5", "1", "2", "3", "4", "5")) # type: ignore + # Version.from_tuple(("1", "5", "1", "2", "3", "4", "5")) # type: ignore[call-arg] # with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 8 were given"): - # Version.from_tuple(["1", "5", "1", "2", "3", "4", "5"]) # type: ignore + # Version.from_tuple(["1", "5", "1", "2", "3", "4", "5"]) # type: ignore[call-arg] # with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 8 were given"): - # Version.from_tuple((1, 5, 1, 2, 3, 4, 5)) # type: ignore + # Version.from_tuple((1, 5, 1, 2, 3, 4, 5)) # type: ignore[call-arg] # with pytest.raises(TypeError, match=".* takes from 1 to 4 positional arguments but 8 were given"): - # Version.from_tuple([1, 5, 1, 2, 3, 4, 5]) # type: ignore + # Version.from_tuple([1, 5, 1, 2, 3, 4, 5]) # type: ignore[call-arg] @pytest.mark.parametrize( @@ -199,29 +183,21 @@ def test_too_many_values(): (('1', '5'), Version(1, 5)), (('1', '5', '1'), Version(1, 5, 1)), (("15", ), Version(15)), - ([ - '1', - ], Version(1)), + (['1'], Version(1)), (['1', '0'], Version(1, 0)), (['1', '5'], Version(1, 5)), (['1', '5', '1'], Version(1, 5, 1)), - ([ - "15", - ], Version(15)), + (["15"], Version(15)), ((1, ), Version(1)), ((1, 0), Version(1, 0)), ((1, 5), Version(1, 5)), ((1, 5, 1), Version(1, 5, 1)), ((15, ), Version(15)), - ([ - 1, - ], Version(1)), + ([1], Version(1)), ([1, 0], Version(1, 0)), ([1, 5], Version(1, 5)), ([1, 5, 1], Version(1, 5, 1)), - ([ - 15, - ], Version(15)), + ([15], Version(15)), ('1', Version(1)), ("1.0", Version(1, 0)), ("1.5", Version(1, 5)), @@ -235,7 +211,7 @@ def test_too_many_values(): (15, Version(15)), ], ) -def test_equals(value, version): +def test_equals(value: Any, version: Version): assert version == value assert value == version @@ -279,14 +255,12 @@ def test_lt(value: Any, version: Version): ((1, ), Version(1.1)), ((0, 9), Version(1)), ((0, 8, 8), Version(0, 9)), - ([ - 1, - ], Version(1.1)), + ([1], Version(1.1)), ([0, 9], Version(1)), ([0, 8, 8], Version(0, 9)), ], ) -def test_gt(value, version): +def test_gt(value: Any, version: Version): assert value < version assert version > value @@ -315,29 +289,21 @@ def test_gt(value, version): (('1', '5'), Version(1, 5)), (('1', '5', '1'), Version(1, 5, 1)), (("15", ), Version(15)), - ([ - '1', - ], Version(1)), + (['1'], Version(1)), (['1', '0'], Version(1, 0)), (['1', '5'], Version(1, 5)), (['1', '5', '1'], Version(1, 5, 1)), - ([ - "15", - ], Version(15)), + (["15"], Version(15)), ((1, ), Version(1)), ((1, 0), Version(1, 0)), ((1, 5), Version(1, 5)), ((1, 5, 1), Version(1, 5, 1)), ((15, ), Version(15)), - ([ - 1, - ], Version(1)), + ([1], Version(1)), ([1, 0], Version(1, 0)), ([1, 5], Version(1, 5)), ([1, 5, 1], Version(1, 5, 1)), - ([ - 15, - ], Version(15)), + ([15], Version(15)), ('1', Version(1)), ("1.0", Version(1, 0)), ("1.5", Version(1, 5)), @@ -351,7 +317,7 @@ def test_gt(value, version): ("1.5.1.2.3.4.5", Version(1, 5, 1)), ], ) -def test_le(value, version): +def test_le(value: Any, version: Version): assert value >= version assert version <= value @@ -369,9 +335,7 @@ def test_le(value, version): ((1, ), Version(1.1)), ((0, 9), Version(1)), ((0, 8, 8), Version(0, 9)), - ([ - 1, - ], Version(1.1)), + ([1], Version(1.1)), ([0, 9], Version(1)), ([0, 8, 8], Version(0, 9)), (('1', ), Version(1)), @@ -379,29 +343,21 @@ def test_le(value, version): (('1', '5'), Version(1, 5)), (('1', '5', '1'), Version(1, 5, 1)), (("15", ), Version(15)), - ([ - '1', - ], Version(1)), + (['1'], Version(1)), (['1', '0'], Version(1, 0)), (['1', '5'], Version(1, 5)), (['1', '5', '1'], Version(1, 5, 1)), - ([ - "15", - ], Version(15)), + (["15"], Version(15)), ((1, ), Version(1)), ((1, 0), Version(1, 0)), ((1, 5), Version(1, 5)), ((1, 5, 1), Version(1, 5, 1)), ((15, ), Version(15)), - ([ - 1, - ], Version(1)), + ([1], Version(1)), ([1, 0], Version(1, 0)), ([1, 5], Version(1, 5)), ([1, 5, 1], Version(1, 5, 1)), - ([ - 15, - ], Version(15)), + ([15], Version(15)), ('1', Version(1)), ("1.0", Version(1, 0)), ("1.5", Version(1, 5)), @@ -415,7 +371,7 @@ def test_le(value, version): ("1.5.0.2.3.4.5", Version(1, 5, 1)), ], ) -def test_ge(value, version): +def test_ge(value: Any, version: Version): assert value <= version assert version >= value @@ -431,7 +387,7 @@ def test_ge(value, version): (Version(minor=3, patch=4), "Version(major=0, minor=3, patch=4)"), ], ) -def test_repr(version, expects): +def test_repr(version: Version, expects: Any): assert repr(version) == expects @@ -446,7 +402,7 @@ def test_repr(version, expects): (Version(minor=3, patch=4), "v0.3.4"), ], ) -def test_str(version, expects): +def test_str(version: Version, expects: Any): assert str(version) == expects @@ -461,7 +417,7 @@ def test_str(version, expects): (Version(minor=3, patch=4), 0.3), ], ) -def test_float(version, expects): +def test_float(version: Version, expects: Any): assert float(version) == expects @@ -476,7 +432,7 @@ def test_float(version, expects): (Version(minor=3, patch=4), 0), ], ) -def test_int(version, expects): +def test_int(version: Version, expects: Any): assert int(version) == expects @@ -492,7 +448,7 @@ def test_int(version, expects): Version(1, 2, 3), ], ) -def test_pickle(obj): +def test_pickle(obj: Version): assert pickle.loads(pickle.dumps(obj)) == obj # nosec: B301 @@ -508,5 +464,5 @@ def test_pickle(obj): Version(1, 2, 3), ], ) -def test_copy(obj): +def test_copy(obj: Version): assert copy.copy(obj) == obj diff --git a/tests/test_words.py b/tests/test_words.py index 9e17beeb..31583d55 100644 --- a/tests/test_words.py +++ b/tests/test_words.py @@ -3,7 +3,7 @@ import pathlib import random import string -from typing import List +from typing import Any, Dict, List, Sequence # 3rd party import pytest @@ -116,7 +116,7 @@ def test_alpha_sort(): (decimal.Decimal("1234"), "1234"), ], ) -def test_as_text(value, expects): +def test_as_text(value: Any, expects: str): assert words.as_text(value) == expects @@ -143,17 +143,23 @@ def test_as_text(value, expects): ((["bob"], ), {"use_repr": True, "oxford": True}, "'bob'"), ((["bob", "alice"], ), {"use_repr": True, "oxford": True}, "'bob' and 'alice'"), ((["bob", "alice", "fred"], ), {"use_repr": True, "oxford": True}, "'bob', 'alice', and 'fred'"), - ((["bob", "alice", "fred"], ), {"use_repr": True, "oxford": True, "delimiter": ';'}, - "'bob'; 'alice'; and 'fred'"), - ((["bob", "alice", "fred"], ), {"use_repr": True, "oxford": True, "connective": "or"}, - "'bob', 'alice', or 'fred'"), + ( + (["bob", "alice", "fred"], ), + {"use_repr": True, "oxford": True, "delimiter": ';'}, + "'bob'; 'alice'; and 'fred'", + ), + ( + (["bob", "alice", "fred"], ), + {"use_repr": True, "oxford": True, "connective": "or"}, + "'bob', 'alice', or 'fred'", + ), ((["bob", "alice"], ), {"connective": "or"}, "bob or alice"), ((("bob", ), ), {"use_repr": True, "oxford": True}, "'bob'"), ((("bob", "alice"), ), {"use_repr": True, "oxford": True}, "'bob' and 'alice'"), ((("bob", "alice", "fred"), ), {"use_repr": True, "oxford": True}, "'bob', 'alice', and 'fred'"), ], ) -def test_word_join(args, kwargs, expects): +def test_word_join(args: Sequence, kwargs: Dict[str, Any], expects: str): assert words.word_join(*args, **kwargs) == expects @@ -182,7 +188,8 @@ def test_pluralphrase(): phrase2 = PluralPhrase("The farmer has {n} {0}.", (Plural("cow", "cows"), )) phrase3 = PluralPhrase("The proposed {1} {0} to ...", (Plural("is", "are"), Plural("change", "changes"))) phrase4 = PluralPhrase( - "The farmer has {n} {0}. The {0} {1} brown.", (Plural("cow", "cows"), Plural("is", "are")) + "The farmer has {n} {0}. The {0} {1} brown.", + (Plural("cow", "cows"), Plural("is", "are")), ) n = 1 assert phrase1(n) == "The proposed change is to ..." diff --git a/tox.ini b/tox.ini index 8e920f9c..9c4408bb 100644 --- a/tox.ini +++ b/tox.ini @@ -22,15 +22,14 @@ [tox] envlist = - py36 py37 py38 py39 py310 py311 py312 - py313-dev - pypy36 + py313 + py314 pypy37 pypy38 pypy39 @@ -43,40 +42,36 @@ requires = pip>=21,!=22.2 tox-envlist>=0.2.1 tox~=3.0 - virtualenv!=20.16.0 + virtualenv!=20.16.0,<20.39 [envlists] test = - py36 py37 py38 py39 py310 py311 py312 - py313-dev - pypy36 + py313 + py314 pypy37 pypy38 pypy39 pypy310 qa = mypy, lint -cov = py38, coverage +cov = py39, coverage [testenv:.package] setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 -[testenv:py313-dev] -download = True +[testenv:py313] setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 - UNSAFE_PYO3_SKIP_VERSION_CHECK=1 [testenv:py312] -download = True setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 @@ -84,7 +79,7 @@ setenv = [testenv:docs] setenv = SHOW_TODOS = 1 passenv = SPHINX_BUILDER -basepython = python3.8 +basepython = python3.9 changedir = {toxinidir}/doc-source extras = all deps = -r{toxinidir}/doc-source/requirements.txt @@ -109,7 +104,7 @@ commands = check-wheel-contents dist/ [testenv:lint] -basepython = python3.8 +basepython = python3.9 changedir = {toxinidir} ignore_errors = True skip_install = False @@ -121,7 +116,7 @@ deps = flake8-dunder-all>=0.1.1 flake8-encodings>=0.1.0 flake8-github-actions>=0.1.0 - flake8-noqa>=1.1.0,<=1.2.2 + git+https://github.com/python-formate/flake8-noqa.git@v1.2.2-python-formate.0 flake8-pyi>=20.10.0,<=22.8.0 flake8-pytest-style>=1.3.0,<2 flake8-quotes>=3.3.0 @@ -129,17 +124,20 @@ deps = flake8-sphinx-links>=0.0.4 flake8-strftime>=0.1.1 flake8-typing-imports>=1.10.0 + flake8-params>=0.1.0 + flake8-unused-fstrings>=2.0.0 + git+https://github.com/python-formate/flake8-commas.git@4.0.0-python-formate.0 + git+https://github.com/domdfcoding/restructuredtext-lint.git@fix-deprecations git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git git+https://github.com/domdfcoding/flake8-rst-docstrings.git git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods git+https://github.com/python-formate/flake8-missing-annotations.git - pydocstyle>=6.0.0 + 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 domdf_python_tools tests --allow-toolbox {posargs} [testenv:perflint] -basepython = python3.8 +basepython = python3.9 changedir = {toxinidir} ignore_errors = True skip_install = True @@ -147,19 +145,19 @@ deps = perflint commands = python3 -m perflint domdf_python_tools {posargs} [testenv:mypy] -basepython = python3.8 +basepython = python3.9 ignore_errors = True changedir = {toxinidir} extras = all deps = - mypy==0.971 + mypy==1.17.1 -r{toxinidir}/tests/requirements.txt -r{toxinidir}/stubs.txt pprint36 commands = mypy domdf_python_tools tests {posargs} [testenv:pyup] -basepython = python3.8 +basepython = python3.9 skip_install = True ignore_errors = True changedir = {toxinidir} @@ -168,7 +166,7 @@ extras = all commands = pyup_dirs domdf_python_tools tests --py36-plus --recursive [testenv:coverage] -basepython = python3.8 +basepython = python3.9 skip_install = True ignore_errors = True whitelist_externals = /bin/bash @@ -188,7 +186,7 @@ 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 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 +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 NUF001 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 C818 C819 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 PRM001 PRM002 PRM003 extend-exclude = doc-source,old,build,dist,__pkginfo__.py,setup.py,venv rst-directives = TODO @@ -205,18 +203,22 @@ rst-roles = inline-code manpage 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 - tests/list_tests.py: PT011 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 - tests/seq_tests.py: PT011 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 - tests/test_paths_stdlib.py: PT011 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 - tests/test_pretty_print.py: PT011 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 + 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 PRM001 PRM002 PRM003 + */*.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 PRM001 PRM002 PRM003 + domdf_python_tools/compat/importlib_resources.py: PRM002 + domdf_python_tools/pretty_print.py: MAN001 MAN002 + tests/list_tests.py: PT011 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 MAN001 MAN002 PRM002 PRM003 + tests/seq_tests.py: PT011 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 MAN001 MAN002 PRM002 PRM003 + tests/test_doctools.py: PT011 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 MAN001 MAN002 PRM002 PRM003 + tests/test_getters.py: PT011 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 MAN001 MAN002 PRM002 PRM003 + tests/test_paths_stdlib.py: PT011 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 PRM002 PRM003 + tests/test_pretty_print.py: PT011 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 MAN001 MAN002 PRM002 PRM003 pytest-parametrize-names-type = csv inline-quotes = " multiline-quotes = """ docstring-quotes = """ count = True -min_python_version = 3.6 +min_python_version = 3.7 unused-arguments-ignore-abstract-functions = True unused-arguments-ignore-overload-functions = True unused-arguments-ignore-magic-methods = True