Repository: dbader/pytest-mypy
Branch: main
Commit: 1b3e931cb6ff
Files: 13
Total size: 48.1 KB
Directory structure:
gitextract_ycxknicb/
├── .github/
│ └── workflows/
│ ├── publication.yml
│ └── validation.yml
├── .gitignore
├── CONTRIBUTING.rst
├── LICENSE
├── README.rst
├── changelog.md
├── pyproject.toml
├── src/
│ └── pytest_mypy/
│ ├── __init__.py
│ └── py.typed
├── tests/
│ ├── conftest.py
│ └── test_pytest_mypy.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/publication.yml
================================================
name: Publication
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.8'
- run: python -m pip install --upgrade tox-gh-actions
- env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: tox -e publish -- upload
================================================
FILE: .github/workflows/validation.yml
================================================
name: Validation
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install --upgrade tox-gh-actions
- run: python -m tox
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
================================================
FILE: CONTRIBUTING.rst
================================================
Contributing
============
Contributions are very welcome. Tests can be run with `tox <https://tox.readthedocs.io/en/latest/>`_.
Please ensure the coverage at least stays the same before you submit a pull request.
Development Environment Setup
-----------------------------
Here's how to install pytest-mypy in development mode so you can test your changes locally:
.. code-block:: bash
tox --devenv venv
venv/bin/pytest --mypy test_example.py
How to publish a new version to PyPI
------------------------------------
Push a tag, and the release will be published automatically.
To publish manually:
.. code-block:: bash
tox -e publish -- upload
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Daniel Bader
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.rst
================================================
pytest-mypy
===================================
Mypy static type checker plugin for pytest
.. image:: https://img.shields.io/pypi/v/pytest-mypy.svg
:target: https://pypi.org/project/pytest-mypy/
:alt: See Latest Release on PyPI
Features
--------
* Runs the mypy static type checker on your source files as part of your pytest test runs.
* Does for `mypy`_ what the `pytest-flake8`_ plugin does for `flake8`_.
* This is a work in progress – pull requests appreciated.
Installation
------------
You can install "pytest-mypy" via `pip`_ from `PyPI`_:
.. code-block:: bash
$ pip install pytest-mypy
Usage
-----
You can enable pytest-mypy with the ``--mypy`` flag:
.. code-block:: bash
$ py.test --mypy test_*.py
Mypy supports `reading configuration settings <http://mypy.readthedocs.io/en/latest/config_file.html>`_ from a ``mypy.ini`` file.
Alternatively, the plugin can be configured in a ``conftest.py`` to invoke mypy with extra options:
.. code-block:: python
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
plugin.mypy_argv.append('--check-untyped-defs')
You can restrict your test run to only perform mypy checks and not any other tests by using the `-m` option:
.. code-block:: bash
py.test --mypy -m mypy test_*.py
License
-------
Distributed under the terms of the `MIT`_ license, "pytest-mypy" is free and open source software
Issues
------
If you encounter any problems, please `file an issue`_ along with a detailed description.
Meta
----
Daniel Bader – `@dbader_org`_ – https://dbader.org – mail@dbader.org
https://github.com/realpython/pytest-mypy
.. _`MIT`: http://opensource.org/licenses/MIT
.. _`file an issue`: https://github.com/realpython/pytest-mypy/issues
.. _`pip`: https://pypi.python.org/pypi/pip/
.. _`PyPI`: https://pypi.python.org/pypi
.. _`mypy`: http://mypy-lang.org/
.. _`pytest-flake8`: https://pypi.python.org/pypi/pytest-flake8
.. _`flake8`: https://pypi.python.org/pypi/flake8
.. _`@dbader_org`: https://twitter.com/dbader_org
================================================
FILE: changelog.md
================================================
# Changelog
The Changelog has moved to https://github.com/realpython/pytest-mypy/releases
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools >= 61.0", "setuptools-scm >= 7.1"]
build-backend = "setuptools.build_meta"
[project]
name = "pytest-mypy"
dynamic = ["version"]
description = "A Pytest Plugin for Mypy"
readme = "README.rst"
license = {file = "LICENSE"}
maintainers = [
{name = "David Tucker", email = "david@tucker.name"}
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Pytest",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"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 :: Implementation :: CPython",
"Topic :: Software Development :: Testing",
]
requires-python = ">=3.8"
dependencies = [
"filelock>=3.0",
"mypy>=1.0",
"pytest>=7.0",
]
[project.entry-points.pytest11]
mypy = "pytest_mypy"
[project.urls]
homepage = "https://github.com/realpython/pytest-mypy"
[tool.setuptools_scm]
================================================
FILE: src/pytest_mypy/__init__.py
================================================
"""Mypy static type checker plugin for Pytest"""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from tempfile import NamedTemporaryFile
import typing
from filelock import FileLock
import mypy.api
import pytest
if typing.TYPE_CHECKING: # pragma: no cover
from typing import (
Any,
Dict,
IO,
Iterator,
List,
Optional,
Tuple,
Union,
)
# https://github.com/pytest-dev/pytest/issues/7469
from _pytest._code.code import TerminalRepr
# https://github.com/pytest-dev/pytest/pull/12661
from _pytest.terminal import TerminalReporter
# https://github.com/pytest-dev/pytest-xdist/issues/1121
from xdist.workermanage import WorkerController # type: ignore
@dataclass(frozen=True) # compat python < 3.10 (kw_only=True)
class MypyConfigStash:
"""Plugin data stored in the pytest.Config stash."""
mypy_results_path: Path
@classmethod
def from_serialized(cls, serialized: str) -> MypyConfigStash:
return cls(mypy_results_path=Path(serialized))
def serialized(self) -> str:
return str(self.mypy_results_path)
item_marker = "mypy"
mypy_argv: List[str] = []
nodeid_name = "mypy"
stash_key = {
"config": pytest.StashKey[MypyConfigStash](),
}
terminal_summary_title = "mypy"
def default_test_name_formatter(*, item: MypyFileItem) -> str:
path = item.path.relative_to(item.config.invocation_params.dir)
return f"[{terminal_summary_title}] {path}"
test_name_formatter = default_test_name_formatter
def default_file_error_formatter(
item: MypyItem,
results: MypyResults,
lines: List[str],
) -> str:
"""Create a string to be displayed when mypy finds errors in a file."""
if item.config.option.mypy_report_style == "mypy":
return "\n".join(lines)
return "\n".join(line.partition(":")[2].strip() for line in lines)
file_error_formatter = default_file_error_formatter
def pytest_addoption(parser: pytest.Parser) -> None:
"""Add options for enabling and running mypy."""
group = parser.getgroup("mypy")
group.addoption("--mypy", action="store_true", help="run mypy on .py files")
group.addoption(
"--mypy-ignore-missing-imports",
action="store_true",
help="suppresses error messages about imports that cannot be resolved",
)
group.addoption(
"--mypy-config-file",
action="store",
type=str,
help="adds custom mypy config file",
)
styles = {
"mypy": "modify the original mypy output as little as possible",
"no-path": "(default) strip the path prefix from mypy errors",
}
group.addoption(
"--mypy-report-style",
choices=list(styles),
help="change the way mypy output is reported:\n"
+ "\n".join(f"- {name}: {desc}" for name, desc in styles.items()),
)
group.addoption(
"--mypy-no-status-check",
action="store_true",
help="ignore mypy's exit status",
)
group.addoption(
"--mypy-xfail",
action="store_true",
help="xfail mypy errors",
)
def _xdist_worker(config: pytest.Config) -> Dict[str, Any]:
try:
return {"input": _xdist_workerinput(config)}
except AttributeError:
return {}
def _xdist_workerinput(node: Union[WorkerController, pytest.Config]) -> Any:
try:
# mypy complains that pytest.Config does not have this attribute,
# but xdist.remote defines it in worker processes.
return node.workerinput # type: ignore[union-attr]
except AttributeError: # compat xdist < 2.0
return node.slaveinput # type: ignore[union-attr]
class MypyXdistControllerPlugin:
"""A plugin that is only registered on xdist controller processes."""
def pytest_configure_node(self, node: WorkerController) -> None:
"""Pass the config stash to workers."""
_xdist_workerinput(node)["mypy_config_stash_serialized"] = node.config.stash[
stash_key["config"]
].serialized()
def pytest_configure(config: pytest.Config) -> None:
"""
Initialize the path used to cache mypy results,
register a custom marker for MypyItems,
and configure the plugin based on the CLI.
"""
xdist_worker = _xdist_worker(config)
if not xdist_worker:
config.pluginmanager.register(MypyControllerPlugin())
# Get the path to a temporary file and delete it.
# The first MypyItem to run will see the file does not exist,
# and it will run and parse mypy results to create it.
# Subsequent MypyItems will see the file exists,
# and they will read the parsed results.
with NamedTemporaryFile(delete=True) as tmp_f:
config.stash[stash_key["config"]] = MypyConfigStash(
mypy_results_path=Path(tmp_f.name),
)
# If xdist is enabled, then the results path should be exposed to
# the workers so that they know where to read parsed results from.
if config.pluginmanager.getplugin("xdist"):
config.pluginmanager.register(MypyXdistControllerPlugin())
else:
# xdist workers create the stash using input from the controller plugin.
config.stash[stash_key["config"]] = MypyConfigStash.from_serialized(
xdist_worker["input"]["mypy_config_stash_serialized"]
)
config.addinivalue_line(
"markers",
f"{item_marker}: mark tests to be checked by mypy.",
)
if config.getoption("--mypy-ignore-missing-imports"):
mypy_argv.append("--ignore-missing-imports")
mypy_config_file = config.getoption("--mypy-config-file")
if mypy_config_file:
mypy_argv.append(f"--config-file={mypy_config_file}")
if any(
[
config.option.mypy,
config.option.mypy_config_file,
config.option.mypy_report_style,
config.option.mypy_ignore_missing_imports,
config.option.mypy_no_status_check,
config.option.mypy_xfail,
],
):
config.pluginmanager.register(MypyCollectionPlugin())
class MypyCollectionPlugin:
"""A Pytest plugin that collects MypyFiles."""
def pytest_collect_file(
self,
file_path: Path,
parent: pytest.Collector,
) -> Optional[MypyFile]:
"""Create a MypyFileItem for every file mypy should run on."""
if file_path.suffix in {".py", ".pyi"}:
# Do not create MypyFile instance for a .py file if a
# .pyi file with the same name already exists;
# pytest will complain about duplicate modules otherwise
if (
file_path.suffix == ".pyi"
or not file_path.with_suffix(".pyi").is_file()
):
return MypyFile.from_parent(parent=parent, path=file_path)
return None
class MypyFile(pytest.File):
"""A File that Mypy will run on."""
def collect(self) -> Iterator[MypyItem]:
"""Create a MypyFileItem for the File."""
yield MypyFileItem.from_parent(parent=self, name=nodeid_name)
# Since mypy might check files that were not collected,
# pytest could pass even though mypy failed!
# To prevent that, add an explicit check for the mypy exit status.
if not self.session.config.option.mypy_no_status_check and not any(
isinstance(item, MypyStatusItem) for item in self.session.items
):
yield MypyStatusItem.from_parent(
parent=self,
name=nodeid_name + "-status",
)
class MypyItem(pytest.Item):
"""A Mypy-related test Item."""
def __init__(self, *args: Any, **kwargs: Any):
super().__init__(*args, **kwargs)
self.add_marker(item_marker)
def repr_failure(
self,
excinfo: pytest.ExceptionInfo[BaseException],
style: Optional[str] = None,
) -> Union[str, TerminalRepr]:
"""
Unwrap mypy errors so we get a clean error message without the
full exception repr.
"""
if excinfo.errisinstance(MypyError):
return str(excinfo.value.args[0])
return super().repr_failure(excinfo)
def _error_severity(line: str) -> Optional[str]:
components = [component.strip() for component in line.split(":", 3)]
if len(components) < 2:
return None
# The second component is either the line or the severity:
# demo/note.py:2: note: By default the bodies of untyped functions are not checked
# demo/sub/conftest.py: error: Duplicate module named "conftest"
return components[2] if components[1].isdigit() else components[1]
class MypyFileItem(MypyItem):
"""A check for Mypy errors in a File."""
def runtest(self) -> None:
"""Raise an exception if mypy found errors for this item."""
results = MypyResults.from_session(self.session)
lines = results.path_lines.get(self.path.resolve(), [])
if lines and not all(_error_severity(line) == "note" for line in lines):
if self.session.config.option.mypy_xfail:
self.add_marker(
pytest.mark.xfail(
raises=MypyError,
reason="mypy errors are expected by --mypy-xfail.",
)
)
raise MypyError(file_error_formatter(self, results, lines))
def reportinfo(self) -> Tuple[Path, None, str]:
"""Produce a heading for the test report."""
return (self.path, None, test_name_formatter(item=self))
class MypyStatusItem(MypyItem):
"""A check for a non-zero mypy exit status."""
def runtest(self) -> None:
"""Raise a MypyError if mypy exited with a non-zero status."""
results = MypyResults.from_session(self.session)
if results.status:
if self.session.config.option.mypy_xfail:
self.add_marker(
pytest.mark.xfail(
raises=MypyError,
reason=(
"A non-zero mypy exit status is expected by --mypy-xfail."
),
)
)
raise MypyError(f"mypy exited with status {results.status}.")
@dataclass(frozen=True) # compat python < 3.10 (kw_only=True)
class MypyResults:
"""Parsed results from Mypy."""
_encoding = "utf-8"
opts: List[str]
args: List[str]
stdout: str
stderr: str
status: int
path_lines: Dict[Optional[Path], List[str]]
def dump(self, results_f: IO[bytes]) -> None:
"""Cache results in a format that can be parsed by load()."""
prepared = vars(self).copy()
prepared["path_lines"] = {
str(path or ""): lines for path, lines in prepared["path_lines"].items()
}
results_f.write(json.dumps(prepared).encode(self._encoding))
@classmethod
def load(cls, results_f: IO[bytes]) -> MypyResults:
"""Get results cached by dump()."""
prepared = json.loads(results_f.read().decode(cls._encoding))
prepared["path_lines"] = {
Path(path) if path else None: lines
for path, lines in prepared["path_lines"].items()
}
return cls(**prepared)
@classmethod
def from_mypy(
cls,
paths: List[Path],
*,
opts: Optional[List[str]] = None,
) -> MypyResults:
"""Generate results from mypy."""
if opts is None:
opts = mypy_argv[:]
args = [str(path) for path in paths]
stdout, stderr, status = mypy.api.run(opts + args)
path_lines: Dict[Optional[Path], List[str]] = {
path.resolve(): [] for path in paths
}
path_lines[None] = []
for line in stdout.split("\n"):
if not line:
continue
try:
path = Path(line.partition(":")[0]).resolve()
except OSError:
path = None
try:
lines = path_lines[path]
except KeyError:
lines = path_lines[None]
lines.append(line)
return cls(
opts=opts,
args=args,
stdout=stdout,
stderr=stderr,
status=status,
path_lines=path_lines,
)
@classmethod
def from_session(cls, session: pytest.Session) -> MypyResults:
"""Load (or generate) cached mypy results for a pytest session."""
mypy_results_path = session.config.stash[stash_key["config"]].mypy_results_path
with FileLock(str(mypy_results_path) + ".lock"):
try:
with open(mypy_results_path, mode="rb") as results_f:
results = cls.load(results_f)
except FileNotFoundError:
cwd = Path.cwd()
results = cls.from_mypy(
[
item.path.relative_to(cwd)
for item in session.items
if isinstance(item, MypyFileItem)
],
)
with open(mypy_results_path, mode="wb") as results_f:
results.dump(results_f)
return results
class MypyError(Exception):
"""
An error caught by mypy, e.g a type checker violation
or a syntax error.
"""
class MypyControllerPlugin:
"""A plugin that is not registered on xdist worker processes."""
def pytest_terminal_summary(
self,
terminalreporter: TerminalReporter,
config: pytest.Config,
) -> None:
"""Report mypy results."""
mypy_results_path = config.stash[stash_key["config"]].mypy_results_path
try:
with open(mypy_results_path, mode="rb") as results_f:
results = MypyResults.load(results_f)
except FileNotFoundError:
# No MypyItems executed.
return
if not results.stdout and not results.stderr:
return
terminalreporter.section(terminal_summary_title)
if results.stdout:
if config.option.mypy_xfail:
terminalreporter.write(results.stdout)
else:
for note in (
unreported_note
for path, lines in results.path_lines.items()
if path is not None
if all(_error_severity(line) == "note" for line in lines)
for unreported_note in lines
):
terminalreporter.write_line(note)
if results.path_lines.get(None):
color = {"red": True} if results.status else {"green": True}
terminalreporter.write_line(
"\n".join(results.path_lines[None]), **color
)
if results.stderr:
terminalreporter.write_line(results.stderr, yellow=True)
def pytest_unconfigure(self, config: pytest.Config) -> None:
"""Clean up the mypy results path."""
config.stash[stash_key["config"]].mypy_results_path.unlink(missing_ok=True)
================================================
FILE: src/pytest_mypy/py.typed
================================================
================================================
FILE: tests/conftest.py
================================================
import mypy.version
pytest_plugins = "pytester"
def pytest_report_header():
return f"mypy: {mypy.version.__version__}"
================================================
FILE: tests/test_pytest_mypy.py
================================================
import signal
import sys
import textwrap
import mypy.version
from packaging.version import Version
import pytest
import pytest_mypy
MYPY_VERSION = Version(mypy.version.__version__)
PYTEST_VERSION = Version(pytest.__version__)
PYTHON_VERSION = Version(
".".join(
str(token)
for token in [
sys.version_info.major,
sys.version_info.minor,
sys.version_info.micro,
]
)
)
@pytest.fixture(
params=[
True, # xdist enabled, active
False, # xdist enabled, inactive
None, # xdist disabled
],
)
def xdist_args(request):
if request.param is None:
return ["-p", "no:xdist"]
return ["-n", "auto"] if request.param else []
@pytest.mark.parametrize("pyfile_count", [1, 2])
def test_mypy_success(testdir, pyfile_count, xdist_args):
"""Verify that running on a module with no type errors passes."""
testdir.makepyfile(
**{
"pyfile_{0}".format(
pyfile_i,
): """
def pyfunc(x: int) -> int:
return x * 2
"""
for pyfile_i in range(pyfile_count)
},
)
result = testdir.runpytest_subprocess(*xdist_args)
result.assert_outcomes()
assert result.ret == pytest.ExitCode.NO_TESTS_COLLECTED
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = pyfile_count
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
@pytest.mark.skipif(
PYTEST_VERSION < Version("7.4"),
reason="https://github.com/pytest-dev/pytest/pull/10935",
)
@pytest.mark.skipif(
PYTHON_VERSION < Version("3.10"),
reason="PEP 597 was added in Python 3.10.",
)
@pytest.mark.skipif(
PYTHON_VERSION >= Version("3.12") and MYPY_VERSION < Version("1.5"),
reason="https://github.com/python/mypy/pull/15558",
)
def test_mypy_encoding_warnings(testdir, monkeypatch):
"""Ensure no warnings are detected by PYTHONWARNDEFAULTENCODING."""
testdir.makepyfile("")
monkeypatch.setenv("PYTHONWARNDEFAULTENCODING", "1")
result = testdir.runpytest_subprocess("--mypy")
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
expected_warnings = 2 # https://github.com/python/mypy/issues/14603
result.assert_outcomes(passed=mypy_checks, warnings=expected_warnings)
def test_mypy_pyi(testdir, xdist_args):
"""
Verify that a .py file will be skipped if
a .pyi file exists with the same filename.
"""
# The incorrect signature below should be ignored
# as the .pyi file takes priority
testdir.makepyfile(
pyfile="""
def pyfunc(x: int) -> str:
return x * 2
""",
)
testdir.makefile(
".pyi",
pyfile="""
def pyfunc(x: int) -> int: ...
""",
)
result = testdir.runpytest_subprocess(*xdist_args)
result.assert_outcomes()
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
def test_mypy_error(testdir, xdist_args):
"""Verify that running on a module with type errors fails."""
testdir.makepyfile(
"""
def pyfunc(x: int) -> str:
return x * 2
""",
)
result = testdir.runpytest_subprocess(*xdist_args)
result.assert_outcomes()
assert "_mypy_results_path" not in result.stderr.str()
assert result.ret == pytest.ExitCode.NO_TESTS_COLLECTED
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(failed=mypy_checks)
result.stdout.fnmatch_lines(["2: error: Incompatible return value*"])
assert "_mypy_results_path" not in result.stderr.str()
assert result.ret == pytest.ExitCode.TESTS_FAILED
def test_mypy_path_error(testdir, xdist_args):
"""Verify that runs are not affected by path errors."""
testdir.makepyfile(
conftest="""
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
class FakePath:
def __init__(self, _):
pass
def resolve(self):
raise OSError
Path = plugin.Path
plugin.Path = FakePath
plugin.MypyResults.from_mypy([], opts=['--version'])
plugin.Path = Path
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
def test_mypy_annotation_unchecked(testdir, xdist_args, tmp_path):
"""Verify that annotation-unchecked warnings do not manifest as an error."""
testdir.makepyfile(
"""
def pyfunc(x):
y: int = 2
return x * y
""",
)
result = testdir.runpytest_subprocess(*xdist_args)
result.assert_outcomes()
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
outcomes = {"passed": mypy_checks}
result.assert_outcomes(**outcomes)
result.stdout.fnmatch_lines(
["*:2: note: By default the bodies of untyped functions are not checked*"]
)
assert result.ret == pytest.ExitCode.OK
def test_mypy_ignore_missings_imports(testdir, xdist_args):
"""
Verify that --mypy-ignore-missing-imports
causes mypy to ignore missing imports.
"""
module_name = "is_always_missing"
testdir.makepyfile(
"""
try:
import {module_name}
except ImportError:
pass
""".format(
module_name=module_name,
),
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(failed=mypy_checks)
result.stdout.fnmatch_lines(
[
"2: error: Cannot find *module named *{module_name}*".format(
module_name=module_name,
),
],
)
assert result.ret == pytest.ExitCode.TESTS_FAILED
result = testdir.runpytest_subprocess("--mypy-ignore-missing-imports", *xdist_args)
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
def test_mypy_config_file(testdir, xdist_args):
"""Verify that --mypy-config-file works."""
testdir.makepyfile(
"""
def pyfunc(x):
return x * 2
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
mypy_config_file = testdir.makeini(
"""
[mypy]
disallow_untyped_defs = True
""",
)
result = testdir.runpytest_subprocess(
"--mypy-config-file",
mypy_config_file,
*xdist_args,
)
result.assert_outcomes(failed=mypy_checks)
def test_mypy_marker(testdir, xdist_args):
"""Verify that -m mypy only runs the mypy tests."""
testdir.makepyfile(
"""
def test_fails():
assert False
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
test_count = 1
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(failed=test_count, passed=mypy_checks)
assert result.ret == pytest.ExitCode.TESTS_FAILED
result = testdir.runpytest_subprocess("--mypy", "-m", "mypy", *xdist_args)
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
def test_non_mypy_error(testdir, xdist_args):
"""Verify that non-MypyError exceptions are passed through the plugin."""
message = "This is not a MypyError."
testdir.makepyfile(
conftest="""
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
class PatchedMypyFileItem(plugin.MypyFileItem):
def runtest(self):
raise Exception('{message}')
plugin.MypyFileItem = PatchedMypyFileItem
""".format(
message=message,
),
)
result = testdir.runpytest_subprocess(*xdist_args)
result.assert_outcomes()
assert result.ret == pytest.ExitCode.NO_TESTS_COLLECTED
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1 # conftest.py
mypy_status_check = 1
result.assert_outcomes(
failed=mypy_file_checks, # patched to raise an Exception
passed=mypy_status_check, # conftest.py has no type errors.
)
result.stdout.fnmatch_lines(["*" + message])
assert result.ret == pytest.ExitCode.TESTS_FAILED
def test_mypy_stderr(testdir, xdist_args):
"""Verify that stderr from mypy is printed."""
stderr = "This is stderr from mypy."
testdir.makepyfile(
conftest="""
import mypy.api
def _patched_run(*args, **kwargs):
return '', '{stderr}', 1
mypy.api.run = _patched_run
""".format(
stderr=stderr,
),
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines([stderr])
def test_mypy_unmatched_stdout(testdir, xdist_args):
"""Verify that unexpected output on stdout from mypy is printed."""
stdout = "This is unexpected output on stdout from mypy."
testdir.makepyfile(
conftest="""
import mypy.api
def _patched_run(*args, **kwargs):
return '{stdout}', '', 1
mypy.api.run = _patched_run
""".format(
stdout=stdout,
),
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines([stdout])
def test_api_mypy_argv(testdir, xdist_args):
"""Ensure that the plugin can be configured in a conftest.py."""
testdir.makepyfile(
conftest="""
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
plugin.mypy_argv.append('--version')
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
assert result.ret == pytest.ExitCode.OK
def test_api_nodeid_name(testdir, xdist_args):
"""Ensure that the plugin can be configured in a conftest.py."""
nodeid_name = "UnmistakableNodeIDName"
testdir.makepyfile(
conftest="""
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
plugin.nodeid_name = '{}'
""".format(
nodeid_name,
),
)
result = testdir.runpytest_subprocess("--mypy", "--verbose", *xdist_args)
result.stdout.fnmatch_lines(["*conftest.py::" + nodeid_name + "*"])
assert result.ret == pytest.ExitCode.OK
def test_api_test_name_formatter(testdir, xdist_args):
"""Ensure that the test_name_formatter can be replaced in a conftest.py."""
test_name = "UnmistakableTestName"
testdir.makepyfile(
conftest=f"""
cause_a_mypy_error: str = 5
def custom_test_name_formatter(item):
return "{test_name}"
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
plugin.test_name_formatter = custom_test_name_formatter
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines([f"*{test_name}*"])
mypy_file_check = 1
mypy_status_check = 1
result.assert_outcomes(failed=mypy_file_check + mypy_status_check)
assert result.ret == pytest.ExitCode.TESTS_FAILED
@pytest.mark.xfail(
Version("0.971") <= MYPY_VERSION,
raises=AssertionError,
reason="https://github.com/python/mypy/issues/13701",
)
@pytest.mark.parametrize(
"module_name",
[
"__init__",
"good",
],
)
def test_mypy_indirect(testdir, xdist_args, module_name):
"""Verify that uncollected files checked by mypy cause a failure."""
testdir.makepyfile(
bad="""
def pyfunc(x: int) -> str:
return x * 2
""",
)
pyfile = testdir.makepyfile(
**{
module_name: """
import bad
""",
},
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args, str(pyfile))
mypy_file_checks = 1
mypy_status_check = 1
result.assert_outcomes(passed=mypy_file_checks, failed=mypy_status_check)
assert result.ret == pytest.ExitCode.TESTS_FAILED
def test_api_file_error_formatter(testdir, xdist_args):
"""Ensure that the file_error_formatter can be replaced in a conftest.py."""
testdir.makepyfile(
bad="""
def pyfunc(x: int) -> str:
return x * 2
""",
)
file_error = "UnmistakableFileError"
testdir.makepyfile(
conftest=f"""
def custom_file_error_formatter(item, results, lines):
return '{file_error}'
def pytest_configure(config):
plugin = config.pluginmanager.getplugin('mypy')
plugin.file_error_formatter = custom_file_error_formatter
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines([f"*{file_error}*"])
assert result.ret == pytest.ExitCode.TESTS_FAILED
def test_pyproject_toml(testdir, xdist_args):
"""Ensure that the plugin allows configuration with pyproject.toml."""
testdir.makefile(
".toml",
pyproject="""
[tool.mypy]
disallow_untyped_defs = true
""",
)
testdir.makepyfile(
conftest="""
def pyfunc(x):
return x * 2
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines(["1: error: Function is missing a type annotation*"])
assert result.ret == pytest.ExitCode.TESTS_FAILED
def test_setup_cfg(testdir, xdist_args):
"""Ensure that the plugin allows configuration with setup.cfg."""
testdir.makefile(
".cfg",
setup="""
[mypy]
disallow_untyped_defs = True
""",
)
testdir.makepyfile(
conftest="""
def pyfunc(x):
return x * 2
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
result.stdout.fnmatch_lines(["1: error: Function is missing a type annotation*"])
assert result.ret == pytest.ExitCode.TESTS_FAILED
@pytest.mark.parametrize("module_name", ["__init__", "test_demo"])
def test_looponfail(testdir, module_name):
"""Ensure that the plugin works with --looponfail."""
pass_source = textwrap.dedent(
"""\
def pyfunc(x: int) -> int:
return x * 2
""",
)
fail_source = textwrap.dedent(
"""\
def pyfunc(x: int) -> str:
return x * 2
""",
)
pyfile = testdir.makepyfile(**{module_name: fail_source})
looponfailroot = testdir.mkdir("looponfailroot")
looponfailroot_pyfile = looponfailroot.join(pyfile.basename)
pyfile.move(looponfailroot_pyfile)
pyfile = looponfailroot_pyfile
testdir.makeini(
textwrap.dedent(
"""\
[pytest]
looponfailroots = {looponfailroots}
""".format(
looponfailroots=looponfailroot,
),
),
)
child = testdir.spawn_pytest(
"--mypy --looponfail " + str(pyfile),
expect_timeout=60.0,
)
def _expect_session():
child.expect("==== test session starts ====")
def _expect_failure():
_expect_session()
child.expect("==== FAILURES ====")
child.expect(pyfile.basename + " ____")
child.expect("2: error: Incompatible return value")
child.expect("==== mypy ====")
child.expect("Found 1 error in 1 file (checked 1 source file)")
child.expect("2 failed")
child.expect("#### LOOPONFAILING ####")
_expect_waiting()
def _expect_waiting():
child.expect("#### waiting for changes ####")
child.expect("Watching")
def _fix():
pyfile.write(pass_source)
_expect_changed()
_expect_success()
def _expect_changed():
child.expect("MODIFIED " + str(pyfile))
def _expect_success():
for _ in range(2):
_expect_session()
child.expect("==== mypy ====")
child.expect("Success: no issues found in 1 source file")
child.expect("2 passed")
_expect_waiting()
def _break():
pyfile.write(fail_source)
_expect_changed()
_expect_failure()
_expect_failure()
_fix()
_break()
_fix()
child.kill(signal.SIGTERM)
def test_mypy_results_from_mypy_with_opts():
"""MypyResults.from_mypy respects passed options."""
mypy_results = pytest_mypy.MypyResults.from_mypy([], opts=["--version"])
assert mypy_results.status == 0
assert str(MYPY_VERSION) in mypy_results.stdout
def test_mypy_no_output(testdir, xdist_args):
"""No terminal summary is shown if there is no output from mypy."""
testdir.makepyfile(
# Mypy prints a success message to stderr by default:
# "Success: no issues found in 1 source file"
# Clear stderr and unmatched_stdout to simulate mypy having no output:
conftest="""
import pytest
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
pytest_mypy = config.pluginmanager.getplugin("mypy")
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
with open(mypy_config_stash.mypy_results_path, mode="wb") as results_f:
pytest_mypy.MypyResults(
opts=[],
args=[],
stdout="",
stderr="",
status=0,
path_lines={},
).dump(results_f)
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(passed=mypy_checks)
assert result.ret == pytest.ExitCode.OK
assert f"= {pytest_mypy.terminal_summary_title} =" not in str(result.stdout)
def test_py_typed(testdir):
"""Mypy recognizes that pytest_mypy is typed."""
name = "typed"
testdir.makepyfile(**{name: "import pytest_mypy"})
result = testdir.run("mypy", f"{name}.py")
assert result.ret == 0
def test_mypy_no_status_check(testdir, xdist_args):
"""Verify that --mypy-no-status-check disables MypyStatusItem collection."""
testdir.makepyfile("one: int = 1")
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
result.assert_outcomes(passed=mypy_file_checks + mypy_status_check)
assert result.ret == pytest.ExitCode.OK
result = testdir.runpytest_subprocess("--mypy-no-status-check", *xdist_args)
result.assert_outcomes(passed=mypy_file_checks)
assert result.ret == pytest.ExitCode.OK
def test_mypy_xfail_passes(testdir, xdist_args):
"""Verify that --mypy-xfail passes passes."""
testdir.makepyfile("one: int = 1")
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
result.assert_outcomes(passed=mypy_file_checks + mypy_status_check)
assert result.ret == pytest.ExitCode.OK
result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args)
result.assert_outcomes(passed=mypy_file_checks + mypy_status_check)
assert result.ret == pytest.ExitCode.OK
def test_mypy_xfail_xfails(testdir, xdist_args):
"""Verify that --mypy-xfail xfails failures."""
testdir.makepyfile("one: str = 1")
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
result.assert_outcomes(failed=mypy_file_checks + mypy_status_check)
assert result.ret == pytest.ExitCode.TESTS_FAILED
result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args)
result.assert_outcomes(xfailed=mypy_file_checks + mypy_status_check)
assert result.ret == pytest.ExitCode.OK
def test_mypy_xfail_reports_stdout(testdir, xdist_args):
"""Verify that --mypy-xfail reports stdout from mypy."""
stdout = "a distinct string on stdout"
testdir.makepyfile(
conftest=f"""
import pytest
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
pytest_mypy = config.pluginmanager.getplugin("mypy")
mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]]
with open(mypy_config_stash.mypy_results_path, mode="wb") as results_f:
pytest_mypy.MypyResults(
opts=[],
args=[],
stdout="{stdout}",
stderr="",
status=0,
path_lines={{}},
).dump(results_f)
""",
)
result = testdir.runpytest_subprocess("--mypy", *xdist_args)
assert result.ret == pytest.ExitCode.OK
assert stdout not in result.stdout.str()
result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args)
assert result.ret == pytest.ExitCode.OK
assert stdout in result.stdout.str()
def test_error_severity():
"""Verify that non-error lines produce no severity."""
assert pytest_mypy._error_severity("arbitrary line with no error") is None
def test_mypy_report_style(testdir, xdist_args):
"""Verify that --mypy-report-style functions correctly."""
module_name = "unmistakable_module_name"
testdir.makepyfile(
**{
module_name: """
def pyfunc(x: int) -> str:
return x * 2
"""
},
)
result = testdir.runpytest_subprocess("--mypy-report-style", "no-path", *xdist_args)
mypy_file_checks = 1
mypy_status_check = 1
mypy_checks = mypy_file_checks + mypy_status_check
result.assert_outcomes(failed=mypy_checks)
result.stdout.fnmatch_lines(["2: error: Incompatible return value*"])
assert result.ret == pytest.ExitCode.TESTS_FAILED
result = testdir.runpytest_subprocess("--mypy-report-style", "mypy", *xdist_args)
result.assert_outcomes(failed=mypy_checks)
result.stdout.fnmatch_lines(
[f"{module_name}.py:2: error: Incompatible return value*"]
)
assert result.ret == pytest.ExitCode.TESTS_FAILED
================================================
FILE: tox.ini
================================================
# For more information about tox, see https://tox.readthedocs.io/en/latest/
[tox]
minversion = 4.4
isolated_build = true
envlist =
py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
py313-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
static
publish
[gh-actions]
python =
3.8: py38-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
3.9: py39-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
3.10: py310-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
3.11: py311-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
3.12: py312-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}, static, publish
3.13: py313-pytest{7.0, 7.x, 8.0, 8.x}-mypy{1.0, 1.x}-xdist{1.x, 2.0, 2.x, 3.0, 3.x}
[testenv]
constrain_package_deps = true
deps =
pytest7.0: pytest ~= 7.0.0
pytest7.x: pytest ~= 7.0
pytest8.0: pytest ~= 8.0.0
pytest8.x: pytest ~= 8.0
mypy1.0: mypy ~= 1.0.0
mypy1.x: mypy ~= 1.0
xdist1.x: pytest-xdist ~= 1.0
xdist2.0: pytest-xdist ~= 2.0.0
xdist2.x: pytest-xdist ~= 2.0
xdist3.0: pytest-xdist ~= 3.0.0
xdist3.x: pytest-xdist ~= 3.0
packaging ~= 21.3
pytest-cov ~= 4.1.0
pytest-randomly ~= 3.4
setenv =
COVERAGE_FILE = .coverage.{envname}
commands = pytest -p no:mypy {posargs:--cov pytest_mypy --cov-branch --cov-fail-under 100 --cov-report term-missing -n auto}
[pytest]
testpaths = tests
[testenv:publish]
passenv = TWINE_*
constrain_package_deps = false
deps =
build[virtualenv] ~= 1.0.0
twine ~= 5.0.0
commands =
{envpython} -m build --outdir {envtmpdir} .
twine {posargs:check} {envtmpdir}/*
[testenv:static]
basepython = py312 # pytest.Node.from_parent uses typing.Self
deps =
bandit ~= 1.7.0
black ~= 24.2.0
flake8 ~= 7.0.0
mypy ~= 1.11.0
pytest-xdist >= 3.6.0 # needed for type-checking
commands =
black --check src tests
flake8 src tests
mypy --strict src
bandit --recursive src
[flake8]
max-line-length = 88
extend-ignore = E203
gitextract_ycxknicb/ ├── .github/ │ └── workflows/ │ ├── publication.yml │ └── validation.yml ├── .gitignore ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── changelog.md ├── pyproject.toml ├── src/ │ └── pytest_mypy/ │ ├── __init__.py │ └── py.typed ├── tests/ │ ├── conftest.py │ └── test_pytest_mypy.py └── tox.ini
SYMBOL INDEX (64 symbols across 3 files)
FILE: src/pytest_mypy/__init__.py
class MypyConfigStash (line 38) | class MypyConfigStash:
method from_serialized (line 44) | def from_serialized(cls, serialized: str) -> MypyConfigStash:
method serialized (line 47) | def serialized(self) -> str:
function default_test_name_formatter (line 60) | def default_test_name_formatter(*, item: MypyFileItem) -> str:
function default_file_error_formatter (line 68) | def default_file_error_formatter(
function pytest_addoption (line 82) | def pytest_addoption(parser: pytest.Parser) -> None:
function _xdist_worker (line 119) | def _xdist_worker(config: pytest.Config) -> Dict[str, Any]:
function _xdist_workerinput (line 126) | def _xdist_workerinput(node: Union[WorkerController, pytest.Config]) -> ...
class MypyXdistControllerPlugin (line 135) | class MypyXdistControllerPlugin:
method pytest_configure_node (line 138) | def pytest_configure_node(self, node: WorkerController) -> None:
function pytest_configure (line 145) | def pytest_configure(config: pytest.Config) -> None:
class MypyCollectionPlugin (line 199) | class MypyCollectionPlugin:
method pytest_collect_file (line 202) | def pytest_collect_file(
class MypyFile (line 220) | class MypyFile(pytest.File):
method collect (line 223) | def collect(self) -> Iterator[MypyItem]:
class MypyItem (line 238) | class MypyItem(pytest.Item):
method __init__ (line 241) | def __init__(self, *args: Any, **kwargs: Any):
method repr_failure (line 245) | def repr_failure(
function _error_severity (line 259) | def _error_severity(line: str) -> Optional[str]:
class MypyFileItem (line 269) | class MypyFileItem(MypyItem):
method runtest (line 272) | def runtest(self) -> None:
method reportinfo (line 286) | def reportinfo(self) -> Tuple[Path, None, str]:
class MypyStatusItem (line 291) | class MypyStatusItem(MypyItem):
method runtest (line 294) | def runtest(self) -> None:
class MypyResults (line 311) | class MypyResults:
method dump (line 323) | def dump(self, results_f: IO[bytes]) -> None:
method load (line 332) | def load(cls, results_f: IO[bytes]) -> MypyResults:
method from_mypy (line 342) | def from_mypy(
method from_session (line 383) | def from_session(cls, session: pytest.Session) -> MypyResults:
class MypyError (line 404) | class MypyError(Exception):
class MypyControllerPlugin (line 411) | class MypyControllerPlugin:
method pytest_terminal_summary (line 414) | def pytest_terminal_summary(
method pytest_unconfigure (line 450) | def pytest_unconfigure(self, config: pytest.Config) -> None:
FILE: tests/conftest.py
function pytest_report_header (line 6) | def pytest_report_header():
FILE: tests/test_pytest_mypy.py
function xdist_args (line 33) | def xdist_args(request):
function test_mypy_success (line 40) | def test_mypy_success(testdir, pyfile_count, xdist_args):
function test_mypy_encoding_warnings (line 76) | def test_mypy_encoding_warnings(testdir, monkeypatch):
function test_mypy_pyi (line 88) | def test_mypy_pyi(testdir, xdist_args):
function test_mypy_error (line 119) | def test_mypy_error(testdir, xdist_args):
function test_mypy_path_error (line 141) | def test_mypy_path_error(testdir, xdist_args):
function test_mypy_annotation_unchecked (line 168) | def test_mypy_annotation_unchecked(testdir, xdist_args, tmp_path):
function test_mypy_ignore_missings_imports (line 191) | def test_mypy_ignore_missings_imports(testdir, xdist_args):
function test_mypy_config_file (line 225) | def test_mypy_config_file(testdir, xdist_args):
function test_mypy_marker (line 253) | def test_mypy_marker(testdir, xdist_args):
function test_non_mypy_error (line 273) | def test_non_mypy_error(testdir, xdist_args):
function test_mypy_stderr (line 304) | def test_mypy_stderr(testdir, xdist_args):
function test_mypy_unmatched_stdout (line 323) | def test_mypy_unmatched_stdout(testdir, xdist_args):
function test_api_mypy_argv (line 342) | def test_api_mypy_argv(testdir, xdist_args):
function test_api_nodeid_name (line 355) | def test_api_nodeid_name(testdir, xdist_args):
function test_api_test_name_formatter (line 372) | def test_api_test_name_formatter(testdir, xdist_args):
function test_mypy_indirect (line 407) | def test_mypy_indirect(testdir, xdist_args, module_name):
function test_api_file_error_formatter (line 429) | def test_api_file_error_formatter(testdir, xdist_args):
function test_pyproject_toml (line 453) | def test_pyproject_toml(testdir, xdist_args):
function test_setup_cfg (line 473) | def test_setup_cfg(testdir, xdist_args):
function test_looponfail (line 494) | def test_looponfail(testdir, module_name):
function test_mypy_results_from_mypy_with_opts (line 576) | def test_mypy_results_from_mypy_with_opts():
function test_mypy_no_output (line 583) | def test_mypy_no_output(testdir, xdist_args):
function test_py_typed (line 616) | def test_py_typed(testdir):
function test_mypy_no_status_check (line 624) | def test_mypy_no_status_check(testdir, xdist_args):
function test_mypy_xfail_passes (line 637) | def test_mypy_xfail_passes(testdir, xdist_args):
function test_mypy_xfail_xfails (line 650) | def test_mypy_xfail_xfails(testdir, xdist_args):
function test_mypy_xfail_reports_stdout (line 663) | def test_mypy_xfail_reports_stdout(testdir, xdist_args):
function test_error_severity (line 693) | def test_error_severity():
function test_mypy_report_style (line 698) | def test_mypy_report_style(testdir, xdist_args):
Condensed preview — 13 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (52K chars).
[
{
"path": ".github/workflows/publication.yml",
"chars": 429,
"preview": "name: Publication\non:\n release:\n types: [created]\njobs:\n deploy:\n runs-on: ubuntu-latest\n steps:\n - uses: "
},
{
"path": ".github/workflows/validation.yml",
"chars": 409,
"preview": "name: Validation\non: [push, pull_request]\njobs:\n build:\n runs-on: ubuntu-20.04\n strategy:\n matrix:\n p"
},
{
"path": ".gitignore",
"chars": 1713,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": "CONTRIBUTING.rst",
"chars": 666,
"preview": "Contributing\n============\n\nContributions are very welcome. Tests can be run with `tox <https://tox.readthedocs.io/en/lat"
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Daniel Bader\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.rst",
"chars": 2055,
"preview": "pytest-mypy\n===================================\n\nMypy static type checker plugin for pytest\n\n.. image:: https://img.shie"
},
{
"path": "changelog.md",
"chars": 91,
"preview": "# Changelog\n\nThe Changelog has moved to https://github.com/realpython/pytest-mypy/releases\n"
},
{
"path": "pyproject.toml",
"chars": 1278,
"preview": "[build-system]\nrequires = [\"setuptools >= 61.0\", \"setuptools-scm >= 7.1\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[proj"
},
{
"path": "src/pytest_mypy/__init__.py",
"chars": 15278,
"preview": "\"\"\"Mypy static type checker plugin for Pytest\"\"\"\n\nfrom __future__ import annotations\n\nfrom dataclasses import dataclass\n"
},
{
"path": "src/pytest_mypy/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "tests/conftest.py",
"chars": 126,
"preview": "import mypy.version\n\npytest_plugins = \"pytester\"\n\n\ndef pytest_report_header():\n return f\"mypy: {mypy.version.__versio"
},
{
"path": "tests/test_pytest_mypy.py",
"chars": 23622,
"preview": "import signal\nimport sys\nimport textwrap\n\nimport mypy.version\nfrom packaging.version import Version\nimport pytest\n\nimpor"
},
{
"path": "tox.ini",
"chars": 2496,
"preview": "# For more information about tox, see https://tox.readthedocs.io/en/latest/\n[tox]\nminversion = 4.4\nisolated_build = true"
}
]
About this extraction
This page contains the full source code of the dbader/pytest-mypy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 13 files (48.1 KB), approximately 12.8k tokens, and a symbol index with 64 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.