decoy 2.2.0__py3-none-any.whl → 2.2.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/__init__.py +5 -5
- decoy/errors.py +19 -11
- decoy/matchers.py +4 -5
- decoy/spy_log.py +9 -9
- decoy/verifier.py +2 -2
- decoy/warning_checker.py +4 -4
- decoy/warnings.py +22 -11
- {decoy-2.2.0.dist-info → decoy-2.2.2.dist-info}/METADATA +7 -14
- {decoy-2.2.0.dist-info → decoy-2.2.2.dist-info}/RECORD +12 -12
- {decoy-2.2.0.dist-info → decoy-2.2.2.dist-info}/WHEEL +1 -1
- {decoy-2.2.0.dist-info → decoy-2.2.2.dist-info}/entry_points.txt +0 -0
- {decoy-2.2.0.dist-info → decoy-2.2.2.dist-info/licenses}/LICENSE +0 -0
decoy/__init__.py
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
from typing import Any, Callable, Coroutine, Generic, Optional, Union, overload
|
|
4
4
|
|
|
5
5
|
from . import errors, matchers, warnings
|
|
6
|
-
from .core import DecoyCore, StubCore, PropCore
|
|
7
|
-
from .types import ClassT, ContextValueT, FuncT, ReturnT
|
|
8
6
|
from .context_managers import (
|
|
9
|
-
ContextManager,
|
|
10
7
|
AsyncContextManager,
|
|
11
|
-
GeneratorContextManager,
|
|
12
8
|
AsyncGeneratorContextManager,
|
|
9
|
+
ContextManager,
|
|
10
|
+
GeneratorContextManager,
|
|
13
11
|
)
|
|
12
|
+
from .core import DecoyCore, PropCore, StubCore
|
|
13
|
+
from .types import ClassT, ContextValueT, FuncT, ReturnT
|
|
14
14
|
|
|
15
15
|
# ensure decoy does not pollute pytest tracebacks
|
|
16
16
|
__tracebackhide__ = True
|
|
@@ -82,7 +82,7 @@ class Decoy:
|
|
|
82
82
|
spec = cls or func
|
|
83
83
|
|
|
84
84
|
if spec is None and name is None:
|
|
85
|
-
raise errors.MockNameRequiredError()
|
|
85
|
+
raise errors.MockNameRequiredError.create()
|
|
86
86
|
|
|
87
87
|
return self._core.mock(spec=spec, name=name, is_async=is_async)
|
|
88
88
|
|
decoy/errors.py
CHANGED
|
@@ -19,8 +19,10 @@ class MockNameRequiredError(ValueError):
|
|
|
19
19
|
[MockNameRequiredError guide]: usage/errors-and-warnings.md#mocknamerequirederror
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
@classmethod
|
|
23
|
+
def create(cls) -> "MockNameRequiredError":
|
|
24
|
+
"""Create a MockNameRequiredError."""
|
|
25
|
+
return cls("Mocks without `cls` or `func` require a `name`.")
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class MissingRehearsalError(ValueError):
|
|
@@ -36,8 +38,10 @@ class MissingRehearsalError(ValueError):
|
|
|
36
38
|
[MissingRehearsalError guide]: usage/errors-and-warnings.md#missingrehearsalerror
|
|
37
39
|
"""
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
@classmethod
|
|
42
|
+
def create(cls) -> "MissingRehearsalError":
|
|
43
|
+
"""Create a MissingRehearsalError."""
|
|
44
|
+
return cls("Rehearsal not found.")
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
class MockNotAsyncError(TypeError):
|
|
@@ -68,12 +72,14 @@ class VerifyError(AssertionError):
|
|
|
68
72
|
calls: Sequence[SpyEvent]
|
|
69
73
|
times: Optional[int]
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
@classmethod
|
|
76
|
+
def create(
|
|
77
|
+
cls,
|
|
73
78
|
rehearsals: Sequence[VerifyRehearsal],
|
|
74
79
|
calls: Sequence[SpyEvent],
|
|
75
80
|
times: Optional[int],
|
|
76
|
-
) ->
|
|
81
|
+
) -> "VerifyError":
|
|
82
|
+
"""Create a VerifyError."""
|
|
77
83
|
if times is not None:
|
|
78
84
|
heading = f"Expected exactly {count(times, 'call')}:"
|
|
79
85
|
elif len(rehearsals) == 1:
|
|
@@ -88,7 +94,9 @@ class VerifyError(AssertionError):
|
|
|
88
94
|
include_calls=times is None or times == len(calls),
|
|
89
95
|
)
|
|
90
96
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
result = cls(message)
|
|
98
|
+
result.rehearsals = rehearsals
|
|
99
|
+
result.calls = calls
|
|
100
|
+
result.times = times
|
|
101
|
+
|
|
102
|
+
return result
|
decoy/matchers.py
CHANGED
|
@@ -28,8 +28,7 @@ See the [matchers guide][] for more details.
|
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
from re import compile as compile_re
|
|
31
|
-
from typing import
|
|
32
|
-
|
|
31
|
+
from typing import Any, List, Mapping, Optional, Pattern, Type, TypeVar, cast
|
|
33
32
|
|
|
34
33
|
__all__ = [
|
|
35
34
|
"Anything",
|
|
@@ -212,9 +211,9 @@ def HasAttributes(attributes: Mapping[str, Any]) -> Any:
|
|
|
212
211
|
|
|
213
212
|
|
|
214
213
|
class _DictMatching:
|
|
215
|
-
_values: Mapping[
|
|
214
|
+
_values: Mapping[Any, Any]
|
|
216
215
|
|
|
217
|
-
def __init__(self, values: Mapping[
|
|
216
|
+
def __init__(self, values: Mapping[Any, Any]) -> None:
|
|
218
217
|
self._values = values
|
|
219
218
|
|
|
220
219
|
def __eq__(self, target: object) -> bool:
|
|
@@ -235,7 +234,7 @@ class _DictMatching:
|
|
|
235
234
|
return f"<DictMatching {self._values!r}>"
|
|
236
235
|
|
|
237
236
|
|
|
238
|
-
def DictMatching(values: Mapping[
|
|
237
|
+
def DictMatching(values: Mapping[Any, Any]) -> Any:
|
|
239
238
|
"""Match any dictionary with the passed in keys / values.
|
|
240
239
|
|
|
241
240
|
Arguments:
|
decoy/spy_log.py
CHANGED
|
@@ -5,13 +5,13 @@ from typing import List, Sequence
|
|
|
5
5
|
from .errors import MissingRehearsalError
|
|
6
6
|
from .spy_events import (
|
|
7
7
|
AnySpyEvent,
|
|
8
|
+
PropAccessType,
|
|
9
|
+
PropRehearsal,
|
|
8
10
|
SpyCall,
|
|
9
11
|
SpyEvent,
|
|
10
12
|
SpyPropAccess,
|
|
11
|
-
WhenRehearsal,
|
|
12
13
|
VerifyRehearsal,
|
|
13
|
-
|
|
14
|
-
PropRehearsal,
|
|
14
|
+
WhenRehearsal,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
|
|
@@ -33,10 +33,10 @@ class SpyLog:
|
|
|
33
33
|
try:
|
|
34
34
|
event = self._log[-1]
|
|
35
35
|
except IndexError as e:
|
|
36
|
-
raise MissingRehearsalError() from e
|
|
36
|
+
raise MissingRehearsalError.create() from e
|
|
37
37
|
|
|
38
38
|
if not isinstance(event, SpyEvent):
|
|
39
|
-
raise MissingRehearsalError()
|
|
39
|
+
raise MissingRehearsalError.create()
|
|
40
40
|
|
|
41
41
|
spy, payload = _apply_ignore_extra_args(event, ignore_extra_args)
|
|
42
42
|
|
|
@@ -59,12 +59,12 @@ class SpyLog:
|
|
|
59
59
|
|
|
60
60
|
while len(rehearsals) < count:
|
|
61
61
|
if index < 0:
|
|
62
|
-
raise MissingRehearsalError()
|
|
62
|
+
raise MissingRehearsalError.create()
|
|
63
63
|
|
|
64
64
|
event = self._log[index]
|
|
65
65
|
|
|
66
66
|
if not isinstance(event, (SpyEvent, PropRehearsal)):
|
|
67
|
-
raise MissingRehearsalError()
|
|
67
|
+
raise MissingRehearsalError.create()
|
|
68
68
|
|
|
69
69
|
if _is_verifiable(event):
|
|
70
70
|
rehearsal = VerifyRehearsal(
|
|
@@ -82,7 +82,7 @@ class SpyLog:
|
|
|
82
82
|
try:
|
|
83
83
|
event = self._log[-1]
|
|
84
84
|
except IndexError as e:
|
|
85
|
-
raise MissingRehearsalError() from e
|
|
85
|
+
raise MissingRehearsalError.create() from e
|
|
86
86
|
|
|
87
87
|
spy, payload = event
|
|
88
88
|
|
|
@@ -91,7 +91,7 @@ class SpyLog:
|
|
|
91
91
|
or not isinstance(payload, SpyPropAccess)
|
|
92
92
|
or payload.access_type != PropAccessType.GET
|
|
93
93
|
):
|
|
94
|
-
raise MissingRehearsalError()
|
|
94
|
+
raise MissingRehearsalError.create()
|
|
95
95
|
|
|
96
96
|
rehearsal = PropRehearsal(spy, payload)
|
|
97
97
|
self._log[-1] = rehearsal
|
decoy/verifier.py
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional, Sequence
|
|
4
4
|
|
|
5
|
-
from .spy_events import SpyEvent, VerifyRehearsal, match_event
|
|
6
5
|
from .errors import VerifyError
|
|
6
|
+
from .spy_events import SpyEvent, VerifyRehearsal, match_event
|
|
7
7
|
|
|
8
8
|
# ensure decoy.verifier does not pollute Pytest tracebacks
|
|
9
9
|
__tracebackhide__ = True
|
|
@@ -42,7 +42,7 @@ class Verifier:
|
|
|
42
42
|
calls_verified = match_count != 0 if times is None else match_count == times
|
|
43
43
|
|
|
44
44
|
if not calls_verified:
|
|
45
|
-
raise VerifyError(
|
|
45
|
+
raise VerifyError.create(
|
|
46
46
|
rehearsals=rehearsals,
|
|
47
47
|
calls=calls,
|
|
48
48
|
times=times,
|
decoy/warning_checker.py
CHANGED
|
@@ -9,9 +9,9 @@ from .spy_events import (
|
|
|
9
9
|
AnySpyEvent,
|
|
10
10
|
SpyCall,
|
|
11
11
|
SpyEvent,
|
|
12
|
+
SpyRehearsal,
|
|
12
13
|
VerifyRehearsal,
|
|
13
14
|
WhenRehearsal,
|
|
14
|
-
SpyRehearsal,
|
|
15
15
|
match_event,
|
|
16
16
|
)
|
|
17
17
|
from .warnings import DecoyWarning, MiscalledStubWarning, RedundantVerifyWarning
|
|
@@ -78,7 +78,7 @@ def _check_no_miscalled_stubs(all_events: Sequence[AnySpyEvent]) -> None:
|
|
|
78
78
|
|
|
79
79
|
if is_stubbed and all(len(c.matching_rehearsals) == 0 for c in calls):
|
|
80
80
|
_warn(
|
|
81
|
-
MiscalledStubWarning(
|
|
81
|
+
MiscalledStubWarning.create(
|
|
82
82
|
calls=[c.event for c in calls],
|
|
83
83
|
rehearsals=rehearsals,
|
|
84
84
|
)
|
|
@@ -90,8 +90,8 @@ def _check_no_redundant_verify(all_calls: Sequence[AnySpyEvent]) -> None:
|
|
|
90
90
|
verify_rehearsals = [c for c in all_calls if isinstance(c, VerifyRehearsal)]
|
|
91
91
|
|
|
92
92
|
for vr in verify_rehearsals:
|
|
93
|
-
if any(wr for wr in when_rehearsals if wr == vr):
|
|
94
|
-
_warn(RedundantVerifyWarning(rehearsal=vr))
|
|
93
|
+
if any(wr for wr in when_rehearsals if wr == vr): # type: ignore[comparison-overlap]
|
|
94
|
+
_warn(RedundantVerifyWarning.create(rehearsal=vr))
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
def _warn(warning: DecoyWarning) -> None:
|
decoy/warnings.py
CHANGED
|
@@ -9,7 +9,7 @@ import os
|
|
|
9
9
|
from typing import Sequence
|
|
10
10
|
|
|
11
11
|
from .spy_events import SpyEvent, SpyRehearsal, VerifyRehearsal
|
|
12
|
-
from .stringify import stringify_call, stringify_error_message
|
|
12
|
+
from .stringify import count, stringify_call, stringify_error_message
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class DecoyWarning(UserWarning):
|
|
@@ -31,18 +31,20 @@ class MiscalledStubWarning(DecoyWarning):
|
|
|
31
31
|
[MiscalledStubWarning guide]: usage/errors-and-warnings.md#miscalledstubwarning
|
|
32
32
|
|
|
33
33
|
Attributes:
|
|
34
|
-
rehearsals: The
|
|
34
|
+
rehearsals: The mock's configured rehearsals.
|
|
35
35
|
calls: Actual calls to the mock.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
rehearsals: Sequence[SpyRehearsal]
|
|
39
39
|
calls: Sequence[SpyEvent]
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
@classmethod
|
|
42
|
+
def create(
|
|
43
|
+
cls,
|
|
43
44
|
rehearsals: Sequence[SpyRehearsal],
|
|
44
45
|
calls: Sequence[SpyEvent],
|
|
45
|
-
) ->
|
|
46
|
+
) -> "MiscalledStubWarning":
|
|
47
|
+
"""Create a MiscalledStubWarning."""
|
|
46
48
|
heading = os.linesep.join(
|
|
47
49
|
[
|
|
48
50
|
"Stub was called but no matching rehearsal found.",
|
|
@@ -56,9 +58,11 @@ class MiscalledStubWarning(DecoyWarning):
|
|
|
56
58
|
calls=calls,
|
|
57
59
|
)
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
result = cls(message)
|
|
62
|
+
result.rehearsals = rehearsals
|
|
63
|
+
result.calls = calls
|
|
64
|
+
|
|
65
|
+
return result
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
class RedundantVerifyWarning(DecoyWarning):
|
|
@@ -74,7 +78,11 @@ class RedundantVerifyWarning(DecoyWarning):
|
|
|
74
78
|
[RedundantVerifyWarning guide]: usage/errors-and-warnings.md#redundantverifywarning
|
|
75
79
|
"""
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
rehearsal: VerifyRehearsal
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def create(cls, rehearsal: VerifyRehearsal) -> "RedundantVerifyWarning":
|
|
85
|
+
"""Create a RedundantVerifyWarning."""
|
|
78
86
|
message = os.linesep.join(
|
|
79
87
|
[
|
|
80
88
|
"The same rehearsal was used in both a `when` and a `verify`.",
|
|
@@ -83,8 +91,11 @@ class RedundantVerifyWarning(DecoyWarning):
|
|
|
83
91
|
"See https://michael.cousins.io/decoy/usage/errors-and-warnings/#redundantverifywarning",
|
|
84
92
|
]
|
|
85
93
|
)
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
|
|
95
|
+
result = cls(message)
|
|
96
|
+
result.rehearsal = rehearsal
|
|
97
|
+
|
|
98
|
+
return result
|
|
88
99
|
|
|
89
100
|
|
|
90
101
|
class IncorrectCallWarning(DecoyWarning):
|
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: decoy
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Opinionated mocking library for Python
|
|
5
|
-
License: MIT
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Author: Michael Cousins
|
|
7
|
-
Author-email: michael@cousins.io
|
|
8
|
-
Requires-Python: >=3.7
|
|
8
|
+
Author-email: michael@cousins.io>
|
|
9
|
+
Requires-Python: >=3.7
|
|
9
10
|
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
|
-
Classifier: Programming Language :: Python :: 3
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
21
13
|
Classifier: Topic :: Software Development :: Testing
|
|
22
14
|
Classifier: Topic :: Software Development :: Testing :: Mocking
|
|
23
15
|
Classifier: Typing :: Typed
|
|
24
16
|
Project-URL: Changelog, https://github.com/mcous/decoy/releases
|
|
25
17
|
Project-URL: Documentation, https://michael.cousins.io/decoy/
|
|
26
18
|
Project-URL: Homepage, https://michael.cousins.io/decoy/
|
|
19
|
+
Project-URL: Issues, https://github.com/mcous/decoy/issues
|
|
27
20
|
Project-URL: Repository, https://github.com/mcous/decoy
|
|
28
21
|
Description-Content-Type: text/markdown
|
|
29
22
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
decoy/__init__.py,sha256=
|
|
1
|
+
decoy/__init__.py,sha256=v3LUyAxuxFrO2M4Iw0VRX0IM6axFMxPmtgIbBemmwj8,11232
|
|
2
2
|
decoy/call_handler.py,sha256=fuhWr9VAnh0FUvbfWw11fpdQ_GCUE3Hah-EXvdk3gGw,1512
|
|
3
3
|
decoy/context_managers.py,sha256=oYsH99ZPmkaCcqjCrOWInueO5ympHm_ICWmbpFoQzqA,1287
|
|
4
4
|
decoy/core.py,sha256=0YsQFnL4Kw_oDX7jRgOVLw9BGsnNdekymQCd1aq-4sQ,5830
|
|
5
|
-
decoy/errors.py,sha256=
|
|
6
|
-
decoy/matchers.py,sha256=
|
|
5
|
+
decoy/errors.py,sha256=mmUAOoQQyXhCLjMcf_3bHMz3PjU7bingIU6sC3hk2B4,3119
|
|
6
|
+
decoy/matchers.py,sha256=TFa3ZLewXPxhuUy_YDktzJWMfdOgfz4jq2TQ9oa6hwk,11298
|
|
7
7
|
decoy/mypy/__init__.py,sha256=GdJCf-gXpYB4SBr-Rm8zCLZQxAUuLjPyUieIWgLmdwA,86
|
|
8
8
|
decoy/mypy/plugin.py,sha256=nCfo4XpDxBRDsInDsvCe6h8t7-1OV0ZCJzIJqTsAU0I,1359
|
|
9
9
|
decoy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -11,15 +11,15 @@ decoy/pytest_plugin.py,sha256=muiWN6zwQPD-AaL4YH6usp-25P4EYjrROTx6wdLE9hM,853
|
|
|
11
11
|
decoy/spy.py,sha256=cxlwDFxd2dcTb6vFJJGc0B3IuFCPJs2cgVMeB0qNXW4,7581
|
|
12
12
|
decoy/spy_core.py,sha256=b3PTgCkYnijT08ak_iRUKnA95FnmspSRmT8cs_YtjtM,7965
|
|
13
13
|
decoy/spy_events.py,sha256=PFTfkgN7CVWfbVoSSUMSaCSxfRrUMd7U419iwQ1Xofg,2383
|
|
14
|
-
decoy/spy_log.py,sha256=
|
|
14
|
+
decoy/spy_log.py,sha256=5JcjcH7zHrn12jp7xyrs_UWY7ATcWJkQ4v9oCR_Cclg,3929
|
|
15
15
|
decoy/stringify.py,sha256=6JA2VA9eK-4AtwSSZPnMYT8ucW7EMpGdbTXokbOpgOg,2240
|
|
16
16
|
decoy/stub_store.py,sha256=tVqKOW65NjotVdDhDDXpLfwagKHypMS4WMob71RYk1c,1744
|
|
17
17
|
decoy/types.py,sha256=LXzky6T0RXvS53NoPpVgCi3KVV6KCyB6EvnBl0NaKug,404
|
|
18
|
-
decoy/verifier.py,sha256=
|
|
19
|
-
decoy/warning_checker.py,sha256=
|
|
20
|
-
decoy/warnings.py,sha256=
|
|
21
|
-
decoy-2.2.
|
|
22
|
-
decoy-2.2.
|
|
23
|
-
decoy-2.2.
|
|
24
|
-
decoy-2.2.
|
|
25
|
-
decoy-2.2.
|
|
18
|
+
decoy/verifier.py,sha256=3ZVqaUJPjaNM9Nue3FK6Zzj15qWskX2CEQXoe_yjUEs,1586
|
|
19
|
+
decoy/warning_checker.py,sha256=OOkcrr_I98Pzs3o9UUNgaihJDHwNiu1LlcEK9hi5ZFs,3577
|
|
20
|
+
decoy/warnings.py,sha256=DLdX1ABPk6QvhJWt5s98uR7rCQFQdJJjSPPXRSX0nXA,3203
|
|
21
|
+
decoy-2.2.2.dist-info/METADATA,sha256=hKPZiHbc6RDbYOJ9iwIZ0wvRhrOn3UYxPSCAqABg9Tc,6855
|
|
22
|
+
decoy-2.2.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
23
|
+
decoy-2.2.2.dist-info/entry_points.txt,sha256=P2wF8zdthEM-3Yo32kxHDhZDjbW6AE489HPWqnvPLOU,38
|
|
24
|
+
decoy-2.2.2.dist-info/licenses/LICENSE,sha256=Rxi19kHgqakAsJNG1jMuORmgKx9bI8Pcu_gtzFkhflQ,1078
|
|
25
|
+
decoy-2.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|