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

string-types.mddocs/

String Types

String and byte sequence validation with support for length constraints, case sensitivity, and regular expression matching. These types enable comprehensive validation of text and binary data with flexible pattern matching and formatting constraints.

Capabilities

IsAnyStr

Base class for string and bytes comparison providing common functionality for length constraints, case handling, and regular expression matching. Supports both string and bytes objects.

class IsAnyStr(DirtyEquals):
    """
    Base string/bytes comparison with configurable constraints.
    
    Provides length validation, case handling, and regex matching
    for both string and bytes objects.
    """
    
    def __init__(
        self,
        *,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
        case: Optional[str] = None,
        regex: Optional[Union[str, Pattern]] = None,
        regex_flags: Optional[int] = None
    ):
        """
        Initialize string/bytes validator.
        
        Args:
            min_length: Minimum string/bytes length
            max_length: Maximum string/bytes length
            case: Case constraint ('upper', 'lower', 'title', 'capitalize')
            regex: Regular expression pattern to match
            regex_flags: Regex flags (e.g., re.IGNORECASE, re.MULTILINE)
        """
    
    expected_types: ClassVar[Tuple[type, ...]] = (str, bytes)
    
    def equals(self, other: Any) -> bool:
        """
        Check if value matches string/bytes constraints.
        
        Args:
            other: String or bytes value to validate
            
        Returns:
            bool: True if value satisfies all constraints
        """

Usage Examples

from dirty_equals import IsAnyStr
import re

# Basic string or bytes validation
assert "hello" == IsAnyStr
assert b"hello" == IsAnyStr

# Length constraints
assert "hello world" == IsAnyStr(min_length=5, max_length=20)
assert b"data" == IsAnyStr(min_length=1, max_length=10)

# Case constraints
assert "HELLO" == IsAnyStr(case='upper')
assert "hello" == IsAnyStr(case='lower') 
assert "Hello World" == IsAnyStr(case='title')
assert "Hello" == IsAnyStr(case='capitalize')

# Regex matching
assert "test@example.com" == IsAnyStr(regex=r'.+@.+\..+')
assert "123-456-7890" == IsAnyStr(regex=r'\d{3}-\d{3}-\d{4}')

# Regex with flags
assert "HELLO world" == IsAnyStr(
    regex=r'hello world',
    regex_flags=re.IGNORECASE
)

# Combined constraints
assert "Hello World!" == IsAnyStr(
    min_length=5,
    max_length=20,
    regex=r'^[A-Za-z\s!]+$'  # Letters, spaces, exclamation only
)

# API response validation
api_data = {
    'username': 'john_doe',
    'email': 'john@example.com',
    'status': 'ACTIVE',
    'description': b'binary data here'
}

assert api_data == {
    'username': IsAnyStr(min_length=3, max_length=20),
    'email': IsAnyStr(regex=r'.+@.+\..+'),
    'status': IsAnyStr(case='upper'),
    'description': IsAnyStr(min_length=1)  # Any non-empty bytes
}

IsStr

String-specific validation that inherits all functionality from IsAnyStr but restricts validation to string objects only, excluding bytes.

class IsStr(IsAnyStr):
    """
    String comparison with constraints (excludes bytes).
    
    Inherits all functionality from IsAnyStr but only
    accepts str objects, not bytes.
    """
    
    expected_types: ClassVar[Tuple[type, ...]] = (str,)

Usage Examples

from dirty_equals import IsStr
import re

# Basic string validation
assert "hello world" == IsStr
assert "12345" == IsStr

# Bytes won't match
# assert b"hello" == IsStr  # Would fail

# Length validation
assert "username" == IsStr(min_length=3, max_length=20)
assert "short" == IsStr(min_length=1, max_length=10)

# Case validation
assert "HELLO" == IsStr(case='upper')
assert "hello" == IsStr(case='lower')
assert "Hello World" == IsStr(case='title')
assert "Hello" == IsStr(case='capitalize')

# Pattern matching
assert "john@example.com" == IsStr(regex=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
assert "+1-555-123-4567" == IsStr(regex=r'^\+\d{1,3}-\d{3}-\d{3}-\d{4}$')

# URL validation
assert "https://www.example.com/path" == IsStr(regex=r'^https?://.+')

# Username validation
assert "user_name123" == IsStr(
    min_length=3,
    max_length=20,
    regex=r'^[a-zA-Z0-9_]+$'  # Alphanumeric and underscore only
)

# Password strength validation
assert "MyPassword123!" == IsStr(
    min_length=8,
    regex=r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).*$'
)

# Form data validation
form_input = {
    'first_name': 'John',
    'last_name': 'Doe',
    'email': 'john.doe@company.com',
    'phone': '555-0123',
    'zip_code': '12345',
    'country': 'US'
}

assert form_input == {
    'first_name': IsStr(min_length=1, max_length=50, case='capitalize'),
    'last_name': IsStr(min_length=1, max_length=50, case='capitalize'),
    'email': IsStr(regex=r'^[^@]+@[^@]+\.[^@]+$'),
    'phone': IsStr(regex=r'^\d{3}-\d{4}$'),
    'zip_code': IsStr(regex=r'^\d{5}$'),
    'country': IsStr(case='upper', min_length=2, max_length=2)
}

# Configuration validation
config = {
    'app_name': 'MyApplication',
    'environment': 'production',
    'log_level': 'INFO',
    'secret_key': 'abc123def456ghi789'
}

assert config == {
    'app_name': IsStr(min_length=1),
    'environment': IsStr(regex=r'^(development|staging|production)$'),
    'log_level': IsStr(regex=r'^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$'),
    'secret_key': IsStr(min_length=16)
}

# File path validation
file_paths = {
    'config_file': '/etc/myapp/config.json',
    'log_file': '/var/log/myapp.log',
    'temp_dir': '/tmp/myapp/'
}

assert file_paths == {
    'config_file': IsStr(regex=r'^/.+\.json$'),
    'log_file': IsStr(regex=r'^/.+\.log$'), 
    'temp_dir': IsStr(regex=r'^/.+/$')  # Ends with slash
}

# Social media validation
social_handles = {
    'twitter': '@johndoe',
    'instagram': 'john.doe.photography',
    'linkedin': 'john-doe-engineer'
}

assert social_handles == {
    'twitter': IsStr(regex=r'^@[a-zA-Z0-9_]{1,15}$'),
    'instagram': IsStr(regex=r'^[a-zA-Z0-9_.]{1,30}$'),
    'linkedin': IsStr(regex=r'^[a-zA-Z0-9-]{3,100}$')
}

# API token validation
tokens = {
    'api_key': 'sk-1234567890abcdef1234567890abcdef',
    'session_token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
    'refresh_token': 'rt_1234567890abcdef'
}

assert tokens == {
    'api_key': IsStr(regex=r'^sk-[a-f0-9]{32}$'),
    'session_token': IsStr(min_length=100),  # JWT tokens are long
    'refresh_token': IsStr(regex=r'^rt_[a-f0-9]{16}$')
}

IsBytes

Bytes-specific validation that inherits all functionality from IsAnyStr but restricts validation to bytes objects only, excluding strings.

class IsBytes(IsAnyStr):
    """
    Bytes comparison with constraints (excludes strings).
    
    Inherits all functionality from IsAnyStr but only
    accepts bytes objects, not strings.
    """
    
    expected_types: ClassVar[Tuple[type, ...]] = (bytes,)

Usage Examples

from dirty_equals import IsBytes
import re

# Basic bytes validation
assert b"hello world" == IsBytes
assert b"\x00\x01\x02\x03" == IsBytes

# Strings won't match
# assert "hello" == IsBytes  # Would fail

# Length validation
assert b"binary_data" == IsBytes(min_length=5, max_length=50)
assert b"short" == IsBytes(min_length=1, max_length=10)

# Pattern matching on bytes
assert b"Content-Type: application/json" == IsBytes(regex=rb'Content-Type: \w+/\w+')
assert b"HTTP/1.1 200 OK" == IsBytes(regex=rb'HTTP/\d\.\d \d{3} \w+')

# File signature validation (magic numbers)
png_header = b'\x89PNG\r\n\x1a\n'
jpeg_header = b'\xff\xd8\xff'
pdf_header = b'%PDF-'

assert png_header == IsBytes(regex=rb'^\x89PNG\r\n\x1a\n')
assert jpeg_header == IsBytes(regex=rb'^\xff\xd8\xff')  
assert pdf_header == IsBytes(regex=rb'^%PDF-')

# Network packet validation
http_request = b'GET /api/users HTTP/1.1\r\nHost: example.com\r\n\r\n'
assert http_request == IsBytes(
    regex=rb'^GET .+ HTTP/\d\.\d\r\n.*Host: .+\r\n\r\n$'
)

# Binary protocol validation
protocol_message = b'\x02\x00\x10Hello World!\x03'  # STX + length + data + ETX
assert protocol_message == IsBytes(
    min_length=4,  # At least header + footer
    regex=rb'^\x02.+\x03$'  # Starts with STX, ends with ETX
)

# Cryptographic data validation
encrypted_data = {
    'iv': b'\x12\x34\x56\x78' * 4,  # 16 bytes IV
    'ciphertext': b'encrypted_payload_here_with_padding',
    'tag': b'\xaa\xbb\xcc\xdd' * 4  # 16 bytes auth tag
}

assert encrypted_data == {
    'iv': IsBytes(min_length=16, max_length=16),        # Exactly 16 bytes
    'ciphertext': IsBytes(min_length=1),                # Non-empty
    'tag': IsBytes(min_length=16, max_length=16)        # Exactly 16 bytes
}

# File content validation
file_data = {
    'image': b'\x89PNG\r\n\x1a\n' + b'image_data_here',
    'document': b'%PDF-1.4\n' + b'pdf_content_here',
    'archive': b'PK\x03\x04' + b'zip_content_here'
}

assert file_data == {
    'image': IsBytes(regex=rb'^\x89PNG'),      # PNG file
    'document': IsBytes(regex=rb'^%PDF-'),     # PDF file
    'archive': IsBytes(regex=rb'^PK\x03\x04') # ZIP file
}

# Database blob validation
blob_data = {
    'profile_picture': b'binary_image_data_here',
    'document_content': b'binary_document_data',
    'metadata': b'{"created": "2023-01-01"}'
}

assert blob_data == {
    'profile_picture': IsBytes(min_length=1, max_length=5*1024*1024),  # Max 5MB
    'document_content': IsBytes(min_length=1),
    'metadata': IsBytes(regex=rb'^\{.*\}$')  # JSON-like structure
}

# Message queue payloads
queue_messages = [
    b'{"type": "user_created", "data": {...}}',
    b'{"type": "order_processed", "data": {...}}',
    b'{"type": "email_sent", "data": {...}}'
]

message_pattern = IsBytes(regex=rb'^\{"type": "\w+", "data": .*\}$')
for message in queue_messages:
    assert message == message_pattern

# Network response validation
api_response_bytes = b'{"status": "success", "data": [...]}'
assert api_response_bytes == IsBytes(
    min_length=10,                    # Minimum JSON structure
    regex=rb'^\{.*"status".*\}$'     # Contains status field
)

# Base64 encoded data (still bytes before decoding)
base64_data = b'SGVsbG8gV29ybGQh'  # "Hello World!" encoded
assert base64_data == IsBytes(regex=rb'^[A-Za-z0-9+/]+=*$')

# Binary log entries
log_entry = b'\x00\x00\x00\x20' + b'2023-01-01 12:00:00 INFO: App started'
assert log_entry == IsBytes(
    min_length=8,                           # Header + some content
    regex=rb'^\x00\x00\x00.+INFO:'        # Starts with header, contains INFO
)

Type Definitions

from typing import ClassVar, Optional, Pattern, Tuple, Union
import re

# All string types inherit from IsAnyStr which inherits from DirtyEquals
# They work with Python's standard str and bytes types

# Common regex flags that can be used with regex_flags parameter:
# re.IGNORECASE, re.MULTILINE, re.DOTALL, re.VERBOSE, etc.

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