From a820b1e28f5638702ff873a67450dc79a6d0d511 Mon Sep 17 00:00:00 2001 From: David Fritzsche Date: Sat, 13 Dec 2025 00:40:45 +0100 Subject: [PATCH] Update combinations of Python/mypy/pytest versions used for testing --- .github/workflows/pythonpackage.yml | 14 ++++--- constraints.txt | 28 +++++-------- lock-requirements.sh | 8 ++-- mypy.ini | 2 +- mypy37.ini | 53 +++++++++++++++++++++++++ mypy38.ini | 53 +++++++++++++++++++++++++ mypy39.ini | 53 +++++++++++++++++++++++++ requirements.in | 11 ++---- requirements.txt | 61 +++++++++++------------------ src/pytest_mypy_testing/parser.py | 2 +- src/pytest_mypy_testing/plugin.py | 17 +++++++- tasks.py | 4 +- tox.ini | 35 ++++++++++------- 13 files changed, 245 insertions(+), 96 deletions(-) create mode 100644 mypy37.ini create mode 100644 mypy38.ini create mode 100644 mypy39.ini diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index cc90e2d..58a8bde 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -40,11 +40,13 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: ${{ matrix.python-version }} + python-version: | + ${{ matrix.python-version }} + 3.14 - name: Install dependencies run: | python -m pip install -c constraints.txt pip @@ -62,12 +64,12 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ["3.11"] + python-version: ["3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/constraints.txt b/constraints.txt index 32910a8..dd970c3 100644 --- a/constraints.txt +++ b/constraints.txt @@ -10,23 +10,21 @@ attrs==23.2.0 binaryornot==0.4.4 black==24.2.0 boolean-py==4.0 -build==1.0.3 bump2version==1.0.1 certifi==2024.2.2 chardet==5.2.0 charset-normalizer==3.3.2 click==8.1.7 +colorama==0.4.6 ; sys_platform == 'win32' coverage==7.4.3 distlib==0.3.8 docutils==0.20.1 -exceptiongroup==1.2.0 +exceptiongroup==1.2.0 ; python_full_version < '3.11' filelock==3.13.1 -flake8==7.0.0 +flake8==7.3.0 flake8-bugbear==24.2.6 flake8-comprehensions==3.14.0 flake8-html==0.4.3 -flake8-logging-format==0.9.0 -flake8-mutable==1.2.0 flake8-pyi==24.1.0 flit==3.12.0 flit-core==3.12.0 @@ -36,22 +34,21 @@ iniconfig==2.0.0 invoke==2.2.0 isort==5.13.2 jinja2==3.1.3 +librt==0.7.3 license-expression==30.2.0 markupsafe==2.1.5 mccabe==0.7.0 -mypy==1.8.0 +mypy==1.19.0 mypy-extensions==1.0.0 packaging==23.2 pathspec==0.12.1 pip==24.0 -pip-tools==7.4.0 platformdirs==4.2.0 pluggy==1.4.0 py==1.11.0 -pycodestyle==2.11.1 -pyflakes==3.2.0 +pycodestyle==2.14.0 +pyflakes==3.4.0 pygments==2.17.2 -pyproject-hooks==1.0.0 pytest==8.0.2 pytest-cov==4.1.0 pytest-html==4.1.1 @@ -61,19 +58,12 @@ requests==2.31.0 reuse==3.0.1 setuptools==69.1.1 six==1.16.0 -tomli==2.0.1 +tomli==2.0.1 ; python_full_version <= '3.11' tomli-w==1.2.0 tox==3.28.0 tox-pyenv==1.1.0 types-invoke==2.0.0.10 typing-extensions==4.10.0 urllib3==2.2.1 +uv==0.9.17 virtualenv==20.25.1 -wheel==0.42.0 -atomicwrites==1.4.0 -filelock <3.12.3; python_version < "3.8" -importlib-metadata <6.8; python_version < "3.8" -platformdirs <4.1; python_version < "3.8" -pluggy <1.3; python_version < "3.8" -typing-extensions <4.8; python_version < "3.8" -zipp <3.16; python_version < "3.8" diff --git a/lock-requirements.sh b/lock-requirements.sh index 9d0baaf..ecbcfe3 100755 --- a/lock-requirements.sh +++ b/lock-requirements.sh @@ -7,10 +7,10 @@ export CUSTOM_COMPILE_COMMAND="./lock-requirements.sh" export PYTHONWARNINGS=ignore -pip-compile \ - --unsafe-package='' \ +uv pip compile \ --no-emit-index-url \ - --resolver=backtracking \ + --universal \ + --no-build \ -o requirements.txt \ requirements.in \ "$@" @@ -26,4 +26,4 @@ cat >constraints.txt <>constraints.txt -cat constraints.in | grep -v -E '^#' >>constraints.txt +# cat constraints.in | grep -v -E '^#' >>constraints.txt diff --git a/mypy.ini b/mypy.ini index 0ddd5a5..97f10a6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ # SPDX-License-Identifier: CC0-1.0 [mypy] -python_version = 3.8 +python_version = 3.10 mypy_path = src diff --git a/mypy37.ini b/mypy37.ini new file mode 100644 index 0000000..7dbb797 --- /dev/null +++ b/mypy37.ini @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: David Fritzsche +# SPDX-License-Identifier: CC0-1.0 +[mypy] + +python_version = 3.7 + +mypy_path = src + +verbosity = 0 + +# Show some context in the error message +show_error_context = True + +# Unfortunately, outputting the column number confuses Visual Studio Code +show_column_numbers = True + +# follow_imports = (normal|silent|skip|error) +# cf. https://mypy.readthedocs.io/en/latest/running_mypy.html#follow-imports +# silent = Follow all imports and type check, but suppress any error messages +# in imported modules +follow_imports = silent + +# Do not complain about missing imports +ignore_missing_imports = False + +# Enables PEP 420 style namespace packages. (default False) +namespace_packages = False + +# explicit_package_bases = True + +# Type-checks the interior of functions without type annotations (default False) +check_untyped_defs = True + +# Warn about unused per-module sections (default False) +warn_unused_configs = True + +# Warns about casting an expression to its inferred type (default False) +warn_redundant_casts = True + +# Warn about unused `# type: ignore` comments (default False) +warn_unused_ignores = True + +# Shows a warning when returning a value with type Any from a function declared +# with a non-Any return type (default False) +warn_return_any = True + +# Strict Optional checks. +# If False, mypy treats None as compatible with every type. (default True) +strict_optional = True + + +[mypy-py.*] +ignore_missing_imports = True diff --git a/mypy38.ini b/mypy38.ini new file mode 100644 index 0000000..0ddd5a5 --- /dev/null +++ b/mypy38.ini @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: David Fritzsche +# SPDX-License-Identifier: CC0-1.0 +[mypy] + +python_version = 3.8 + +mypy_path = src + +verbosity = 0 + +# Show some context in the error message +show_error_context = True + +# Unfortunately, outputting the column number confuses Visual Studio Code +show_column_numbers = True + +# follow_imports = (normal|silent|skip|error) +# cf. https://mypy.readthedocs.io/en/latest/running_mypy.html#follow-imports +# silent = Follow all imports and type check, but suppress any error messages +# in imported modules +follow_imports = silent + +# Do not complain about missing imports +ignore_missing_imports = False + +# Enables PEP 420 style namespace packages. (default False) +namespace_packages = False + +# explicit_package_bases = True + +# Type-checks the interior of functions without type annotations (default False) +check_untyped_defs = True + +# Warn about unused per-module sections (default False) +warn_unused_configs = True + +# Warns about casting an expression to its inferred type (default False) +warn_redundant_casts = True + +# Warn about unused `# type: ignore` comments (default False) +warn_unused_ignores = True + +# Shows a warning when returning a value with type Any from a function declared +# with a non-Any return type (default False) +warn_return_any = True + +# Strict Optional checks. +# If False, mypy treats None as compatible with every type. (default True) +strict_optional = True + + +[mypy-py.*] +ignore_missing_imports = True diff --git a/mypy39.ini b/mypy39.ini new file mode 100644 index 0000000..ab879ee --- /dev/null +++ b/mypy39.ini @@ -0,0 +1,53 @@ +# SPDX-FileCopyrightText: David Fritzsche +# SPDX-License-Identifier: CC0-1.0 +[mypy] + +python_version = 3.9 + +mypy_path = src + +verbosity = 0 + +# Show some context in the error message +show_error_context = True + +# Unfortunately, outputting the column number confuses Visual Studio Code +show_column_numbers = True + +# follow_imports = (normal|silent|skip|error) +# cf. https://mypy.readthedocs.io/en/latest/running_mypy.html#follow-imports +# silent = Follow all imports and type check, but suppress any error messages +# in imported modules +follow_imports = silent + +# Do not complain about missing imports +ignore_missing_imports = False + +# Enables PEP 420 style namespace packages. (default False) +namespace_packages = False + +# explicit_package_bases = True + +# Type-checks the interior of functions without type annotations (default False) +check_untyped_defs = True + +# Warn about unused per-module sections (default False) +warn_unused_configs = True + +# Warns about casting an expression to its inferred type (default False) +warn_redundant_casts = True + +# Warn about unused `# type: ignore` comments (default False) +warn_unused_ignores = True + +# Shows a warning when returning a value with type Any from a function declared +# with a non-Any return type (default False) +warn_return_any = True + +# Strict Optional checks. +# If False, mypy treats None as compatible with every type. (default True) +strict_optional = True + + +[mypy-py.*] +ignore_missing_imports = True diff --git a/requirements.in b/requirements.in index d27fe60..885b2f7 100644 --- a/requirements.in +++ b/requirements.in @@ -3,19 +3,16 @@ black >=24,<25 bump2version coverage[toml] -flit >=3.12,<4 flake8-bugbear flake8-comprehensions flake8-html -flake8-logging-format -flake8-mutable flake8-pyi +flit >=3.12,<4 fsfe-reuse invoke isort -mypy ~=1.8 +mypy ~=1.19.0 pip -pip-tools pytest pytest-cov pytest-html @@ -23,6 +20,4 @@ setuptools >=69 tox <4 tox-pyenv types-invoke - -# Consider constraints constraints.in --c constraints.in +uv ~=0.9.17 diff --git a/requirements.txt b/requirements.txt index d510e9c..cd51162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,5 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# ./lock-requirements.sh -# +# This file was autogenerated by uv via the following command: +# uv pip compile --no-emit-index-url --universal --no-build -o requirements.txt requirements.in attrs==23.2.0 # via flake8-bugbear binaryornot==0.4.4 @@ -14,8 +10,6 @@ boolean-py==4.0 # via # license-expression # reuse -build==1.0.3 - # via pip-tools bump2version==1.0.1 # via -r requirements.in certifi==2024.2.2 @@ -27,10 +21,13 @@ chardet==5.2.0 charset-normalizer==3.3.2 # via requests click==8.1.7 + # via black +colorama==0.4.6 ; sys_platform == 'win32' # via - # black - # pip-tools -coverage[toml]==7.4.3 + # click + # pytest + # tox +coverage==7.4.3 # via # -r requirements.in # pytest-cov @@ -38,18 +35,17 @@ distlib==0.3.8 # via virtualenv docutils==0.20.1 # via flit -exceptiongroup==1.2.0 +exceptiongroup==1.2.0 ; python_full_version < '3.11' # via pytest filelock==3.13.1 # via # tox # virtualenv -flake8==7.0.0 +flake8==7.3.0 # via # flake8-bugbear # flake8-comprehensions # flake8-html - # flake8-mutable # flake8-pyi flake8-bugbear==24.2.6 # via -r requirements.in @@ -57,10 +53,6 @@ flake8-comprehensions==3.14.0 # via -r requirements.in flake8-html==0.4.3 # via -r requirements.in -flake8-logging-format==0.9.0 - # via -r requirements.in -flake8-mutable==1.2.0 - # via -r requirements.in flake8-pyi==24.1.0 # via -r requirements.in flit==3.12.0 @@ -82,13 +74,15 @@ jinja2==3.1.3 # flake8-html # pytest-html # reuse +librt==0.7.3 + # via mypy license-expression==30.2.0 # via reuse markupsafe==2.1.5 # via jinja2 mccabe==0.7.0 # via flake8 -mypy==1.8.0 +mypy==1.19.0 # via -r requirements.in mypy-extensions==1.0.0 # via @@ -97,18 +91,16 @@ mypy-extensions==1.0.0 packaging==23.2 # via # black - # build # pytest # tox pathspec==0.12.1 - # via black + # via + # black + # mypy pip==24.0 # via # -r requirements.in # flit - # pip-tools -pip-tools==7.4.0 - # via -r requirements.in platformdirs==4.2.0 # via # black @@ -119,18 +111,14 @@ pluggy==1.4.0 # tox py==1.11.0 # via tox -pycodestyle==2.11.1 +pycodestyle==2.14.0 # via flake8 -pyflakes==3.2.0 +pyflakes==3.4.0 # via # flake8 # flake8-pyi pygments==2.17.2 # via flake8-html -pyproject-hooks==1.0.0 - # via - # build - # pip-tools pytest==8.0.2 # via # -r requirements.in @@ -150,19 +138,14 @@ requests==2.31.0 reuse==3.0.1 # via fsfe-reuse setuptools==69.1.1 - # via - # -r requirements.in - # pip-tools + # via -r requirements.in six==1.16.0 # via tox -tomli==2.0.1 +tomli==2.0.1 ; python_full_version <= '3.11' # via # black - # build # coverage # mypy - # pip-tools - # pyproject-hooks # pytest # tox tomli-w==1.2.0 @@ -181,7 +164,7 @@ typing-extensions==4.10.0 # mypy urllib3==2.2.1 # via requests +uv==0.9.17 + # via -r requirements.in virtualenv==20.25.1 # via tox -wheel==0.42.0 - # via pip-tools diff --git a/src/pytest_mypy_testing/parser.py b/src/pytest_mypy_testing/parser.py index aea3eeb..a92a7d0 100644 --- a/src/pytest_mypy_testing/parser.py +++ b/src/pytest_mypy_testing/parser.py @@ -144,7 +144,7 @@ def _add_end_lineno_if_missing(tree, line_count: int): prev_node: Optional[ast.AST] = None for node in ast.iter_child_nodes(tree): if prev_node is not None: - setattr(prev_node, "end_lineno", node.lineno) # noqa: B010 + setattr(prev_node, "end_lineno", getattr(node, "lineno", 0)) # noqa: B010 prev_node = node if prev_node: setattr(prev_node, "end_lineno", line_count) # noqa: B010 diff --git a/src/pytest_mypy_testing/plugin.py b/src/pytest_mypy_testing/plugin.py index 13cc7d6..932b0d4 100644 --- a/src/pytest_mypy_testing/plugin.py +++ b/src/pytest_mypy_testing/plugin.py @@ -57,7 +57,7 @@ def __init__( self.add_marker(mark) @classmethod - def from_parent(cls, parent, name, mypy_item): + def from_parent(cls, parent, *, name=None, mypy_item=None): # type: ignore return super().from_parent(parent=parent, name=name, mypy_item=mypy_item) def runtest(self) -> None: @@ -120,6 +120,8 @@ def __init__( self.add_marker("mypy") self.mypy_file = parse_file(self.path, config=config) self._mypy_result: Optional[MypyResult] = None + args = getattr(config, "option", None) + self._config_file: Optional[str] = getattr(args, "mypy_config_file", None) @classmethod def from_parent(cls, parent, **kwargs): @@ -148,7 +150,10 @@ def _run_mypy(self, filename: Union[pathlib.Path, os.PathLike, str]) -> MypyResu mypy_cache_dir = os.path.join(tmp_dir_name, "mypy_cache") os.makedirs(mypy_cache_dir) - mypy_args = [ + mypy_args: List[str] = [] + if self._config_file: + mypy_args.append("--config-file={}".format(self._config_file)) + mypy_args += [ "--cache-dir={}".format(mypy_cache_dir), "--check-untyped-defs", "--hide-error-context", @@ -231,6 +236,14 @@ def pytest_configure(config): ) +def pytest_addoption(parser): + parser.addoption( + "--mypy-config-file", + action="store", + default=os.environ.get("PYTEST_MYPY_CONFIG_FILE"), + ) + + def _add_reveal_type_to_builtins(): # Add a reveal_type function to the builtins module import builtins diff --git a/tasks.py b/tasks.py index 7ac44c1..ab78135 100644 --- a/tasks.py +++ b/tasks.py @@ -26,7 +26,7 @@ def pth(ctx): @task(pre=[pth]) -def tox(ctx, parallel="auto", e="ALL"): +def tox(ctx, e="ALL"): import fnmatch import itertools @@ -43,7 +43,7 @@ def tox(ctx, parallel="auto", e="ALL"): ) ) envlist = ",".join(sorted(envs)) - ctx.run(f"tox --parallel={parallel} -e {envlist}", echo=True, pty=MAYBE_PTY) + ctx.run(f"tox -e {envlist}", echo=True, pty=MAYBE_PTY) @task diff --git a/tox.ini b/tox.ini index 9a8a1e3..d8cbd62 100644 --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,10 @@ isolated_build = True envlist = py37-pytest{70,74}-mypy{10,14} - {py38,py39,py310,py311,py312}-pytest{74,83}-mypy{18,114} - py-pytest{74,83}-mypy{18,114} + py38-pytest{74,83}-mypy{18,114} + py39-pytest{74,84}-mypy{18,119} + {py310,py311,py312}-pytest{84,90}-mypy{119} + py-pytest{84,90}-mypy{119} linting minversion = 3.28 @@ -18,23 +20,30 @@ deps = pytest70: pytest~=7.0.1 pytest71: pytest~=7.1.3 pytest72: pytest~=7.2.2 - pytest74: pytest~=7.4.4 # last version to support Python 3.7 + pytest74: pytest~=7.4.4 # last version to support Python 3.7 / noble pytest80: pytest~=8.0.2 pytest80: pytest~=8.0.2 pytest81: pytest~=8.1.2 - pytest83: pytest~=8.3.5 # last version to support Python 3.8 + pytest83: pytest~=8.3.5 # last version to support Python 3.8 / trixie pytest84: pytest~=8.4.2 # last version to support Python 3.9 + pytest90: pytest~=9.0.2 mypy14: mypy==1.4.1 # last version to support Python 3.7 mypy17: mypy==1.7.1 mypy18: mypy==1.8.0 + mypy19: mypy==1.9.0 # noble mypy114: mypy==1.14.1 # last version to support Python 3.8 + mypy115: mypy==1.15.0 # trixie + mypy119: mypy==1.19.0 # last version to support Python 3.9 setenv = COVERAGE_FILE={toxinidir}/build/{envname}/coverage + py37: PYTEST_MYPY_CONFIG_FILE={toxinidir}/mypy37.ini + py38: PYTEST_MYPY_CONFIG_FILE={toxinidir}/mypy38.ini + py39: PYTEST_MYPY_CONFIG_FILE={toxinidir}/mypy39.ini commands = python -m coverage run --context "{envname}" -m pytest {posargs} --junitxml={toxinidir}/build/{envname}/junit.xml [testenv:black] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -c constraints.txt @@ -43,7 +52,7 @@ commands = python -m black --check --fast --diff {posargs} . [testenv:flake8] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -c constraints.txt @@ -51,14 +60,12 @@ deps = flake8-bugbear flake8-comprehensions flake8-html - flake8-mutable flake8-pyi - flake8-logging-format commands = python -m flake8 {posargs} [testenv:isort] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -c constraints.txt @@ -67,17 +74,17 @@ commands = python -m isort . --check {posargs} [testenv:mypy] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -cconstraints.txt - mypy + mypy ~=1.19.0 pytest commands = mypy src tests [testenv:linting] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -cconstraints.txt @@ -92,11 +99,11 @@ commands = {[testenv:mypy]commands} [testenv:lock-requirements] -basepython = python3.10 +basepython = python3.14 skip_install = True deps = -cconstraints.txt - pip-tools + uv allowlist_externals = sh commands =