Python library that leverages the __eq__ method to make unit tests more declarative and readable through flexible comparison classes.
—
Object introspection and type validation classes for checking instances, attributes, names, and string representations. These types enable validation of object properties and metadata beyond simple value comparison.
Type instance checking with support for generic type syntax and optional direct instance validation. Provides flexible type validation including inheritance checking and strict instance validation.
class IsInstance(DirtyEquals):
"""
Type instance checking with generic support.
Validates that a value is an instance of the specified type,
with options for inheritance checking or direct instance validation.
"""
def __init__(self, expected_type: type, *, only_direct_instance: bool = False):
"""
Initialize instance type checker.
Args:
expected_type: The type to check against
only_direct_instance: If True, require exact type match (no inheritance)
"""
@classmethod
def __class_getitem__(cls, expected_type: type) -> 'IsInstance':
"""
Support generic syntax: IsInstance[Type] equivalent to IsInstance(Type).
Args:
expected_type: The type to check against
Returns:
IsInstance: New instance configured for the specified type
"""
def equals(self, other: Any) -> bool:
"""
Check if value is an instance of the expected type.
Args:
other: Value to type-check
Returns:
bool: True if isinstance(other, expected_type) or direct match
"""from dirty_equals import IsInstance
# Basic type checking
assert 42 == IsInstance(int)
assert "hello" == IsInstance(str)
assert [1, 2, 3] == IsInstance(list)
assert {"key": "value"} == IsInstance(dict)
# Generic syntax (equivalent to above)
assert 42 == IsInstance[int]
assert "hello" == IsInstance[str]
assert [1, 2, 3] == IsInstance[list]
# Inheritance checking (default behavior)
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
assert dog == IsInstance(Animal) # True - Dog inherits from Animal
assert dog == IsInstance(Dog) # True - Dog is direct instance
# Direct instance only (no inheritance)
assert dog == IsInstance(Dog, only_direct_instance=True) # True
# assert dog == IsInstance(Animal, only_direct_instance=True) # False
# Multiple type options with unions
from typing import Union
import numbers
assert 42 == IsInstance((int, float)) # Built-in union
assert 3.14 == IsInstance((int, float))
assert 42 == IsInstance(numbers.Number) # Abstract base class
# Complex data validation
api_response = {
'user_id': 123,
'data': [1, 2, 3],
'metadata': {'created': 'today'},
'success': True
}
assert api_response == {
'user_id': IsInstance[int],
'data': IsInstance[list],
'metadata': IsInstance[dict],
'success': IsInstance[bool]
}
# Custom class validation
from datetime import datetime
class User:
def __init__(self, name, created_at):
self.name = name
self.created_at = created_at
user = User("John", datetime.now())
user_data = {
'user_object': user,
'created_timestamp': datetime.now()
}
assert user_data == {
'user_object': IsInstance[User],
'created_timestamp': IsInstance[datetime]
}
# With boolean logic
from dirty_equals import IsPositive
# Must be both an integer and positive
assert 42 == (IsInstance[int] & IsPositive)
# Can be either string or integer
assert "test" == (IsInstance[str] | IsInstance[int])
assert 123 == (IsInstance[str] | IsInstance[int])Checks an object's __name__ attribute against an expected value. Useful for validating functions, classes, or other named objects, with optional support for instances.
class HasName(DirtyEquals):
"""
Checks object's __name__ attribute.
Validates that an object has a __name__ attribute matching
the expected value, with configurable instance support.
"""
def __init__(self, expected_name: str, *, allow_instances: bool = True):
"""
Initialize name checker.
Args:
expected_name: Expected value of __name__ attribute
allow_instances: If True, check instances' class names too
"""
@classmethod
def __class_getitem__(cls, expected_name: str) -> 'HasName':
"""
Support generic syntax: HasName['name'] equivalent to HasName('name').
Args:
expected_name: Expected name value
Returns:
HasName: New instance configured for the specified name
"""
def equals(self, other: Any) -> bool:
"""
Check if object's __name__ matches expected value.
Args:
other: Object to check (should have __name__ attribute)
Returns:
bool: True if __name__ matches expected value
"""from dirty_equals import HasName
# Function name checking
def my_function():
pass
assert my_function == HasName('my_function')
assert my_function == HasName['my_function'] # Generic syntax
# Class name checking
class MyClass:
pass
assert MyClass == HasName('MyClass')
assert MyClass == HasName['MyClass']
# Instance name checking (class name)
obj = MyClass()
assert obj == HasName('MyClass') # Checks obj.__class__.__name__
# Disable instance checking
assert obj == HasName('MyClass', allow_instances=True) # True
# assert obj == HasName('MyClass', allow_instances=False) # False (no __name__ on instance)
# Built-in type/function validation
assert int == HasName('int')
assert len == HasName('len')
assert list == HasName('list')
# Module validation
import json
import datetime
assert json == HasName('json')
assert datetime == HasName('datetime')
# API endpoint validation
def get_users():
pass
def create_user():
pass
def update_user():
pass
endpoints = [get_users, create_user, update_user]
expected_names = ['get_users', 'create_user', 'update_user']
for endpoint, expected in zip(endpoints, expected_names):
assert endpoint == HasName(expected)
# Class method validation
class APIHandler:
def handle_request(self):
pass
def validate_input(self):
pass
handler = APIHandler()
method = getattr(handler, 'handle_request')
assert method == HasName('handle_request')
# Dynamic validation
function_registry = {
'handler1': get_users,
'handler2': create_user
}
# Validate function names match registry expectations
for key, func in function_registry.items():
if key == 'handler1':
assert func == HasName('get_users')
elif key == 'handler2':
assert func == HasName('create_user')Checks an object's string representation (via repr()) against an expected value. Useful for validating object representations, especially for debugging or testing object state.
class HasRepr(DirtyEquals):
"""
Checks object's repr() output.
Validates that an object's string representation matches
the expected value exactly.
"""
def __init__(self, expected_repr: str):
"""
Initialize repr checker.
Args:
expected_repr: Expected result of repr(object)
"""
@classmethod
def __class_getitem__(cls, expected_repr: str) -> 'HasRepr':
"""
Support generic syntax: HasRepr['repr'] equivalent to HasRepr('repr').
Args:
expected_repr: Expected repr string
Returns:
HasRepr: New instance configured for the specified repr
"""
def equals(self, other: Any) -> bool:
"""
Check if object's repr() matches expected value.
Args:
other: Object to check repr() of
Returns:
bool: True if repr(other) equals expected_repr
"""from dirty_equals import HasRepr
# Basic repr checking
numbers = [1, 2, 3]
assert numbers == HasRepr('[1, 2, 3]')
assert numbers == HasRepr['[1, 2, 3]'] # Generic syntax
# String repr checking
text = "hello"
assert text == HasRepr("'hello'") # Note the quotes in repr
# Dict repr checking
data = {'a': 1, 'b': 2}
assert data == HasRepr("{'a': 1, 'b': 2}")
# Custom class with __repr__
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
point = Point(3, 4)
assert point == HasRepr('Point(3, 4)')
# Complex object validation
from datetime import datetime
from collections import namedtuple
# Named tuple representation
Person = namedtuple('Person', ['name', 'age'])
person = Person('Alice', 25)
assert person == HasRepr("Person(name='Alice', age=25)")
# Set representation (order may vary, so use with caution)
small_set = {1, 2}
# Note: set repr order is not guaranteed, so this might be fragile
# assert small_set == HasRepr('{1, 2}') or assert small_set == HasRepr('{2, 1}')
# Regex pattern repr
import re
pattern = re.compile(r'\d+')
expected_repr = "re.compile('\\\\d+')" # Escaped backslashes in repr
assert pattern == HasRepr[expected_repr]
# Exception representation
try:
raise ValueError("test error")
except ValueError as e:
assert e == HasRepr("ValueError('test error')")
# Debugging object state
class StatefulObject:
def __init__(self, value):
self.value = value
self.modified = False
def __repr__(self):
return f"StatefulObject(value={self.value}, modified={self.modified})"
obj = StatefulObject(42)
assert obj == HasRepr('StatefulObject(value=42, modified=False)')
obj.modified = True
assert obj == HasRepr('StatefulObject(value=42, modified=True)')
# Testing object snapshots in different states
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
def __repr__(self):
return f"Counter(count={self.count})"
counter = Counter()
assert counter == HasRepr('Counter(count=0)')
counter.increment()
assert counter == HasRepr('Counter(count=1)')
counter.increment()
assert counter == HasRepr('Counter(count=2)')Checks that an object has specified attributes with optional value validation. Supports both attribute presence checking and attribute value validation.
class HasAttributes(DirtyEquals):
"""
Checks object has specified attributes.
Validates that an object contains the specified attributes,
optionally checking their values as well.
"""
def __init__(self, *expected_args, **expected_kwargs):
"""
Initialize attribute checker (overloaded constructor).
Can be called in multiple ways:
- HasAttributes('attr1', 'attr2') - check presence only
- HasAttributes(attr1=value1, attr2=value2) - check presence and values
- HasAttributes({'attr1': value1}, attr2='value2') - mixed approach
Args:
*expected_args: Attribute names or dict of attribute-value pairs
**expected_kwargs: Attribute names and expected values
"""
def equals(self, other: Any) -> bool:
"""
Check if object has the specified attributes.
Args:
other: Object to check for attributes
Returns:
bool: True if all specified attributes exist (and match values if given)
"""from dirty_equals import HasAttributes, IsPositive, IsStr
# Basic attribute presence checking
class User:
def __init__(self, name, email, age):
self.name = name
self.email = email
self.age = age
user = User("John", "john@example.com", 25)
# Check that attributes exist
assert user == HasAttributes('name', 'email', 'age')
# Check attributes with specific values
assert user == HasAttributes(name="John", email="john@example.com", age=25)
# Check attributes with validation
assert user == HasAttributes(
name=IsStr,
email=IsStr,
age=IsPositive
)
# Mixed approach - some presence, some values
assert user == HasAttributes('name', email="john@example.com", age=IsPositive)
# Complex object validation
import datetime
class Article:
def __init__(self, title, author, created_at):
self.title = title
self.author = author
self.created_at = created_at
self.view_count = 0
self.published = False
article = Article("Python Guide", "Jane Doe", datetime.datetime.now())
# Validate essential attributes exist
assert article == HasAttributes('title', 'author', 'created_at', 'view_count')
# Validate attributes with conditions
from dirty_equals import IsNow, IsFalseLike
assert article == HasAttributes(
title=IsStr,
author=IsStr,
created_at=IsNow(delta=60), # Created within last minute
view_count=0,
published=IsFalseLike
)
# Dictionary-style attribute checking
expected_attrs = {
'title': 'Python Guide',
'author': IsStr,
'view_count': IsPositive | 0 # Can be positive or zero
}
assert article == HasAttributes(expected_attrs)
# API response object validation
class APIResponse:
def __init__(self, status_code, data, headers):
self.status_code = status_code
self.data = data
self.headers = headers
self.response_time = 0.123
response = APIResponse(200, {"users": [1, 2, 3]}, {"Content-Type": "application/json"})
# Validate response structure
assert response == HasAttributes(
status_code=200,
data=dict, # Just check it's a dict
headers=dict,
response_time=IsPositive
)
# Database model validation
class DatabaseRecord:
def __init__(self, id, created_at, updated_at):
self.id = id
self.created_at = created_at
self.updated_at = updated_at
self.version = 1
record = DatabaseRecord(123, datetime.datetime.now(), datetime.datetime.now())
# Validate standard database fields
assert record == HasAttributes(
'id', 'created_at', 'updated_at', 'version'
)
# Validate with type and value constraints
assert record == HasAttributes(
id=IsPositive,
created_at=IsNow(delta=300), # Within 5 minutes
updated_at=IsNow(delta=300),
version=1
)
# Configuration object validation
class Config:
def __init__(self):
self.database_url = "postgresql://localhost/mydb"
self.debug = True
self.secret_key = "super-secret"
self.port = 8000
config = Config()
# Validate configuration completeness
assert config == HasAttributes(
'database_url', 'debug', 'secret_key', 'port'
)
# Validate configuration values
assert config == HasAttributes(
database_url=IsStr,
debug=bool,
secret_key=IsStr,
port=IsPositive
)from typing import Any, Union
# All inspection types inherit from DirtyEquals
# They work with any Python objects that have the inspected propertiesInstall with Tessl CLI
npx tessl i tessl/pypi-dirty-equals