diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42837d1..7ad4877 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.0 + rev: 0.36.1 hooks: - id: check-github-workflows args: ["--verbose"] @@ -13,24 +13,23 @@ repos: rev: v2.4.1 hooks: - id: codespell - additional_dependencies: ["tomli>=2.2.1"] - - repo: https://github.com/tox-dev/tox-ini-fmt - rev: "1.7.1" + additional_dependencies: ["tomli>=2.4"] + - repo: https://github.com/tox-dev/tox-toml-fmt + rev: "v1.5.4" hooks: - - id: tox-ini-fmt - args: ["-p", "fix"] + - id: tox-toml-fmt - repo: https://github.com/tox-dev/pyproject-fmt - rev: "v2.11.1" + rev: "v2.15.2" hooks: - id: pyproject-fmt - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.14.10" + rev: "v0.15.0" hooks: - id: ruff-format - id: ruff args: ["--fix", "--unsafe-fixes", "--exit-non-zero-on-fix"] - repo: https://github.com/rbubley/mirrors-prettier - rev: "v3.7.4" + rev: "v3.8.1" hooks: - id: prettier args: ["--print-width=120", "--prose-wrap=always"] diff --git a/pyproject.toml b/pyproject.toml index 439838d..bab869e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,15 @@ build-backend = "hatchling.build" requires = [ "hatch-vcs>=0.5", - "hatchling>=1.27", + "hatchling>=1.28", ] [project] name = "pytest-print" -description = "pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)" +description = """\ + pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not \ + stdout)\ + """ readme = "README.md" keywords = [ "env", @@ -36,11 +39,11 @@ dynamic = [ "version", ] dependencies = [ - "pytest>=8.4.2", + "pytest>=9.0.2", ] optional-dependencies.test = [ "covdefaults>=2.3", - "coverage>=7.10.7", + "coverage>=7.13.4", "pytest-mock>=3.15.1", ] urls.Homepage = "https://github.com/pytest-dev/pytest-print" @@ -67,6 +70,7 @@ lint.ignore = [ "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible "DOC", # no support "ISC001", # Conflict with formatter + "RUF067", # code in __init__ modules is intentional "S104", # Possible binding to all interface ] lint.per-file-ignores."tests/**/*.py" = [ @@ -93,20 +97,16 @@ count = true max_supported_python = "3.13" [tool.coverage] +run.branch = true +run.dynamic_context = "test_function" +run.parallel = true +run.plugins = [ + "covdefaults", +] run.source = [ "pytest_print", "tests", ] -run.dynamic_context = "test_function" -run.branch = true -run.parallel = true -report.omit = [ - "tests/example_*.py", -] -report.fail_under = 100 -report.show_missing = true -html.show_contexts = true -html.skip_covered = false paths.source = [ "src", ".tox*/*/lib/python*/site-packages", @@ -115,11 +115,13 @@ paths.source = [ "*/src", "*\\src", ] -run.plugins = [ - "covdefaults", +report.fail_under = 100 +report.omit = [ + "tests/example_*.py", ] +report.show_missing = true +html.show_contexts = true +html.skip_covered = false -[tool.mypy] -python_version = "3.13" -show_error_codes = true -strict = true +[tool.ty] +environment.python-version = "3.14" diff --git a/src/pytest_print/__init__.py b/src/pytest_print/__init__.py index 0e33e15..0a83a68 100644 --- a/src/pytest_print/__init__.py +++ b/src/pytest_print/__init__.py @@ -2,7 +2,6 @@ from __future__ import annotations -import sys from dataclasses import dataclass, replace from timeit import default_timer from typing import TYPE_CHECKING, Protocol, TypeVar, cast @@ -11,8 +10,6 @@ if TYPE_CHECKING: from _pytest.capture import CaptureManager - from _pytest.fixtures import SubRequest - from _pytest.terminal import TerminalReporter def pytest_addoption(parser: pytest.Parser) -> None: @@ -45,7 +42,7 @@ def __call__(self, msg: str) -> None: @pytest.fixture(scope="session") -def printer_session(request: SubRequest) -> Printer: +def printer_session(request: pytest.FixtureRequest) -> Printer: """Pytest plugin to print test progress steps in verbose mode (session scoped).""" return _create(request, _Printer, Formatter()) @@ -76,7 +73,7 @@ def indent(self, *, icon: str) -> PrettyPrinter: @pytest.fixture(scope="session") -def pretty_printer(request: SubRequest) -> PrettyPrinter: +def pretty_printer(request: pytest.FixtureRequest) -> PrettyPrinter: """Pytest plugin to print test progress steps in verbose mode.""" formatter = Formatter(head=" ", icon="⏩", space=" ", indentation=" ", timer_fmt="[{elapsed:.20f}]") return _create(request, _PrettyPrinter, formatter) @@ -94,9 +91,8 @@ def __call__(self, *, formatter: Formatter) -> PrettyPrinter: @pytest.fixture(scope="session") -def create_pretty_printer(request: SubRequest) -> PrettyPrinterFactory: +def create_pretty_printer(request: pytest.FixtureRequest) -> PrettyPrinterFactory: """Pytest plugin to print test progress steps in verbose mode.""" - Formatter(head=" ", icon="⏩", space=" ", indentation=" ", timer_fmt="[{elapsed:.20f}]") def meth(*, formatter: Formatter) -> PrettyPrinter: return _create(request, _PrettyPrinter, formatter) @@ -104,7 +100,7 @@ def meth(*, formatter: Formatter) -> PrettyPrinter: return meth -@dataclass(frozen=True, **{"slots": True, "kw_only": True} if sys.version_info >= (3, 10) else {}) +@dataclass(frozen=True, slots=True, kw_only=True) class Formatter: """Configures how to format messages to be printed.""" @@ -137,7 +133,7 @@ class _Printer: def __init__( self, *, - reporter: TerminalReporter | None, + reporter: pytest.TerminalReporter | None, capture_manager: CaptureManager | None, formatter: Formatter, level: int, @@ -177,9 +173,9 @@ def indent(self, *, icon: str) -> PrettyPrinter: _OfType = TypeVar("_OfType", bound=_Printer) -def _create(request: SubRequest, of_type: type[_OfType], formatter: Formatter) -> _OfType: +def _create(request: pytest.FixtureRequest, of_type: type[_OfType], formatter: Formatter) -> _OfType: return of_type( - reporter=cast("TerminalReporter | None", request.config.pluginmanager.getplugin("terminalreporter")) + reporter=cast("pytest.TerminalReporter | None", request.config.pluginmanager.getplugin("terminalreporter")) if request.config.getoption("pytest_print_on") or cast("int", request.config.getoption("verbose")) > 0 else None, capture_manager=cast("CaptureManager | None", request.config.pluginmanager.getplugin("capturemanager")), diff --git a/tests/__init__.py b/tests/__init__.py index 0bac3da..d7c37e8 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,12 +17,11 @@ def extract_printer_text(lines: list[str]) -> str: return re.sub(r"[ \t]+\n", "\n", output) -def seed_test(filename: str, testdir: pytest.Testdir) -> pytest.Testdir: +def seed_test(filename: str, pytester: pytest.Pytester) -> pytest.Pytester: src = Path(__file__).parent / filename assert src.exists() - dest = Path(str(testdir.tmpdir)) / "test_a.py" - copy2(src, dest) - return testdir + copy2(src, pytester.path / "test_a.py") + return pytester __all__ = [ diff --git a/tests/test_create_pretty_print.py b/tests/test_create_pretty_print.py index a789a93..3d6d206 100644 --- a/tests/test_create_pretty_print.py +++ b/tests/test_create_pretty_print.py @@ -8,8 +8,8 @@ @pytest.fixture -def example(testdir: pytest.Testdir) -> pytest.Testdir: - return seed_test("example_create_pretty_print.py", testdir) +def example(pytester: pytest.Pytester) -> pytest.Pytester: + return seed_test("example_create_pretty_print.py", pytester) def fix_floats_in_relative_time(txt: str) -> str: @@ -17,14 +17,14 @@ def fix_floats_in_relative_time(txt: str) -> str: return re.sub(float_pattern, "0.1", txt) -def test_progress_no_v(example: pytest.Testdir) -> None: +def test_progress_no_v(example: pytest.Pytester) -> None: result = example.runpytest() result.assert_outcomes(passed=3) assert " start server from virtual env" not in result.outlines assert "global peace" not in result.outlines -def test_progress_v_no_relative(example: pytest.Testdir) -> None: +def test_progress_v_no_relative(example: pytest.Pytester) -> None: result_verbose = example.runpytest("-v", "--print") result_verbose.assert_outcomes(passed=3) @@ -57,7 +57,7 @@ def test_progress_v_no_relative(example: pytest.Testdir) -> None: assert output == expected -def test_progress_v_relative(example: pytest.Testdir) -> None: +def test_progress_v_relative(example: pytest.Pytester) -> None: result_verbose_relative = example.runpytest( "--print", "-v", @@ -83,7 +83,7 @@ def test_progress_v_relative(example: pytest.Testdir) -> None: assert output == expected -def test_progress_no_v_but_with_print_request(example: pytest.Testdir) -> None: +def test_progress_no_v_but_with_print_request(example: pytest.Pytester) -> None: result = example.runpytest("--print") result.assert_outcomes(passed=3) assert " 🚀 start server from virtual env" in result.outlines diff --git a/tests/test_pretty_print.py b/tests/test_pretty_print.py index 95cd8b9..4c9959b 100644 --- a/tests/test_pretty_print.py +++ b/tests/test_pretty_print.py @@ -8,8 +8,8 @@ @pytest.fixture -def example(testdir: pytest.Testdir) -> pytest.Testdir: - return seed_test("example_pretty_print.py", testdir) +def example(pytester: pytest.Pytester) -> pytest.Pytester: + return seed_test("example_pretty_print.py", pytester) def fix_floats_in_relative_time(txt: str) -> str: @@ -17,7 +17,7 @@ def fix_floats_in_relative_time(txt: str) -> str: return re.sub(float_pattern, "0.1", txt) -def test_progress_v_no_relative(example: pytest.Testdir) -> None: +def test_progress_v_no_relative(example: pytest.Pytester) -> None: result = example.runpytest("-v", "--print") result.assert_outcomes(passed=1) @@ -37,7 +37,7 @@ def test_progress_v_no_relative(example: pytest.Testdir) -> None: assert output == expected -def test_progress_v_relative(example: pytest.Testdir) -> None: +def test_progress_v_relative(example: pytest.Pytester) -> None: result = example.runpytest( "--print", "-v", diff --git a/tests/test_print.py b/tests/test_print.py index 0e6e0c3..7ced998 100644 --- a/tests/test_print.py +++ b/tests/test_print.py @@ -6,18 +6,18 @@ @pytest.fixture -def example(testdir: pytest.Testdir) -> pytest.Testdir: - return seed_test("example_print.py", testdir) +def example(pytester: pytest.Pytester) -> pytest.Pytester: + return seed_test("example_print.py", pytester) -def test_progress_no_v(example: pytest.Testdir) -> None: +def test_progress_no_v(example: pytest.Pytester) -> None: result = example.runpytest() result.assert_outcomes(passed=2) assert " start server from virtual env" not in result.outlines assert "global peace" not in result.outlines -def test_progress_v_no_relative(example: pytest.Testdir, monkeypatch: pytest.MonkeyPatch) -> None: +def test_progress_v_no_relative(example: pytest.Pytester, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr("_pytest._io.terminalwriter.get_terminal_width", lambda: 80) monkeypatch.setenv("COLUMNS", str(80)) result_verbose = example.runpytest("-v", "--print") @@ -40,7 +40,7 @@ def test_progress_v_no_relative(example: pytest.Testdir, monkeypatch: pytest.Mon assert found == expected -def test_progress_v_relative(example: pytest.Testdir) -> None: +def test_progress_v_relative(example: pytest.Pytester) -> None: result_verbose_relative = example.runpytest( "--print", "-v", @@ -69,7 +69,7 @@ def test_progress_v_relative(example: pytest.Testdir) -> None: ], session -def test_progress_no_v_but_with_print_request(example: pytest.Testdir) -> None: +def test_progress_no_v_but_with_print_request(example: pytest.Pytester) -> None: result = example.runpytest("--print") result.assert_outcomes(passed=2) assert " start server from virtual env" in result.outlines diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 75792b2..0000000 --- a/tox.ini +++ /dev/null @@ -1,70 +0,0 @@ -[tox] -requires = - tox>=4.30.3 -env_list = - fix - 3.14 - 3.13 - 3.12 - 3.11 - 3.10 - type - pkg_meta -skip_missing_interpreters = true - -[testenv] -description = run the tests with pytest -package = wheel -wheel_build_env = .pkg -extras = - test -set_env = - COVERAGE_FILE = {env:COVERAGE_FILE:{work_dir}{/}.coverage.{env_name}} -commands = - coverage erase - coverage run -m pytest {tty:--color=yes} \ - --junitxml {work_dir}{/}junit.{env_name}.xml \ - {posargs:tests} - coverage combine - coverage report - coverage html -d {env_tmp_dir}{/}htmlcov - -[testenv:fix] -description = run static analysis and style check using flake8 -skip_install = true -deps = - pre-commit-uv>=4.1.5 -pass_env = - HOMEPATH - PROGRAMDATA -commands = - pre-commit run --all-files --show-diff-on-failure - -[testenv:type] -description = run type check on code base -deps = - mypy==1.18.2 -commands = - mypy --strict src - mypy --strict tests - -[testenv:pkg_meta] -description = check that the long description is valid -skip_install = true -deps = - check-wheel-contents>=0.6.3 - twine>=6.2 - uv>=0.8.22 -commands = - uv build --sdist --wheel --out-dir {env_tmp_dir} . - twine check {env_tmp_dir}{/}* - check-wheel-contents --no-config {env_tmp_dir} - -[testenv:dev] -description = generate a DEV environment -package = editable -extras = - test -commands = - uv pip tree - python -c 'import sys; print(sys.executable)' diff --git a/tox.toml b/tox.toml new file mode 100644 index 0000000..bb6951d --- /dev/null +++ b/tox.toml @@ -0,0 +1,57 @@ +requires = [ "tox>=4.34.1" ] +env_list = [ "fix", "3.14", "3.13", "3.12", "3.11", "3.10", "type", "pkg_meta" ] +skip_missing_interpreters = true + +[env_run_base] +description = "run the tests with pytest" +package = "wheel" +wheel_build_env = ".pkg" +extras = [ "test" ] +set_env.COVERAGE_FILE = "{env:COVERAGE_FILE:{work_dir}{/}.coverage.{env_name}}" +commands = [ + [ "coverage", "erase" ], + [ + "coverage", + "run", + "-m", + "pytest", + "{tty:--color=yes}", + "--junitxml", + "{work_dir}{/}junit.{env_name}.xml", + "{posargs:tests}" + ], + [ "coverage", "combine" ], + [ "coverage", "report" ], + [ "coverage", "html", "-d", "{env_tmp_dir}{/}htmlcov" ], +] + +[env.fix] +description = "run static analysis and style check using flake8" +skip_install = true +deps = [ "pre-commit-uv>=4.2" ] +pass_env = [ "HOMEPATH", "PROGRAMDATA" ] +commands = [ [ "pre-commit", "run", "--all-files", "--show-diff-on-failure" ] ] + +[env.type] +description = "run type check on code base" +deps = [ "ty==0.0.16" ] +commands = [ [ "ty", "check", "--output-format", "concise", "--error-on-warning", "." ] ] + +[env.pkg_meta] +description = "check that the long description is valid" +skip_install = true +deps = [ "check-wheel-contents>=0.6.3", "twine>=6.2", "uv>=0.10.2" ] +commands = [ + [ "uv", "build", "--sdist", "--wheel", "--out-dir", "{env_tmp_dir}", "." ], + [ "twine", "check", "{env_tmp_dir}{/}*" ], + [ "check-wheel-contents", "--no-config", "{env_tmp_dir}" ], +] + +[env.dev] +description = "generate a DEV environment" +package = "editable" +extras = [ "test" ] +commands = [ + [ "uv", "pip", "tree" ], + [ "python", "-c", "import sys; print(sys.executable)" ], +]