panda-pytest-assertions 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- panda_pytest_assertions/__init__.py +0 -0
- panda_pytest_assertions/assert_context/__init__.py +15 -0
- panda_pytest_assertions/assert_context/_assert_context.py +128 -0
- panda_pytest_assertions/assert_object/__init__.py +41 -0
- panda_pytest_assertions/assert_object/_assert_object.py +36 -0
- panda_pytest_assertions/assert_object/_asserter_factories/__init__.py +8 -0
- panda_pytest_assertions/assert_object/_asserter_factories/asserter_factory.py +66 -0
- panda_pytest_assertions/assert_object/_asserter_factories/built_in_asserter_factory.py +29 -0
- panda_pytest_assertions/assert_object/_converters/__init__.py +10 -0
- panda_pytest_assertions/assert_object/_converters/expectation_from_yaml.py +114 -0
- panda_pytest_assertions/assert_object/_converters/expectation_to_yaml.py +78 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/__init__.py +15 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/mapping_subset.py +15 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/object_attibutes.py +15 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/stringified.py +11 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/unordered.py +18 -0
- panda_pytest_assertions/assert_object/_expectation_modificators/with_type.py +135 -0
- panda_pytest_assertions/assert_object/_objects_asserters/__init__.py +36 -0
- panda_pytest_assertions/assert_object/_objects_asserters/asserter.py +35 -0
- panda_pytest_assertions/assert_object/_objects_asserters/equality_asserter.py +33 -0
- panda_pytest_assertions/assert_object/_objects_asserters/mapping_asserter.py +55 -0
- panda_pytest_assertions/assert_object/_objects_asserters/mapping_subset_asserter.py +53 -0
- panda_pytest_assertions/assert_object/_objects_asserters/object_attributes_asserter.py +52 -0
- panda_pytest_assertions/assert_object/_objects_asserters/ordered_elements_asserter.py +55 -0
- panda_pytest_assertions/assert_object/_objects_asserters/stringified_asserter.py +36 -0
- panda_pytest_assertions/assert_object/_objects_asserters/unordered_elements_asserter.py +77 -0
- panda_pytest_assertions/assert_object/_objects_asserters/with_type_asserter.py +57 -0
- panda_pytest_assertions/assert_object/_protocols.py +93 -0
- panda_pytest_assertions/assert_object/generators/__init__.py +41 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/__init__.py +30 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/equality.py +6 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/mapping.py +14 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/mapping_subset.py +14 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/object_attributes.py +12 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/of_type.py +13 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/stringified.py +4 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_mapping.py +13 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_mapping_subset.py +13 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_ordered.py +39 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_unordered.py +11 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/union.py +13 -0
- panda_pytest_assertions/assert_object/generators/_expectation_definitions/with_type.py +30 -0
- panda_pytest_assertions/assert_object/generators/_generate_expectation.py +23 -0
- panda_pytest_assertions/assert_object/generators/_generator_factories/__init__.py +8 -0
- panda_pytest_assertions/assert_object/generators/_generator_factories/built_in_generator_factory.py +39 -0
- panda_pytest_assertions/assert_object/generators/_generator_factories/generator_factory.py +66 -0
- panda_pytest_assertions/assert_object/generators/_generators/__init__.py +34 -0
- panda_pytest_assertions/assert_object/generators/_generators/equality_generator.py +35 -0
- panda_pytest_assertions/assert_object/generators/_generators/equality_key_mapping_generator.py +54 -0
- panda_pytest_assertions/assert_object/generators/_generators/exceptions.py +1 -0
- panda_pytest_assertions/assert_object/generators/_generators/generator.py +35 -0
- panda_pytest_assertions/assert_object/generators/_generators/mapping_generator.py +60 -0
- panda_pytest_assertions/assert_object/generators/_generators/mapping_subset_generator.py +57 -0
- panda_pytest_assertions/assert_object/generators/_generators/object_attributes_generator.py +53 -0
- panda_pytest_assertions/assert_object/generators/_generators/of_type_generator.py +47 -0
- panda_pytest_assertions/assert_object/generators/_generators/ordered_generator.py +55 -0
- panda_pytest_assertions/assert_object/generators/_generators/stringified_generator.py +35 -0
- panda_pytest_assertions/assert_object/generators/_generators/uniform_mapping_generator.py +50 -0
- panda_pytest_assertions/assert_object/generators/_generators/uniform_mapping_subset_generator.py +52 -0
- panda_pytest_assertions/assert_object/generators/_generators/uniform_ordered_generator.py +49 -0
- panda_pytest_assertions/assert_object/generators/_generators/union_generator.py +51 -0
- panda_pytest_assertions/assert_object/generators/_generators/unordered_generator.py +46 -0
- panda_pytest_assertions/assert_object/generators/_generators/with_type_generator.py +46 -0
- panda_pytest_assertions/py.typed +0 -0
- panda_pytest_assertions-0.1.0.dist-info/LICENSE +201 -0
- panda_pytest_assertions-0.1.0.dist-info/METADATA +32 -0
- panda_pytest_assertions-0.1.0.dist-info/RECORD +68 -0
- panda_pytest_assertions-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, overload
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class WithType:
|
|
6
|
+
"""
|
|
7
|
+
Wrapper for the expectation that additionally checks object's type.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
expectation: Any, # noqa: ANN401
|
|
13
|
+
expected_type_name: str,
|
|
14
|
+
expected_type_module: str | None,
|
|
15
|
+
/,
|
|
16
|
+
) -> None:
|
|
17
|
+
#: object expectation
|
|
18
|
+
self.expectation = expectation
|
|
19
|
+
#: expected name of the object type
|
|
20
|
+
self.expected_type_name = expected_type_name
|
|
21
|
+
#: expected module of the object type
|
|
22
|
+
self.expected_type_module = expected_type_module
|
|
23
|
+
|
|
24
|
+
def __repr__(self) -> str:
|
|
25
|
+
return (
|
|
26
|
+
f'WithType('
|
|
27
|
+
f'expectation={self.expectation!r}, '
|
|
28
|
+
f'expected_type_name={self.expected_type_name!r}, '
|
|
29
|
+
f'expected_type_module={self.expected_type_module!r}'
|
|
30
|
+
f')'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#: type alias to simplify definitions below
|
|
35
|
+
_WithTypeCreator = Callable[[Any], WithType]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@overload
|
|
39
|
+
def with_type() -> _WithTypeCreator: # noqa: D418
|
|
40
|
+
"""
|
|
41
|
+
Return the function that will accept an object and create expectations additionally asserting object type.
|
|
42
|
+
|
|
43
|
+
Returned function accepts any kind of value and will create proper expectation.
|
|
44
|
+
|
|
45
|
+
Expected type name and type module will be extracted from the object passed to the returned function.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@overload
|
|
50
|
+
def with_type(value_type: type[Any], /) -> _WithTypeCreator: # noqa: D418
|
|
51
|
+
"""
|
|
52
|
+
Return the function that will accept an object and create expectations additionally asserting object type.
|
|
53
|
+
|
|
54
|
+
Returned function accepts any kind of value and will create proper expectation.
|
|
55
|
+
|
|
56
|
+
Expected type name and type module will be extracted from the provided type type.
|
|
57
|
+
|
|
58
|
+
:param value_type: type to extract expected type name and module from
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@overload
|
|
63
|
+
def with_type( # noqa: D418
|
|
64
|
+
value_type_name: str,
|
|
65
|
+
value_type_module: str | None = None,
|
|
66
|
+
/,
|
|
67
|
+
) -> _WithTypeCreator:
|
|
68
|
+
"""
|
|
69
|
+
Return the function that will accept an object and create expectations additionally asserting object type.
|
|
70
|
+
|
|
71
|
+
Returned function accepts any kind of value and will create proper expectation.
|
|
72
|
+
|
|
73
|
+
Expected type name will be equal to `value_type_name` and expected module name will be
|
|
74
|
+
equal to `value_type_module` (which can be not set if is not supposed to be asserted).
|
|
75
|
+
|
|
76
|
+
:param value_type_name: name of the expected type
|
|
77
|
+
:param value_type_module: module of the expected type
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def with_type(
|
|
82
|
+
value_type: type[Any] | str | None = None,
|
|
83
|
+
value_type_module: str | None = None,
|
|
84
|
+
/,
|
|
85
|
+
) -> _WithTypeCreator:
|
|
86
|
+
"""
|
|
87
|
+
Return the function that will accept an object and create expectations additionally asserting object type.
|
|
88
|
+
|
|
89
|
+
Returned function accepts any kind of value and will create WithType expectation.
|
|
90
|
+
|
|
91
|
+
If `value_type` parameter is not set, expected type name and type module will be extracted from the object
|
|
92
|
+
passed to the returned function.
|
|
93
|
+
|
|
94
|
+
If `value_type` is a type, expected type name and type module will be extracted from this type.
|
|
95
|
+
|
|
96
|
+
If `value_type` is a string, expected type name will be equal to it and expected module name will be
|
|
97
|
+
equal to provided `value_type_module` (which can be not set if is not supposed to be asserted).
|
|
98
|
+
|
|
99
|
+
:param value_type: expected type or name of the type
|
|
100
|
+
:param value_type_module: expected type's module
|
|
101
|
+
:return: function that will take create proper type-aware expectation
|
|
102
|
+
"""
|
|
103
|
+
match value_type, value_type_module:
|
|
104
|
+
case None, None:
|
|
105
|
+
expected_type_name = None
|
|
106
|
+
expected_type_module = None
|
|
107
|
+
case type(), None:
|
|
108
|
+
assert isinstance(value_type, type)
|
|
109
|
+
expected_type_name = value_type.__name__
|
|
110
|
+
expected_type_module = value_type.__module__
|
|
111
|
+
case str(), str() | None:
|
|
112
|
+
assert isinstance(value_type, str)
|
|
113
|
+
expected_type_name = value_type
|
|
114
|
+
expected_type_module = value_type_module
|
|
115
|
+
case _:
|
|
116
|
+
msg = (
|
|
117
|
+
f'WithType helper constructor does not accept the following arguments: '
|
|
118
|
+
f'{(value_type, value_type_module)}'
|
|
119
|
+
)
|
|
120
|
+
raise ValueError(msg)
|
|
121
|
+
|
|
122
|
+
def _creator(expectation: Any) -> WithType: # noqa: ANN401
|
|
123
|
+
if expected_type_name is None:
|
|
124
|
+
return WithType(
|
|
125
|
+
expectation,
|
|
126
|
+
expectation.__class__.__name__,
|
|
127
|
+
expectation.__class__.__module__,
|
|
128
|
+
)
|
|
129
|
+
return WithType(
|
|
130
|
+
expectation,
|
|
131
|
+
expected_type_name,
|
|
132
|
+
expected_type_module,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return _creator
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# ruff: noqa: E402
|
|
2
|
+
# isort: off
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
pytest.register_assert_rewrite(__name__ + '.equality_asserter')
|
|
6
|
+
pytest.register_assert_rewrite(__name__ + '.mapping_asserter')
|
|
7
|
+
pytest.register_assert_rewrite(__name__ + '.mapping_subset_asserter')
|
|
8
|
+
pytest.register_assert_rewrite(__name__ + '.object_attributes_asserter')
|
|
9
|
+
pytest.register_assert_rewrite(__name__ + '.ordered_elements_asserter')
|
|
10
|
+
pytest.register_assert_rewrite(__name__ + '.stringified_asserter')
|
|
11
|
+
pytest.register_assert_rewrite(__name__ + '.unordered_elements_asserter')
|
|
12
|
+
pytest.register_assert_rewrite(__name__ + '.with_type_asserter')
|
|
13
|
+
# isort: on
|
|
14
|
+
|
|
15
|
+
from .asserter import Asserter
|
|
16
|
+
from .equality_asserter import EqualityAsserter
|
|
17
|
+
from .mapping_asserter import MappingAsserter
|
|
18
|
+
from .mapping_subset_asserter import MappingSubsetAsserter
|
|
19
|
+
from .object_attributes_asserter import ObjectAttributesAsserter
|
|
20
|
+
from .ordered_elements_asserter import OrderedElementsAsserter
|
|
21
|
+
from .stringified_asserter import StringifiedAsserter
|
|
22
|
+
from .unordered_elements_asserter import UnorderedElementsAsserter
|
|
23
|
+
from .with_type_asserter import WithTypeAsserter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'Asserter',
|
|
28
|
+
'EqualityAsserter',
|
|
29
|
+
'MappingAsserter',
|
|
30
|
+
'MappingSubsetAsserter',
|
|
31
|
+
'ObjectAttributesAsserter',
|
|
32
|
+
'OrderedElementsAsserter',
|
|
33
|
+
'StringifiedAsserter',
|
|
34
|
+
'UnorderedElementsAsserter',
|
|
35
|
+
'WithTypeAsserter',
|
|
36
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Asserter(abc.ABC):
|
|
8
|
+
"""
|
|
9
|
+
Asserter checking that the given expectation is fulfilled by the object.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
15
|
+
"""
|
|
16
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
17
|
+
|
|
18
|
+
:param expectation: expectation to be tested
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
asserter_factory: type[AsserterFactoryProtocol],
|
|
24
|
+
expectation: Any, # noqa: ANN401
|
|
25
|
+
) -> None:
|
|
26
|
+
self.asserter_factory = asserter_factory
|
|
27
|
+
self.expectation = expectation
|
|
28
|
+
|
|
29
|
+
@abc.abstractmethod
|
|
30
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
31
|
+
"""
|
|
32
|
+
Assert if the expectation is fulfilled by an object.
|
|
33
|
+
|
|
34
|
+
:param object_: an object to be tested
|
|
35
|
+
"""
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .asserter import Asserter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EqualityAsserter(Asserter):
|
|
7
|
+
"""
|
|
8
|
+
Assert that the object is equal to the expectation.
|
|
9
|
+
|
|
10
|
+
It is a default, fallback asserter that matches ANY object, so it is critical that it is
|
|
11
|
+
the last one checked.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
16
|
+
"""
|
|
17
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
18
|
+
|
|
19
|
+
This asserter can handle any object, as __eq__ is defined for all types.
|
|
20
|
+
|
|
21
|
+
:param expectation: expectation to be tested
|
|
22
|
+
:return: whether expectation can be handled by this asserter
|
|
23
|
+
"""
|
|
24
|
+
_ = expectation
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
28
|
+
"""
|
|
29
|
+
Assert if the expectation is fulfilled by an object.
|
|
30
|
+
|
|
31
|
+
:param object_: an object to be tested
|
|
32
|
+
"""
|
|
33
|
+
assert self.expectation == object_
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
5
|
+
|
|
6
|
+
from .asserter import Asserter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MappingAsserter(Asserter):
|
|
10
|
+
"""
|
|
11
|
+
Assert mapping items fulfill expectations defined in another mapping.
|
|
12
|
+
|
|
13
|
+
Expectation is a mapping between object keys and coresponding expectation. The object must
|
|
14
|
+
contain the exact same keys as the expectation, no more, no less.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
19
|
+
"""
|
|
20
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
21
|
+
|
|
22
|
+
This asserter can handle any mapping expectation.
|
|
23
|
+
|
|
24
|
+
:param expectation: expectation to be tested
|
|
25
|
+
:return: whether expectation can be handled by this asserter
|
|
26
|
+
"""
|
|
27
|
+
return isinstance(expectation, Mapping)
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
asserter_factory: type[AsserterFactoryProtocol],
|
|
32
|
+
expectation: Mapping[Any, Any],
|
|
33
|
+
) -> None:
|
|
34
|
+
super().__init__(asserter_factory, expectation)
|
|
35
|
+
self._items_asserters = {
|
|
36
|
+
key: asserter_factory.create(expectation) for key, expectation in expectation.items()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
40
|
+
"""
|
|
41
|
+
Assert if the expectation is fulfilled by an object.
|
|
42
|
+
|
|
43
|
+
:param object_: an object to be tested
|
|
44
|
+
"""
|
|
45
|
+
assert isinstance(object_, Mapping), f'Object must be a mapping. Got: {type(object_)}.'
|
|
46
|
+
assert (
|
|
47
|
+
object_.keys() == self._items_asserters.keys()
|
|
48
|
+
), 'Object keys must be equal to expectation keys.'
|
|
49
|
+
for key, asserter in self._items_asserters.items():
|
|
50
|
+
object_value = object_[key]
|
|
51
|
+
try:
|
|
52
|
+
asserter.assert_object(object_value)
|
|
53
|
+
except AssertionError as exc:
|
|
54
|
+
msg = f'Mapping value for key "{key}" is invalid. {exc!s}'
|
|
55
|
+
raise AssertionError(msg) from None
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
5
|
+
|
|
6
|
+
from .._expectation_modificators import MappingSubset # noqa: TID252
|
|
7
|
+
from .asserter import Asserter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MappingSubsetAsserter(Asserter):
|
|
11
|
+
"""
|
|
12
|
+
Assert mapping items fulfill expectations defined in another mapping.
|
|
13
|
+
|
|
14
|
+
Expectation is a mapping between object keys and coresponding expectation. The object
|
|
15
|
+
must contain all the keys defined in expectation, but may also contain more.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
20
|
+
"""
|
|
21
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
22
|
+
|
|
23
|
+
This asserter can handle a MappingSubset expectation.
|
|
24
|
+
|
|
25
|
+
:param expectation: expectation to be tested
|
|
26
|
+
:return: whether expectation can be handled by this asserter
|
|
27
|
+
"""
|
|
28
|
+
return isinstance(expectation, MappingSubset)
|
|
29
|
+
|
|
30
|
+
def __init__(self, asserter_factory: type[AsserterFactoryProtocol], expectation: MappingSubset) -> None:
|
|
31
|
+
super().__init__(asserter_factory, expectation)
|
|
32
|
+
self._items_asserters = {
|
|
33
|
+
key: asserter_factory.create(expectation)
|
|
34
|
+
for key, expectation in expectation.items_expectations.items()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
38
|
+
"""
|
|
39
|
+
Assert if the expectation is fulfilled by an object.
|
|
40
|
+
|
|
41
|
+
:param object_: an object to be tested
|
|
42
|
+
"""
|
|
43
|
+
assert isinstance(object_, Mapping), f'Object must be a mapping. Got: {type(object_)}.'
|
|
44
|
+
assert (
|
|
45
|
+
object_.keys() >= self._items_asserters.keys()
|
|
46
|
+
), 'Object keys must be a superset of expectation keys.'
|
|
47
|
+
for key, asserter in self._items_asserters.items():
|
|
48
|
+
object_value = object_[key]
|
|
49
|
+
try:
|
|
50
|
+
asserter.assert_object(object_value)
|
|
51
|
+
except AssertionError as exc:
|
|
52
|
+
msg = f'Mapping value for key "{key}" is invalid. {exc!s}'
|
|
53
|
+
raise AssertionError(msg) from None
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
4
|
+
|
|
5
|
+
from .._expectation_modificators import ObjectAttributes # noqa: TID252
|
|
6
|
+
from .asserter import Asserter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ObjectAttributesAsserter(Asserter):
|
|
10
|
+
"""
|
|
11
|
+
Assert the values of object's attributes fulfill expectation defined in the dictionary.
|
|
12
|
+
|
|
13
|
+
Expectation dictionary is a mapping between attribute name and its expectation.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
18
|
+
"""
|
|
19
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
20
|
+
|
|
21
|
+
This asserter can handle an ObjectAttributes expectation.
|
|
22
|
+
|
|
23
|
+
:param expectation: expectation to be tested
|
|
24
|
+
:return: whether expectation can be handled by this asserter
|
|
25
|
+
"""
|
|
26
|
+
return isinstance(expectation, ObjectAttributes)
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
asserter_factory: type[AsserterFactoryProtocol],
|
|
31
|
+
expectation: ObjectAttributes,
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(asserter_factory, expectation)
|
|
34
|
+
self._attributes_asserters = {
|
|
35
|
+
attr_name: asserter_factory.create(attr_expectation)
|
|
36
|
+
for attr_name, attr_expectation in expectation.attributes_expectations.items()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
40
|
+
"""
|
|
41
|
+
Assert if the expectation is fulfilled by an object.
|
|
42
|
+
|
|
43
|
+
:param object_: an object to be tested
|
|
44
|
+
"""
|
|
45
|
+
for attr_name, attr_asserter in self._attributes_asserters.items():
|
|
46
|
+
assert hasattr(object_, attr_name), f'Objects is missing attribute "{attr_name}".'
|
|
47
|
+
attr_value = getattr(object_, attr_name)
|
|
48
|
+
try:
|
|
49
|
+
attr_asserter.assert_object(attr_value)
|
|
50
|
+
except AssertionError as exc:
|
|
51
|
+
msg = f'Objects attribute "{attr_name}" has invalid value. {exc!s}'
|
|
52
|
+
raise AssertionError(msg) from None
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from collections.abc import Iterable, Sequence
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
5
|
+
|
|
6
|
+
from .asserter import Asserter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OrderedElementsAsserter(Asserter):
|
|
10
|
+
"""
|
|
11
|
+
Assert that elements of a flat collection are fulfill by provided expectations.
|
|
12
|
+
|
|
13
|
+
The order of expectations must match the order fo elements.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
18
|
+
"""
|
|
19
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
20
|
+
|
|
21
|
+
This asserter can handle a list or a tuple expectation.
|
|
22
|
+
|
|
23
|
+
:param expectation: expectation to be tested
|
|
24
|
+
:return: whether expectation can be handled by this asserter
|
|
25
|
+
"""
|
|
26
|
+
return isinstance(expectation, list | tuple)
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
asserter_factory: type[AsserterFactoryProtocol],
|
|
31
|
+
expectation: list[Any] | tuple[Any, ...],
|
|
32
|
+
) -> None:
|
|
33
|
+
super().__init__(asserter_factory, expectation)
|
|
34
|
+
self._elements_asserters = [
|
|
35
|
+
asserter_factory.create(element_expectation) for element_expectation in expectation
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
def assert_object(self, object_: Sequence[Any]) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Assert if the expectation is fulfilled by an object.
|
|
41
|
+
|
|
42
|
+
:param object_: an object to be tested
|
|
43
|
+
"""
|
|
44
|
+
assert isinstance(object_, Iterable), f'Object must be iterable. Got: {type(object_)}.'
|
|
45
|
+
elements = list(object_)
|
|
46
|
+
assert len(elements) == len(self._elements_asserters), (
|
|
47
|
+
f'Length of an object ({len(elements)}) does not equal to the number of '
|
|
48
|
+
f'expectations ({len(self._elements_asserters)}).'
|
|
49
|
+
)
|
|
50
|
+
for index, (element, asserter) in enumerate(zip(elements, self._elements_asserters, strict=True)):
|
|
51
|
+
try:
|
|
52
|
+
asserter.assert_object(element)
|
|
53
|
+
except AssertionError as exc:
|
|
54
|
+
msg = f'Element with index [{index}] has invalid value. {exc!s}'
|
|
55
|
+
raise AssertionError(msg) from None
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
4
|
+
|
|
5
|
+
from .._expectation_modificators import Stringified # noqa: TID252
|
|
6
|
+
from .asserter import Asserter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StringifiedAsserter(Asserter):
|
|
10
|
+
"""
|
|
11
|
+
Assert that the stringified object is a given value.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
16
|
+
"""
|
|
17
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
18
|
+
|
|
19
|
+
This asserter can handle a Stringified expectation.
|
|
20
|
+
|
|
21
|
+
:param expectation: expectation to be tested
|
|
22
|
+
:return: whether expectation can be handled by this asserter
|
|
23
|
+
"""
|
|
24
|
+
return isinstance(expectation, Stringified)
|
|
25
|
+
|
|
26
|
+
def __init__(self, asserter_factory: type[AsserterFactoryProtocol], expectation: Stringified) -> None:
|
|
27
|
+
super().__init__(asserter_factory, expectation)
|
|
28
|
+
self.expected_value = expectation.stringified_value
|
|
29
|
+
|
|
30
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
31
|
+
"""
|
|
32
|
+
Assert if the expectation is fulfilled by an object.
|
|
33
|
+
|
|
34
|
+
:param object_: an object to be tested
|
|
35
|
+
"""
|
|
36
|
+
assert str(object_) == self.expected_value, 'Stringified value of an object is invalid.'
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol, AsserterProtocol
|
|
5
|
+
|
|
6
|
+
from .._expectation_modificators import Unordered # noqa: TID252
|
|
7
|
+
from .asserter import Asserter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UnorderedElementsAsserter(Asserter):
|
|
11
|
+
"""
|
|
12
|
+
Assert that elements of a flat collection are fulfill by provided expectations.
|
|
13
|
+
|
|
14
|
+
The order of expectations does not matter, but all elements of an array must match at least one
|
|
15
|
+
expectation and each expectation must match at least one element.
|
|
16
|
+
|
|
17
|
+
The algorithm iterates over expectatations and if any of the element matches an expectation,
|
|
18
|
+
both expectation and element are not considered anymore.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
23
|
+
"""
|
|
24
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
25
|
+
|
|
26
|
+
This asserter can handle an UnorderedList or a set expectation.
|
|
27
|
+
|
|
28
|
+
:param expectation: expectation to be tested
|
|
29
|
+
:return: whether expectation can be handled by this asserter
|
|
30
|
+
"""
|
|
31
|
+
return isinstance(expectation, Unordered | set)
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
asserter_factory: type[AsserterFactoryProtocol],
|
|
36
|
+
expectation: Unordered | set[Any],
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(asserter_factory, expectation)
|
|
39
|
+
self._elements_asserters = [
|
|
40
|
+
asserter_factory.create(element_expectation) for element_expectation in expectation
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def assert_object(self, object_: Iterable[Any]) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Assert if the expectation is fulfilled by an object.
|
|
46
|
+
|
|
47
|
+
:param object_: an object to be tested
|
|
48
|
+
"""
|
|
49
|
+
assert isinstance(object_, Iterable), f'Object must be iterable. Got: {type(object_)}.'
|
|
50
|
+
elements_yet_not_fulfilled = list(object_)
|
|
51
|
+
assert len(elements_yet_not_fulfilled) == len(self._elements_asserters), (
|
|
52
|
+
f'Length of an object ({len(elements_yet_not_fulfilled)}) does not equal to the number of '
|
|
53
|
+
f'expectations ({len(self._elements_asserters)})'
|
|
54
|
+
)
|
|
55
|
+
for element_asserter in self._elements_asserters:
|
|
56
|
+
matching_element = self._get_first_element_asserting(element_asserter, elements_yet_not_fulfilled)
|
|
57
|
+
if matching_element is not None:
|
|
58
|
+
elements_yet_not_fulfilled.remove(matching_element)
|
|
59
|
+
continue
|
|
60
|
+
msg = (
|
|
61
|
+
f'None of the elements of a collection has fulfilled expectation '
|
|
62
|
+
f'"{element_asserter.expectation!r}"'
|
|
63
|
+
)
|
|
64
|
+
raise AssertionError(msg)
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _get_first_element_asserting(
|
|
68
|
+
element_asserter: AsserterProtocol,
|
|
69
|
+
elements: list[Any],
|
|
70
|
+
) -> Any | None: # noqa: ANN401
|
|
71
|
+
for element in elements:
|
|
72
|
+
try:
|
|
73
|
+
element_asserter.assert_object(element)
|
|
74
|
+
except Exception: # noqa: S112, BLE001
|
|
75
|
+
continue
|
|
76
|
+
return element
|
|
77
|
+
return None
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from panda_pytest_assertions.assert_object._protocols import AsserterFactoryProtocol
|
|
4
|
+
|
|
5
|
+
from .._expectation_modificators import WithType # noqa: TID252
|
|
6
|
+
from .asserter import Asserter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WithTypeAsserter(Asserter):
|
|
10
|
+
"""
|
|
11
|
+
Assert that the object matches given expectation and is of a proper type.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def matches(cls, expectation: Any) -> bool: # noqa: ANN401
|
|
16
|
+
"""
|
|
17
|
+
Decide whether provided expectation can be handled by this asserter.
|
|
18
|
+
|
|
19
|
+
This asserter can handle a WithType expectation.
|
|
20
|
+
|
|
21
|
+
:param expectation: expectation to be tested
|
|
22
|
+
:return: whether expectation can be handled by this asserter
|
|
23
|
+
"""
|
|
24
|
+
return isinstance(expectation, WithType)
|
|
25
|
+
|
|
26
|
+
def __init__(self, asserter_factory: type[AsserterFactoryProtocol], expectation: WithType) -> None:
|
|
27
|
+
super().__init__(asserter_factory, expectation)
|
|
28
|
+
self.expected_type_name = expectation.expected_type_name
|
|
29
|
+
self.expected_type_module = expectation.expected_type_module
|
|
30
|
+
self.object_asserter = asserter_factory.create(expectation.expectation)
|
|
31
|
+
|
|
32
|
+
def assert_object(self, object_: Any) -> None: # noqa: ANN401
|
|
33
|
+
"""
|
|
34
|
+
Assert if the expectation is fulfilled by an object.
|
|
35
|
+
|
|
36
|
+
:param object_: an object to be tested
|
|
37
|
+
"""
|
|
38
|
+
if object_.__class__.__name__ != self.expected_type_name:
|
|
39
|
+
msg = (
|
|
40
|
+
f'Object type ("{type(object_)}") has invalid name. '
|
|
41
|
+
f'Expected "{self.expected_type_name}", got "{object_.__class__.__name__}".'
|
|
42
|
+
)
|
|
43
|
+
raise AssertionError(msg)
|
|
44
|
+
if (
|
|
45
|
+
self.expected_type_module is not None
|
|
46
|
+
and object_.__class__.__module__ != self.expected_type_module
|
|
47
|
+
):
|
|
48
|
+
msg = (
|
|
49
|
+
f'Object type ("{type(object_)}") is from invalid module. '
|
|
50
|
+
f'Expected "{self.expected_type_module}", got "{object_.__class__.__module__}".'
|
|
51
|
+
)
|
|
52
|
+
raise AssertionError(msg)
|
|
53
|
+
try:
|
|
54
|
+
self.object_asserter.assert_object(object_)
|
|
55
|
+
except AssertionError as exc:
|
|
56
|
+
msg = f'Object is invalid. {exc!s}'
|
|
57
|
+
raise AssertionError(msg) from None
|