CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-dirty-equals

Python library that leverages the __eq__ method to make unit tests more declarative and readable through flexible comparison classes.

Pending
Overview
Eval results
Files

sequence-types.mddocs/

Sequence Types

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.

Capabilities

HasLen

Length validation for any object that supports the len() function, with support for exact length, minimum length, or length ranges.

class HasLen(DirtyEquals):
    """
    Length checking for any object supporting len().
    
    Validates that an object has a specific length, minimum length,
    or length within a specified range.
    """
    
    def __init__(self, min_length: int, max_length: Optional[int] = None):
        """
        Initialize length checker (overloaded constructor).
        
        Can be called as:
        - HasLen(exact_length) - exact length match
        - HasLen(min_length, max_length) - length range
        
        Args:
            min_length: Minimum length (or exact if max_length is None)
            max_length: Maximum length (None for exact match)
        """
    
    def equals(self, other: Any) -> bool:
        """
        Check if object length matches constraints.
        
        Args:
            other: Object to check length of (must support len())
            
        Returns:
            bool: True if length satisfies constraints
        """

Usage Examples

from dirty_equals import HasLen

# Exact length checking
assert [1, 2, 3] == HasLen(3)
assert "hello" == HasLen(5)
assert {"a": 1, "b": 2} == HasLen(2)

# Length range checking  
assert [1, 2, 3, 4] == HasLen(2, 5)  # Between 2 and 5 items
assert "hello world" == HasLen(5, 15)  # Between 5 and 15 characters

# Minimum length (no maximum)
assert [1, 2, 3, 4, 5] == HasLen(3, None)  # At least 3 items

# API response validation
api_results = {
    'users': [{'id': 1}, {'id': 2}, {'id': 3}],
    'message': 'Success',
    'errors': []
}

assert api_results == {
    'users': HasLen(1, 10),    # 1-10 users expected
    'message': HasLen(1, 100), # Non-empty message
    'errors': HasLen(0)        # No errors
}

# Form validation
form_data = {
    'username': 'john_doe',
    'password': 'secret123',
    'tags': ['python', 'web', 'api']
}

assert form_data == {
    'username': HasLen(3, 20),  # Username length constraints
    'password': HasLen(8),      # Exact password length
    'tags': HasLen(1, 5)        # 1-5 tags allowed
}

# Database query results
query_results = [
    {'id': 1, 'name': 'Alice'},
    {'id': 2, 'name': 'Bob'}
]

assert query_results == HasLen(2)  # Expecting exactly 2 results

# File content validation
file_lines = ['line 1', 'line 2', 'line 3', 'line 4']
assert file_lines == HasLen(1, 100)  # File should have content but not be huge

Contains

Containment checking that validates whether one or more values are present in a collection using Python's in operator.

class Contains(DirtyEquals):
    """
    Containment checking using 'in' operator.
    
    Validates that all specified values are contained
    within the target collection.
    """
    
    def __init__(self, contained_value: Any, *more_contained_values: Any):
        """
        Initialize containment checker.
        
        Args:
            contained_value: First value that must be contained
            *more_contained_values: Additional values that must be contained
        """
    
    def equals(self, other: Any) -> bool:
        """
        Check if all specified values are contained in other.
        
        Args:
            other: Collection to check containment in
            
        Returns:
            bool: True if all values are contained in other
        """

Usage Examples

from dirty_equals import Contains

# Basic containment checking
assert [1, 2, 3, 4, 5] == Contains(3)
assert "hello world" == Contains("world")
assert {"a": 1, "b": 2, "c": 3} == Contains("b")

# Multiple values must all be contained
assert [1, 2, 3, 4, 5] == Contains(2, 4, 5)
assert "hello world" == Contains("hello", "world")
assert {"a": 1, "b": 2, "c": 3} == Contains("a", "c")

# String containment
text = "The quick brown fox jumps over the lazy dog"
assert text == Contains("quick", "fox", "lazy")
assert text == Contains("brown")

# List containment
shopping_list = ['bread', 'milk', 'eggs', 'cheese', 'butter']
assert shopping_list == Contains('milk', 'eggs')
assert shopping_list == Contains('bread')

# Dict key containment  
config = {
    'database_url': 'postgresql://localhost/db',
    'secret_key': 'super-secret',
    'debug': True,
    'port': 5000
}

assert config == Contains('database_url', 'secret_key')
assert config == Contains('debug')

# API response validation
api_response = {
    'data': [
        {'id': 1, 'name': 'Alice', 'roles': ['admin', 'user']},
        {'id': 2, 'name': 'Bob', 'roles': ['user', 'moderator']}
    ]
}

# Check that admin role exists somewhere
user_roles = api_response['data'][0]['roles']
assert user_roles == Contains('admin')

# Check for required permissions
required_permissions = ['read', 'write', 'delete', 'admin']
user_permissions = ['read', 'write', 'admin', 'moderate', 'create']
assert user_permissions == Contains('read', 'write', 'admin')

# Tag validation
blog_post = {
    'title': 'Python Tips',
    'content': '...',
    'tags': ['python', 'programming', 'tutorial', 'beginner']
}

# Must contain essential tags
assert blog_post['tags'] == Contains('python', 'programming')

# Search results validation
search_results = [
    'python-guide.pdf',
    'advanced-python-tricks.txt', 
    'python-best-practices.md',
    'django-tutorial.html'
]

# Must contain files with 'python' in name
filenames_with_python = [f for f in search_results if 'python' in f]
assert filenames_with_python == Contains('python-guide.pdf', 'advanced-python-tricks.txt')

# Set operations
available_features = {'auth', 'logging', 'caching', 'monitoring', 'backup'}
required_features = {'auth', 'logging'}

for feature in required_features:
    assert available_features == Contains(feature)

IsListOrTuple

Base class for list and tuple validation with flexible matching strategies including item validation, position constraints, order checking, and length validation.

class IsListOrTuple(DirtyEquals):
    """
    List/tuple comparison with flexible matching constraints.
    
    Supports item validation, position-specific checks, order enforcement,
    and length constraints for both lists and tuples.
    """
    
    def __init__(
        self,
        *items: Any,
        positions: Optional[Dict[int, Any]] = None,
        check_order: bool = True,
        length: Optional[int] = None
    ):
        """
        Initialize list/tuple validator.
        
        Args:
            *items: Expected items (order matters if check_order=True)
            positions: Dict mapping positions to expected values
            check_order: Whether to enforce item order
            length: Expected exact length
        """
    
    allowed_type: ClassVar[Tuple[type, ...]] = (list, tuple)
    
    def equals(self, other: Any) -> bool:
        """
        Check if sequence matches constraints.
        
        Args:
            other: List or tuple to validate
            
        Returns:
            bool: True if sequence satisfies all constraints
        """

Usage Examples

from dirty_equals import IsListOrTuple, IsPositive, IsStr

# Basic sequence matching - exact order
assert [1, 2, 3] == IsListOrTuple(1, 2, 3)
assert (1, 2, 3) == IsListOrTuple(1, 2, 3)

# With validators
assert ['hello', 42, True] == IsListOrTuple(IsStr, IsPositive, bool)

# Position-specific validation
data = [10, 'name', 3.14, True, 'end']
assert data == IsListOrTuple(
    positions={
        0: IsPositive,   # First item must be positive
        1: IsStr,        # Second item must be string
        2: float,        # Third item must be float
        4: 'end'         # Last item must be 'end'
    }
)

# Ignore order - just check that items exist somewhere
unordered_list = [3, 1, 2]
assert unordered_list == IsListOrTuple(1, 2, 3, check_order=False)

# Length validation
assert [1, 2, 3, 4, 5] == IsListOrTuple(length=5)
assert ('a', 'b') == IsListOrTuple(length=2)

# Combined constraints
api_response = ['success', 200, {'data': 'result'}, True]
assert api_response == IsListOrTuple(
    'success',           # First item exact match
    IsPositive,         # Second item positive number  
    dict,               # Third item is dict
    bool,               # Fourth item is boolean
    length=4            # Exactly 4 items
)

# Flexible API result validation
results = [
    {'id': 1, 'name': 'Alice'},
    {'id': 2, 'name': 'Bob'},
    {'id': 3, 'name': 'Charlie'}
]

# Check specific positions and overall structure
assert results == IsListOrTuple(
    positions={
        0: {'id': 1, 'name': IsStr},  # First user
        2: {'id': IsPositive, 'name': 'Charlie'}  # Last user
    },
    length=3
)

# Configuration tuple validation
config_tuple = ('production', 8080, True, '/var/log/app.log')
assert config_tuple == IsListOrTuple(
    IsStr,              # Environment name
    IsPositive,         # Port number
    bool,               # Debug flag
    IsStr,              # Log file path
    check_order=True,
    length=4
)

IsList

List-specific validation that inherits all functionality from IsListOrTuple but restricts validation to list objects only.

class IsList(IsListOrTuple):
    """
    List-specific comparison with constraints.
    
    Inherits all functionality from IsListOrTuple but only
    accepts list objects, not tuples.
    """
    
    allowed_type: ClassVar[type] = list

Usage Examples

from dirty_equals import IsList, IsPositive, IsStr

# Basic list validation
assert [1, 2, 3] == IsList(1, 2, 3)
assert ['a', 'b', 'c'] == IsList('a', 'b', 'c')

# Tuples won't match
# assert (1, 2, 3) == IsList(1, 2, 3)  # Would fail

# API response list validation
user_list = [
    {'id': 1, 'name': 'Alice', 'active': True},
    {'id': 2, 'name': 'Bob', 'active': False}
]

assert user_list == IsList(
    {'id': IsPositive, 'name': IsStr, 'active': bool},
    {'id': IsPositive, 'name': IsStr, 'active': bool}
)

# Dynamic list validation
numbers = [1, 2, 3, 4, 5]
assert numbers == IsList(*range(1, 6))  # Unpack range

# Shopping cart validation
cart_items = [
    {'product_id': 101, 'quantity': 2, 'price': 29.99},
    {'product_id': 202, 'quantity': 1, 'price': 15.50},
    {'product_id': 303, 'quantity': 3, 'price': 8.75}
]

cart_item_schema = {
    'product_id': IsPositive,
    'quantity': IsPositive, 
    'price': IsPositive
}

assert cart_items == IsList(
    cart_item_schema,
    cart_item_schema, 
    cart_item_schema,
    length=3
)

# Log entries validation
log_entries = [
    'INFO: Application started',
    'DEBUG: Loading configuration',
    'ERROR: Database connection failed'
]

assert log_entries == IsList(
    positions={
        0: IsStr,  # Any string for first entry
        2: Contains('ERROR')  # Last entry must contain ERROR
    },
    length=3
)

# Nested list validation
matrix = [
    [1, 2, 3],
    [4, 5, 6], 
    [7, 8, 9]
]

row_pattern = IsList(IsPositive, IsPositive, IsPositive)
assert matrix == IsList(row_pattern, row_pattern, row_pattern)

# File processing results
processed_files = [
    'document1.txt',
    'image2.jpg',
    'data3.csv',
    'config4.json'
]

assert processed_files == IsList(
    Contains('.txt'),
    Contains('.jpg'),
    Contains('.csv'), 
    Contains('.json'),
    check_order=False  # Files might be processed in any order
)

IsTuple

Tuple-specific validation that inherits all functionality from IsListOrTuple but restricts validation to tuple objects only.

class IsTuple(IsListOrTuple):
    """
    Tuple-specific comparison with constraints.
    
    Inherits all functionality from IsListOrTuple but only
    accepts tuple objects, not lists.
    """
    
    allowed_type: ClassVar[type] = tuple

Usage Examples

from dirty_equals import IsTuple, IsPositive, IsStr

# Basic tuple validation
assert (1, 2, 3) == IsTuple(1, 2, 3)
assert ('a', 'b', 'c') == IsTuple('a', 'b', 'c')

# Lists won't match
# assert [1, 2, 3] == IsTuple(1, 2, 3)  # Would fail

# Named tuple-like validation
person_tuple = ('John Doe', 25, 'Engineer', True)
assert person_tuple == IsTuple(
    IsStr,         # Name
    IsPositive,    # Age
    IsStr,         # Job title
    bool           # Active status
)

# Coordinate validation
point_2d = (3.14, 2.71)
point_3d = (1.0, 2.0, 3.0)

assert point_2d == IsTuple(float, float)
assert point_3d == IsTuple(float, float, float)

# Database record as tuple
db_record = (123, 'Alice Johnson', 'alice@example.com', '2023-01-15')
assert db_record == IsTuple(
    IsPositive,    # ID
    IsStr,         # Name
    Contains('@'), # Email
    IsStr,         # Date string
    length=4
)

# Function return value validation
def get_user_info(user_id):
    return (user_id, 'John Doe', 'john@example.com', True)

result = get_user_info(123)
assert result == IsTuple(
    123,           # Expected user ID
    IsStr,         # Name
    IsStr,         # Email  
    True           # Active status
)

# Configuration tuple with position validation
config = ('production', 443, True, 30, '/var/log/')
assert config == IsTuple(
    positions={
        0: 'production',  # Environment
        1: IsPositive,    # Port
        2: True,          # SSL enabled
        3: IsPositive,    # Timeout
        4: Contains('/')  # Log directory
    },
    length=5
)

# API response tuple format
api_result = ('success', 200, {'user_id': 123}, None)
assert api_result == IsTuple(
    'success',     # Status
    200,           # HTTP code
    dict,          # Data payload
    None,          # Error (should be None for success)
    check_order=True,
    length=4
)

# Mathematical vector validation
vector = (1.0, 0.0, -1.0, 2.5)
assert vector == IsTuple(
    float, float, float, float,  # All components must be floats
    length=4
)

# RGB color tuple
rgb_color = (255, 128, 0)
assert rgb_color == IsTuple(
    positions={
        0: IsRange(0, 255),  # Red component
        1: IsRange(0, 255),  # Green component  
        2: IsRange(0, 255)   # Blue component
    },
    length=3
)

Type Definitions

from typing import Any, ClassVar, Dict, Optional, Tuple, Union

# All sequence types inherit from DirtyEquals
# IsListOrTuple is the base class for IsList and IsTuple
# They work with Python's standard list and tuple types

Install with Tessl CLI

npx tessl i tessl/pypi-dirty-equals

docs

base-types.md

boolean-types.md

datetime-types.md

dict-types.md

index.md

inspection-types.md

numeric-types.md

other-types.md

sequence-types.md

string-types.md

tile.json