decoy 2.1.0__py3-none-any.whl → 2.1.2__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.
- decoy/spy_core.py +38 -2
- decoy/warnings.py +1 -1
- {decoy-2.1.0.dist-info → decoy-2.1.2.dist-info}/LICENSE +1 -1
- {decoy-2.1.0.dist-info → decoy-2.1.2.dist-info}/METADATA +17 -15
- {decoy-2.1.0.dist-info → decoy-2.1.2.dist-info}/RECORD +7 -7
- {decoy-2.1.0.dist-info → decoy-2.1.2.dist-info}/WHEEL +1 -1
- {decoy-2.1.0.dist-info → decoy-2.1.2.dist-info}/entry_points.txt +0 -0
decoy/spy_core.py
CHANGED
|
@@ -2,7 +2,17 @@
|
|
|
2
2
|
import inspect
|
|
3
3
|
import functools
|
|
4
4
|
import warnings
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import (
|
|
6
|
+
Any,
|
|
7
|
+
Dict,
|
|
8
|
+
NamedTuple,
|
|
9
|
+
Optional,
|
|
10
|
+
Tuple,
|
|
11
|
+
Type,
|
|
12
|
+
Union,
|
|
13
|
+
Sequence,
|
|
14
|
+
get_type_hints,
|
|
15
|
+
)
|
|
6
16
|
|
|
7
17
|
from .spy_events import SpyInfo
|
|
8
18
|
from .warnings import IncorrectCallWarning, MissingSpecAttributeWarning
|
|
@@ -112,12 +122,15 @@ class SpyCore:
|
|
|
112
122
|
source = self._source
|
|
113
123
|
child_name = f"{self._name}.{name}"
|
|
114
124
|
child_source = None
|
|
125
|
+
child_found = False
|
|
115
126
|
|
|
116
127
|
if inspect.isclass(source):
|
|
117
128
|
# use type hints to get child spec for class attributes
|
|
118
129
|
child_hint = _get_type_hints(source).get(name)
|
|
119
130
|
# use inspect to get child spec for methods and properties
|
|
120
131
|
child_source = inspect.getattr_static(source, name, child_hint)
|
|
132
|
+
# record whether a child was found before we make modifications
|
|
133
|
+
child_found = child_source is not None
|
|
121
134
|
|
|
122
135
|
if isinstance(child_source, property):
|
|
123
136
|
child_source = _get_type_hints(child_source.fget).get("return")
|
|
@@ -136,7 +149,9 @@ class SpyCore:
|
|
|
136
149
|
# signature reporting by wrapping it in a partial
|
|
137
150
|
child_source = functools.partial(child_source, None)
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
child_source = _unwrap_optional(child_source)
|
|
153
|
+
|
|
154
|
+
if source is not None and child_found is False:
|
|
140
155
|
# stacklevel: 4 ensures warning is linked to call location
|
|
141
156
|
warnings.warn(
|
|
142
157
|
MissingSpecAttributeWarning(f"{self._name} has no attribute '{name}'"),
|
|
@@ -215,3 +230,24 @@ def _get_type_hints(obj: Any) -> Dict[str, Any]:
|
|
|
215
230
|
return get_type_hints(obj)
|
|
216
231
|
except Exception:
|
|
217
232
|
return {}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _unwrap_optional(source: Any) -> Any:
|
|
236
|
+
"""Return the source's base type if it's a optional.
|
|
237
|
+
|
|
238
|
+
If the type is a union of more than just T | None,
|
|
239
|
+
bail out and return None to avoid potentially false warnings.
|
|
240
|
+
"""
|
|
241
|
+
origin = getattr(source, "__origin__", None)
|
|
242
|
+
args: Sequence[Any] = getattr(source, "__args__", ())
|
|
243
|
+
|
|
244
|
+
# TODO(mc, 2025-03-19): support larger unions? might be a lot of work for little payoff
|
|
245
|
+
if origin is Union:
|
|
246
|
+
if len(args) == 2 and args[0] is type(None):
|
|
247
|
+
return args[1]
|
|
248
|
+
if len(args) == 2 and args[1] is type(None):
|
|
249
|
+
return args[0]
|
|
250
|
+
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
return source
|
decoy/warnings.py
CHANGED
|
@@ -79,7 +79,7 @@ class RedundantVerifyWarning(DecoyWarning):
|
|
|
79
79
|
"The same rehearsal was used in both a `when` and a `verify`.",
|
|
80
80
|
"This is redundant and probably a misuse of the mock.",
|
|
81
81
|
f"\t{stringify_call(rehearsal)}",
|
|
82
|
-
"See https://
|
|
82
|
+
"See https://michael.cousins.io/decoy/usage/errors-and-warnings/#redundantverifywarning",
|
|
83
83
|
]
|
|
84
84
|
)
|
|
85
85
|
super().__init__(message)
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: decoy
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Opinionated mocking library for Python
|
|
5
|
-
Home-page: https://mike.cousins.io/decoy/
|
|
6
5
|
License: MIT
|
|
7
|
-
Author:
|
|
8
|
-
Author-email:
|
|
6
|
+
Author: Michael Cousins
|
|
7
|
+
Author-email: michael@cousins.io
|
|
9
8
|
Requires-Python: >=3.7,<4.0
|
|
10
9
|
Classifier: Development Status :: 5 - Production/Stable
|
|
11
10
|
Classifier: Intended Audience :: Developers
|
|
@@ -17,16 +16,19 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
21
|
Classifier: Topic :: Software Development :: Testing
|
|
21
22
|
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
22
23
|
Classifier: Typing :: Typed
|
|
23
24
|
Project-URL: Changelog, https://github.com/mcous/decoy/releases
|
|
24
|
-
Project-URL: Documentation, https://
|
|
25
|
+
Project-URL: Documentation, https://michael.cousins.io/decoy/
|
|
26
|
+
Project-URL: Homepage, https://michael.cousins.io/decoy/
|
|
25
27
|
Project-URL: Repository, https://github.com/mcous/decoy
|
|
26
28
|
Description-Content-Type: text/markdown
|
|
27
29
|
|
|
28
30
|
<div align="center">
|
|
29
|
-
<img alt="Decoy logo" src="https://
|
|
31
|
+
<img alt="Decoy logo" src="https://michael.cousins.io/decoy/img/decoy.png" width="256px">
|
|
30
32
|
<h1 class="decoy-title">Decoy</h1>
|
|
31
33
|
<p>Opinionated mocking library for Python</p>
|
|
32
34
|
<p>
|
|
@@ -38,7 +40,7 @@ Description-Content-Type: text/markdown
|
|
|
38
40
|
<a title="Supported Python Versions" href="https://pypi.org/project/decoy/"><img src="https://img.shields.io/pypi/pyversions/decoy?style=flat-square"></a>
|
|
39
41
|
</p>
|
|
40
42
|
<p>
|
|
41
|
-
<a href="https://
|
|
43
|
+
<a href="https://michael.cousins.io/decoy/" class="decoy-hidden">Usage guide and documentation</a>
|
|
42
44
|
</p>
|
|
43
45
|
</div>
|
|
44
46
|
|
|
@@ -86,8 +88,8 @@ plugins = decoy.mypy
|
|
|
86
88
|
|
|
87
89
|
Decoy works well with [pytest][], but if you use another testing library or framework, you can still use Decoy! You just need to do two things:
|
|
88
90
|
|
|
89
|
-
1. Create a new instance of [`Decoy()`](https://
|
|
90
|
-
2. Call [`decoy.reset()`](https://
|
|
91
|
+
1. Create a new instance of [`Decoy()`](https://michael.cousins.io/decoy/api/#decoy.Decoy) before each test
|
|
92
|
+
2. Call [`decoy.reset()`](https://michael.cousins.io/decoy/api/#decoy.Decoy.reset) after each test
|
|
91
93
|
|
|
92
94
|
For example, using the built-in [unittest][] framework, you would use the `setUp` fixture method to do `self.decoy = Decoy()` and the `tearDown` method to call `self.decoy.reset()`. For a working example, see [`tests/test_unittest.py`](https://github.com/mcous/decoy/blob/main/tests/test_unittest.py).
|
|
93
95
|
|
|
@@ -171,9 +173,9 @@ See [spying with verify][] for more details.
|
|
|
171
173
|
[unittest]: https://docs.python.org/3/library/unittest.html
|
|
172
174
|
[typing]: https://docs.python.org/3/library/typing.html
|
|
173
175
|
[mypy]: https://mypy.readthedocs.io/
|
|
174
|
-
[api reference]: https://
|
|
175
|
-
[usage guide]: https://
|
|
176
|
-
[creating mocks]: https://
|
|
177
|
-
[stubbing with when]: https://
|
|
178
|
-
[spying with verify]: https://
|
|
176
|
+
[api reference]: https://michael.cousins.io/decoy/api/
|
|
177
|
+
[usage guide]: https://michael.cousins.io/decoy/usage/create/
|
|
178
|
+
[creating mocks]: https://michael.cousins.io/decoy/usage/create/
|
|
179
|
+
[stubbing with when]: https://michael.cousins.io/decoy/usage/when/
|
|
180
|
+
[spying with verify]: https://michael.cousins.io/decoy/usage/verify/
|
|
179
181
|
|
|
@@ -9,7 +9,7 @@ decoy/mypy/plugin.py,sha256=LLJCfYQdEEayym0S6Re3BZG4gdi3drmyoIjiFXasfw0,1358
|
|
|
9
9
|
decoy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
decoy/pytest_plugin.py,sha256=bPJ9v8cwEFxtPQRWeTuP97WsWsaWHaXPPVKxiFf3VyE,852
|
|
11
11
|
decoy/spy.py,sha256=kpPXruiWcGwu8jOlIU4HWjaJUiSEzZ_AFc19eFevOhc,7596
|
|
12
|
-
decoy/spy_core.py,sha256=
|
|
12
|
+
decoy/spy_core.py,sha256=QrmS7q8ABo-pXabk55IXVot3TN_fMhaG5CUGNuk2mz8,8435
|
|
13
13
|
decoy/spy_events.py,sha256=paDLR_Pdn9KDemSOFRvHcDSidt-yzjX7sidXE8S67z8,2382
|
|
14
14
|
decoy/spy_log.py,sha256=cgl4inDJ-bpAiwuGbkWKWRaOGlGPTN8DKv1LiDJVbd4,3886
|
|
15
15
|
decoy/stringify.py,sha256=-xlYGfYfjHJuAATmH9dQk7kIQbiBknzZ07GOzO4p7yU,2239
|
|
@@ -17,9 +17,9 @@ decoy/stub_store.py,sha256=lLJGqv1CIxZlJFHhnXuoQdbp_q524eT-e8QHEDPSAew,1743
|
|
|
17
17
|
decoy/types.py,sha256=y2DYDekkGzjKTshW0VN3Yw6pifb5T9AyqdrpwohKV3A,403
|
|
18
18
|
decoy/verifier.py,sha256=FK_HmJnNSqwkkCAlkeZVtb2sK0sUZEwhnlP42dgXN4Y,1578
|
|
19
19
|
decoy/warning_checker.py,sha256=NVA4KwPeM7vneTku60XiDGjD9tG_ywkz6R7WvmTAlQA,3526
|
|
20
|
-
decoy/warnings.py,sha256=
|
|
21
|
-
decoy-2.1.
|
|
22
|
-
decoy-2.1.
|
|
23
|
-
decoy-2.1.
|
|
24
|
-
decoy-2.1.
|
|
25
|
-
decoy-2.1.
|
|
20
|
+
decoy/warnings.py,sha256=kPRR0ujdNGS_W_ciNvR5dGWyQuuWbooZduPW6OB32t8,3347
|
|
21
|
+
decoy-2.1.2.dist-info/LICENSE,sha256=Rxi19kHgqakAsJNG1jMuORmgKx9bI8Pcu_gtzFkhflQ,1078
|
|
22
|
+
decoy-2.1.2.dist-info/METADATA,sha256=5qGNUFcf_B1Blly9uBnBQV_S3nemThdQobxh6dZudv0,7220
|
|
23
|
+
decoy-2.1.2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
|
24
|
+
decoy-2.1.2.dist-info/entry_points.txt,sha256=P2wF8zdthEM-3Yo32kxHDhZDjbW6AE489HPWqnvPLOU,38
|
|
25
|
+
decoy-2.1.2.dist-info/RECORD,,
|
|
File without changes
|