diff --git a/pyproject.toml b/pyproject.toml index 4a7e58fdb..c93111492 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -133,6 +133,7 @@ select = [ "F", # pyflakes "I", # isort "UP", # pyupgrade + "RUF", # ruff "W", # pycodestyle ] ignore = [ @@ -156,6 +157,8 @@ ignore = [ "D402", # First line should not be the function's signature "D404", # First word of the docstring should not be "This" "D415", # First line should end with a period, question mark, or exclamation point + # ruff ignore + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` ] [tool.ruff.format] diff --git a/scripts/release.py b/scripts/release.py index 66617feb5..73f5f52b1 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -107,7 +107,7 @@ def pre_release( def changelog(version: str, write_out: bool = False) -> None: addopts = [] if write_out else ["--draft"] - check_call(["towncrier", "--yes", "--version", version] + addopts) + check_call(["towncrier", "--yes", "--version", version, *addopts]) def main() -> None: diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 0662eb8cb..12168be60 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1018,7 +1018,7 @@ class FormattedExcinfo: extraline: Optional[str] = ( "!!! 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__}: {str(e)}\n" + f" {type(e).__name__}: {e!s}\n" f" Displaying first and last {max_frames} stack frames out of {len(traceback)}." ) # Type ignored because adding two instances of a List subtype diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py index af02d67b9..554cb4a67 100644 --- a/src/_pytest/_py/path.py +++ b/src/_pytest/_py/path.py @@ -1105,9 +1105,7 @@ class LocalPath: modname = self.purebasename spec = importlib.util.spec_from_file_location(modname, str(self)) if spec is None or spec.loader is None: - raise ImportError( - f"Can't find module {modname} at location {str(self)}" - ) + raise ImportError(f"Can't find module {modname} at location {self!s}") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 0ab6eaa13..9f3b65e8f 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -925,7 +925,7 @@ class AssertionRewriter(ast.NodeVisitor): # If any hooks implement assert_pass hook hook_impl_test = ast.If( self.helper("_check_if_assertion_pass_impl"), - self.expl_stmts + [hook_call_pass], + [*self.expl_stmts, hook_call_pass], [], ) statements_pass = [hook_impl_test] diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 902d4baf8..4fdfd86a5 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -92,7 +92,8 @@ def _truncate_explanation( else: # Add proper ellipsis when we were able to fit a full line exactly truncated_explanation[-1] = "..." - return truncated_explanation + [ + return [ + *truncated_explanation, "", f"...Full output truncated ({truncated_line_count} line" f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}", diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 33dcd4628..ca3df7490 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -233,8 +233,8 @@ def assertrepr_compare( return None if explanation[0] != "": - explanation = [""] + explanation - return [summary] + explanation + explanation = ["", *explanation] + return [summary, *explanation] def _compare_eq_any( diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 32e635255..dbe22e4aa 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -27,7 +27,7 @@ _S = TypeVar("_S") # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: Final = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # fmt: on diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 8c2f05239..0d48ef489 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -238,7 +238,8 @@ essential_plugins = ( "helpconfig", # Provides -p. ) -default_plugins = essential_plugins + ( +default_plugins = ( + *essential_plugins, "python", "terminal", "debugging", @@ -671,7 +672,7 @@ class PytestPluginManager(PluginManager): if dirpath in path.parents or path == dirpath: if mod in mods: raise AssertionError( - f"While trying to load conftest path {str(conftestpath)}, " + f"While trying to load conftest path {conftestpath!s}, " f"found that the module {mod} is already loaded with path {mod.__file__}. " "This is not supposed to happen. Please report this issue to pytest." ) diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 18cf4600a..da05acf39 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -122,7 +122,7 @@ class Parser: from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) - groups = self._groups + [self._anonymous] + groups = [*self._groups, self._anonymous] for group in groups: if group.options: desc = group.description or group.name diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index a4e03f80c..b0bbc0e04 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1225,7 +1225,7 @@ def fixture( @overload -def fixture( # noqa: F811 +def fixture( fixture_function: None = ..., *, scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., @@ -1239,7 +1239,7 @@ def fixture( # noqa: F811 ... -def fixture( # noqa: F811 +def fixture( fixture_function: Optional[FixtureFunction] = None, *, scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", @@ -1673,7 +1673,7 @@ class FixtureManager: raise NotImplementedError() @overload - def parsefactories( # noqa: F811 + def parsefactories( self, node_or_obj: object, nodeid: Optional[str], @@ -1682,7 +1682,7 @@ class FixtureManager: ) -> None: raise NotImplementedError() - def parsefactories( # noqa: F811 + def parsefactories( self, node_or_obj: Union[nodes.Node, object], nodeid: Union[str, NotSetType, None] = NOTSET, diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 7a952231c..51becf97b 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -375,7 +375,7 @@ def record_testsuite_property(request: FixtureRequest) -> Callable[[str, object] xml = request.config.stash.get(xml_key, None) if xml is not None: - record_func = xml.add_global_property # noqa + record_func = xml.add_global_property return record_func diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 4907a4256..c9ef877d7 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -726,12 +726,12 @@ class Session(nodes.Collector): ... @overload - def perform_collect( # noqa: F811 + def perform_collect( self, args: Optional[Sequence[str]] = ..., genitems: bool = ... ) -> Sequence[Union[nodes.Item, nodes.Collector]]: ... - def perform_collect( # noqa: F811 + def perform_collect( self, args: Optional[Sequence[str]] = None, genitems: bool = True ) -> Sequence[Union[nodes.Item, nodes.Collector]]: """Perform the collection phase for this session. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 7c8501d73..5a7f9b202 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -406,7 +406,7 @@ def normalize_mark_list( for mark in mark_list: mark_obj = getattr(mark, "mark", mark) if not isinstance(mark_obj, Mark): - raise TypeError(f"got {repr(mark_obj)} instead of Mark") + raise TypeError(f"got {mark_obj!r} instead of Mark") yield mark_obj diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 5190bc35a..8002528b9 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1061,7 +1061,7 @@ class Pytester: :param cmdlineargs: Any extra command line arguments to use. """ p = self.makepyfile(source) - values = list(cmdlineargs) + [p] + values = [*list(cmdlineargs), p] return self.inline_run(*values) def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: @@ -1491,10 +1491,10 @@ class Pytester: """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = ("--basetemp=%s" % p,) + args + args = ("--basetemp=%s" % p, *args) plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: - args = ("-p", plugins[0]) + args + args = ("-p", plugins[0], *args) args = self._getpytestargs() + args return self.run(*args, timeout=timeout) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index d0d744733..a5ae48134 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -358,7 +358,7 @@ class PyobjMixin(nodes.Node): # hook is not called for them. # fmt: off class _EmptyClass: pass # noqa: E701 -IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305 +IGNORED_ATTRIBUTES = frozenset.union( frozenset(), # Module. dir(types.ModuleType("empty_module")), diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index bd3de2897..336b052fc 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -730,7 +730,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: # Type ignored because the error is wrong -- not unreachable. and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable] ): - msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}" + msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" raise TypeError(msg) else: cls = ApproxScalar @@ -780,7 +780,7 @@ def raises( @overload -def raises( # noqa: F811 +def raises( expected_exception: Union[Type[E], Tuple[Type[E], ...]], func: Callable[..., Any], *args: Any, @@ -789,7 +789,7 @@ def raises( # noqa: F811 ... -def raises( # noqa: F811 +def raises( expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any ) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: r"""Assert that a code block/function call raises an exception type, or one of its subclasses. diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index ddd4c9a7b..aa58d43b4 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -47,13 +47,11 @@ def deprecated_call( @overload -def deprecated_call( # noqa: F811 - func: Callable[..., T], *args: Any, **kwargs: Any -) -> T: +def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... -def deprecated_call( # noqa: F811 +def deprecated_call( func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any ) -> Union["WarningsRecorder", Any]: """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. @@ -81,7 +79,7 @@ def deprecated_call( # noqa: F811 """ __tracebackhide__ = True if func is not None: - args = (func,) + args + args = (func, *args) return warns( (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs ) @@ -97,7 +95,7 @@ def warns( @overload -def warns( # noqa: F811 +def warns( expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], func: Callable[..., T], *args: Any, @@ -106,7 +104,7 @@ def warns( # noqa: F811 ... -def warns( # noqa: F811 +def warns( expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, *args: Any, match: Optional[Union[str, Pattern[str]]] = None, diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 57c953f17..11519c7c1 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -17,7 +17,7 @@ import pytest def ignore_encoding_warning(): with warnings.catch_warnings(): with contextlib.suppress(NameError): # new in 3.10 - warnings.simplefilter("ignore", EncodingWarning) # type: ignore [name-defined] # noqa: F821 + warnings.simplefilter("ignore", EncodingWarning) # type: ignore [name-defined] yield diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 846e23b2a..a0ee28d48 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1739,7 +1739,7 @@ def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) def add_note(err: BaseException, msg: str) -> None: """Adds a note to an exception inplace.""" if sys.version_info < (3, 11): - err.__notes__ = getattr(err, "__notes__", []) + [msg] # type: ignore[attr-defined] + err.__notes__ = [*getattr(err, "__notes__", []), msg] # type: ignore[attr-defined] else: err.add_note(msg) diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py index 3803b3b24..fbfda2e5d 100644 --- a/testing/freeze/create_executable.py +++ b/testing/freeze/create_executable.py @@ -9,5 +9,5 @@ if __name__ == "__main__": for x in pytest.freeze_includes(): hidden.extend(["--hidden-import", x]) hidden.extend(["--hidden-import", "distutils"]) - args = ["pyinstaller", "--noconfirm"] + hidden + ["runtests_script.py"] + args = ["pyinstaller", "--noconfirm", *hidden, "runtests_script.py"] subprocess.check_call(" ".join(args), shell=True) diff --git a/testing/io/test_wcwidth.py b/testing/io/test_wcwidth.py index 9ed2a6d3c..0989af00d 100644 --- a/testing/io/test_wcwidth.py +++ b/testing/io/test_wcwidth.py @@ -15,7 +15,7 @@ import pytest ("\u1ABE", 0), ("\u0591", 0), ("🉐", 2), - ("$", 2), + ("$", 2), # noqa: RUF001 ], ) def test_wcwidth(c: str, expected: int) -> None: diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 7ef334950..24eae19b7 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1032,7 +1032,7 @@ def test_log_set_path(pytester: Pytester) -> None: def pytest_runtest_setup(item): config = item.config logging_plugin = config.pluginmanager.get_plugin("logging-plugin") - report_file = os.path.join({repr(report_dir_base)}, item._request.node.name) + report_file = os.path.join({report_dir_base!r}, item._request.node.name) logging_plugin.set_log_path(report_file) return (yield) """ diff --git a/testing/python/collect.py b/testing/python/collect.py index 4d1f97b34..745550f07 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1209,7 +1209,7 @@ class TestReportInfo: classcol = pytester.collect_by_name(modcol, "TestClass") assert isinstance(classcol, Class) path, lineno, msg = classcol.reportinfo() - func = list(classcol.collect())[0] + func = next(iter(classcol.collect())) assert isinstance(func, Function) path, lineno, msg = func.reportinfo() diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 62f25cb0b..d0e8723cb 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3086,7 +3086,7 @@ class TestFixtureMarker: def test_other(): pass """ # noqa: UP031 (python syntax issues) - % {"scope": scope} # noqa: UP031 (python syntax issues) + % {"scope": scope} ) reprec = pytester.inline_run("-lvs") reprec.assertoutcome(passed=3) @@ -4536,5 +4536,5 @@ def test_yield_fixture_with_no_value(pytester: Pytester) -> None: def test_deduplicate_names() -> None: items = deduplicate_names("abacd") assert items == ("a", "b", "c", "d") - items = deduplicate_names(items + ("g", "f", "g", "e", "b")) + items = deduplicate_names((*items, "g", "f", "g", "e", "b")) assert items == ("a", "b", "c", "d", "g", "f", "e") diff --git a/testing/python/raises.py b/testing/python/raises.py index 35d7f1a44..ef6607d96 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -147,7 +147,7 @@ class TestRaises: try: pytest.raises(ValueError, int, "0") except pytest.fail.Exception as e: - assert e.msg == f"DID NOT RAISE {repr(ValueError)}" + assert e.msg == f"DID NOT RAISE {ValueError!r}" else: assert False, "Expected pytest.raises.Exception" @@ -155,7 +155,7 @@ class TestRaises: with pytest.raises(ValueError): pass except pytest.fail.Exception as e: - assert e.msg == f"DID NOT RAISE {repr(ValueError)}" + assert e.msg == f"DID NOT RAISE {ValueError!r}" else: assert False, "Expected pytest.raises.Exception" diff --git a/testing/test_assertion.py b/testing/test_assertion.py index f8196efea..2fa6fbe37 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -914,16 +914,16 @@ class TestAssert_reprcompare: assert expl == [ r"'hyv\xe4' == 'hyva\u0308'", "", - f"- {str(right)}", - f"+ {str(left)}", + f"- {right!s}", + f"+ {left!s}", ] expl = callequal(left, right, verbose=2) assert expl == [ r"'hyv\xe4' == 'hyva\u0308'", "", - f"- {str(right)}", - f"+ {str(left)}", + f"- {right!s}", + f"+ {left!s}", ] @@ -1149,7 +1149,7 @@ class TestAssert_reprcompare_attrsclass: def test_attrs_with_auto_detect_and_custom_eq(self) -> None: @attr.s( auto_detect=True - ) # attr.s doesn’t ignore a custom eq if auto_detect=True + ) # attr.s doesn't ignore a custom eq if auto_detect=True class SimpleDataObject: field_a = attr.ib() diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 835e0e1e6..372528490 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -190,7 +190,7 @@ class TestDoctests: ) doctest = f""" >>> "{test_string}" - {repr(test_string)} + {test_string!r} """ fn = pytester.path / "test_encoding.txt" fn.write_text(doctest, encoding=encoding) @@ -729,7 +729,7 @@ class TestDoctests: >>> name = 'с' # not letter 'c' but instead Cyrillic 's'. 'anything' """ - ''' + ''' # noqa: RUF001 ) result = pytester.runpytest("--doctest-modules") result.stdout.fnmatch_lines(["Got nothing", "* 1 failed in*"]) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 9b8030e7d..42104255b 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -42,7 +42,7 @@ class RunAndParse: self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" ) -> Tuple[RunResult, "DomNode"]: if family: - args = ("-o", "junit_family=" + family) + args + args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) if family == "xunit2": diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 84fb96ccb..9c6081a56 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -227,7 +227,7 @@ class TestInlineRunModulesCleanup: def spy_factory(self): class SysModulesSnapshotSpy: - instances: List["SysModulesSnapshotSpy"] = [] # noqa: F821 + instances: List["SysModulesSnapshotSpy"] = [] def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) @@ -725,7 +725,7 @@ def test_run_result_repr() -> None: # known exit code r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5) assert repr(r) == ( - f"" ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 0eb50926e..22f041ced 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -417,8 +417,8 @@ class TestTerminal: result = pytester.runpytest("-v") result.stdout.fnmatch_lines( - common_output - + [ + [ + *common_output, "test_verbose_skip_reason.py::test_long_skip SKIPPED (1 cannot *...) *", "test_verbose_skip_reason.py::test_long_xfail XFAIL (2 cannot *...) *", ] @@ -426,17 +426,13 @@ class TestTerminal: result = pytester.runpytest("-vv") result.stdout.fnmatch_lines( - common_output - + [ - ( - "test_verbose_skip_reason.py::test_long_skip SKIPPED" - " (1 cannot do foobar" - ), + [ + *common_output, + "test_verbose_skip_reason.py::test_long_skip SKIPPED" + " (1 cannot do foobar", "because baz is missing due to I don't know what) *", - ( - "test_verbose_skip_reason.py::test_long_xfail XFAIL" - " (2 cannot do foobar" - ), + "test_verbose_skip_reason.py::test_long_xfail XFAIL" + " (2 cannot do foobar", "because baz is missing due to I don't know what) *", ] )