punit 1.9.2__tar.gz → 1.10.0__tar.gz
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.
- {punit-1.9.2/src/pUnit.egg-info → punit-1.10.0}/PKG-INFO +1 -2
- {punit-1.9.2 → punit-1.10.0}/pyproject.toml +1 -2
- {punit-1.9.2 → punit-1.10.0/src/pUnit.egg-info}/PKG-INFO +1 -2
- {punit-1.9.2 → punit-1.10.0}/src/pUnit.egg-info/SOURCES.txt +4 -0
- {punit-1.9.2 → punit-1.10.0}/src/pUnit.egg-info/requires.txt +0 -1
- {punit-1.9.2 → punit-1.10.0}/src/punit/__init__.py +2 -2
- punit-1.10.0/src/punit/mocks/__init__.py +75 -0
- punit-1.10.0/src/punit/mocks/matcher.py +318 -0
- punit-1.10.0/src/punit/mocks/mock.py +691 -0
- punit-1.10.0/src/punit/mocks/patch.py +167 -0
- {punit-1.9.2 → punit-1.10.0}/LICENSE +0 -0
- {punit-1.9.2 → punit-1.10.0}/README.md +0 -0
- {punit-1.9.2 → punit-1.10.0}/setup.cfg +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/pUnit.egg-info/dependency_links.txt +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/pUnit.egg-info/entry_points.txt +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/pUnit.egg-info/top_level.txt +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/TestResult.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/__main__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/assertions/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/assertions/collections.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/assertions/exceptions.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/assertions/numeric.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/assertions/strings.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/cli.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/discovery/TestModuleDiscovery.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/discovery/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/facts/Fact.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/facts/FactManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/facts/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/filters/Filter.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/filters/FilterManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/filters/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/metadata/CallableMetadata.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/metadata/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/py.typed +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/reports/HtmlReportGenerator.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/reports/JUnitReportGenerator.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/reports/JsonReportGenerator.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/reports/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/results/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/results/fails.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/runner.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/setups/Setup.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/setups/SetupManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/setups/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/teardowns/Teardown.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/teardowns/TeardownManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/teardowns/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/theories/Theory.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/theories/TheoryManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/theories/__init__.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/traits/Trait.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/traits/TraitManager.py +0 -0
- {punit-1.9.2 → punit-1.10.0}/src/punit/traits/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License: MIT License
|
|
@@ -36,7 +36,6 @@ Classifier: Intended Audience :: Developers
|
|
|
36
36
|
Requires-Python: >=3.11
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
License-File: LICENSE
|
|
39
|
-
Requires-Dist: hazrakah
|
|
40
39
|
Provides-Extra: dev
|
|
41
40
|
Requires-Dist: coverage; extra == "dev"
|
|
42
41
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pUnit"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.10.0"
|
|
4
4
|
description = "A modernized unit-test framework for Python."
|
|
5
5
|
keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
|
|
6
6
|
authors = [
|
|
@@ -16,7 +16,6 @@ classifiers = [
|
|
|
16
16
|
"Intended Audience :: Developers"
|
|
17
17
|
]
|
|
18
18
|
dependencies = [
|
|
19
|
-
"hazrakah"
|
|
20
19
|
]
|
|
21
20
|
|
|
22
21
|
[project.optional-dependencies]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License: MIT License
|
|
@@ -36,7 +36,6 @@ Classifier: Intended Audience :: Developers
|
|
|
36
36
|
Requires-Python: >=3.11
|
|
37
37
|
Description-Content-Type: text/markdown
|
|
38
38
|
License-File: LICENSE
|
|
39
|
-
Requires-Dist: hazrakah
|
|
40
39
|
Provides-Extra: dev
|
|
41
40
|
Requires-Dist: coverage; extra == "dev"
|
|
42
41
|
Requires-Dist: mypy; extra == "dev"
|
|
@@ -28,6 +28,10 @@ src/punit/filters/FilterManager.py
|
|
|
28
28
|
src/punit/filters/__init__.py
|
|
29
29
|
src/punit/metadata/CallableMetadata.py
|
|
30
30
|
src/punit/metadata/__init__.py
|
|
31
|
+
src/punit/mocks/__init__.py
|
|
32
|
+
src/punit/mocks/matcher.py
|
|
33
|
+
src/punit/mocks/mock.py
|
|
34
|
+
src/punit/mocks/patch.py
|
|
31
35
|
src/punit/reports/HtmlReportGenerator.py
|
|
32
36
|
src/punit/reports/JUnitReportGenerator.py
|
|
33
37
|
src/punit/reports/JsonReportGenerator.py
|
|
@@ -15,8 +15,8 @@ from .assertions.exceptions import raises
|
|
|
15
15
|
from .assertions.numeric import approx
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
__version__ = '1.
|
|
19
|
-
__commit__ = '
|
|
18
|
+
__version__ = '1.10.0'
|
|
19
|
+
__commit__ = '4e4c2b6'
|
|
20
20
|
__all__ = [
|
|
21
21
|
'__version__', '__commit__',
|
|
22
22
|
'assertions',
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2026 Shaun Wilson
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Provides :class:`Mock`, :class:`Matcher` argument matchers, a ``patch`` mechanism,
|
|
6
|
+
and convenience functions.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from punit.mocks import Mock, mock, is_any, contains, patch
|
|
11
|
+
|
|
12
|
+
# Fluent syntax
|
|
13
|
+
mock = (
|
|
14
|
+
Mock()
|
|
15
|
+
.do_stuff.
|
|
16
|
+
returns(42)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Constructor kwargs for fixture-style initialization
|
|
20
|
+
user = Mock(first_name='Alice', email='alice@example.com')
|
|
21
|
+
assert user.first_name == 'Alice'
|
|
22
|
+
assert user.email == 'alice@example.com'
|
|
23
|
+
|
|
24
|
+
# Matcher-based verification
|
|
25
|
+
assert mock.called_with(
|
|
26
|
+
is_any(),
|
|
27
|
+
neg(contains('foo')),
|
|
28
|
+
is_in(1, 2, 3))
|
|
29
|
+
|
|
30
|
+
# Module-level patching
|
|
31
|
+
with patch('some.module.ClassName') as m:
|
|
32
|
+
m.method.returns('result')
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .matcher import (
|
|
36
|
+
Matcher,
|
|
37
|
+
neg,
|
|
38
|
+
contains,
|
|
39
|
+
is_any,
|
|
40
|
+
is_gte,
|
|
41
|
+
is_gt,
|
|
42
|
+
is_in,
|
|
43
|
+
is_lte,
|
|
44
|
+
is_lt,
|
|
45
|
+
is_type,
|
|
46
|
+
)
|
|
47
|
+
from .mock import (
|
|
48
|
+
CallDetail,
|
|
49
|
+
Call,
|
|
50
|
+
CallList,
|
|
51
|
+
CallRecord,
|
|
52
|
+
Mock,
|
|
53
|
+
MockError
|
|
54
|
+
)
|
|
55
|
+
from .patch import patch
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
'CallDetail',
|
|
59
|
+
'Call',
|
|
60
|
+
'CallList',
|
|
61
|
+
'CallRecord',
|
|
62
|
+
'Matcher',
|
|
63
|
+
'Mock',
|
|
64
|
+
'MockError',
|
|
65
|
+
'neg',
|
|
66
|
+
'contains',
|
|
67
|
+
'is_any',
|
|
68
|
+
'is_gte',
|
|
69
|
+
'is_gt',
|
|
70
|
+
'is_in',
|
|
71
|
+
'is_lte',
|
|
72
|
+
'is_lt',
|
|
73
|
+
'is_type',
|
|
74
|
+
'patch',
|
|
75
|
+
]
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2026 Shaun Wilson
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Argument matchers for Mock call verification.
|
|
6
|
+
|
|
7
|
+
Matchers are lightweight, duck-typed objects that implement ``__eq__`` to dispatch
|
|
8
|
+
matching logic. No :class:`typing.Protocol` is used -- any object with a working
|
|
9
|
+
``__eq__`` method satisfies the matcher interface. This avoids needing a separate
|
|
10
|
+
``.matches()`` API -- Python's equality operator dispatches naturally.
|
|
11
|
+
|
|
12
|
+
Usage::
|
|
13
|
+
|
|
14
|
+
from punit.mocks import Mock, is_any, is_gt, is_in
|
|
15
|
+
|
|
16
|
+
mock = Mock()
|
|
17
|
+
mock(42, 'hello', ['a', 'b'])
|
|
18
|
+
|
|
19
|
+
assert mock.called_with(
|
|
20
|
+
is_gt(10), # first arg > 10
|
|
21
|
+
is_any(), # second arg can be anything
|
|
22
|
+
is_in('a', 'b'), # third arg is 'a' or 'b'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Custom matchers: subclass :class:`Matcher` and implement ``__eq__``::
|
|
26
|
+
|
|
27
|
+
class GreaterThan(Matcher):
|
|
28
|
+
def __init__(self, threshold: int) -> None:
|
|
29
|
+
self._threshold = threshold
|
|
30
|
+
|
|
31
|
+
def __eq__(self, other: object) -> bool:
|
|
32
|
+
return isinstance(other, (int, float)) and other > self._threshold
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Matcher:
|
|
41
|
+
"""Base class for argument matchers.
|
|
42
|
+
|
|
43
|
+
Subclasses must implement ``__eq__(self, other: Any) -> bool`` and return
|
|
44
|
+
``True`` when *other* matches the expected pattern. This allows :meth:`Mock.called_with`
|
|
45
|
+
to dispatch via Python's equality operator naturally.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class _IsAny(Matcher):
|
|
50
|
+
"""Singleton matcher that matches any value."""
|
|
51
|
+
|
|
52
|
+
__slots__ = ()
|
|
53
|
+
_instance: '_IsAny | None' = None
|
|
54
|
+
|
|
55
|
+
def __new__(cls) -> '_IsAny':
|
|
56
|
+
if _IsAny._instance is None:
|
|
57
|
+
_IsAny._instance = super().__new__(cls)
|
|
58
|
+
return _IsAny._instance
|
|
59
|
+
|
|
60
|
+
def __eq__(self, other: Any) -> bool:
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
return 'is_any'
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def is_any() -> Matcher:
|
|
68
|
+
"""Return a singleton matcher that matches any single value.
|
|
69
|
+
|
|
70
|
+
:returns: A :class:`Matcher` instance.
|
|
71
|
+
"""
|
|
72
|
+
return _IsAny()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _Contains(Matcher):
|
|
76
|
+
"""Matcher that checks membership via the ``in`` operator."""
|
|
77
|
+
|
|
78
|
+
__slots__ = ('_value',)
|
|
79
|
+
|
|
80
|
+
def __init__(self, value: Any) -> None:
|
|
81
|
+
object.__setattr__(self, '_value', value)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def value(self) -> Any:
|
|
85
|
+
return object.__getattribute__(self, '_value')
|
|
86
|
+
|
|
87
|
+
def __eq__(self, other: Any) -> bool:
|
|
88
|
+
if isinstance(other, str):
|
|
89
|
+
return self._value in other # type: ignore[operator, attr-defined]
|
|
90
|
+
try:
|
|
91
|
+
return self._value in other # type: ignore[operator, attr-defined]
|
|
92
|
+
except TypeError:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
def __repr__(self) -> str:
|
|
96
|
+
return f'contains({self._value!r})' # type: ignore[attr-defined]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def contains(value: Any) -> Matcher:
|
|
100
|
+
"""Return a matcher that checks if *value* is a substring of a string or
|
|
101
|
+
an element of a container (list, tuple, set, dict keys).
|
|
102
|
+
|
|
103
|
+
:param value: The value to search for.
|
|
104
|
+
:returns: A :class:`Matcher` instance.
|
|
105
|
+
"""
|
|
106
|
+
return _Contains(value)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _IsGt(Matcher):
|
|
110
|
+
"""Value is strictly greater than *n*."""
|
|
111
|
+
|
|
112
|
+
__slots__ = ('_value',)
|
|
113
|
+
|
|
114
|
+
def __init__(self, value: float) -> None:
|
|
115
|
+
object.__setattr__(self, '_value', value)
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def value(self) -> float:
|
|
119
|
+
return object.__getattribute__(self, '_value')
|
|
120
|
+
|
|
121
|
+
def __eq__(self, other: Any) -> bool:
|
|
122
|
+
return isinstance(other, (int, float)) and other > self._value # type: ignore[attr-defined]
|
|
123
|
+
|
|
124
|
+
def __repr__(self) -> str:
|
|
125
|
+
return f'is_gt({self._value!r})' # type: ignore[attr-defined]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def is_gt(n: float) -> Matcher:
|
|
129
|
+
"""Return a matcher that checks if the value is strictly greater than *n*.
|
|
130
|
+
|
|
131
|
+
:param n: The threshold to compare against.
|
|
132
|
+
:returns: A :class:`Matcher` instance.
|
|
133
|
+
"""
|
|
134
|
+
return _IsGt(n)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class _IsGte(Matcher):
|
|
138
|
+
"""Value is greater than or equal to *n*."""
|
|
139
|
+
|
|
140
|
+
__slots__ = ('_value',)
|
|
141
|
+
|
|
142
|
+
def __init__(self, value: float) -> None:
|
|
143
|
+
object.__setattr__(self, '_value', value)
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def value(self) -> float:
|
|
147
|
+
return object.__getattribute__(self, '_value')
|
|
148
|
+
|
|
149
|
+
def __eq__(self, other: Any) -> bool:
|
|
150
|
+
return isinstance(other, (int, float)) and other >= self._value # type: ignore[attr-defined]
|
|
151
|
+
|
|
152
|
+
def __repr__(self) -> str:
|
|
153
|
+
return f'is_gte({self._value!r})' # type: ignore[attr-defined]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def is_gte(n: float) -> Matcher:
|
|
157
|
+
"""Return a matcher that checks if the value is greater than or equal to *n*.
|
|
158
|
+
|
|
159
|
+
:param n: The threshold to compare against.
|
|
160
|
+
:returns: A :class:`Matcher` instance.
|
|
161
|
+
"""
|
|
162
|
+
return _IsGte(n)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class _IsLt(Matcher):
|
|
166
|
+
"""Value is strictly less than *n*."""
|
|
167
|
+
|
|
168
|
+
__slots__ = ('_value',)
|
|
169
|
+
|
|
170
|
+
def __init__(self, value: float) -> None:
|
|
171
|
+
object.__setattr__(self, '_value', value)
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def value(self) -> float:
|
|
175
|
+
return object.__getattribute__(self, '_value')
|
|
176
|
+
|
|
177
|
+
def __eq__(self, other: Any) -> bool:
|
|
178
|
+
return isinstance(other, (int, float)) and other < self._value # type: ignore[attr-defined]
|
|
179
|
+
|
|
180
|
+
def __repr__(self) -> str:
|
|
181
|
+
return f'is_lt({self._value!r})' # type: ignore[attr-defined]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def is_lt(n: float) -> Matcher:
|
|
185
|
+
"""Return a matcher that checks if the value is strictly less than *n*.
|
|
186
|
+
|
|
187
|
+
:param n: The threshold to compare against.
|
|
188
|
+
:returns: A :class:`Matcher` instance.
|
|
189
|
+
"""
|
|
190
|
+
return _IsLt(n)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class _IsLte(Matcher):
|
|
194
|
+
"""Value is less than or equal to *n*."""
|
|
195
|
+
|
|
196
|
+
__slots__ = ('_value',)
|
|
197
|
+
|
|
198
|
+
def __init__(self, value: float) -> None:
|
|
199
|
+
object.__setattr__(self, '_value', value)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def value(self) -> float:
|
|
203
|
+
return object.__getattribute__(self, '_value')
|
|
204
|
+
|
|
205
|
+
def __eq__(self, other: Any) -> bool:
|
|
206
|
+
return isinstance(other, (int, float)) and other <= self._value # type: ignore[attr-defined]
|
|
207
|
+
|
|
208
|
+
def __repr__(self) -> str:
|
|
209
|
+
return f'is_lte({self._value!r})' # type: ignore[attr-defined]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def is_lte(n: float) -> Matcher:
|
|
213
|
+
"""Return a matcher that checks if the value is less than or equal to *n*.
|
|
214
|
+
|
|
215
|
+
:param n: The threshold to compare against.
|
|
216
|
+
:returns: A :class:`Matcher` instance.
|
|
217
|
+
"""
|
|
218
|
+
return _IsLte(n)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class _IsIn(Matcher):
|
|
222
|
+
"""Value equals one of the provided values."""
|
|
223
|
+
|
|
224
|
+
__slots__ = ('_values',)
|
|
225
|
+
|
|
226
|
+
def __init__(self, *values: Any) -> None:
|
|
227
|
+
object.__setattr__(self, '_values', tuple(values))
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def values(self) -> tuple[Any, ...]:
|
|
231
|
+
return object.__getattribute__(self, '_values')
|
|
232
|
+
|
|
233
|
+
def __eq__(self, other: Any) -> bool:
|
|
234
|
+
return other in self._values # type: ignore[attr-defined]
|
|
235
|
+
|
|
236
|
+
def __repr__(self) -> str:
|
|
237
|
+
return f'is_in({self._values!r})' # type: ignore[attr-defined]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def is_in(*values: Any) -> Matcher:
|
|
241
|
+
"""Return a matcher that checks if the value equals one of the provided values.
|
|
242
|
+
|
|
243
|
+
This is the inverse of :func:`contains` -- ``contains(x)`` checks if x is in the arg,
|
|
244
|
+
while ``is_in(a, b, c)`` checks if the arg is one of a, b, or c.
|
|
245
|
+
|
|
246
|
+
:param values: The candidate values to check against.
|
|
247
|
+
:returns: A :class:`Matcher` instance.
|
|
248
|
+
"""
|
|
249
|
+
return _IsIn(*values)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class _IsType(Matcher):
|
|
253
|
+
"""Value is an instance of any given type(s)."""
|
|
254
|
+
|
|
255
|
+
__slots__ = ('_types',)
|
|
256
|
+
|
|
257
|
+
def __init__(self, *types: type) -> None:
|
|
258
|
+
object.__setattr__(self, '_types', tuple(types))
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def types(self) -> tuple[type, ...]:
|
|
262
|
+
return object.__getattribute__(self, '_types')
|
|
263
|
+
|
|
264
|
+
def __eq__(self, other: Any) -> bool:
|
|
265
|
+
return isinstance(other, self._types) # type: ignore[attr-defined]
|
|
266
|
+
|
|
267
|
+
def __repr__(self) -> str:
|
|
268
|
+
return f'is_type({self._types!r})' # type: ignore[attr-defined]
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def is_type(*types: type) -> Matcher:
|
|
272
|
+
"""Return a matcher that checks if the value is an instance of any given type(s).
|
|
273
|
+
|
|
274
|
+
:param types: One or more types to check against.
|
|
275
|
+
:returns: A :class:`Matcher` instance.
|
|
276
|
+
"""
|
|
277
|
+
return _IsType(*types)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class neg(Matcher):
|
|
281
|
+
"""
|
|
282
|
+
Wraps another matcher and negates its result.
|
|
283
|
+
|
|
284
|
+
This enables composing matchers freely -- e.g. ``neg(is_in('admin', 'root'))``
|
|
285
|
+
matches any value that is not 'admin' or 'root'.
|
|
286
|
+
|
|
287
|
+
:param inner: The matcher to negate.
|
|
288
|
+
:returns: A :class:`Matcher` instance.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
__slots__ = ('_inner',)
|
|
292
|
+
|
|
293
|
+
def __init__(self, inner: Matcher) -> None:
|
|
294
|
+
object.__setattr__(self, '_inner', inner)
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def inner(self) -> Matcher:
|
|
298
|
+
return object.__getattribute__(self, '_inner')
|
|
299
|
+
|
|
300
|
+
def __eq__(self, other: Any) -> bool:
|
|
301
|
+
return not self._inner.__eq__(other) # type: ignore[attr-defined]
|
|
302
|
+
|
|
303
|
+
def __repr__(self) -> str:
|
|
304
|
+
return f'neg({self._inner!r})' # type: ignore[attr-defined]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
__all__ = [
|
|
308
|
+
'Matcher',
|
|
309
|
+
'neg',
|
|
310
|
+
'is_any',
|
|
311
|
+
'contains',
|
|
312
|
+
'is_gt',
|
|
313
|
+
'is_gte',
|
|
314
|
+
'is_lt',
|
|
315
|
+
'is_lte',
|
|
316
|
+
'is_in',
|
|
317
|
+
'is_type',
|
|
318
|
+
]
|