diff --git a/fixtures/_fixtures/popen.py b/fixtures/_fixtures/popen.py index 038b8d0..e128adc 100644 --- a/fixtures/_fixtures/popen.py +++ b/fixtures/_fixtures/popen.py @@ -13,6 +13,8 @@ # license you chose for the specific language governing permissions and # limitations under that license. +from __future__ import annotations + __all__ = [ "FakePopen", "PopenFixture", @@ -21,11 +23,17 @@ import random import subprocess import sys -from typing import Any, IO, Final +from typing import Any, IO, Final, TYPE_CHECKING from collections.abc import Callable from fixtures import Fixture +if TYPE_CHECKING: + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + class _Unpassed: """Sentinel type for unpassed arguments.""" @@ -77,7 +85,7 @@ def communicate( err = "" return out, err - def __enter__(self) -> "FakeProcess": + def __enter__(self) -> Self: return self def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: diff --git a/fixtures/_fixtures/warnings.py b/fixtures/_fixtures/warnings.py index dc37752..984afbb 100644 --- a/fixtures/_fixtures/warnings.py +++ b/fixtures/_fixtures/warnings.py @@ -11,17 +11,35 @@ # license you chose for the specific language governing permissions and # limitations under that license. +from __future__ import annotations + __all__ = [ "WarningsCapture", "WarningsFilter", ] +import sys import warnings -from typing import Any, cast +from typing import Any, cast, Literal, TextIO, TypedDict, TYPE_CHECKING import fixtures from fixtures._fixtures.monkeypatch import MonkeyPatch +if TYPE_CHECKING: + if sys.version_info >= (3, 11): + from typing import NotRequired + else: + from typing_extensions import NotRequired + + +class _WarningFilterArgs(TypedDict): + action: Literal["error", "ignore", "always", "default", "module", "once"] + message: NotRequired[str] + category: NotRequired[type[Warning]] + module: NotRequired[str] + lineno: NotRequired[int] + append: NotRequired[bool] + class WarningsCapture(fixtures.Fixture): """Capture warnings. @@ -34,8 +52,18 @@ class WarningsCapture(fixtures.Fixture): captures: list[warnings.WarningMessage] - def _showwarning(self, *args: Any, **kwargs: Any) -> None: - self.captures.append(warnings.WarningMessage(*args, **kwargs)) + def _showwarning( + self, + message: str, + category: type[Warning], + filename: str, + lineno: int, + file: TextIO | None = None, + line: str | None = None, + ) -> None: + self.captures.append( + warnings.WarningMessage(message, category, filename, lineno, file, line) + ) def _setUp(self) -> None: patch = MonkeyPatch("warnings.showwarning", self._showwarning) @@ -50,7 +78,7 @@ class WarningsFilter(fixtures.Fixture): configuration. """ - def __init__(self, filters: list[dict[str, Any]] | None = None) -> None: + def __init__(self, filters: list[_WarningFilterArgs] | None = None) -> None: """Create a WarningsFilter fixture. :param filters: An optional list of dictionaries with arguments diff --git a/fixtures/callmany.py b/fixtures/callmany.py index b756184..10d750d 100644 --- a/fixtures/callmany.py +++ b/fixtures/callmany.py @@ -13,17 +13,22 @@ # license you chose for the specific language governing permissions and # limitations under that license. +from __future__ import annotations + __all__ = [ "CallMany", ] import sys -from typing import Any, Literal, Optional, TYPE_CHECKING from collections.abc import Callable +from typing import Any, Literal, ParamSpec, TYPE_CHECKING +from types import TracebackType if TYPE_CHECKING: - from types import TracebackType - + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self try: from testtools import MultipleExceptions @@ -33,6 +38,9 @@ class MultipleExceptions(Exception): # type: ignore[no-redef] """Report multiple exc_info tuples in self.args.""" +P = ParamSpec("P") + + class CallMany: """A stack of functions which will all be called on __call__. @@ -48,7 +56,9 @@ def __init__(self) -> None: tuple[Callable[..., Any], tuple[Any, ...], dict[str, Any]] ] = [] - def push(self, cleanup: Callable[..., Any], *args: Any, **kwargs: Any) -> None: + def push( + self, cleanup: Callable[P, Any], *args: P.args, **kwargs: P.kwargs + ) -> None: """Add a function to be called from __call__. On __call__ all functions are called - see __call__ for details on how @@ -68,7 +78,7 @@ def __call__( tuple[ type[BaseException] | None, BaseException | None, - Optional["TracebackType"], + TracebackType | None, ] ] | None @@ -115,7 +125,7 @@ def __call__( return result return None - def __enter__(self) -> "CallMany": + def __enter__(self) -> Self: return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Literal[False]: diff --git a/fixtures/fixture.py b/fixtures/fixture.py index e81859d..72d895d 100644 --- a/fixtures/fixture.py +++ b/fixtures/fixture.py @@ -13,6 +13,8 @@ # license you chose for the specific language governing permissions and # limitations under that license. +from __future__ import annotations + __all__ = [ "CompoundFixture", "Fixture", @@ -24,26 +26,24 @@ import itertools import sys -from typing import ( - Any, - Literal, - Optional, - TypeVar, - TYPE_CHECKING, -) from collections.abc import Callable, Iterable +from typing import Any, Literal, ParamSpec, TypeVar, TYPE_CHECKING +from types import TracebackType -from fixtures.callmany import ( - CallMany, -) +from fixtures.callmany import CallMany # Deprecated, imported for compatibility. import fixtures.callmany if TYPE_CHECKING: - from types import TracebackType + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + T = TypeVar("T", bound="Fixture") +P = ParamSpec("P") MultipleExceptions = fixtures.callmany.MultipleExceptions # type: ignore[attr-defined] @@ -80,7 +80,7 @@ class SetupError(Exception): class Fixture: _cleanups: CallMany | None _details: dict[str, Any] | None - _detail_sources: list["Fixture"] | None + _detail_sources: list[Fixture] | None """A Fixture representing some state or resource. Often used in tests, a Fixture must be setUp before using it, and cleanUp @@ -92,7 +92,7 @@ class Fixture: """ def addCleanup( - self, cleanup: Callable[..., Any], *args: Any, **kwargs: Any + self, cleanup: Callable[P, Any], *args: P.args, **kwargs: P.kwargs ) -> None: """Add a clean function to be called from cleanUp. @@ -130,7 +130,7 @@ def cleanUp( tuple[ type[BaseException] | None, BaseException | None, - Optional["TracebackType"], + TracebackType | None, ] ] | None @@ -185,7 +185,7 @@ def _remove_state(self) -> None: self._details = None self._detail_sources = None - def __enter__(self) -> "Fixture": + def __enter__(self) -> Self: self.setUp() return self @@ -467,7 +467,7 @@ def cleanUp( tuple[ type[BaseException] | None, BaseException | None, - Optional["TracebackType"], + TracebackType | None, ] ] | None @@ -489,7 +489,7 @@ class CompoundFixture(Fixture): :ivar fixtures: The list of fixtures that make up this one. (read only). """ - def __init__(self, fixtures: Iterable["Fixture"]) -> None: + def __init__(self, fixtures: Iterable[Fixture]) -> None: """Construct a fixture made of many fixtures. :param fixtures: An iterable of fixtures.