assertpy2 2.0.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.
assertpy2/dict.py ADDED
@@ -0,0 +1,255 @@
1
+ # Copyright (c) 2015-2019, Activision Publishing, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import TYPE_CHECKING
32
+
33
+ if TYPE_CHECKING:
34
+ from typing_extensions import Self
35
+
36
+ __tracebackhide__ = True
37
+
38
+
39
+ class DictMixin:
40
+ """Dict assertions mixin."""
41
+
42
+ def contains_key(self, *keys) -> Self:
43
+ """Asserts the val is a dict and contains the given key or keys. Alias for :meth:`~assertpy.contains.ContainsMixin.contains`.
44
+
45
+ Checks if the dict contains the given key or keys using ``in`` operator.
46
+
47
+ Args:
48
+ *keys: the key or keys expected to be contained
49
+
50
+ Examples:
51
+ Usage::
52
+
53
+ assert_that({'a': 1, 'b': 2}).contains_key('a')
54
+ assert_that({'a': 1, 'b': 2}).contains_key('a', 'b')
55
+
56
+ Returns:
57
+ AssertionBuilder: returns this instance to chain to the next assertion
58
+
59
+ Raises:
60
+ AssertionError: if val does **not** contain the key or keys
61
+ """
62
+ self._check_dict_like(self.val, check_values=False, check_getitem=False)
63
+ return self.contains(*keys)
64
+
65
+ def does_not_contain_key(self, *keys) -> Self:
66
+ """Asserts the val is a dict and does not contain the given key or keys. Alias for :meth:`~assertpy.contains.ContainsMixin.does_not_contain`.
67
+
68
+ Checks if the dict excludes the given key or keys using ``in`` operator.
69
+
70
+ Args:
71
+ *keys: the key or keys expected to be excluded
72
+
73
+ Examples:
74
+ Usage::
75
+
76
+ assert_that({'a': 1, 'b': 2}).does_not_contain_key('x')
77
+ assert_that({'a': 1, 'b': 2}).does_not_contain_key('x', 'y')
78
+
79
+ Returns:
80
+ AssertionBuilder: returns this instance to chain to the next assertion
81
+
82
+ Raises:
83
+ AssertionError: if val **does** contain the key or keys
84
+ """
85
+ self._check_dict_like(self.val, check_values=False, check_getitem=False)
86
+ return self.does_not_contain(*keys)
87
+
88
+ def contains_value(self, *values) -> Self:
89
+ """Asserts that val is a dict and contains the given value or values.
90
+
91
+ Checks if the dict contains the given value or values in *any* key.
92
+
93
+ Args:
94
+ *values: the value or values expected to be contained
95
+
96
+ Examples:
97
+ Usage::
98
+
99
+ assert_that({'a': 1, 'b': 2}).contains_value(1)
100
+ assert_that({'a': 1, 'b': 2}).contains_value(1, 2)
101
+
102
+ Returns:
103
+ AssertionBuilder: returns this instance to chain to the next assertion
104
+
105
+ Raises:
106
+ AssertionError: if val does **not** contain the value or values
107
+ """
108
+ self._check_dict_like(self.val, check_getitem=False)
109
+ if len(values) == 0:
110
+ raise ValueError("one or more value args must be given")
111
+ missing = []
112
+ for v in values:
113
+ if v not in self.val.values():
114
+ missing.append(v)
115
+ if missing:
116
+ return self.error(
117
+ "Expected <%s> to contain values %s, but did not contain %s."
118
+ % (self.val, self._fmt_items(values), self._fmt_items(missing))
119
+ )
120
+ return self
121
+
122
+ def does_not_contain_value(self, *values) -> Self:
123
+ """Asserts that val is a dict and does not contain the given value or values.
124
+
125
+ Checks if the dict excludes the given value or values across *all* keys.
126
+
127
+ Args:
128
+ *values: the value or values expected to be excluded
129
+
130
+ Examples:
131
+ Usage::
132
+
133
+ assert_that({'a': 1, 'b': 2}).does_not_contain_value(3)
134
+ assert_that({'a': 1, 'b': 2}).does_not_contain_value(3, 4)
135
+
136
+ Returns:
137
+ AssertionBuilder: returns this instance to chain to the next assertion
138
+
139
+ Raises:
140
+ AssertionError: if val **does** contain the value or values
141
+ """
142
+ self._check_dict_like(self.val, check_getitem=False)
143
+ if len(values) == 0:
144
+ raise ValueError("one or more value args must be given")
145
+ else:
146
+ found = []
147
+ for v in values:
148
+ if v in self.val.values():
149
+ found.append(v)
150
+ if found:
151
+ return self.error(
152
+ "Expected <%s> to not contain values %s, but did contain %s."
153
+ % (self.val, self._fmt_items(values), self._fmt_items(found))
154
+ )
155
+ return self
156
+
157
+ def contains_entry(self, *args, **kwargs) -> Self:
158
+ """Asserts that val is a dict and contains the given entry or entries.
159
+
160
+ Checks if the dict contains the given key-value pair or pairs.
161
+
162
+ Args:
163
+ *args: the entry or entries expected to be contained (as ``{k: v}`` args)
164
+ **kwargs: the entry or entries expected to be contained (as ``k=v`` kwargs)
165
+
166
+ Examples:
167
+ Usage::
168
+
169
+ # using args
170
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry({'a': 1})
171
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry({'a': 1}, {'b': 2})
172
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry({'a': 1}, {'b': 2}, {'c': 3})
173
+
174
+ # using kwargs
175
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry(a=1)
176
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry(a=1, b=2)
177
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry(a=1, b=2, c=3)
178
+
179
+ # or args and kwargs
180
+ assert_that({'a': 1, 'b': 2, 'c': 3}).contains_entry({'c': 3}, a=1, b=2)
181
+
182
+
183
+ Returns:
184
+ AssertionBuilder: returns this instance to chain to the next assertion
185
+
186
+ Raises:
187
+ AssertionError: if val does **not** contain the entry or entries
188
+ """
189
+ self._check_dict_like(self.val, check_values=False)
190
+ entries = list(args) + [{k: v} for k, v in kwargs.items()]
191
+ if len(entries) == 0:
192
+ raise ValueError("one or more entry args must be given")
193
+ missing = []
194
+ for e in entries:
195
+ if type(e) is not dict:
196
+ raise TypeError("given entry arg must be a dict")
197
+ if len(e) != 1:
198
+ raise ValueError("given entry args must contain exactly one key-value pair")
199
+ k = next(iter(e))
200
+ if k not in self.val:
201
+ missing.append(e) # bad key
202
+ elif self.val[k] != e[k]:
203
+ missing.append(e) # bad val
204
+ if missing:
205
+ return self.error(
206
+ "Expected <%s> to contain entries %s, but did not contain %s."
207
+ % (self.val, self._fmt_items(entries), self._fmt_items(missing))
208
+ )
209
+ return self
210
+
211
+ def does_not_contain_entry(self, *args, **kwargs) -> Self:
212
+ """Asserts that val is a dict and does not contain the given entry or entries.
213
+
214
+ Checks if the dict excludes the given key-value pair or pairs.
215
+
216
+ Args:
217
+ *args: the entry or entries expected to be excluded (as ``{k: v}`` args)
218
+ **kwargs: the entry or entries expected to be excluded (as ``k=v`` kwargs)
219
+
220
+ Examples:
221
+ Usage::
222
+
223
+ # using args
224
+ assert_that({'a': 1, 'b': 2, 'c': 3}).does_not_contain_entry({'a': 2})
225
+ assert_that({'a': 1, 'b': 2, 'c': 3}).does_not_contain_entry({'a': 2}, {'x': 4})
226
+
227
+ # using kwargs
228
+ assert_that({'a': 1, 'b': 2, 'c': 3}).does_not_contain_entry(a=2)
229
+ assert_that({'a': 1, 'b': 2, 'c': 3}).does_not_contain_entry(a=2, x=4)
230
+
231
+ Returns:
232
+ AssertionBuilder: returns this instance to chain to the next assertion
233
+
234
+ Raises:
235
+ AssertionError: if val **does** contain the entry or entries
236
+ """
237
+ self._check_dict_like(self.val, check_values=False)
238
+ entries = list(args) + [{k: v} for k, v in kwargs.items()]
239
+ if len(entries) == 0:
240
+ raise ValueError("one or more entry args must be given")
241
+ found = []
242
+ for e in entries:
243
+ if type(e) is not dict:
244
+ raise TypeError("given entry arg must be a dict")
245
+ if len(e) != 1:
246
+ raise ValueError("given entry args must contain exactly one key-value pair")
247
+ k = next(iter(e))
248
+ if k in self.val and e[k] == self.val[k]:
249
+ found.append(e)
250
+ if found:
251
+ return self.error(
252
+ "Expected <%s> to not contain entries %s, but did contain %s."
253
+ % (self.val, self._fmt_items(entries), self._fmt_items(found))
254
+ )
255
+ return self
assertpy2/dynamic.py ADDED
@@ -0,0 +1,113 @@
1
+ # Copyright (c) 2015-2019, Activision Publishing, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ from __future__ import annotations
30
+
31
+ import collections
32
+
33
+ __tracebackhide__ = True
34
+
35
+
36
+ class DynamicMixin:
37
+ """Dynamic assertions mixin.
38
+
39
+ When testing attributes of an object (or the contents of a dict), the
40
+ :meth:`~assertpy.base.BaseMixin.is_equal_to` assertion can be a bit verbose::
41
+
42
+ fred = Person('Fred', 'Smith')
43
+
44
+ assert_that(fred.first_name).is_equal_to('Fred')
45
+ assert_that(fred.name).is_equal_to('Fred Smith')
46
+ assert_that(fred.say_hello()).is_equal_to('Hello, Fred!')
47
+
48
+ Instead, use dynamic assertions in the form of ``has_<name>()`` where ``<name>`` is the name of
49
+ any attribute, property, or zero-argument method on the given object. Dynamic equality
50
+ assertions test if actual is equal to expected using the ``==`` operator. Using dynamic
51
+ assertions, we can rewrite the above example as::
52
+
53
+ assert_that(fred).has_first_name('Fred')
54
+ assert_that(fred).has_name('Fred Smith')
55
+ assert_that(fred).has_say_hello('Hello, Fred!')
56
+
57
+ Similarly, dynamic assertions also work on any *dict-like* object::
58
+
59
+ fred = {
60
+ 'first_name': 'Fred',
61
+ 'last_name': 'Smith',
62
+ 'shoe_size': 12
63
+ }
64
+
65
+ assert_that(fred).has_first_name('Fred')
66
+ assert_that(fred).has_last_name('Smith')
67
+ assert_that(fred).has_shoe_size(12)
68
+ """
69
+
70
+ def __getattr__(self, attr):
71
+ """Asserts that val has attribute attr and that its value is equal to other via a dynamic
72
+ assertion of the form ``has_<attr>()``."""
73
+ if not attr.startswith("has_"):
74
+ raise AttributeError("assertpy has no assertion <%s()>" % attr)
75
+
76
+ attr_name = attr[4:]
77
+ err_msg = False
78
+ is_namedtuple = isinstance(self.val, tuple) and hasattr(self.val, "_fields")
79
+ is_dict = isinstance(self.val, collections.abc.Iterable) and hasattr(self.val, "__getitem__")
80
+
81
+ if not hasattr(self.val, attr_name):
82
+ if is_dict and not is_namedtuple:
83
+ if attr_name not in self.val:
84
+ err_msg = "Expected key <%s>, but val has no key <%s>." % (attr_name, attr_name)
85
+ else:
86
+ err_msg = "Expected attribute <%s>, but val has no attribute <%s>." % (attr_name, attr_name)
87
+
88
+ def _wrapper(*args, **kwargs):
89
+ if err_msg:
90
+ return self.error(err_msg) # ok to raise AssertionError now that we are inside wrapper
91
+ else:
92
+ if len(args) != 1:
93
+ raise TypeError("assertion <%s()> takes exactly 1 argument (%d given)" % (attr, len(args)))
94
+
95
+ val_attr = self.val[attr_name] if is_dict and not is_namedtuple else getattr(self.val, attr_name)
96
+
97
+ if callable(val_attr):
98
+ try:
99
+ actual = val_attr()
100
+ except TypeError:
101
+ raise TypeError("val does not have zero-arg method <%s()>" % attr_name) from None
102
+ else:
103
+ actual = val_attr
104
+
105
+ expected = args[0]
106
+ if actual != expected:
107
+ return self.error(
108
+ "Expected <%s> to be equal to <%s> on %s <%s>, but was not."
109
+ % (actual, expected, "key" if is_dict else "attribute", attr_name)
110
+ )
111
+ return self
112
+
113
+ return _wrapper
assertpy2/exception.py ADDED
@@ -0,0 +1,128 @@
1
+ # Copyright (c) 2015-2019, Activision Publishing, Inc.
2
+ # All rights reserved.
3
+ #
4
+ # Redistribution and use in source and binary forms, with or without modification,
5
+ # are permitted provided that the following conditions are met:
6
+ #
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ #
10
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # 3. Neither the name of the copyright holder nor the names of its contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission.
17
+ #
18
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
+ # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+ from __future__ import annotations
30
+
31
+ from typing import TYPE_CHECKING
32
+
33
+ if TYPE_CHECKING:
34
+ from typing_extensions import Self
35
+
36
+ __tracebackhide__ = True
37
+
38
+
39
+ class _InertBuilder:
40
+ """No-op builder returned after a failed raises/when_called_with in soft mode.
41
+
42
+ Silently absorbs all chained assertions so they don't crash on wrong val type.
43
+ """
44
+
45
+ def __getattr__(self, name):
46
+ return lambda *args, **kwargs: self
47
+
48
+
49
+ class ExceptionMixin:
50
+ """Expected exception mixin."""
51
+
52
+ def raises(self, ex) -> Self:
53
+ """Asserts that val is callable and set the expected exception.
54
+
55
+ Just sets the expected exception, but never calls val, and therefore never failes. You must
56
+ chain to :meth:`~when_called_with` to invoke ``val()``.
57
+
58
+ Args:
59
+ ex: the expected exception
60
+
61
+ Examples:
62
+ Usage::
63
+
64
+ assert_that(some_func).raises(RuntimeError).when_called_with('foo')
65
+
66
+ Returns:
67
+ AssertionBuilder: returns a new instance (now with the given expected exception) to chain to the next assertion
68
+ """
69
+ if not callable(self.val):
70
+ raise TypeError("val must be callable")
71
+ if not issubclass(ex, BaseException):
72
+ raise TypeError("given arg must be exception")
73
+
74
+ # chain on with ex as the expected exception
75
+ return self.builder(self.val, self.description, self.kind, ex, self.logger)
76
+
77
+ def when_called_with(self, *some_args, **some_kwargs) -> Self:
78
+ """Asserts that val, when invoked with the given args and kwargs, raises the expected exception.
79
+
80
+ Invokes ``val()`` with the given args and kwargs. You must first set the expected
81
+ exception with :meth:`~raises`.
82
+
83
+ Args:
84
+ *some_args: the args to call ``val()``
85
+ **some_kwargs: the kwargs to call ``val()``
86
+
87
+ Examples:
88
+ Usage::
89
+
90
+ def some_func(a):
91
+ raise RuntimeError('some error!')
92
+
93
+ assert_that(some_func).raises(RuntimeError).when_called_with('foo')
94
+
95
+ Returns:
96
+ AssertionBuilder: returns a new instance (now with the captured exception error message as the val) to chain to the next assertion
97
+
98
+ Raises:
99
+ AssertionError: if val does **not** raise the expected exception
100
+ TypeError: if expected exception not set via :meth:`raises`
101
+ """
102
+ if not self.expected:
103
+ raise TypeError("expected exception not set, raises() must be called first")
104
+ try:
105
+ self.val(*some_args, **some_kwargs)
106
+ except BaseException as e:
107
+ if issubclass(type(e), self.expected):
108
+ # chain on with error message
109
+ return self.builder(str(e), self.description, self.kind, logger=self.logger)
110
+ else:
111
+ # got exception, but wrong type, so raise
112
+ self.error(
113
+ "Expected <%s> to raise <%s> when called with (%s), but raised <%s>."
114
+ % (
115
+ self.val.__name__,
116
+ self.expected.__name__,
117
+ self._fmt_args_kwargs(*some_args, **some_kwargs),
118
+ type(e).__name__,
119
+ )
120
+ )
121
+ return _InertBuilder()
122
+
123
+ # didn't fail as expected, so raise
124
+ self.error(
125
+ "Expected <%s> to raise <%s> when called with (%s)."
126
+ % (self.val.__name__, self.expected.__name__, self._fmt_args_kwargs(*some_args, **some_kwargs))
127
+ )
128
+ return _InertBuilder()