Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions fixtures/_fixtures/popen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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."""
Expand Down Expand Up @@ -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:
Expand Down
36 changes: 32 additions & 4 deletions fixtures/_fixtures/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand All @@ -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
Expand Down
22 changes: 16 additions & 6 deletions fixtures/callmany.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__.

Expand All @@ -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
Expand All @@ -68,7 +78,7 @@ def __call__(
tuple[
type[BaseException] | None,
BaseException | None,
Optional["TracebackType"],
TracebackType | None,
]
]
| None
Expand Down Expand Up @@ -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]:
Expand Down
34 changes: 17 additions & 17 deletions fixtures/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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]

Expand Down Expand Up @@ -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
Expand All @@ -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.

Expand Down Expand Up @@ -130,7 +130,7 @@ def cleanUp(
tuple[
type[BaseException] | None,
BaseException | None,
Optional["TracebackType"],
TracebackType | None,
]
]
| None
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -467,7 +467,7 @@ def cleanUp(
tuple[
type[BaseException] | None,
BaseException | None,
Optional["TracebackType"],
TracebackType | None,
]
]
| None
Expand All @@ -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.
Expand Down