diff --git a/.github/workflows/auto_updates.yml b/.github/workflows/auto_updates.yml index cb41847..2cd2698 100644 --- a/.github/workflows/auto_updates.yml +++ b/.github/workflows/auto_updates.yml @@ -15,7 +15,7 @@ jobs: name: Update dependencies and hooks runs-on: ubuntu-latest env: - PYTHON_VERSION: "3.12" + PYTHON_VERSION: "3.13" POETRY_VERSION: "1.8.5" defaults: run: diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 7c170b2..7b7e4ad 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -25,7 +25,7 @@ on: - main env: - PYTHON_VERSION: "3.12" + PYTHON_VERSION: "3.13" POETRY_VERSION: "1.8.5" jobs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6556e79..1b982f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ on: default: false env: - PYTHON_VERSION: "3.12" + PYTHON_VERSION: "3.13" POETRY_VERSION: "1.8.5" jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6c584f..4a02d98 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ defaults: shell: bash env: - PYTHON_VERSION: "3.12" + PYTHON_VERSION: "3.13" POETRY_VERSION: "1.8.5" jobs: @@ -64,7 +64,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout the repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 45f7af4..09861c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,26 +104,26 @@ Here's how to set up `phylum-ci` for local development. 3. Ensure all supported Python versions are installed locally 1. The strategy is to support the current/latest release plus the previous three minor versions of Python 2. The current list - 1. at the time of this writing is 3.9, 3.10, 3.11, and 3.12 + 1. at the time of this writing is 3.10, 3.11, 3.12, and 3.13 2. can be inferred with the Python Developer's Guide, which maintains the [status of active Python releases](https://devguide.python.org/versions/) 3. It is recommended to use [`pyenv`](https://github.com/pyenv/pyenv) to manage multiple Python installations ```sh # Use `pyenv install --list` to get available versions and usually install the - # latest patch version. For example, use `pyenv install --list |grep 3.9.` to - # show latest patch version for the cpython 3.9 minor release. + # latest patch version. For example, use `pyenv install --list |grep 3.10.` to + # show latest patch version for the cpython 3.10 minor release. # NOTE: These versions are examples; the latest patch version available from # pyenv should be used in place of `.x`. - pyenv install 3.9.x pyenv install 3.10.x pyenv install 3.11.x pyenv install 3.12.x + pyenv install 3.13.x pyenv rehash # Ensure all environments are available globally (helps tox to find them) - pyenv global 3.12.x 3.11.x 3.10.x 3.9.x + pyenv global 3.13.x 3.12.x 3.11.x 3.10.x ``` 4. Ensure [poetry v1.7+](https://python-poetry.org/docs/) is installed @@ -217,7 +217,7 @@ Before you submit a pull request, check that it meets these guidelines: * Have you created sufficient tests? * Have you updated all affected documentation? -The pull request should work for Python 3.9, 3.10, 3.11, and 3.12. +The pull request should work for Python 3.10, 3.11, 3.12 and 3.13. Check and make sure that the tests pass for all supported Python versions. @@ -254,10 +254,10 @@ poetry run -- tox run -e py310 -- --help poetry run -- tox run-parallel -- tests/unit/test_package_metadata.py # run a specific test module across a specific test environment -poetry run -- tox run -e py39 -- tests/unit/test_package_metadata.py +poetry run -- tox run -e py310 -- tests/unit/test_package_metadata.py # run a specific test function within a test module, in a specific test environment -poetry run -- tox run -e py310 -- tests/unit/test_package_metadata.py::test_python_version +poetry run -- tox run -e py311 -- tests/unit/test_package_metadata.py::test_python_version ``` To run a script entry point with the local checkout of the code (in develop mode), use `poetry`: diff --git a/Dockerfile b/Dockerfile index 68229af..51bbebb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,7 @@ # $ scripts/docker_tests.sh --image phylum-ci ########################################################################################## -FROM python:3.12-slim-bookworm AS builder +FROM python:3.13-slim-bookworm AS builder # PKG_SRC is the path to a built distribution/wheel and PKG_NAME is the name of the built # distribution/wheel. Both can optionally be specified in glob form. When not defined, @@ -121,7 +121,7 @@ RUN find ${PHYLUM_VENV} -type f -name '*.pyc' -delete # in the final layer and also known to be part of the $PATH COPY entrypoint.sh ${PHYLUM_VENV}/bin/ -FROM python:3.12-slim-bookworm +FROM python:3.13-slim-bookworm # CLI_VER specifies the Phylum CLI version to install in the image. # Values should be provided in a format acceptable to the `phylum-init` script. diff --git a/Dockerfile.slim b/Dockerfile.slim index e8dd21f..53216db 100644 --- a/Dockerfile.slim +++ b/Dockerfile.slim @@ -77,7 +77,7 @@ # $ scripts/docker_tests.sh --image phylum-ci --slim ########################################################################################## -FROM python:3.12-slim-bookworm AS builder +FROM python:3.13-slim-bookworm AS builder # PKG_SRC is the path to a built distribution/wheel and PKG_NAME is the name of the built # distribution/wheel. Both can optionally be specified in glob form. When not defined, @@ -129,7 +129,7 @@ RUN find ${PHYLUM_VENV} -type f -name '*.pyc' -delete # in the final layer and also known to be part of the $PATH COPY entrypoint.sh ${PHYLUM_VENV}/bin/ -FROM python:3.12-slim-bookworm +FROM python:3.13-slim-bookworm # CLI_VER specifies the Phylum CLI version to install in the image. # Values should be provided in a format acceptable to the `phylum-init` script. diff --git a/README.md b/README.md index a23a46e..9f4661f 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ pipx run --spec phylum phylum-init pipx run --spec phylum phylum-ci ``` -These installation methods require Python 3.9+ to run. +These installation methods require Python 3.10+ to run. For a self contained environment, consider using the Docker image as described below. Windows binaries are offered as [release artifacts][latest_rels] for a "standalone" solution that does not require diff --git a/poetry.lock b/poetry.lock index 75eee72..22c6f70 100644 --- a/poetry.lock +++ b/poetry.lock @@ -300,51 +300,51 @@ files = [ [[package]] name = "cryptography" -version = "43.0.3" +version = "44.0.0" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, - {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, - {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, - {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, - {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, - {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, - {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, - {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, - {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, - {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, - {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, - {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, - {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, +python-versions = "!=3.9.0,!=3.9.1,>=3.7" +files = [ + {file = "cryptography-44.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15492a11f9e1b62ba9d73c210e2416724633167de94607ec6069ef724fad092"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:761817a3377ef15ac23cd7834715081791d4ec77f9297ee694ca1ee9c2c7e5eb"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b"}, + {file = "cryptography-44.0.0-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ed3534eb1090483c96178fcb0f8893719d96d5274dfde98aa6add34614e97c8e"}, + {file = "cryptography-44.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f3f6fdfa89ee2d9d496e2c087cebef9d4fcbb0ad63c40e821b39f74bf48d9c5e"}, + {file = "cryptography-44.0.0-cp37-abi3-win32.whl", hash = "sha256:eb33480f1bad5b78233b0ad3e1b0be21e8ef1da745d8d2aecbb20671658b9053"}, + {file = "cryptography-44.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd"}, + {file = "cryptography-44.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1923cb251c04be85eec9fda837661c67c1049063305d6be5721643c22dd4e2b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c5eb858beed7835e5ad1faba59e865109f3e52b3783b9ac21e7e47dc5554e289"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f53c2c87e0fb4b0c00fa9571082a057e37690a8f12233306161c8f4b819960b7"}, + {file = "cryptography-44.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2436114e46b36d00f8b72ff57e598978b37399d2786fd39793c36c6d5cb1c64"}, + {file = "cryptography-44.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285"}, + {file = "cryptography-44.0.0-cp39-abi3-win32.whl", hash = "sha256:eca27345e1214d1b9f9490d200f9db5a874479be914199194e746c893788d417"}, + {file = "cryptography-44.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37d76e6863da3774cd9db5b409a9ecfd2c71c981c38788d3fcfaf177f447b731"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f677e1268c4e23420c3acade68fac427fffcb8d19d7df95ed7ad17cdef8404f4"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f5e7cb1e5e56ca0933b4873c0220a78b773b24d40d186b6738080b73d3d0a756"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:be4ce505894d15d5c5037167ffb7f0ae90b7be6f2a98f9a5c3442395501c32fa"}, + {file = "cryptography-44.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c"}, + {file = "cryptography-44.0.0.tar.gz", hash = "sha256:cd4e834f340b4293430701e772ec543b0fbe6c2dea510a5286fe0acabe153a02"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=3.0.0)"] +docstest = ["pyenchant (>=3)", "readme-renderer (>=30.0)", "sphinxcontrib-spelling (>=7.3.1)"] +nox = ["nox (>=2024.4.15)", "nox[uv] (>=2024.3.2)"] +pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.4)", "ruff (>=0.3.6)"] +sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi (>=2024)", "cryptography-vectors (==44.0.0)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -531,9 +531,6 @@ files = [ {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, ] -[package.dependencies] -zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} - [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] @@ -1823,25 +1820,6 @@ platformdirs = ">=3.9.1,<5" docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] -[[package]] -name = "zipp" -version = "3.21.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [[package]] name = "zstandard" version = "0.23.0" @@ -1956,5 +1934,5 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" -python-versions = ">=3.9,<3.13" -content-hash = "f89fca2c51c09bd7470e5ede6259ae24d84f6c9cd749b70dc8bfdeb0772b65a8" +python-versions = ">=3.10,<3.14" +content-hash = "de4dff7ccf2316cb801fbda552c775dcda28152c4d93f6470688cb784f9a4f63" diff --git a/pyproject.toml b/pyproject.toml index 221ce56..544f3ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,10 +27,10 @@ classifiers = [ "Topic :: Software Development", "Topic :: Software Development :: Quality Assurance", "Programming Language :: Python :: 3", - "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", ] packages = [ { include = "phylum", from = "src" }, @@ -46,7 +46,7 @@ phylum-init = "phylum.init.cli:main" phylum-ci = "phylum.ci.cli:script_main" [tool.poetry.dependencies] -python = ">=3.9,<3.13" +python = ">=3.10,<3.14" requests = "*" cryptography = "*" packaging = "*" @@ -103,7 +103,7 @@ paths = ["src", "tests"] exclude = ["conftest.py"] [tool.refurb] -python_version = "3.9" +python_version = "3.10" format = "github" # FURB184 (use-fluent-interface) is ignored because the benefits of "chaining" calls identified by this rule are not # outweighed by the type checking based autocompletion and syntax highlighting provided by the IDE with the calls split. @@ -112,7 +112,7 @@ ignore = [184] [tool.ruff] # Reference: https://docs.astral.sh/ruff/configuration/ line-length = 120 -target-version = "py39" +target-version = "py310" force-exclude = true src = ["src", "tests"] diff --git a/src/phylum/ci/ci_azure.py b/src/phylum/ci/ci_azure.py index bbbe72d..027b87c 100644 --- a/src/phylum/ci/ci_azure.py +++ b/src/phylum/ci/ci_azure.py @@ -24,7 +24,6 @@ import re import shlex import subprocess -from typing import Optional import urllib.parse import requests @@ -176,7 +175,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit.""" remote = git_remote() @@ -294,7 +293,7 @@ def phylum_comment_exists(self) -> bool: return False @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # Ref: https://learn.microsoft.com/azure/devops/pipelines/build/variables if self.triggering_repo == "TfsGit": @@ -365,7 +364,7 @@ def get_pr_branches() -> tuple[str, str]: # The function is meant to be used internally, where it is known that the comments on the PR at the time # of first execution will suffice for the duration of the rest of the lifetime of the running integration. @lru_cache(maxsize=1) -def get_most_recent_phylum_comment_azure(azure_token: str) -> Optional[str]: +def get_most_recent_phylum_comment_azure(azure_token: str) -> str | None: """Get the raw text of the most recently posted Phylum-generated comment for Azure Repos PRs. Return `None` when one does not exist. diff --git a/src/phylum/ci/ci_base.py b/src/phylum/ci/ci_base.py index 69fdfb4..3453f8c 100644 --- a/src/phylum/ci/ci_base.py +++ b/src/phylum/ci/ci_base.py @@ -19,7 +19,6 @@ import shutil import subprocess import tempfile -from typing import Optional import pathspec from rich.markdown import Markdown @@ -62,7 +61,7 @@ def __init__(self, args: Namespace) -> None: self._force_analysis = args.force_analysis self._returncode = ReturnCode.SUCCESS self._analysis_report = "No analysis output yet" - self._env: Optional[Mapping[str, str]] = None + self._env: Mapping[str, str] | None = None self.ci_platform_name = "Unknown" self.disable_lockfile_generation = False @@ -186,7 +185,7 @@ def depfiles(self) -> Depfiles: Detected dependency files can be modified with exclusion patterns provided as an argument. """ - arg_depfiles: Optional[list[list[Path]]] = self.args.depfile + arg_depfiles: list[list[Path]] | None = self.args.depfile provided_arg_depfiles: DepfileEntries = [] if arg_depfiles: # Flatten the list of lists @@ -225,7 +224,7 @@ def depfiles(self) -> Depfiles: def _exclude_depfiles(self, provided_depfiles: DepfileEntries) -> DepfileEntries: """Apply exclusion patterns to provided dependency files and return the remaining ones.""" - arg_exclusions: Optional[list[list[str]]] = self.args.exclude + arg_exclusions: list[list[str]] | None = self.args.exclude if not arg_exclusions: LOG.debug("No dependency file exclusion patterns provided.") return provided_depfiles @@ -400,7 +399,7 @@ def phylum_project(self) -> str: return project_name @cached_property - def phylum_org(self) -> Optional[str]: + def phylum_org(self) -> str | None: """Get the effective Phylum organization in use. The Phylum organization name can be specified as an option or contained in the `settings.yaml` file. @@ -442,7 +441,7 @@ def phylum_org(self) -> Optional[str]: return None @cached_property - def phylum_group(self) -> Optional[str]: + def phylum_group(self) -> str | None: """Get the effective Phylum group in use. The Phylum group name can be specified as an option or contained in the `.phylum_project` file. @@ -556,7 +555,7 @@ def phylum_label(self) -> str: @property @abstractmethod - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata. The value should only exist for implementations that make use of a web hosted CI environment. The local use @@ -589,7 +588,7 @@ def depfile_hash_object(self) -> str: @cached_property @abstractmethod - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit. When found, it should be returned as a string of the SHA1 sum representing the commit. @@ -615,7 +614,7 @@ def phylum_comment_exists(self) -> bool: """ raise NotImplementedError - def _update_depfiles_change_status(self, commit: str, err_msg: Optional[str] = None) -> None: + def _update_depfiles_change_status(self, commit: str, err_msg: str | None = None) -> None: """Update each dependency file's change status. The input `commit` is the one to use in a `git diff` command to view the changes relative to the working tree. @@ -930,21 +929,10 @@ def analyze(self) -> None: dep_txt = "dependency" if num_new_packages == 1 else "dependencies" LOG.debug("%s new %s: %s", num_new_packages, dep_txt, new_packages) - # TODO(maxrake): Better formatting with parenthesized context managers is available in Python 3.10+ - # https://github.com/phylum-dev/phylum-ci/issues/357 - # https://docs.python.org/3.10/whatsnew/3.10.html#parenthesized-context-managers - # https://stackoverflow.com/q/67808977 - with tempfile.NamedTemporaryFile( - mode="w+", - encoding="utf-8", - prefix="base_", - suffix=".json", - ) as base_fd, tempfile.NamedTemporaryFile( - mode="w+", - encoding="utf-8", - prefix="curr_", - suffix=".json", - ) as curr_fd: + with ( + tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", prefix="base_", suffix=".json") as base_fd, + tempfile.NamedTemporaryFile(mode="w+", encoding="utf-8", prefix="curr_", suffix=".json") as curr_fd, + ): json.dump(base_packages, base_fd, cls=DataclassJSONEncoder) base_fd.flush() cmd.append(base_fd.name) @@ -1024,21 +1012,19 @@ def _parse_analysis_result(self, analysis_result: str) -> None: self._analysis_report = analysis.report - # TODO(maxrake): The logic below would make for a good match statement, which was introduced in Python 3.10 - # https://github.com/phylum-dev/phylum-ci/issues/357 - if analysis.incomplete_count == 0: - if analysis.is_failure: + match (analysis.incomplete_count, analysis.is_failure): + case (0, True): LOG.error("Analysis is complete and there were failures") self.returncode = ReturnCode.ANALYSIS_POLICY_FAILURE - else: + case (0, False): LOG.info("Analysis is complete and there were NO failures") self.returncode = ReturnCode.SUCCESS - elif analysis.is_failure: - LOG.error("There were failures in one or more completed packages") - self.returncode = ReturnCode.ANALYSIS_FAILURE_INCOMPLETE - else: - LOG.warning("There were no failures in the packages that have completed so far") - self.returncode = ReturnCode.ANALYSIS_INCOMPLETE + case (_, True): + LOG.error("There were failures in one or more completed packages") + self.returncode = ReturnCode.ANALYSIS_FAILURE_INCOMPLETE + case (_, False): + LOG.warning("There were no failures in the packages that have completed so far") + self.returncode = ReturnCode.ANALYSIS_INCOMPLETE # Type alias diff --git a/src/phylum/ci/ci_bitbucket.py b/src/phylum/ci/ci_bitbucket.py index c6b4468..f88bca6 100644 --- a/src/phylum/ci/ci_bitbucket.py +++ b/src/phylum/ci/ci_bitbucket.py @@ -24,7 +24,6 @@ import re import shlex import subprocess -from typing import Optional import urllib.parse import requests @@ -122,7 +121,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit. Some pre-defined variables are used: https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ @@ -221,7 +220,7 @@ def phylum_comment_exists(self) -> bool: return bool(self.most_recent_phylum_comment) @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # This is the "URL for the origin", which uses `HTTP:` instead of # `HTTPS:`, even though the value redirects to the `HTTPS:` site. @@ -285,7 +284,7 @@ def headers(self) -> dict: return headers @cached_property - def most_recent_phylum_comment(self) -> Optional[str]: + def most_recent_phylum_comment(self) -> str | None: """Get the raw text of the most recently posted Phylum-generated comment. Return `None` when one does not exist. diff --git a/src/phylum/ci/ci_github.py b/src/phylum/ci/ci_github.py index 86410fd..e59d75f 100644 --- a/src/phylum/ci/ci_github.py +++ b/src/phylum/ci/ci_github.py @@ -19,7 +19,6 @@ from pathlib import Path import re import subprocess -from typing import Optional import requests @@ -157,7 +156,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit.""" return self.pr_event.get("pull_request", {}).get("base", {}).get("sha") @@ -194,7 +193,7 @@ def phylum_comment_exists(self) -> bool: return bool(get_most_recent_phylum_comment_github(self.comments_url, self.github_token)) @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # Ref: https://docs.github.com/actions/learn-github-actions/variables#default-environment-variables server_url = os.getenv("GITHUB_SERVER_URL") @@ -226,7 +225,7 @@ def post_output(self) -> None: # The function is meant to be used internally, where it is known that the comments on the PR at the time # of first execution will suffice for the duration of the rest of the lifetime of the running integration. @lru_cache(maxsize=1) -def get_most_recent_phylum_comment_github(comments_url: str, github_token: str) -> Optional[str]: +def get_most_recent_phylum_comment_github(comments_url: str, github_token: str) -> str | None: """Get the raw text of the most recently posted Phylum-generated comment for GitHub PRs. Return `None` when one does not exist. diff --git a/src/phylum/ci/ci_gitlab.py b/src/phylum/ci/ci_gitlab.py index d4fb4db..45258f2 100644 --- a/src/phylum/ci/ci_gitlab.py +++ b/src/phylum/ci/ci_gitlab.py @@ -16,7 +16,6 @@ import re import shlex import subprocess -from typing import Optional import requests @@ -103,7 +102,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit. Some pre-defined variables are used: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html @@ -184,7 +183,7 @@ def phylum_comment_exists(self) -> bool: return bool(self.most_recent_phylum_note) @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # Ref: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html return os.getenv("CI_PROJECT_URL") @@ -234,7 +233,7 @@ def headers(self) -> dict: return headers @cached_property - def most_recent_phylum_note(self) -> Optional[str]: + def most_recent_phylum_note(self) -> str | None: """Get the raw text of the most recently posted Phylum-generated note. Return `None` when one does not exist. diff --git a/src/phylum/ci/ci_jenkins.py b/src/phylum/ci/ci_jenkins.py index 5c1e33e..aaa8e98 100644 --- a/src/phylum/ci/ci_jenkins.py +++ b/src/phylum/ci/ci_jenkins.py @@ -18,7 +18,6 @@ import re import shlex import subprocess -from typing import Optional from phylum.ci.ci_base import CIBase from phylum.ci.git import git_remote, git_set_remote_head @@ -83,7 +82,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit. Some pre-defined variables are used: @@ -159,7 +158,7 @@ def phylum_comment_exists(self) -> bool: return False @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # This is the "Remote URL of the first git repository in the workspace." # It comes from the git plugin and may not be set depending on the context. diff --git a/src/phylum/ci/ci_none.py b/src/phylum/ci/ci_none.py index 2730b40..5f01da5 100644 --- a/src/phylum/ci/ci_none.py +++ b/src/phylum/ci/ci_none.py @@ -9,7 +9,6 @@ from functools import cached_property import re import subprocess -from typing import Optional from phylum.ci.ci_base import CIBase from phylum.ci.git import git_current_branch_name, git_remote, git_set_remote_head @@ -41,7 +40,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit.""" remote = git_remote() cmd = ["git", "merge-base", "HEAD", f"refs/remotes/{remote}/HEAD"] @@ -112,7 +111,7 @@ def phylum_comment_exists(self) -> bool: return False @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # There is no repository URL in this implementation return None diff --git a/src/phylum/ci/ci_precommit.py b/src/phylum/ci/ci_precommit.py index 138fe28..d70361b 100644 --- a/src/phylum/ci/ci_precommit.py +++ b/src/phylum/ci/ci_precommit.py @@ -15,7 +15,6 @@ from pathlib import Path import re import subprocess -from typing import Optional from phylum.ci.ci_base import CIBase from phylum.ci.git import git_current_branch_name @@ -112,7 +111,7 @@ def phylum_label(self) -> str: return label @cached_property - def common_ancestor_commit(self) -> Optional[str]: + def common_ancestor_commit(self) -> str | None: """Find the common ancestor commit.""" cmd = ["git", "rev-parse", "--verify", "HEAD"] try: @@ -153,7 +152,7 @@ def phylum_comment_exists(self) -> bool: return False @property - def repo_url(self) -> Optional[str]: + def repo_url(self) -> str | None: """Get the repository URL for reference in Phylum project metadata.""" # There is no repository URL in this implementation return None diff --git a/src/phylum/ci/cli.py b/src/phylum/ci/cli.py index 85f42b8..cdc0e89 100644 --- a/src/phylum/ci/cli.py +++ b/src/phylum/ci/cli.py @@ -6,7 +6,6 @@ import os import pathlib import sys -from typing import Optional from phylum import __version__ from phylum.ci import SCRIPT_NAME @@ -86,7 +85,7 @@ def detect_ci_platform(args: argparse.Namespace, remainder: list[str]) -> CIBase return ci_env -def get_args(args: Optional[Sequence[str]] = None) -> tuple[argparse.Namespace, list[str]]: +def get_args(args: Sequence[str] | None = None) -> tuple[argparse.Namespace, list[str]]: """Get the arguments from the command line and return them.""" parser = argparse.ArgumentParser( prog=SCRIPT_NAME, @@ -243,7 +242,7 @@ def get_args(args: Optional[Sequence[str]] = None) -> tuple[argparse.Namespace, return parser.parse_known_args(args=args) -def main(args: Optional[Sequence[str]] = None) -> int: +def main(args: Sequence[str] | None = None) -> int: """Provide the main entrypoint.""" parsed_args, remainder_args = get_args(args=args) set_logger_level(parsed_args.verbose - parsed_args.quiet) diff --git a/src/phylum/ci/common.py b/src/phylum/ci/common.py index 61664e4..5c6e5c8 100644 --- a/src/phylum/ci/common.py +++ b/src/phylum/ci/common.py @@ -5,7 +5,6 @@ import json import os from pathlib import Path -from typing import Union @dataclasses.dataclass(order=True, frozen=True) @@ -46,7 +45,7 @@ class DepfileEntry: Current commands that return entries in this format include `status --json` and `find-dependency-files`. """ - path_: dataclasses.InitVar[Union[str, Path]] + path_: dataclasses.InitVar[str | Path] type: str = "auto" path: Path = dataclasses.field(init=False) diff --git a/src/phylum/ci/depfile.py b/src/phylum/ci/depfile.py index e29ecc0..6f18f20 100644 --- a/src/phylum/ci/depfile.py +++ b/src/phylum/ci/depfile.py @@ -13,7 +13,6 @@ from pathlib import Path import shlex import subprocess -from typing import Optional from phylum.ci.common import CLIExitCode, DepfileEntry, Package, Packages from phylum.exceptions import PhylumCalledProcessError @@ -54,7 +53,7 @@ def __init__( self.cli_path = cli_path self._depfile_type = depfile_type self.disable_lockfile_generation = disable_lockfile_generation - self._is_depfile_changed: Optional[bool] = None + self._is_depfile_changed: bool | None = None def __repr__(self) -> str: """Return a debug printable string representation of the `Depfile` object.""" @@ -88,7 +87,7 @@ def type(self) -> str: return self._type @property - def is_depfile_changed(self) -> Optional[bool]: + def is_depfile_changed(self) -> bool | None: """Predicate for detecting if the dependency file has changed.""" return self._is_depfile_changed @@ -158,7 +157,7 @@ def parse_depfile( depfile_type: str, depfile_path: Path, *, - start: Optional[Path] = None, + start: Path | None = None, disable_lockfile_generation: bool = False, ) -> Packages: """Parse a dependency file and return its packages. diff --git a/src/phylum/ci/git.py b/src/phylum/ci/git.py index 9e203e4..65a5e8a 100644 --- a/src/phylum/ci/git.py +++ b/src/phylum/ci/git.py @@ -7,13 +7,12 @@ import shlex import subprocess import tempfile -from typing import Optional from phylum.exceptions import PhylumCalledProcessError, pprint_subprocess_error from phylum.logger import LOG, MARKUP -def git_base_cmd(git_c_path: Optional[Path] = None) -> list[str]: +def git_base_cmd(git_c_path: Path | None = None) -> list[str]: """Provide a normalized base command list for use in constructing git commands. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -24,7 +23,7 @@ def git_base_cmd(git_c_path: Optional[Path] = None) -> list[str]: return ["git", "-C", str(git_c_path.resolve())] -def is_in_git_repo(git_c_path: Optional[Path] = None) -> bool: +def is_in_git_repo(git_c_path: Path | None = None) -> bool: """Predicate for determining if operating within the context of an accessible git repository. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -37,7 +36,7 @@ def is_in_git_repo(git_c_path: Optional[Path] = None) -> bool: return not bool(subprocess.run(cmd, check=False, capture_output=True).returncode) # noqa: S603 -def ensure_git_repo_access(git_c_path: Optional[Path] = None) -> None: +def ensure_git_repo_access(git_c_path: Path | None = None) -> None: """Ensure user account executing `git` has access to the repository. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -104,7 +103,7 @@ def ensure_git_repo_access(git_c_path: Optional[Path] = None) -> None: LOG.warning(cleandoc(msg), extra=MARKUP) -def git_remote(git_c_path: Optional[Path] = None) -> str: +def git_remote(git_c_path: Path | None = None) -> str: """Get the git remote and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -135,7 +134,7 @@ def git_remote(git_c_path: Optional[Path] = None) -> str: return remote -def git_fetch(repo: Optional[str] = None, ref: Optional[str] = None, git_c_path: Optional[Path] = None) -> None: +def git_fetch(repo: str | None = None, ref: str | None = None, git_c_path: Path | None = None) -> None: """Execute a `git fetch` command with optional repository and refspec specified. See git documentation for more detail: https://git-scm.com/docs/git-fetch @@ -158,7 +157,7 @@ def git_fetch(repo: Optional[str] = None, ref: Optional[str] = None, git_c_path: raise PhylumCalledProcessError(err, msg) from err -def git_branch_exists(ref_path: str, git_c_path: Optional[Path] = None) -> bool: +def git_branch_exists(ref_path: str, git_c_path: Path | None = None) -> bool: """Predicate for whether a given branch exists. `ref_path` is meant to be an "exact path" to a specific reference (e.g., `refs/remotes/origin/main`) @@ -178,7 +177,7 @@ def git_branch_exists(ref_path: str, git_c_path: Optional[Path] = None) -> bool: return True -def git_set_remote_head(remote: str, git_c_path: Optional[Path] = None) -> None: +def git_set_remote_head(remote: str, git_c_path: Path | None = None) -> None: """Set the remote HEAD ref for a given remote. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -197,7 +196,7 @@ def git_set_remote_head(remote: str, git_c_path: Optional[Path] = None) -> None: raise PhylumCalledProcessError(err, msg) from err -def git_default_branch_name(remote: str, git_c_path: Optional[Path] = None) -> str: +def git_default_branch_name(remote: str, git_c_path: Path | None = None) -> str: """Get the default branch name and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -239,7 +238,7 @@ def git_default_branch_name(remote: str, git_c_path: Optional[Path] = None) -> s return default_branch_name -def git_root_dir(git_c_path: Optional[Path] = None) -> Path: +def git_root_dir(git_c_path: Path | None = None) -> Path: """Get the top-level (root) directory of the git working tree and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -261,7 +260,7 @@ def git_root_dir(git_c_path: Optional[Path] = None) -> Path: return Path(git_root).resolve() -def git_current_branch_name(git_c_path: Optional[Path] = None) -> str: +def git_current_branch_name(git_c_path: Path | None = None) -> str: """Get the current branch name and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -283,7 +282,7 @@ def git_current_branch_name(git_c_path: Optional[Path] = None) -> str: return current_branch -def git_hash_object(object_path: Path, git_c_path: Optional[Path] = None) -> str: +def git_hash_object(object_path: Path, git_c_path: Path | None = None) -> str: """Get the unique key that git uses to refer to the blob type data object for the provided path and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -306,7 +305,7 @@ def git_hash_object(object_path: Path, git_c_path: Optional[Path] = None) -> str return hash_object -def git_repo_name(git_c_path: Optional[Path] = None) -> str: +def git_repo_name(git_c_path: Path | None = None) -> str: """Get the git repository name and return it. The optional `git_c_path` is used to tell `git` to run as if it were started in that @@ -361,8 +360,8 @@ def git_repo_name(git_c_path: Optional[Path] = None) -> str: @contextlib.contextmanager def git_worktree( commit: str, - env: Optional[Mapping[str, str]] = None, - git_c_path: Optional[Path] = None, + env: Mapping[str, str] | None = None, + git_c_path: Path | None = None, ) -> Generator[Path, None, None]: """Create a git worktree at the given commit. @@ -395,7 +394,7 @@ def git_worktree( remove_git_worktree(temp_dir_path) -def remove_git_worktree(worktree: Path, git_c_path: Optional[Path] = None) -> None: +def remove_git_worktree(worktree: Path, git_c_path: Path | None = None) -> None: """Remove a given git worktree. The optional `git_c_path` is used to tell `git` to run as if it were started in that diff --git a/src/phylum/console.py b/src/phylum/console.py index b095eb0..ad5e7f5 100644 --- a/src/phylum/console.py +++ b/src/phylum/console.py @@ -5,7 +5,6 @@ """ from collections.abc import Mapping -from typing import Union from rich.console import Console from rich.style import Style @@ -19,7 +18,7 @@ # References: # * https://rich.readthedocs.io/en/latest/style.html # * https://rich.readthedocs.io/en/latest/appendix/colors.html -PHYLUM_STYLES: Mapping[str, Union[str, Style]] = { +PHYLUM_STYLES: Mapping[str, str | Style] = { # Styles that have been added to the default: "logging.level.trace": Style(dim=True), # Styles that have been modified from the default: diff --git a/src/phylum/github.py b/src/phylum/github.py index d1a2558..ab64bcb 100644 --- a/src/phylum/github.py +++ b/src/phylum/github.py @@ -3,7 +3,7 @@ from inspect import cleandoc import os import time -from typing import Any, Optional +from typing import Any import requests @@ -25,7 +25,7 @@ PAT_REF = "https://docs.github.com/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" -def get_headers(github_token: Optional[str] = None) -> dict[str, str]: +def get_headers(github_token: str | None = None) -> dict[str, str]: """Get the headers to use for a GitHub API request. Authenticated requests are made by providing a GitHub token. The token can be passed by parameter @@ -50,8 +50,8 @@ def get_headers(github_token: Optional[str] = None) -> dict[str, str]: @progress_spinner("Making GitHub API request") def github_request( api_url: str, - params: Optional[dict] = None, - github_token: Optional[str] = None, + params: dict | None = None, + github_token: str | None = None, timeout: float = REQ_TIMEOUT, ) -> Any: """Make a request to a given GitHub API endpoint and return the response. diff --git a/src/phylum/init/cli.py b/src/phylum/init/cli.py index 885058c..73e9bd6 100644 --- a/src/phylum/init/cli.py +++ b/src/phylum/init/cli.py @@ -13,7 +13,6 @@ import subprocess import sys import tempfile -from typing import Optional import zipfile from packaging.utils import canonicalize_version @@ -77,7 +76,7 @@ def get_phylum_cli_version(cli_path: Path) -> str: return version -def get_phylum_bin_path() -> tuple[Optional[Path], Optional[str]]: +def get_phylum_bin_path() -> tuple[Path | None, str | None]: """Get the current path and corresponding version to the Phylum CLI binary and return them.""" # Look for `phylum` on the PATH first which_cli_path = shutil.which("phylum") @@ -248,7 +247,7 @@ def version_check(version: str) -> str: return version -def process_version(version: Optional[str]) -> str: +def process_version(version: str | None) -> str: """Process the version argument and return it. Avoid external GitHub API calls when an existing Phylum CLI is installed and it's version is supported. @@ -298,7 +297,7 @@ def get_archive_url(tag_name: str, archive_name: str) -> str: return archive_url -def is_token_set(phylum_settings_path: Path, token: Optional[str] = None) -> bool: +def is_token_set(phylum_settings_path: Path, token: str | None = None) -> bool: """Check if any token is already set in the given CLI configuration file. Optionally, check if a specific given `token` is set. @@ -562,7 +561,7 @@ def confirm_setup() -> None: LOG.debug(help_output) -def get_args(args: Optional[Sequence[str]] = None) -> argparse.Namespace: +def get_args(args: Sequence[str] | None = None) -> argparse.Namespace: """Get the arguments from the command line or input parameter, parse and return them.""" parser = argparse.ArgumentParser( prog=SCRIPT_NAME, @@ -642,7 +641,7 @@ def get_args(args: Optional[Sequence[str]] = None) -> argparse.Namespace: return parser.parse_args(args=args) -def main(args: Optional[Sequence[str]] = None) -> int: +def main(args: Sequence[str] | None = None) -> int: """Provide the main entrypoint.""" parsed_args = get_args(args=args) set_logger_level(parsed_args.verbose - parsed_args.quiet) diff --git a/src/phylum/logger.py b/src/phylum/logger.py index bd81362..746ed17 100644 --- a/src/phylum/logger.py +++ b/src/phylum/logger.py @@ -1,10 +1,11 @@ """Configure the logging features for the package.""" +from collections.abc import Callable import inspect import logging import sys from types import FunctionType, MethodType -from typing import Any, Callable, Optional +from typing import Any from rich.logging import RichHandler from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn @@ -41,7 +42,7 @@ # * https://stackoverflow.com/a/35804945 # * https://github.com/python/cpython/issues/75913#issuecomment-1093761548 # * https://github.com/madphysicist/haggis/blob/master/src/haggis/logs.py -def add_logging_level(level_name: str, level_num: int, method_name: Optional[str] = None) -> None: +def add_logging_level(level_name: str, level_num: int, method_name: str | None = None) -> None: """Add a new logging level to the `logging` module and the currently configured logging class. `level_name` becomes an attribute of the `logging` module with the value `level_num`. diff --git a/tests/unit/test_package_metadata.py b/tests/unit/test_package_metadata.py index 987760e..8d61c34 100644 --- a/tests/unit/test_package_metadata.py +++ b/tests/unit/test_package_metadata.py @@ -18,7 +18,7 @@ def test_project_version(): def test_python_version(): """Ensure the python version used to test is a supported version.""" - supported_minor_versions = (9, 10, 11, 12) + supported_minor_versions = (10, 11, 12, 13) python_version = sys.version_info acceptable_python_major_version = 3 assert python_version.major == acceptable_python_major_version, "Only Python 3 is supported" diff --git a/tox.ini b/tox.ini index 3852d47..4e50d42 100644 --- a/tox.ini +++ b/tox.ini @@ -1,15 +1,15 @@ [tox] # Sub-commands were introduced in v4. These are used in workflows and docs. min_version = 4 -envlist = py39, py310, py311, py312 +envlist = py310, py311, py312, py313 isolated_build = true [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [testenv] description = Test environment for minor Python version