assert-matcher 0.9.1__tar.gz → 0.9.2__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.
- assert_matcher-0.9.2/CHANGELOG +18 -0
- assert_matcher-0.9.2/MANIFEST.in +5 -0
- assert_matcher-0.9.2/PKG-INFO +229 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher/__init__.py +1 -1
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher/compare.py +7 -2
- assert_matcher-0.9.2/assert_matcher.egg-info/PKG-INFO +229 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher.egg-info/SOURCES.txt +0 -6
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/setup.cfg +3 -2
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/tests/test_compare.py +1 -0
- assert_matcher-0.9.1/.github/workflows/ci.yml +0 -43
- assert_matcher-0.9.1/.gitignore +0 -10
- assert_matcher-0.9.1/CHANGELOG +0 -2
- assert_matcher-0.9.1/GEMINI.md +0 -42
- assert_matcher-0.9.1/MANIFEST.in +0 -3
- assert_matcher-0.9.1/PKG-INFO +0 -31
- assert_matcher-0.9.1/assert_matcher.egg-info/PKG-INFO +0 -31
- assert_matcher-0.9.1/publish.sh +0 -11
- assert_matcher-0.9.1/test_requirements.txt +0 -1
- assert_matcher-0.9.1/tox.ini +0 -20
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/LICENSE +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/README.md +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher/approx.py +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher/matchers.py +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher/plugin.py +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher.egg-info/dependency_links.txt +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher.egg-info/entry_points.txt +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/assert_matcher.egg-info/top_level.txt +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/setup.py +0 -0
- {assert_matcher-0.9.1 → assert_matcher-0.9.2}/tests/test_matchers.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
0.9.2
|
|
2
|
+
- support Python 3.15
|
|
3
|
+
- support pytest 9.1
|
|
4
|
+
- fixed package metadata: license classifier, project url, long description
|
|
5
|
+
|
|
6
|
+
0.9.1
|
|
7
|
+
- added convenience import: from assert_matcher import M
|
|
8
|
+
- dropped Python 3.7 and 3.8 support
|
|
9
|
+
|
|
10
|
+
0.9
|
|
11
|
+
- first release as assert-matcher, formerly pytest-matcher
|
|
12
|
+
- matchers: M(), M.dict(), M.re(), M.any, M.any_of(), M.unordered(), M.isa(), M.approx()
|
|
13
|
+
- M.approx() supports datetimes
|
|
14
|
+
- path-pinpointing diff output for nested structures
|
|
15
|
+
- fixed nested comparison in newer pytest versions
|
|
16
|
+
|
|
17
|
+
0.5
|
|
18
|
+
- initial version
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: assert-matcher
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: Makes comparing nested data structures easy and glancable
|
|
5
|
+
Home-page: https://github.com/Suor/pytest-matcher
|
|
6
|
+
Author: Alexander Schepanovski
|
|
7
|
+
Author-email: suor.web@gmail.com
|
|
8
|
+
License: BSD-2-Clause
|
|
9
|
+
Keywords: tests
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: Pytest
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
|
+
Classifier: Topic :: Software Development :: Testing
|
|
28
|
+
Classifier: Topic :: Utilities
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Assert Matcher [](https://github.com/Suor/assert-matcher/actions/workflows/test.yml?query=branch%3Amaster)
|
|
35
|
+
|
|
36
|
+
The purpose of this library is to simplify asserts containing objects and nested data structures, i.e.:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
def test_smth(M): # A special Matcher fixture
|
|
40
|
+
# ...
|
|
41
|
+
assert result.errors == [
|
|
42
|
+
M(message=M.re("^Smth went wrong:"), extensions=M.dict(code=523)),
|
|
43
|
+
M(message=M.any, tags=M.unordered("one", "two")),
|
|
44
|
+
]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Here all the structures like lists and dicts are followed as usual both outside and inside
|
|
48
|
+
a matcher object, while the matcher object and other helpers provide their own equality. These could be freely intermixed.
|
|
49
|
+
|
|
50
|
+
Alternatively may import Matcher directly this is more verbose but also more universal, i.e. works outside of pytest:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from assert_matcher import M
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install assert-matcher
|
|
61
|
+
pip install git+https://github.com/Suor/assert-matcher.git@master # the newest one
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Matchers
|
|
66
|
+
|
|
67
|
+
The `M` fixture provides a number of matchers to help you write more expressive assertions.
|
|
68
|
+
|
|
69
|
+
#### `M(**attrs)`
|
|
70
|
+
|
|
71
|
+
Matches an object's attributes.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
User = namedtuple("User", ["name", "email", "is_active"])
|
|
75
|
+
|
|
76
|
+
users = [User("Alice", "alice@example.com", True), User("Bob", "bob@example.com", False)]
|
|
77
|
+
|
|
78
|
+
assert users == [
|
|
79
|
+
M(name="Alice", is_active=True),
|
|
80
|
+
M(name="Bob", is_active=False),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# May test class if that is important
|
|
84
|
+
assert user == M(__class__=User, name="Alice")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `M.re(pattern, flags=0)`
|
|
88
|
+
|
|
89
|
+
Matches a string against a regular expression using `re.search`.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
assert "hello world" == M.re(r"h.*o")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `M.dict(d=None, **kwargs)`
|
|
96
|
+
|
|
97
|
+
Matches a dictionary against a subset of keys. It checks that the keys exist and their values match, but ignores other keys.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
assert {"a": 1, "b": 2} == M.dict(a=1)
|
|
101
|
+
assert {"a": 1, "b": 2} == M.dict({"a": 1}) # same
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### M.any
|
|
105
|
+
|
|
106
|
+
Matches any value. This is useful when you want to ignore a particular field value in a larger data structure, but still want to test for a key presence.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
assert {"a": 1, "b": 2, "c": "hi"} == M.dict(a=1, b=M.any)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `M.any_of(*items)`
|
|
113
|
+
|
|
114
|
+
Matches if the value is one of the provided items.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
assert "a" == M.any_of("a", "b", "c")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### `M.unordered(*items)`
|
|
121
|
+
|
|
122
|
+
Compares a list to the provided items, ignoring the order.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
assert [1, 2, 3] == M.unordered(3, 1, 2)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `M.isa(*types)`
|
|
129
|
+
|
|
130
|
+
Checks if an object is an instance of one of the provided types.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
assert "hello" == M.isa(str)
|
|
134
|
+
assert 42 == M.isa(int, float)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### `M.approx(expected, rel=None, abs=None, nan_ok=False)`
|
|
138
|
+
|
|
139
|
+
Performs approximate comparison of numbers. It's a wrapper around `pytest.approx`.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
assert 0.1 + 0.2 == M.approx(0.3)
|
|
143
|
+
assert end_time == M.approx(start_time + timedelta(seconds=1), abs=0.1)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Running tests
|
|
148
|
+
|
|
149
|
+
To run the tests using your default python:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
pip install -r test_requirements.txt
|
|
153
|
+
pytest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To fully run `tox` you need all the supported pythons to be installed. These are
|
|
157
|
+
3.9+ and PyPy3. You can run it for particular environment even in absense
|
|
158
|
+
of all of the above:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
tox -e py314
|
|
162
|
+
tox -e pypy311
|
|
163
|
+
tox -e lint
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Pytest Plugin
|
|
167
|
+
|
|
168
|
+
`assert-matcher` provides enhanced assertion representation for pytest, making diffs for nested data structures more readable when using `Matcher` objects.
|
|
169
|
+
|
|
170
|
+
When a test fails, pytest usually prints a diff of the two objects that were compared. However, these diffs can be hard to read. `assert-matcher` plugin overrides the default diff representation to pinpoint an exact path and difference.
|
|
171
|
+
|
|
172
|
+
For example, consider the following failing test:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
def test_user(M):
|
|
176
|
+
user = User("Alice", "alice@example.com", True)
|
|
177
|
+
assert user == M(name="Bob", is_active=False)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Without the plugin, the diff would look something like this:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
E AssertionError: assert User(name='Alice', email='alice@example.com', is_active=True) == M(name='Bob', is_active=False)
|
|
184
|
+
E + where M(name='Bob', is_active=False) = <class 'assert_matcher.matchers.Matcher'>(name='Bob', is_active=False)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
With the `assert-matcher` plugin enabled, the diff is much more readable:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
E AssertionError: assert User(name=...tive=True) == M(name='Bo...ive=False)
|
|
191
|
+
E name: 'Alice' != 'Bob'
|
|
192
|
+
E is_active: True != False
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This is even more useful when you compare nested data structures. For example, consider the following test:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
def test_nested_structure(M):
|
|
199
|
+
result = {
|
|
200
|
+
"users": [alice, bob],
|
|
201
|
+
"meta": ...
|
|
202
|
+
}
|
|
203
|
+
expected = {
|
|
204
|
+
"users": [
|
|
205
|
+
M(name="Alice", status="active"),
|
|
206
|
+
M(name="Bob", email=M.re(r"@example\.com$")),
|
|
207
|
+
],
|
|
208
|
+
"meta": M.any
|
|
209
|
+
}
|
|
210
|
+
assert result == expected
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Without the plugin, the diff is hard to read (imagine these objects having 10+ fields):
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
E AssertionError: assert {'meta': {'er... 'inactive'}]} == {'meta': M.di...ample.com$')]}
|
|
217
|
+
E
|
|
218
|
+
E Omitting 1 identical items, use -vv to show
|
|
219
|
+
E Differing items:
|
|
220
|
+
E {'users': [{'email': 'alice@example.com', 'name': 'Alice', 'status': 'active'}, {'email': 'bob@invalid', 'name': 'Bob', 'status': 'inactive'}]} != {'users': [M.dict(name='Alice', status='active'), M.dict(name='Bob', email=r'@example.com$')]}
|
|
221
|
+
E Use -v to get more diff
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
With the plugin, the diff is much more concise and points to the exact location of the error:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
E AssertionError: assert {'meta': {...active'}} == {'meta': M...e.com$')]}
|
|
228
|
+
E users[1].email: 'bob@invalid' != r'@example.com$'
|
|
229
|
+
```
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
VERSION = '0.9.
|
|
1
|
+
VERSION = '0.9.2'
|
|
2
2
|
from .matchers import Matcher, Matcher as M # noqa
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import pprint
|
|
2
2
|
from collections import defaultdict
|
|
3
|
-
from collections.abc import Mapping, Set
|
|
3
|
+
from collections.abc import Mapping, Sequence, Set
|
|
4
4
|
from functools import partial
|
|
5
5
|
from itertools import count
|
|
6
6
|
|
|
7
7
|
from _pytest._io.saferepr import safeformat, saferepr
|
|
8
|
-
|
|
8
|
+
# Unlike util.assertrepr_compare() this keeps its signature across pytest versions
|
|
9
|
+
from _pytest.assertion import pytest_assertrepr_compare as assertrepr_compare
|
|
9
10
|
|
|
10
11
|
from .matchers import Matcher, MatcherDict
|
|
11
12
|
|
|
12
13
|
|
|
14
|
+
def issequence(x):
|
|
15
|
+
return isinstance(x, Sequence) and not isinstance(x, str)
|
|
16
|
+
|
|
17
|
+
|
|
13
18
|
def pytest_assertrepr_compare(config, op: str, left, right):
|
|
14
19
|
import shutil
|
|
15
20
|
import _pytest._code
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: assert-matcher
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: Makes comparing nested data structures easy and glancable
|
|
5
|
+
Home-page: https://github.com/Suor/pytest-matcher
|
|
6
|
+
Author: Alexander Schepanovski
|
|
7
|
+
Author-email: suor.web@gmail.com
|
|
8
|
+
License: BSD-2-Clause
|
|
9
|
+
Keywords: tests
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: Pytest
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
27
|
+
Classifier: Topic :: Software Development :: Testing
|
|
28
|
+
Classifier: Topic :: Utilities
|
|
29
|
+
Requires-Python: >=3.9
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# Assert Matcher [](https://github.com/Suor/assert-matcher/actions/workflows/test.yml?query=branch%3Amaster)
|
|
35
|
+
|
|
36
|
+
The purpose of this library is to simplify asserts containing objects and nested data structures, i.e.:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
def test_smth(M): # A special Matcher fixture
|
|
40
|
+
# ...
|
|
41
|
+
assert result.errors == [
|
|
42
|
+
M(message=M.re("^Smth went wrong:"), extensions=M.dict(code=523)),
|
|
43
|
+
M(message=M.any, tags=M.unordered("one", "two")),
|
|
44
|
+
]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Here all the structures like lists and dicts are followed as usual both outside and inside
|
|
48
|
+
a matcher object, while the matcher object and other helpers provide their own equality. These could be freely intermixed.
|
|
49
|
+
|
|
50
|
+
Alternatively may import Matcher directly this is more verbose but also more universal, i.e. works outside of pytest:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from assert_matcher import M
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install assert-matcher
|
|
61
|
+
pip install git+https://github.com/Suor/assert-matcher.git@master # the newest one
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
## Matchers
|
|
66
|
+
|
|
67
|
+
The `M` fixture provides a number of matchers to help you write more expressive assertions.
|
|
68
|
+
|
|
69
|
+
#### `M(**attrs)`
|
|
70
|
+
|
|
71
|
+
Matches an object's attributes.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
User = namedtuple("User", ["name", "email", "is_active"])
|
|
75
|
+
|
|
76
|
+
users = [User("Alice", "alice@example.com", True), User("Bob", "bob@example.com", False)]
|
|
77
|
+
|
|
78
|
+
assert users == [
|
|
79
|
+
M(name="Alice", is_active=True),
|
|
80
|
+
M(name="Bob", is_active=False),
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
# May test class if that is important
|
|
84
|
+
assert user == M(__class__=User, name="Alice")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `M.re(pattern, flags=0)`
|
|
88
|
+
|
|
89
|
+
Matches a string against a regular expression using `re.search`.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
assert "hello world" == M.re(r"h.*o")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### `M.dict(d=None, **kwargs)`
|
|
96
|
+
|
|
97
|
+
Matches a dictionary against a subset of keys. It checks that the keys exist and their values match, but ignores other keys.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
assert {"a": 1, "b": 2} == M.dict(a=1)
|
|
101
|
+
assert {"a": 1, "b": 2} == M.dict({"a": 1}) # same
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### M.any
|
|
105
|
+
|
|
106
|
+
Matches any value. This is useful when you want to ignore a particular field value in a larger data structure, but still want to test for a key presence.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
assert {"a": 1, "b": 2, "c": "hi"} == M.dict(a=1, b=M.any)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `M.any_of(*items)`
|
|
113
|
+
|
|
114
|
+
Matches if the value is one of the provided items.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
assert "a" == M.any_of("a", "b", "c")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### `M.unordered(*items)`
|
|
121
|
+
|
|
122
|
+
Compares a list to the provided items, ignoring the order.
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
assert [1, 2, 3] == M.unordered(3, 1, 2)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `M.isa(*types)`
|
|
129
|
+
|
|
130
|
+
Checks if an object is an instance of one of the provided types.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
assert "hello" == M.isa(str)
|
|
134
|
+
assert 42 == M.isa(int, float)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### `M.approx(expected, rel=None, abs=None, nan_ok=False)`
|
|
138
|
+
|
|
139
|
+
Performs approximate comparison of numbers. It's a wrapper around `pytest.approx`.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
assert 0.1 + 0.2 == M.approx(0.3)
|
|
143
|
+
assert end_time == M.approx(start_time + timedelta(seconds=1), abs=0.1)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Running tests
|
|
148
|
+
|
|
149
|
+
To run the tests using your default python:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
pip install -r test_requirements.txt
|
|
153
|
+
pytest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To fully run `tox` you need all the supported pythons to be installed. These are
|
|
157
|
+
3.9+ and PyPy3. You can run it for particular environment even in absense
|
|
158
|
+
of all of the above:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
tox -e py314
|
|
162
|
+
tox -e pypy311
|
|
163
|
+
tox -e lint
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Pytest Plugin
|
|
167
|
+
|
|
168
|
+
`assert-matcher` provides enhanced assertion representation for pytest, making diffs for nested data structures more readable when using `Matcher` objects.
|
|
169
|
+
|
|
170
|
+
When a test fails, pytest usually prints a diff of the two objects that were compared. However, these diffs can be hard to read. `assert-matcher` plugin overrides the default diff representation to pinpoint an exact path and difference.
|
|
171
|
+
|
|
172
|
+
For example, consider the following failing test:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
def test_user(M):
|
|
176
|
+
user = User("Alice", "alice@example.com", True)
|
|
177
|
+
assert user == M(name="Bob", is_active=False)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Without the plugin, the diff would look something like this:
|
|
181
|
+
|
|
182
|
+
```
|
|
183
|
+
E AssertionError: assert User(name='Alice', email='alice@example.com', is_active=True) == M(name='Bob', is_active=False)
|
|
184
|
+
E + where M(name='Bob', is_active=False) = <class 'assert_matcher.matchers.Matcher'>(name='Bob', is_active=False)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
With the `assert-matcher` plugin enabled, the diff is much more readable:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
E AssertionError: assert User(name=...tive=True) == M(name='Bo...ive=False)
|
|
191
|
+
E name: 'Alice' != 'Bob'
|
|
192
|
+
E is_active: True != False
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This is even more useful when you compare nested data structures. For example, consider the following test:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
def test_nested_structure(M):
|
|
199
|
+
result = {
|
|
200
|
+
"users": [alice, bob],
|
|
201
|
+
"meta": ...
|
|
202
|
+
}
|
|
203
|
+
expected = {
|
|
204
|
+
"users": [
|
|
205
|
+
M(name="Alice", status="active"),
|
|
206
|
+
M(name="Bob", email=M.re(r"@example\.com$")),
|
|
207
|
+
],
|
|
208
|
+
"meta": M.any
|
|
209
|
+
}
|
|
210
|
+
assert result == expected
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Without the plugin, the diff is hard to read (imagine these objects having 10+ fields):
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
E AssertionError: assert {'meta': {'er... 'inactive'}]} == {'meta': M.di...ample.com$')]}
|
|
217
|
+
E
|
|
218
|
+
E Omitting 1 identical items, use -vv to show
|
|
219
|
+
E Differing items:
|
|
220
|
+
E {'users': [{'email': 'alice@example.com', 'name': 'Alice', 'status': 'active'}, {'email': 'bob@invalid', 'name': 'Bob', 'status': 'inactive'}]} != {'users': [M.dict(name='Alice', status='active'), M.dict(name='Bob', email=r'@example.com$')]}
|
|
221
|
+
E Use -v to get more diff
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
With the plugin, the diff is much more concise and points to the exact location of the error:
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
E AssertionError: assert {'meta': {...active'}} == {'meta': M...e.com$')]}
|
|
228
|
+
E users[1].email: 'bob@invalid' != r'@example.com$'
|
|
229
|
+
```
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
.gitignore
|
|
2
1
|
CHANGELOG
|
|
3
|
-
GEMINI.md
|
|
4
2
|
LICENSE
|
|
5
3
|
MANIFEST.in
|
|
6
4
|
README.md
|
|
7
|
-
publish.sh
|
|
8
5
|
setup.cfg
|
|
9
6
|
setup.py
|
|
10
|
-
test_requirements.txt
|
|
11
|
-
tox.ini
|
|
12
|
-
.github/workflows/ci.yml
|
|
13
7
|
assert_matcher/__init__.py
|
|
14
8
|
assert_matcher/approx.py
|
|
15
9
|
assert_matcher/compare.py
|
|
@@ -3,9 +3,9 @@ name = assert-matcher
|
|
|
3
3
|
version = attr: assert_matcher.VERSION
|
|
4
4
|
author = Alexander Schepanovski
|
|
5
5
|
author_email = suor.web@gmail.com
|
|
6
|
-
url =
|
|
6
|
+
url = https://github.com/Suor/pytest-matcher
|
|
7
7
|
description = Makes comparing nested data structures easy and glancable
|
|
8
|
-
long_description = file: README.
|
|
8
|
+
long_description = file: README.md
|
|
9
9
|
long_description_content_type = text/markdown
|
|
10
10
|
keywords = tests
|
|
11
11
|
license = BSD-2-Clause
|
|
@@ -25,6 +25,7 @@ classifiers =
|
|
|
25
25
|
Programming Language :: Python :: 3.12
|
|
26
26
|
Programming Language :: Python :: 3.13
|
|
27
27
|
Programming Language :: Python :: 3.14
|
|
28
|
+
Programming Language :: Python :: 3.15
|
|
28
29
|
Programming Language :: Python :: Implementation :: CPython
|
|
29
30
|
Programming Language :: Python :: Implementation :: PyPy
|
|
30
31
|
Topic :: Software Development :: Testing
|
|
@@ -7,6 +7,7 @@ _config = Mock()
|
|
|
7
7
|
_config.get_terminal_writer.return_value._highlight = lambda source, lexer="python": source
|
|
8
8
|
_config.get_verbosity.return_value = 0
|
|
9
9
|
_config.getoption.return_value = 0 # older pytest versions
|
|
10
|
+
_config.getini.return_value = "ndiff" # assertion_text_diff_style, pytest 9.1+
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def comp_eq(left, right):
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- master
|
|
7
|
-
pull_request:
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
lint:
|
|
11
|
-
runs-on: ubuntu-22.04
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v3
|
|
14
|
-
- name: Set up Python 3.10
|
|
15
|
-
uses: actions/setup-python@v4
|
|
16
|
-
with:
|
|
17
|
-
python-version: "3.10"
|
|
18
|
-
- name: Lint
|
|
19
|
-
run: |
|
|
20
|
-
pip install flake8
|
|
21
|
-
flake8 assert_matcher
|
|
22
|
-
flake8 tests
|
|
23
|
-
|
|
24
|
-
test:
|
|
25
|
-
runs-on: ubuntu-22.04
|
|
26
|
-
strategy:
|
|
27
|
-
fail-fast: false
|
|
28
|
-
matrix:
|
|
29
|
-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy3.9", "pypy3.10", "pypy3.11"]
|
|
30
|
-
|
|
31
|
-
steps:
|
|
32
|
-
- uses: actions/checkout@v3
|
|
33
|
-
- name: Set up Python ${{ matrix.python-version }}
|
|
34
|
-
uses: actions/setup-python@v4
|
|
35
|
-
with:
|
|
36
|
-
python-version: ${{ matrix.python-version }}
|
|
37
|
-
- name: Install dependencies
|
|
38
|
-
run: |
|
|
39
|
-
python -m pip install --upgrade pip
|
|
40
|
-
pip install -r test_requirements.txt
|
|
41
|
-
pip install -e . # Need to install for pytest plugin to work
|
|
42
|
-
- name: Run tests
|
|
43
|
-
run: pytest -W error
|
assert_matcher-0.9.1/.gitignore
DELETED
assert_matcher-0.9.1/CHANGELOG
DELETED
assert_matcher-0.9.1/GEMINI.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Project Overview
|
|
2
|
-
|
|
3
|
-
This project is a Python library called `assert-matcher`. Its purpose is to simplify asserts containing objects and nested data structures in tests.
|
|
4
|
-
|
|
5
|
-
It will work with any test framework by overloaing equality, it also provides pytest plugin to show nice diffs with its matcher objects.
|
|
6
|
-
|
|
7
|
-
The project is structured as a standard Python package with a `setup.py` file and a `assert_matcher` module.
|
|
8
|
-
|
|
9
|
-
# Building and Running
|
|
10
|
-
|
|
11
|
-
**Installation:**
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
pip install -r test_requirements.txt
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
**Running tests:**
|
|
18
|
-
|
|
19
|
-
To run tests with the default python version:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pytest
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
To run tests for a specific python version with `tox`:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
tox -e py310
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
To run linting checks:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
flake8
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
# Development Conventions
|
|
38
|
-
|
|
39
|
-
* The code is formatted with a maximum line length of 100 characters.
|
|
40
|
-
* The project uses `flake8` for linting.
|
|
41
|
-
* Tests are located in the `tests/` directory.
|
|
42
|
-
* The project uses `tox` to automate testing in different python environments.
|
assert_matcher-0.9.1/MANIFEST.in
DELETED
assert_matcher-0.9.1/PKG-INFO
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: assert-matcher
|
|
3
|
-
Version: 0.9.1
|
|
4
|
-
Summary: Makes comparing nested data structures easy and glancable
|
|
5
|
-
Home-page: http://github.com/Suor/assert-macther
|
|
6
|
-
Author: Alexander Schepanovski
|
|
7
|
-
Author-email: suor.web@gmail.com
|
|
8
|
-
License: BSD-2-Clause
|
|
9
|
-
Keywords: tests
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Framework :: Pytest
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
|
-
Classifier: Topic :: Software Development :: Testing
|
|
27
|
-
Classifier: Topic :: Utilities
|
|
28
|
-
Requires-Python: >=3.9
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Dynamic: license-file
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: assert-matcher
|
|
3
|
-
Version: 0.9.1
|
|
4
|
-
Summary: Makes comparing nested data structures easy and glancable
|
|
5
|
-
Home-page: http://github.com/Suor/assert-macther
|
|
6
|
-
Author: Alexander Schepanovski
|
|
7
|
-
Author-email: suor.web@gmail.com
|
|
8
|
-
License: BSD-2-Clause
|
|
9
|
-
Keywords: tests
|
|
10
|
-
Classifier: Development Status :: 4 - Beta
|
|
11
|
-
Classifier: Framework :: Pytest
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python
|
|
16
|
-
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
-
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
|
-
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
26
|
-
Classifier: Topic :: Software Development :: Testing
|
|
27
|
-
Classifier: Topic :: Utilities
|
|
28
|
-
Requires-Python: >=3.9
|
|
29
|
-
Description-Content-Type: text/markdown
|
|
30
|
-
License-File: LICENSE
|
|
31
|
-
Dynamic: license-file
|
assert_matcher-0.9.1/publish.sh
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/bash
|
|
2
|
-
|
|
3
|
-
set -ex
|
|
4
|
-
|
|
5
|
-
NAME=assert_matcher
|
|
6
|
-
VERSION=`awk '/VERSION = /{gsub(/'\''/, "", $3); print $3}' assert_matcher/__init__.py`
|
|
7
|
-
|
|
8
|
-
echo "Publishing $NAME-$VERSION..."
|
|
9
|
-
python setup.py sdist bdist_wheel
|
|
10
|
-
twine check dist/$NAME-$VERSION*
|
|
11
|
-
twine upload --skip-existing -u__token__ dist/$NAME-$VERSION*
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pytest
|
assert_matcher-0.9.1/tox.ini
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
[tox]
|
|
2
|
-
envlist = py39, py310, py311, py312, py313, py314, pypy39, pypy310, pypy311, lint
|
|
3
|
-
|
|
4
|
-
[testenv]
|
|
5
|
-
deps = -r test_requirements.txt
|
|
6
|
-
commands = pytest -W error {posargs}
|
|
7
|
-
|
|
8
|
-
[flake8]
|
|
9
|
-
max-line-length = 100
|
|
10
|
-
exclude = .tox
|
|
11
|
-
|
|
12
|
-
[testenv:lint]
|
|
13
|
-
basepython = python3.10
|
|
14
|
-
passenv = PYTHONPATH
|
|
15
|
-
deps =
|
|
16
|
-
flake8
|
|
17
|
-
commands =
|
|
18
|
-
python --version
|
|
19
|
-
flake8 assert_matcher
|
|
20
|
-
flake8 tests
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|