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
26 changes: 22 additions & 4 deletions assertpy2/_engine/_builder_check_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from pathlib import Path
from typing import Any, Protocol, SupportsFloat, TypeVar, overload

from .._matcher_impls import ClassInfo
from ..matchers import Matcher
from ..outcome import AssertionOutcome
from ._capable_typing import _Callable, _Orderable, _PathLike
Expand All @@ -44,6 +45,8 @@
_V = TypeVar("_V")
_R = TypeVar("_R")
_B_co = TypeVar("_B_co", bytes, bytearray, covariant=True)
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3")
_Number = SupportsFloat

# the rungs below restrict `self` with the annotations `assert_that()` overloads are written with
Expand Down Expand Up @@ -1170,11 +1173,28 @@ def is_instance_of(self: _CheckAnyValue[_T], some_class: type[bytes]) -> Asserti
@overload
def is_instance_of(self: _CheckAnyValue[_T], some_class: type[bytearray]) -> AssertionOutcome: ...
@overload
def is_instance_of(self: _CheckAnyValue[_T], some_class: tuple[type[_U], type[_U2]]) -> AssertionOutcome: ...
@overload
def is_instance_of(
self: _CheckAnyValue[_T], some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> AssertionOutcome: ...
@overload
def is_instance_of(self: _CheckAnyValue[_T], some_class: type[_U]) -> AssertionOutcome: ...
@overload
def is_instance_of(self: _CheckAnyValue[_T], some_class: type) -> AssertionOutcome: ...
def is_instance_of(self: _CheckAnyValue[_T], some_class: ClassInfo) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: type) -> AssertionOutcome: ...
def is_instance_of(self, some_class: ClassInfo) -> AssertionOutcome: ...

@overload
def is_instance_of_any(self: _CheckAnyValue[_T], first: type[_U], second: type[_U2], /) -> AssertionOutcome: ...
@overload
def is_instance_of_any(
self: _CheckAnyValue[_T], first: type[_U], second: type[_U2], third: type[_U3], /
) -> AssertionOutcome: ...
@overload
def is_instance_of_any(self: _CheckAnyValue[_T], *some_classes: ClassInfo) -> AssertionOutcome: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> AssertionOutcome: ...

def is_equal_to(
self,
Expand Down Expand Up @@ -1202,8 +1222,6 @@ def is_none(self) -> AssertionOutcome: ...

def is_type_of(self, some_type: type) -> AssertionOutcome: ...

def is_instance_of_any(self, *some_classes: type) -> AssertionOutcome: ...

def is_subclass_of(self, some_class: type) -> AssertionOutcome: ...

def is_length(self, length: int) -> AssertionOutcome: ...
Expand Down
20 changes: 18 additions & 2 deletions assertpy2/_engine/_capable_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

from typing_extensions import TypeIs

from .._matcher_impls import ClassInfo
from ..assertpy import AssertionBuilder
from ..matchers import Matcher
from ._builder_check_typing import _CheckAnyValue
Expand All @@ -39,6 +40,8 @@

# covariant: the façade only ever hands the subject back, through `value`
_CapableT_co = TypeVar("_CapableT_co", covariant=True)
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3")
_E_co = TypeVar("_E_co", covariant=True)

class _Orderable(Protocol):
Expand Down Expand Up @@ -88,9 +91,23 @@ def is_not_none(self: _CapableAssertion[_U | None]) -> _CapableAssertion[_U]: ..
@overload
def is_not_none(self) -> Self: ...
@overload
def is_instance_of(self, some_class: tuple[type[_U], type[_U2]]) -> AssertionBuilder[_U | _U2]: ...
@overload
def is_instance_of(
self, some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> AssertionBuilder[_U | _U2 | _U3]: ...
@overload
def is_instance_of(self, some_class: type[_U]) -> AssertionBuilder[_U]: ...
@overload
def is_instance_of(self, some_class: type) -> Self: ...
def is_instance_of(self, some_class: ClassInfo) -> Self: ...
@overload
def is_instance_of_any(self, first: type[_U], second: type[_U2], /) -> AssertionBuilder[_U | _U2]: ...
@overload
def is_instance_of_any(
self, first: type[_U], second: type[_U2], third: type[_U3], /
) -> AssertionBuilder[_U | _U2 | _U3]: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> Self: ...
@overload
def first(self: _CapableAssertion[Mapping[_K, _V]]) -> AssertionBuilder[_K]: ...
@overload
Expand Down Expand Up @@ -294,7 +311,6 @@ def is_hex_equal_to(
) -> _CapableAssertion[_CapableT_co]: ...
def is_in(self, *items: object) -> Self: ...
def is_inf(self) -> Self: ...
def is_instance_of_any(self, *some_classes: type) -> Self: ...
def is_iterable(self) -> Self: ...
def is_length(self, length: int) -> Self: ...
def is_length_between(self, low: int, high: int) -> Self: ...
Expand Down
19 changes: 16 additions & 3 deletions assertpy2/_engine/_check_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from typing_extensions import TypeIs

from .._matcher_impls import ClassInfo
from ..errors import AssertionOutcome
from ..matchers import Matcher
from ._compat import Self
Expand All @@ -45,6 +46,8 @@
_K = TypeVar("_K")
_V = TypeVar("_V")
_B_co = TypeVar("_B_co", bytes, bytearray, covariant=True)
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3")
_Number = SupportsFloat

class _CheckMembershipAssertion(Protocol):
Expand Down Expand Up @@ -178,8 +181,8 @@ def is_false(self) -> AssertionOutcome: ...
def is_none(self) -> AssertionOutcome: ...
def is_not_none(self) -> AssertionOutcome: ...
def is_type_of(self, some_type: type) -> AssertionOutcome: ...
def is_instance_of(self, some_class: type) -> AssertionOutcome: ...
def is_instance_of_any(self, *some_classes: type) -> AssertionOutcome: ...
def is_instance_of(self, some_class: ClassInfo) -> AssertionOutcome: ...
def is_instance_of_any(self, *some_classes: ClassInfo) -> AssertionOutcome: ...
def is_subclass_of(self, some_class: type) -> AssertionOutcome: ...
def is_length(self, length: int) -> AssertionOutcome: ...
def is_length_between(self, low: int, high: int) -> AssertionOutcome: ...
Expand Down Expand Up @@ -321,9 +324,19 @@ def is_instance_of(self, some_class: type[bytes]) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: type[bytearray]) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: tuple[type[_U], type[_U2]]) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: tuple[type[_U], type[_U2], type[_U3]]) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: type[_U]) -> AssertionOutcome: ...
@overload
def is_instance_of(self, some_class: type) -> AssertionOutcome: ...
def is_instance_of(self, some_class: ClassInfo) -> AssertionOutcome: ...
@overload
def is_instance_of_any(self, first: type[_U], second: type[_U2], /) -> AssertionOutcome: ...
@overload
def is_instance_of_any(self, first: type[_U], second: type[_U2], third: type[_U3], /) -> AssertionOutcome: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> AssertionOutcome: ...
@property
def not_(self) -> Self: ...

Expand Down
49 changes: 41 additions & 8 deletions assertpy2/_engine/_poll_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from pathlib import Path
from typing import Any, Protocol, SupportsFloat, TypeVar, overload

from .._matcher_impls import ClassInfo
from ..assertpy import AssertionBuilder
from ..matchers import Matcher
from ._capable_typing import _Callable, _Orderable, _PathLike
Expand All @@ -52,6 +53,8 @@
_V = TypeVar("_V")
_R = TypeVar("_R")
_B_co = TypeVar("_B_co", bytes, bytearray, covariant=True)
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3")
_Number = SupportsFloat

# the rungs below restrict `self` with the annotations `assert_that()` overloads are written with
Expand Down Expand Up @@ -1256,11 +1259,28 @@ def is_instance_of(self: _SyncPoll[_T], some_class: type[bytes]) -> _SyncPoll[by
@overload
def is_instance_of(self: _SyncPoll[_T], some_class: type[bytearray]) -> _SyncPoll[bytearray]: ...
@overload
def is_instance_of(self: _SyncPoll[_T], some_class: tuple[type[_U], type[_U2]]) -> _SyncPoll[_U | _U2]: ...
@overload
def is_instance_of(
self: _SyncPoll[_T], some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> _SyncPoll[_U | _U2 | _U3]: ...
@overload
def is_instance_of(self: _SyncPoll[_T], some_class: type[_U]) -> _SyncPoll[_U]: ...
@overload
def is_instance_of(self: _SyncPoll[_T], some_class: type) -> _SyncPoll[_P_co]: ...
def is_instance_of(self: _SyncPoll[_T], some_class: ClassInfo) -> _SyncPoll[_P_co]: ...
@overload
def is_instance_of(self, some_class: type) -> _SyncPoll[_P_co]: ...
def is_instance_of(self, some_class: ClassInfo) -> _SyncPoll[_P_co]: ...

@overload
def is_instance_of_any(self: _SyncPoll[_T], first: type[_U], second: type[_U2], /) -> _SyncPoll[_U | _U2]: ...
@overload
def is_instance_of_any(
self: _SyncPoll[_T], first: type[_U], second: type[_U2], third: type[_U3], /
) -> _SyncPoll[_U | _U2 | _U3]: ...
@overload
def is_instance_of_any(self: _SyncPoll[_T], *some_classes: ClassInfo) -> _SyncPoll[_P_co]: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> _SyncPoll[_P_co]: ...

def described_as(self, description: str) -> _SyncPoll[_P_co]: ...

Expand Down Expand Up @@ -1290,8 +1310,6 @@ def is_none(self) -> _SyncPoll[_P_co]: ...

def is_type_of(self, some_type: type) -> _SyncPoll[_P_co]: ...

def is_instance_of_any(self, *some_classes: type) -> _SyncPoll[_P_co]: ...

def is_subclass_of(self, some_class: type) -> _SyncPoll[_P_co]: ...

def is_length(self, length: int) -> _SyncPoll[_P_co]: ...
Expand Down Expand Up @@ -2563,11 +2581,28 @@ def is_instance_of(self: _AsyncPoll[_T], some_class: type[bytes]) -> _AsyncPoll[
@overload
def is_instance_of(self: _AsyncPoll[_T], some_class: type[bytearray]) -> _AsyncPoll[bytearray]: ...
@overload
def is_instance_of(self: _AsyncPoll[_T], some_class: tuple[type[_U], type[_U2]]) -> _AsyncPoll[_U | _U2]: ...
@overload
def is_instance_of(
self: _AsyncPoll[_T], some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> _AsyncPoll[_U | _U2 | _U3]: ...
@overload
def is_instance_of(self: _AsyncPoll[_T], some_class: type[_U]) -> _AsyncPoll[_U]: ...
@overload
def is_instance_of(self: _AsyncPoll[_T], some_class: type) -> _AsyncPoll[_P_co]: ...
def is_instance_of(self: _AsyncPoll[_T], some_class: ClassInfo) -> _AsyncPoll[_P_co]: ...
@overload
def is_instance_of(self, some_class: type) -> _AsyncPoll[_P_co]: ...
def is_instance_of(self, some_class: ClassInfo) -> _AsyncPoll[_P_co]: ...

@overload
def is_instance_of_any(self: _AsyncPoll[_T], first: type[_U], second: type[_U2], /) -> _AsyncPoll[_U | _U2]: ...
@overload
def is_instance_of_any(
self: _AsyncPoll[_T], first: type[_U], second: type[_U2], third: type[_U3], /
) -> _AsyncPoll[_U | _U2 | _U3]: ...
@overload
def is_instance_of_any(self: _AsyncPoll[_T], *some_classes: ClassInfo) -> _AsyncPoll[_P_co]: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> _AsyncPoll[_P_co]: ...

def described_as(self, description: str) -> _AsyncPoll[_P_co]: ...

Expand Down Expand Up @@ -2597,8 +2632,6 @@ def is_none(self) -> _AsyncPoll[_P_co]: ...

def is_type_of(self, some_type: type) -> _AsyncPoll[_P_co]: ...

def is_instance_of_any(self, *some_classes: type) -> _AsyncPoll[_P_co]: ...

def is_subclass_of(self, some_class: type) -> _AsyncPoll[_P_co]: ...

def is_length(self, length: int) -> _AsyncPoll[_P_co]: ...
Expand Down
26 changes: 22 additions & 4 deletions assertpy2/_engine/_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing_extensions import TypeIs

from .._engine._introspection import MappingLike
from .._matcher_impls import ClassInfo
from ..assertpy import AssertionBuilder
from ..matchers import Matcher
from ._check_typing import (
Expand Down Expand Up @@ -44,7 +45,9 @@
_K = TypeVar("_K") # tracked dict key type
_V = TypeVar("_V") # tracked dict value type
_B_co = TypeVar("_B_co", bytes, bytearray, covariant=True) # tracked bytes type (output-only -> covariant)
_U = TypeVar("_U") # the type a TypeIs predicate refines the tracked value to
_U = TypeVar("_U")
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3") # the type a TypeIs predicate refines the tracked value to
_Other = TypeVar("_Other") # an element of the sequence a pairwise quantifier walks alongside
_T_co = TypeVar("_T_co", covariant=True) # the subject of a value no overload recognises
_P_co = TypeVar("_P_co", covariant=True) # what a polled probe hands back, which is what its chain asserts on
Expand Down Expand Up @@ -234,8 +237,8 @@ def is_false(self) -> Self: ...
def is_none(self) -> Self: ...
def is_not_none(self) -> Self: ...
def is_type_of(self, some_type: type) -> Self: ...
def is_instance_of(self, some_class: type) -> Self: ...
def is_instance_of_any(self, *some_classes: type) -> Self: ...
def is_instance_of(self, some_class: ClassInfo) -> Self: ...
def is_instance_of_any(self, *some_classes: ClassInfo) -> Self: ...
def is_subclass_of(self, some_class: type) -> Self: ...
def is_length(self, length: int) -> Self: ...
def is_length_between(self, low: int, high: int) -> Self: ...
Expand Down Expand Up @@ -413,9 +416,24 @@ def is_instance_of(self, some_class: type[bytes]) -> _BytesAssertion[bytes]: ...
@overload
def is_instance_of(self, some_class: type[bytearray]) -> _BytesAssertion[bytearray]: ...
@overload
def is_instance_of(self, some_class: tuple[type[_U], type[_U2]]) -> _ObjectAssertion[_U | _U2]: ...
@overload
def is_instance_of(
self, some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> _ObjectAssertion[_U | _U2 | _U3]: ...
@overload
def is_instance_of(self, some_class: type[_U]) -> _ObjectAssertion[_U]: ...
@overload
def is_instance_of(self, some_class: type) -> Self: ...
def is_instance_of(self, some_class: ClassInfo) -> Self: ...

@overload
def is_instance_of_any(self, first: type[_U], second: type[_U2], /) -> _ObjectAssertion[_U | _U2]: ...
@overload
def is_instance_of_any(
self, first: type[_U], second: type[_U2], third: type[_U3], /
) -> _ObjectAssertion[_U | _U2 | _U3]: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> Self: ...

class _TextAssertion(_MembershipAssertion, _RepeatableAssertion[str], _SizedAssertion, _CoreAssertion, Protocol):
"""What a piece of text can be asked, whether a caller passed it in or the library caught it.
Expand Down
10 changes: 7 additions & 3 deletions assertpy2/_matcher_impls.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@
from ._engine._compare import _CompareConfig
from .errors import DiffResult

# recursive because `isinstance()` accepts tuples nested to any depth, and a declaration that refuses what
# the runtime takes is the defect this alias exists to avoid
ClassInfo: TypeAlias = "type | UnionType | tuple[ClassInfo, ...]"
# nested because `isinstance()` accepts tuples nested to any depth, and a declaration that refuses what the
# runtime takes is the defect this alias exists to avoid. Only the recursive reference is quoted, and the
# first level is written out: ty ignores an alias whose whole right-hand side is a string, so the parameter
# it annotates accepted anything and `is_instance_of("int")` stopped being a type error. This way pyright
# and mypy still check a member at any depth while ty checks the outermost one. A PEP 695 `type` statement
# is read correctly by all of them and is a SyntaxError on 3.10, even under `TYPE_CHECKING`
ClassInfo: TypeAlias = type | UnionType | tuple[type | UnionType | tuple["ClassInfo", ...], ...]


_M_contra = TypeVar("_M_contra", contravariant=True)
Expand Down
27 changes: 23 additions & 4 deletions assertpy2/assertpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
_PathAssertion,
_StringAssertion,
)
from ._matcher_impls import ClassInfo
from .errors import PollTrace
from .matchers import Matcher

Expand Down Expand Up @@ -88,6 +89,8 @@
_S = TypeVar("_S")
if TYPE_CHECKING:
_U = TypeVar("_U")
_U2 = TypeVar("_U2")
_U3 = TypeVar("_U3")
_E = TypeVar("_E") # element type of a collection, so first()/element()/... narrow to it
_R = TypeVar("_R") # result element type after a mapping pivot
_K = TypeVar("_K") # dict key type, so .value keeps dict[K, V]
Expand Down Expand Up @@ -1305,13 +1308,29 @@ def is_not_none(self: AssertionBuilder[_U | None]) -> AssertionBuilder[_U]: ...
def is_not_none(self) -> Self: ...
def is_not_none(self) -> Any: ...

# never picked by a call, and it keeps the class conformant with the protocols' `(type) -> Self`; pyright
# reports the overlap and that is intended
# the second rung is what a tuple lands on, and what a union lands on under the checkers that read it
# as `UnionType`. It also keeps the class conformant with the protocols' `(ClassInfo) -> Self`
@overload
def is_instance_of(self, some_class: tuple[type[_U], type[_U2]]) -> AssertionBuilder[_U | _U2]: ...
@overload
def is_instance_of(
self, some_class: tuple[type[_U], type[_U2], type[_U3]]
) -> AssertionBuilder[_U | _U2 | _U3]: ...
@overload
def is_instance_of(self, some_class: type[_U]) -> AssertionBuilder[_U]: ...
@overload
def is_instance_of(self, some_class: type) -> Self: ...
def is_instance_of(self, some_class: type) -> Any: ...
def is_instance_of(self, some_class: ClassInfo) -> Self: ...
def is_instance_of(self, some_class: Any) -> Any: ...

@overload
def is_instance_of_any(self, first: type[_U], second: type[_U2], /) -> AssertionBuilder[_U | _U2]: ...
@overload
def is_instance_of_any(
self, first: type[_U], second: type[_U2], third: type[_U3], /
) -> AssertionBuilder[_U | _U2 | _U3]: ...
@overload
def is_instance_of_any(self, *some_classes: ClassInfo) -> Self: ...
def is_instance_of_any(self, *some_classes: Any) -> Any: ...

# the element pivots return `self.builder(<an element>)` while `CollectionMixin` declares `-> Self`, so
# `assert_that(rows).first().value.count(1)` type-checked and raised. Structural because `_T` is invariant:
Expand Down
Loading