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
File without changes
@@ -0,0 +1,15 @@
1
+ # ruff: noqa: E402
2
+ # isort: off
3
+ import pytest
4
+
5
+ pytest.register_assert_rewrite(__name__ + '._assert_context')
6
+ # isort: on
7
+
8
+
9
+ from ._assert_context import assert_context, AssertContext
10
+
11
+
12
+ __all__ = [
13
+ 'assert_context',
14
+ 'AssertContext',
15
+ ]
@@ -0,0 +1,128 @@
1
+ from collections.abc import Generator
2
+ from contextlib import contextmanager, ExitStack, nullcontext
3
+ from typing import Any, overload
4
+
5
+ import pytest
6
+
7
+
8
+ class _Unset:
9
+ __instance: '_Unset | None' = None
10
+
11
+ def __new__(cls) -> '_Unset':
12
+ if cls.__instance is None:
13
+ cls.__instance = object.__new__(cls)
14
+ return cls.__instance
15
+
16
+
17
+ _UNSET = _Unset()
18
+
19
+
20
+ class AssertContext:
21
+ __slots__ = ('_result',)
22
+
23
+ def __init__(self) -> None:
24
+ self._result: Any = _UNSET
25
+
26
+ def set(self, value: Any) -> None: # noqa: ANN401
27
+ self._result = value
28
+
29
+ @contextmanager
30
+ def assert_result_equal(self, expected_result: Any) -> Generator[None, None, None]: # noqa: ANN401
31
+ yield
32
+ assert expected_result == self._result
33
+
34
+
35
+ @overload
36
+ @contextmanager
37
+ def assert_context( # noqa: D418
38
+ *,
39
+ exception: type[Exception] | _Unset = ...,
40
+ result: Any | _Unset = ..., # noqa: ANN401
41
+ ) -> Generator[AssertContext, None, None]:
42
+ """
43
+ Context manager that makes sure `with` block behaved according to the arguments.
44
+
45
+ If the `exception` parameter is set, the `with` block is expected to raise an exception of provided type.
46
+ If it is NOT set, the `with` block is expected NOT TO raise any exceptions.
47
+
48
+ If the `result` parameter is set, its value is expected to be equal to `with` block result. The `with`
49
+ block result is set with the `set` method of an object yielded by the context manager. The comparison
50
+ is used with `==` operator, where `result` argument is on the left side.
51
+ If the `result` parameter is NOT set, the `set` method of yielded object is expected
52
+ not to be called at all.
53
+
54
+ :param exception: type of exception, expected to be raised
55
+ :param result: value expected to be set with `set` method of yielded object
56
+ """
57
+
58
+
59
+ @overload
60
+ @contextmanager
61
+ def assert_context( # noqa: D418
62
+ *,
63
+ behaviour: type[Exception] | Any | _Unset = ..., # noqa: ANN401
64
+ ) -> Generator[AssertContext, None, None]:
65
+ """
66
+ Context manager that makes sure `with` block behaved according to the arguments.
67
+
68
+ Context manager yields a single object used that provides `set` method used to set the `with`
69
+ block result that is to be asserted.
70
+
71
+ If the `behaviour` parameter is set to an exception type, the `with` block is expected
72
+ to raise an exception of provided type and the `set` method of yielded object is expected
73
+ not to be called at all.
74
+
75
+ If the `behaviour` parameter is set to anything else, its value is expected to be equal to `with`
76
+ block result. The comparison is used with `==` operator, where `result` argument is on the left side and
77
+ the `with` block is expected to not raise any exceptions.
78
+
79
+ If the `behaviour` parameter is NOT set, the `with` block is expected NOT TO raise any exceptions and
80
+ the `set` method of yielded object is expected not to be called at all.
81
+
82
+ :param behaviour: describes expected behaviour of the `with` block
83
+ """
84
+
85
+
86
+ @contextmanager
87
+ def assert_context(
88
+ *,
89
+ exception: type[Exception] | _Unset = _UNSET,
90
+ result: Any | _Unset = _UNSET,
91
+ behaviour: type[Exception] | Any | _Unset = _UNSET,
92
+ ) -> Generator[AssertContext, None, None]:
93
+ """
94
+ Context manager that makes sure `with` block behaved according to the arguments.
95
+
96
+ Pair of parameters `exception` and `result` are exclusive with parameter `behaviour`. If `behaviour`
97
+ is set to an exception type, it is the same as if `exception` would be set to this value and `result`
98
+ would not be set. If `behaviour` is set to any other value, its treated as the value of `result` and the
99
+ `exception` remains unset.
100
+
101
+ If the `exception` parameter is set, the `with` block is expected to raise an exception of provided type.
102
+ If it is NOT set, the `with` block is expected NOT TO raise any exceptions.
103
+
104
+ If the `result` parameter is set, its value is expected to be equal to `with` block result. The `with`
105
+ block result is set with the `set` method of an object yielded by the context manager. The comparison
106
+ is used with `==` operator, where `result` argument is on the left side.
107
+ If the `result` parameter is NOT set, the `set` method of yielded object is expected
108
+ not to be called at all.
109
+
110
+ :param exception: type of exception, expected to be raised
111
+ :param result: value expected to be set with `set` method of yielded object
112
+ :param behaviour: a common name for `exception` and `result` parameters, exclusive to them
113
+ :yield: object used to set the result of a `with` block
114
+ """
115
+ if behaviour != _UNSET:
116
+ assert exception == _UNSET
117
+ assert result == _UNSET
118
+ if isinstance(behaviour, type) and issubclass(behaviour, Exception):
119
+ exception = behaviour
120
+ else:
121
+ result = behaviour
122
+ context = AssertContext()
123
+ exception_behaviour = nullcontext() if isinstance(exception, _Unset) else pytest.raises(exception)
124
+
125
+ with ExitStack() as behaviours_stack:
126
+ behaviours_stack.enter_context(context.assert_result_equal(result))
127
+ behaviours_stack.enter_context(exception_behaviour)
128
+ yield context
@@ -0,0 +1,41 @@
1
+ from ._assert_object import assert_object, Expectation
2
+ from ._asserter_factories import AsserterFactory, BuiltInAsserterFactory
3
+ from ._converters import (
4
+ expectation_from_yaml,
5
+ expectation_to_yaml,
6
+ ExpectationYamlDumper,
7
+ ExpectationYamlLoader,
8
+ )
9
+ from ._expectation_modificators import (
10
+ MappingSubset,
11
+ ObjectAttributes,
12
+ Stringified,
13
+ Unordered,
14
+ with_type,
15
+ WithType,
16
+ )
17
+ from ._objects_asserters import Asserter
18
+ from ._protocols import AsserterFactoryProtocol, AsserterProtocol, GeneratorFactoryProtocol, GeneratorProtocol
19
+
20
+
21
+ __all__ = [
22
+ 'assert_object',
23
+ 'Expectation',
24
+ 'AsserterFactory',
25
+ 'BuiltInAsserterFactory',
26
+ 'expectation_to_yaml',
27
+ 'expectation_from_yaml',
28
+ 'ExpectationYamlDumper',
29
+ 'ExpectationYamlLoader',
30
+ 'MappingSubset',
31
+ 'ObjectAttributes',
32
+ 'Stringified',
33
+ 'Unordered',
34
+ 'with_type',
35
+ 'WithType',
36
+ 'Asserter',
37
+ 'AsserterProtocol',
38
+ 'AsserterFactoryProtocol',
39
+ 'GeneratorFactoryProtocol',
40
+ 'GeneratorProtocol',
41
+ ]
@@ -0,0 +1,36 @@
1
+ from typing import Any
2
+
3
+ from ._asserter_factories import BuiltInAsserterFactory
4
+ from ._protocols import AsserterFactoryProtocol
5
+
6
+
7
+ def assert_object(
8
+ expectation: Any, # noqa: ANN401
9
+ object_: Any, # noqa: ANN401
10
+ *,
11
+ asserter_factory: type[AsserterFactoryProtocol] = BuiltInAsserterFactory,
12
+ ) -> None:
13
+ asserter = asserter_factory.create(expectation)
14
+ try:
15
+ asserter.assert_object(object_)
16
+ except AssertionError as exc:
17
+ msg = f'Object assertion failed. {exc!s}'
18
+ raise AssertionError(msg) from None
19
+
20
+
21
+ class Expectation:
22
+ def __init__(
23
+ self,
24
+ expectation: Any, # noqa: ANN401
25
+ *,
26
+ asserter_factory: type[AsserterFactoryProtocol] = BuiltInAsserterFactory,
27
+ ) -> None:
28
+ self.expectation = expectation
29
+ self.asserter_factory = asserter_factory
30
+
31
+ def __eq__(self, value: object) -> bool:
32
+ try:
33
+ assert_object(self.expectation, value, asserter_factory=self.asserter_factory)
34
+ except AssertionError:
35
+ return False
36
+ return True
@@ -0,0 +1,8 @@
1
+ from .asserter_factory import AsserterFactory
2
+ from .built_in_asserter_factory import BuiltInAsserterFactory
3
+
4
+
5
+ __all__ = [
6
+ 'AsserterFactory',
7
+ 'BuiltInAsserterFactory',
8
+ ]
@@ -0,0 +1,66 @@
1
+ from collections import deque
2
+ from typing import Any, ClassVar
3
+
4
+ from panda_pytest_assertions.assert_object._protocols import AsserterProtocol
5
+
6
+
7
+ class AsserterFactory:
8
+ """
9
+ Factory for registered asserters.
10
+ """
11
+
12
+ #: list of registered asserter classes
13
+ _ASSERTER_TYPES: ClassVar[deque[type[AsserterProtocol]]] = deque()
14
+
15
+ @classmethod
16
+ def create(cls, expectation: Any) -> AsserterProtocol: # noqa: ANN401
17
+ """
18
+ Create asserter object for given expectation.
19
+
20
+ The function goes through all registered asserter types and creates the first one that matches.
21
+
22
+ :param expectation: expectation to create asserter for
23
+ :return: created asserter
24
+ """
25
+ for asserter_type in cls._ASSERTER_TYPES:
26
+ if asserter_type.matches(expectation):
27
+ return asserter_type(cls, expectation)
28
+ msg = (
29
+ f'None of the registered asserters matches expectation "{expectation}" '
30
+ f'of type "{type(expectation)}".'
31
+ )
32
+ raise ValueError(msg)
33
+
34
+ @classmethod
35
+ def register_asserter(
36
+ cls,
37
+ asserter: type[AsserterProtocol],
38
+ *,
39
+ after: type[AsserterProtocol] | None = None,
40
+ before: type[AsserterProtocol] | None = None,
41
+ ) -> None:
42
+ """
43
+ Register the provided asserter to be used.
44
+
45
+ The asserter is registered at the earliest possible position. If `after` and `before`
46
+ parameters are not set, it is registered at the beginning of the list to be matched
47
+ when creating asserter for expectations.
48
+
49
+ :param asserter: asserter to be registered
50
+ :param after: registered asserter must be matched after this one
51
+ :param before: registered asserter must be matched before this one
52
+ """
53
+ if '_ASSERTER_TYPES' not in cls.__dict__:
54
+ cls._ASSERTER_TYPES = cls._ASSERTER_TYPES.copy()
55
+
56
+ after_position = cls._ASSERTER_TYPES.index(after) if after is not None else -1
57
+ before_position = (
58
+ cls._ASSERTER_TYPES.index(before) if before is not None else len(cls._ASSERTER_TYPES) + 1
59
+ )
60
+ if after_position >= before_position:
61
+ msg = (
62
+ f'Cannot register asserter between "{after}" and "{before}" as their '
63
+ f'respective positions are: "{after_position}" and "{before_position}"'
64
+ )
65
+ raise ValueError(msg)
66
+ cls._ASSERTER_TYPES.insert(after_position + 1, asserter)
@@ -0,0 +1,29 @@
1
+ from panda_pytest_assertions.assert_object._objects_asserters import (
2
+ EqualityAsserter,
3
+ MappingAsserter,
4
+ MappingSubsetAsserter,
5
+ ObjectAttributesAsserter,
6
+ OrderedElementsAsserter,
7
+ StringifiedAsserter,
8
+ UnorderedElementsAsserter,
9
+ WithTypeAsserter,
10
+ )
11
+
12
+ from .asserter_factory import AsserterFactory
13
+
14
+
15
+ class BuiltInAsserterFactory(AsserterFactory):
16
+ """
17
+ Factory that has all built-in asserters registered.
18
+ """
19
+
20
+
21
+ # MUST be the first one as it implements a fallback mechanism and matches everything
22
+ BuiltInAsserterFactory.register_asserter(EqualityAsserter)
23
+ BuiltInAsserterFactory.register_asserter(MappingAsserter)
24
+ BuiltInAsserterFactory.register_asserter(MappingSubsetAsserter)
25
+ BuiltInAsserterFactory.register_asserter(ObjectAttributesAsserter)
26
+ BuiltInAsserterFactory.register_asserter(OrderedElementsAsserter)
27
+ BuiltInAsserterFactory.register_asserter(StringifiedAsserter)
28
+ BuiltInAsserterFactory.register_asserter(UnorderedElementsAsserter)
29
+ BuiltInAsserterFactory.register_asserter(WithTypeAsserter)
@@ -0,0 +1,10 @@
1
+ from .expectation_from_yaml import expectation_from_yaml, ExpectationYamlLoader
2
+ from .expectation_to_yaml import expectation_to_yaml, ExpectationYamlDumper
3
+
4
+
5
+ __all__ = [
6
+ 'expectation_from_yaml',
7
+ 'ExpectationYamlLoader',
8
+ 'expectation_to_yaml',
9
+ 'ExpectationYamlDumper',
10
+ ]
@@ -0,0 +1,114 @@
1
+ from collections.abc import Callable
2
+ from typing import Any, cast, TypeVar
3
+
4
+ import yaml
5
+ from yaml.constructor import ConstructorError
6
+
7
+ from panda_pytest_assertions.assert_object._expectation_modificators import (
8
+ MappingSubset,
9
+ ObjectAttributes,
10
+ Stringified,
11
+ Unordered,
12
+ WithType,
13
+ )
14
+
15
+
16
+ def expectation_from_yaml(yaml_string: str) -> Any: # noqa: ANN401
17
+ """
18
+ Serialize expectation to YAML format.
19
+
20
+ :param yaml_string: YAML string to create expectation from
21
+ :return: YAML string representing expectation
22
+ """
23
+ return yaml.load(yaml_string, Loader=ExpectationYamlLoader) # noqa: S506
24
+
25
+
26
+ _T = TypeVar('_T')
27
+ _Constructor = Callable[['ExpectationYamlLoader', yaml.Node], _T]
28
+
29
+
30
+ class ExpectationYamlLoader(yaml.SafeLoader):
31
+ @classmethod
32
+ def add_construct(cls, tag: str) -> Callable[[_Constructor[_T]], _Constructor[_T]]:
33
+ def _wrapper(constructor: _Constructor[_T]) -> _Constructor[_T]:
34
+ ExpectationYamlLoader.add_constructor(tag, constructor)
35
+ return constructor
36
+
37
+ return _wrapper
38
+
39
+
40
+ @ExpectationYamlLoader.add_construct('!MappingSubset')
41
+ def _(loader: ExpectationYamlLoader, node: yaml.Node) -> MappingSubset:
42
+ if not isinstance(node, yaml.MappingNode):
43
+ raise ConstructorError(
44
+ problem=f'expected a mapping node, but found {type(node)}',
45
+ problem_mark=node.start_mark,
46
+ )
47
+ return MappingSubset(loader.construct_mapping(node))
48
+
49
+
50
+ @ExpectationYamlLoader.add_construct('!ObjectAttributes')
51
+ def _(loader: ExpectationYamlLoader, node: yaml.Node) -> ObjectAttributes:
52
+ if not isinstance(node, yaml.MappingNode):
53
+ raise ConstructorError(
54
+ problem=f'expected a mapping node, but found {type(node)}',
55
+ problem_mark=node.start_mark,
56
+ )
57
+ node_data = loader.construct_mapping(node)
58
+ if any(not isinstance(key, str) for key in node_data):
59
+ raise ConstructorError(
60
+ problem='all keys in ObjectAttributes must be strings',
61
+ problem_mark=node.start_mark,
62
+ )
63
+ return ObjectAttributes(cast(dict[str, Any], node_data))
64
+
65
+
66
+ @ExpectationYamlLoader.add_construct('!Stringified')
67
+ def _(loader: ExpectationYamlLoader, node: yaml.Node) -> Stringified:
68
+ if not isinstance(node, yaml.ScalarNode):
69
+ raise ConstructorError(
70
+ problem=f'expected a scalar node, but found {type(node)}',
71
+ problem_mark=node.start_mark,
72
+ )
73
+ # I was not able to produce yaml that would return something else than a string
74
+ node_data = cast(str, loader.construct_scalar(node))
75
+ return Stringified(node_data)
76
+
77
+
78
+ @ExpectationYamlLoader.add_construct('!Unordered')
79
+ def _(loader: ExpectationYamlLoader, node: yaml.Node) -> Unordered:
80
+ if not isinstance(node, yaml.SequenceNode):
81
+ raise ConstructorError(
82
+ problem=f'expected a sequence node, but found {type(node)}',
83
+ problem_mark=node.start_mark,
84
+ )
85
+ return Unordered(loader.construct_sequence(node))
86
+
87
+
88
+ _WITH_TYPE_KEYS = {'expectation', 'expected_type_name', 'expected_type_module'}
89
+
90
+
91
+ @ExpectationYamlLoader.add_construct('!WithType')
92
+ def _(loader: ExpectationYamlLoader, node: yaml.Node) -> WithType:
93
+ if not isinstance(node, yaml.MappingNode):
94
+ raise ConstructorError(
95
+ problem=f'expected a mapping node, but found {type(node)}',
96
+ problem_mark=node.start_mark,
97
+ )
98
+ node_data = loader.construct_mapping(node)
99
+ if node_data.keys() != _WITH_TYPE_KEYS:
100
+ raise ConstructorError(
101
+ problem=f'WithType keys must be {_WITH_TYPE_KEYS}',
102
+ problem_mark=node.start_mark,
103
+ )
104
+ if not isinstance(expected_type_name := node_data['expected_type_name'], str):
105
+ raise ConstructorError(
106
+ problem='value of WithType "expected_type_name" must be a string',
107
+ problem_mark=node.start_mark,
108
+ )
109
+ if not isinstance(expected_type_module := node_data['expected_type_module'], str | None):
110
+ raise ConstructorError(
111
+ problem='value of WithType "expected_type_module" must be a string or None',
112
+ problem_mark=node.start_mark,
113
+ )
114
+ return WithType(node_data['expectation'], expected_type_name, expected_type_module)
@@ -0,0 +1,78 @@
1
+ from collections.abc import Callable
2
+ from enum import Enum
3
+ from typing import Any, TypeVar
4
+
5
+ import yaml
6
+
7
+ from panda_pytest_assertions.assert_object._expectation_modificators import (
8
+ MappingSubset,
9
+ ObjectAttributes,
10
+ Stringified,
11
+ Unordered,
12
+ with_type,
13
+ WithType,
14
+ )
15
+
16
+
17
+ def expectation_to_yaml(expectation: Any) -> str: # noqa: ANN401
18
+ """
19
+ Serialize expectation to YAML format.
20
+
21
+ :param expectation: expectation to be dumped to YAML
22
+ :return: YAML string representing expectation
23
+ """
24
+ return yaml.dump(expectation, Dumper=ExpectationYamlDumper, width=10000)
25
+
26
+
27
+ _T = TypeVar('_T')
28
+
29
+ _Representer = Callable[['ExpectationYamlDumper', _T], yaml.Node]
30
+
31
+
32
+ class ExpectationYamlDumper(yaml.SafeDumper):
33
+ @classmethod
34
+ def add_repr(
35
+ cls,
36
+ data_type: type[_T],
37
+ *,
38
+ is_multi: bool = False,
39
+ ) -> Callable[[_Representer[_T]], _Representer[_T]]:
40
+ def _wrapper(representer: _Representer[_T]) -> _Representer[_T]:
41
+ if is_multi:
42
+ ExpectationYamlDumper.add_multi_representer(data_type, representer)
43
+ else:
44
+ ExpectationYamlDumper.add_representer(data_type, representer)
45
+ return representer
46
+
47
+ return _wrapper
48
+
49
+
50
+ @ExpectationYamlDumper.add_repr(MappingSubset)
51
+ def _(dumper: ExpectationYamlDumper, data: MappingSubset) -> yaml.MappingNode:
52
+ return dumper.represent_mapping('!MappingSubset', data.items_expectations)
53
+
54
+
55
+ @ExpectationYamlDumper.add_repr(ObjectAttributes)
56
+ def _(dumper: ExpectationYamlDumper, data: ObjectAttributes) -> yaml.MappingNode:
57
+ return dumper.represent_mapping('!ObjectAttributes', data.attributes_expectations)
58
+
59
+
60
+ @ExpectationYamlDumper.add_repr(Stringified)
61
+ def _(dumper: ExpectationYamlDumper, data: Stringified) -> yaml.ScalarNode:
62
+ return dumper.represent_scalar('!Stringified', data.stringified_value)
63
+
64
+
65
+ @ExpectationYamlDumper.add_repr(Unordered)
66
+ def _(dumper: ExpectationYamlDumper, data: Unordered) -> yaml.SequenceNode:
67
+ return dumper.represent_sequence('!Unordered', data.elements_expectations)
68
+
69
+
70
+ @ExpectationYamlDumper.add_repr(WithType)
71
+ def _with_type_representer(dumper: ExpectationYamlDumper, data: WithType) -> yaml.MappingNode:
72
+ return dumper.represent_mapping('!WithType', data.__dict__)
73
+
74
+
75
+ @ExpectationYamlDumper.add_repr(Enum, is_multi=True)
76
+ def _(dumper: ExpectationYamlDumper, data: Enum) -> yaml.Node:
77
+ expectation = with_type(type(data))(Stringified(str(data)))
78
+ return _with_type_representer(dumper, expectation)
@@ -0,0 +1,15 @@
1
+ from .mapping_subset import MappingSubset
2
+ from .object_attibutes import ObjectAttributes
3
+ from .stringified import Stringified
4
+ from .unordered import Unordered
5
+ from .with_type import with_type, WithType
6
+
7
+
8
+ __all__ = [
9
+ 'MappingSubset',
10
+ 'ObjectAttributes',
11
+ 'Stringified',
12
+ 'Unordered',
13
+ 'WithType',
14
+ 'with_type',
15
+ ]
@@ -0,0 +1,15 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any
3
+
4
+
5
+ class MappingSubset:
6
+ """
7
+ Wrapper for the expectation that describes a subset of a mapping object.
8
+ """
9
+
10
+ def __init__(self, items_expectations: Mapping[Any, Any], /) -> None:
11
+ #: mapping of items expectations
12
+ self.items_expectations = items_expectations
13
+
14
+ def __repr__(self) -> str:
15
+ return f'MappingSubset({self.items_expectations!r})'
@@ -0,0 +1,15 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any
3
+
4
+
5
+ class ObjectAttributes:
6
+ """
7
+ Wrapper for the expectation that describes a object attributes.
8
+ """
9
+
10
+ def __init__(self, attributes_expectations: Mapping[str, Any], /) -> None:
11
+ #: mapping of attributes expectations
12
+ self.attributes_expectations = attributes_expectations
13
+
14
+ def __repr__(self) -> str:
15
+ return f'ObjectAttributes({self.attributes_expectations!r})'
@@ -0,0 +1,11 @@
1
+ class Stringified:
2
+ """
3
+ Wrapper for the expectation that describes stringified object.
4
+ """
5
+
6
+ def __init__(self, stringified_value: str, /) -> None:
7
+ #: stringified value expectation
8
+ self.stringified_value = stringified_value
9
+
10
+ def __repr__(self) -> str:
11
+ return f'Stringified({self.stringified_value!r})'
@@ -0,0 +1,18 @@
1
+ from collections.abc import Iterator
2
+ from typing import Any
3
+
4
+
5
+ class Unordered:
6
+ """
7
+ Wrapper for the expectation that describes unordered list.
8
+ """
9
+
10
+ def __init__(self, elements_expectations: list[Any], /) -> None:
11
+ #: list of elements expectations
12
+ self.elements_expectations = elements_expectations
13
+
14
+ def __iter__(self) -> Iterator[Any]:
15
+ return iter(self.elements_expectations)
16
+
17
+ def __repr__(self) -> str:
18
+ return f'Unordered({self.elements_expectations!r})'