0
# Dictionary Types
1
2
Dictionary comparison with flexible matching strategies including partial matching, strict ordering, and key filtering. These types enable sophisticated validation of dictionary structures with configurable constraints for different use cases.
3
4
## Capabilities
5
6
### IsDict
7
8
Configurable dictionary comparison that serves as the base for all dictionary validation. Supports multiple matching modes through the settings method and direct instantiation with expected key-value pairs.
9
10
```python { .api }
11
class IsDict(DirtyEquals):
12
"""
13
Flexible dictionary comparison with configurable matching behavior.
14
15
Supports exact matching, partial matching, key filtering, and order enforcement
16
through various configuration options.
17
"""
18
19
def __init__(self, *expected_args, **expected_kwargs):
20
"""
21
Initialize dictionary comparison (overloaded constructor).
22
23
Can be called in multiple ways:
24
- IsDict() - matches any dictionary
25
- IsDict(expected_dict) - matches specific dictionary
26
- IsDict(key1=value1, key2=value2) - matches with keyword arguments
27
- IsDict(expected_dict, key3=value3) - combines dict and kwargs
28
29
Args:
30
*expected_args: Expected dictionary as positional argument
31
**expected_kwargs: Expected key-value pairs as keyword arguments
32
"""
33
34
def settings(
35
self,
36
*,
37
strict: Optional[bool] = None,
38
partial: Optional[bool] = None,
39
ignore: Optional[Sequence[str]] = None
40
) -> 'IsDict':
41
"""
42
Configure dictionary matching behavior.
43
44
Args:
45
strict: If True, enforce key order and exact matching
46
partial: If True, allow subset matching (only check specified keys)
47
ignore: List of keys to ignore during comparison
48
49
Returns:
50
IsDict: New instance with updated settings
51
"""
52
53
@property
54
def expected_values(self) -> Dict[str, Any]:
55
"""Get expected key-value pairs."""
56
57
@property
58
def strict(self) -> bool:
59
"""Whether strict mode (order enforcement) is enabled."""
60
61
@property
62
def partial(self) -> bool:
63
"""Whether partial mode (subset matching) is enabled."""
64
65
@property
66
def ignore(self) -> Optional[Sequence[str]]:
67
"""Keys to ignore during comparison."""
68
69
def equals(self, other: Any) -> bool:
70
"""
71
Check if value matches dictionary constraints.
72
73
Args:
74
other: Value to validate (should be dict-like)
75
76
Returns:
77
bool: True if value satisfies all conditions
78
"""
79
```
80
81
#### Usage Examples
82
83
```python
84
from dirty_equals import IsDict, IsPositive, IsStr
85
86
# Basic dictionary matching
87
test_dict = {'a': 1, 'b': 2}
88
assert test_dict == IsDict({'a': 1, 'b': 2})
89
assert test_dict == IsDict(a=1, b=2)
90
91
# Match any dictionary
92
assert test_dict == IsDict()
93
assert {'x': 'y'} == IsDict()
94
95
# Combine positional and keyword arguments
96
base_dict = {'a': 1}
97
assert {'a': 1, 'b': 2} == IsDict(base_dict, b=2)
98
99
# With value validators
100
user_data = {
101
'id': 123,
102
'name': 'John',
103
'email': 'john@example.com',
104
'active': True
105
}
106
107
assert user_data == IsDict({
108
'id': IsPositive,
109
'name': IsStr(min_length=1),
110
'email': IsStr(regex=r'.+@.+\..+'),
111
'active': True
112
})
113
114
# Using settings for flexible matching
115
config = {'debug': True, 'port': 8080, 'host': 'localhost'}
116
117
# Partial matching - only check specified keys
118
assert config == IsDict({'debug': True}).settings(partial=True)
119
assert config == IsDict(debug=True).settings(partial=True)
120
121
# Ignore specific keys
122
assert config == IsDict({
123
'debug': True,
124
'port': 8080
125
}).settings(ignore=['host'])
126
127
# Strict mode - enforce order (for ordered dicts)
128
from collections import OrderedDict
129
ordered = OrderedDict([('a', 1), ('b', 2)])
130
assert ordered == IsDict(OrderedDict([('a', 1), ('b', 2)])).settings(strict=True)
131
132
# Chaining configurations
133
partial_ignored = IsDict(debug=True).settings(partial=True, ignore=['timestamp'])
134
test_config = {'debug': True, 'env': 'test', 'timestamp': '2023-01-01'}
135
assert test_config == partial_ignored
136
```
137
138
### IsPartialDict
139
140
Specialized dictionary comparison that only validates specified keys, ignoring any additional keys in the actual dictionary. Equivalent to `IsDict(...).settings(partial=True)`.
141
142
```python { .api }
143
class IsPartialDict(IsDict):
144
"""
145
Partial dictionary matching - validates only specified keys.
146
147
Checks that the target dictionary contains at least the specified keys
148
with matching values, but allows additional keys to be present.
149
"""
150
151
def __init__(self, *expected_args, **expected_kwargs):
152
"""
153
Initialize partial dictionary comparison.
154
155
Args:
156
*expected_args: Expected dictionary as positional argument
157
**expected_kwargs: Expected key-value pairs as keyword arguments
158
"""
159
```
160
161
#### Usage Examples
162
163
```python
164
from dirty_equals import IsPartialDict, IsPositive
165
166
# Basic partial matching
167
full_dict = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
168
assert full_dict == IsPartialDict({'a': 1, 'b': 2})
169
assert full_dict == IsPartialDict(a=1, b=2)
170
171
# API response validation - ignore extra fields
172
api_response = {
173
'user_id': 123,
174
'username': 'john_doe',
175
'email': 'john@example.com',
176
'created_at': '2023-01-01T00:00:00Z',
177
'last_login': '2023-01-15T12:30:00Z',
178
'profile_picture': 'https://example.com/pic.jpg',
179
'preferences': {'theme': 'dark', 'language': 'en'}
180
}
181
182
# Only validate essential fields
183
assert api_response == IsPartialDict({
184
'user_id': IsPositive,
185
'username': 'john_doe',
186
'email': IsStr
187
})
188
189
# Database record validation - focus on updated fields
190
updated_record = {
191
'id': 456,
192
'name': 'Updated Name',
193
'email': 'updated@example.com',
194
'updated_at': '2023-01-15T12:00:00Z',
195
'version': 2,
196
# ... many other fields we don't care about for this test
197
}
198
199
assert updated_record == IsPartialDict({
200
'name': 'Updated Name',
201
'email': 'updated@example.com'
202
})
203
204
# Nested partial matching
205
complex_data = {
206
'user': {
207
'id': 123,
208
'profile': {'name': 'John', 'age': 30, 'city': 'NYC'},
209
'settings': {'notifications': True}
210
},
211
'metadata': {'version': 1, 'source': 'api'}
212
}
213
214
assert complex_data == IsPartialDict({
215
'user': IsPartialDict({
216
'id': 123,
217
'profile': IsPartialDict({'name': 'John'})
218
})
219
})
220
```
221
222
### IsIgnoreDict
223
224
Dictionary comparison that ignores keys with `None` values during matching. Useful when dealing with APIs or databases that include null fields that should be excluded from validation.
225
226
```python { .api }
227
class IsIgnoreDict(IsDict):
228
"""
229
Dictionary comparison ignoring None values.
230
231
Performs dictionary matching but excludes any key-value pairs
232
where the value is None from the comparison.
233
"""
234
235
def __init__(self, *expected_args, **expected_kwargs):
236
"""
237
Initialize dictionary comparison that ignores None values.
238
239
Args:
240
*expected_args: Expected dictionary as positional argument
241
**expected_kwargs: Expected key-value pairs as keyword arguments
242
"""
243
```
244
245
#### Usage Examples
246
247
```python
248
from dirty_equals import IsIgnoreDict, IsStr
249
250
# Basic None value ignoring
251
test_dict = {'a': 1, 'b': None, 'c': 'hello'}
252
expected = {'a': 1, 'c': 'hello'}
253
assert test_dict == IsIgnoreDict(expected)
254
255
# API response with null fields
256
api_data = {
257
'user_id': 123,
258
'username': 'john_doe',
259
'first_name': 'John',
260
'last_name': None, # Not provided
261
'middle_name': None, # Not provided
262
'email': 'john@example.com',
263
'phone': None # Not provided
264
}
265
266
# Validate ignoring null fields
267
assert api_data == IsIgnoreDict({
268
'user_id': 123,
269
'username': 'john_doe',
270
'first_name': 'John',
271
'email': IsStr
272
})
273
274
# Database record with optional fields
275
db_record = {
276
'id': 456,
277
'title': 'Test Article',
278
'content': 'Article content...',
279
'author_id': 123,
280
'editor_id': None, # No editor assigned
281
'published_at': '2023-01-01T00:00:00Z',
282
'featured_image': None, # No image
283
'tags': ['tech', 'python']
284
}
285
286
assert db_record == IsIgnoreDict({
287
'id': 456,
288
'title': 'Test Article',
289
'content': IsStr,
290
'author_id': 123,
291
'published_at': IsStr,
292
'tags': ['tech', 'python']
293
})
294
295
# Form data processing
296
form_input = {
297
'name': 'John Doe',
298
'email': 'john@example.com',
299
'company': 'Acme Corp',
300
'phone': None, # Optional field not filled
301
'address': None, # Optional field not filled
302
'newsletter': True
303
}
304
305
# Validate required fields only
306
assert form_input == IsIgnoreDict({
307
'name': IsStr,
308
'email': IsStr,
309
'company': IsStr,
310
'newsletter': True
311
})
312
```
313
314
### IsStrictDict
315
316
Dictionary comparison with strict ordering enforcement. Validates both the key-value pairs and their order, useful for ordered dictionaries or when insertion order matters.
317
318
```python { .api }
319
class IsStrictDict(IsDict):
320
"""
321
Strict dictionary comparison with order enforcement.
322
323
Validates dictionary contents and enforces that keys appear
324
in the same order as specified in the expected dictionary.
325
"""
326
327
def __init__(self, *expected_args, **expected_kwargs):
328
"""
329
Initialize strict dictionary comparison.
330
331
Args:
332
*expected_args: Expected dictionary as positional argument
333
**expected_kwargs: Expected key-value pairs as keyword arguments
334
"""
335
```
336
337
#### Usage Examples
338
339
```python
340
from dirty_equals import IsStrictDict
341
from collections import OrderedDict
342
343
# Order-sensitive dictionary matching
344
ordered_dict = OrderedDict([('first', 1), ('second', 2), ('third', 3)])
345
assert ordered_dict == IsStrictDict(OrderedDict([('first', 1), ('second', 2), ('third', 3)]))
346
347
# Regular dict with insertion order (Python 3.7+)
348
test_dict = {'a': 1, 'b': 2, 'c': 3}
349
assert test_dict == IsStrictDict({'a': 1, 'b': 2, 'c': 3})
350
351
# This would fail due to different order:
352
# different_order = {'b': 2, 'a': 1, 'c': 3}
353
# assert different_order == IsStrictDict({'a': 1, 'b': 2, 'c': 3}) # False
354
355
# Configuration validation where order matters
356
config_sections = {
357
'database': {'host': 'localhost', 'port': 5432},
358
'cache': {'host': 'redis', 'port': 6379},
359
'logging': {'level': 'INFO', 'file': 'app.log'}
360
}
361
362
# Ensure sections appear in specific order
363
expected_order = {
364
'database': IsStrictDict({'host': 'localhost', 'port': 5432}),
365
'cache': IsStrictDict({'host': 'redis', 'port': 6379}),
366
'logging': IsStrictDict({'level': 'INFO', 'file': 'app.log'})
367
}
368
assert config_sections == IsStrictDict(expected_order)
369
370
# API response with ordered fields
371
api_response = OrderedDict([
372
('status', 'success'),
373
('data', {'id': 123, 'name': 'John'}),
374
('metadata', {'timestamp': '2023-01-01T00:00:00Z'})
375
])
376
377
assert api_response == IsStrictDict({
378
'status': 'success',
379
'data': {'id': 123, 'name': 'John'},
380
'metadata': {'timestamp': IsStr}
381
})
382
383
# JSON object with preserved field order
384
import json
385
json_string = '{"id": 123, "name": "John", "email": "john@example.com"}'
386
parsed = json.loads(json_string, object_pairs_hook=OrderedDict)
387
388
assert parsed == IsStrictDict(OrderedDict([
389
('id', 123),
390
('name', 'John'),
391
('email', IsStr)
392
]))
393
```
394
395
## Type Definitions
396
397
```python { .api }
398
from typing import Any, Dict, Optional, Sequence, Union
399
400
# All dictionary types inherit from IsDict which inherits from DirtyEquals
401
# They work with standard Python dict objects and dict-like objects
402
```