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.
Files changed (68) hide show
  1. panda_pytest_assertions/__init__.py +0 -0
  2. panda_pytest_assertions/assert_context/__init__.py +15 -0
  3. panda_pytest_assertions/assert_context/_assert_context.py +128 -0
  4. panda_pytest_assertions/assert_object/__init__.py +41 -0
  5. panda_pytest_assertions/assert_object/_assert_object.py +36 -0
  6. panda_pytest_assertions/assert_object/_asserter_factories/__init__.py +8 -0
  7. panda_pytest_assertions/assert_object/_asserter_factories/asserter_factory.py +66 -0
  8. panda_pytest_assertions/assert_object/_asserter_factories/built_in_asserter_factory.py +29 -0
  9. panda_pytest_assertions/assert_object/_converters/__init__.py +10 -0
  10. panda_pytest_assertions/assert_object/_converters/expectation_from_yaml.py +114 -0
  11. panda_pytest_assertions/assert_object/_converters/expectation_to_yaml.py +78 -0
  12. panda_pytest_assertions/assert_object/_expectation_modificators/__init__.py +15 -0
  13. panda_pytest_assertions/assert_object/_expectation_modificators/mapping_subset.py +15 -0
  14. panda_pytest_assertions/assert_object/_expectation_modificators/object_attibutes.py +15 -0
  15. panda_pytest_assertions/assert_object/_expectation_modificators/stringified.py +11 -0
  16. panda_pytest_assertions/assert_object/_expectation_modificators/unordered.py +18 -0
  17. panda_pytest_assertions/assert_object/_expectation_modificators/with_type.py +135 -0
  18. panda_pytest_assertions/assert_object/_objects_asserters/__init__.py +36 -0
  19. panda_pytest_assertions/assert_object/_objects_asserters/asserter.py +35 -0
  20. panda_pytest_assertions/assert_object/_objects_asserters/equality_asserter.py +33 -0
  21. panda_pytest_assertions/assert_object/_objects_asserters/mapping_asserter.py +55 -0
  22. panda_pytest_assertions/assert_object/_objects_asserters/mapping_subset_asserter.py +53 -0
  23. panda_pytest_assertions/assert_object/_objects_asserters/object_attributes_asserter.py +52 -0
  24. panda_pytest_assertions/assert_object/_objects_asserters/ordered_elements_asserter.py +55 -0
  25. panda_pytest_assertions/assert_object/_objects_asserters/stringified_asserter.py +36 -0
  26. panda_pytest_assertions/assert_object/_objects_asserters/unordered_elements_asserter.py +77 -0
  27. panda_pytest_assertions/assert_object/_objects_asserters/with_type_asserter.py +57 -0
  28. panda_pytest_assertions/assert_object/_protocols.py +93 -0
  29. panda_pytest_assertions/assert_object/generators/__init__.py +41 -0
  30. panda_pytest_assertions/assert_object/generators/_expectation_definitions/__init__.py +30 -0
  31. panda_pytest_assertions/assert_object/generators/_expectation_definitions/equality.py +6 -0
  32. panda_pytest_assertions/assert_object/generators/_expectation_definitions/mapping.py +14 -0
  33. panda_pytest_assertions/assert_object/generators/_expectation_definitions/mapping_subset.py +14 -0
  34. panda_pytest_assertions/assert_object/generators/_expectation_definitions/object_attributes.py +12 -0
  35. panda_pytest_assertions/assert_object/generators/_expectation_definitions/of_type.py +13 -0
  36. panda_pytest_assertions/assert_object/generators/_expectation_definitions/stringified.py +4 -0
  37. panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_mapping.py +13 -0
  38. panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_mapping_subset.py +13 -0
  39. panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_ordered.py +39 -0
  40. panda_pytest_assertions/assert_object/generators/_expectation_definitions/uniform_unordered.py +11 -0
  41. panda_pytest_assertions/assert_object/generators/_expectation_definitions/union.py +13 -0
  42. panda_pytest_assertions/assert_object/generators/_expectation_definitions/with_type.py +30 -0
  43. panda_pytest_assertions/assert_object/generators/_generate_expectation.py +23 -0
  44. panda_pytest_assertions/assert_object/generators/_generator_factories/__init__.py +8 -0
  45. panda_pytest_assertions/assert_object/generators/_generator_factories/built_in_generator_factory.py +39 -0
  46. panda_pytest_assertions/assert_object/generators/_generator_factories/generator_factory.py +66 -0
  47. panda_pytest_assertions/assert_object/generators/_generators/__init__.py +34 -0
  48. panda_pytest_assertions/assert_object/generators/_generators/equality_generator.py +35 -0
  49. panda_pytest_assertions/assert_object/generators/_generators/equality_key_mapping_generator.py +54 -0
  50. panda_pytest_assertions/assert_object/generators/_generators/exceptions.py +1 -0
  51. panda_pytest_assertions/assert_object/generators/_generators/generator.py +35 -0
  52. panda_pytest_assertions/assert_object/generators/_generators/mapping_generator.py +60 -0
  53. panda_pytest_assertions/assert_object/generators/_generators/mapping_subset_generator.py +57 -0
  54. panda_pytest_assertions/assert_object/generators/_generators/object_attributes_generator.py +53 -0
  55. panda_pytest_assertions/assert_object/generators/_generators/of_type_generator.py +47 -0
  56. panda_pytest_assertions/assert_object/generators/_generators/ordered_generator.py +55 -0
  57. panda_pytest_assertions/assert_object/generators/_generators/stringified_generator.py +35 -0
  58. panda_pytest_assertions/assert_object/generators/_generators/uniform_mapping_generator.py +50 -0
  59. panda_pytest_assertions/assert_object/generators/_generators/uniform_mapping_subset_generator.py +52 -0
  60. panda_pytest_assertions/assert_object/generators/_generators/uniform_ordered_generator.py +49 -0
  61. panda_pytest_assertions/assert_object/generators/_generators/union_generator.py +51 -0
  62. panda_pytest_assertions/assert_object/generators/_generators/unordered_generator.py +46 -0
  63. panda_pytest_assertions/assert_object/generators/_generators/with_type_generator.py +46 -0
  64. panda_pytest_assertions/py.typed +0 -0
  65. panda_pytest_assertions-0.1.0.dist-info/LICENSE +201 -0
  66. panda_pytest_assertions-0.1.0.dist-info/METADATA +32 -0
  67. panda_pytest_assertions-0.1.0.dist-info/RECORD +68 -0
  68. 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