From 9295f9ffffc11595cc4d7241008f60783f1f46c2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 17:01:28 +0200 Subject: [PATCH 1/6] RFC: from __future__ import annotations + migrate --- bench/bench.py | 2 + bench/bench_argcomplete.py | 2 + bench/empty.py | 3 + bench/manyparam.py | 2 + bench/skip.py | 2 + bench/unit_test.py | 2 + bench/xunit.py | 3 + doc/en/conf.py | 4 +- doc/en/conftest.py | 3 + doc/en/example/assertion/failure_demo.py | 2 + .../global_testmodule_config/conftest.py | 2 + .../test_hello_world.py | 3 + doc/en/example/assertion/test_failures.py | 2 + .../assertion/test_setup_flow_example.py | 3 + doc/en/example/conftest.py | 3 + doc/en/example/customdirectory/conftest.py | 2 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + .../fixtures/test_fixtures_order_autouse.py | 2 + ..._fixtures_order_autouse_multiple_scopes.py | 2 + ...est_fixtures_order_autouse_temp_effects.py | 2 + .../test_fixtures_order_dependencies.py | 2 + .../fixtures/test_fixtures_order_scope.py | 2 + .../test_fixtures_request_different_scope.py | 2 + doc/en/example/multipython.py | 2 + doc/en/example/nonpython/conftest.py | 2 + doc/en/example/pythoncollection.py | 1 + doc/en/example/xfail_demo.py | 2 + extra/get_issues.py | 2 + pyproject.toml | 9 + scripts/generate-gh-release-notes.py | 2 + scripts/prepare-release-pr.py | 2 + scripts/release.py | 2 + scripts/update-plugin-list.py | 2 + src/_pytest/__init__.py | 3 + src/_pytest/_argcomplete.py | 8 +- src/_pytest/_code/__init__.py | 2 + src/_pytest/_code/code.py | 225 ++++++++---------- src/_pytest/_code/source.py | 36 ++- src/_pytest/_io/__init__.py | 2 + src/_pytest/_io/pprint.py | 69 +++--- src/_pytest/_io/saferepr.py | 7 +- src/_pytest/_io/terminalwriter.py | 17 +- src/_pytest/_io/wcwidth.py | 2 + src/_pytest/assertion/__init__.py | 14 +- src/_pytest/assertion/rewrite.py | 114 +++++---- src/_pytest/assertion/truncate.py | 17 +- src/_pytest/assertion/util.py | 48 ++-- src/_pytest/cacheprovider.py | 41 ++-- src/_pytest/capture.py | 59 ++--- src/_pytest/config/__init__.py | 150 ++++++------ src/_pytest/config/argparsing.py | 87 ++++--- src/_pytest/config/exceptions.py | 2 + src/_pytest/config/findpaths.py | 27 +-- src/_pytest/debugging.py | 29 +-- src/_pytest/deprecated.py | 2 + src/_pytest/doctest.py | 82 +++---- src/_pytest/faulthandler.py | 2 + src/_pytest/fixtures.py | 222 ++++++++--------- src/_pytest/freeze_support.py | 8 +- src/_pytest/helpconfig.py | 13 +- src/_pytest/hookspec.py | 173 +++++++------- src/_pytest/junitxml.py | 49 ++-- src/_pytest/legacypath.py | 29 +-- src/_pytest/logging.py | 60 +++-- src/_pytest/main.py | 115 +++++---- src/_pytest/mark/__init__.py | 24 +- src/_pytest/mark/expression.py | 7 +- src/_pytest/mark/structures.py | 108 ++++----- src/_pytest/monkeypatch.py | 32 ++- src/_pytest/nodes.py | 120 +++++----- src/_pytest/outcomes.py | 21 +- src/_pytest/pastebin.py | 5 +- src/_pytest/pathlib.py | 69 +++--- src/_pytest/pytester.py | 184 +++++++------- src/_pytest/pytester_assertions.py | 18 +- src/_pytest/python.py | 147 ++++++------ src/_pytest/python_api.py | 57 +++-- src/_pytest/python_path.py | 2 + src/_pytest/recwarn.py | 69 +++--- src/_pytest/reports.py | 118 +++++---- src/_pytest/runner.py | 67 +++--- src/_pytest/scope.py | 13 +- src/_pytest/setuponly.py | 6 +- src/_pytest/setupplan.py | 7 +- src/_pytest/skipping.py | 14 +- src/_pytest/stash.py | 8 +- src/_pytest/stepwise.py | 11 +- src/_pytest/terminal.py | 132 +++++----- src/_pytest/threadexception.py | 23 +- src/_pytest/timing.py | 2 + src/_pytest/tmpdir.py | 20 +- src/_pytest/unittest.py | 34 +-- src/_pytest/unraisableexception.py | 23 +- src/_pytest/warning_types.py | 7 +- src/_pytest/warnings.py | 7 +- src/py.py | 2 + src/pytest/__init__.py | 2 + src/pytest/__main__.py | 2 + testing/_py/test_local.py | 2 + testing/acceptance_test.py | 2 + testing/code/test_code.py | 2 + testing/conftest.py | 11 +- testing/deprecated_test.py | 2 + .../acceptance/fixture_mock_integration.py | 2 + .../collect_init_tests/tests/__init__.py | 3 + .../collect_init_tests/tests/test_foo.py | 3 + .../package_infinite_recursion/conftest.py | 3 + .../tests/test_basic.py | 3 + .../package_init_given_as_arg/pkg/__init__.py | 3 + .../package_init_given_as_arg/pkg/test_foo.py | 3 + .../config/collect_pytest_prefix/conftest.py | 3 + .../config/collect_pytest_prefix/test_foo.py | 3 + .../conftest_usageerror/conftest.py | 3 + .../customdirectory/conftest.py | 2 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + .../dataclasses/test_compare_dataclasses.py | 2 + ...ompare_dataclasses_field_comparison_off.py | 2 + .../test_compare_dataclasses_verbose.py | 2 + ...test_compare_dataclasses_with_custom_eq.py | 2 + .../dataclasses/test_compare_initvar.py | 2 + .../test_compare_recursive_dataclasses.py | 2 + .../test_compare_two_different_dataclasses.py | 2 + .../doctest/main_py/__main__.py | 3 + .../doctest/main_py/test_normal_module.py | 3 + .../fixtures/custom_item/conftest.py | 2 + .../fixtures/custom_item/foo/test_foo.py | 3 + .../sub1/conftest.py | 2 + .../sub1/test_in_sub1.py | 3 + .../sub2/conftest.py | 2 + .../sub2/test_in_sub2.py | 3 + .../test_detect_recursive_dependency_error.py | 2 + .../conftest.py | 2 + .../pkg/conftest.py | 2 + .../pkg/test_spam.py | 3 + .../conftest.py | 2 + .../test_extend_fixture_conftest_module.py | 2 + .../test_extend_fixture_module_class.py | 2 + .../fill_fixtures/test_funcarg_basic.py | 2 + .../test_funcarg_lookup_classlevel.py | 2 + .../test_funcarg_lookup_modulelevel.py | 2 + .../fill_fixtures/test_funcarg_lookupfails.py | 2 + .../fixtures/test_fixture_named_request.py | 2 + .../fixtures/test_getfixturevalue_dynamic.py | 2 + .../conftest.py | 2 + .../test_hello.py | 3 + testing/example_scripts/issue_519.py | 6 +- .../test_marks_as_keywords.py | 2 + .../collect_stats/generate_folders.py | 2 + .../collect_stats/template_test.py | 3 + .../tmpdir/tmp_path_fixture.py | 2 + ...test_parametrized_fixture_error_message.py | 2 + .../unittest/test_setup_skip.py | 2 + .../unittest/test_setup_skip_class.py | 2 + .../unittest/test_setup_skip_module.py | 2 + .../unittest/test_unittest_asyncio.py | 5 +- .../unittest/test_unittest_asynctest.py | 5 +- .../unittest/test_unittest_plain_async.py | 2 + .../test_group_warnings_by_message.py | 2 + .../test_1.py | 2 + .../test_2.py | 2 + testing/examples/test_issue519.py | 2 + testing/freeze/create_executable.py | 3 + testing/freeze/runtests_script.py | 3 + testing/freeze/tests/test_trivial.py | 3 + testing/freeze/tox_run.py | 3 + testing/io/test_pprint.py | 2 + testing/io/test_saferepr.py | 2 + testing/io/test_terminalwriter.py | 5 +- testing/io/test_wcwidth.py | 2 + testing/logging/test_fixture.py | 2 + testing/logging/test_formatter.py | 2 + testing/logging/test_reporting.py | 2 + testing/plugins_integration/bdd_wallet.py | 2 + .../plugins_integration/django_settings.py | 3 + .../pytest_anyio_integration.py | 2 + .../pytest_asyncio_integration.py | 2 + .../pytest_mock_integration.py | 3 + .../pytest_rerunfailures_integration.py | 2 + .../pytest_trio_integration.py | 2 + .../pytest_twisted_integration.py | 2 + .../plugins_integration/simple_integration.py | 2 + testing/python/approx.py | 7 +- testing/python/collect.py | 5 +- testing/python/fixtures.py | 4 +- testing/python/integration.py | 2 + testing/python/metafunc.py | 22 +- testing/python/raises.py | 2 + testing/python/show_fixtures_per_test.py | 2 + testing/test_argcomplete.py | 2 + testing/test_assertion.py | 14 +- testing/test_assertrewrite.py | 18 +- testing/test_cacheprovider.py | 8 +- testing/test_capture.py | 2 + testing/test_collection.py | 8 +- testing/test_compat.py | 5 +- testing/test_config.py | 17 +- testing/test_conftest.py | 11 +- testing/test_debugging.py | 9 +- testing/test_doctest.py | 5 +- testing/test_entry_points.py | 2 + testing/test_error_diffs.py | 2 + testing/test_faulthandler.py | 2 + testing/test_findpaths.py | 2 + testing/test_helpconfig.py | 2 + testing/test_junitxml.py | 18 +- testing/test_legacypath.py | 2 + testing/test_link_resolve.py | 2 + testing/test_main.py | 5 +- testing/test_mark.py | 16 +- testing/test_mark_expression.py | 2 + testing/test_meta.py | 5 +- testing/test_monkeypatch.py | 10 +- testing/test_nodes.py | 5 +- testing/test_parseopt.py | 2 + testing/test_pastebin.py | 8 +- testing/test_pathlib.py | 12 +- testing/test_pluginmanager.py | 5 +- testing/test_pytester.py | 7 +- testing/test_python_path.py | 8 +- testing/test_recwarn.py | 12 +- testing/test_reports.py | 7 +- testing/test_runner.py | 14 +- testing/test_runner_xunit.py | 4 +- testing/test_scope.py | 2 + testing/test_session.py | 2 + testing/test_setuponly.py | 2 + testing/test_setupplan.py | 2 + testing/test_skipping.py | 2 + testing/test_stash.py | 2 + testing/test_stepwise.py | 2 + testing/test_terminal.py | 9 +- testing/test_threadexception.py | 2 + testing/test_tmpdir.py | 8 +- testing/test_unittest.py | 9 +- testing/test_unraisableexception.py | 2 + testing/test_warning_types.py | 2 + testing/test_warnings.py | 13 +- testing/typing_checks.py | 2 + 242 files changed, 1961 insertions(+), 1777 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 0bb13c75a15..139c292ecd8 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys diff --git a/bench/bench_argcomplete.py b/bench/bench_argcomplete.py index 459a12f9314..468c59217df 100644 --- a/bench/bench_argcomplete.py +++ b/bench/bench_argcomplete.py @@ -2,6 +2,8 @@ # 2.7.5 3.3.2 # FilesCompleter 75.1109 69.2116 # FastFilesCompleter 0.7383 1.0760 +from __future__ import annotations + import timeit diff --git a/bench/empty.py b/bench/empty.py index 4e7371b6f80..35abeef4140 100644 --- a/bench/empty.py +++ b/bench/empty.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + for i in range(1000): exec("def test_func_%d(): pass" % i) diff --git a/bench/manyparam.py b/bench/manyparam.py index 1226c73bd9c..579f7b2488d 100644 --- a/bench/manyparam.py +++ b/bench/manyparam.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/bench/skip.py b/bench/skip.py index fd5c292d92c..9145cc0ceed 100644 --- a/bench/skip.py +++ b/bench/skip.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/bench/unit_test.py b/bench/unit_test.py index d3db111e1ae..0f106e16b6c 100644 --- a/bench/unit_test.py +++ b/bench/unit_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase # noqa: F401 diff --git a/bench/xunit.py b/bench/xunit.py index 3a77dcdce42..31ab432441c 100644 --- a/bench/xunit.py +++ b/bench/xunit.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + for i in range(5000): exec( f""" diff --git a/doc/en/conf.py b/doc/en/conf.py index 3e87003c510..670d6adf74b 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,6 +15,8 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. +from __future__ import annotations + import os from pathlib import Path import shutil @@ -455,7 +457,7 @@ } -def setup(app: "sphinx.application.Sphinx") -> None: +def setup(app: sphinx.application.Sphinx) -> None: app.add_crossref_type( "fixture", "fixture", diff --git a/doc/en/conftest.py b/doc/en/conftest.py index 1a62e1b5df5..50e43a0b544 100644 --- a/doc/en/conftest.py +++ b/doc/en/conftest.py @@ -1 +1,4 @@ +from __future__ import annotations + + collect_ignore = ["conf.py"] diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index f7a9c279426..dd1485b0b21 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from pytest import raises diff --git a/doc/en/example/assertion/global_testmodule_config/conftest.py b/doc/en/example/assertion/global_testmodule_config/conftest.py index 4aa7ec23bd1..835726473ba 100644 --- a/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import pytest diff --git a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py index a31a601a1ce..e3c927316f9 100644 --- a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py +++ b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + hello = "world" diff --git a/doc/en/example/assertion/test_failures.py b/doc/en/example/assertion/test_failures.py index 19d862f60b7..17373f62213 100644 --- a/doc/en/example/assertion/test_failures.py +++ b/doc/en/example/assertion/test_failures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import shutil diff --git a/doc/en/example/assertion/test_setup_flow_example.py b/doc/en/example/assertion/test_setup_flow_example.py index 0e7eded06b6..fe11c2bf3f2 100644 --- a/doc/en/example/assertion/test_setup_flow_example.py +++ b/doc/en/example/assertion/test_setup_flow_example.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def setup_module(module): module.TestStateFullThing.classcount = 0 diff --git a/doc/en/example/conftest.py b/doc/en/example/conftest.py index 66e70f14dd7..21c9a489961 100644 --- a/doc/en/example/conftest.py +++ b/doc/en/example/conftest.py @@ -1 +1,4 @@ +from __future__ import annotations + + collect_ignore = ["nonpython", "customdirectory"] diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py index b2f68dba41a..ea922e04723 100644 --- a/doc/en/example/customdirectory/conftest.py +++ b/doc/en/example/customdirectory/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/doc/en/example/customdirectory/tests/test_first.py b/doc/en/example/customdirectory/tests/test_first.py index 0a78de59945..9953dd37785 100644 --- a/doc/en/example/customdirectory/tests/test_first.py +++ b/doc/en/example/customdirectory/tests/test_first.py @@ -1,3 +1,6 @@ # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/doc/en/example/customdirectory/tests/test_second.py b/doc/en/example/customdirectory/tests/test_second.py index eed724a7d96..df264f48b3b 100644 --- a/doc/en/example/customdirectory/tests/test_second.py +++ b/doc/en/example/customdirectory/tests/test_second.py @@ -1,3 +1,6 @@ # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/doc/en/example/customdirectory/tests/test_third.py b/doc/en/example/customdirectory/tests/test_third.py index 61cf59dc16c..b8b072dd770 100644 --- a/doc/en/example/customdirectory/tests/test_third.py +++ b/doc/en/example/customdirectory/tests/test_third.py @@ -1,3 +1,6 @@ # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py index ec282ab4b2b..04cbc268b7f 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py index de0c2642793..828fa4cf6d6 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py index ba01ad32f57..ebd5d10f5bb 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index e76e3f93c62..1c59f010341 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py index 5d9487cab34..4b4260fbdcd 100644 --- a/doc/en/example/fixtures/test_fixtures_order_scope.py +++ b/doc/en/example/fixtures/test_fixtures_order_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py index 00e2e46d845..dee61f8c4d7 100644 --- a/doc/en/example/fixtures/test_fixtures_request_different_scope.py +++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 861ae9e528d..f54524213bc 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -1,6 +1,8 @@ """Module containing a parametrized tests testing cross-python serialization via the pickle module.""" +from __future__ import annotations + import shutil import subprocess import textwrap diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index e969e3e2518..b7bdc77a004 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import pytest diff --git a/doc/en/example/pythoncollection.py b/doc/en/example/pythoncollection.py index 8742526a191..7595ee02ca4 100644 --- a/doc/en/example/pythoncollection.py +++ b/doc/en/example/pythoncollection.py @@ -1,5 +1,6 @@ # run this with $ pytest --collect-only test_collectonly.py # +from __future__ import annotations def test_function(): diff --git a/doc/en/example/xfail_demo.py b/doc/en/example/xfail_demo.py index 1040c89298d..4999e15f238 100644 --- a/doc/en/example/xfail_demo.py +++ b/doc/en/example/xfail_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/extra/get_issues.py b/extra/get_issues.py index 64e859e0c12..851d2f6d7f3 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from pathlib import Path import sys diff --git a/pyproject.toml b/pyproject.toml index 0627c94c732..734c508ca70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ lint.select = [ "D", # pydocstyle "E", # pycodestyle "F", # pyflakes + "FA100", # add future annotations "I", # isort "PGH004", # pygrep-hooks - Use specific rule codes when using noqa "PIE", # flake8-pie @@ -155,6 +156,10 @@ lint.per-file-ignores."src/_pytest/_version.py" = [ lint.per-file-ignores."testing/python/approx.py" = [ "B015", ] +lint.extend-safe-fixes = [ + "UP006", + "UP007", +] lint.isort.combine-as-imports = true lint.isort.force-single-line = true lint.isort.force-sort-within-sections = true @@ -164,9 +169,13 @@ lint.isort.known-local-folder = [ ] lint.isort.lines-after-imports = 2 lint.isort.order-by-type = false +lint.isort.required-imports = [ + "from __future__ import annotations", +] # In order to be able to format for 88 char in ruff format lint.pycodestyle.max-line-length = 120 lint.pydocstyle.convention = "pep257" +lint.pyupgrade.keep-runtime-typing = false [tool.pylint.main] # Maximum number of characters on a single line. diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py index 4222702d5d4..7f195ba1e0a 100644 --- a/scripts/generate-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -9,6 +9,8 @@ Requires Python3.6+. """ +from __future__ import annotations + from pathlib import Path import re import sys diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index 7dabbd3b328..49cb2110639 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -14,6 +14,8 @@ `pytest bot ` commit author. """ +from __future__ import annotations + import argparse from pathlib import Path import re diff --git a/scripts/release.py b/scripts/release.py index bcbc4262d08..545919cd60b 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,6 +1,8 @@ # mypy: disallow-untyped-defs """Invoke development tasks.""" +from __future__ import annotations + import argparse import os from pathlib import Path diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index 6831fc984dd..75df0ddba40 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -1,4 +1,6 @@ # mypy: disallow-untyped-defs +from __future__ import annotations + import datetime import pathlib import re diff --git a/src/_pytest/__init__.py b/src/_pytest/__init__.py index b694a5f244a..8eb8ec9605c 100644 --- a/src/_pytest/__init__.py +++ b/src/_pytest/__init__.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __all__ = ["__version__", "version_tuple"] try: diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index c24f925202a..59426ef949e 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -62,13 +62,13 @@ global argcomplete script). """ +from __future__ import annotations + import argparse from glob import glob import os import sys from typing import Any -from typing import List -from typing import Optional class FastFilesCompleter: @@ -77,7 +77,7 @@ class FastFilesCompleter: def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix: str, **kwargs: Any) -> List[str]: + def __call__(self, prefix: str, **kwargs: Any) -> list[str]: # Only called on non option completions. if os.sep in prefix[1:]: prefix_dir = len(os.path.dirname(prefix) + os.sep) @@ -104,7 +104,7 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]: import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter() + filescompleter: FastFilesCompleter | None = FastFilesCompleter() def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/src/_pytest/_code/__init__.py b/src/_pytest/_code/__init__.py index b0a418e9555..0bfde42604d 100644 --- a/src/_pytest/_code/__init__.py +++ b/src/_pytest/_code/__init__.py @@ -1,5 +1,7 @@ """Python inspection/code generation API.""" +from __future__ import annotations + from .code import Code from .code import ExceptionInfo from .code import filter_traceback diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b6e06340dbe..750195b94e9 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast import dataclasses import inspect @@ -17,7 +19,6 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import Final from typing import final from typing import Generic @@ -25,16 +26,11 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence -from typing import Set from typing import SupportsIndex -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union import pluggy @@ -57,6 +53,10 @@ TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +EXCEPTION_OR_MORE = type[Exception] | tuple[type[Exception], ...] + +type_alias = type # to sidestep shadowing + class Code: """Wrapper around Python code objects.""" @@ -67,7 +67,7 @@ def __init__(self, obj: CodeType) -> None: self.raw = obj @classmethod - def from_function(cls, obj: object) -> "Code": + def from_function(cls, obj: object) -> Code: return cls(getrawcode(obj)) def __eq__(self, other): @@ -85,7 +85,7 @@ def name(self) -> str: return self.raw.co_name @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Return a path object pointing to source code, or an ``str`` in case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: @@ -102,17 +102,17 @@ def path(self) -> Union[Path, str]: return self.raw.co_filename @property - def fullsource(self) -> Optional["Source"]: + def fullsource(self) -> Source | None: """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) return full - def source(self) -> "Source": + def source(self) -> Source: """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code return Source(self.raw) - def getargs(self, var: bool = False) -> Tuple[str, ...]: + def getargs(self, var: bool = False) -> tuple[str, ...]: """Return a tuple with the argument names for the code object. If 'var' is set True also return the names of the variable and @@ -141,11 +141,11 @@ def lineno(self) -> int: return self.raw.f_lineno - 1 @property - def f_globals(self) -> Dict[str, Any]: + def f_globals(self) -> dict[str, Any]: return self.raw.f_globals @property - def f_locals(self) -> Dict[str, Any]: + def f_locals(self) -> dict[str, Any]: return self.raw.f_locals @property @@ -153,7 +153,7 @@ def code(self) -> Code: return Code(self.raw.f_code) @property - def statement(self) -> "Source": + def statement(self) -> Source: """Statement this frame is at.""" if self.code.fullsource is None: return Source("") @@ -197,14 +197,14 @@ class TracebackEntry: def __init__( self, rawentry: TracebackType, - repr_style: Optional['Literal["short", "long"]'] = None, + repr_style: Literal["short", "long"] | None = None, ) -> None: self._rawentry: Final = rawentry self._repr_style: Final = repr_style def with_repr_style( - self, repr_style: Optional['Literal["short", "long"]'] - ) -> "TracebackEntry": + self, repr_style: Literal["short", "long"] | None + ) -> TracebackEntry: return TracebackEntry(self._rawentry, repr_style) @property @@ -223,19 +223,19 @@ def __repr__(self) -> str: return "" % (self.frame.code.path, self.lineno + 1) @property - def statement(self) -> "Source": + def statement(self) -> Source: """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource assert source is not None return source.getstatement(self.lineno) @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Path to the source code.""" return self.frame.code.path @property - def locals(self) -> Dict[str, Any]: + def locals(self) -> dict[str, Any]: """Locals of underlying frame.""" return self.frame.f_locals @@ -243,8 +243,8 @@ def getfirstlinesource(self) -> int: return self.frame.code.firstlineno def getsource( - self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None - ) -> Optional["Source"]: + self, astcache: dict[str | Path, ast.AST] | None = None + ) -> Source | None: """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing @@ -270,7 +270,7 @@ def getsource( source = property(getsource) - def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -279,9 +279,7 @@ def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: Mostly for internal use. """ - tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( - False - ) + tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): # in normal cases, f_locals and f_globals are dictionaries # however via `exec(...)` / `eval(...)` they can be other types @@ -326,13 +324,13 @@ class Traceback(List[TracebackEntry]): def __init__( self, - tb: Union[TracebackType, Iterable[TracebackEntry]], + tb: TracebackType | Iterable[TracebackEntry], ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: Optional[TracebackType] = cur + cur_: TracebackType | None = cur while cur_ is not None: yield TracebackEntry(cur_) cur_ = cur_.tb_next @@ -343,11 +341,11 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]: def cut( self, - path: Optional[Union["os.PathLike[str]", str]] = None, - lineno: Optional[int] = None, - firstlineno: Optional[int] = None, - excludepath: Optional["os.PathLike[str]"] = None, - ) -> "Traceback": + path: os.PathLike[str] | str | None = None, + lineno: int | None = None, + firstlineno: int | None = None, + excludepath: os.PathLike[str] | None = None, + ) -> Traceback: """Return a Traceback instance wrapping part of this Traceback. By providing any combination of path, lineno and firstlineno, the @@ -378,14 +376,12 @@ def cut( return self @overload - def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ... + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... @overload - def __getitem__(self, key: slice) -> "Traceback": ... + def __getitem__(self, key: slice) -> Traceback: ... - def __getitem__( - self, key: Union["SupportsIndex", slice] - ) -> Union[TracebackEntry, "Traceback"]: + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: if isinstance(key, slice): return self.__class__(super().__getitem__(key)) else: @@ -393,12 +389,9 @@ def __getitem__( def filter( self, - excinfo_or_fn: Union[ - "ExceptionInfo[BaseException]", - Callable[[TracebackEntry], bool], - ], + excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], /, - ) -> "Traceback": + ) -> Traceback: """Return a Traceback instance with certain items removed. If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s @@ -414,10 +407,10 @@ def filter( fn = excinfo_or_fn return Traceback(filter(fn, self)) - def recursionindex(self) -> Optional[int]: + def recursionindex(self) -> int | None: """Return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred.""" - cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} + cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi @@ -445,15 +438,15 @@ class ExceptionInfo(Generic[E]): _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] + _excinfo: tuple[type[E], E, TracebackType] | None _striptext: str - _traceback: Optional[Traceback] + _traceback: Traceback | None def __init__( self, - excinfo: Optional[Tuple[Type["E"], "E", TracebackType]], + excinfo: tuple[type[E], E, TracebackType] | None, striptext: str = "", - traceback: Optional[Traceback] = None, + traceback: Traceback | None = None, *, _ispytest: bool = False, ) -> None: @@ -469,8 +462,8 @@ def from_exception( # This is OK to ignore because this class is (conceptually) readonly. # See https://github.com/python/mypy/issues/7049. exception: E, # type: ignore[misc] - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Return an ExceptionInfo for an existing exception. The exception must have a non-``None`` ``__traceback__`` attribute, @@ -495,9 +488,9 @@ def from_exception( @classmethod def from_exc_info( cls, - exc_info: Tuple[Type[E], E, TracebackType], - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exc_info: tuple[type[E], E, TracebackType], + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Like :func:`from_exception`, but using old-style exc_info tuple.""" _striptext = "" if exprinfo is None and isinstance(exc_info[1], AssertionError): @@ -510,9 +503,7 @@ def from_exc_info( return cls(exc_info, _striptext, _ispytest=True) @classmethod - def from_current( - cls, exprinfo: Optional[str] = None - ) -> "ExceptionInfo[BaseException]": + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: """Return an ExceptionInfo matching the current traceback. .. warning:: @@ -532,17 +523,17 @@ def from_current( return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod - def for_later(cls) -> "ExceptionInfo[E]": + def for_later(cls) -> ExceptionInfo[E]: """Return an unfilled ExceptionInfo.""" return cls(None, _ispytest=True) - def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None: + def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: """Fill an unfilled ExceptionInfo created with ``for_later()``.""" assert self._excinfo is None, "ExceptionInfo was already filled" self._excinfo = exc_info @property - def type(self) -> Type[E]: + def type(self) -> type[E]: """The exception class.""" assert ( self._excinfo is not None @@ -605,16 +596,14 @@ def exconly(self, tryshort: bool = False) -> str: text = text[len(self._striptext) :] return text - def errisinstance( - self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ) -> bool: + def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: """Return True if the exception is an instance of exc. Consider using ``isinstance(excinfo.value, exc)`` instead. """ return isinstance(self.value, exc) - def _getreprcrash(self) -> Optional["ReprFileLocation"]: + def _getreprcrash(self) -> ReprFileLocation | None: # Find last non-hidden traceback entry that led to the exception of the # traceback, or None if all hidden. for i in range(-1, -len(self.traceback) - 1, -1): @@ -630,14 +619,12 @@ def getrepr( showlocals: bool = False, style: TracebackStyle = "long", abspath: bool = False, - tbfilter: Union[ - bool, Callable[["ExceptionInfo[BaseException]"], Traceback] - ] = True, + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, truncate_args: bool = True, chain: bool = True, - ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + ) -> ReprExceptionInfo | ExceptionChainRepr: """Return str()able representation of this exception info. :param bool showlocals: @@ -719,7 +706,7 @@ def _stringify_exception(self, exc: BaseException) -> str: ] ) - def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": + def match(self, regexp: str | Pattern[str]) -> Literal[True]: """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. @@ -737,9 +724,9 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": def _group_contains( self, exc_group: BaseExceptionGroup[BaseException], - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], - match: Union[str, Pattern[str], None], - target_depth: Optional[int] = None, + expected_exception: EXCEPTION_OR_MORE, + match: str | Pattern[str] | None, + target_depth: int | None = None, current_depth: int = 1, ) -> bool: """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" @@ -766,10 +753,10 @@ def _group_contains( def group_contains( self, - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + expected_exception: EXCEPTION_OR_MORE, *, - match: Union[str, Pattern[str], None] = None, - depth: Optional[int] = None, + match: str | Pattern[str] | None = None, + depth: int | None = None, ) -> bool: """Check whether a captured exception group contains a matching exception. @@ -811,16 +798,16 @@ class FormattedExcinfo: showlocals: bool = False style: TracebackStyle = "long" abspath: bool = True - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True funcargs: bool = False truncate_locals: bool = True truncate_args: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + astcache: dict[str | Path, ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False ) - def _getindent(self, source: "Source") -> int: + def _getindent(self, source: Source) -> int: # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) @@ -835,13 +822,13 @@ def _getindent(self, source: "Source") -> int: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: + def _getentrysource(self, entry: TracebackEntry) -> Source | None: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): @@ -855,11 +842,11 @@ def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: def get_source( self, - source: Optional["Source"], + source: Source | None, line_index: int = -1, - excinfo: Optional[ExceptionInfo[BaseException]] = None, + excinfo: ExceptionInfo[BaseException] | None = None, short: bool = False, - ) -> List[str]: + ) -> list[str]: """Return formatted and marked up source lines.""" lines = [] if source is not None and line_index < 0: @@ -888,7 +875,7 @@ def get_exconly( excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, - ) -> List[str]: + ) -> list[str]: lines = [] indentstr = " " * indent # Get the real exception information out. @@ -900,7 +887,7 @@ def get_exconly( failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -928,10 +915,10 @@ def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: def repr_traceback_entry( self, - entry: Optional[TracebackEntry], - excinfo: Optional[ExceptionInfo[BaseException]] = None, - ) -> "ReprEntry": - lines: List[str] = [] + entry: TracebackEntry | None, + excinfo: ExceptionInfo[BaseException] | None = None, + ) -> ReprEntry: + lines: list[str] = [] style = ( entry._repr_style if entry is not None and entry._repr_style is not None @@ -966,7 +953,7 @@ def repr_traceback_entry( lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path: Union[Path, str]) -> str: + def _makepath(self, path: Path | str) -> str: if not self.abspath and isinstance(path, Path): try: np = bestrelpath(Path.cwd(), path) @@ -976,7 +963,7 @@ def _makepath(self, path: Union[Path, str]) -> str: return np return str(path) - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: traceback = excinfo.traceback if callable(self.tbfilter): traceback = self.tbfilter(excinfo) @@ -1007,7 +994,7 @@ def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTracebac def _truncate_recursive_traceback( self, traceback: Traceback - ) -> Tuple[Traceback, Optional[str]]: + ) -> tuple[Traceback, str | None]: """Truncate the given recursive traceback trying to find the starting point of the recursion. @@ -1024,7 +1011,7 @@ def _truncate_recursive_traceback( recursionindex = traceback.recursionindex() except Exception as e: max_frames = 10 - extraline: Optional[str] = ( + extraline: str | None = ( "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" " The following exception happened when comparing locals in the stack frame:\n" f" {type(e).__name__}: {e!s}\n" @@ -1042,16 +1029,12 @@ def _truncate_recursive_traceback( return traceback, extraline - def repr_excinfo( - self, excinfo: ExceptionInfo[BaseException] - ) -> "ExceptionChainRepr": - repr_chain: List[ - Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] - ] = [] - e: Optional[BaseException] = excinfo.value - excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo + def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: + repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] + e: BaseException | None = excinfo.value + excinfo_: ExceptionInfo[BaseException] | None = excinfo descr = None - seen: Set[int] = set() + seen: set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) @@ -1060,7 +1043,7 @@ def repr_excinfo( # full support for exception groups added to ExceptionInfo. # See https://github.com/pytest-dev/pytest/issues/9159 if isinstance(e, BaseExceptionGroup): - reprtraceback: Union[ReprTracebackNative, ReprTraceback] = ( + reprtraceback: ReprTracebackNative | ReprTraceback = ( ReprTracebackNative( traceback.format_exception( type(excinfo_.value), @@ -1118,9 +1101,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] - sections: List[Tuple[str, str, str]] = dataclasses.field( + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( init=False, default_factory=list ) @@ -1135,13 +1118,11 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] def __init__( self, - chain: Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ], + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. @@ -1162,8 +1143,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1172,8 +1153,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): - reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] - extraline: Optional[str] + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None style: TracebackStyle entrysep: ClassVar = "_ " @@ -1217,9 +1198,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] - reprfuncargs: Optional["ReprFuncArgs"] - reprlocals: Optional["ReprLocals"] - reprfileloc: Optional["ReprFileLocation"] + reprfuncargs: ReprFuncArgs | None + reprlocals: ReprLocals | None + reprfileloc: ReprFileLocation | None style: TracebackStyle def _write_entry_lines(self, tw: TerminalWriter) -> None: @@ -1243,9 +1224,9 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: # such as "> assert 0" fail_marker = f"{FormattedExcinfo.fail_marker} " indent_size = len(fail_marker) - indents: List[str] = [] - source_lines: List[str] = [] - failure_lines: List[str] = [] + indents: list[str] = [] + source_lines: list[str] = [] + failure_lines: list[str] = [] for index, line in enumerate(self.lines): is_failure_line = line.startswith(fail_marker) if is_failure_line: @@ -1324,7 +1305,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None: @dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): - args: Sequence[Tuple[str, object]] + args: Sequence[tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: @@ -1345,7 +1326,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: +def getfslineno(obj: object) -> tuple[str | Path, int]: """Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1). diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 7fa577e03b3..604aff8ba19 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast from bisect import bisect_right import inspect @@ -7,11 +9,7 @@ import types from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import overload -from typing import Tuple -from typing import Union import warnings @@ -23,7 +21,7 @@ class Source: def __init__(self, obj: object = None) -> None: if not obj: - self.lines: List[str] = [] + self.lines: list[str] = [] elif isinstance(obj, Source): self.lines = obj.lines elif isinstance(obj, (tuple, list)): @@ -50,9 +48,9 @@ def __eq__(self, other: object) -> bool: def __getitem__(self, key: int) -> str: ... @overload - def __getitem__(self, key: slice) -> "Source": ... + def __getitem__(self, key: slice) -> Source: ... - def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: + def __getitem__(self, key: int | slice) -> str | Source: if isinstance(key, int): return self.lines[key] else: @@ -68,7 +66,7 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self.lines) - def strip(self) -> "Source": + def strip(self) -> Source: """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): @@ -79,20 +77,20 @@ def strip(self) -> "Source": source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> "Source": + def indent(self, indent: str = " " * 4) -> Source: """Return a copy of the source object with all lines indented by the given indent-string.""" newsource = Source() newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> "Source": + def getstatement(self, lineno: int) -> Source: """Return Source statement which contains the given linenumber (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno: int) -> Tuple[int, int]: + def getstatementrange(self, lineno: int) -> tuple[int, int]: """Return (start, end) tuple which spans the minimal statement region which containing the given lineno.""" if not (0 <= lineno < len(self)): @@ -100,7 +98,7 @@ def getstatementrange(self, lineno: int) -> Tuple[int, int]: ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> "Source": + def deindent(self) -> Source: """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) @@ -115,7 +113,7 @@ def __str__(self) -> str: # -def findsource(obj) -> Tuple[Optional[Source], int]: +def findsource(obj) -> tuple[Source | None, int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: @@ -138,14 +136,14 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> List[str]: +def deindent(lines: Iterable[str]) -> list[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: +def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: # Flatten all statements and except handlers into one lineno-list. # AST's line numbers start indexing at 1. - values: List[int] = [] + values: list[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): # The lineno points to the class/def, so need to include the decorators. @@ -154,7 +152,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i values.append(d.lineno - 1) values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val: Optional[List[ast.stmt]] = getattr(x, name, None) + val: list[ast.stmt] | None = getattr(x, name, None) if val: # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) @@ -172,8 +170,8 @@ def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, - astnode: Optional[ast.AST] = None, -) -> Tuple[ast.AST, int, int]: + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: diff --git a/src/_pytest/_io/__init__.py b/src/_pytest/_io/__init__.py index db001e918cb..b0155b18b60 100644 --- a/src/_pytest/_io/__init__.py +++ b/src/_pytest/_io/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .terminalwriter import get_terminal_width from .terminalwriter import TerminalWriter diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index e637eec59e1..7213be7ba9b 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -13,6 +13,8 @@ # tuples with fairly non-descriptive content. This is modeled very much # after Lisp/Scheme - style pretty-printing of lists. If you find it # useful, thank small children who sleep at night. +from __future__ import annotations + import collections as _collections import dataclasses as _dataclasses from io import StringIO as _StringIO @@ -20,13 +22,8 @@ import types as _types from typing import Any from typing import Callable -from typing import Dict from typing import IO from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple class _safe_key: @@ -64,7 +61,7 @@ def __init__( self, indent: int = 4, width: int = 80, - depth: Optional[int] = None, + depth: int | None = None, ) -> None: """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -100,7 +97,7 @@ def _format( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: objid = id(object) @@ -136,7 +133,7 @@ def _pprint_dataclass( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: cls_name = object.__class__.__name__ @@ -149,9 +146,9 @@ def _pprint_dataclass( self._format_namespace_items(items, stream, indent, allowance, context, level) stream.write(")") - _dispatch: Dict[ + _dispatch: dict[ Callable[..., str], - Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None], + Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], ] = {} def _pprint_dict( @@ -160,7 +157,7 @@ def _pprint_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -177,7 +174,7 @@ def _pprint_ordered_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -196,7 +193,7 @@ def _pprint_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("[") @@ -211,7 +208,7 @@ def _pprint_tuple( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("(") @@ -226,7 +223,7 @@ def _pprint_set( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -252,7 +249,7 @@ def _pprint_str( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -311,7 +308,7 @@ def _pprint_bytes( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -340,7 +337,7 @@ def _pprint_bytearray( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -358,7 +355,7 @@ def _pprint_mappingproxy( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("mappingproxy(") @@ -373,7 +370,7 @@ def _pprint_simplenamespace( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if type(object) is _types.SimpleNamespace: @@ -391,11 +388,11 @@ def _pprint_simplenamespace( def _format_dict_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -415,11 +412,11 @@ def _format_dict_items( def _format_namespace_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -452,11 +449,11 @@ def _format_namespace_items( def _format_items( self, - items: List[Any], + items: list[Any], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -473,7 +470,7 @@ def _format_items( write("\n" + " " * indent) - def _repr(self, object: Any, context: Set[int], level: int) -> str: + def _repr(self, object: Any, context: set[int], level: int) -> str: return self._safe_repr(object, context.copy(), self._depth, level) def _pprint_default_dict( @@ -482,7 +479,7 @@ def _pprint_default_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: rdf = self._repr(object.default_factory, context, level) @@ -498,7 +495,7 @@ def _pprint_counter( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -519,7 +516,7 @@ def _pprint_chain_map( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): @@ -538,7 +535,7 @@ def _pprint_deque( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -557,7 +554,7 @@ def _pprint_user_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -570,7 +567,7 @@ def _pprint_user_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -583,7 +580,7 @@ def _pprint_user_string( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -591,7 +588,7 @@ def _pprint_user_string( _dispatch[_collections.UserString.__repr__] = _pprint_user_string def _safe_repr( - self, object: Any, context: Set[int], maxlevels: Optional[int], level: int + self, object: Any, context: set[int], maxlevels: int | None, level: int ) -> str: typ = type(object) if typ in _builtin_scalars: @@ -608,7 +605,7 @@ def _safe_repr( if objid in context: return _recursion(object) context.add(objid) - components: List[str] = [] + components: list[str] = [] append = components.append level += 1 for k, v in sorted(object.items(), key=_safe_tuple): diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 9f33fced676..5ace418227d 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import pprint import reprlib -from typing import Optional def _try_repr_or_str(obj: object) -> str: @@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -97,7 +98,7 @@ def safeformat(obj: object) -> str: def saferepr( - obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False ) -> str: """Return a size-limited safe repr-string for the given object. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 083c18232ff..70ebd3d061b 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -1,11 +1,12 @@ """Helper functions for writing to terminals and files.""" +from __future__ import annotations + import os import shutil import sys from typing import final from typing import Literal -from typing import Optional from typing import Sequence from typing import TextIO from typing import TYPE_CHECKING @@ -71,7 +72,7 @@ class TerminalWriter: invert=7, ) - def __init__(self, file: Optional[TextIO] = None) -> None: + def __init__(self, file: TextIO | None = None) -> None: if file is None: file = sys.stdout if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": @@ -85,7 +86,7 @@ def __init__(self, file: Optional[TextIO] = None) -> None: self._file = file self.hasmarkup = should_do_markup(file) self._current_line = "" - self._terminal_width: Optional[int] = None + self._terminal_width: int | None = None self.code_highlight = True @property @@ -116,8 +117,8 @@ def markup(self, text: str, **markup: bool) -> str: def sep( self, sepchar: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: if fullwidth is None: @@ -200,9 +201,7 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No for indent, new_line in zip(indents, new_lines): self.line(indent + new_line) - def _get_pygments_lexer( - self, lexer: Literal["python", "diff"] - ) -> Optional["Lexer"]: + def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None: try: if lexer == "python": from pygments.lexers.python import PythonLexer @@ -217,7 +216,7 @@ def _get_pygments_lexer( except ModuleNotFoundError: return None - def _get_pygments_formatter(self) -> Optional["Formatter"]: + def _get_pygments_formatter(self) -> Formatter | None: try: import pygments.util except ModuleNotFoundError: diff --git a/src/_pytest/_io/wcwidth.py b/src/_pytest/_io/wcwidth.py index 53803133519..23886ff1581 100644 --- a/src/_pytest/_io/wcwidth.py +++ b/src/_pytest/_io/wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import lru_cache import unicodedata diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 21dd4a4a4bb..f2f1d029b4c 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -1,11 +1,11 @@ # mypy: allow-untyped-defs """Support for presenting detailed information in failing assertions.""" +from __future__ import annotations + import sys from typing import Any from typing import Generator -from typing import List -from typing import Optional from typing import TYPE_CHECKING from _pytest.assertion import rewrite @@ -94,7 +94,7 @@ class AssertionState: def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook: Optional[rewrite.AssertionRewritingHook] = None + self.hook: rewrite.AssertionRewritingHook | None = None def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: @@ -113,7 +113,7 @@ def undo() -> None: return hook -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: # This hook is only called when test modules are collected # so for example not in the managing process of pytest-xdist # (which does not collect test modules). @@ -133,7 +133,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: """ ihook = item.ihook - def callbinrepr(op, left: object, right: object) -> Optional[str]: + def callbinrepr(op, left: object, right: object) -> str | None: """Call the pytest_assertrepr_compare hook and prepare the result. This uses the first result from the hook and then ensures the @@ -179,7 +179,7 @@ def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: util._config = None -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: assertstate = session.config.stash.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: @@ -188,5 +188,5 @@ def pytest_sessionfinish(session: "Session") -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any -) -> Optional[List[str]]: +) -> list[str] | None: return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index b29a254f5f7..442fc5d9f30 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -1,5 +1,7 @@ """Rewrite assertion AST to produce nice error messages.""" +from __future__ import annotations + import ast from collections import defaultdict import errno @@ -18,17 +20,11 @@ import tokenize import types from typing import Callable -from typing import Dict from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr @@ -73,17 +69,17 @@ def __init__(self, config: Config) -> None: self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session: Optional[Session] = None - self._rewritten_names: Dict[str, Path] = {} - self._must_rewrite: Set[str] = set() + self.session: Session | None = None + self._rewritten_names: dict[str, Path] = {} + self._must_rewrite: set[str] = set() # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache: Dict[str, bool] = {} + self._marked_for_rewrite_cache: dict[str, bool] = {} self._session_paths_checked = False - def set_session(self, session: Optional[Session]) -> None: + def set_session(self, session: Session | None) -> None: self.session = session self._session_paths_checked = False @@ -93,9 +89,9 @@ def set_session(self, session: Optional[Session]) -> None: def find_spec( self, name: str, - path: Optional[Sequence[Union[str, bytes]]] = None, - target: Optional[types.ModuleType] = None, - ) -> Optional[importlib.machinery.ModuleSpec]: + path: Sequence[str | bytes] | None = None, + target: types.ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: if self._writing_pyc: return None state = self.config.stash[assertstate_key] @@ -132,7 +128,7 @@ def find_spec( def create_module( self, spec: importlib.machinery.ModuleSpec - ) -> Optional[types.ModuleType]: + ) -> types.ModuleType | None: return None # default behaviour is fine def exec_module(self, module: types.ModuleType) -> None: @@ -177,7 +173,7 @@ def exec_module(self, module: types.ModuleType) -> None: state.trace(f"found cached rewritten pyc for {fn}") exec(co, module.__dict__) - def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: + def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: """A fast way to get out of rewriting modules. Profiling has shown that the call to PathFinder.find_spec (inside of @@ -216,7 +212,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: state.trace(f"early skip of rewriting module: {name}") return True - def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: + def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: # always rewrite conftest files if os.path.basename(fn) == "conftest.py": state.trace(f"rewriting conftest file: {fn!r}") @@ -237,7 +233,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -278,7 +274,7 @@ def _warn_already_imported(self, name: str) -> None: stacklevel=5, ) - def get_data(self, pathname: Union[str, bytes]) -> bytes: + def get_data(self, pathname: str | bytes) -> bytes: """Optional PEP302 get_data API.""" with open(pathname, "rb") as f: return f.read() @@ -317,7 +313,7 @@ def _write_pyc_fp( def _write_pyc( - state: "AssertionState", + state: AssertionState, co: types.CodeType, source_stat: os.stat_result, pyc: Path, @@ -341,7 +337,7 @@ def _write_pyc( return True -def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: +def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: """Read and rewrite *fn* and return the code object.""" stat = os.stat(fn) source = fn.read_bytes() @@ -354,7 +350,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT def _read_pyc( source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> Optional[types.CodeType]: +) -> types.CodeType | None: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. @@ -404,8 +400,8 @@ def _read_pyc( def rewrite_asserts( mod: ast.Module, source: bytes, - module_path: Optional[str] = None, - config: Optional[Config] = None, + module_path: str | None = None, + config: Config | None = None, ) -> None: """Rewrite the assert statements in mod.""" AssertionRewriter(module_path, config, source).run(mod) @@ -425,7 +421,7 @@ def _saferepr(obj: object) -> str: return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") -def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]: +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: """Get `maxsize` configuration for saferepr based on the given config object.""" if config is None: verbosity = 0 @@ -543,14 +539,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]: @functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> Dict[int, str]: +def _get_assertion_exprs(src: bytes) -> dict[int, str]: """Return a mapping from {lineno: "assertion test expression"}.""" - ret: Dict[int, str] = {} + ret: dict[int, str] = {} depth = 0 - lines: List[str] = [] - assert_lineno: Optional[int] = None - seen_lines: Set[int] = set() + lines: list[str] = [] + assert_lineno: int | None = None + seen_lines: set[int] = set() def _write_and_reset() -> None: nonlocal depth, lines, assert_lineno, seen_lines @@ -657,7 +653,7 @@ class AssertionRewriter(ast.NodeVisitor): """ def __init__( - self, module_path: Optional[str], config: Optional[Config], source: bytes + self, module_path: str | None, config: Config | None, source: bytes ) -> None: super().__init__() self.module_path = module_path @@ -670,7 +666,7 @@ def __init__( self.enable_assertion_pass_hook = False self.source = source self.scope: tuple[ast.AST, ...] = () - self.variables_overwrite: defaultdict[tuple[ast.AST, ...], Dict[str, str]] = ( + self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( defaultdict(dict) ) @@ -737,7 +733,7 @@ def run(self, mod: ast.Module) -> None: # Collect asserts. self.scope = (mod,) - nodes: List[Union[ast.AST, Sentinel]] = [mod] + nodes: list[ast.AST | Sentinel] = [mod] while nodes: node = nodes.pop() if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): @@ -749,7 +745,7 @@ def run(self, mod: ast.Module) -> None: assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): - new: List[ast.AST] = [] + new: list[ast.AST] = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -821,7 +817,7 @@ def push_format_context(self) -> None: to format a string of %-formatted values as added by .explanation_param(). """ - self.explanation_specifiers: Dict[str, ast.expr] = {} + self.explanation_specifiers: dict[str, ast.expr] = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: @@ -835,7 +831,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys: List[Optional[ast.expr]] = [ast.Constant(key) for key in current.keys()] + keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()] format_dict = ast.Dict(keys, list(current.values())) form = ast.BinOp(expl_expr, ast.Mod(), format_dict) name = "@py_format" + str(next(self.variable_counter)) @@ -844,13 +840,13 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) return ast.Name(name, ast.Load()) - def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]: + def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: """Handle expressions we don't have custom code for.""" assert isinstance(node, ast.expr) res = self.assign(node) return res, self.explanation_param(self.display(res)) - def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: + def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: """Return the AST statements to replace the ast.Assert instance. This rewrites the test of an assertion to provide @@ -874,15 +870,15 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: lineno=assert_.lineno, ) - self.statements: List[ast.stmt] = [] - self.variables: List[str] = [] + self.statements: list[ast.stmt] = [] + self.variables: list[str] = [] self.variable_counter = itertools.count() if self.enable_assertion_pass_hook: - self.format_variables: List[str] = [] + self.format_variables: list[str] = [] - self.stack: List[Dict[str, ast.expr]] = [] - self.expl_stmts: List[ast.stmt] = [] + self.stack: list[dict[str, ast.expr]] = [] + self.expl_stmts: list[ast.stmt] = [] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) @@ -926,13 +922,13 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: [*self.expl_stmts, hook_call_pass], [], ) - statements_pass: List[ast.stmt] = [hook_impl_test] + statements_pass: list[ast.stmt] = [hook_impl_test] # Test for assertion condition main_test = ast.If(negation, statements_fail, statements_pass) self.statements.append(main_test) if self.format_variables: - variables: List[ast.expr] = [ + variables: list[ast.expr] = [ ast.Name(name, ast.Store()) for name in self.format_variables ] clear_format = ast.Assign(variables, ast.Constant(None)) @@ -968,7 +964,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: ast.copy_location(node, assert_) return self.statements - def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: + def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]: # This method handles the 'walrus operator' repr of the target # name if it's a local variable or _should_repr_global_name() # thinks it's acceptable. @@ -980,7 +976,7 @@ def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) return name, self.explanation_param(expr) - def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: + def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) @@ -990,7 +986,7 @@ def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) return name, self.explanation_param(expr) - def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: + def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: res_var = self.variable() expl_list = self.assign(ast.List([], ast.Load())) app = ast.Attribute(expl_list, "append", ast.Load()) @@ -1002,7 +998,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner: List[ast.stmt] = [] + fail_inner: list[ast.stmt] = [] # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 self.expl_stmts = fail_inner @@ -1030,7 +1026,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: cond: ast.expr = res if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner: List[ast.stmt] = [] + inner: list[ast.stmt] = [] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save @@ -1039,13 +1035,13 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]: + def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: pattern = UNARY_MAP[unary.op.__class__] operand_res, operand_expl = self.visit(unary.operand) res = self.assign(ast.UnaryOp(unary.op, operand_res)) return res, pattern % (operand_expl,) - def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: + def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: symbol = BINOP_MAP[binop.op.__class__] left_expr, left_expl = self.visit(binop.left) right_expr, right_expl = self.visit(binop.right) @@ -1053,7 +1049,7 @@ def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -1085,13 +1081,13 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" return res, outer_expl - def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]: + def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: # A Starred node can appear in a function call. res, expl = self.visit(starred.value) new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: + def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) value, value_expl = self.visit(attr.value) @@ -1101,7 +1097,7 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: + def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]: self.push_format_context() # We first check if we have overwritten a variable in the previous assert if isinstance( @@ -1114,11 +1110,11 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" res_variables = [self.variable() for i in range(len(comp.ops))] - load_names: List[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] + load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables] it = zip(range(len(comp.ops)), comp.ops, comp.comparators) - expls: List[ast.expr] = [] - syms: List[ast.expr] = [] + expls: list[ast.expr] = [] + syms: list[ast.expr] = [] results = [left_res] for i, op, next_operand in it: if ( diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 4fdfd86a519..b67f02ccaf8 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -4,8 +4,7 @@ terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. """ -from typing import List -from typing import Optional +from __future__ import annotations from _pytest.assertion import util from _pytest.config import Config @@ -18,8 +17,8 @@ def truncate_if_required( - explanation: List[str], item: Item, max_length: Optional[int] = None -) -> List[str]: + explanation: list[str], item: Item, max_length: int | None = None +) -> list[str]: """Truncate this assertion explanation if the given test item is eligible.""" if _should_truncate_item(item): return _truncate_explanation(explanation) @@ -33,10 +32,10 @@ def _should_truncate_item(item: Item) -> bool: def _truncate_explanation( - input_lines: List[str], - max_lines: Optional[int] = None, - max_chars: Optional[int] = None, -) -> List[str]: + input_lines: list[str], + max_lines: int | None = None, + max_chars: int | None = None, +) -> list[str]: """Truncate given list of strings that makes up the assertion explanation. Truncates to either 8 lines, or 640 characters - whichever the input reaches @@ -100,7 +99,7 @@ def _truncate_explanation( ] -def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: +def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: # Find point at which input length exceeds total allowed length iterated_char_count = 0 for iterated_index, input_line in enumerate(input_lines): diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index a118befcc16..4dc1af4af03 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Utilities for assertion debugging.""" +from __future__ import annotations + import collections.abc import os import pprint @@ -8,10 +10,8 @@ from typing import Any from typing import Callable from typing import Iterable -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Protocol from typing import Sequence from unicodedata import normalize @@ -28,14 +28,14 @@ # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None +_reprcompare: Callable[[str, object, object], str | None] | None = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. -_assertion_pass: Optional[Callable[[int, str, str], None]] = None +_assertion_pass: Callable[[int, str, str], None] | None = None # Config object which is assigned during pytest_runtest_protocol. -_config: Optional[Config] = None +_config: Config | None = None class _HighlightFunc(Protocol): @@ -58,7 +58,7 @@ def format_explanation(explanation: str) -> str: return "\n".join(result) -def _split_explanation(explanation: str) -> List[str]: +def _split_explanation(explanation: str) -> list[str]: r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. @@ -75,7 +75,7 @@ def _split_explanation(explanation: str) -> List[str]: return lines -def _format_lines(lines: Sequence[str]) -> List[str]: +def _format_lines(lines: Sequence[str]) -> list[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting @@ -169,7 +169,7 @@ def has_default_eq( def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False -) -> Optional[List[str]]: +) -> list[str] | None: """Return specialised explanations for some operators/operands.""" verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) @@ -239,7 +239,7 @@ def assertrepr_compare( def _compare_eq_any( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 -) -> List[str]: +) -> list[str]: explanation = [] if istext(left) and istext(right): explanation = _diff_text(left, right, verbose) @@ -274,7 +274,7 @@ def _compare_eq_any( return explanation -def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: +def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]: """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing @@ -282,7 +282,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: """ from difflib import ndiff - explanation: List[str] = [] + explanation: list[str] = [] if verbose < 1: i = 0 # just in case left or right has zero length @@ -327,7 +327,7 @@ def _compare_eq_iterable( right: Iterable[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: if verbose <= 0 and not running_on_ci(): return ["Use -v to get more diff"] # dynamic import to speedup pytest @@ -356,9 +356,9 @@ def _compare_eq_sequence( right: Sequence[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - explanation: List[str] = [] + explanation: list[str] = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -417,7 +417,7 @@ def _compare_eq_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = [] explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) @@ -429,7 +429,7 @@ def _compare_gt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_gte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -441,7 +441,7 @@ def _compare_lt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_lte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -453,7 +453,7 @@ def _compare_gte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("right", right, left, highlighter) @@ -462,7 +462,7 @@ def _compare_lte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("left", left, right, highlighter) @@ -471,7 +471,7 @@ def _set_one_sided_diff( set1: AbstractSet[Any], set2: AbstractSet[Any], highlighter: _HighlightFunc, -) -> List[str]: +) -> list[str]: explanation = [] diff = set1 - set2 if diff: @@ -486,8 +486,8 @@ def _compare_eq_dict( right: Mapping[Any, Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: - explanation: List[str] = [] +) -> list[str]: + explanation: list[str] = [] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) @@ -531,7 +531,7 @@ def _compare_eq_dict( def _compare_eq_cls( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int -) -> List[str]: +) -> list[str]: if not has_default_eq(left): return [] if isdatacls(left): @@ -584,7 +584,7 @@ def _compare_eq_cls( return explanation -def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: +def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: index = text.find(term) head = text[:index] tail = text[index + len(term) :] diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 8ad36f9b91c..51778c456c4 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -3,20 +3,17 @@ # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +from __future__ import annotations + import dataclasses import errno import json import os from pathlib import Path import tempfile -from typing import Dict from typing import final from typing import Generator from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Union from .pathlib import resolve_from_str from .pathlib import rm_rf @@ -77,7 +74,7 @@ def __init__( self._config = config @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": + def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: """Create the Cache instance for a Config. :meta private: @@ -249,7 +246,7 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: class LFPluginCollWrapper: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False @@ -263,7 +260,7 @@ def pytest_make_collect_report( lf_paths = self.lfplugin._last_failed_paths # Use stable sort to prioritize last failed. - def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + def sort_key(node: nodes.Item | nodes.Collector) -> bool: return node.path in lf_paths res.result = sorted( @@ -301,13 +298,13 @@ def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: class LFPluginCollSkipfiles: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin @hookimpl def pytest_make_collect_report( self, collector: nodes.Collector - ) -> Optional[CollectReport]: + ) -> CollectReport | None: if isinstance(collector, File): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -326,9 +323,9 @@ def __init__(self, config: Config) -> None: active_keys = "lf", "failedfirst" self.active = any(config.getoption(key) for key in active_keys) assert config.cache - self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count: Optional[int] = None - self._report_status: Optional[str] = None + self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._previously_failed_count: int | None = None + self._report_status: str | None = None self._skipped_files = 0 # count skipped files during collection due to --lf if config.getoption("lf"): @@ -337,7 +334,7 @@ def __init__(self, config: Config) -> None: LFPluginCollWrapper(self), "lfplugin-collwrapper" ) - def get_last_failed_paths(self) -> Set[Path]: + def get_last_failed_paths(self) -> set[Path]: """Return a set with all Paths of the previously failed nodeids and their parents.""" rootpath = self.config.rootpath @@ -348,7 +345,7 @@ def get_last_failed_paths(self) -> Set[Path]: result.update(path.parents) return {x for x in result if x.exists()} - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.active and self.config.getoption("verbose") >= 0: return f"run-last-failure: {self._report_status}" return None @@ -370,7 +367,7 @@ def pytest_collectreport(self, report: CollectReport) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield @@ -442,13 +439,13 @@ def __init__(self, config: Config) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, items: List[nodes.Item] + self, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield if self.active: - new_items: Dict[str, nodes.Item] = {} - other_items: Dict[str, nodes.Item] = {} + new_items: dict[str, nodes.Item] = {} + other_items: dict[str, nodes.Item] = {} for item in items: if item.nodeid not in self.cached_nodeids: new_items[item.nodeid] = item @@ -464,7 +461,7 @@ def pytest_collection_modifyitems( return res - def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) def pytest_sessionfinish(self) -> None: @@ -541,7 +538,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session @@ -572,7 +569,7 @@ def cache(request: FixtureRequest) -> Cache: return request.config.cache -def pytest_report_header(config: Config) -> Optional[str]: +def pytest_report_header(config: Config) -> str | None: """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": assert config.cache is not None diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 89a938d5416..c4dfcc27552 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Per-test stdout/stderr capturing mechanism.""" +from __future__ import annotations + import abc import collections import contextlib @@ -19,15 +21,14 @@ from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import NamedTuple -from typing import Optional from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union + + +if TYPE_CHECKING: + from typing_extensions import Self from _pytest.config import Config from _pytest.config import hookimpl @@ -213,7 +214,7 @@ def read(self, size: int = -1) -> str: def __next__(self) -> str: return self.readline() - def readlines(self, hint: Optional[int] = -1) -> List[str]: + def readlines(self, hint: int | None = -1) -> list[str]: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) @@ -245,7 +246,7 @@ def seekable(self) -> bool: def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: Optional[int] = None) -> int: + def truncate(self, size: int | None = None) -> int: raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: @@ -257,14 +258,14 @@ def writelines(self, lines: Iterable[str]) -> None: def writable(self) -> bool: return False - def __enter__(self) -> "DontReadFromInput": + def __enter__(self) -> Self: return self def __exit__( self, - type: Optional[Type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: pass @@ -339,7 +340,7 @@ def writeorg(self, data: str) -> None: class SysCaptureBase(CaptureBase[AnyStr]): def __init__( - self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False ) -> None: name = patchsysdict[fd] self._old: TextIO = getattr(sys, name) @@ -370,7 +371,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -457,7 +458,7 @@ def __init__(self, targetfd: int) -> None: # Further complications are the need to support suspend() and the # possibility of FD reuse (e.g. the tmpfile getting the very same # target FD). The following approach is robust, I believe. - self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -487,7 +488,7 @@ def __repr__(self) -> str: f"_state={self._state!r} tmpfile={self.tmpfile!r}>" ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -609,13 +610,13 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: Optional[CaptureBase[AnyStr]], - out: Optional[CaptureBase[AnyStr]], - err: Optional[CaptureBase[AnyStr]], + in_: CaptureBase[AnyStr] | None, + out: CaptureBase[AnyStr] | None, + err: CaptureBase[AnyStr] | None, ) -> None: - self.in_: Optional[CaptureBase[AnyStr]] = in_ - self.out: Optional[CaptureBase[AnyStr]] = out - self.err: Optional[CaptureBase[AnyStr]] = err + self.in_: CaptureBase[AnyStr] | None = in_ + self.out: CaptureBase[AnyStr] | None = out + self.err: CaptureBase[AnyStr] | None = err def __repr__(self) -> str: return ( @@ -632,7 +633,7 @@ def start_capturing(self) -> None: if self.err: self.err.start() - def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: @@ -725,8 +726,8 @@ class CaptureManager: def __init__(self, method: _CaptureMethod) -> None: self._method: Final = method - self._global_capturing: Optional[MultiCapture[str]] = None - self._capture_fixture: Optional[CaptureFixture[Any]] = None + self._global_capturing: MultiCapture[str] | None = None + self._capture_fixture: CaptureFixture[Any] | None = None def __repr__(self) -> str: return ( @@ -734,7 +735,7 @@ def __repr__(self) -> str: f"_capture_fixture={self._capture_fixture!r}>" ) - def is_capturing(self) -> Union[str, bool]: + def is_capturing(self) -> str | bool: if self.is_globally_capturing(): return "global" if self._capture_fixture: @@ -782,7 +783,7 @@ def read_global_capture(self) -> CaptureResult[str]: # Fixture Control - def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: + def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: if self._capture_fixture: current_fixture = self._capture_fixture.request.fixturename requested_fixture = capture_fixture.request.fixturename @@ -897,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]): def __init__( self, - captureclass: Type[CaptureBase[AnyStr]], + captureclass: type[CaptureBase[AnyStr]], request: SubRequest, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass: Type[CaptureBase[AnyStr]] = captureclass + self.captureclass: type[CaptureBase[AnyStr]] = captureclass self.request = request - self._capture: Optional[MultiCapture[AnyStr]] = None + self._capture: MultiCapture[AnyStr] | None = None self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 058aaa1ff30..47e3f5b5a50 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Command line options, ini-file and conftest.py processing.""" +from __future__ import annotations + import argparse import collections.abc import copy @@ -21,22 +23,16 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import Type from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -141,9 +137,9 @@ def filter_traceback_for_conftest_import_failure( def main( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> Union[int, ExitCode]: + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: """Perform an in-process test run. :param args: @@ -176,9 +172,7 @@ def main( return ExitCode.USAGE_ERROR else: try: - ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( - config=config - ) + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) try: return ExitCode(ret) except ValueError: @@ -286,9 +280,9 @@ def directory_arg(path: str, optname: str) -> str: def get_config( - args: Optional[List[str]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() config = Config( @@ -310,7 +304,7 @@ def get_config( return config -def get_plugin_manager() -> "PytestPluginManager": +def get_plugin_manager() -> PytestPluginManager: """Obtain a new instance of the :py:class:`pytest.PytestPluginManager`, with default plugins already loaded. @@ -322,9 +316,9 @@ def get_plugin_manager() -> "PytestPluginManager": def _prepareconfig( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: if args is None: args = sys.argv[1:] elif isinstance(args, os.PathLike): @@ -364,14 +358,14 @@ def _get_directory(path: Path) -> Path: def _get_legacy_hook_marks( method: Any, hook_type: str, - opt_names: Tuple[str, ...], -) -> Dict[str, bool]: + opt_names: tuple[str, ...], +) -> dict[str, bool]: if TYPE_CHECKING: # abuse typeguard from importlib to avoid massive method type union thats lacking a alias assert inspect.isroutine(method) - known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])} - must_warn: List[str] = [] - opts: Dict[str, bool] = {} + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} for opt_name in opt_names: opt_attr = getattr(method, opt_name, AttributeError) if opt_attr is not AttributeError: @@ -410,13 +404,13 @@ def __init__(self) -> None: # -- State related to local conftest plugins. # All loaded conftest modules. - self._conftest_plugins: Set[types.ModuleType] = set() + self._conftest_plugins: set[types.ModuleType] = set() # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} + self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Optional[Path] = None + self._confcutdir: Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -430,7 +424,7 @@ def __init__(self) -> None: # previously we would issue a warning when a plugin was skipped, but # since we refactored warnings as first citizens of Config, they are # just stored here to be used later. - self.skipped_plugins: List[Tuple[str, str]] = [] + self.skipped_plugins: list[tuple[str, str]] = [] self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -456,7 +450,7 @@ def __init__(self) -> None: def parse_hookimpl_opts( self, plugin: _PluggyPlugin, name: str - ) -> Optional[HookimplOpts]: + ) -> HookimplOpts | None: """:meta private:""" # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes @@ -480,7 +474,7 @@ def parse_hookimpl_opts( method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") ) - def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]: + def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: """:meta private:""" opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: @@ -493,9 +487,7 @@ def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOp ) return opts - def register( - self, plugin: _PluggyPlugin, name: Optional[str] = None - ) -> Optional[str]: + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( @@ -522,14 +514,14 @@ def register( def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: Optional[_PluggyPlugin] = self.get_plugin(name) + plugin: _PluggyPlugin | None = self.get_plugin(name) return plugin def hasplugin(self, name: str) -> bool: """Return whether a plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config: "Config") -> None: + def pytest_configure(self, config: Config) -> None: """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. @@ -552,13 +544,13 @@ def pytest_configure(self, config: "Config") -> None: # def _set_initial_conftests( self, - args: Sequence[Union[str, Path]], + args: Sequence[str | Path], pyargs: bool, noconftest: bool, rootpath: Path, - confcutdir: Optional[Path], + confcutdir: Path | None, invocation_dir: Path, - importmode: Union[ImportMode, str], + importmode: ImportMode | str, *, consider_namespace_packages: bool, ) -> None: @@ -619,7 +611,7 @@ def _is_in_confcutdir(self, path: Path) -> bool: def _try_load_conftest( self, anchor: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -644,7 +636,7 @@ def _try_load_conftest( def _loadconftestmodules( self, path: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -681,7 +673,7 @@ def _rget_with_confmod( self, name: str, path: Path, - ) -> Tuple[types.ModuleType, Any]: + ) -> tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path) for mod in reversed(modules): try: @@ -693,7 +685,7 @@ def _rget_with_confmod( def _importconftest( self, conftestpath: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -832,7 +824,7 @@ def consider_module(self, mod: types.ModuleType) -> None: self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) def _import_plugin_specs( - self, spec: Union[None, types.ModuleType, str, Sequence[str]] + self, spec: None | types.ModuleType | str | Sequence[str] ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: @@ -877,8 +869,8 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No def _get_plugin_specs_as_list( - specs: Union[None, types.ModuleType, str, Sequence[str]], -) -> List[str]: + specs: None | types.ModuleType | str | Sequence[str], +) -> list[str]: """Parse a plugins specification into a list of plugin names.""" # None means empty. if specs is None: @@ -999,9 +991,9 @@ class InvocationParams: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] + args: tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] + plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" @@ -1010,7 +1002,7 @@ def __init__( self, *, args: Iterable[str], - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + plugins: Sequence[str | _PluggyPlugin] | None, dir: Path, ) -> None: object.__setattr__(self, "args", tuple(args)) @@ -1032,13 +1024,13 @@ class ArgsSource(enum.Enum): TESTPATHS = enum.auto() # Set by cacheprovider plugin. - cache: Optional["Cache"] + cache: Cache | None def __init__( self, pluginmanager: PytestPluginManager, *, - invocation_params: Optional[InvocationParams] = None, + invocation_params: InvocationParams | None = None, ) -> None: from .argparsing import FILE_OR_DIR from .argparsing import Parser @@ -1083,17 +1075,17 @@ def __init__( self.trace = self.pluginmanager.trace.root.get("config") self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] - self._inicache: Dict[str, Any] = {} + self._inicache: dict[str, Any] = {} self._override_ini: Sequence[str] = () - self._opt2dest: Dict[str, str] = {} - self._cleanup: List[Callable[[], None]] = [] + self._opt2dest: dict[str, str] = {} + self._cleanup: list[Callable[[], None]] = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) self.args_source = Config.ArgsSource.ARGS - self.args: List[str] = [] + self.args: list[str] = [] @property def rootpath(self) -> Path: @@ -1106,7 +1098,7 @@ def rootpath(self) -> Path: return self._rootpath @property - def inipath(self) -> Optional[Path]: + def inipath(self) -> Path | None: """The path to the :ref:`configfile `. :type: Optional[pathlib.Path] @@ -1137,15 +1129,15 @@ def _ensure_unconfigure(self) -> None: fin() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( + terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( "terminalreporter" ) assert terminalreporter is not None return terminalreporter._tw def pytest_cmdline_parse( - self, pluginmanager: PytestPluginManager, args: List[str] - ) -> "Config": + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: try: self.parse(args) except UsageError: @@ -1171,7 +1163,7 @@ def pytest_cmdline_parse( def notify_exception( self, excinfo: ExceptionInfo[BaseException], - option: Optional[argparse.Namespace] = None, + option: argparse.Namespace | None = None, ) -> None: if option and getattr(option, "fulltrace", False): style: TracebackStyle = "long" @@ -1194,7 +1186,7 @@ def cwd_relative_nodeid(self, nodeid: str) -> str: return nodeid @classmethod - def fromdictargs(cls, option_dict, args) -> "Config": + def fromdictargs(cls, option_dict, args) -> Config: """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) @@ -1203,7 +1195,7 @@ def fromdictargs(cls, option_dict, args) -> "Config": config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt: "Argument") -> None: + def _processopt(self, opt: Argument) -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest @@ -1212,7 +1204,7 @@ def _processopt(self, opt: "Argument") -> None: setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: "Config") -> None: + def pytest_load_initial_conftests(self, early_config: Config) -> None: # We haven't fully parsed the command line arguments yet, so # early_config.args it not set yet. But we need it for # discovering the initial conftests. So "pre-run" the logic here. @@ -1303,7 +1295,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None: for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args: List[str], via: str) -> List[str]: + def _validate_args(self, args: list[str], via: str) -> list[str]: """Validate known args.""" self._parser._config_source_hint = via # type: ignore try: @@ -1318,13 +1310,13 @@ def _validate_args(self, args: List[str], via: str) -> List[str]: def _decide_args( self, *, - args: List[str], + args: list[str], pyargs: bool, - testpaths: List[str], + testpaths: list[str], invocation_dir: Path, rootpath: Path, warn: bool, - ) -> Tuple[List[str], ArgsSource]: + ) -> tuple[list[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. :param warn: Whether can issue warnings. @@ -1360,7 +1352,7 @@ def _decide_args( result = [str(invocation_dir)] return result, source - def _preparse(self, args: List[str], addopts: bool = True) -> None: + def _preparse(self, args: list[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") if len(env_addopts): @@ -1484,11 +1476,11 @@ def _warn_or_fail_if_strict(self, message: str) -> None: self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) - def _get_unknown_ini_keys(self) -> List[str]: + def _get_unknown_ini_keys(self) -> list[str]: parser_inicfg = self._parser._inidict return [name for name in self.inicfg if name not in parser_inicfg] - def parse(self, args: List[str], addopts: bool = True) -> None: + def parse(self, args: list[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. assert ( self.args == [] @@ -1592,7 +1584,7 @@ def getini(self, name: str): # Meant for easy monkeypatching by legacypath plugin. # Can be inlined back (with no cover removed) once legacypath is gone. - def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): + def _getini_unknown_type(self, name: str, type: str, value: str | list[str]): msg = f"unknown configuration type: {type}" raise ValueError(msg, value) # pragma: no cover @@ -1648,14 +1640,14 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: + def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None: try: mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None modpath = Path(mod.__file__).parent - values: List[Path] = [] + values: list[Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): relroot = Path(relroot) @@ -1665,7 +1657,7 @@ def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: values.append(relroot) return values - def _get_override_ini_value(self, name: str) -> Optional[str]: + def _get_override_ini_value(self, name: str) -> str | None: value = None # override_ini is a list of "ini=value" options. # Always use the last item if multiple values are set for same ini-name, @@ -1720,7 +1712,7 @@ def getvalueorskip(self, name: str, path=None): VERBOSITY_TEST_CASES: Final = "test_cases" _VERBOSITY_INI_DEFAULT: Final = "auto" - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: r"""Retrieve the verbosity level for a fine-grained verbosity type. :param verbosity_type: Verbosity type to get level for. If a level is @@ -1771,7 +1763,7 @@ def _verbosity_ini_name(verbosity_type: str) -> str: return f"verbosity_{verbosity_type}" @staticmethod - def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None: + def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: """Add a output verbosity configuration option for the given output type. :param parser: Parser for command line arguments and ini-file values. @@ -1827,7 +1819,7 @@ def _assertion_supported() -> bool: def create_terminal_writer( - config: Config, file: Optional[TextIO] = None + config: Config, file: TextIO | None = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. @@ -1871,7 +1863,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1917,7 +1909,7 @@ def parse_warning_filter( except warnings._OptionError as e: raise UsageError(error_template.format(error=str(e))) from None try: - category: Type[Warning] = _resolve_warning_category(category_) + category: type[Warning] = _resolve_warning_category(category_) except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") @@ -1940,7 +1932,7 @@ def parse_warning_filter( return action, message, category, module, lineno -def _resolve_warning_category(category: str) -> Type[Warning]: +def _resolve_warning_category(category: str) -> type[Warning]: """ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) propagate so we can get access to their tracebacks (#9218). diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index f270b864c6a..85aa4632702 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse from gettext import gettext import os @@ -6,16 +8,12 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import _pytest._io from _pytest.config.exceptions import UsageError @@ -41,32 +39,32 @@ class Parser: there's an error processing the command line arguments. """ - prog: Optional[str] = None + prog: str | None = None def __init__( self, - usage: Optional[str] = None, - processopt: Optional[Callable[["Argument"], None]] = None, + usage: str | None = None, + processopt: Callable[[Argument], None] | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) - self._groups: List[OptionGroup] = [] + self._groups: list[OptionGroup] = [] self._processopt = processopt self._usage = usage - self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {} - self._ininames: List[str] = [] - self.extra_info: Dict[str, Any] = {} + self._inidict: dict[str, tuple[str, str | None, Any]] = {} + self._ininames: list[str] = [] + self.extra_info: dict[str, Any] = {} - def processoption(self, option: "Argument") -> None: + def processoption(self, option: Argument) -> None: if self._processopt: if option.dest: self._processopt(option) def getgroup( - self, name: str, description: str = "", after: Optional[str] = None - ) -> "OptionGroup": + self, name: str, description: str = "", after: str | None = None + ) -> OptionGroup: """Get (or create) a named option Group. :param name: Name of the option group. @@ -108,8 +106,8 @@ def addoption(self, *opts: str, **attrs: Any) -> None: def parse( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: from _pytest._argcomplete import try_argcomplete @@ -118,7 +116,7 @@ def parse( strargs = [os.fspath(x) for x in args] return self.optparser.parse_args(strargs, namespace=namespace) - def _getparser(self) -> "MyOptionParser": + def _getparser(self) -> MyOptionParser: from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) @@ -139,10 +137,10 @@ def _getparser(self) -> "MyOptionParser": def parse_setoption( self, - args: Sequence[Union[str, "os.PathLike[str]"]], + args: Sequence[str | os.PathLike[str]], option: argparse.Namespace, - namespace: Optional[argparse.Namespace] = None, - ) -> List[str]: + namespace: argparse.Namespace | None = None, + ) -> list[str]: parsedoption = self.parse(args, namespace=namespace) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) @@ -150,8 +148,8 @@ def parse_setoption( def parse_known_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Parse the known arguments at this point. @@ -161,9 +159,9 @@ def parse_known_args( def parse_known_and_unknown_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, - ) -> Tuple[argparse.Namespace, List[str]]: + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> tuple[argparse.Namespace, list[str]]: """Parse the known arguments at this point, and also return the remaining unknown arguments. @@ -179,9 +177,8 @@ def addini( self, name: str, help: str, - type: Optional[ - Literal["string", "paths", "pathlist", "args", "linelist", "bool"] - ] = None, + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] + | None = None, default: Any = NOT_SET, ) -> None: """Register an ini-file option. @@ -224,7 +221,7 @@ def addini( def get_ini_default_for_type( - type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]], + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None, ) -> Any: """ Used by addini to get the default value for a given ini-option type, when @@ -244,7 +241,7 @@ class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" - def __init__(self, msg: str, option: Union["Argument", str]) -> None: + def __init__(self, msg: str, option: Argument | str) -> None: self.msg = msg self.option_id = str(option) @@ -267,8 +264,8 @@ class Argument: def __init__(self, *names: str, **attrs: Any) -> None: """Store params in private vars for use in add_argument.""" self._attrs = attrs - self._short_opts: List[str] = [] - self._long_opts: List[str] = [] + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] try: self.type = attrs["type"] except KeyError: @@ -279,7 +276,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: except KeyError: pass self._set_opt_strings(names) - dest: Optional[str] = attrs.get("dest") + dest: str | None = attrs.get("dest") if dest: self.dest = dest elif self._long_opts: @@ -291,7 +288,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: self.dest = "???" # Needed for the error repr. raise ArgumentError("need a long or short option", self) from e - def names(self) -> List[str]: + def names(self) -> list[str]: return self._short_opts + self._long_opts def attrs(self) -> Mapping[str, Any]: @@ -335,7 +332,7 @@ def _set_opt_strings(self, opts: Sequence[str]) -> None: self._long_opts.append(opt) def __repr__(self) -> str: - args: List[str] = [] + args: list[str] = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -355,14 +352,14 @@ def __init__( self, name: str, description: str = "", - parser: Optional[Parser] = None, + parser: Parser | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self.name = name self.description = description - self.options: List[Argument] = [] + self.options: list[Argument] = [] self.parser = parser def addoption(self, *opts: str, **attrs: Any) -> None: @@ -391,7 +388,7 @@ def _addoption(self, *opts: str, **attrs: Any) -> None: option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: + def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: if not shortupper: for opt in option._short_opts: if opt[0] == "-" and opt[1].islower(): @@ -405,8 +402,8 @@ class MyOptionParser(argparse.ArgumentParser): def __init__( self, parser: Parser, - extra_info: Optional[Dict[str, Any]] = None, - prog: Optional[str] = None, + extra_info: dict[str, Any] | None = None, + prog: str | None = None, ) -> None: self._parser = parser super().__init__( @@ -433,8 +430,8 @@ def error(self, message: str) -> NoReturn: # Type ignored because typeshed has a very complex type in the superclass. def parse_args( # type: ignore self, - args: Optional[Sequence[str]] = None, - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str] | None = None, + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Allow splitting of positional arguments.""" parsed, unrecognized = self.parse_known_args(args, namespace) @@ -455,7 +452,7 @@ def parse_args( # type: ignore # disable long --argument abbreviations without breaking short flags. def _parse_optional( self, arg_string: str - ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: + ) -> tuple[argparse.Action | None, str, str | None] | None: if not arg_string: return None if arg_string[0] not in self.prefix_chars: @@ -507,7 +504,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: Optional[str] = getattr(action, "_formatted_action_invocation", None) + res: str | None = getattr(action, "_formatted_action_invocation", None) if res: return res options = orgstr.split(", ") @@ -516,7 +513,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - short_long: Dict[str, str] = {} + short_long: dict[str, str] = {} for option in options: if len(option) == 2 or option[2] == " ": continue diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py index 4031ea732f3..90108eca904 100644 --- a/src/_pytest/config/exceptions.py +++ b/src/_pytest/config/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import final diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 9909376de0f..ce4c990b810 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,13 +1,10 @@ +from __future__ import annotations + import os from pathlib import Path import sys -from typing import Dict from typing import Iterable -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import iniconfig @@ -32,7 +29,7 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: def load_config_dict_from_file( filepath: Path, -) -> Optional[Dict[str, Union[str, List[str]]]]: +) -> dict[str, str | list[str]] | None: """Load pytest configuration from the given file path, if supported. Return None if the file does not contain valid pytest configuration. @@ -77,7 +74,7 @@ def load_config_dict_from_file( # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), # however we need to convert all scalar values to str for compatibility with the rest # of the configuration system, which expects strings only. - def make_scalar(v: object) -> Union[str, List[str]]: + def make_scalar(v: object) -> str | list[str]: return v if isinstance(v, list) else str(v) return {k: make_scalar(v) for k, v in result.items()} @@ -88,7 +85,7 @@ def make_scalar(v: object) -> Union[str, List[str]]: def locate_config( invocation_dir: Path, args: Iterable[Path], -) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]: """Search in the list of arguments for a valid ini-file for pytest, and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ @@ -101,7 +98,7 @@ def locate_config( args = [x for x in args if not str(x).startswith("-")] if not args: args = [invocation_dir] - found_pyproject_toml: Optional[Path] = None + found_pyproject_toml: Path | None = None for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): @@ -122,7 +119,7 @@ def get_common_ancestor( invocation_dir: Path, paths: Iterable[Path], ) -> Path: - common_ancestor: Optional[Path] = None + common_ancestor: Path | None = None for path in paths: if not path.exists(): continue @@ -144,7 +141,7 @@ def get_common_ancestor( return common_ancestor -def get_dirs_from_args(args: Iterable[str]) -> List[Path]: +def get_dirs_from_args(args: Iterable[str]) -> list[Path]: def is_option(x: str) -> bool: return x.startswith("-") @@ -171,11 +168,11 @@ def get_dir_from_path(path: Path) -> Path: def determine_setup( *, - inifile: Optional[str], + inifile: str | None, args: Sequence[str], - rootdir_cmd_arg: Optional[str], + rootdir_cmd_arg: str | None, invocation_dir: Path, -) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path, Path | None, dict[str, str | list[str]]]: """Determine the rootdir, inifile and ini configuration values from the command line arguments. @@ -192,7 +189,7 @@ def determine_setup( dirs = get_dirs_from_args(args) if inifile: inipath_ = absolutepath(inifile) - inipath: Optional[Path] = inipath_ + inipath: Path | None = inipath_ inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: rootdir = inipath_.parent diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index eacb2836d6c..3e1463fff26 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -2,6 +2,8 @@ # ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" +from __future__ import annotations + import argparse import functools import sys @@ -9,11 +11,6 @@ from typing import Any from typing import Callable from typing import Generator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type -from typing import Union import unittest from _pytest import outcomes @@ -30,7 +27,7 @@ from _pytest.runner import CallInfo -def _validate_usepdb_cls(value: str) -> Tuple[str, str]: +def _validate_usepdb_cls(value: str) -> tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") @@ -95,22 +92,22 @@ def fin() -> None: class pytestPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: Optional[PytestPluginManager] = None - _config: Optional[Config] = None - _saved: List[ - Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]] + _pluginmanager: PytestPluginManager | None = None + _config: Config | None = None + _saved: list[ + tuple[Callable[..., None], PytestPluginManager | None, Config | None] ] = [] _recursive_debug = 0 - _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None + _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None @classmethod - def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: + def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): + def _import_pdb_cls(cls, capman: CaptureManager | None): if not cls._config: import pdb @@ -149,7 +146,7 @@ def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): import _pytest.config class PytestPdbWrapper(pdb_cls): @@ -238,7 +235,7 @@ def _init_pdb(cls, method, *args, **kwargs): import _pytest.config if cls._pluginmanager is None: - capman: Optional[CaptureManager] = None + capman: CaptureManager | None = None else: capman = cls._pluginmanager.getplugin("capturemanager") if capman: @@ -281,7 +278,7 @@ def set_trace(cls, *args, **kwargs) -> None: class PdbInvoke: def pytest_exception_interact( - self, node: Node, call: "CallInfo[Any]", report: BaseReport + self, node: Node, call: CallInfo[Any], report: BaseReport ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 10811d158aa..a605c24e58f 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -9,6 +9,8 @@ in case of warnings which need to format their messages. """ +from __future__ import annotations + from warnings import warn from _pytest.warning_types import PytestDeprecationWarning diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index e61694d553f..cb46d9a3bb5 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Discover and run doctests in modules and test files.""" +from __future__ import annotations + import bdb from contextlib import contextmanager import functools @@ -13,17 +15,11 @@ import types from typing import Any from typing import Callable -from typing import Dict from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union import warnings from _pytest import outcomes @@ -67,7 +63,7 @@ # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None +CHECKER_CLASS: type[doctest.OutputChecker] | None = None def pytest_addoption(parser: Parser) -> None: @@ -129,7 +125,7 @@ def pytest_unconfigure() -> None: def pytest_collect_file( file_path: Path, parent: Collector, -) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: +) -> DoctestModule | DoctestTextfile | None: config = parent.config if file_path.suffix == ".py": if config.option.doctestmodules and not any( @@ -161,7 +157,7 @@ def _is_main_py(path: Path) -> bool: class ReprFailDoctest(TerminalRepr): def __init__( - self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] ) -> None: self.reprlocation_lines = reprlocation_lines @@ -173,12 +169,12 @@ def toterminal(self, tw: TerminalWriter) -> None: class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> Type["doctest.DocTestRunner"]: +def _init_runner_class() -> type[doctest.DocTestRunner]: import doctest class PytestDoctestRunner(doctest.DebugRunner): @@ -190,8 +186,8 @@ class PytestDoctestRunner(doctest.DebugRunner): def __init__( self, - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: @@ -201,8 +197,8 @@ def __init__( def report_failure( self, out, - test: "doctest.DocTest", - example: "doctest.Example", + test: doctest.DocTest, + example: doctest.Example, got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) @@ -214,9 +210,9 @@ def report_failure( def report_unexpected_exception( self, out, - test: "doctest.DocTest", - example: "doctest.Example", - exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + test: doctest.DocTest, + example: doctest.Example, + exc_info: tuple[type[BaseException], BaseException, types.TracebackType], ) -> None: if isinstance(exc_info[1], OutcomeException): raise exc_info[1] @@ -232,11 +228,11 @@ def report_unexpected_exception( def _get_runner( - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> "doctest.DocTestRunner": +) -> doctest.DocTestRunner: # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -255,9 +251,9 @@ class DoctestItem(Item): def __init__( self, name: str, - parent: "Union[DoctestTextfile, DoctestModule]", - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", + parent: DoctestTextfile | DoctestModule, + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, ) -> None: super().__init__(name, parent) self.runner = runner @@ -274,18 +270,18 @@ def __init__( @classmethod def from_parent( # type: ignore[override] cls, - parent: "Union[DoctestTextfile, DoctestModule]", + parent: DoctestTextfile | DoctestModule, *, name: str, - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", - ) -> "Self": + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, + ) -> Self: # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] def setup(self) -> None: @@ -298,7 +294,7 @@ def setup(self) -> None: def runtest(self) -> None: _check_all_skipped(self.dtest) self._disable_output_capturing_for_darwin() - failures: List[doctest.DocTestFailure] = [] + failures: list[doctest.DocTestFailure] = [] # Type ignored because we change the type of `out` from what # doctest expects. self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] @@ -320,12 +316,12 @@ def _disable_output_capturing_for_darwin(self) -> None: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: import doctest - failures: Optional[ - Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = None + failures: ( + Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None + ) = None if isinstance( excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): @@ -381,11 +377,11 @@ def repr_failure( # type: ignore[override] reprlocation_lines.append((reprlocation, lines)) return ReprFailDoctest(reprlocation_lines) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: return self.path, self.dtest.lineno, f"[doctest] {self.name}" -def _get_flag_lookup() -> Dict[str, int]: +def _get_flag_lookup() -> dict[str, int]: import doctest return dict( @@ -451,7 +447,7 @@ def collect(self) -> Iterable[DoctestItem]: ) -def _check_all_skipped(test: "doctest.DocTest") -> None: +def _check_all_skipped(test: doctest.DocTest) -> None: """Raise pytest.skip() if all examples in the given DocTest have the SKIP option set.""" import doctest @@ -477,7 +473,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]: real_unwrap = inspect.unwrap def _mock_aware_unwrap( - func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None ) -> Any: try: if stop is None or stop is _is_mocked: @@ -594,7 +590,7 @@ def _from_module(self, module, object): ) -def _init_checker_class() -> Type["doctest.OutputChecker"]: +def _init_checker_class() -> type[doctest.OutputChecker]: import doctest import re @@ -662,8 +658,8 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return got offset = 0 for w, g in zip(wants, gots): - fraction: Optional[str] = w.group("fraction") - exponent: Optional[str] = w.group("exponent1") + fraction: str | None = w.group("fraction") + exponent: str | None = w.group("exponent1") if exponent is None: exponent = w.group("exponent2") precision = 0 if fraction is None else len(fraction) @@ -682,7 +678,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return LiteralsOutputChecker -def _get_checker() -> "doctest.OutputChecker": +def _get_checker() -> doctest.OutputChecker: """Return a doctest.OutputChecker subclass that supports some additional options: @@ -741,7 +737,7 @@ def _get_report_choice(key: str) -> int: @fixture(scope="session") -def doctest_namespace() -> Dict[str, Any]: +def doctest_namespace() -> dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index 083bcb83739..07e60f03fc9 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from typing import Generator diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 383084e07f1..0151a4d9c86 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import abc from collections import defaultdict from collections import deque @@ -20,7 +22,6 @@ from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import Mapping from typing import MutableMapping from typing import NoReturn @@ -28,7 +29,6 @@ from typing import OrderedDict from typing import overload from typing import Sequence -from typing import Set from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar @@ -113,18 +113,18 @@ @dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): - cached_result: "_FixtureCachedResult[FixtureValue]" + cached_result: _FixtureCachedResult[FixtureValue] _scope: Scope -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._fixturemanager = FixtureManager(session) def get_scope_package( node: nodes.Item, - fixturedef: "FixtureDef[object]", -) -> Optional[nodes.Node]: + fixturedef: FixtureDef[object], +) -> nodes.Node | None: from _pytest.python import Package for parent in node.iter_parents(): @@ -133,7 +133,7 @@ def get_scope_package( return node.session -def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: +def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: import _pytest.python if scope is Scope.Function: @@ -152,7 +152,7 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: assert_never(scope) -def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: +def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: """Return fixturemarker or None if it doesn't exist or raised exceptions.""" return cast( @@ -171,8 +171,8 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: class FixtureArgKey: argname: str param_index: int - scoped_item_path: Optional[Path] - item_cls: Optional[type] + scoped_item_path: Path | None + item_cls: type | None _V = TypeVar("_V") @@ -212,10 +212,10 @@ def get_parametrized_fixture_argkeys( yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls) -def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: - argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {} - items_by_argkey: Dict[ - Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]] +def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: + argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {} + items_by_argkey: dict[ + Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]] ] = {} for scope in HIGH_SCOPES: scoped_argkeys_by_item = argkeys_by_item[scope] = {} @@ -249,7 +249,7 @@ def reorder_items_atscope( scoped_items_by_argkey = items_by_argkey[scope] scoped_argkeys_by_item = argkeys_by_item[scope] - ignore: Set[FixtureArgKey] = set() + ignore: set[FixtureArgKey] = set() items_deque = deque(items) items_done: OrderedSet[nodes.Item] = {} while items_deque: @@ -309,19 +309,19 @@ class FuncFixtureInfo: __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") # Fixture names that the item requests directly by function parameters. - argnames: Tuple[str, ...] + argnames: tuple[str, ...] # Fixture names that the item immediately requires. These include # argnames + fixture names specified via usefixtures and via autouse=True in # fixture definitions. - initialnames: Tuple[str, ...] + initialnames: tuple[str, ...] # The transitive closure of the fixture names that the item requires. # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). - names_closure: List[str] + names_closure: list[str] # A map from a fixture name in the transitive closure to the FixtureDefs # matching the name which are applicable to this function. # There may be multiple overriding fixtures with the same name. The # sequence is ordered from furthest to closes to the function. - name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] + name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] def prune_dependency_tree(self) -> None: """Recompute names_closure from initialnames and name2fixturedefs. @@ -334,7 +334,7 @@ def prune_dependency_tree(self) -> None: tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure: Set[str] = set() + closure: set[str] = set() working_set = set(self.initialnames) while working_set: argname = working_set.pop() @@ -360,10 +360,10 @@ class FixtureRequest(abc.ABC): def __init__( self, - pyfuncitem: "Function", - fixturename: Optional[str], - arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]], - fixture_defs: Dict[str, "FixtureDef[Any]"], + pyfuncitem: Function, + fixturename: str | None, + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], + fixture_defs: dict[str, FixtureDef[Any]], *, _ispytest: bool = False, ) -> None: @@ -390,7 +390,7 @@ def __init__( self.param: Any @property - def _fixturemanager(self) -> "FixtureManager": + def _fixturemanager(self) -> FixtureManager: return self._pyfuncitem.session._fixturemanager @property @@ -406,13 +406,13 @@ def scope(self) -> _ScopeName: @abc.abstractmethod def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: raise NotImplementedError() @property - def fixturenames(self) -> List[str]: + def fixturenames(self) -> list[str]: """Names of all active fixtures in this request.""" result = list(self._pyfuncitem.fixturenames) result.extend(set(self._fixture_defs).difference(result)) @@ -477,7 +477,7 @@ def keywords(self) -> MutableMapping[str, Any]: return node.keywords @property - def session(self) -> "Session": + def session(self) -> Session: """Pytest session object.""" return self._pyfuncitem.session @@ -487,7 +487,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: the last test within the requesting test context finished execution.""" raise NotImplementedError() - def applymarker(self, marker: Union[str, MarkDecorator]) -> None: + def applymarker(self, marker: str | MarkDecorator) -> None: """Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker @@ -498,7 +498,7 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> NoReturn: + def raiseerror(self, msg: str | None) -> NoReturn: """Raise a FixtureLookupError exception. :param msg: @@ -535,7 +535,7 @@ def getfixturevalue(self, argname: str) -> Any: ) return fixturedef.cached_result[0] - def _iter_chain(self) -> Iterator["SubRequest"]: + def _iter_chain(self) -> Iterator[SubRequest]: """Yield all SubRequests in the chain, from self up. Note: does *not* yield the TopRequest. @@ -547,7 +547,7 @@ def _iter_chain(self) -> Iterator["SubRequest"]: def _get_active_fixturedef( self, argname: str - ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: + ) -> FixtureDef[object] | PseudoFixtureDef[object]: if argname == "request": cached_result = (self, [0], None) return PseudoFixtureDef(cached_result, Scope.Function) @@ -618,7 +618,7 @@ def _get_active_fixturedef( self._fixture_defs[argname] = fixturedef return fixturedef - def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None: + def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: """Check that this request is allowed to execute this fixturedef without a param.""" funcitem = self._pyfuncitem @@ -651,7 +651,7 @@ def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> N ) fail(msg, pytrace=False) - def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + def _get_fixturestack(self) -> list[FixtureDef[Any]]: values = [request._fixturedef for request in self._iter_chain()] values.reverse() return values @@ -661,7 +661,7 @@ def _get_fixturestack(self) -> List["FixtureDef[Any]"]: class TopRequest(FixtureRequest): """The type of the ``request`` fixture in a test function.""" - def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None: + def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: super().__init__( fixturename=None, pyfuncitem=pyfuncitem, @@ -676,7 +676,7 @@ def _scope(self) -> Scope: def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: # TopRequest always has function scope so always valid. @@ -710,7 +710,7 @@ def __init__( scope: Scope, param: Any, param_index: int, - fixturedef: "FixtureDef[object]", + fixturedef: FixtureDef[object], *, _ispytest: bool = False, ) -> None: @@ -740,7 +740,7 @@ def node(self): scope = self._scope if scope is Scope.Function: # This might also be a non-function Item despite its attribute name. - node: Optional[nodes.Node] = self._pyfuncitem + node: nodes.Node | None = self._pyfuncitem elif scope is Scope.Package: node = get_scope_package(self._pyfuncitem, self._fixturedef) else: @@ -753,7 +753,7 @@ def node(self): def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: if isinstance(requested_fixturedef, PseudoFixtureDef): @@ -774,7 +774,7 @@ def _check_scope( pytrace=False, ) - def _format_fixturedef_line(self, fixturedef: "FixtureDef[object]") -> str: + def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: factory = fixturedef.func path, lineno = getfslineno(factory) if isinstance(path, Path): @@ -791,15 +791,15 @@ class FixtureLookupError(LookupError): """Could not return a requested fixture (missing or invalid).""" def __init__( - self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + self, argname: str | None, request: FixtureRequest, msg: str | None = None ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() self.msg = msg - def formatrepr(self) -> "FixtureLookupErrorRepr": - tblines: List[str] = [] + def formatrepr(self) -> FixtureLookupErrorRepr: + tblines: list[str] = [] addline = tblines.append stack = [self.request._pyfuncitem.obj] stack.extend(map(lambda x: x.func, self.fixturestack)) @@ -847,11 +847,11 @@ def formatrepr(self) -> "FixtureLookupErrorRepr": class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: Union[str, "os.PathLike[str]"], + filename: str | os.PathLike[str], firstlineno: int, tblines: Sequence[str], errorstring: str, - argname: Optional[str], + argname: str | None, ) -> None: self.tblines = tblines self.errorstring = errorstring @@ -879,7 +879,7 @@ def toterminal(self, tw: TerminalWriter) -> None: def call_fixture_func( - fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs + fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: if is_generator(fixturefunc): fixturefunc = cast( @@ -950,14 +950,12 @@ class FixtureDef(Generic[FixtureValue]): def __init__( self, config: Config, - baseid: Optional[str], + baseid: str | None, argname: str, - func: "_FixtureFunc[FixtureValue]", - scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None], - params: Optional[Sequence[object]], - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + func: _FixtureFunc[FixtureValue], + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, + params: Sequence[object] | None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, *, _ispytest: bool = False, ) -> None: @@ -1003,8 +1001,8 @@ def __init__( self.argnames: Final = getfuncargnames(func, name=argname) # If the fixture was executed, the current value of the fixture. # Can change if the fixture is executed with different parameters. - self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: Final[List[Callable[[], object]]] = [] + self.cached_result: _FixtureCachedResult[FixtureValue] | None = None + self._finalizers: Final[list[Callable[[], object]]] = [] @property def scope(self) -> _ScopeName: @@ -1015,7 +1013,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - exceptions: List[BaseException] = [] + exceptions: list[BaseException] = [] while self._finalizers: fin = self._finalizers.pop() try: @@ -1099,7 +1097,7 @@ def __repr__(self) -> str: def resolve_fixture_function( fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> "_FixtureFunc[FixtureValue]": +) -> _FixtureFunc[FixtureValue]: """Get the actual callable that can be called to obtain the fixture value.""" fixturefunc = fixturedef.func @@ -1147,7 +1145,7 @@ def pytest_fixture_setup( def wrap_function_to_error_out_if_called_directly( function: FixtureFunction, - fixture_marker: "FixtureFunctionMarker", + fixture_marker: FixtureFunctionMarker, ) -> FixtureFunction: """Wrap the given fixture function so we can raise an error about it being called directly, instead of used as an argument in a test function.""" @@ -1173,13 +1171,11 @@ def result(*args, **kwargs): @final @dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] + scope: _ScopeName | Callable[[str, Config], _ScopeName] + params: tuple[object, ...] | None autouse: bool = False - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None - name: Optional[str] = None + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None + name: str | None = None _ispytest: dataclasses.InitVar[bool] = False @@ -1217,13 +1213,11 @@ def __call__(self, function: FixtureFunction) -> FixtureFunction: def fixture( fixture_function: FixtureFunction, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = ..., + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = ..., ) -> FixtureFunction: ... @@ -1231,27 +1225,23 @@ def fixture( def fixture( fixture_function: None = ..., *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = None, + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = None, ) -> FixtureFunctionMarker: ... def fixture( - fixture_function: Optional[FixtureFunction] = None, + fixture_function: FixtureFunction | None = None, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", - params: Optional[Iterable[object]] = None, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Iterable[object] | None = None, autouse: bool = False, - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - name: Optional[str] = None, -) -> Union[FixtureFunctionMarker, FixtureFunction]: + ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, + name: str | None = None, +) -> FixtureFunctionMarker | FixtureFunction: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1385,7 +1375,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.showfixtures: showfixtures(config) return 0 @@ -1395,7 +1385,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: +def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: """Return all direct parametrization arguments of a node, so we don't mistake them for fixtures. @@ -1404,7 +1394,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: These things are done later as well when dealing with parametrization so this could be improved. """ - parametrize_argnames: Set[str] = set() + parametrize_argnames: set[str] = set() for marker in node.iter_markers(name="parametrize"): if not marker.kwargs.get("indirect", False): p_argnames, _ = ParameterSet._parse_parametrize_args( @@ -1414,7 +1404,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: return parametrize_argnames -def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]: +def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: """De-duplicate the sequence of names while keeping the original order.""" # Ideally we would use a set, but it does not preserve insertion order. return tuple(dict.fromkeys(name for seq in seqs for name in seq)) @@ -1451,17 +1441,17 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - def __init__(self, session: "Session") -> None: + def __init__(self, session: Session) -> None: self.session = session self.config: Config = session.config # Maps a fixture name (argname) to all of the FixtureDefs in the test # suite/plugins defined with this name. Populated by parsefactories(). # TODO: The order of the FixtureDefs list of each arg is significant, # explain. - self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {} - self._holderobjseen: Final[Set[object]] = set() + self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[set[object]] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Final[Dict[str, List[str]]] = { + self._nodeid_autousenames: Final[dict[str, list[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") @@ -1469,8 +1459,8 @@ def __init__(self, session: "Session") -> None: def getfixtureinfo( self, node: nodes.Item, - func: Optional[Callable[..., object]], - cls: Optional[type], + func: Callable[..., object] | None, + cls: type | None, ) -> FuncFixtureInfo: """Calculate the :class:`FuncFixtureInfo` for an item. @@ -1541,9 +1531,9 @@ def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: def getfixtureclosure( self, parentnode: nodes.Node, - initialnames: Tuple[str, ...], + initialnames: tuple[str, ...], ignore_args: AbstractSet[str], - ) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: # Collect the closure of all fixtures, starting with the given # fixturenames as the initial set. As we have to visit all # factory definitions anyway, we also return an arg2fixturedefs @@ -1553,7 +1543,7 @@ def getfixtureclosure( fixturenames_closure = list(initialnames) - arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} lastlen = -1 while lastlen != len(fixturenames_closure): lastlen = len(fixturenames_closure) @@ -1580,7 +1570,7 @@ def sort_by_scope(arg_name: str) -> Scope: fixturenames_closure.sort(key=sort_by_scope, reverse=True) return fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc: "Metafunc") -> None: + def pytest_generate_tests(self, metafunc: Metafunc) -> None: """Generate new tests based on parametrized fixtures used by the given metafunc""" def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: @@ -1625,7 +1615,7 @@ def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: # Try next super fixture, if any. - def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: + def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: # Separate parametrized setups. items[:] = reorder_items(items) @@ -1633,15 +1623,11 @@ def _register_fixture( self, *, name: str, - func: "_FixtureFunc[object]", - nodeid: Optional[str], - scope: Union[ - Scope, _ScopeName, Callable[[str, Config], _ScopeName] - ] = "function", - params: Optional[Sequence[object]] = None, - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + func: _FixtureFunc[object], + nodeid: str | None, + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Sequence[object] | None = None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, autouse: bool = False, ) -> None: """Register a fixture @@ -1699,14 +1685,14 @@ def parsefactories( def parsefactories( self, node_or_obj: object, - nodeid: Optional[str], + nodeid: str | None, ) -> None: raise NotImplementedError() def parsefactories( self, - node_or_obj: Union[nodes.Node, object], - nodeid: Union[str, NotSetType, None] = NOTSET, + node_or_obj: nodes.Node | object, + nodeid: str | NotSetType | None = NOTSET, ) -> None: """Collect fixtures from a collection node or object. @@ -1764,7 +1750,7 @@ def parsefactories( def getfixturedefs( self, argname: str, node: nodes.Node - ) -> Optional[Sequence[FixtureDef[Any]]]: + ) -> Sequence[FixtureDef[Any]] | None: """Get FixtureDefs for a fixture name which are applicable to a given node. @@ -1791,7 +1777,7 @@ def _matchfactories( yield fixturedef -def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]: +def show_fixtures_per_test(config: Config) -> int | ExitCode: from _pytest.main import wrap_session return wrap_session(config, _show_fixtures_per_test) @@ -1809,7 +1795,7 @@ def _pretty_fixture_path(invocation_dir: Path, func) -> str: return bestrelpath(invocation_dir, loc) -def _show_fixtures_per_test(config: Config, session: "Session") -> None: +def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1842,7 +1828,7 @@ def write_fixture(fixture_def: FixtureDef[object]) -> None: def write_item(item: nodes.Item) -> None: # Not all items have _fixtureinfo attribute. - info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) + info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None) if info is None or not info.name2fixturedefs: # This test item does not use any fixtures. return @@ -1862,13 +1848,13 @@ def write_item(item: nodes.Item) -> None: write_item(session_item) -def showfixtures(config: Config) -> Union[int, ExitCode]: +def showfixtures(config: Config) -> int | ExitCode: from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) -def _showfixtures_main(config: Config, session: "Session") -> None: +def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1879,7 +1865,7 @@ def _showfixtures_main(config: Config, session: "Session") -> None: fm = session._fixturemanager available = [] - seen: Set[Tuple[str, str]] = set() + seen: set[tuple[str, str]] = set() for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None diff --git a/src/_pytest/freeze_support.py b/src/_pytest/freeze_support.py index e03a6d1753d..2ba6f9b8bcc 100644 --- a/src/_pytest/freeze_support.py +++ b/src/_pytest/freeze_support.py @@ -1,13 +1,13 @@ """Provides a function to report all internal modules for using freezing tools.""" +from __future__ import annotations + import types from typing import Iterator -from typing import List -from typing import Union -def freeze_includes() -> List[str]: +def freeze_includes() -> list[str]: """Return a list of module names used by pytest that should be included by cx_freeze.""" import _pytest @@ -17,7 +17,7 @@ def freeze_includes() -> List[str]: def _iter_all_modules( - package: Union[str, types.ModuleType], + package: str | types.ModuleType, prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 68e0bd881a7..e7a1afab526 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -1,13 +1,12 @@ # mypy: allow-untyped-defs """Version info, help messages, tracing configuration.""" +from __future__ import annotations + from argparse import Action import os import sys from typing import Generator -from typing import List -from typing import Optional -from typing import Union from _pytest.config import Config from _pytest.config import ExitCode @@ -147,7 +146,7 @@ def showversion(config: Config) -> None: sys.stdout.write(f"pytest {pytest.__version__}\n") -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.version > 0: showversion(config) return 0 @@ -162,7 +161,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: def showhelp(config: Config) -> None: import textwrap - reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( "terminalreporter" ) assert reporter is not None @@ -239,7 +238,7 @@ def showhelp(config: Config) -> None: conftest_options = [("pytest_plugins", "list of plugin names to load")] -def getpluginversioninfo(config: Config) -> List[str]: +def getpluginversioninfo(config: Config) -> list[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -251,7 +250,7 @@ def getpluginversioninfo(config: Config) -> List[str]: return lines -def pytest_report_header(config: Config) -> List[str]: +def pytest_report_header(config: Config) -> list[str]: lines = [] if config.option.debug or config.option.traceconfig: lines.append(f"using: pytest-{pytest.__version__}") diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index c7f9d036c33..13f4fddbddb 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -3,16 +3,13 @@ """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" +from __future__ import annotations + from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Mapping -from typing import Optional from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from pluggy import HookspecMarker @@ -57,7 +54,7 @@ @hookspec(historic=True) -def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: +def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: """Called at plugin registration time to allow adding new hooks via a call to :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. @@ -76,9 +73,9 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: @hookspec(historic=True) def pytest_plugin_registered( - plugin: "_PluggyPlugin", + plugin: _PluggyPlugin, plugin_name: str, - manager: "PytestPluginManager", + manager: PytestPluginManager, ) -> None: """A new pytest plugin got registered. @@ -100,7 +97,7 @@ def pytest_plugin_registered( @hookspec(historic=True) -def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: """Register argparse-style options and ini-style config values, called once at the beginning of a test run. @@ -141,7 +138,7 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> @hookspec(historic=True) -def pytest_configure(config: "Config") -> None: +def pytest_configure(config: Config) -> None: """Allow plugins and conftest files to perform initial configuration. .. note:: @@ -166,8 +163,8 @@ def pytest_configure(config: "Config") -> None: @hookspec(firstresult=True) def pytest_cmdline_parse( - pluginmanager: "PytestPluginManager", args: List[str] -) -> Optional["Config"]: + pluginmanager: PytestPluginManager, args: list[str] +) -> Config | None: """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. @@ -189,7 +186,7 @@ def pytest_cmdline_parse( def pytest_load_initial_conftests( - early_config: "Config", parser: "Parser", args: List[str] + early_config: Config, parser: Parser, args: list[str] ) -> None: """Called to implement the loading of :ref:`initial conftest files ` ahead of command line option parsing. @@ -206,7 +203,7 @@ def pytest_load_initial_conftests( @hookspec(firstresult=True) -def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: +def pytest_cmdline_main(config: Config) -> ExitCode | int | None: """Called for performing the main command line action. The default implementation will invoke the configure hooks and @@ -230,7 +227,7 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: @hookspec(firstresult=True) -def pytest_collection(session: "Session") -> Optional[object]: +def pytest_collection(session: Session) -> object | None: """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. @@ -272,7 +269,7 @@ def pytest_collection(session: "Session") -> Optional[object]: def pytest_collection_modifyitems( - session: "Session", config: "Config", items: List["Item"] + session: Session, config: Config, items: list[Item] ) -> None: """Called after collection has been performed. May filter or re-order the items in-place. @@ -288,7 +285,7 @@ def pytest_collection_modifyitems( """ -def pytest_collection_finish(session: "Session") -> None: +def pytest_collection_finish(session: Session) -> None: """Called after collection has been performed and modified. :param session: The pytest session object. @@ -309,8 +306,8 @@ def pytest_collection_finish(session: "Session") -> None: }, ) def pytest_ignore_collect( - collection_path: Path, path: "LEGACY_PATH", config: "Config" -) -> Optional[bool]: + collection_path: Path, path: LEGACY_PATH, config: Config +) -> bool | None: """Return ``True`` to ignore this path for collection. Return ``None`` to let other plugins ignore the path for collection. @@ -343,7 +340,7 @@ def pytest_ignore_collect( @hookspec(firstresult=True) -def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]": +def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: """Create a :class:`~pytest.Collector` for the given directory, or None if not relevant. @@ -379,8 +376,8 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle }, ) def pytest_collect_file( - file_path: Path, path: "LEGACY_PATH", parent: "Collector" -) -> "Optional[Collector]": + file_path: Path, path: LEGACY_PATH, parent: Collector +) -> Collector | None: """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. For best results, the returned collector should be a subclass of @@ -407,7 +404,7 @@ def pytest_collect_file( # logging hooks for collection -def pytest_collectstart(collector: "Collector") -> None: +def pytest_collectstart(collector: Collector) -> None: """Collector starts collecting. :param collector: @@ -422,7 +419,7 @@ def pytest_collectstart(collector: "Collector") -> None: """ -def pytest_itemcollected(item: "Item") -> None: +def pytest_itemcollected(item: Item) -> None: """We just collected a test item. :param item: @@ -436,7 +433,7 @@ def pytest_itemcollected(item: "Item") -> None: """ -def pytest_collectreport(report: "CollectReport") -> None: +def pytest_collectreport(report: CollectReport) -> None: """Collector finished collecting. :param report: @@ -451,7 +448,7 @@ def pytest_collectreport(report: "CollectReport") -> None: """ -def pytest_deselected(items: Sequence["Item"]) -> None: +def pytest_deselected(items: Sequence[Item]) -> None: """Called for deselected test items, e.g. by keyword. May be called multiple times. @@ -467,7 +464,7 @@ def pytest_deselected(items: Sequence["Item"]) -> None: @hookspec(firstresult=True) -def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]": +def pytest_make_collect_report(collector: Collector) -> CollectReport | None: """Perform :func:`collector.collect() ` and return a :class:`~pytest.CollectReport`. @@ -499,8 +496,8 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor }, ) def pytest_pycollect_makemodule( - module_path: Path, path: "LEGACY_PATH", parent -) -> Optional["Module"]: + module_path: Path, path: LEGACY_PATH, parent +) -> Module | None: """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. @@ -529,8 +526,8 @@ def pytest_pycollect_makemodule( @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: + collector: Module | Class, name: str, obj: object +) -> None | Item | Collector | list[Item | Collector]: """Return a custom item/collector for a Python object in a module, or None. Stops at first non-None result, see :ref:`firstresult`. @@ -554,7 +551,7 @@ def pytest_pycollect_makeitem( @hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. @@ -571,7 +568,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """ -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: """Generate (multiple) parametrized calls to a test function. :param metafunc: @@ -587,9 +584,7 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None: @hookspec(firstresult=True) -def pytest_make_parametrize_id( - config: "Config", val: object, argname: str -) -> Optional[str]: +def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls, or None if the hook doesn't know about ``val``. @@ -615,7 +610,7 @@ def pytest_make_parametrize_id( @hookspec(firstresult=True) -def pytest_runtestloop(session: "Session") -> Optional[object]: +def pytest_runtestloop(session: Session) -> object | None: """Perform the main runtest loop (after collection finished). The default hook implementation performs the runtest protocol for all items @@ -641,9 +636,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]: @hookspec(firstresult=True) -def pytest_runtest_protocol( - item: "Item", nextitem: "Optional[Item]" -) -> Optional[object]: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: """Perform the runtest protocol for a single test item. The default runtest protocol is this (see individual hooks for full details): @@ -683,9 +676,7 @@ def pytest_runtest_protocol( """ -def pytest_runtest_logstart( - nodeid: str, location: Tuple[str, Optional[int], str] -) -> None: +def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: """Called at the start of running the runtest protocol for a single item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. @@ -704,7 +695,7 @@ def pytest_runtest_logstart( def pytest_runtest_logfinish( - nodeid: str, location: Tuple[str, Optional[int], str] + nodeid: str, location: tuple[str, int | None, str] ) -> None: """Called at the end of running the runtest protocol for a single item. @@ -723,7 +714,7 @@ def pytest_runtest_logfinish( """ -def pytest_runtest_setup(item: "Item") -> None: +def pytest_runtest_setup(item: Item) -> None: """Called to perform the setup phase for a test item. The default implementation runs ``setup()`` on ``item`` and all of its @@ -742,7 +733,7 @@ def pytest_runtest_setup(item: "Item") -> None: """ -def pytest_runtest_call(item: "Item") -> None: +def pytest_runtest_call(item: Item) -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. @@ -758,7 +749,7 @@ def pytest_runtest_call(item: "Item") -> None: """ -def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: """Called to perform the teardown phase for a test item. The default implementation runs the finalizers and calls ``teardown()`` @@ -783,9 +774,7 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: @hookspec(firstresult=True) -def pytest_runtest_makereport( - item: "Item", call: "CallInfo[None]" -) -> Optional["TestReport"]: +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: """Called to create a :class:`~pytest.TestReport` for each of the setup, call and teardown runtest phases of a test item. @@ -804,7 +793,7 @@ def pytest_runtest_makereport( """ -def pytest_runtest_logreport(report: "TestReport") -> None: +def pytest_runtest_logreport(report: TestReport) -> None: """Process the :class:`~pytest.TestReport` produced for each of the setup, call and teardown runtest phases of an item. @@ -820,9 +809,9 @@ def pytest_runtest_logreport(report: "TestReport") -> None: @hookspec(firstresult=True) def pytest_report_to_serializable( - config: "Config", - report: Union["CollectReport", "TestReport"], -) -> Optional[Dict[str, Any]]: + config: Config, + report: CollectReport | TestReport, +) -> dict[str, Any] | None: """Serialize the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON. @@ -839,9 +828,9 @@ def pytest_report_to_serializable( @hookspec(firstresult=True) def pytest_report_from_serializable( - config: "Config", - data: Dict[str, Any], -) -> Optional[Union["CollectReport", "TestReport"]]: + config: Config, + data: dict[str, Any], +) -> CollectReport | TestReport | None: """Restore a report object previously serialized with :hook:`pytest_report_to_serializable`. @@ -862,8 +851,8 @@ def pytest_report_from_serializable( @hookspec(firstresult=True) def pytest_fixture_setup( - fixturedef: "FixtureDef[Any]", request: "SubRequest" -) -> Optional[object]: + fixturedef: FixtureDef[Any], request: SubRequest +) -> object | None: """Perform fixture setup execution. :param fixturedef: @@ -890,7 +879,7 @@ def pytest_fixture_setup( def pytest_fixture_post_finalizer( - fixturedef: "FixtureDef[Any]", request: "SubRequest" + fixturedef: FixtureDef[Any], request: SubRequest ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not @@ -915,7 +904,7 @@ def pytest_fixture_post_finalizer( # ------------------------------------------------------------------------- -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. @@ -929,8 +918,8 @@ def pytest_sessionstart(session: "Session") -> None: def pytest_sessionfinish( - session: "Session", - exitstatus: Union[int, "ExitCode"], + session: Session, + exitstatus: int | ExitCode, ) -> None: """Called after whole test run finished, right before returning the exit status to the system. @@ -944,7 +933,7 @@ def pytest_sessionfinish( """ -def pytest_unconfigure(config: "Config") -> None: +def pytest_unconfigure(config: Config) -> None: """Called before test process is exited. :param config: The pytest config object. @@ -962,8 +951,8 @@ def pytest_unconfigure(config: "Config") -> None: def pytest_assertrepr_compare( - config: "Config", op: str, left: object, right: object -) -> Optional[List[str]]: + config: Config, op: str, left: object, right: object +) -> list[str] | None: """Return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list @@ -984,7 +973,7 @@ def pytest_assertrepr_compare( """ -def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None: +def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: """Called whenever an assertion passes. .. versionadded:: 5.0 @@ -1031,8 +1020,8 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No }, ) def pytest_report_header( # type:ignore[empty-body] - config: "Config", start_path: Path, startdir: "LEGACY_PATH" -) -> Union[str, List[str]]: + config: Config, start_path: Path, startdir: LEGACY_PATH +) -> str | list[str]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param config: The pytest config object. @@ -1066,11 +1055,11 @@ def pytest_report_header( # type:ignore[empty-body] }, ) def pytest_report_collectionfinish( # type:ignore[empty-body] - config: "Config", + config: Config, start_path: Path, - startdir: "LEGACY_PATH", - items: Sequence["Item"], -) -> Union[str, List[str]]: + startdir: LEGACY_PATH, + items: Sequence[Item], +) -> str | list[str]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -1104,8 +1093,8 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] @hookspec(firstresult=True) def pytest_report_teststatus( # type:ignore[empty-body] - report: Union["CollectReport", "TestReport"], config: "Config" -) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": + report: CollectReport | TestReport, config: Config +) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: """Return result-category, shortletter and verbose word for status reporting. @@ -1136,9 +1125,9 @@ def pytest_report_teststatus( # type:ignore[empty-body] def pytest_terminal_summary( - terminalreporter: "TerminalReporter", - exitstatus: "ExitCode", - config: "Config", + terminalreporter: TerminalReporter, + exitstatus: ExitCode, + config: Config, ) -> None: """Add a section to terminal summary reporting. @@ -1158,10 +1147,10 @@ def pytest_terminal_summary( @hookspec(historic=True) def pytest_warning_recorded( - warning_message: "warnings.WarningMessage", - when: "Literal['config', 'collect', 'runtest']", + warning_message: warnings.WarningMessage, + when: Literal["config", "collect", "runtest"], nodeid: str, - location: Optional[Tuple[str, int, str]], + location: tuple[str, int, str] | None, ) -> None: """Process a warning captured by the internal pytest warnings plugin. @@ -1202,8 +1191,8 @@ def pytest_warning_recorded( def pytest_markeval_namespace( # type:ignore[empty-body] - config: "Config", -) -> Dict[str, Any]: + config: Config, +) -> dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -1231,9 +1220,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body] def pytest_internalerror( - excrepr: "ExceptionRepr", - excinfo: "ExceptionInfo[BaseException]", -) -> Optional[bool]: + excrepr: ExceptionRepr, + excinfo: ExceptionInfo[BaseException], +) -> bool | None: """Called for internal errors. Return True to suppress the fallback handling of printing an @@ -1250,7 +1239,7 @@ def pytest_internalerror( def pytest_keyboard_interrupt( - excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", + excinfo: ExceptionInfo[KeyboardInterrupt | Exit], ) -> None: """Called for keyboard interrupt. @@ -1264,9 +1253,9 @@ def pytest_keyboard_interrupt( def pytest_exception_interact( - node: Union["Item", "Collector"], - call: "CallInfo[Any]", - report: Union["CollectReport", "TestReport"], + node: Item | Collector, + call: CallInfo[Any], + report: CollectReport | TestReport, ) -> None: """Called when an exception was raised which can potentially be interactively handled. @@ -1295,7 +1284,7 @@ def pytest_exception_interact( """ -def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called upon pdb.set_trace(). Can be used by plugins to take special action just before the python @@ -1311,7 +1300,7 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: """ -def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 011af6100e9..a525f1d14de 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -8,18 +8,15 @@ https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ +from __future__ import annotations + from datetime import datetime import functools import os import platform import re from typing import Callable -from typing import Dict -from typing import List from typing import Match -from typing import Optional -from typing import Tuple -from typing import Union import xml.etree.ElementTree as ET from _pytest import nodes @@ -89,15 +86,15 @@ def merge_family(left, right) -> None: class _NodeReporter: - def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: + def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: self.id = nodeid self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family self.duration = 0.0 - self.properties: List[Tuple[str, str]] = [] - self.nodes: List[ET.Element] = [] - self.attrs: Dict[str, str] = {} + self.properties: list[tuple[str, str]] = [] + self.nodes: list[ET.Element] = [] + self.attrs: dict[str, str] = {} def append(self, node: ET.Element) -> None: self.xml.add_stats(node.tag) @@ -109,7 +106,7 @@ def add_property(self, name: str, value: object) -> None: def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self) -> Optional[ET.Element]: + def make_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.properties: properties = ET.Element("properties") @@ -124,7 +121,7 @@ def record_testreport(self, testreport: TestReport) -> None: classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs: Dict[str, str] = { + attrs: dict[str, str] = { "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], @@ -156,7 +153,7 @@ def to_xml(self) -> ET.Element: testcase.extend(self.nodes) return testcase - def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None: + def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: node = ET.Element(tag, message=message) node.text = bin_xml_escape(data) self.append(node) @@ -201,7 +198,7 @@ def append_failure(self, report: TestReport) -> None: self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( + reprcrash: ReprFileLocation | None = getattr( report.longrepr, "reprcrash", None ) if reprcrash is not None: @@ -221,9 +218,7 @@ def append_collect_skipped(self, report: TestReport) -> None: def append_error(self, report: TestReport) -> None: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( - report.longrepr, "reprcrash", None - ) + reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) if reprcrash is not None: reason = reprcrash.message else: @@ -451,7 +446,7 @@ def pytest_unconfigure(config: Config) -> None: config.pluginmanager.unregister(xml) -def mangle_test_address(address: str) -> List[str]: +def mangle_test_address(address: str) -> list[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") # Convert file path to dotted path. @@ -466,7 +461,7 @@ class LogXML: def __init__( self, logfile, - prefix: Optional[str], + prefix: str | None, suite_name: str = "pytest", logging: str = "no", report_duration: str = "total", @@ -481,17 +476,15 @@ def __init__( self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats: Dict[str, int] = dict.fromkeys( + self.stats: dict[str, int] = dict.fromkeys( ["error", "passed", "failure", "skipped"], 0 ) - self.node_reporters: Dict[ - Tuple[Union[str, TestReport], object], _NodeReporter - ] = {} - self.node_reporters_ordered: List[_NodeReporter] = [] - self.global_properties: List[Tuple[str, str]] = [] + self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} + self.node_reporters_ordered: list[_NodeReporter] = [] + self.global_properties: list[tuple[str, str]] = [] # List of reports that failed on call but teardown is pending. - self.open_reports: List[TestReport] = [] + self.open_reports: list[TestReport] = [] self.cnt_double_fail_tests = 0 # Replaces convenience family with real family. @@ -510,8 +503,8 @@ def finalize(self, report: TestReport) -> None: if reporter is not None: reporter.finalize() - def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: - nodeid: Union[str, TestReport] = getattr(report, "nodeid", report) + def node_reporter(self, report: TestReport | str) -> _NodeReporter: + nodeid: str | TestReport = getattr(report, "nodeid", report) # Local hack to handle xdist report order. workernode = getattr(report, "node", None) @@ -691,7 +684,7 @@ def add_global_property(self, name: str, value: object) -> None: _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) - def _get_global_properties_node(self) -> Optional[ET.Element]: + def _get_global_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.global_properties: properties = ET.Element("properties") diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index d9de65b1a53..61476d68932 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,16 +1,15 @@ # mypy: allow-untyped-defs """Add backward compatibility support for the legacy py path type.""" +from __future__ import annotations + import dataclasses from pathlib import Path import shlex import subprocess from typing import Final from typing import final -from typing import List -from typing import Optional from typing import TYPE_CHECKING -from typing import Union from iniconfig import SectionWrapper @@ -50,8 +49,8 @@ class Testdir: __test__ = False - CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN - TimeoutExpired: "Final" = Pytester.TimeoutExpired + CLOSE_STDIN: Final = Pytester.CLOSE_STDIN + TimeoutExpired: Final = Pytester.TimeoutExpired def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -145,7 +144,7 @@ def copy_example(self, name=None) -> LEGACY_PATH: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: + def getnode(self, config: Config, arg) -> Item | Collector | None: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -153,7 +152,7 @@ def getpathnode(self, path): """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: list[Item | Collector]) -> list[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -205,9 +204,7 @@ def getmodulecol(self, source, configargs=(), withinit=False): source, configargs=configargs, withinit=withinit ) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """See :meth:`Pytester.collect_by_name`.""" return self._pytester.collect_by_name(modcol, name) @@ -238,13 +235,11 @@ def runpytest_subprocess(self, *args, timeout=None) -> RunResult: """See :meth:`Pytester.runpytest_subprocess`.""" return self._pytester.runpytest_subprocess(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn_pytest`.""" return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn`.""" return self._pytester.spawn(cmd, expect_timeout=expect_timeout) @@ -374,7 +369,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: +def Config_inifile(self: Config) -> LEGACY_PATH | None: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. @@ -394,9 +389,7 @@ def Session_startdir(self: Session) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config__getini_unknown_type( - self, name: str, type: str, value: Union[str, List[str]] -): +def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): if type == "pathlist": # TODO: This assert is probably not valid in all cases. assert self.inipath is not None diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index c9139d369ef..fe3be060fdd 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Access and control log capturing.""" +from __future__ import annotations + from contextlib import contextmanager from contextlib import nullcontext from datetime import datetime @@ -22,12 +24,8 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from _pytest import nodes from _pytest._io import TerminalWriter @@ -68,7 +66,7 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: + def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: if datefmt and "%f" in datefmt: ct = self.converter(record.created) tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) @@ -100,7 +98,7 @@ def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._terminalwriter = terminalwriter self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: Dict[int, str] = {} + self._level_to_fmt_mapping: dict[int, str] = {} for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): self.add_color_level(level, *color_opts) @@ -148,12 +146,12 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: + def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: super().__init__(fmt) self._auto_indent = self._get_auto_indent(auto_indent) @staticmethod - def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: + def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: """Determine the current auto indentation setting. Specify auto indent behavior (on/off/fixed) by passing in @@ -348,7 +346,7 @@ class catching_logs(Generic[_HandlerType]): __slots__ = ("handler", "level", "orig_level") - def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: + def __init__(self, handler: _HandlerType, level: int | None = None) -> None: self.handler = handler self.level = level @@ -364,9 +362,9 @@ def __enter__(self) -> _HandlerType: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: root_logger = logging.getLogger() if self.level is not None: @@ -380,7 +378,7 @@ class LogCaptureHandler(logging_StreamHandler): def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) - self.records: List[logging.LogRecord] = [] + self.records: list[logging.LogRecord] = [] def emit(self, record: logging.LogRecord) -> None: """Keep the log records in a list in addition to the log text.""" @@ -411,10 +409,10 @@ class LogCaptureFixture: def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._item = item - self._initial_handler_level: Optional[int] = None + self._initial_handler_level: int | None = None # Dict of log name -> log level. - self._initial_logger_levels: Dict[Optional[str], int] = {} - self._initial_disabled_logging_level: Optional[int] = None + self._initial_logger_levels: dict[str | None, int] = {} + self._initial_disabled_logging_level: int | None = None def _finalize(self) -> None: """Finalize the fixture. @@ -439,7 +437,7 @@ def handler(self) -> LogCaptureHandler: def get_records( self, when: Literal["setup", "call", "teardown"] - ) -> List[logging.LogRecord]: + ) -> list[logging.LogRecord]: """Get the logging records for one of the possible test phases. :param when: @@ -458,12 +456,12 @@ def text(self) -> str: return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self) -> List[logging.LogRecord]: + def records(self) -> list[logging.LogRecord]: """The list of log records.""" return self.handler.records @property - def record_tuples(self) -> List[Tuple[str, int, str]]: + def record_tuples(self) -> list[tuple[str, int, str]]: """A list of a stripped down version of log records intended for use in assertion comparison. @@ -474,7 +472,7 @@ def record_tuples(self) -> List[Tuple[str, int, str]]: return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self) -> List[str]: + def messages(self) -> list[str]: """A list of format-interpolated log messages. Unlike 'records', which contains the format string and parameters for @@ -497,7 +495,7 @@ def clear(self) -> None: self.handler.clear() def _force_enable_logging( - self, level: Union[int, str], logger_obj: logging.Logger + self, level: int | str, logger_obj: logging.Logger ) -> int: """Enable the desired logging level if the global level was disabled via ``logging.disabled``. @@ -530,7 +528,7 @@ def _force_enable_logging( return original_disable_level - def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: + def set_level(self, level: int | str, logger: str | None = None) -> None: """Set the threshold level of a logger for the duration of a test. Logging messages which are less severe than this level will not be captured. @@ -557,7 +555,7 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non @contextmanager def at_level( - self, level: Union[int, str], logger: Optional[str] = None + self, level: int | str, logger: str | None = None ) -> Generator[None, None, None]: """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the level is restored to its original @@ -615,7 +613,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: result._finalize() -def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: +def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: @@ -701,9 +699,9 @@ def __init__(self, config: Config) -> None: assert terminal_reporter is not None capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. - self.log_cli_handler: Union[ - _LiveLoggingStreamHandler, _LiveLoggingNullHandler - ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + self.log_cli_handler: ( + _LiveLoggingStreamHandler | _LiveLoggingNullHandler + ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) else: self.log_cli_handler = _LiveLoggingNullHandler() log_cli_formatter = self._create_formatter( @@ -714,7 +712,7 @@ def __init__(self, config: Config) -> None: self.log_cli_handler.setFormatter(log_cli_formatter) self._disable_loggers(loggers_to_disable=config.option.logger_disable) - def _disable_loggers(self, loggers_to_disable: List[str]) -> None: + def _disable_loggers(self, loggers_to_disable: list[str]) -> None: if not loggers_to_disable: return @@ -839,7 +837,7 @@ def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, Non def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("setup") - empty: Dict[str, List[logging.LogRecord]] = {} + empty: dict[str, list[logging.LogRecord]] = {} item.stash[caplog_records_key] = empty yield from self._runtest_for(item, "setup") @@ -902,7 +900,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): def __init__( self, terminal_reporter: TerminalReporter, - capture_manager: Optional[CaptureManager], + capture_manager: CaptureManager | None, ) -> None: super().__init__(stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager @@ -914,7 +912,7 @@ def reset(self) -> None: """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when: Optional[str]) -> None: + def set_when(self, when: str | None) -> None: """Prepare for the given test phase (setup/call/teardown).""" self._when = when self._section_name_shown = False diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d200a6877ff..a19ddef58fb 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -1,5 +1,7 @@ """Core implementation of the testing process: init, session, runtest loop.""" +from __future__ import annotations + import argparse import dataclasses import fnmatch @@ -13,17 +15,12 @@ from typing import Callable from typing import Dict from typing import final -from typing import FrozenSet from typing import Iterable from typing import Iterator -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -271,8 +268,8 @@ def is_ancestor(base: Path, query: Path) -> bool: def wrap_session( - config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] -) -> Union[int, ExitCode]: + config: Config, doit: Callable[[Config, Session], int | ExitCode | None] +) -> int | ExitCode: """Skeleton command line program.""" session = Session.from_config(config) session.exitstatus = ExitCode.OK @@ -291,7 +288,7 @@ def wrap_session( session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED + exitstatus: int | ExitCode = ExitCode.INTERRUPTED if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -329,11 +326,11 @@ def wrap_session( return session.exitstatus -def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: +def pytest_cmdline_main(config: Config) -> int | ExitCode: return wrap_session(config, _main) -def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: +def _main(config: Config, session: Session) -> int | ExitCode | None: """Default command line protocol for initialization, session, running tests and reporting.""" config.hook.pytest_collection(session=session) @@ -346,11 +343,11 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: return None -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: session.perform_collect() -def pytest_runtestloop(session: "Session") -> bool: +def pytest_runtestloop(session: Session) -> bool: if session.testsfailed and not session.config.option.continue_on_collection_errors: raise session.Interrupted( "%d error%s during collection" @@ -390,7 +387,7 @@ def _in_venv(path: Path) -> bool: return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: if collection_path.name == "__pycache__": return True @@ -430,11 +427,11 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: return Dir.from_parent(parent, path=path) -def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: +def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return @@ -508,7 +505,7 @@ def from_parent( # type: ignore[override] parent: nodes.Collector, *, path: Path, - ) -> "Self": + ) -> Self: """The public constructor. :param parent: The parent collector of this Dir. @@ -516,9 +513,9 @@ def from_parent( # type: ignore[override] """ return super().from_parent(parent=parent, path=path) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path): @@ -552,8 +549,8 @@ class Session(nodes.Collector): # Set on the session by runner.pytest_sessionstart. _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. - _fixturemanager: "FixtureManager" - exitstatus: Union[int, ExitCode] + _fixturemanager: FixtureManager + exitstatus: int | ExitCode def __init__(self, config: Config) -> None: super().__init__( @@ -567,22 +564,22 @@ def __init__(self, config: Config) -> None: ) self.testsfailed = 0 self.testscollected = 0 - self._shouldstop: Union[bool, str] = False - self._shouldfail: Union[bool, str] = False + self._shouldstop: bool | str = False + self._shouldfail: bool | str = False self.trace = config.trace.root.get("collection") - self._initialpaths: FrozenSet[Path] = frozenset() - self._initialpaths_with_parents: FrozenSet[Path] = frozenset() - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[CollectionArgument] = [] - self._collection_cache: Dict[nodes.Collector, CollectReport] = {} - self.items: List[nodes.Item] = [] + self._initialpaths: frozenset[Path] = frozenset() + self._initialpaths_with_parents: frozenset[Path] = frozenset() + self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: list[CollectionArgument] = [] + self._collection_cache: dict[nodes.Collector, CollectReport] = {} + self.items: list[nodes.Item] = [] - self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) + self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) self.config.pluginmanager.register(self, name="session") @classmethod - def from_config(cls, config: Config) -> "Session": + def from_config(cls, config: Config) -> Session: session: Session = cls._create(config=config) return session @@ -596,11 +593,11 @@ def __repr__(self) -> str: ) @property - def shouldstop(self) -> Union[bool, str]: + def shouldstop(self) -> bool | str: return self._shouldstop @shouldstop.setter - def shouldstop(self, value: Union[bool, str]) -> None: + def shouldstop(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldstop: @@ -614,11 +611,11 @@ def shouldstop(self, value: Union[bool, str]) -> None: self._shouldstop = value @property - def shouldfail(self) -> Union[bool, str]: + def shouldfail(self) -> bool | str: return self._shouldfail @shouldfail.setter - def shouldfail(self, value: Union[bool, str]) -> None: + def shouldfail(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldfail: @@ -651,9 +648,7 @@ def pytest_collectstart(self) -> None: raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport( - self, report: Union[TestReport, CollectReport] - ) -> None: + def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: if report.failed and not hasattr(report, "wasxfail"): self.testsfailed += 1 maxfail = self.config.getvalue("maxfail") @@ -664,7 +659,7 @@ def pytest_runtest_logreport( def isinitpath( self, - path: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, with_parents: bool = False, ) -> bool: @@ -686,7 +681,7 @@ def isinitpath( else: return path_ in self._initialpaths - def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: + def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager @@ -706,7 +701,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: def _collect_path( self, path: Path, - path_cache: Dict[Path, Sequence[nodes.Collector]], + path_cache: dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: """Create a Collector for the given path. @@ -718,7 +713,7 @@ def _collect_path( if path.is_dir(): ihook = self.gethookproxy(path.parent) - col: Optional[nodes.Collector] = ihook.pytest_collect_directory( + col: nodes.Collector | None = ihook.pytest_collect_directory( path=path, parent=self ) cols: Sequence[nodes.Collector] = (col,) if col is not None else () @@ -736,17 +731,17 @@ def _collect_path( @overload def perform_collect( - self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... + self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... ) -> Sequence[nodes.Item]: ... @overload def perform_collect( - self, args: Optional[Sequence[str]] = ..., genitems: bool = ... - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: ... + self, args: Sequence[str] | None = ..., genitems: bool = ... + ) -> Sequence[nodes.Item | nodes.Collector]: ... def perform_collect( - self, args: Optional[Sequence[str]] = None, genitems: bool = True - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + self, args: Sequence[str] | None = None, genitems: bool = True + ) -> Sequence[nodes.Item | nodes.Collector]: """Perform the collection phase for this session. This is called by the default :hook:`pytest_collection` hook @@ -772,10 +767,10 @@ def perform_collect( self._initial_parts = [] self._collection_cache = {} self.items = [] - items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items + items: Sequence[nodes.Item | nodes.Collector] = self.items try: - initialpaths: List[Path] = [] - initialpaths_with_parents: List[Path] = [] + initialpaths: list[Path] = [] + initialpaths_with_parents: list[Path] = [] for arg in args: collection_argument = resolve_collection_argument( self.config.invocation_params.dir, @@ -830,7 +825,7 @@ def _collect_one_node( self, node: nodes.Collector, handle_dupes: bool = True, - ) -> Tuple[CollectReport, bool]: + ) -> tuple[CollectReport, bool]: if node in self._collection_cache and handle_dupes: rep = self._collection_cache[node] return rep, True @@ -839,11 +834,11 @@ def _collect_one_node( self._collection_cache[node] = rep return rep, False - def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterator[nodes.Item | nodes.Collector]: # This is a cache for the root directories of the initial paths. # We can't use collection_cache for Session because of its special # role as the bootstrapping collector. - path_cache: Dict[Path, Sequence[nodes.Collector]] = {} + path_cache: dict[Path, Sequence[nodes.Collector]] = {} pm = self.config.pluginmanager @@ -881,9 +876,9 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # and discarding all nodes which don't match the level's part. any_matched_in_initial_part = False notfound_collectors = [] - work: List[ - Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]] - ] = [(self, [*paths, *names])] + work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ + (self, [*paths, *names]) + ] while work: matchnode, matchparts = work.pop() @@ -900,7 +895,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # Collect this level of matching. # Collecting Session (self) is done directly to avoid endless # recursion to this function. - subnodes: Sequence[Union[nodes.Collector, nodes.Item]] + subnodes: Sequence[nodes.Collector | nodes.Item] if isinstance(matchnode, Session): assert isinstance(matchparts[0], Path) subnodes = matchnode._collect_path(matchparts[0], path_cache) @@ -960,9 +955,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: self.trace.root.indent -= 1 - def genitems( - self, node: Union[nodes.Item, nodes.Collector] - ) -> Iterator[nodes.Item]: + def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) @@ -982,7 +975,7 @@ def genitems( node.ihook.pytest_collectreport(report=rep) -def search_pypath(module_name: str) -> Optional[str]: +def search_pypath(module_name: str) -> str | None: """Search sys.path for the given a dotted module name, and return its file system path if found.""" try: @@ -1006,7 +999,7 @@ class CollectionArgument: path: Path parts: Sequence[str] - module_name: Optional[str] + module_name: str | None def resolve_collection_argument( diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 950a6959ec9..b8a3092151f 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,12 +1,12 @@ """Generic mechanism for marking and selecting python functions.""" +from __future__ import annotations + import dataclasses from typing import AbstractSet from typing import Collection -from typing import List from typing import Optional from typing import TYPE_CHECKING -from typing import Union from .expression import Expression from .expression import ParseError @@ -44,8 +44,8 @@ def param( *values: object, - marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (), - id: Optional[str] = None, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, ) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures `. @@ -112,7 +112,7 @@ def pytest_addoption(parser: Parser) -> None: @hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: import _pytest.config if config.option.markers: @@ -151,7 +151,7 @@ class KeywordMatcher: _names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "KeywordMatcher": + def from_item(cls, item: Item) -> KeywordMatcher: mapped_names = set() # Add the names of the current item and any parent items, @@ -191,7 +191,7 @@ def __call__(self, subname: str) -> bool: return False -def deselect_by_keyword(items: "List[Item]", config: Config) -> None: +def deselect_by_keyword(items: list[Item], config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return @@ -223,7 +223,7 @@ class MarkMatcher: own_mark_names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "MarkMatcher": + def from_item(cls, item: Item) -> MarkMatcher: mark_names = {mark.name for mark in item.iter_markers()} return cls(mark_names) @@ -231,14 +231,14 @@ def __call__(self, name: str) -> bool: return name in self.own_mark_names -def deselect_by_mark(items: "List[Item]", config: Config) -> None: +def deselect_by_mark(items: list[Item], config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") - remaining: List[Item] = [] - deselected: List[Item] = [] + remaining: list[Item] = [] + deselected: list[Item] = [] for item in items: if expr.evaluate(MarkMatcher.from_item(item)): remaining.append(item) @@ -256,7 +256,7 @@ def _parse_expression(expr: str, exc_message: str) -> Expression: raise UsageError(f"{exc_message}: {expr}: {e}") from None -def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: +def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 78b7fda696b..e65b028589b 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -15,6 +15,8 @@ - or/and/not evaluate according to the usual boolean semantics. """ +from __future__ import annotations + import ast import dataclasses import enum @@ -24,7 +26,6 @@ from typing import Iterator from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence @@ -105,7 +106,7 @@ def lex(self, input: str) -> Iterator[Token]: ) yield Token(TokenType.EOF, "", pos) - def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: if self.current.type is type: token = self.current if token.type is not TokenType.EOF: @@ -197,7 +198,7 @@ def __init__(self, code: types.CodeType) -> None: self.code = code @classmethod - def compile(self, input: str) -> "Expression": + def compile(self, input: str) -> Expression: """Compile a match expression. :param input: The input expression - one line. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 456808063ad..92ade55f7c0 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import collections.abc import dataclasses import inspect @@ -8,16 +10,11 @@ from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Mapping from typing import MutableMapping from typing import NamedTuple -from typing import Optional from typing import overload from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -48,7 +45,7 @@ def istestfunc(func) -> bool: def get_empty_parameterset_mark( config: Config, argnames: Sequence[str], func -) -> "MarkDecorator": +) -> MarkDecorator: from ..nodes import Collector fs, lineno = getfslineno(func) @@ -76,17 +73,17 @@ def get_empty_parameterset_mark( class ParameterSet(NamedTuple): - values: Sequence[Union[object, NotSetType]] - marks: Collection[Union["MarkDecorator", "Mark"]] - id: Optional[str] + values: Sequence[object | NotSetType] + marks: Collection[MarkDecorator | Mark] + id: str | None @classmethod def param( cls, *values: object, - marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), - id: Optional[str] = None, - ) -> "ParameterSet": + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, + ) -> ParameterSet: if isinstance(marks, MarkDecorator): marks = (marks,) else: @@ -101,9 +98,9 @@ def param( @classmethod def extract_from( cls, - parameterset: Union["ParameterSet", Sequence[object], object], + parameterset: ParameterSet | Sequence[object] | object, force_tuple: bool = False, - ) -> "ParameterSet": + ) -> ParameterSet: """Extract from an object or objects. :param parameterset: @@ -128,11 +125,11 @@ def extract_from( @staticmethod def _parse_parametrize_args( - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *args, **kwargs, - ) -> Tuple[Sequence[str], bool]: + ) -> tuple[Sequence[str], bool]: if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -142,9 +139,9 @@ def _parse_parametrize_args( @staticmethod def _parse_parametrize_parameters( - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argvalues: Iterable[ParameterSet | Sequence[object] | object], force_tuple: bool, - ) -> List["ParameterSet"]: + ) -> list[ParameterSet]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @@ -152,12 +149,12 @@ def _parse_parametrize_parameters( @classmethod def _for_parametrize( cls, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], func, config: Config, nodeid: str, - ) -> Tuple[Sequence[str], List["ParameterSet"]]: + ) -> tuple[Sequence[str], list[ParameterSet]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -200,24 +197,24 @@ class Mark: #: Name of the mark. name: str #: Positional arguments of the mark decorator. - args: Tuple[Any, ...] + args: tuple[Any, ...] #: Keyword arguments of the mark decorator. kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + _param_ids_generated: Sequence[str] | None = dataclasses.field( default=None, repr=False ) def __init__( self, name: str, - args: Tuple[Any, ...], + args: tuple[Any, ...], kwargs: Mapping[str, Any], - param_ids_from: Optional["Mark"] = None, - param_ids_generated: Optional[Sequence[str]] = None, + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -233,7 +230,7 @@ def __init__( def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: "Mark") -> "Mark": + def combined_with(self, other: Mark) -> Mark: """Return a new Mark which is a combination of this Mark and another Mark. @@ -245,7 +242,7 @@ def combined_with(self, other: "Mark") -> "Mark": assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None + param_ids_from: Mark | None = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -316,7 +313,7 @@ def name(self) -> str: return self.mark.name @property - def args(self) -> Tuple[Any, ...]: + def args(self) -> tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -330,7 +327,7 @@ def markname(self) -> str: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: """Return a MarkDecorator with extra arguments added. Unlike calling the MarkDecorator, with_args() can be used even @@ -347,7 +344,7 @@ def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] pass @overload - def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: pass def __call__(self, *args: object, **kwargs: object): @@ -362,10 +359,10 @@ def __call__(self, *args: object, **kwargs: object): def get_unpacked_marks( - obj: Union[object, type], + obj: object | type, *, consider_mro: bool = True, -) -> List[Mark]: +) -> list[Mark]: """Obtain the unpacked marks that are stored on an object. If obj is a class and consider_mro is true, return marks applied to @@ -395,7 +392,7 @@ def get_unpacked_marks( def normalize_mark_list( - mark_list: Iterable[Union[Mark, MarkDecorator]], + mark_list: Iterable[Mark | MarkDecorator], ) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks @@ -437,13 +434,13 @@ class _SkipMarkDecorator(MarkDecorator): def __call__(self, arg: Markable) -> Markable: ... @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": ... + def __call__(self, reason: str = ...) -> MarkDecorator: ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], + condition: str | bool = ..., + *conditions: str | bool, reason: str = ..., ) -> MarkDecorator: ... @@ -454,30 +451,25 @@ def __call__(self, arg: Markable) -> Markable: ... @overload def __call__( self, - condition: Union[str, bool] = False, - *conditions: Union[str, bool], + condition: str | bool = False, + *conditions: str | bool, reason: str = ..., run: bool = ..., - raises: Union[ - None, Type[BaseException], Tuple[Type[BaseException], ...] - ] = ..., + raises: None | type[BaseException] | tuple[type[BaseException], ...] = ..., strict: bool = ..., ) -> MarkDecorator: ... class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *, - indirect: Union[bool, Sequence[str]] = ..., - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ] = ..., - scope: Optional[_ScopeName] = ..., + indirect: bool | Sequence[str] = ..., + ids: Iterable[None | str | float | int | bool] + | Callable[[Any], object | None] + | None = ..., + scope: _ScopeName | None = ..., ) -> MarkDecorator: ... class _UsefixturesMarkDecorator(MarkDecorator): @@ -517,8 +509,8 @@ def test_function(): def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._config: Optional[Config] = None - self._markers: Set[str] = set() + self._config: Config | None = None + self._markers: set[str] = set() def __getattr__(self, name: str) -> MarkDecorator: """Generate a new :class:`MarkDecorator` with the given name.""" @@ -569,7 +561,7 @@ def __getattr__(self, name: str) -> MarkDecorator: class NodeKeywords(MutableMapping[str, Any]): __slots__ = ("node", "parent", "_markers") - def __init__(self, node: "Node") -> None: + def __init__(self, node: Node) -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} @@ -597,7 +589,7 @@ def __contains__(self, key: object) -> bool: def update( # type: ignore[override] self, - other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), **kwds: Any, ) -> None: self._markers.update(other) diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index f498d60df14..75b019a3be6 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Monkeypatching and mocking functionality.""" +from __future__ import annotations + from contextlib import contextmanager import os import re @@ -8,14 +10,10 @@ from typing import Any from typing import final from typing import Generator -from typing import List from typing import Mapping from typing import MutableMapping -from typing import Optional from typing import overload -from typing import Tuple from typing import TypeVar -from typing import Union import warnings from _pytest.fixtures import fixture @@ -30,7 +28,7 @@ @fixture -def monkeypatch() -> Generator["MonkeyPatch", None, None]: +def monkeypatch() -> Generator[MonkeyPatch, None, None]: """A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -97,7 +95,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: return obj -def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: +def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: if not isinstance(import_path, str) or "." not in import_path: raise TypeError(f"must be absolute import path string, not {import_path!r}") module, attr = import_path.rsplit(".", 1) @@ -130,14 +128,14 @@ class MonkeyPatch: """ def __init__(self) -> None: - self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: Optional[str] = None - self._savesyspath: Optional[List[str]] = None + self._setattr: list[tuple[object, str, object]] = [] + self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] + self._cwd: str | None = None + self._savesyspath: list[str] | None = None @classmethod @contextmanager - def context(cls) -> Generator["MonkeyPatch", None, None]: + def context(cls) -> Generator[MonkeyPatch, None, None]: """Context manager that returns a new :class:`MonkeyPatch` object which undoes any patching done inside the ``with`` block upon exit. @@ -182,8 +180,8 @@ def setattr( def setattr( self, - target: Union[str, object], - name: Union[object, str], + target: str | object, + name: object | str, value: object = notset, raising: bool = True, ) -> None: @@ -254,8 +252,8 @@ def setattr( def delattr( self, - target: Union[object, str], - name: Union[str, Notset] = notset, + target: object | str, + name: str | Notset = notset, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -310,7 +308,7 @@ def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict del dic[name] # type: ignore[attr-defined] - def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: + def setenv(self, name: str, value: str, prepend: str | None = None) -> None: """Set environment variable ``name`` to ``value``. If ``prepend`` is a character, read the current environment variable @@ -363,7 +361,7 @@ def syspath_prepend(self, path) -> None: invalidate_caches() - def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: + def chdir(self, path: str | os.PathLike[str]) -> None: """Change the current working directory to the specified path. :param path: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index aad54f8b325..bbde2664b90 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import abc from functools import cached_property from inspect import signature @@ -10,17 +12,11 @@ from typing import cast from typing import Iterable from typing import Iterator -from typing import List from typing import MutableMapping from typing import NoReturn -from typing import Optional from typing import overload -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import warnings import pluggy @@ -62,9 +58,9 @@ def _imply_path( - node_type: Type["Node"], - path: Optional[Path], - fspath: Optional[LEGACY_PATH], + node_type: type[Node], + path: Path | None, + fspath: LEGACY_PATH | None, ) -> Path: if fspath is not None: warnings.warn( @@ -109,7 +105,7 @@ def __call__(cls, *k, **kw) -> NoReturn: ).format(name=f"{cls.__module__}.{cls.__name__}") fail(msg, pytrace=False) - def _create(cls: Type[_T], *k, **kw) -> _T: + def _create(cls: type[_T], *k, **kw) -> _T: try: return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] except TypeError: @@ -160,12 +156,12 @@ class Node(abc.ABC, metaclass=NodeMeta): def __init__( self, name: str, - parent: "Optional[Node]" = None, - config: Optional[Config] = None, - session: "Optional[Session]" = None, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, - nodeid: Optional[str] = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, + nodeid: str | None = None, ) -> None: #: A unique name within the scope of the parent node. self.name: str = name @@ -199,10 +195,10 @@ def __init__( self.keywords: MutableMapping[str, Any] = NodeKeywords(self) #: The marker objects belonging to this node. - self.own_markers: List[Mark] = [] + self.own_markers: list[Mark] = [] #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: Set[str] = set() + self.extra_keyword_matches: set[str] = set() if nodeid is not None: assert "::()" not in nodeid @@ -219,7 +215,7 @@ def __init__( self._store = self.stash @classmethod - def from_parent(cls, parent: "Node", **kw) -> "Self": + def from_parent(cls, parent: Node, **kw) -> Self: """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -295,31 +291,29 @@ def setup(self) -> None: def teardown(self) -> None: pass - def iter_parents(self) -> Iterator["Node"]: + def iter_parents(self) -> Iterator[Node]: """Iterate over all parent collectors starting from and including self up to the root of the collection tree. .. versionadded:: 8.1 """ - parent: Optional[Node] = self + parent: Node | None = self while parent is not None: yield parent parent = parent.parent - def listchain(self) -> List["Node"]: + def listchain(self) -> list[Node]: """Return a list of all parent collectors starting from the root of the collection tree down to and including self.""" chain = [] - item: Optional[Node] = self + item: Node | None = self while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker( - self, marker: Union[str, MarkDecorator], append: bool = True - ) -> None: + def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: """Dynamically add a marker object to the node. :param marker: @@ -341,7 +335,7 @@ def add_marker( else: self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: + def iter_markers(self, name: str | None = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -350,8 +344,8 @@ def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: return (x[1] for x in self.iter_markers_with_node(name=name)) def iter_markers_with_node( - self, name: Optional[str] = None - ) -> Iterator[Tuple["Node", Mark]]: + self, name: str | None = None + ) -> Iterator[tuple[Node, Mark]]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -363,14 +357,12 @@ def iter_markers_with_node( yield node, mark @overload - def get_closest_marker(self, name: str) -> Optional[Mark]: ... + def get_closest_marker(self, name: str) -> Mark | None: ... @overload def get_closest_marker(self, name: str, default: Mark) -> Mark: ... - def get_closest_marker( - self, name: str, default: Optional[Mark] = None - ) -> Optional[Mark]: + def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: """Return the first marker matching the name, from closest (for example function) to farther level (for example module level). @@ -379,14 +371,14 @@ def get_closest_marker( """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self) -> Set[str]: + def listextrakeywords(self) -> set[str]: """Return a set of all extra keywords in self and any parents.""" - extra_keywords: Set[str] = set() + extra_keywords: set[str] = set() for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords - def listnames(self) -> List[str]: + def listnames(self) -> list[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: @@ -398,7 +390,7 @@ def addfinalizer(self, fin: Callable[[], object]) -> None: """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: + def getparent(self, cls: type[_NodeType]) -> _NodeType | None: """Get the closest parent node (including self) which is an instance of the given class. @@ -416,7 +408,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[TracebackStyle]" = None, + style: TracebackStyle | None = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError @@ -428,7 +420,7 @@ def _repr_failure_py( if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] if self.config.getoption("fulltrace", False): style = "long" tbfilter = False @@ -474,8 +466,8 @@ def _repr_failure_py( def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[TracebackStyle]" = None, - ) -> Union[str, TerminalRepr]: + style: TracebackStyle | None = None, + ) -> str | TerminalRepr: """Return a representation of a collection or test failure. .. seealso:: :ref:`non-python tests` @@ -485,7 +477,7 @@ def repr_failure( return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: +def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: """Try to extract the actual location from a node, depending on available attributes: * "location": a pair (path, lineno) @@ -495,7 +487,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. - location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) + location: tuple[str, int | None, str] | None = getattr(node, "location", None) if location is not None: return location[:2] obj = getattr(node, "obj", None) @@ -515,14 +507,14 @@ class CollectError(Exception): """An error during collection, contains a custom message.""" @abc.abstractmethod - def collect(self) -> Iterable[Union["Item", "Collector"]]: + def collect(self) -> Iterable[Item | Collector]: """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException] - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: """Return a representation of a collection failure. :param excinfo: Exception information for the failure. @@ -551,7 +543,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: return excinfo.traceback -def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: +def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None: for initial_path in session._initialpaths: if commonpath(path, initial_path) == initial_path: rel = str(path.relative_to(initial_path)) @@ -564,14 +556,14 @@ class FSCollector(Collector, abc.ABC): def __init__( self, - fspath: Optional[LEGACY_PATH] = None, - path_or_parent: Optional[Union[Path, Node]] = None, - path: Optional[Path] = None, - name: Optional[str] = None, - parent: Optional[Node] = None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + fspath: LEGACY_PATH | None = None, + path_or_parent: Path | Node | None = None, + path: Path | None = None, + name: str | None = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, ) -> None: if path_or_parent: if isinstance(path_or_parent, Node): @@ -621,10 +613,10 @@ def from_parent( cls, parent, *, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, **kw, - ) -> "Self": + ) -> Self: """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) @@ -666,9 +658,9 @@ def __init__( self, name, parent=None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, **kw, ) -> None: # The first two arguments are intentionally passed positionally, @@ -683,11 +675,11 @@ def __init__( nodeid=nodeid, **kw, ) - self._report_sections: List[Tuple[str, str, str]] = [] + self._report_sections: list[tuple[str, str, str]] = [] #: A list of tuples (name, value) that holds user defined properties #: for this test. - self.user_properties: List[Tuple[str, object]] = [] + self.user_properties: list[tuple[str, object]] = [] self._check_item_and_collector_diamond_inheritance() @@ -747,7 +739,7 @@ def add_report_section(self, when: str, key: str, content: str) -> None: if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: """Get location information for this item for test reports. Returns a tuple with three elements: @@ -761,7 +753,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str return self.path, None, "" @cached_property - def location(self) -> Tuple[str, Optional[int], str]: + def location(self) -> tuple[str, int | None, str]: """ Returns a tuple of ``(relfspath, lineno, testname)`` for this item where ``relfspath`` is file path relative to ``config.rootpath`` diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index f953dabe03d..5b20803e586 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -1,12 +1,13 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" +from __future__ import annotations + import sys from typing import Any from typing import Callable from typing import cast from typing import NoReturn -from typing import Optional from typing import Protocol from typing import Type from typing import TypeVar @@ -18,7 +19,7 @@ class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info about test and collection outcomes.""" - def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: + def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: if msg is not None and not isinstance(msg, str): error_msg = ( # type: ignore[unreachable] "{} expected string as 'msg' parameter, got '{}' instead.\n" @@ -47,7 +48,7 @@ class Skipped(OutcomeException): def __init__( self, - msg: Optional[str] = None, + msg: str | None = None, pytrace: bool = True, allow_module_level: bool = False, *, @@ -70,7 +71,7 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: Optional[int] = None + self, msg: str = "unknown reason", returncode: int | None = None ) -> None: self.msg = msg self.returncode = returncode @@ -104,7 +105,7 @@ def decorate(func: _F) -> _WithException[_F, _ET]: @_with_exception(Exit) def exit( reason: str = "", - returncode: Optional[int] = None, + returncode: int | None = None, ) -> NoReturn: """Exit testing process. @@ -207,10 +208,10 @@ def xfail(reason: str = "") -> NoReturn: def importorskip( modname: str, - minversion: Optional[str] = None, - reason: Optional[str] = None, + minversion: str | None = None, + reason: str | None = None, *, - exc_type: Optional[Type[ImportError]] = None, + exc_type: type[ImportError] | None = None, ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. @@ -267,8 +268,8 @@ def importorskip( else: warn_on_import_error = False - skipped: Optional[Skipped] = None - warning: Optional[Warning] = None + skipped: Skipped | None = None + warning: Warning | None = None with warnings.catch_warnings(): # Make sure to ignore ImportWarnings that might happen because diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 20cb8ca83c3..69c011ed24a 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs """Submit failure or test session information to a pastebin service.""" +from __future__ import annotations + from io import StringIO import tempfile from typing import IO -from typing import Union from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -68,7 +69,7 @@ def pytest_unconfigure(config: Config) -> None: tr.write_line(f"pastebin session-log: {pastebinurl}\n") -def create_new_paste(contents: Union[str, bytes]) -> str: +def create_new_paste(contents: str | bytes) -> str: """Create a new paste using the bpaste.net service. :contents: Paste contents string. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b11eea4e7ef..e4dc4eddc9c 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import atexit import contextlib from enum import Enum @@ -24,16 +26,9 @@ from types import ModuleType from typing import Any from typing import Callable -from typing import Dict from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union import uuid import warnings @@ -71,12 +66,10 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: def on_rm_rf_error( - func: Optional[Callable[..., Any]], + func: Callable[..., Any] | None, path: str, - excinfo: Union[ - BaseException, - Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], - ], + excinfo: BaseException + | tuple[type[BaseException], BaseException, types.TracebackType | None], *, start_path: Path, ) -> bool: @@ -172,7 +165,7 @@ def rm_rf(path: Path) -> None: shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: +def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]: """Find all elements in root that begin with the prefix, case-insensitive.""" l_prefix = prefix.lower() for x in os.scandir(root): @@ -180,7 +173,7 @@ def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: yield x -def extract_suffixes(iter: Iterable["os.DirEntry[str]"], prefix: str) -> Iterator[str]: +def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]: """Return the parts of the paths following the prefix. :param iter: Iterator over path names. @@ -204,9 +197,7 @@ def parse_num(maybe_num: str) -> int: return -1 -def _force_symlink( - root: Path, target: Union[str, PurePath], link_to: Union[str, Path] -) -> None: +def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: """Helper to create the current symlink. It's full of race conditions that are reasonably OK to ignore @@ -420,7 +411,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: +def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: """A port of FNMatcher from py.path.common which works with PurePath() instances. The difference between this algorithm and PurePath.match() is that the @@ -456,14 +447,14 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: return fnmatch.fnmatch(name, pattern) -def parts(s: str) -> Set[str]: +def parts(s: str) -> set[str]: parts = s.split(sep) return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} def symlink_or_skip( - src: Union["os.PathLike[str]", str], - dst: Union["os.PathLike[str]", str], + src: os.PathLike[str] | str, + dst: os.PathLike[str] | str, **kwargs: Any, ) -> None: """Make a symlink, or skip the test in case symlinks are not supported.""" @@ -491,9 +482,9 @@ class ImportPathMismatchError(ImportError): def import_path( - path: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, - mode: Union[str, ImportMode] = ImportMode.prepend, + mode: str | ImportMode = ImportMode.prepend, root: Path, consider_namespace_packages: bool, ) -> ModuleType: @@ -618,7 +609,7 @@ def import_path( def _import_module_using_spec( module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool -) -> Optional[ModuleType]: +) -> ModuleType | None: """ Tries to import a module by its canonical name, path to the .py file, and its parent location. @@ -641,7 +632,7 @@ def _import_module_using_spec( # Attempt to import the parent module, seems is our responsibility: # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 parent_module_name, _, name = module_name.rpartition(".") - parent_module: Optional[ModuleType] = None + parent_module: ModuleType | None = None if parent_module_name: parent_module = sys.modules.get(parent_module_name) if parent_module is None: @@ -680,9 +671,7 @@ def _import_module_using_spec( return None -def spec_matches_module_path( - module_spec: Optional[ModuleSpec], module_path: Path -) -> bool: +def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool: """Return true if the given ModuleSpec can be used to import the given module path.""" if module_spec is None or module_spec.origin is None: return False @@ -734,7 +723,7 @@ def module_name_from_path(path: Path, root: Path) -> str: return ".".join(path_parts) -def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None: +def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: """ Used by ``import_path`` to create intermediate modules when using mode=importlib. @@ -772,7 +761,7 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> module_name = ".".join(module_parts) -def resolve_package_path(path: Path) -> Optional[Path]: +def resolve_package_path(path: Path) -> Path | None: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. @@ -791,7 +780,7 @@ def resolve_package_path(path: Path) -> Optional[Path]: def resolve_pkg_root_and_module_name( path: Path, *, consider_namespace_packages: bool = False -) -> Tuple[Path, str]: +) -> tuple[Path, str]: """ Return the path to the directory of the root package that contains the given Python file, and its module name: @@ -812,7 +801,7 @@ def resolve_pkg_root_and_module_name( Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). """ - pkg_root: Optional[Path] = None + pkg_root: Path | None = None pkg_path = resolve_package_path(path) if pkg_path is not None: pkg_root = pkg_path.parent @@ -859,7 +848,7 @@ def is_importable(module_name: str, module_path: Path) -> bool: return spec_matches_module_path(spec, module_path) -def compute_module_name(root: Path, module_path: Path) -> Optional[str]: +def compute_module_name(root: Path, module_path: Path) -> str | None: """Compute a module name based on a path and a root anchor.""" try: path_without_suffix = module_path.with_suffix("") @@ -884,9 +873,9 @@ class CouldNotResolvePathError(Exception): def scandir( - path: Union[str, "os.PathLike[str]"], - sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name, -) -> List["os.DirEntry[str]"]: + path: str | os.PathLike[str], + sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, +) -> list[os.DirEntry[str]]: """Scan a directory recursively, in breadth-first order. The returned entries are sorted according to the given key. @@ -909,8 +898,8 @@ def scandir( def visit( - path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] -) -> Iterator["os.DirEntry[str]"]: + path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool] +) -> Iterator[os.DirEntry[str]]: """Walk a directory recursively, in breadth-first order. The `recurse` predicate determines whether a directory is recursed. @@ -924,7 +913,7 @@ def visit( yield from visit(entry.path, recurse) -def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path: +def absolutepath(path: str | os.PathLike[str]) -> Path: """Convert a path to an absolute path using os.path.abspath. Prefer this over Path.resolve() (see #6523). @@ -933,7 +922,7 @@ def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path: return Path(os.path.abspath(path)) -def commonpath(path1: Path, path2: Path) -> Optional[Path]: +def commonpath(path1: Path, path2: Path) -> Path | None: """Return the common part shared with the other path, or None if there is no common part. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 42f50900ada..e27648507e9 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -4,6 +4,8 @@ PYTEST_DONT_REWRITE """ +from __future__ import annotations + import collections.abc import contextlib from fnmatch import fnmatch @@ -21,22 +23,16 @@ import traceback from typing import Any from typing import Callable -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from weakref import WeakKeyDictionary from iniconfig import IniConfig @@ -123,7 +119,7 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: - def get_open_files(self) -> List[Tuple[str, str]]: + def get_open_files(self) -> list[tuple[str, str]]: if sys.version_info >= (3, 11): # New in Python 3.11, ignores utf-8 mode encoding = locale.getencoding() @@ -199,7 +195,7 @@ def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object] @fixture -def _pytest(request: FixtureRequest) -> "PytestArg": +def _pytest(request: FixtureRequest) -> PytestArg: """Return a helper which offers a gethookrecorder(hook) method which returns a HookRecorder instance which helps to make assertions about called hooks.""" @@ -210,13 +206,13 @@ class PytestArg: def __init__(self, request: FixtureRequest) -> None: self._request = request - def gethookrecorder(self, hook) -> "HookRecorder": + def gethookrecorder(self, hook) -> HookRecorder: hookrecorder = HookRecorder(hook._pm) self._request.addfinalizer(hookrecorder.finish_recording) return hookrecorder -def get_public_names(values: Iterable[str]) -> List[str]: +def get_public_names(values: Iterable[str]) -> list[str]: """Only return names from iterator values without a leading underscore.""" return [x for x in values if x[0] != "_"] @@ -265,8 +261,8 @@ def __init__( check_ispytest(_ispytest) self._pluginmanager = pluginmanager - self.calls: List[RecordedHookCall] = [] - self.ret: Optional[Union[int, ExitCode]] = None + self.calls: list[RecordedHookCall] = [] + self.ret: int | ExitCode | None = None def before(hook_name: str, hook_impls, kwargs) -> None: self.calls.append(RecordedHookCall(hook_name, kwargs)) @@ -279,13 +275,13 @@ def after(outcome, hook_name: str, hook_impls, kwargs) -> None: def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]: + def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: """Get all recorded calls to hooks with the given names (or name).""" if isinstance(names, str): names = names.split() return [call for call in self.calls if call._name in names] - def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: + def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) @@ -327,42 +323,42 @@ def getcall(self, name: str) -> RecordedHookCall: @overload def getreports( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getreports( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: ... + ) -> Sequence[CollectReport | TestReport]: ... def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [x.report for x in self.getcalls(names)] def matchreport( self, inamepart: str = "", - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_runtest_logreport", "pytest_collectreport", ), - when: Optional[str] = None, - ) -> Union[CollectReport, TestReport]: + when: str | None = None, + ) -> CollectReport | TestReport: """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): @@ -387,31 +383,31 @@ def matchreport( @overload def getfailures( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getfailures( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: ... + ) -> Sequence[CollectReport | TestReport]: ... def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [rep for rep in self.getreports(names) if rep.failed] def getfailedcollections(self) -> Sequence[CollectReport]: @@ -419,10 +415,10 @@ def getfailedcollections(self) -> Sequence[CollectReport]: def listoutcomes( self, - ) -> Tuple[ + ) -> tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ]: passed = [] skipped = [] @@ -441,7 +437,7 @@ def listoutcomes( failed.append(rep) return passed, skipped, failed - def countoutcomes(self) -> List[int]: + def countoutcomes(self) -> list[int]: return [len(x) for x in self.listoutcomes()] def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: @@ -461,14 +457,14 @@ def clear(self) -> None: @fixture -def linecomp() -> "LineComp": +def linecomp() -> LineComp: """A :class: `LineComp` instance for checking that an input linearly contains a sequence of strings.""" return LineComp() @fixture(name="LineMatcher") -def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: +def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: """A reference to the :class: `LineMatcher`. This is instantiable with a list of lines (without their trailing newlines). @@ -480,7 +476,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture def pytester( request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> "Pytester": +) -> Pytester: """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -524,13 +520,13 @@ class RunResult: def __init__( self, - ret: Union[int, ExitCode], - outlines: List[str], - errlines: List[str], + ret: int | ExitCode, + outlines: list[str], + errlines: list[str], duration: float, ) -> None: try: - self.ret: Union[int, ExitCode] = ExitCode(ret) + self.ret: int | ExitCode = ExitCode(ret) """The return value.""" except ValueError: self.ret = ret @@ -555,7 +551,7 @@ def __repr__(self) -> str: % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) ) - def parseoutcomes(self) -> Dict[str, int]: + def parseoutcomes(self) -> dict[str, int]: """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. @@ -568,7 +564,7 @@ def parseoutcomes(self) -> Dict[str, int]: return self.parse_summary_nouns(self.outlines) @classmethod - def parse_summary_nouns(cls, lines) -> Dict[str, int]: + def parse_summary_nouns(cls, lines) -> dict[str, int]: """Extract the nouns from a pytest terminal summary line. It always returns the plural noun for consistency:: @@ -599,8 +595,8 @@ def assert_outcomes( errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """ Assert that the specified outcomes appear with the respective @@ -626,7 +622,7 @@ def assert_outcomes( class SysModulesSnapshot: - def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -659,7 +655,7 @@ class Pytester: __test__ = False - CLOSE_STDIN: "Final" = NOTSET + CLOSE_STDIN: Final = NOTSET class TimeoutExpired(Exception): pass @@ -674,9 +670,9 @@ def __init__( ) -> None: check_ispytest(_ispytest) self._request = request - self._mod_collections: WeakKeyDictionary[ - Collector, List[Union[Item, Collector]] - ] = WeakKeyDictionary() + self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( + WeakKeyDictionary() + ) if request.function: name: str = request.function.__name__ else: @@ -687,7 +683,7 @@ def __init__( #: :py:meth:`runpytest`. Initially this is an empty list but plugins can #: be added to the list. The type of items to add to the list depends on #: the method using them so refer to them for details. - self.plugins: List[Union[str, _PluggyPlugin]] = [] + self.plugins: list[str | _PluggyPlugin] = [] self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() self._request.addfinalizer(self._finalize) @@ -755,8 +751,8 @@ def chdir(self) -> None: def _makefile( self, ext: str, - lines: Sequence[Union[Any, bytes]], - files: Dict[str, str], + lines: Sequence[Any | bytes], + files: dict[str, str], encoding: str = "utf-8", ) -> Path: items = list(files.items()) @@ -769,7 +765,7 @@ def _makefile( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" ) - def to_text(s: Union[Any, bytes]) -> str: + def to_text(s: Any | bytes) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) if lines: @@ -892,9 +888,7 @@ def test_something(pytester): """ return self._makefile(".txt", args, kwargs) - def syspathinsert( - self, path: Optional[Union[str, "os.PathLike[str]"]] = None - ) -> None: + def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: """Prepend a directory to sys.path, defaults to :attr:`path`. This is undone automatically when this object dies at the end of each @@ -908,7 +902,7 @@ def syspathinsert( self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkdir(self, name: str | os.PathLike[str]) -> Path: """Create a new (sub)directory. :param name: @@ -920,7 +914,7 @@ def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.mkdir() return p - def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkpydir(self, name: str | os.PathLike[str]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -931,7 +925,7 @@ def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.joinpath("__init__.py").touch() return p - def copy_example(self, name: Optional[str] = None) -> Path: + def copy_example(self, name: str | None = None) -> Path: """Copy file from project's directory into the testdir. :param name: @@ -976,9 +970,7 @@ def copy_example(self, name: Optional[str] = None) -> Path: f'example "{example_path}" is not found as a file or directory' ) - def getnode( - self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: """Get the collection node of a file. :param config: @@ -997,9 +989,7 @@ def getnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode( - self, path: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to @@ -1019,7 +1009,7 @@ def getpathnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the @@ -1031,7 +1021,7 @@ def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: The collected items. """ session = colitems[0].session - result: List[Item] = [] + result: list[Item] = [] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -1065,7 +1055,7 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: values = [*list(cmdlineargs), p] return self.inline_run(*values) - def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: + def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: """Run ``pytest.main(['--collect-only'])`` in-process. Runs the :py:func:`pytest.main` function to run all of pytest inside @@ -1078,7 +1068,7 @@ def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: def inline_run( self, - *args: Union[str, "os.PathLike[str]"], + *args: str | os.PathLike[str], plugins=(), no_reraise_ctrlc: bool = False, ) -> HookRecorder: @@ -1148,7 +1138,7 @@ class reprec: # type: ignore finalizer() def runpytest_inprocess( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any + self, *args: str | os.PathLike[str], **kwargs: Any ) -> RunResult: """Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.""" @@ -1191,9 +1181,7 @@ class reprec: # type: ignore res.reprec = reprec # type: ignore return res - def runpytest( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any - ) -> RunResult: + def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: """Run pytest inline or in a subprocess, depending on the command line option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" new_args = self._ensure_basetemp(args) @@ -1204,8 +1192,8 @@ def runpytest( raise RuntimeError(f"Unrecognized runpytest option: {self._method}") def _ensure_basetemp( - self, args: Sequence[Union[str, "os.PathLike[str]"]] - ) -> List[Union[str, "os.PathLike[str]"]]: + self, args: Sequence[str | os.PathLike[str]] + ) -> list[str | os.PathLike[str]]: new_args = list(args) for x in new_args: if str(x).startswith("--basetemp"): @@ -1216,7 +1204,7 @@ def _ensure_basetemp( ) return new_args - def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfig(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest :class:`pytest.Config` instance from given commandline args. @@ -1240,7 +1228,7 @@ def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: self._request.addfinalizer(config._ensure_unconfigure) return config - def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like @@ -1252,7 +1240,7 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: return config def getitem( - self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" + self, source: str | os.PathLike[str], funcname: str = "test_func" ) -> Item: """Return the test item for a test function. @@ -1273,7 +1261,7 @@ def getitem( return item assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" - def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: + def getitems(self, source: str | os.PathLike[str]) -> list[Item]: """Return all test items collected from the module. Writes the source to a Python file and runs pytest's collection on @@ -1284,7 +1272,7 @@ def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: def getmodulecol( self, - source: Union[str, "os.PathLike[str]"], + source: str | os.PathLike[str], configargs=(), *, withinit: bool = False, @@ -1316,9 +1304,7 @@ def getmodulecol( self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """Return the collection node for name from the module collection. Searches a module collection node for a collection node matching the @@ -1336,10 +1322,10 @@ def collect_by_name( def popen( self, - cmdargs: Sequence[Union[str, "os.PathLike[str]"]], - stdout: Union[int, TextIO] = subprocess.PIPE, - stderr: Union[int, TextIO] = subprocess.PIPE, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1374,9 +1360,9 @@ def popen( def run( self, - *cmdargs: Union[str, "os.PathLike[str]"], - timeout: Optional[float] = None, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1462,10 +1448,10 @@ def _dump_lines(self, lines, fp): except UnicodeEncodeError: print(f"couldn't print to {fp} because of encoding") - def _getpytestargs(self) -> Tuple[str, ...]: + def _getpytestargs(self) -> tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script: "os.PathLike[str]") -> RunResult: + def runpython(self, script: os.PathLike[str]) -> RunResult: """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) @@ -1474,7 +1460,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1501,9 +1487,7 @@ def runpytest_subprocess( args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect. This makes sure to use the right pytest and sets up the temporary @@ -1517,7 +1501,7 @@ def spawn_pytest( cmd = f"{invoke} --basetemp={basetemp} {string}" return self.spawn(cmd, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run a command using pexpect. The pexpect child is returned. @@ -1562,9 +1546,9 @@ class LineMatcher: ``text.splitlines()``. """ - def __init__(self, lines: List[str]) -> None: + def __init__(self, lines: list[str]) -> None: self.lines = lines - self._log_output: List[str] = [] + self._log_output: list[str] = [] def __str__(self) -> str: """Return the entire original text. @@ -1574,7 +1558,7 @@ def __str__(self) -> str: """ return "\n".join(self.lines) - def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: + def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py index d20c2bb5999..d543798f75a 100644 --- a/src/_pytest/pytester_assertions.py +++ b/src/_pytest/pytester_assertions.py @@ -4,21 +4,19 @@ # contain them itself, since it is imported by the `pytest` module, # hence cannot be subject to assertion rewriting, which requires a # module to not be already imported. -from typing import Dict -from typing import Optional +from __future__ import annotations + from typing import Sequence -from typing import Tuple -from typing import Union from _pytest.reports import CollectReport from _pytest.reports import TestReport def assertoutcome( - outcomes: Tuple[ + outcomes: tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ], passed: int = 0, skipped: int = 0, @@ -37,15 +35,15 @@ def assertoutcome( def assert_outcomes( - outcomes: Dict[str, int], + outcomes: dict[str, int], passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4887614de3b..2904c3a1e0f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Python test discovery, setup and run of test functions.""" +from __future__ import annotations + import abc from collections import Counter from collections import defaultdict @@ -20,16 +22,11 @@ from typing import Generator from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Set -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import _pytest @@ -113,7 +110,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) @@ -153,7 +150,7 @@ def async_warn_and_skip(nodeid: str) -> None: @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: testfunction = pyfuncitem.obj if is_async_function(testfunction): async_warn_and_skip(pyfuncitem.nodeid) @@ -174,7 +171,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: pkginit = path / "__init__.py" try: has_pkginit = pkginit.is_file() @@ -186,7 +183,7 @@ def pytest_collect_directory( return None -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: +def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): if not path_matches_patterns( @@ -206,14 +203,14 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": +def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: return Module.from_parent(parent, path=module_path) @hookimpl(trylast=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + collector: Module | Class, name: str, obj: object +) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): @@ -320,7 +317,7 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> parts.reverse() return ".".join(parts) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: # XXX caching? path, lineno = getfslineno(self.obj) modpath = self.getmodpath() @@ -394,7 +391,7 @@ def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: return True return False - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not getattr(self.obj, "__test__", True): return [] @@ -406,11 +403,11 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: # In each class, nodes should be definition ordered. # __dict__ is definition ordered. - seen: Set[str] = set() - dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] + seen: set[str] = set() + dict_values: list[list[nodes.Item | nodes.Collector]] = [] ihook = self.ihook for dic in dicts: - values: List[Union[nodes.Item, nodes.Collector]] = [] + values: list[nodes.Item | nodes.Collector] = [] # Note: seems like the dict can change during iteration - # be careful not to remove the list() without consideration. for name, obj in list(dic.items()): @@ -437,7 +434,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: result.extend(values) return result - def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: + def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: modulecol = self.getparent(Module) assert modulecol is not None module = modulecol.obj @@ -548,7 +545,7 @@ class Module(nodes.File, PyCollector): def _getobj(self): return importtestmodule(self.path, self.config) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: self._register_setup_module_fixture() self._register_setup_function_fixture() self.session._fixturemanager.parsefactories(self) @@ -642,13 +639,13 @@ class Package(nodes.Directory): def __init__( self, - fspath: Optional[LEGACY_PATH], + fspath: LEGACY_PATH | None, parent: nodes.Collector, # NOTE: following args are unused: config=None, session=None, nodeid=None, - path: Optional[Path] = None, + path: Path | None = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. # super().__init__(self, fspath, parent=parent) @@ -680,13 +677,13 @@ def setup(self) -> None: func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: # Always collect __init__.py first. - def sort_key(entry: "os.DirEntry[str]") -> object: + def sort_key(entry: os.DirEntry[str]) -> object: return (entry.name != "__init__.py", entry.name) config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path, sort_key): @@ -720,12 +717,12 @@ def _call_with_optional_argument(func, arg) -> None: func() -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: +def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: """Return the attribute from the given object to be used as a setup/teardown xunit-style function, but only if not marked as a fixture to avoid calling it twice. """ for name in names: - meth: Optional[object] = getattr(obj, name, None) + meth: object | None = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: return meth return None @@ -735,14 +732,14 @@ class Class(PyCollector): """Collector for test methods (and nested classes) in a Python class.""" @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw) -> "Self": # type: ignore[override] + def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] """The public constructor.""" return super().from_parent(name=name, parent=parent, **kw) def newinstance(self): return self.obj() - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): @@ -872,21 +869,21 @@ class IdMaker: parametersets: Sequence[ParameterSet] # Optionally, a user-provided callable to make IDs for parameters in a # ParameterSet. - idfn: Optional[Callable[[Any], Optional[object]]] + idfn: Callable[[Any], object | None] | None # Optionally, explicit IDs for ParameterSets by index. - ids: Optional[Sequence[Optional[object]]] + ids: Sequence[object | None] | None # Optionally, the pytest config. # Used for controlling ASCII escaping, and for calling the # :hook:`pytest_make_parametrize_id` hook. - config: Optional[Config] + config: Config | None # Optionally, the ID of the node being parametrized. # Used only for clearer error messages. - nodeid: Optional[str] + nodeid: str | None # Optionally, the ID of the function being parametrized. # Used only for clearer error messages. - func_name: Optional[str] + func_name: str | None - def make_unique_parameterset_ids(self) -> List[str]: + def make_unique_parameterset_ids(self) -> list[str]: """Make a unique identifier for each ParameterSet, that may be used to identify the parametrization in a node ID. @@ -903,7 +900,7 @@ def make_unique_parameterset_ids(self) -> List[str]: # Record the number of occurrences of each ID. id_counts = Counter(resolved_ids) # Map the ID to its next suffix. - id_suffixes: Dict[str, int] = defaultdict(int) + id_suffixes: dict[str, int] = defaultdict(int) # Suffix non-unique IDs to make them unique. for index, id in enumerate(resolved_ids): if id_counts[id] > 1: @@ -950,9 +947,7 @@ def _idval(self, val: object, argname: str, idx: int) -> str: return idval return self._idval_from_argname(argname, idx) - def _idval_from_function( - self, val: object, argname: str, idx: int - ) -> Optional[str]: + def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: """Try to make an ID for a parameter in a ParameterSet using the user-provided id callable, if given.""" if self.idfn is None: @@ -968,17 +963,17 @@ def _idval_from_function( return None return self._idval_from_value(id) - def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + def _idval_from_hook(self, val: object, argname: str) -> str | None: """Try to make an ID for a parameter in a ParameterSet by calling the :hook:`pytest_make_parametrize_id` hook.""" if self.config: - id: Optional[str] = self.config.hook.pytest_make_parametrize_id( + id: str | None = self.config.hook.pytest_make_parametrize_id( config=self.config, val=val, argname=argname ) return id return None - def _idval_from_value(self, val: object) -> Optional[str]: + def _idval_from_value(self, val: object) -> str | None: """Try to make an ID for a parameter in a ParameterSet from its value, if the value type is supported.""" if isinstance(val, (str, bytes)): @@ -1036,15 +1031,15 @@ class CallSpec2: # arg name -> arg value which will be passed to a fixture or pseudo-fixture # of the same name. (indirect or direct parametrization respectively) - params: Dict[str, object] = dataclasses.field(default_factory=dict) + params: dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = dataclasses.field(default_factory=dict) + indices: dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. - marks: List[Mark] = dataclasses.field(default_factory=list) + marks: list[Mark] = dataclasses.field(default_factory=list) def setmulti( self, @@ -1052,10 +1047,10 @@ def setmulti( argnames: Iterable[str], valset: Iterable[object], id: str, - marks: Iterable[Union[Mark, MarkDecorator]], + marks: Iterable[Mark | MarkDecorator], scope: Scope, param_index: int, - ) -> "CallSpec2": + ) -> CallSpec2: params = self.params.copy() indices = self.indices.copy() arg2scope = dict(self._arg2scope) @@ -1103,7 +1098,7 @@ class Metafunc: def __init__( self, - definition: "FunctionDefinition", + definition: FunctionDefinition, fixtureinfo: fixtures.FuncFixtureInfo, config: Config, cls=None, @@ -1134,19 +1129,17 @@ def __init__( self._arg2fixturedefs = fixtureinfo.name2fixturedefs # Result of parametrize(). - self._calls: List[CallSpec2] = [] + self._calls: list[CallSpec2] = [] def parametrize( self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], - indirect: Union[bool, Sequence[str]] = False, - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - scope: Optional[_ScopeName] = None, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + indirect: bool | Sequence[str] = False, + ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, + scope: _ScopeName | None = None, *, - _param_mark: Optional[Mark] = None, + _param_mark: Mark | None = None, ) -> None: """Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed @@ -1275,7 +1268,7 @@ def parametrize( if node is None: name2pseudofixturedef = None else: - default: Dict[str, FixtureDef[Any]] = {} + default: dict[str, FixtureDef[Any]] = {} name2pseudofixturedef = node.stash.setdefault( name2pseudofixturedef_key, default ) @@ -1322,12 +1315,10 @@ def parametrize( def _resolve_parameter_set_ids( self, argnames: Sequence[str], - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ], + ids: Iterable[object | None] | Callable[[Any], object | None] | None, parametersets: Sequence[ParameterSet], nodeid: str, - ) -> List[str]: + ) -> list[str]: """Resolve the actual ids for the given parameter sets. :param argnames: @@ -1365,10 +1356,10 @@ def _resolve_parameter_set_ids( def _validate_ids( self, - ids: Iterable[Optional[object]], + ids: Iterable[object | None], parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Optional[object]]: + ) -> list[object | None]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1388,8 +1379,8 @@ def _validate_ids( def _resolve_args_directness( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], - ) -> Dict[str, Literal["indirect", "direct"]]: + indirect: bool | Sequence[str], + ) -> dict[str, Literal["indirect", "direct"]]: """Resolve if each parametrized argument must be considered an indirect parameter to a fixture of the same name, or a direct parameter to the parametrized function, based on the ``indirect`` parameter of the @@ -1402,7 +1393,7 @@ def _resolve_args_directness( :returns A dict mapping each arg name to either "indirect" or "direct". """ - arg_directness: Dict[str, Literal["indirect", "direct"]] + arg_directness: dict[str, Literal["indirect", "direct"]] if isinstance(indirect, bool): arg_directness = dict.fromkeys( argnames, "indirect" if indirect else "direct" @@ -1427,7 +1418,7 @@ def _resolve_args_directness( def _validate_if_using_arg_names( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1458,7 +1449,7 @@ def _validate_if_using_arg_names( def _find_parametrized_scope( argnames: Sequence[str], arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> Scope: """Find the most appropriate scope for a parametrized call based on its arguments. @@ -1487,7 +1478,7 @@ def _find_parametrized_scope( return Scope.Function -def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: +def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: if config is None: escape_option = False else: @@ -1536,13 +1527,13 @@ def __init__( self, name: str, parent, - config: Optional[Config] = None, - callspec: Optional[CallSpec2] = None, + config: Config | None = None, + callspec: CallSpec2 | None = None, callobj=NOTSET, - keywords: Optional[Mapping[str, Any]] = None, - session: Optional[Session] = None, - fixtureinfo: Optional[FuncFixtureInfo] = None, - originalname: Optional[str] = None, + keywords: Mapping[str, Any] | None = None, + session: Session | None = None, + fixtureinfo: FuncFixtureInfo | None = None, + originalname: str | None = None, ) -> None: super().__init__(name, parent, config=config, session=session) @@ -1585,12 +1576,12 @@ def __init__( # todo: determine sound type limitations @classmethod - def from_parent(cls, parent, **kw) -> "Self": + def from_parent(cls, parent, **kw) -> Self: """The public constructor.""" return super().from_parent(parent=parent, **kw) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = fixtures.TopRequest(self, _ispytest=True) @property @@ -1671,7 +1662,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: style = self.config.getoption("tbstyle", "auto") if style == "auto": style = "long" diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index bfc8dc33445..c1e851391b0 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from collections.abc import Collection from collections.abc import Sized from decimal import Decimal @@ -11,9 +13,7 @@ from typing import cast from typing import ContextManager from typing import final -from typing import List from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence @@ -21,7 +21,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import _pytest._code from _pytest.outcomes import fail @@ -33,12 +32,12 @@ def _compare_approx( full_object: object, - message_data: Sequence[Tuple[str, str, str]], + message_data: Sequence[tuple[str, str, str]], number_of_elements: int, different_ids: Sequence[object], max_abs_diff: float, max_rel_diff: float, -) -> List[str]: +) -> list[str]: message_list = list(message_data) message_list.insert(0, ("Index", "Obtained", "Expected")) max_sizes = [0, 0, 0] @@ -79,7 +78,7 @@ def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: def __repr__(self) -> str: raise NotImplementedError - def _repr_compare(self, other_side: Any) -> List[str]: + def _repr_compare(self, other_side: Any) -> list[str]: return [ "comparison failed", f"Obtained: {other_side}", @@ -103,7 +102,7 @@ def __bool__(self): def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> "ApproxScalar": + def _approx_scalar(self, x) -> ApproxScalar: if isinstance(x, Decimal): return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) @@ -144,12 +143,12 @@ def __repr__(self) -> str: ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]: + def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]: import itertools import math def get_value_from_nested_list( - nested_list: List[Any], nd_index: Tuple[Any, ...] + nested_list: list[Any], nd_index: tuple[Any, ...] ) -> Any: """ Helper function to get the value out of a nested list, given an n-dimensional index. @@ -246,7 +245,7 @@ class ApproxMapping(ApproxBase): def __repr__(self) -> str: return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" - def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: + def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: import math approx_side_as_map = { @@ -321,7 +320,7 @@ def __repr__(self) -> str: seq_type = list return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" - def _repr_compare(self, other_side: Sequence[float]) -> List[str]: + def _repr_compare(self, other_side: Sequence[float]) -> list[str]: import math if len(self.expected) != len(other_side): @@ -386,8 +385,8 @@ class ApproxScalar(ApproxBase): # Using Real should be better than this Union, but not possible yet: # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 + DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 def __repr__(self) -> str: """Return a string communicating both the expected value and the @@ -717,7 +716,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: Type[ApproxBase] = ApproxDecimal + cls: type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): @@ -750,7 +749,7 @@ def _is_numpy_array(obj: object) -> bool: return _as_numpy_array(obj) is not None -def _as_numpy_array(obj: object) -> Optional["ndarray"]: +def _as_numpy_array(obj: object) -> ndarray | None: """ Return an ndarray if the given object is implicitly convertible to ndarray, and numpy is already imported, otherwise None. @@ -776,15 +775,15 @@ def _as_numpy_array(obj: object) -> Optional["ndarray"]: @overload def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[E]": ... + match: str | Pattern[str] | None = ..., +) -> RaisesContext[E]: ... @overload def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], func: Callable[..., Any], *args: Any, **kwargs: Any, @@ -792,8 +791,8 @@ def raises( def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any -) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: + expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any +) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]: r"""Assert that a code block/function call raises an exception type, or one of its subclasses. :param expected_exception: @@ -941,7 +940,7 @@ def raises( f"any special code to say 'this should never raise an exception'." ) if isinstance(expected_exception, type): - expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) + expected_exceptions: tuple[type[E], ...] = (expected_exception,) else: expected_exceptions = expected_exception for exc in expected_exceptions: @@ -953,7 +952,7 @@ def raises( message = f"DID NOT RAISE {expected_exception}" if not args: - match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) + match: str | Pattern[str] | None = kwargs.pop("match", None) if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " msg += ", ".join(sorted(kwargs)) @@ -979,14 +978,14 @@ def raises( class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], message: str, - match_expr: Optional[Union[str, Pattern[str]]] = None, + match_expr: str | Pattern[str] | None = None, ) -> None: self.expected_exception = expected_exception self.message = message self.match_expr = match_expr - self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None + self.excinfo: _pytest._code.ExceptionInfo[E] | None = None def __enter__(self) -> _pytest._code.ExceptionInfo[E]: self.excinfo = _pytest._code.ExceptionInfo.for_later() @@ -994,9 +993,9 @@ def __enter__(self) -> _pytest._code.ExceptionInfo[E]: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> bool: __tracebackhide__ = True if exc_type is None: diff --git a/src/_pytest/python_path.py b/src/_pytest/python_path.py index cceabbca12a..6e33c8a39f2 100644 --- a/src/_pytest/python_path.py +++ b/src/_pytest/python_path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import pytest diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 63e7a4bd6dc..3fc00d94736 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Record warnings during test function execution.""" +from __future__ import annotations + from pprint import pformat import re from types import TracebackType @@ -9,14 +11,15 @@ from typing import final from typing import Generator from typing import Iterator -from typing import List -from typing import Optional from typing import overload from typing import Pattern -from typing import Tuple -from typing import Type +from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union + + +if TYPE_CHECKING: + from typing_extensions import Self + import warnings from _pytest.deprecated import check_ispytest @@ -29,7 +32,7 @@ @fixture -def recwarn() -> Generator["WarningsRecorder", None, None]: +def recwarn() -> Generator[WarningsRecorder, None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information @@ -42,9 +45,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: @overload -def deprecated_call( - *, match: Optional[Union[str, Pattern[str]]] = ... -) -> "WarningsRecorder": ... +def deprecated_call(*, match: str | Pattern[str] | None = ...) -> WarningsRecorder: ... @overload @@ -52,8 +53,8 @@ def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... def deprecated_call( - func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any -) -> Union["WarningsRecorder", Any]: + func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any +) -> WarningsRecorder | Any: """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. This function can be used as a context manager:: @@ -87,15 +88,15 @@ def deprecated_call( @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., + expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "WarningsChecker": ... + match: str | Pattern[str] | None = ..., +) -> WarningsChecker: ... @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], + expected_warning: type[Warning] | tuple[type[Warning], ...], func: Callable[..., T], *args: Any, **kwargs: Any, @@ -103,11 +104,11 @@ def warns( def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, *args: Any, - match: Optional[Union[str, Pattern[str]]] = None, + match: str | Pattern[str] | None = None, **kwargs: Any, -) -> Union["WarningsChecker", Any]: +) -> WarningsChecker | Any: r"""Assert that code raises a particular class of warning. Specifically, the parameter ``expected_warning`` can be a warning class or tuple @@ -183,18 +184,18 @@ def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) super().__init__(record=True) self._entered = False - self._list: List[warnings.WarningMessage] = [] + self._list: list[warnings.WarningMessage] = [] @property - def list(self) -> List["warnings.WarningMessage"]: + def list(self) -> list[warnings.WarningMessage]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i: int) -> "warnings.WarningMessage": + def __getitem__(self, i: int) -> warnings.WarningMessage: """Get a recorded warning by index.""" return self._list[i] - def __iter__(self) -> Iterator["warnings.WarningMessage"]: + def __iter__(self) -> Iterator[warnings.WarningMessage]: """Iterate through the recorded warnings.""" return iter(self._list) @@ -202,12 +203,12 @@ def __len__(self) -> int: """The number of recorded warnings.""" return len(self._list) - def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": + def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: """Pop the first recorded warning which is an instance of ``cls``, but not an instance of a child class of any other match. Raises ``AssertionError`` if there is no match. """ - best_idx: Optional[int] = None + best_idx: int | None = None for i, w in enumerate(self._list): if w.category == cls: return self._list.pop(i) # exact match, stop looking @@ -225,9 +226,7 @@ def clear(self) -> None: """Clear the list of recorded warnings.""" self._list[:] = [] - # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ - # -- it returns a List but we only emulate one. - def __enter__(self) -> "WarningsRecorder": # type: ignore + def __enter__(self) -> Self: if self._entered: __tracebackhide__ = True raise RuntimeError(f"Cannot enter {self!r} twice") @@ -240,9 +239,9 @@ def __enter__(self) -> "WarningsRecorder": # type: ignore def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: if not self._entered: __tracebackhide__ = True @@ -259,8 +258,8 @@ def __exit__( class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, - match_expr: Optional[Union[str, Pattern[str]]] = None, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + match_expr: str | Pattern[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -291,9 +290,9 @@ def matches(self, warning: warnings.WarningMessage) -> bool: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: super().__exit__(exc_type, exc_val, exc_tb) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 2064183d0f7..2f39adbfa6f 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,24 +1,19 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses from io import StringIO import os from pprint import pprint from typing import Any from typing import cast -from typing import Dict from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -39,6 +34,8 @@ if TYPE_CHECKING: + from typing_extensions import Self + from _pytest.runner import CallInfo @@ -54,16 +51,13 @@ def getworkerinfoline(node): return s -_R = TypeVar("_R", bound="BaseReport") - - class BaseReport: - when: Optional[str] - location: Optional[Tuple[str, Optional[int], str]] - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ] - sections: List[Tuple[str, str]] + when: str | None + location: tuple[str, int | None, str] | None + longrepr: ( + None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr + ) + sections: list[tuple[str, str]] nodeid: str outcome: Literal["passed", "failed", "skipped"] @@ -94,7 +88,7 @@ def toterminal(self, out: TerminalWriter) -> None: s = "" out.line(s) - def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: + def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @@ -176,7 +170,7 @@ def count_towards_summary(self) -> bool: return True @property - def head_line(self) -> Optional[str]: + def head_line(self) -> str | None: """**Experimental** The head line shown with longrepr output for this report, more commonly during traceback representation during failures:: @@ -202,7 +196,7 @@ def _get_verbose_word(self, config: Config): ) return verbose - def _to_json(self) -> Dict[str, Any]: + def _to_json(self) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -213,7 +207,7 @@ def _to_json(self) -> Dict[str, Any]: return _report_to_json(self) @classmethod - def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: + def _from_json(cls, reportdict: dict[str, object]) -> Self: """Create either a TestReport or CollectReport, depending on the calling class. It is the callers responsibility to know which class to pass here. @@ -227,7 +221,7 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: def _report_unserialization_failure( - type_name: str, report_class: Type[BaseReport], reportdict + type_name: str, report_class: type[BaseReport], reportdict ) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() @@ -256,18 +250,20 @@ class TestReport(BaseReport): def __init__( self, nodeid: str, - location: Tuple[str, Optional[int], str], + location: tuple[str, int | None, str], keywords: Mapping[str, Any], outcome: Literal["passed", "failed", "skipped"], - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, when: Literal["setup", "call", "teardown"], - sections: Iterable[Tuple[str, str]] = (), + sections: Iterable[tuple[str, str]] = (), duration: float = 0, start: float = 0, stop: float = 0, - user_properties: Optional[Iterable[Tuple[str, object]]] = None, + user_properties: Iterable[tuple[str, object]] | None = None, **extra, ) -> None: #: Normalized collection nodeid. @@ -278,7 +274,7 @@ def __init__( #: collected one e.g. if a method is inherited from a different module. #: The filesystempath may be relative to ``config.rootdir``. #: The line number is 0-based. - self.location: Tuple[str, Optional[int], str] = location + self.location: tuple[str, int | None, str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. @@ -317,7 +313,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" @classmethod - def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": + def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: """Create and fill a TestReport with standard item and call info. :param item: The item. @@ -334,13 +330,13 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": sections = [] if not call.excinfo: outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: Union[ - None, - ExceptionInfo[BaseException], - Tuple[str, int, str], - str, - TerminalRepr, - ] = None + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ) = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" @@ -394,12 +390,14 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'failed', 'skipped']", - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], - result: Optional[List[Union[Item, Collector]]], - sections: Iterable[Tuple[str, str]] = (), + outcome: Literal["passed", "failed", "skipped"], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, + result: list[Item | Collector] | None, + sections: Iterable[tuple[str, str]] = (), **extra, ) -> None: #: Normalized collection nodeid. @@ -425,7 +423,7 @@ def __init__( @property def location( # type:ignore[override] self, - ) -> Optional[Tuple[str, Optional[int], str]]: + ) -> tuple[str, int | None, str] | None: return (self.fspath, None, self.fspath) def __repr__(self) -> str: @@ -441,8 +439,8 @@ def toterminal(self, out: TerminalWriter) -> None: def pytest_report_to_serializable( - report: Union[CollectReport, TestReport], -) -> Optional[Dict[str, Any]]: + report: CollectReport | TestReport, +) -> dict[str, Any] | None: if isinstance(report, (TestReport, CollectReport)): data = report._to_json() data["$report_type"] = report.__class__.__name__ @@ -452,8 +450,8 @@ def pytest_report_to_serializable( def pytest_report_from_serializable( - data: Dict[str, Any], -) -> Optional[Union[CollectReport, TestReport]]: + data: dict[str, Any], +) -> CollectReport | TestReport | None: if "$report_type" in data: if data["$report_type"] == "TestReport": return TestReport._from_json(data) @@ -465,7 +463,7 @@ def pytest_report_from_serializable( return None -def _report_to_json(report: BaseReport) -> Dict[str, Any]: +def _report_to_json(report: BaseReport) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -473,8 +471,8 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: """ def serialize_repr_entry( - entry: Union[ReprEntry, ReprEntryNative], - ) -> Dict[str, Any]: + entry: ReprEntry | ReprEntryNative, + ) -> dict[str, Any]: data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): @@ -482,7 +480,7 @@ def serialize_repr_entry( entry_data = {"type": type(entry).__name__, "data": data} return entry_data - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries @@ -490,18 +488,18 @@ def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: return result def serialize_repr_crash( - reprcrash: Optional[ReprFileLocation], - ) -> Optional[Dict[str, Any]]: + reprcrash: ReprFileLocation | None, + ) -> dict[str, Any] | None: if reprcrash is not None: return dataclasses.asdict(reprcrash) else: return None - def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: + def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: assert rep.longrepr is not None # TODO: Investigate whether the duck typing is really necessary here. longrepr = cast(ExceptionRepr, rep.longrepr) - result: Dict[str, Any] = { + result: dict[str, Any] = { "reprcrash": serialize_repr_crash(longrepr.reprcrash), "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), "sections": longrepr.sections, @@ -538,7 +536,7 @@ def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: return d -def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: +def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: """Return **kwargs that can be used to construct a TestReport or CollectReport instance. @@ -559,7 +557,7 @@ def deserialize_repr_entry(entry_data): if data["reprlocals"]: reprlocals = ReprLocals(data["reprlocals"]["lines"]) - reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry( + reprentry: ReprEntry | ReprEntryNative = ReprEntry( lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, @@ -578,7 +576,7 @@ def deserialize_repr_traceback(repr_traceback_dict): ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): + def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): if repr_crash_dict is not None: return ReprFileLocation(**repr_crash_dict) else: @@ -605,8 +603,8 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): description, ) ) - exception_info: Union[ExceptionChainRepr, ReprExceptionInfo] = ( - ExceptionChainRepr(chain) + exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( + chain ) else: exception_info = ReprExceptionInfo( diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e5af60e388b..bf30a7d2d27 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Basic collect and runtest protocol implementations.""" +from __future__ import annotations + import bdb import dataclasses import os @@ -8,17 +10,11 @@ import types from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import Generic -from typing import List from typing import Literal -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from .reports import BaseReport from .reports import CollectErrorRepr @@ -72,7 +68,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: durations = terminalreporter.config.option.durations durations_min = terminalreporter.config.option.durations_min verbose = terminalreporter.config.getvalue("verbose") @@ -103,15 +99,15 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: session._setupstate.teardown_exact(None) -def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: ihook = item.ihook ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) runtestprotocol(item, nextitem=nextitem) @@ -120,8 +116,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: def runtestprotocol( - item: Item, log: bool = True, nextitem: Optional[Item] = None -) -> List[TestReport]: + item: Item, log: bool = True, nextitem: Item | None = None +) -> list[TestReport]: hasrequest = hasattr(item, "_request") if hasrequest and not item._request: # type: ignore[attr-defined] # This only happens if the item is re-run, as is done by @@ -188,14 +184,14 @@ def pytest_runtest_call(item: Item) -> None: raise -def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: _update_current_test_var(item, "teardown") item.session._setupstate.teardown_exact(nextitem) _update_current_test_var(item, None) def _update_current_test_var( - item: Item, when: Optional[Literal["setup", "call", "teardown"]] + item: Item, when: Literal["setup", "call", "teardown"] | None ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -211,7 +207,7 @@ def _update_current_test_var( os.environ.pop(var_name) -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -239,7 +235,7 @@ def call_and_report( runtest_hook = ihook.pytest_runtest_teardown else: assert False, f"Unhandled runtest hook case: {when}" - reraise: Tuple[Type[BaseException], ...] = (Exit,) + reraise: tuple[type[BaseException], ...] = (Exit,) if not item.config.getoption("usepdb", False): reraise += (KeyboardInterrupt,) call = CallInfo.from_call( @@ -253,7 +249,7 @@ def call_and_report( return report -def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: +def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: """Check whether the call raised an exception that should be reported as interactive.""" if call.excinfo is None: @@ -276,9 +272,9 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" - _result: Optional[TResult] + _result: TResult | None #: The captured exception of the call, if it raised. - excinfo: Optional[ExceptionInfo[BaseException]] + excinfo: ExceptionInfo[BaseException] | None #: The system time when the call started, in seconds since the epoch. start: float #: The system time when the call ended, in seconds since the epoch. @@ -290,8 +286,8 @@ class CallInfo(Generic[TResult]): def __init__( self, - result: Optional[TResult], - excinfo: Optional[ExceptionInfo[BaseException]], + result: TResult | None, + excinfo: ExceptionInfo[BaseException] | None, start: float, stop: float, duration: float, @@ -325,10 +321,8 @@ def from_call( cls, func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], - reraise: Optional[ - Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ] = None, - ) -> "CallInfo[TResult]": + reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, + ) -> CallInfo[TResult]: """Call func, wrapping the result in a CallInfo. :param func: @@ -343,7 +337,7 @@ def from_call( start = timing.time() precise_start = timing.perf_counter() try: - result: Optional[TResult] = func() + result: TResult | None = func() except BaseException: excinfo = ExceptionInfo.from_current() if reraise is not None and isinstance(excinfo.value, reraise): @@ -374,7 +368,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - def collect() -> List[Union[Item, Collector]]: + def collect() -> list[Item | Collector]: # Before collecting, if this is a Directory, load the conftests. # If a conftest import fails to load, it is considered a collection # error of the Directory collector. This is why it's done inside of the @@ -396,7 +390,7 @@ def collect() -> List[Union[Item, Collector]]: call = CallInfo.from_call( collect, "collect", reraise=(KeyboardInterrupt, SystemExit) ) - longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None + longrepr: None | tuple[str, int, str] | str | TerminalRepr = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" else: @@ -490,18 +484,13 @@ class SetupState: def __init__(self) -> None: # The stack is in the dict insertion order. - self.stack: Dict[ + self.stack: dict[ Node, - Tuple[ + tuple[ # Node's finalizers. - List[Callable[[], object]], + list[Callable[[], object]], # Node's exception and original traceback, if its setup raised. - Optional[ - Tuple[ - Union[OutcomeException, Exception], - Optional[types.TracebackType], - ] - ], + tuple[OutcomeException | Exception, types.TracebackType | None] | None, ], ] = {} @@ -536,7 +525,7 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: assert node in self.stack, (node, self.stack) self.stack[node][0].append(finalizer) - def teardown_exact(self, nextitem: Optional[Item]) -> None: + def teardown_exact(self, nextitem: Item | None) -> None: """Teardown the current stack up until reaching nodes that nextitem also descends from. @@ -544,7 +533,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: stack is torn down. """ needed_collectors = nextitem and nextitem.listchain() or [] - exceptions: List[BaseException] = [] + exceptions: list[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break diff --git a/src/_pytest/scope.py b/src/_pytest/scope.py index 2c6e23208f2..976a3ba242e 100644 --- a/src/_pytest/scope.py +++ b/src/_pytest/scope.py @@ -8,10 +8,11 @@ Also this makes the module light to import, as it should. """ +from __future__ import annotations + from enum import Enum from functools import total_ordering from typing import Literal -from typing import Optional _ScopeName = Literal["session", "package", "module", "class", "function"] @@ -38,29 +39,29 @@ class Scope(Enum): Package: _ScopeName = "package" Session: _ScopeName = "session" - def next_lower(self) -> "Scope": + def next_lower(self) -> Scope: """Return the next lower scope.""" index = _SCOPE_INDICES[self] if index == 0: raise ValueError(f"{self} is the lower-most scope") return _ALL_SCOPES[index - 1] - def next_higher(self) -> "Scope": + def next_higher(self) -> Scope: """Return the next higher scope.""" index = _SCOPE_INDICES[self] if index == len(_SCOPE_INDICES) - 1: raise ValueError(f"{self} is the upper-most scope") return _ALL_SCOPES[index + 1] - def __lt__(self, other: "Scope") -> bool: + def __lt__(self, other: Scope) -> bool: self_index = _SCOPE_INDICES[self] other_index = _SCOPE_INDICES[other] return self_index < other_index @classmethod def from_user( - cls, scope_name: _ScopeName, descr: str, where: Optional[str] = None - ) -> "Scope": + cls, scope_name: _ScopeName, descr: str, where: str | None = None + ) -> Scope: """ Given a scope name from the user, return the equivalent Scope enum. Should be used whenever we want to convert a user provided scope name to its enum object. diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 39ab28b466b..de297f408d3 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -1,6 +1,6 @@ +from __future__ import annotations + from typing import Generator -from typing import Optional -from typing import Union from _pytest._io.saferepr import saferepr from _pytest.config import Config @@ -96,7 +96,7 @@ def _show_fixture_action( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setuponly: config.option.setupshow = True return None diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py index 13c0df84ea1..4e124cce243 100644 --- a/src/_pytest/setupplan.py +++ b/src/_pytest/setupplan.py @@ -1,5 +1,4 @@ -from typing import Optional -from typing import Union +from __future__ import annotations from _pytest.config import Config from _pytest.config import ExitCode @@ -23,7 +22,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Optional[object]: +) -> object | None: # Will return a dummy fixture if the setuponly option is provided. if request.config.option.setupplan: my_cache_key = fixturedef.cache_key(request) @@ -33,7 +32,7 @@ def pytest_fixture_setup( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 54500b2851b..08fcb283eb2 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Support for skip/xfail functions and markers.""" +from __future__ import annotations + from collections.abc import Mapping import dataclasses import os @@ -9,8 +11,6 @@ import traceback from typing import Generator from typing import Optional -from typing import Tuple -from typing import Type from _pytest.config import Config from _pytest.config import hookimpl @@ -84,7 +84,7 @@ def nop(*args, **kwargs): ) -def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: +def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: """Evaluate a single skipif/xfail condition. If an old-style string condition is given, it is eval()'d, otherwise the @@ -164,7 +164,7 @@ class Skip: reason: str = "unconditional skip" -def evaluate_skip_marks(item: Item) -> Optional[Skip]: +def evaluate_skip_marks(item: Item) -> Skip | None: """Evaluate skip and skipif marks on item, returning Skip if triggered.""" for mark in item.iter_markers(name="skipif"): if "condition" not in mark.kwargs: @@ -201,10 +201,10 @@ class Xfail: reason: str run: bool strict: bool - raises: Optional[Tuple[Type[BaseException], ...]] + raises: tuple[type[BaseException], ...] | None -def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: +def evaluate_xfail_marks(item: Item) -> Xfail | None: """Evaluate xfail marks on item, returning Xfail if triggered.""" for mark in item.iter_markers(name="xfail"): run = mark.kwargs.get("run", True) @@ -292,7 +292,7 @@ def pytest_runtest_makereport( return rep -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" diff --git a/src/_pytest/stash.py b/src/_pytest/stash.py index a4b829fc6dd..6a9ff884e04 100644 --- a/src/_pytest/stash.py +++ b/src/_pytest/stash.py @@ -1,9 +1,9 @@ +from __future__ import annotations + from typing import Any from typing import cast -from typing import Dict from typing import Generic from typing import TypeVar -from typing import Union __all__ = ["Stash", "StashKey"] @@ -70,7 +70,7 @@ class Stash: __slots__ = ("_storage",) def __init__(self) -> None: - self._storage: Dict[StashKey[Any], object] = {} + self._storage: dict[StashKey[Any], object] = {} def __setitem__(self, key: StashKey[T], value: T) -> None: """Set a value for key.""" @@ -83,7 +83,7 @@ def __getitem__(self, key: StashKey[T]) -> T: """ return cast(T, self._storage[key]) - def get(self, key: StashKey[T], default: D) -> Union[T, D]: + def get(self, key: StashKey[T], default: D) -> T | D: """Get the value for key, or return default if the key wasn't set before.""" try: diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 1e3a09d9678..bd906ce63c1 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -1,5 +1,4 @@ -from typing import List -from typing import Optional +from __future__ import annotations from _pytest import nodes from _pytest.cacheprovider import Cache @@ -55,18 +54,18 @@ def pytest_sessionfinish(session: Session) -> None: class StepwisePlugin: def __init__(self, config: Config) -> None: self.config = config - self.session: Optional[Session] = None + self.session: Session | None = None self.report_status = "" assert config.cache is not None self.cache: Cache = config.cache - self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None) + self.lastfailed: str | None = self.cache.get(STEPWISE_CACHE_DIR, None) self.skip: bool = config.getoption("stepwise_skip") def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> None: if not self.lastfailed: self.report_status = "no previously failed tests, not skipping." @@ -113,7 +112,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if report.nodeid == self.lastfailed: self.lastfailed = None - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.config.getoption("verbose") >= 0 and self.report_status: return f"stepwise: {self.report_status}" return None diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 2eef8d1f227..26c573f583e 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -4,6 +4,8 @@ This is a good source for looking at the various reporting hooks. """ +from __future__ import annotations + import argparse from collections import Counter import dataclasses @@ -17,20 +19,14 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import final from typing import Generator -from typing import List from typing import Literal from typing import Mapping from typing import NamedTuple -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -90,7 +86,7 @@ def __init__( dest: str, default: object = None, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: super().__init__( option_strings=option_strings, @@ -105,8 +101,8 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[object], None], - option_string: Optional[str] = None, + values: str | Sequence[object] | None, + option_string: str | None = None, ) -> None: new_count = getattr(namespace, self.dest, 0) - 1 setattr(namespace, self.dest, new_count) @@ -131,7 +127,7 @@ class TestShortLogReport(NamedTuple): category: str letter: str - word: Union[str, Tuple[str, Mapping[str, bool]]] + word: str | tuple[str, Mapping[str, bool]] def pytest_addoption(parser: Parser) -> None: @@ -311,7 +307,7 @@ def getreportopt(config: Config) -> str: @hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: letter = "F" if report.passed: letter = "." @@ -339,12 +335,12 @@ class WarningReport: """ message: str - nodeid: Optional[str] = None - fslocation: Optional[Tuple[str, int]] = None + nodeid: str | None = None + fslocation: tuple[str, int] | None = None count_towards_summary: ClassVar = True - def get_location(self, config: Config) -> Optional[str]: + def get_location(self, config: Config) -> str | None: """Return the more user-friendly information about the location of a warning, or None.""" if self.nodeid: return self.nodeid @@ -357,31 +353,31 @@ def get_location(self, config: Config) -> Optional[str]: @final class TerminalReporter: - def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: + def __init__(self, config: Config, file: TextIO | None = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session: Optional[Session] = None - self._showfspath: Optional[bool] = None + self._session: Session | None = None + self._showfspath: bool | None = None - self.stats: Dict[str, List[Any]] = {} - self._main_color: Optional[str] = None - self._known_types: Optional[List[str]] = None + self.stats: dict[str, list[Any]] = {} + self._main_color: str | None = None + self._known_types: list[str] | None = None self.startpath = config.invocation_params.dir if file is None: file = sys.stdout self._tw = _pytest.config.create_terminal_writer(config, file) self._screen_width = self._tw.fullwidth - self.currentfspath: Union[None, Path, str, int] = None + self.currentfspath: None | Path | str | int = None self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() - self._progress_nodeids_reported: Set[str] = set() + self._progress_nodeids_reported: set[str] = set() self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write: Optional[float] = None - self._already_displayed_warnings: Optional[int] = None - self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None + self._collect_report_last_write: float | None = None + self._already_displayed_warnings: int | None = None + self._keyboardinterrupt_memo: ExceptionRepr | None = None def _determine_show_progress_info(self) -> Literal["progress", "count", False]: """Return whether we should display progress information based on the current config.""" @@ -428,7 +424,7 @@ def showfspath(self) -> bool: return self._showfspath @showfspath.setter - def showfspath(self, value: Optional[bool]) -> None: + def showfspath(self, value: bool | None) -> None: self._showfspath = value @property @@ -492,7 +488,7 @@ def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: def flush(self) -> None: self._tw.flush() - def write_line(self, line: Union[str, bytes], **markup: bool) -> None: + def write_line(self, line: str | bytes, **markup: bool) -> None: if not isinstance(line, str): line = str(line, errors="replace") self.ensure_newline() @@ -519,8 +515,8 @@ def rewrite(self, line: str, **markup: bool) -> None: def write_sep( self, sep: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: self.ensure_newline() @@ -570,7 +566,7 @@ def pytest_deselected(self, items: Sequence[Item]) -> None: self._add_stats("deselected", items) def pytest_runtest_logstart( - self, nodeid: str, location: Tuple[str, Optional[int], str] + self, nodeid: str, location: tuple[str, int | None, str] ) -> None: fspath, lineno, domain = location # Ensure that the path is printed before the @@ -777,7 +773,7 @@ def report_collect(self, final: bool = False) -> None: self.write_line(line) @hookimpl(trylast=True) - def pytest_sessionstart(self, session: "Session") -> None: + def pytest_sessionstart(self, session: Session) -> None: self._session = session self._sessionstarttime = timing.time() if not self.showheader: @@ -804,7 +800,7 @@ def pytest_sessionstart(self, session: "Session") -> None: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: Sequence[Union[str, Sequence[str]]] + self, lines: Sequence[str | Sequence[str]] ) -> None: for line_or_lines in reversed(lines): if isinstance(line_or_lines, str): @@ -813,14 +809,14 @@ def _write_report_lines_from_hooks( for line in line_or_lines: self.write_line(line) - def pytest_report_header(self, config: Config) -> List[str]: + def pytest_report_header(self, config: Config) -> list[str]: result = [f"rootdir: {config.rootpath}"] if config.inipath: result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: List[str] = config.getini("testpaths") + testpaths: list[str] = config.getini("testpaths") result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() @@ -830,7 +826,7 @@ def pytest_report_header(self, config: Config) -> List[str]: ) return result - def pytest_collection_finish(self, session: "Session") -> None: + def pytest_collection_finish(self, session: Session) -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( @@ -863,7 +859,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: for item in items: self._tw.line(item.nodeid) return - stack: List[Node] = [] + stack: list[Node] = [] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -884,7 +880,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: @hookimpl(wrapper=True) def pytest_sessionfinish( - self, session: "Session", exitstatus: Union[int, ExitCode] + self, session: Session, exitstatus: int | ExitCode ) -> Generator[None, None, None]: result = yield self._tw.line("") @@ -948,7 +944,7 @@ def _report_keyboardinterrupt(self) -> None: ) def _locationline( - self, nodeid: str, fspath: str, lineno: Optional[int], domain: str + self, nodeid: str, fspath: str, lineno: int | None, domain: str ) -> str: def mkrel(nodeid: str) -> str: line = self.config.cwd_relative_nodeid(nodeid) @@ -993,7 +989,7 @@ def getreports(self, name: str): def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings") + all_warnings: list[WarningReport] | None = self.stats.get("warnings") if not all_warnings: return @@ -1006,11 +1002,11 @@ def summary_warnings(self) -> None: if not warning_reports: return - reports_grouped_by_message: Dict[str, List[WarningReport]] = {} + reports_grouped_by_message: dict[str, list[WarningReport]] = {} for wr in warning_reports: reports_grouped_by_message.setdefault(wr.message, []).append(wr) - def collapsed_location_report(reports: List[WarningReport]) -> str: + def collapsed_location_report(reports: list[WarningReport]) -> str: locations = [] for w in reports: location = w.get_location(self.config) @@ -1056,7 +1052,7 @@ def summary_passes_combined( ) -> None: if self.config.option.tbstyle != "no": if self.hasopt(needed_opt): - reports: List[TestReport] = self.getreports(which_reports) + reports: list[TestReport] = self.getreports(which_reports) if not reports: return self.write_sep("=", sep_title) @@ -1067,7 +1063,7 @@ def summary_passes_combined( self._outrep_summary(rep) self._handle_teardown_sections(rep.nodeid) - def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: + def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: reports = self.getreports("") return [ report @@ -1107,11 +1103,11 @@ def summary_failures_combined( sep_title: str, *, style: str, - needed_opt: Optional[str] = None, + needed_opt: str | None = None, ) -> None: if style != "no": if not needed_opt or self.hasopt(needed_opt): - reports: List[BaseReport] = self.getreports(which_reports) + reports: list[BaseReport] = self.getreports(which_reports) if not reports: return self.write_sep("=", sep_title) @@ -1128,7 +1124,7 @@ def summary_failures_combined( def summary_errors(self) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("error") + reports: list[BaseReport] = self.getreports("error") if not reports: return self.write_sep("=", "ERRORS") @@ -1195,7 +1191,7 @@ def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(lines: List[str], *, stat: str) -> None: + def show_simple(lines: list[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return @@ -1207,7 +1203,7 @@ def show_simple(lines: List[str], *, stat: str) -> None: ) lines.append(line) - def show_xfailed(lines: List[str]) -> None: + def show_xfailed(lines: list[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: verbose_word = rep._get_verbose_word(self.config) @@ -1222,7 +1218,7 @@ def show_xfailed(lines: List[str]) -> None: lines.append(line) - def show_xpassed(lines: List[str]) -> None: + def show_xpassed(lines: list[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: verbose_word = rep._get_verbose_word(self.config) @@ -1236,8 +1232,8 @@ def show_xpassed(lines: List[str]) -> None: line += " - " + str(reason) lines.append(line) - def show_skipped(lines: List[str]) -> None: - skipped: List[CollectReport] = self.stats.get("skipped", []) + def show_skipped(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return @@ -1256,7 +1252,7 @@ def show_skipped(lines: List[str]) -> None: else: lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) - REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { + REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { "x": show_xfailed, "X": show_xpassed, "f": partial(show_simple, stat="failed"), @@ -1265,7 +1261,7 @@ def show_skipped(lines: List[str]) -> None: "E": partial(show_simple, stat="error"), } - lines: List[str] = [] + lines: list[str] = [] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -1276,7 +1272,7 @@ def show_skipped(lines: List[str]) -> None: for line in lines: self.write_line(line) - def _get_main_color(self) -> Tuple[str, List[str]]: + def _get_main_color(self) -> tuple[str, list[str]]: if self._main_color is None or self._known_types is None or self._is_last_item: self._set_main_color() assert self._main_color @@ -1296,7 +1292,7 @@ def _determine_main_color(self, unknown_type_seen: bool) -> str: return main_color def _set_main_color(self) -> None: - unknown_types: List[str] = [] + unknown_types: list[str] = [] for found_type in self.stats: if found_type: # setup/teardown reports have an empty key, ignore them if found_type not in KNOWN_TYPES and found_type not in unknown_types: @@ -1304,7 +1300,7 @@ def _set_main_color(self) -> None: self._known_types = list(KNOWN_TYPES) + unknown_types self._main_color = self._determine_main_color(bool(unknown_types)) - def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: """ Build the parts used in the last summary stats line. @@ -1329,14 +1325,14 @@ def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], s else: return self._build_normal_summary_stats_line() - def _get_reports_to_display(self, key: str) -> List[Any]: + def _get_reports_to_display(self, key: str) -> list[Any]: """Get test/collection reports for the given status key, such as `passed` or `error`.""" reports = self.stats.get(key, []) return [x for x in reports if getattr(x, "count_towards_summary", True)] def _build_normal_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: main_color, known_types = self._get_main_color() parts = [] @@ -1355,7 +1351,7 @@ def _build_normal_summary_stats_line( def _build_collect_only_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: deselected = len(self._get_reports_to_display("deselected")) errors = len(self._get_reports_to_display("error")) @@ -1396,7 +1392,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport return path -def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: +def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: """Format msg into format, ellipsizing it if doesn't fit in available_width. Returns None if even the ellipsis can't fit. @@ -1422,7 +1418,7 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" verbose_word = rep._get_verbose_word(config) @@ -1452,8 +1448,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, skipped: Sequence[CollectReport], -) -> List[Tuple[int, str, Optional[int], str]]: - d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {} +) -> list[tuple[int, str, int | None, str]]: + d: dict[tuple[str, int | None, str], list[CollectReport]] = {} for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) @@ -1470,11 +1466,11 @@ def _folded_skips( and "skip" in keywords and "pytestmark" not in keywords ): - key: Tuple[str, Optional[int], str] = (fspath, None, reason) + key: tuple[str, int | None, str] = (fspath, None, reason) else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: List[Tuple[int, str, Optional[int], str]] = [] + values: list[tuple[int, str, int | None, str]] = [] for key, events in d.items(): values.append((len(events), *key)) return values @@ -1489,7 +1485,7 @@ def _folded_skips( _color_for_type_default = "yellow" -def pluralize(count: int, noun: str) -> Tuple[int, str]: +def pluralize(count: int, noun: str) -> tuple[int, str]: # No need to pluralize words such as `failed` or `passed`. if noun not in ["error", "warnings", "test"]: return count, noun @@ -1502,8 +1498,8 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]: return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo) -> List[str]: - values: List[str] = [] +def _plugin_nameversions(plugininfo) -> list[str]: + values: list[str] = [] for plugin, dist in plugininfo: # Gets us name and version! name = f"{dist.project_name}-{dist.version}" diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 603a1777c92..d78c32c852f 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import threading import traceback from types import TracebackType from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type +from typing import TYPE_CHECKING import warnings import pytest +if TYPE_CHECKING: + from typing_extensions import Self + + # Copied from cpython/Lib/test/support/threading_helper.py, with modifications. class catch_threading_exception: """Context manager catching threading.Thread exception using @@ -34,22 +39,22 @@ class catch_threading_exception: """ def __init__(self) -> None: - self.args: Optional[threading.ExceptHookArgs] = None - self._old_hook: Optional[Callable[[threading.ExceptHookArgs], Any]] = None + self.args: threading.ExceptHookArgs | None = None + self._old_hook: Callable[[threading.ExceptHookArgs], Any] | None = None - def _hook(self, args: "threading.ExceptHookArgs") -> None: + def _hook(self, args: threading.ExceptHookArgs) -> None: self.args = args - def __enter__(self) -> "catch_threading_exception": + def __enter__(self) -> Self: self._old_hook = threading.excepthook threading.excepthook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None threading.excepthook = self._old_hook diff --git a/src/_pytest/timing.py b/src/_pytest/timing.py index 0541dc8e0a1..b23c7f69e2d 100644 --- a/src/_pytest/timing.py +++ b/src/_pytest/timing.py @@ -6,6 +6,8 @@ Fixture "mock_timing" also interacts with this module for pytest's own tests. """ +from __future__ import annotations + from time import perf_counter from time import sleep from time import time diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 72efed3e87a..91109ea69ef 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Support for providing temporary directories to test functions.""" +from __future__ import annotations + import dataclasses import os from pathlib import Path @@ -12,8 +14,6 @@ from typing import final from typing import Generator from typing import Literal -from typing import Optional -from typing import Union from .pathlib import cleanup_dead_symlinks from .pathlib import LOCK_TIMEOUT @@ -46,20 +46,20 @@ class TempPathFactory: The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp: Optional[Path] + _given_basetemp: Path | None # pluggy TagTracerSub, not currently exposed, so Any. _trace: Any - _basetemp: Optional[Path] + _basetemp: Path | None _retention_count: int _retention_policy: RetentionType def __init__( self, - given_basetemp: Optional[Path], + given_basetemp: Path | None, retention_count: int, retention_policy: RetentionType, trace, - basetemp: Optional[Path] = None, + basetemp: Path | None = None, *, _ispytest: bool = False, ) -> None: @@ -82,7 +82,7 @@ def from_config( config: Config, *, _ispytest: bool = False, - ) -> "TempPathFactory": + ) -> TempPathFactory: """Create a factory according to pytest configuration. :meta private: @@ -198,7 +198,7 @@ def getbasetemp(self) -> Path: return basetemp -def get_user() -> Optional[str]: +def get_user() -> str | None: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" try: @@ -286,7 +286,7 @@ def tmp_path( del request.node.stash[tmppath_result_key] -def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): +def pytest_sessionfinish(session, exitstatus: int | ExitCode): """After each session, remove base directory if all the tests passed, the policy is "failed", and the basetemp is not specified by a user. """ @@ -317,6 +317,6 @@ def pytest_runtest_makereport( ) -> Generator[None, TestReport, TestReport]: rep = yield assert rep.when is not None - empty: Dict[str, bool] = {} + empty: dict[str, bool] = {} item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed return rep diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 0f201d0f3f5..aefea1333d9 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" +from __future__ import annotations + import inspect import sys import traceback @@ -9,8 +11,6 @@ from typing import Callable from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Tuple from typing import Type from typing import TYPE_CHECKING @@ -49,8 +49,8 @@ def pytest_pycollect_makeitem( - collector: Union[Module, Class], name: str, obj: object -) -> Optional["UnitTestCase"]: + collector: Module | Class, name: str, obj: object +) -> UnitTestCase | None: try: # Has unittest been imported? ut = sys.modules["unittest"] @@ -81,7 +81,7 @@ def newinstance(self): # it. return self.obj("runTest") - def collect(self) -> Iterable[Union[Item, Collector]]: + def collect(self) -> Iterable[Item | Collector]: from unittest import TestLoader cls = self.obj @@ -201,7 +201,7 @@ def unittest_setup_method_fixture( class TestCaseFunction(Function): nofuncargs = True - _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None + _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None def _getinstance(self): assert isinstance(self.parent, UnitTestCase) @@ -215,7 +215,7 @@ def _testcase(self): def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). - self._explicit_tearDown: Optional[Callable[[], None]] = None + self._explicit_tearDown: Callable[[], None] | None = None super().setup() def teardown(self) -> None: @@ -226,7 +226,7 @@ def teardown(self) -> None: del self._instance super().teardown() - def startTest(self, testcase: "unittest.TestCase") -> None: + def startTest(self, testcase: unittest.TestCase) -> None: pass def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: @@ -265,7 +265,7 @@ def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -275,11 +275,11 @@ def addError( self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: self._addexcinfo(rawexcinfo) - def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: + def addSkip(self, testcase: unittest.TestCase, reason: str) -> None: try: raise pytest.skip.Exception(reason, _use_item_location=True) except skip.Exception: @@ -287,7 +287,7 @@ def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: def addExpectedFailure( self, - testcase: "unittest.TestCase", + testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType, reason: str = "", ) -> None: @@ -298,8 +298,8 @@ def addExpectedFailure( def addUnexpectedSuccess( self, - testcase: "unittest.TestCase", - reason: Optional["twisted.trial.unittest.Todo"] = None, + testcase: unittest.TestCase, + reason: twisted.trial.unittest.Todo | None = None, ) -> None: msg = "Unexpected success" if reason: @@ -310,13 +310,13 @@ def addUnexpectedSuccess( except fail.Exception: self._addexcinfo(sys.exc_info()) - def addSuccess(self, testcase: "unittest.TestCase") -> None: + def addSuccess(self, testcase: unittest.TestCase) -> None: pass - def stopTest(self, testcase: "unittest.TestCase") -> None: + def stopTest(self, testcase: unittest.TestCase) -> None: pass - def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: pass def runtest(self) -> None: diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index 50b121e8811..c191703a3de 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import sys import traceback from types import TracebackType from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type +from typing import TYPE_CHECKING import warnings import pytest +if TYPE_CHECKING: + from typing_extensions import Self + + # Copied from cpython/Lib/test/support/__init__.py, with modifications. class catch_unraisable_exception: """Context manager catching unraisable exception using sys.unraisablehook. @@ -34,24 +39,24 @@ class catch_unraisable_exception: """ def __init__(self) -> None: - self.unraisable: Optional[sys.UnraisableHookArgs] = None - self._old_hook: Optional[Callable[[sys.UnraisableHookArgs], Any]] = None + self.unraisable: sys.UnraisableHookArgs | None = None + self._old_hook: Callable[[sys.UnraisableHookArgs], Any] | None = None - def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None: + def _hook(self, unraisable: sys.UnraisableHookArgs) -> None: # Storing unraisable.object can resurrect an object which is being # finalized. Storing unraisable.exc_value creates a reference cycle. self.unraisable = unraisable - def __enter__(self) -> "catch_unraisable_exception": + def __enter__(self) -> Self: self._old_hook = sys.unraisablehook sys.unraisablehook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None sys.unraisablehook = self._old_hook diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index a5884f29582..4ab14e48c92 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import dataclasses import inspect from types import FunctionType from typing import Any from typing import final from typing import Generic -from typing import Type from typing import TypeVar import warnings @@ -72,7 +73,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): __module__ = "pytest" @classmethod - def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": + def simple(cls, apiname: str) -> PytestExperimentalApiWarning: return cls(f"{apiname} is an experimental api that may change over time") @@ -132,7 +133,7 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category: Type["_W"] + category: type[_W] template: str def format(self, **kwargs: Any) -> _W: diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 22590892f8d..5c59e55c5db 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager import sys from typing import Generator from typing import Literal -from typing import Optional import warnings from _pytest.config import apply_warning_filters @@ -28,7 +29,7 @@ def catch_warnings_for_item( config: Config, ihook, when: Literal["config", "collect", "runtest"], - item: Optional[Item], + item: Item | None, ) -> Generator[None, None, None]: """Context manager that catches warnings generated in the contained execution block. @@ -142,7 +143,7 @@ def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: @pytest.hookimpl(wrapper=True) def pytest_load_initial_conftests( - early_config: "Config", + early_config: Config, ) -> Generator[None, None, None]: with catch_warnings_for_item( config=early_config, ihook=early_config.hook, when="config", item=None diff --git a/src/py.py b/src/py.py index d1c39d203a8..5c661e66c1f 100644 --- a/src/py.py +++ b/src/py.py @@ -1,6 +1,8 @@ # shim for pylib going away # if pylib is installed this file will get skipped # (`py/__init__.py` has higher precedence) +from __future__ import annotations + import sys import _pytest._py.error as error diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index c6b6de827e9..90abcdab036 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -1,6 +1,8 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" +from __future__ import annotations + from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo diff --git a/src/pytest/__main__.py b/src/pytest/__main__.py index e4cb67d5dd5..cccab5d57b8 100644 --- a/src/pytest/__main__.py +++ b/src/pytest/__main__.py @@ -1,5 +1,7 @@ """The pytest entry point.""" +from __future__ import annotations + import pytest diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 0215aba9695..4a95e2d0cd9 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import contextlib import multiprocessing import os diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 0f3b35036d7..01d911e8ca4 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import importlib.metadata import os diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 57ab4cdfddb..7ae5ad46100 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import re import sys from types import FrameType diff --git a/testing/conftest.py b/testing/conftest.py index b7e2d6111af..24e5d183094 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import re import sys from typing import Generator -from typing import List from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -190,22 +191,22 @@ class ColorMapping: NO_COLORS = {k: "" for k in COLORS.keys()} @classmethod - def format(cls, lines: List[str]) -> List[str]: + def format(cls, lines: list[str]) -> list[str]: """Straightforward replacement of color names to their ASCII codes.""" return [line.format(**cls.COLORS) for line in lines] @classmethod - def format_for_fnmatch(cls, lines: List[str]) -> List[str]: + def format_for_fnmatch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.fnmatch_lines""" return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines] @classmethod - def format_for_rematch(cls, lines: List[str]) -> List[str]: + def format_for_rematch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.re_match_lines""" return [line.format(**cls.RE_COLORS) for line in lines] @classmethod - def strip_colors(cls, lines: List[str]) -> List[str]: + def strip_colors(cls, lines: list[str]) -> list[str]: """Entirely remove every color code""" return [line.format(**cls.NO_COLORS) for line in lines] diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 9e83a49d554..5d0e69c58c1 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import re import sys diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py index d802a7f8728..e612ae01e66 100644 --- a/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Reproduces issue #3774""" +from __future__ import annotations + from unittest import mock import pytest diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py index 58c41942d1c..5e30bb15883 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_init(): pass diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py index d88c001c2cc..3cb8f1be095 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/testing/example_scripts/collect/package_infinite_recursion/conftest.py index bba5db8b2fd..c2d2b918874 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/conftest.py +++ b/testing/example_scripts/collect/package_infinite_recursion/conftest.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def pytest_ignore_collect(collection_path): return False diff --git a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py index 2809d0cc689..38c51e586fc 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +++ b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test(): pass diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py index 58c41942d1c..5e30bb15883 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_init(): pass diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py index d88c001c2cc..3cb8f1be095 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/testing/example_scripts/config/collect_pytest_prefix/conftest.py index 2da4ffe2fed..5e0ab54411b 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/conftest.py +++ b/testing/example_scripts/config/collect_pytest_prefix/conftest.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + class pytest_something: pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py index d88c001c2cc..3cb8f1be095 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py +++ b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/conftest_usageerror/conftest.py b/testing/example_scripts/conftest_usageerror/conftest.py index 64bbeefac1d..a6690bdc303 100644 --- a/testing/example_scripts/conftest_usageerror/conftest.py +++ b/testing/example_scripts/conftest_usageerror/conftest.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def pytest_configure(config): import pytest diff --git a/testing/example_scripts/customdirectory/conftest.py b/testing/example_scripts/customdirectory/conftest.py index fe1c743a686..4718d7d5be3 100644 --- a/testing/example_scripts/customdirectory/conftest.py +++ b/testing/example_scripts/customdirectory/conftest.py @@ -1,5 +1,7 @@ # mypy: allow-untyped-defs # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/testing/example_scripts/customdirectory/tests/test_first.py b/testing/example_scripts/customdirectory/tests/test_first.py index 890ca3dea38..06f40ca4733 100644 --- a/testing/example_scripts/customdirectory/tests/test_first.py +++ b/testing/example_scripts/customdirectory/tests/test_first.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_second.py b/testing/example_scripts/customdirectory/tests/test_second.py index 42108d5da84..79bcc099e65 100644 --- a/testing/example_scripts/customdirectory/tests/test_second.py +++ b/testing/example_scripts/customdirectory/tests/test_second.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_third.py b/testing/example_scripts/customdirectory/tests/test_third.py index ede0f3e6025..5af476ad44d 100644 --- a/testing/example_scripts/customdirectory/tests/test_third.py +++ b/testing/example_scripts/customdirectory/tests/test_third.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py index d96c90a91bd..18180b99f2d 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py index 7479c66c1be..0dcc7ab2802 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py index 4737ef904e0..4985c69ff30 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py index e026fe3d192..b787cb39ee2 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_initvar.py b/testing/example_scripts/dataclasses/test_compare_initvar.py index d687fc22530..fc589e1fde4 100644 --- a/testing/example_scripts/dataclasses/test_compare_initvar.py +++ b/testing/example_scripts/dataclasses/test_compare_initvar.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from dataclasses import dataclass from dataclasses import InitVar diff --git a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py index 801aa0a732e..885edd7d9d7 100644 --- a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from dataclasses import dataclass diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index 0a4820c69ba..b45a6772c59 100644 --- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/doctest/main_py/__main__.py b/testing/example_scripts/doctest/main_py/__main__.py index c8a124f5416..3a0f6bed1d6 100644 --- a/testing/example_scripts/doctest/main_py/__main__.py +++ b/testing/example_scripts/doctest/main_py/__main__.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_this_is_ignored(): assert True diff --git a/testing/example_scripts/doctest/main_py/test_normal_module.py b/testing/example_scripts/doctest/main_py/test_normal_module.py index 26a4d90bc89..8c150da5c02 100644 --- a/testing/example_scripts/doctest/main_py/test_normal_module.py +++ b/testing/example_scripts/doctest/main_py/test_normal_module.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_doc(): """ >>> 10 > 5 diff --git a/testing/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py index fe1ae620aa6..274ab97d01b 100644 --- a/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py index 2809d0cc689..38c51e586fc 100644 --- a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py +++ b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test(): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py index 3a5d3ac33fe..94eaa3e0796 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py index d0c4bdbdfd9..cb3f9fbf469 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_1(arg1): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index a1f3b2d58b9..112d1e05f27 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py index 45e9744786a..3dea97f544c 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_2(arg2): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py index 84e5256f070..d90961ae3c4 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py index 7f1769beb9b..b4fcc17bfc7 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py index ad26fdd8cdc..b933b70edf3 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py index 9ee74a47186..d31ab971f2b 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_spam(spam): assert spam == "spamspam" diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py index 7f1769beb9b..b4fcc17bfc7 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py index fa688f0a844..2d6d7faef61 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py index f78a57c322b..45e5deaafea 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py index 12e0e3e91d4..1c7a710cd0c 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py index 8b6e8697e06..96f0cacfafd 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py index 40587cf2bd1..b78ca04b3ab 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py index 0cc8446d8ee..0dd782e4285 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_fixture_named_request.py b/testing/example_scripts/fixtures/test_fixture_named_request.py index a2ab7ee330d..db88bcdabb9 100644 --- a/testing/example_scripts/fixtures/test_fixture_named_request.py +++ b/testing/example_scripts/fixtures/test_fixture_named_request.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py index 0f316f0e449..0559905cea4 100644 --- a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +++ b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index bde5c0711ac..2e88c5ad5a9 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py index dd18e1741f0..b10f874e78d 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_hello(): pass diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py index 39766164490..138c07e95be 100644 --- a/testing/example_scripts/issue_519.py +++ b/testing/example_scripts/issue_519.py @@ -1,7 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pprint -from typing import List -from typing import Tuple import pytest @@ -16,7 +16,7 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope="session") def checked_order(): - order: List[Tuple[str, str, str]] = [] + order: list[tuple[str, str, str]] = [] yield order pprint.pprint(order) diff --git a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py index d95ad0a83d9..c98e58316eb 100644 --- a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +++ b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py index 17085e50b54..3b580aa341a 100644 --- a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +++ b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import pathlib diff --git a/testing/example_scripts/perf_examples/collect_stats/template_test.py b/testing/example_scripts/perf_examples/collect_stats/template_test.py index f50eb65525c..d9449485db6 100644 --- a/testing/example_scripts/perf_examples/collect_stats/template_test.py +++ b/testing/example_scripts/perf_examples/collect_stats/template_test.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_x(): pass diff --git a/testing/example_scripts/tmpdir/tmp_path_fixture.py b/testing/example_scripts/tmpdir/tmp_path_fixture.py index 4aa35faa0b6..503ead473e7 100644 --- a/testing/example_scripts/tmpdir/tmp_path_fixture.py +++ b/testing/example_scripts/tmpdir/tmp_path_fixture.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py index d66b66df5b7..733202915e4 100644 --- a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +++ b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import unittest import pytest diff --git a/testing/example_scripts/unittest/test_setup_skip.py b/testing/example_scripts/unittest/test_setup_skip.py index 7550a097576..52ff96ea8be 100644 --- a/testing/example_scripts/unittest/test_setup_skip.py +++ b/testing/example_scripts/unittest/test_setup_skip.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_class.py b/testing/example_scripts/unittest/test_setup_skip_class.py index 48f7e476f40..fe431d8e794 100644 --- a/testing/example_scripts/unittest/test_setup_skip_class.py +++ b/testing/example_scripts/unittest/test_setup_skip_class.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_module.py b/testing/example_scripts/unittest/test_setup_skip_module.py index eee4263d22b..07fd96c9cef 100644 --- a/testing/example_scripts/unittest/test_setup_skip_module.py +++ b/testing/example_scripts/unittest/test_setup_skip_module.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """setUpModule is always called, even if all tests in the module are skipped""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py index a82ddaebcc3..8792492b38d 100644 --- a/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs -from typing import List +from __future__ import annotations + from unittest import IsolatedAsyncioTestCase -teardowns: List[None] = [] +teardowns: list[None] = [] class AsyncArguments(IsolatedAsyncioTestCase): diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py index e9b10171e8d..8a93366b9a3 100644 --- a/testing/example_scripts/unittest/test_unittest_asynctest.py +++ b/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -1,13 +1,14 @@ # mypy: allow-untyped-defs """Issue #7110""" +from __future__ import annotations + import asyncio -from typing import List import asynctest -teardowns: List[None] = [] +teardowns: list[None] = [] class Test(asynctest.TestCase): diff --git a/testing/example_scripts/unittest/test_unittest_plain_async.py b/testing/example_scripts/unittest/test_unittest_plain_async.py index 2a4a66509a4..ea1ae371551 100644 --- a/testing/example_scripts/unittest/test_unittest_plain_async.py +++ b/testing/example_scripts/unittest/test_unittest_plain_async.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message.py b/testing/example_scripts/warnings/test_group_warnings_by_message.py index be64a1ff2c8..ee3bc2bbee4 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py index 95fa795efe0..cc514bafbe9 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py index 5204fde8a85..33d5ce8ce34 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from test_1 import func diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index 7b9c109889e..80f78d843a2 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py index fbfda2e5d94..2015d22c7c0 100644 --- a/testing/freeze/create_executable.py +++ b/testing/freeze/create_executable.py @@ -1,5 +1,8 @@ """Generate an executable with pytest runner embedded using PyInstaller.""" +from __future__ import annotations + + if __name__ == "__main__": import subprocess diff --git a/testing/freeze/runtests_script.py b/testing/freeze/runtests_script.py index ef63a2d15b9..286c98ac539 100644 --- a/testing/freeze/runtests_script.py +++ b/testing/freeze/runtests_script.py @@ -3,6 +3,9 @@ pytest main(). """ +from __future__ import annotations + + if __name__ == "__main__": import sys diff --git a/testing/freeze/tests/test_trivial.py b/testing/freeze/tests/test_trivial.py index 425f29a649c..000ca97310c 100644 --- a/testing/freeze/tests/test_trivial.py +++ b/testing/freeze/tests/test_trivial.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_upper(): assert "foo".upper() == "FOO" diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py index 1230fcce140..38c1e75cf10 100644 --- a/testing/freeze/tox_run.py +++ b/testing/freeze/tox_run.py @@ -3,6 +3,9 @@ directory. """ +from __future__ import annotations + + if __name__ == "__main__": import os import sys diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 15fe6611280..1326ef34b2e 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import ChainMap from collections import Counter from collections import defaultdict diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index f627434c4e9..075d40cdf44 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index afa8d5cae87..043c2d1d904 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import os from pathlib import Path @@ -6,7 +8,6 @@ import shutil import sys from typing import Generator -from typing import Optional from unittest import mock from _pytest._io import terminalwriter @@ -166,7 +167,7 @@ def test_attr_hasmarkup() -> None: assert "\x1b[0m" in s -def assert_color(expected: bool, default: Optional[bool] = None) -> None: +def assert_color(expected: bool, default: bool | None = None) -> None: file = io.StringIO() if default is None: default = not expected diff --git a/testing/io/test_wcwidth.py b/testing/io/test_wcwidth.py index 82503b8300c..9ff1ad06e60 100644 --- a/testing/io/test_wcwidth.py +++ b/testing/io/test_wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest._io.wcwidth import wcswidth from _pytest._io.wcwidth import wcwidth import pytest diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index c1cfff632af..0603eaba218 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -1,5 +1,7 @@ # mypy: disable-error-code="attr-defined" # mypy: disallow-untyped-defs +from __future__ import annotations + import logging from typing import Iterator diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py index 37971293726..cfe3bee68c4 100644 --- a/testing/logging/test_formatter.py +++ b/testing/logging/test_formatter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Any diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 7e592febf56..cf54788e246 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import os import re diff --git a/testing/plugins_integration/bdd_wallet.py b/testing/plugins_integration/bdd_wallet.py index 2bdb1545424..d748028842a 100644 --- a/testing/plugins_integration/bdd_wallet.py +++ b/testing/plugins_integration/bdd_wallet.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pytest_bdd import given from pytest_bdd import scenario from pytest_bdd import then diff --git a/testing/plugins_integration/django_settings.py b/testing/plugins_integration/django_settings.py index 0715f476531..e36e554db9a 100644 --- a/testing/plugins_integration/django_settings.py +++ b/testing/plugins_integration/django_settings.py @@ -1 +1,4 @@ +from __future__ import annotations + + SECRET_KEY = "mysecret" diff --git a/testing/plugins_integration/pytest_anyio_integration.py b/testing/plugins_integration/pytest_anyio_integration.py index 383d7a0b5db..41ffad18a6e 100644 --- a/testing/plugins_integration/pytest_anyio_integration.py +++ b/testing/plugins_integration/pytest_anyio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import anyio import pytest diff --git a/testing/plugins_integration/pytest_asyncio_integration.py b/testing/plugins_integration/pytest_asyncio_integration.py index b216c4beecd..cef67f83ea6 100644 --- a/testing/plugins_integration/pytest_asyncio_integration.py +++ b/testing/plugins_integration/pytest_asyncio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import asyncio import pytest diff --git a/testing/plugins_integration/pytest_mock_integration.py b/testing/plugins_integration/pytest_mock_integration.py index 5494c44270a..a49129cf0c9 100644 --- a/testing/plugins_integration/pytest_mock_integration.py +++ b/testing/plugins_integration/pytest_mock_integration.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_mocker(mocker): mocker.MagicMock() diff --git a/testing/plugins_integration/pytest_rerunfailures_integration.py b/testing/plugins_integration/pytest_rerunfailures_integration.py index 9a13a3279a9..449661f7294 100644 --- a/testing/plugins_integration/pytest_rerunfailures_integration.py +++ b/testing/plugins_integration/pytest_rerunfailures_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest diff --git a/testing/plugins_integration/pytest_trio_integration.py b/testing/plugins_integration/pytest_trio_integration.py index 60f48ec609b..eceac5076a9 100644 --- a/testing/plugins_integration/pytest_trio_integration.py +++ b/testing/plugins_integration/pytest_trio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import trio import pytest diff --git a/testing/plugins_integration/pytest_twisted_integration.py b/testing/plugins_integration/pytest_twisted_integration.py index 0dbf5faeb8a..4f386bf1b9f 100644 --- a/testing/plugins_integration/pytest_twisted_integration.py +++ b/testing/plugins_integration/pytest_twisted_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest_twisted from twisted.internet.task import deferLater diff --git a/testing/plugins_integration/simple_integration.py b/testing/plugins_integration/simple_integration.py index 48089afcc7e..ed504ae4bf1 100644 --- a/testing/plugins_integration/simple_integration.py +++ b/testing/plugins_integration/simple_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/python/approx.py b/testing/python/approx.py index 31e64c4df3d..69743cdbe17 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager from decimal import Decimal from fractions import Fraction @@ -6,7 +8,6 @@ import operator from operator import eq from operator import ne -from typing import Optional from _pytest.pytester import Pytester from _pytest.python_api import _recursive_sequence_map @@ -415,9 +416,7 @@ def test_zero_tolerance(self): (-1e100, -1e100), ], ) - def test_negative_tolerance( - self, rel: Optional[float], abs: Optional[float] - ) -> None: + def test_negative_tolerance(self, rel: float | None, abs: float | None) -> None: # Negative tolerances are not allowed. with pytest.raises(ValueError): 1.1 == approx(1, rel, abs) diff --git a/testing/python/collect.py b/testing/python/collect.py index 843fa3c0e6b..06386611279 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys import textwrap from typing import Any -from typing import Dict import _pytest._code from _pytest.config import ExitCode @@ -1129,7 +1130,7 @@ def test_filter_traceback_generated_code(self) -> None: tb = None try: - ns: Dict[str, Any] = {} + ns: dict[str, Any] = {} exec("def foo(): raise ValueError", ns) ns["foo"]() except ValueError: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index d3cff38f977..bc091bb1f27 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import sys @@ -4514,7 +4516,7 @@ def test_fixture_named_request(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "*'request' is a reserved word for fixtures, use another name:", - " *test_fixture_named_request.py:6", + " *test_fixture_named_request.py:8", ] ) diff --git a/testing/python/integration.py b/testing/python/integration.py index c20aaeed839..c52a683a322 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest._code import getfslineno from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 3d0058fa0a7..2dd85607e71 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import itertools import re @@ -8,11 +10,7 @@ from typing import cast from typing import Dict from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import hypothesis from hypothesis import strategies @@ -35,7 +33,7 @@ def Metafunc(self, func, config=None) -> python.Metafunc: # on the funcarg level, so we don't need a full blown # initialization. class FuncFixtureInfoMock: - name2fixturedefs: Dict[str, List[fixtures.FixtureDef[object]]] = {} + name2fixturedefs: dict[str, list[fixtures.FixtureDef[object]]] = {} def __init__(self, names): self.names_closure = names @@ -101,7 +99,7 @@ class Exc(Exception): def __repr__(self): return "Exc(from_gen)" - def gen() -> Iterator[Union[int, None, Exc]]: + def gen() -> Iterator[int | None | Exc]: yield 0 yield None yield Exc() @@ -346,7 +344,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[str, Any, str]] = [ + values: list[tuple[str, Any, str]] = [ ("ação", MockConfig({option: True}), "ação"), ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -516,7 +514,7 @@ def test_idmaker_enum(self) -> None: def test_idmaker_idfn(self) -> None: """#351""" - def ids(val: object) -> Optional[str]: + def ids(val: object) -> str | None: if isinstance(val, Exception): return repr(val) return None @@ -579,7 +577,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -617,7 +615,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -1748,9 +1746,9 @@ def test_parametrize_some_arguments_auto_scope( self, pytester: Pytester, monkeypatch ) -> None: """Integration test for (#3941)""" - class_fix_setup: List[object] = [] + class_fix_setup: list[object] = [] monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False) - func_fix_setup: List[object] = [] + func_fix_setup: list[object] = [] monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False) pytester.makepyfile( diff --git a/testing/python/raises.py b/testing/python/raises.py index 929865e31a0..271dd3e5a8c 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import re import sys diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py index f756dca41c7..c860b61e21b 100644 --- a/testing/python/show_fixtures_per_test.py +++ b/testing/python/show_fixtures_per_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 0c41c0286a4..5d1513b6206 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import subprocess import sys diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 726235999b4..d4325ab4b62 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1,11 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys import textwrap from typing import Any -from typing import List from typing import MutableSequence from typing import NamedTuple -from typing import Optional import attr @@ -19,7 +19,7 @@ import pytest -def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): +def mock_config(verbose: int = 0, assertion_override: int | None = None): class TerminalWriter: def _highlight(self, source, lexer="python"): return source @@ -28,7 +28,7 @@ class Config: def get_terminal_writer(self): return TerminalWriter() - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: if verbosity_type is None: return verbose if verbosity_type == _Config.VERBOSITY_ASSERTIONS: @@ -369,12 +369,12 @@ def test_check(list): result.stdout.fnmatch_lines(["*test_hello*FAIL*", "*test_check*PASS*"]) -def callop(op: str, left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callop(op: str, left: Any, right: Any, verbose: int = 0) -> list[str] | None: config = mock_config(verbose=verbose) return plugin.pytest_assertrepr_compare(config, op, left, right) -def callequal(left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callequal(left: Any, right: Any, verbose: int = 0) -> list[str] | None: return callop("==", left, right, verbose) @@ -1316,7 +1316,7 @@ class TestTruncateExplanation: LINES_IN_TRUNCATION_MSG = 2 def test_doesnt_truncate_when_input_is_empty_list(self) -> None: - expl: List[str] = [] + expl: list[str] = [] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) assert result == expl diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 8db9dbbe5ff..185cd5ef2eb 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast import errno from functools import partial @@ -12,12 +14,8 @@ import sys import textwrap from typing import cast -from typing import Dict from typing import Generator -from typing import List from typing import Mapping -from typing import Optional -from typing import Set from unittest import mock import zipfile @@ -45,13 +43,13 @@ def rewrite(src: str) -> ast.Module: def getmsg( - f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False -) -> Optional[str]: + f, extra_ns: Mapping[str, object] | None = None, *, must_pass: bool = False +) -> str | None: """Rewrite the assertions in f, run it, and get the failure message.""" src = "\n".join(_pytest._code.Code.from_function(f).source().lines) mod = rewrite(src) code = compile(mod, "", "exec") - ns: Dict[str, object] = {} + ns: dict[str, object] = {} if extra_ns is not None: ns.update(extra_ns) exec(code, ns) @@ -1638,8 +1636,8 @@ def hook( """ import importlib.machinery - self.find_spec_calls: List[str] = [] - self.initial_paths: Set[Path] = set() + self.find_spec_calls: list[str] = [] + self.initial_paths: set[Path] = set() class StubSession: _initialpaths = self.initial_paths @@ -2059,7 +2057,7 @@ class TestReprSizeVerbosity: ) def test_get_maxsize_for_saferepr(self, verbose: int, expected_size) -> None: class FakeConfig: - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: return verbose config = FakeConfig() diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 08158f6191a..72b4265cf75 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import auto from enum import Enum import os @@ -5,9 +7,7 @@ import shutil from typing import Any from typing import Generator -from typing import List from typing import Sequence -from typing import Tuple from _pytest.compat import assert_never from _pytest.config import ExitCode @@ -579,7 +579,7 @@ def test_pass(): def rlf( fail_import: int, fail_run: int, args: Sequence[str] = () - ) -> Tuple[Any, Any]: + ) -> tuple[Any, Any]: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) @@ -693,7 +693,7 @@ def test_lf_and_ff_prints_no_needless_message( else: assert "rerun previous" in result.stdout.str() - def get_cached_last_failed(self, pytester: Pytester) -> List[str]: + def get_cached_last_failed(self, pytester: Pytester) -> list[str]: config = pytester.parseconfigure() assert config.cache is not None return sorted(config.cache.get("cache/lastfailed", {})) diff --git a/testing/test_capture.py b/testing/test_capture.py index b6c206ec47c..fe6bd7d14fa 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import contextlib import io from io import UnsupportedOperation diff --git a/testing/test_collection.py b/testing/test_collection.py index 8ff38a334f4..821c424196f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import pprint @@ -6,8 +8,6 @@ import sys import tempfile import textwrap -from typing import List -from typing import Type from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode @@ -536,7 +536,7 @@ def test_collect_topdir(self, pytester: Pytester) -> None: assert len(colitems) == 1 assert colitems[0].path == topdir - def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: + def get_reported_items(self, hookrec: HookRecorder) -> list[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" calls = hookrec.getcalls("pytest_collectreport") return [ @@ -1885,7 +1885,7 @@ def test_do_not_collect_symlink_siblings( ) def test_respect_system_exceptions( pytester: Pytester, - exception_class: Type[BaseException], + exception_class: type[BaseException], msg: str, ): head = "Before exception" diff --git a/testing/test_compat.py b/testing/test_compat.py index 73ac1bad858..2c6b0269c27 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,11 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import enum from functools import cached_property from functools import partial from functools import wraps import sys from typing import TYPE_CHECKING -from typing import Union from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never @@ -216,7 +217,7 @@ def prop(self) -> int: def test_assert_never_union() -> None: - x: Union[int, str] = 10 + x: int | str = 10 if isinstance(x, int): pass diff --git a/testing/test_config.py b/testing/test_config.py index 1cb31fed06d..232839399e2 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import importlib.metadata import os @@ -7,12 +9,7 @@ import sys import textwrap from typing import Any -from typing import Dict -from typing import List from typing import Sequence -from typing import Tuple -from typing import Type -from typing import Union import _pytest._code from _pytest.config import _get_plugin_specs_as_list @@ -633,7 +630,7 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None: class TestConfigAPI: def test_config_trace(self, pytester: Pytester) -> None: config = pytester.parseconfig() - values: List[str] = [] + values: list[str] = [] config.trace.root.setwriter(values.append) config.trace("hello") assert len(values) == 1 @@ -996,7 +993,7 @@ def test_basic_behavior(self, _sys_snapshot) -> None: def test_invocation_params_args(self, _sys_snapshot) -> None: """Show that fromdictargs can handle args in their "orig" format""" - option_dict: Dict[str, object] = {} + option_dict: dict[str, object] = {} args = ["-vvvv", "-s", "a", "b"] config = Config.fromdictargs(option_dict, args) @@ -1210,7 +1207,7 @@ def distributions(): def test_disable_plugin_autoload( pytester: Pytester, monkeypatch: MonkeyPatch, - parse_args: Union[Tuple[str, str], Tuple[()]], + parse_args: tuple[str, str] | tuple[()], should_load: bool, ) -> None: class DummyEntryPoint: @@ -1304,7 +1301,7 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None: ], ) def test_consider_args_after_options_for_rootdir( - pytester: Pytester, args: List[str] + pytester: Pytester, args: list[str] ) -> None: """ Consider all arguments in the command-line for rootdir @@ -2241,7 +2238,7 @@ def test_strtobool() -> None: ], ) def test_parse_warning_filter( - arg: str, escape: bool, expected: Tuple[str, str, Type[Warning], str, int] + arg: str, escape: bool, expected: tuple[str, str, type[Warning], str, int] ) -> None: assert parse_warning_filter(arg, escape=escape) == expected diff --git a/testing/test_conftest.py b/testing/test_conftest.py index eb3ebecdc98..d51846f2f5f 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,14 +1,13 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import textwrap from typing import cast -from typing import Dict from typing import Generator from typing import List -from typing import Optional from typing import Sequence -from typing import Union from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -27,8 +26,8 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( conftest: PytestPluginManager, - args: Sequence[Union[str, Path]], - confcutdir: Optional[Path] = None, + args: Sequence[str | Path], + confcutdir: Path | None = None, ) -> None: conftest._set_initial_conftests( args=args, @@ -536,7 +535,7 @@ def pytest_addoption(parser): class TestConftestVisibility: - def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]: # for issue616 + def _setup_tree(self, pytester: Pytester) -> dict[str, Path]: # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html runner = pytester.mkdir("empty") diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 1f3422947de..37032f92354 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List import _pytest._code from _pytest.debugging import _validate_usepdb_cls @@ -35,7 +36,7 @@ def runpdb_and_get_report(pytester: Pytester, source: str): @pytest.fixture -def custom_pdb_calls() -> List[str]: +def custom_pdb_calls() -> list[str]: called = [] # install dummy debugger class and track which methods were called on it @@ -854,7 +855,7 @@ def test_post_mortem(): self.flush(child) def test_pdb_custom_cls( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess( @@ -880,7 +881,7 @@ def test_pdb_validate_usepdb_cls(self): assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist") def test_pdb_custom_cls_without_pdb( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 9b33d641a14..4aa4876c711 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import inspect from pathlib import Path import sys import textwrap from typing import Callable -from typing import Optional from _pytest.doctest import _get_checker from _pytest.doctest import _is_main_py @@ -1595,7 +1596,7 @@ def __getattr__(self, _): "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) def test_warning_on_unwrap_of_broken_object( - stop: Optional[Callable[[object], object]], + stop: Callable[[object], object] | None, ) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" diff --git a/testing/test_entry_points.py b/testing/test_entry_points.py index 68e3a8a92e4..543f3252b22 100644 --- a/testing/test_entry_points.py +++ b/testing/test_entry_points.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import importlib.metadata diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index f290eb1679f..741a6ca82d0 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -5,6 +5,8 @@ """ +from __future__ import annotations + from _pytest.pytester import Pytester import pytest diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index e5016976130..c416e81d2d9 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import sys diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 260b9d07c9c..9532f1eef75 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path from textwrap import dedent diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 4906ef5c8f0..daf2bb12714 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest.config import ExitCode from _pytest.pytester import Pytester import pytest diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 1ebc3ed3a51..67234302a89 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,14 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from datetime import datetime import os from pathlib import Path import platform from typing import cast -from typing import List -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from xml.dom import minidom import xmlschema @@ -39,8 +37,8 @@ def __init__(self, pytester: Pytester, schema: xmlschema.XMLSchema) -> None: self.schema = schema def __call__( - self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" - ) -> Tuple[RunResult, "DomNode"]: + self, *args: str | os.PathLike[str], family: str | None = "xunit1" + ) -> tuple[RunResult, DomNode]: if family: args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") @@ -940,7 +938,7 @@ def test_mangle_test_address() -> None: def test_dont_configure_on_workers(tmp_path: Path) -> None: - gotten: List[object] = [] + gotten: list[object] = [] class FakeConfig: if TYPE_CHECKING: @@ -1183,7 +1181,7 @@ def test_unicode_issue368(pytester: Pytester) -> None: class Report(BaseReport): longrepr = ustr - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" when = "teardown" @@ -1495,7 +1493,7 @@ def test_global_properties(pytester: Pytester, xunit_family: str) -> None: log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "test_node_id" log.pytest_sessionstart() @@ -1531,7 +1529,7 @@ def test_url_property(pytester: Pytester) -> None: class Report(BaseReport): longrepr = "FooBarBaz" - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" url = test_url diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index d2b33b4fb8b..72854e4e5c0 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path from _pytest.compat import LEGACY_PATH diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py index 0461cd75554..0557dae669d 100644 --- a/testing/test_link_resolve.py +++ b/testing/test_link_resolve.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager import os.path from pathlib import Path diff --git a/testing/test_main.py b/testing/test_main.py index 6294f66b360..94eac02ce63 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import os from pathlib import Path import re -from typing import Optional from _pytest.config import ExitCode from _pytest.config import UsageError @@ -66,7 +67,7 @@ def pytest_internalerror(excrepr, excinfo): @pytest.mark.parametrize("returncode", (None, 42)) def test_wrap_session_exit_sessionfinish( - returncode: Optional[int], pytester: Pytester + returncode: int | None, pytester: Pytester ) -> None: pytester.makeconftest( f""" diff --git a/testing/test_mark.py b/testing/test_mark.py index 2896afa4532..090e10ee9c4 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1,8 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys -from typing import List -from typing import Optional from unittest import mock from _pytest.config import ExitCode @@ -214,7 +214,7 @@ def test_hello(): ], ) def test_mark_option( - expr: str, expected_passed: List[Optional[str]], pytester: Pytester + expr: str, expected_passed: list[str | None], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -238,7 +238,7 @@ def test_two(): [("interface", ["test_interface"]), ("not interface", ["test_nointer"])], ) def test_mark_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makeconftest( """ @@ -276,7 +276,7 @@ def test_nointer(): ], ) def test_keyword_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -314,7 +314,7 @@ def test_keyword_option_considers_mark(pytester: Pytester) -> None: ], ) def test_keyword_option_parametrize( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -895,7 +895,7 @@ def test_ddd(): pass ) monkeypatch.chdir(pytester.path / "suite") - def get_collected_names(*args: str) -> List[str]: + def get_collected_names(*args: str) -> list[str]: _, rec = pytester.inline_genitems(*args) calls = rec.getcalls("pytest_collection_finish") assert len(calls) == 1 @@ -930,7 +930,7 @@ def test_aliases(self) -> None: @pytest.mark.parametrize("mark", [None, "", "skip", "xfail"]) def test_parameterset_for_parametrize_marks( - pytester: Pytester, mark: Optional[str] + pytester: Pytester, mark: str | None ) -> None: if mark is not None: pytester.makeini( diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 07c89f90838..5bce004cb1c 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable from _pytest.mark.expression import Expression diff --git a/testing/test_meta.py b/testing/test_meta.py index 40ed95d6b47..e7d836f7ace 100644 --- a/testing/test_meta.py +++ b/testing/test_meta.py @@ -4,16 +4,17 @@ namespace being set, which is critical for the initialization of xdist. """ +from __future__ import annotations + import pkgutil import subprocess import sys -from typing import List import _pytest import pytest -def _modules() -> List[str]: +def _modules() -> list[str]: pytest_pkg: str = _pytest.__path__ # type: ignore return sorted( n diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 2ad3ccc4ddc..079d8ff60ad 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -1,12 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import re import sys import textwrap -from typing import Dict from typing import Generator -from typing import Type from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -135,7 +135,7 @@ def test_setitem() -> None: def test_setitem_deleted_meanwhile() -> None: - d: Dict[str, object] = {} + d: dict[str, object] = {} monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) del d["x"] @@ -160,7 +160,7 @@ def test_setenv_deleted_meanwhile(before: bool) -> None: def test_delitem() -> None: - d: Dict[str, object] = {"x": 1} + d: dict[str, object] = {"x": 1} monkeypatch = MonkeyPatch() monkeypatch.delitem(d, "x") assert "x" not in d @@ -360,7 +360,7 @@ class SampleInherit(Sample): [Sample, SampleInherit], ids=["new", "new-inherit"], ) -def test_issue156_undo_staticmethod(Sample: Type[Sample]) -> None: +def test_issue156_undo_staticmethod(Sample: type[Sample]) -> None: monkeypatch = MonkeyPatch() monkeypatch.setattr(Sample, "hello", None) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index a3caf471f70..f039acf243b 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,8 +1,9 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import re from typing import cast -from typing import Type import warnings from _pytest import nodes @@ -73,7 +74,7 @@ def runtest(self): "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")] ) def test_node_warn_is_no_longer_only_pytest_warnings( - pytester: Pytester, warn_type: Type[Warning], msg: str + pytester: Pytester, warn_type: type[Warning], msg: str ) -> None: items = pytester.getitems( """ diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index e959dfd631b..14e2b5f69fb 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import locale import os diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 9ca0da8f69d..8fdd60bac75 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,8 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import email.message import io -from typing import List -from typing import Union from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -11,8 +11,8 @@ class TestPasteCapture: @pytest.fixture - def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]: - pastebinlist: List[Union[str, bytes]] = [] + def pastebinlist(self, monkeypatch, request) -> list[str | bytes]: + pastebinlist: list[str | bytes] = [] plugin = request.config.pluginmanager.getplugin("pastebin") monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append) return pastebinlist diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 688d13f2f05..81aba25f78f 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import errno import importlib.abc import importlib.machinery @@ -12,9 +14,7 @@ from typing import Any from typing import Generator from typing import Iterator -from typing import Optional from typing import Sequence -from typing import Tuple import unittest.mock from _pytest.monkeypatch import MonkeyPatch @@ -865,7 +865,7 @@ def test_my_test(): def create_installed_doctests_and_tests_dir( self, path: Path, monkeypatch: MonkeyPatch - ) -> Tuple[Path, Path, Path]: + ) -> tuple[Path, Path, Path]: """ Create a directory structure where the application code is installed in a virtual environment, and the tests are in an outside ".tests" directory. @@ -1267,8 +1267,8 @@ def setup_imports_tracking(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr(sys, "pytest_namespace_packages_test", [], raising=False) def setup_directories( - self, tmp_path: Path, monkeypatch: Optional[MonkeyPatch], pytester: Pytester - ) -> Tuple[Path, Path]: + self, tmp_path: Path, monkeypatch: MonkeyPatch | None, pytester: Pytester + ) -> tuple[Path, Path]: # Use a code to guard against modules being imported more than once. # This is a safeguard in case future changes break this invariant. code = dedent( @@ -1438,7 +1438,7 @@ class CustomImporter(importlib.abc.MetaPathFinder): def find_spec( self, name: str, path: Any = None, target: Any = None - ) -> Optional[importlib.machinery.ModuleSpec]: + ) -> importlib.machinery.ModuleSpec | None: if name == "com": spec = importlib.machinery.ModuleSpec("com", loader=None) spec.submodule_search_locations = [str(com_root_2), str(com_root_1)] diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 99b003b66ed..db85124bf0d 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import shutil import sys import types -from typing import List from _pytest.config import Config from _pytest.config import ExitCode @@ -152,7 +153,7 @@ def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() - values: List[str] = [] + values: list[str] = [] pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 9c6081a56db..87714b4708f 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import subprocess import sys import time from types import ModuleType -from typing import List from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -227,7 +228,7 @@ def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None def spy_factory(self): class SysModulesSnapshotSpy: - instances: List["SysModulesSnapshotSpy"] = [] + instances: list[SysModulesSnapshotSpy] = [] def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) @@ -399,7 +400,7 @@ def test_preserve_container(self, monkeypatch: MonkeyPatch, path_type) -> None: original_data = list(getattr(sys, path_type)) original_other = getattr(sys, other_path_type) original_other_data = list(original_other) - new: List[object] = [] + new: list[object] = [] snapshot = SysPathsSnapshot() monkeypatch.setattr(sys, path_type, new) snapshot.restore() diff --git a/testing/test_python_path.py b/testing/test_python_path.py index 73a8725680f..1db02252d22 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -1,9 +1,9 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys from textwrap import dedent from typing import Generator -from typing import List -from typing import Optional from _pytest.pytester import Pytester import pytest @@ -91,8 +91,8 @@ def test_clean_up(pytester: Pytester) -> None: pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n") pytester.makepyfile(test_foo="""def test_foo(): pass""") - before: Optional[List[str]] = None - after: Optional[List[str]] = None + before: list[str] | None = None + after: list[str] | None = None class Plugin: @pytest.hookimpl(wrapper=True, tryfirst=True) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 27ee9aa72f0..384f2b66a15 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,9 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List -from typing import Optional -from typing import Type -from typing import Union import warnings import pytest @@ -54,7 +52,7 @@ class ChildOfChildWarning(ChildWarning): pass @staticmethod - def raise_warnings_from_list(_warnings: List[Type[Warning]]): + def raise_warnings_from_list(_warnings: list[type[Warning]]): for warn in _warnings: warnings.warn(f"Warning {warn().__repr__()}", warn) @@ -134,7 +132,7 @@ def test_invalid_enter_exit(self) -> None: class TestDeprecatedCall: """test pytest.deprecated_call()""" - def dep(self, i: int, j: Optional[int] = None) -> int: + def dep(self, i: int, j: int | None = None) -> int: if i == 0: warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) return 42 @@ -563,7 +561,7 @@ def test_raise_type_error_on_invalid_warning() -> None: pytest.param(Warning(), id="Warning"), ], ) -def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None: +def test_no_raise_type_error_on_valid_warning(message: str | Warning) -> None: """Check pytest.warns validates warning messages are strings (#10865) or Warning instances (#11959).""" with pytest.warns(Warning): diff --git a/testing/test_reports.py b/testing/test_reports.py index 7987b401771..3e314d2aade 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from typing import Sequence -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr @@ -294,8 +295,8 @@ def test_a(): reprec = pytester.inline_run() if report_class is TestReport: - reports: Union[Sequence[TestReport], Sequence[CollectReport]] = ( - reprec.getreports("pytest_runtest_logreport") + reports: Sequence[TestReport] | Sequence[CollectReport] = reprec.getreports( + "pytest_runtest_logreport" ) # we have 3 reports: setup/call/teardown assert len(reports) == 3 diff --git a/testing/test_runner.py b/testing/test_runner.py index 3ec5678274e..79f7a3fd4d3 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,14 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from functools import partial import inspect import os from pathlib import Path import sys import types -from typing import Dict -from typing import List -from typing import Tuple -from typing import Type import warnings from _pytest import outcomes @@ -523,7 +521,7 @@ class TestClass(object): assert res[1].name == "TestClass" -reporttypes: List[Type[reports.BaseReport]] = [ +reporttypes: list[type[reports.BaseReport]] = [ reports.BaseReport, reports.TestReport, reports.CollectReport, @@ -533,9 +531,9 @@ class TestClass(object): @pytest.mark.parametrize( "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] ) -def test_report_extra_parameters(reporttype: Type[reports.BaseReport]) -> None: +def test_report_extra_parameters(reporttype: type[reports.BaseReport]) -> None: args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - basekw: Dict[str, List[object]] = dict.fromkeys(args, []) + basekw: dict[str, list[object]] = dict.fromkeys(args, []) report = reporttype(newthing=1, **basekw) assert report.newthing == 1 @@ -1048,7 +1046,7 @@ def runtest(self): def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: - pytest_current_test_vars: List[Tuple[str, str]] = [] + pytest_current_test_vars: list[tuple[str, str]] = [] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index 587c9eb9fef..75e838a49e8 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -1,7 +1,7 @@ # mypy: allow-untyped-defs """Test correct setup/teardowns at module, class, and instance level.""" -from typing import List +from __future__ import annotations from _pytest.pytester import Pytester import pytest @@ -251,7 +251,7 @@ def test_setup_teardown_function_level_with_optional_argument( """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys - trace_setups_teardowns: List[str] = [] + trace_setups_teardowns: list[str] = [] monkeypatch.setattr( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) diff --git a/testing/test_scope.py b/testing/test_scope.py index 1727c2ee1bb..3cb811469a9 100644 --- a/testing/test_scope.py +++ b/testing/test_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from _pytest.scope import Scope diff --git a/testing/test_session.py b/testing/test_session.py index 8624af478b1..ba904916033 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester diff --git a/testing/test_setuponly.py b/testing/test_setuponly.py index 8638f5a6140..87123bd9a16 100644 --- a/testing/test_setuponly.py +++ b/testing/test_setuponly.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys from _pytest.config import ExitCode diff --git a/testing/test_setupplan.py b/testing/test_setupplan.py index d51a1873959..5a9211d7806 100644 --- a/testing/test_setupplan.py +++ b/testing/test_setupplan.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 459216a6d6b..558e3d35c6a 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys import textwrap diff --git a/testing/test_stash.py b/testing/test_stash.py index e523c4e6f2b..c7f6f4f95fe 100644 --- a/testing/test_stash.py +++ b/testing/test_stash.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.stash import Stash from _pytest.stash import StashKey import pytest diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 472afea6620..affdb73375e 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path from _pytest.cacheprovider import Cache diff --git a/testing/test_terminal.py b/testing/test_terminal.py index ce9fdc50c8a..01a84fd8d2c 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Terminal reporting of the full testing process.""" +from __future__ import annotations + from io import StringIO import os from pathlib import Path @@ -8,10 +10,7 @@ import textwrap from types import SimpleNamespace from typing import cast -from typing import Dict -from typing import List from typing import NamedTuple -from typing import Tuple import pluggy @@ -1929,9 +1928,9 @@ def tr() -> TerminalReporter: ) def test_summary_stats( tr: TerminalReporter, - exp_line: List[Tuple[str, Dict[str, bool]]], + exp_line: list[tuple[str, dict[str, bool]]], exp_color: str, - stats_arg: Dict[str, List[object]], + stats_arg: dict[str, list[object]], ) -> None: tr.stats = stats_arg diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py index 99837b94e8a..abd30144914 100644 --- a/testing/test_threadexception.py +++ b/testing/test_threadexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester import pytest diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index f424998e50f..865d8e0b05c 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import os from pathlib import Path @@ -6,8 +8,6 @@ import sys from typing import Callable from typing import cast -from typing import List -from typing import Union import warnings from _pytest import pathlib @@ -34,7 +34,7 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: @dataclasses.dataclass class FakeConfig: - basetemp: Union[str, Path] + basetemp: str | Path @property def trace(self): @@ -394,7 +394,7 @@ def test_cleanup_lock_create(self, tmp_path): def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: lock = create_cleanup_lock(tmp_path) - registry: List[Callable[..., None]] = [] + registry: list[Callable[..., None]] = [] register_cleanup_lock_removal(lock, register=registry.append) (cleanup_func,) = registry diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 9561cad5eb5..56224c08228 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch @@ -1214,7 +1215,7 @@ def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> No We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling tearDown() eventually to avoid memory leaks when using --pdb. """ - teardowns: List[str] = [] + teardowns: list[str] = [] monkeypatch.setattr( pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False ) @@ -1251,7 +1252,7 @@ def test_pdb_teardown_skipped_for_functions( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator (#7215). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( @@ -1286,7 +1287,7 @@ def test_pdb_teardown_skipped_for_classes( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator on the class (#10060). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( diff --git a/testing/test_unraisableexception.py b/testing/test_unraisableexception.py index 1657cfe4a84..a15c754d067 100644 --- a/testing/test_unraisableexception.py +++ b/testing/test_unraisableexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from _pytest.pytester import Pytester diff --git a/testing/test_warning_types.py b/testing/test_warning_types.py index a50d278bde2..19fe0f8a272 100644 --- a/testing/test_warning_types.py +++ b/testing/test_warning_types.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import inspect from _pytest import warning_types diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 73c8c1b3231..d4d0e0b7f93 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,9 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys -from typing import List -from typing import Optional -from typing import Tuple import warnings from _pytest.fixtures import FixtureRequest @@ -618,11 +617,11 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None: f"*== {WARNINGS_SUMMARY_HEADER} ==*", "test_1.py: 21 warnings", "test_2.py: 1 warning", - " */test_1.py:8: UserWarning: foo", + " */test_1.py:10: UserWarning: foo", " warnings.warn(UserWarning(msg))", "", "test_1.py: 20 warnings", - " */test_1.py:8: UserWarning: bar", + " */test_1.py:10: UserWarning: bar", " warnings.warn(UserWarning(msg))", "", "-- Docs: *", @@ -654,8 +653,8 @@ class TestStackLevel: @pytest.fixture def capwarn(self, pytester: Pytester): class CapturedWarnings: - captured: List[ - Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]] + captured: list[ + tuple[warnings.WarningMessage, tuple[str, int, str] | None] ] = [] @classmethod diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 4b146a25110..d4d6a97aea6 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -5,6 +5,8 @@ none of the code triggers any mypy errors. """ +from __future__ import annotations + import contextlib from typing import Optional From b7c0295e1a5186214fa6919f15dfaf379681e8e5 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 17:12:34 +0200 Subject: [PATCH 2/6] use runtime union for EXCEPTION_OR_MORE --- src/_pytest/_code/code.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 750195b94e9..229c7deee90 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -30,7 +30,10 @@ from typing import Pattern from typing import Sequence from typing import SupportsIndex +from typing import Tuple +from typing import Type from typing import TypeVar +from typing import Union import pluggy @@ -53,9 +56,7 @@ TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] -EXCEPTION_OR_MORE = type[Exception] | tuple[type[Exception], ...] - -type_alias = type # to sidestep shadowing +EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]] class Code: From 5e1649f59ace652a6feb84dafa591f3f06cdd6c2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 18:03:10 +0200 Subject: [PATCH 3/6] resolve most sphinx lookup errors add the extra sphinx annotations to refer to Path instances add Path to nitpicky ignore --- doc/en/conf.py | 8 +++- src/_pytest/_code/code.py | 3 +- src/_pytest/config/__init__.py | 68 +++++++++++++++++----------------- src/_pytest/hookspec.py | 6 +++ src/_pytest/main.py | 1 + src/_pytest/pytester.py | 4 ++ src/_pytest/python.py | 2 +- src/_pytest/runner.py | 1 + tox.ini | 8 +++- 9 files changed, 62 insertions(+), 39 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 670d6adf74b..53056024b37 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -23,9 +23,11 @@ from textwrap import dedent from typing import TYPE_CHECKING -from _pytest import __version__ as version +from _pytest import __version__ as full_version +version = full_version.split("+")[0] + if TYPE_CHECKING: import sphinx.application @@ -191,6 +193,7 @@ ("py:class", "SubRequest"), ("py:class", "TerminalReporter"), ("py:class", "_pytest._code.code.TerminalRepr"), + ("py:class", "TerminalRepr"), ("py:class", "_pytest.fixtures.FixtureFunctionMarker"), ("py:class", "_pytest.logging.LogCaptureHandler"), ("py:class", "_pytest.mark.structures.ParameterSet"), @@ -212,13 +215,16 @@ ("py:class", "_PluggyPlugin"), # TypeVars ("py:class", "_pytest._code.code.E"), + ("py:class", "E"), # due to delayed annotation ("py:class", "_pytest.fixtures.FixtureFunction"), ("py:class", "_pytest.nodes._NodeType"), + ("py:class", "_NodeType"), # due to delayed annotation ("py:class", "_pytest.python_api.E"), ("py:class", "_pytest.recwarn.T"), ("py:class", "_pytest.runner.TResult"), ("py:obj", "_pytest.fixtures.FixtureValue"), ("py:obj", "_pytest.stash.T"), + ("py:class", "_ScopeName"), ] diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 229c7deee90..e7452825756 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -620,7 +620,8 @@ def getrepr( showlocals: bool = False, style: TracebackStyle = "long", abspath: bool = False, - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, + tbfilter: bool + | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, truncate_args: bool = True, diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 47e3f5b5a50..23a2c47970a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -13,7 +13,7 @@ import importlib.metadata import inspect import os -from pathlib import Path +import pathlib import re import shlex import sys @@ -114,7 +114,7 @@ class ExitCode(enum.IntEnum): class ConftestImportFailure(Exception): def __init__( self, - path: Path, + path: pathlib.Path, *, cause: Exception, ) -> None: @@ -290,7 +290,7 @@ def get_config( invocation_params=Config.InvocationParams( args=args or (), plugins=plugins, - dir=Path.cwd(), + dir=pathlib.Path.cwd(), ), ) @@ -347,7 +347,7 @@ def _prepareconfig( raise -def _get_directory(path: Path) -> Path: +def _get_directory(path: pathlib.Path) -> pathlib.Path: """Get the directory of a path - itself if already a directory.""" if path.is_file(): return path.parent @@ -408,9 +408,9 @@ def __init__(self) -> None: # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {} + self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Path | None = None + self._confcutdir: pathlib.Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -544,12 +544,12 @@ def pytest_configure(self, config: Config) -> None: # def _set_initial_conftests( self, - args: Sequence[str | Path], + args: Sequence[str | pathlib.Path], pyargs: bool, noconftest: bool, - rootpath: Path, - confcutdir: Path | None, - invocation_dir: Path, + rootpath: pathlib.Path, + confcutdir: pathlib.Path | None, + invocation_dir: pathlib.Path, importmode: ImportMode | str, *, consider_namespace_packages: bool, @@ -593,7 +593,7 @@ def _set_initial_conftests( consider_namespace_packages=consider_namespace_packages, ) - def _is_in_confcutdir(self, path: Path) -> bool: + def _is_in_confcutdir(self, path: pathlib.Path) -> bool: """Whether to consider the given path to load conftests from.""" if self._confcutdir is None: return True @@ -610,9 +610,9 @@ def _is_in_confcutdir(self, path: Path) -> bool: def _try_load_conftest( self, - anchor: Path, + anchor: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> None: @@ -635,9 +635,9 @@ def _try_load_conftest( def _loadconftestmodules( self, - path: Path, + path: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> None: @@ -665,14 +665,14 @@ def _loadconftestmodules( clist.append(mod) self._dirpath2confmods[directory] = clist - def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]: + def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: directory = self._get_directory(path) return self._dirpath2confmods.get(directory, ()) def _rget_with_confmod( self, name: str, - path: Path, + path: pathlib.Path, ) -> tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path) for mod in reversed(modules): @@ -684,9 +684,9 @@ def _rget_with_confmod( def _importconftest( self, - conftestpath: Path, + conftestpath: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> types.ModuleType: @@ -738,7 +738,7 @@ def _importconftest( def _check_non_top_pytest_plugins( self, mod: types.ModuleType, - conftestpath: Path, + conftestpath: pathlib.Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -995,15 +995,15 @@ class InvocationParams: """The command-line arguments as passed to :func:`pytest.main`.""" plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" - dir: Path - """The directory from which :func:`pytest.main` was invoked.""" + dir: pathlib.Path + """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path""" def __init__( self, *, args: Iterable[str], plugins: Sequence[str | _PluggyPlugin] | None, - dir: Path, + dir: pathlib.Path, ) -> None: object.__setattr__(self, "args", tuple(args)) object.__setattr__(self, "plugins", plugins) @@ -1037,7 +1037,7 @@ def __init__( if invocation_params is None: invocation_params = self.InvocationParams( - args=(), plugins=None, dir=Path.cwd() + args=(), plugins=None, dir=pathlib.Path.cwd() ) self.option = argparse.Namespace() @@ -1088,7 +1088,7 @@ def __init__( self.args: list[str] = [] @property - def rootpath(self) -> Path: + def rootpath(self) -> pathlib.Path: """The path to the :ref:`rootdir `. :type: pathlib.Path @@ -1098,11 +1098,9 @@ def rootpath(self) -> Path: return self._rootpath @property - def inipath(self) -> Path | None: + def inipath(self) -> pathlib.Path | None: """The path to the :ref:`configfile `. - :type: Optional[pathlib.Path] - .. versionadded:: 6.1 """ return self._inipath @@ -1313,8 +1311,8 @@ def _decide_args( args: list[str], pyargs: bool, testpaths: list[str], - invocation_dir: Path, - rootpath: Path, + invocation_dir: pathlib.Path, + rootpath: pathlib.Path, warn: bool, ) -> tuple[list[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. @@ -1640,17 +1638,19 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None: + def _getconftest_pathlist( + self, name: str, path: pathlib.Path + ) -> list[pathlib.Path] | None: try: mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None - modpath = Path(mod.__file__).parent - values: list[Path] = [] + modpath = pathlib.Path(mod.__file__).parent + values: list[pathlib.Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): - relroot = Path(relroot) + relroot = pathlib.Path(relroot) else: relroot = relroot.replace("/", os.sep) relroot = absolutepath(modpath / relroot) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 13f4fddbddb..99614899994 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -321,6 +321,7 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. :param collection_path: The path to analyze. + :type collection_path: pathlib.Path :param path: The path to analyze (deprecated). :param config: The pytest config object. @@ -354,6 +355,7 @@ def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: Stops at first non-None result, see :ref:`firstresult`. :param path: The path to analyze. + :type path: pathlib.Path See :ref:`custom directory collectors` for a simple example of use of this hook. @@ -386,6 +388,7 @@ def pytest_collect_file( The new node needs to have the specified ``parent`` as a parent. :param file_path: The path to analyze. + :type file_path: pathlib.Path :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 @@ -507,6 +510,7 @@ def pytest_pycollect_makemodule( Stops at first non-None result, see :ref:`firstresult`. :param module_path: The path of the module to collect. + :type module_path: pathlib.Path :param path: The path of the module to collect (deprecated). .. versionchanged:: 7.0.0 @@ -1026,6 +1030,7 @@ def pytest_report_header( # type:ignore[empty-body] :param config: The pytest config object. :param start_path: The starting dir. + :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). .. note:: @@ -1069,6 +1074,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] :param config: The pytest config object. :param start_path: The starting dir. + :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). :param items: List of pytest items that are going to be executed; this list should not be modified. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index a19ddef58fb..47ebad4713d 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -510,6 +510,7 @@ def from_parent( # type: ignore[override] :param parent: The parent collector of this Dir. :param path: The directory's path. + :type path: pathlib.Path """ return super().from_parent(parent=parent, path=path) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index e27648507e9..5c6ce5e889f 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -909,6 +909,7 @@ def mkdir(self, name: str | os.PathLike[str]) -> Path: The name of the directory, relative to the pytester path. :returns: The created directory. + :rtype: pathlib.Path """ p = self.path / name p.mkdir() @@ -932,6 +933,7 @@ def copy_example(self, name: str | None = None) -> Path: The name of the file to copy. :return: Path to the copied directory (inside ``self.path``). + :rtype: pathlib.Path """ example_dir_ = self._request.config.getini("pytester_example_dir") if example_dir_ is None: @@ -1390,8 +1392,10 @@ def run( - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. + :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int :returns: The result. + """ __tracebackhide__ = True diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2904c3a1e0f..9182ce7dfe9 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1168,7 +1168,7 @@ def parametrize( If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname. - + :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] :param indirect: A list of arguments' names (subset of argnames) or a boolean. If True the list contains all names from the argnames. Each diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index bf30a7d2d27..716c4948f4a 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -327,6 +327,7 @@ def from_call( :param func: The function to call. Called without arguments. + :type func: Callable[[], _pytest.runner.TResult] :param when: The phase in which the function is called. :param reraise: diff --git a/tox.ini b/tox.ini index dae89975467..c421a63b4dd 100644 --- a/tox.ini +++ b/tox.ini @@ -81,7 +81,7 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs] -basepython = python3 +basepython = python3.9 # sync with rtd to get errors usedevelop = True deps = -r{toxinidir}/doc/en/requirements.txt @@ -92,7 +92,11 @@ commands = -git fetch --unshallow -git fetch --tags - sphinx-build -W --keep-going -b html doc/en doc/en/_build/html {posargs:} + sphinx-build \ + -j auto \ + -W --keep-going \ + -b html doc/en doc/en/_build/html \ + {posargs:} setenv = # Sphinx is not clean of this warning. PYTHONWARNDEFAULTENCODING= From 4e54f19be1d45b2dee778becfe766e90906ef274 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 20 Jun 2024 11:00:21 +0200 Subject: [PATCH 4/6] update tox:docs python to rtd python --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c421a63b4dd..2cd8c72a62f 100644 --- a/tox.ini +++ b/tox.ini @@ -81,7 +81,7 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs] -basepython = python3.9 # sync with rtd to get errors +basepython = python3.12 # sync with rtd to get errors usedevelop = True deps = -r{toxinidir}/doc/en/requirements.txt From 85e451a2cc45315dc39ae905d3f6774bf97d75c0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 20 Jun 2024 11:09:48 +0200 Subject: [PATCH 5/6] add changelog entry --- changelog/12467.improvement.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/12467.improvement.rst diff --git a/changelog/12467.improvement.rst b/changelog/12467.improvement.rst new file mode 100644 index 00000000000..b1e0581ed16 --- /dev/null +++ b/changelog/12467.improvement.rst @@ -0,0 +1,3 @@ +Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import. + +-- by :user:`RonnyPfannschmidt` From 6a8e9ed43ade6ea60d0cf72819b187613e592354 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 21 Jun 2024 09:42:24 +0200 Subject: [PATCH 6/6] fixup: Config.cache cannot be None --- src/_pytest/config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 23a2c47970a..0a96d1a31f5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1024,7 +1024,7 @@ class ArgsSource(enum.Enum): TESTPATHS = enum.auto() # Set by cacheprovider plugin. - cache: Cache | None + cache: Cache def __init__( self,