0
# Sequence Types
1
2
List, tuple, and general sequence validation with support for partial matching, order constraints, containment checking, and length validation. These types enable flexible validation of sequential data structures with various matching strategies.
3
4
## Capabilities
5
6
### HasLen
7
8
Length validation for any object that supports the `len()` function, with support for exact length, minimum length, or length ranges.
9
10
```python { .api }
11
class HasLen(DirtyEquals):
12
"""
13
Length checking for any object supporting len().
14
15
Validates that an object has a specific length, minimum length,
16
or length within a specified range.
17
"""
18
19
def __init__(self, min_length: int, max_length: Optional[int] = None):
20
"""
21
Initialize length checker (overloaded constructor).
22
23
Can be called as:
24
- HasLen(exact_length) - exact length match
25
- HasLen(min_length, max_length) - length range
26
27
Args:
28
min_length: Minimum length (or exact if max_length is None)
29
max_length: Maximum length (None for exact match)
30
"""
31
32
def equals(self, other: Any) -> bool:
33
"""
34
Check if object length matches constraints.
35
36
Args:
37
other: Object to check length of (must support len())
38
39
Returns:
40
bool: True if length satisfies constraints
41
"""
42
```
43
44
#### Usage Examples
45
46
```python
47
from dirty_equals import HasLen
48
49
# Exact length checking
50
assert [1, 2, 3] == HasLen(3)
51
assert "hello" == HasLen(5)
52
assert {"a": 1, "b": 2} == HasLen(2)
53
54
# Length range checking
55
assert [1, 2, 3, 4] == HasLen(2, 5) # Between 2 and 5 items
56
assert "hello world" == HasLen(5, 15) # Between 5 and 15 characters
57
58
# Minimum length (no maximum)
59
assert [1, 2, 3, 4, 5] == HasLen(3, None) # At least 3 items
60
61
# API response validation
62
api_results = {
63
'users': [{'id': 1}, {'id': 2}, {'id': 3}],
64
'message': 'Success',
65
'errors': []
66
}
67
68
assert api_results == {
69
'users': HasLen(1, 10), # 1-10 users expected
70
'message': HasLen(1, 100), # Non-empty message
71
'errors': HasLen(0) # No errors
72
}
73
74
# Form validation
75
form_data = {
76
'username': 'john_doe',
77
'password': 'secret123',
78
'tags': ['python', 'web', 'api']
79
}
80
81
assert form_data == {
82
'username': HasLen(3, 20), # Username length constraints
83
'password': HasLen(8), # Exact password length
84
'tags': HasLen(1, 5) # 1-5 tags allowed
85
}
86
87
# Database query results
88
query_results = [
89
{'id': 1, 'name': 'Alice'},
90
{'id': 2, 'name': 'Bob'}
91
]
92
93
assert query_results == HasLen(2) # Expecting exactly 2 results
94
95
# File content validation
96
file_lines = ['line 1', 'line 2', 'line 3', 'line 4']
97
assert file_lines == HasLen(1, 100) # File should have content but not be huge
98
```
99
100
### Contains
101
102
Containment checking that validates whether one or more values are present in a collection using Python's `in` operator.
103
104
```python { .api }
105
class Contains(DirtyEquals):
106
"""
107
Containment checking using 'in' operator.
108
109
Validates that all specified values are contained
110
within the target collection.
111
"""
112
113
def __init__(self, contained_value: Any, *more_contained_values: Any):
114
"""
115
Initialize containment checker.
116
117
Args:
118
contained_value: First value that must be contained
119
*more_contained_values: Additional values that must be contained
120
"""
121
122
def equals(self, other: Any) -> bool:
123
"""
124
Check if all specified values are contained in other.
125
126
Args:
127
other: Collection to check containment in
128
129
Returns:
130
bool: True if all values are contained in other
131
"""
132
```
133
134
#### Usage Examples
135
136
```python
137
from dirty_equals import Contains
138
139
# Basic containment checking
140
assert [1, 2, 3, 4, 5] == Contains(3)
141
assert "hello world" == Contains("world")
142
assert {"a": 1, "b": 2, "c": 3} == Contains("b")
143
144
# Multiple values must all be contained
145
assert [1, 2, 3, 4, 5] == Contains(2, 4, 5)
146
assert "hello world" == Contains("hello", "world")
147
assert {"a": 1, "b": 2, "c": 3} == Contains("a", "c")
148
149
# String containment
150
text = "The quick brown fox jumps over the lazy dog"
151
assert text == Contains("quick", "fox", "lazy")
152
assert text == Contains("brown")
153
154
# List containment
155
shopping_list = ['bread', 'milk', 'eggs', 'cheese', 'butter']
156
assert shopping_list == Contains('milk', 'eggs')
157
assert shopping_list == Contains('bread')
158
159
# Dict key containment
160
config = {
161
'database_url': 'postgresql://localhost/db',
162
'secret_key': 'super-secret',
163
'debug': True,
164
'port': 5000
165
}
166
167
assert config == Contains('database_url', 'secret_key')
168
assert config == Contains('debug')
169
170
# API response validation
171
api_response = {
172
'data': [
173
{'id': 1, 'name': 'Alice', 'roles': ['admin', 'user']},
174
{'id': 2, 'name': 'Bob', 'roles': ['user', 'moderator']}
175
]
176
}
177
178
# Check that admin role exists somewhere
179
user_roles = api_response['data'][0]['roles']
180
assert user_roles == Contains('admin')
181
182
# Check for required permissions
183
required_permissions = ['read', 'write', 'delete', 'admin']
184
user_permissions = ['read', 'write', 'admin', 'moderate', 'create']
185
assert user_permissions == Contains('read', 'write', 'admin')
186
187
# Tag validation
188
blog_post = {
189
'title': 'Python Tips',
190
'content': '...',
191
'tags': ['python', 'programming', 'tutorial', 'beginner']
192
}
193
194
# Must contain essential tags
195
assert blog_post['tags'] == Contains('python', 'programming')
196
197
# Search results validation
198
search_results = [
199
'python-guide.pdf',
200
'advanced-python-tricks.txt',
201
'python-best-practices.md',
202
'django-tutorial.html'
203
]
204
205
# Must contain files with 'python' in name
206
filenames_with_python = [f for f in search_results if 'python' in f]
207
assert filenames_with_python == Contains('python-guide.pdf', 'advanced-python-tricks.txt')
208
209
# Set operations
210
available_features = {'auth', 'logging', 'caching', 'monitoring', 'backup'}
211
required_features = {'auth', 'logging'}
212
213
for feature in required_features:
214
assert available_features == Contains(feature)
215
```
216
217
### IsListOrTuple
218
219
Base class for list and tuple validation with flexible matching strategies including item validation, position constraints, order checking, and length validation.
220
221
```python { .api }
222
class IsListOrTuple(DirtyEquals):
223
"""
224
List/tuple comparison with flexible matching constraints.
225
226
Supports item validation, position-specific checks, order enforcement,
227
and length constraints for both lists and tuples.
228
"""
229
230
def __init__(
231
self,
232
*items: Any,
233
positions: Optional[Dict[int, Any]] = None,
234
check_order: bool = True,
235
length: Optional[int] = None
236
):
237
"""
238
Initialize list/tuple validator.
239
240
Args:
241
*items: Expected items (order matters if check_order=True)
242
positions: Dict mapping positions to expected values
243
check_order: Whether to enforce item order
244
length: Expected exact length
245
"""
246
247
allowed_type: ClassVar[Tuple[type, ...]] = (list, tuple)
248
249
def equals(self, other: Any) -> bool:
250
"""
251
Check if sequence matches constraints.
252
253
Args:
254
other: List or tuple to validate
255
256
Returns:
257
bool: True if sequence satisfies all constraints
258
"""
259
```
260
261
#### Usage Examples
262
263
```python
264
from dirty_equals import IsListOrTuple, IsPositive, IsStr
265
266
# Basic sequence matching - exact order
267
assert [1, 2, 3] == IsListOrTuple(1, 2, 3)
268
assert (1, 2, 3) == IsListOrTuple(1, 2, 3)
269
270
# With validators
271
assert ['hello', 42, True] == IsListOrTuple(IsStr, IsPositive, bool)
272
273
# Position-specific validation
274
data = [10, 'name', 3.14, True, 'end']
275
assert data == IsListOrTuple(
276
positions={
277
0: IsPositive, # First item must be positive
278
1: IsStr, # Second item must be string
279
2: float, # Third item must be float
280
4: 'end' # Last item must be 'end'
281
}
282
)
283
284
# Ignore order - just check that items exist somewhere
285
unordered_list = [3, 1, 2]
286
assert unordered_list == IsListOrTuple(1, 2, 3, check_order=False)
287
288
# Length validation
289
assert [1, 2, 3, 4, 5] == IsListOrTuple(length=5)
290
assert ('a', 'b') == IsListOrTuple(length=2)
291
292
# Combined constraints
293
api_response = ['success', 200, {'data': 'result'}, True]
294
assert api_response == IsListOrTuple(
295
'success', # First item exact match
296
IsPositive, # Second item positive number
297
dict, # Third item is dict
298
bool, # Fourth item is boolean
299
length=4 # Exactly 4 items
300
)
301
302
# Flexible API result validation
303
results = [
304
{'id': 1, 'name': 'Alice'},
305
{'id': 2, 'name': 'Bob'},
306
{'id': 3, 'name': 'Charlie'}
307
]
308
309
# Check specific positions and overall structure
310
assert results == IsListOrTuple(
311
positions={
312
0: {'id': 1, 'name': IsStr}, # First user
313
2: {'id': IsPositive, 'name': 'Charlie'} # Last user
314
},
315
length=3
316
)
317
318
# Configuration tuple validation
319
config_tuple = ('production', 8080, True, '/var/log/app.log')
320
assert config_tuple == IsListOrTuple(
321
IsStr, # Environment name
322
IsPositive, # Port number
323
bool, # Debug flag
324
IsStr, # Log file path
325
check_order=True,
326
length=4
327
)
328
```
329
330
### IsList
331
332
List-specific validation that inherits all functionality from IsListOrTuple but restricts validation to list objects only.
333
334
```python { .api }
335
class IsList(IsListOrTuple):
336
"""
337
List-specific comparison with constraints.
338
339
Inherits all functionality from IsListOrTuple but only
340
accepts list objects, not tuples.
341
"""
342
343
allowed_type: ClassVar[type] = list
344
```
345
346
#### Usage Examples
347
348
```python
349
from dirty_equals import IsList, IsPositive, IsStr
350
351
# Basic list validation
352
assert [1, 2, 3] == IsList(1, 2, 3)
353
assert ['a', 'b', 'c'] == IsList('a', 'b', 'c')
354
355
# Tuples won't match
356
# assert (1, 2, 3) == IsList(1, 2, 3) # Would fail
357
358
# API response list validation
359
user_list = [
360
{'id': 1, 'name': 'Alice', 'active': True},
361
{'id': 2, 'name': 'Bob', 'active': False}
362
]
363
364
assert user_list == IsList(
365
{'id': IsPositive, 'name': IsStr, 'active': bool},
366
{'id': IsPositive, 'name': IsStr, 'active': bool}
367
)
368
369
# Dynamic list validation
370
numbers = [1, 2, 3, 4, 5]
371
assert numbers == IsList(*range(1, 6)) # Unpack range
372
373
# Shopping cart validation
374
cart_items = [
375
{'product_id': 101, 'quantity': 2, 'price': 29.99},
376
{'product_id': 202, 'quantity': 1, 'price': 15.50},
377
{'product_id': 303, 'quantity': 3, 'price': 8.75}
378
]
379
380
cart_item_schema = {
381
'product_id': IsPositive,
382
'quantity': IsPositive,
383
'price': IsPositive
384
}
385
386
assert cart_items == IsList(
387
cart_item_schema,
388
cart_item_schema,
389
cart_item_schema,
390
length=3
391
)
392
393
# Log entries validation
394
log_entries = [
395
'INFO: Application started',
396
'DEBUG: Loading configuration',
397
'ERROR: Database connection failed'
398
]
399
400
assert log_entries == IsList(
401
positions={
402
0: IsStr, # Any string for first entry
403
2: Contains('ERROR') # Last entry must contain ERROR
404
},
405
length=3
406
)
407
408
# Nested list validation
409
matrix = [
410
[1, 2, 3],
411
[4, 5, 6],
412
[7, 8, 9]
413
]
414
415
row_pattern = IsList(IsPositive, IsPositive, IsPositive)
416
assert matrix == IsList(row_pattern, row_pattern, row_pattern)
417
418
# File processing results
419
processed_files = [
420
'document1.txt',
421
'image2.jpg',
422
'data3.csv',
423
'config4.json'
424
]
425
426
assert processed_files == IsList(
427
Contains('.txt'),
428
Contains('.jpg'),
429
Contains('.csv'),
430
Contains('.json'),
431
check_order=False # Files might be processed in any order
432
)
433
```
434
435
### IsTuple
436
437
Tuple-specific validation that inherits all functionality from IsListOrTuple but restricts validation to tuple objects only.
438
439
```python { .api }
440
class IsTuple(IsListOrTuple):
441
"""
442
Tuple-specific comparison with constraints.
443
444
Inherits all functionality from IsListOrTuple but only
445
accepts tuple objects, not lists.
446
"""
447
448
allowed_type: ClassVar[type] = tuple
449
```
450
451
#### Usage Examples
452
453
```python
454
from dirty_equals import IsTuple, IsPositive, IsStr
455
456
# Basic tuple validation
457
assert (1, 2, 3) == IsTuple(1, 2, 3)
458
assert ('a', 'b', 'c') == IsTuple('a', 'b', 'c')
459
460
# Lists won't match
461
# assert [1, 2, 3] == IsTuple(1, 2, 3) # Would fail
462
463
# Named tuple-like validation
464
person_tuple = ('John Doe', 25, 'Engineer', True)
465
assert person_tuple == IsTuple(
466
IsStr, # Name
467
IsPositive, # Age
468
IsStr, # Job title
469
bool # Active status
470
)
471
472
# Coordinate validation
473
point_2d = (3.14, 2.71)
474
point_3d = (1.0, 2.0, 3.0)
475
476
assert point_2d == IsTuple(float, float)
477
assert point_3d == IsTuple(float, float, float)
478
479
# Database record as tuple
480
db_record = (123, 'Alice Johnson', 'alice@example.com', '2023-01-15')
481
assert db_record == IsTuple(
482
IsPositive, # ID
483
IsStr, # Name
484
Contains('@'), # Email
485
IsStr, # Date string
486
length=4
487
)
488
489
# Function return value validation
490
def get_user_info(user_id):
491
return (user_id, 'John Doe', 'john@example.com', True)
492
493
result = get_user_info(123)
494
assert result == IsTuple(
495
123, # Expected user ID
496
IsStr, # Name
497
IsStr, # Email
498
True # Active status
499
)
500
501
# Configuration tuple with position validation
502
config = ('production', 443, True, 30, '/var/log/')
503
assert config == IsTuple(
504
positions={
505
0: 'production', # Environment
506
1: IsPositive, # Port
507
2: True, # SSL enabled
508
3: IsPositive, # Timeout
509
4: Contains('/') # Log directory
510
},
511
length=5
512
)
513
514
# API response tuple format
515
api_result = ('success', 200, {'user_id': 123}, None)
516
assert api_result == IsTuple(
517
'success', # Status
518
200, # HTTP code
519
dict, # Data payload
520
None, # Error (should be None for success)
521
check_order=True,
522
length=4
523
)
524
525
# Mathematical vector validation
526
vector = (1.0, 0.0, -1.0, 2.5)
527
assert vector == IsTuple(
528
float, float, float, float, # All components must be floats
529
length=4
530
)
531
532
# RGB color tuple
533
rgb_color = (255, 128, 0)
534
assert rgb_color == IsTuple(
535
positions={
536
0: IsRange(0, 255), # Red component
537
1: IsRange(0, 255), # Green component
538
2: IsRange(0, 255) # Blue component
539
},
540
length=3
541
)
542
```
543
544
## Type Definitions
545
546
```python { .api }
547
from typing import Any, ClassVar, Dict, Optional, Tuple, Union
548
549
# All sequence types inherit from DirtyEquals
550
# IsListOrTuple is the base class for IsList and IsTuple
551
# They work with Python's standard list and tuple types
552
```