Python library that leverages the __eq__ method to make unit tests more declarative and readable through flexible comparison classes.
—
Comprehensive numeric comparison types supporting integers, floats, decimals, and special float values with range checking, approximation, and sign validation. These types enable flexible validation of numeric data with various constraints and special value handling.
Base class for all numeric comparisons providing range checking, approximate matching, and bound validation. Serves as the foundation for all specialized numeric types.
class IsNumeric(DirtyEquals):
"""
Base class for numeric comparisons with configurable constraints.
Supports range checking, approximate matching, and bound validation
for numeric values including dates and datetimes.
"""
def __init__(
self,
*,
exactly: Optional[Union[int, float, Decimal]] = None,
approx: Optional[Union[int, float, Decimal]] = None,
delta: Optional[Union[int, float, Decimal]] = None,
gt: Optional[Union[int, float, Decimal]] = None,
lt: Optional[Union[int, float, Decimal]] = None,
ge: Optional[Union[int, float, Decimal]] = None,
le: Optional[Union[int, float, Decimal]] = None
):
"""
Initialize numeric comparison with constraints.
Args:
exactly: Exact value to match
approx: Approximate value for comparison
delta: Tolerance for approximate comparison
gt: Value must be greater than this
lt: Value must be less than this
ge: Value must be greater than or equal to this
le: Value must be less than or equal to this
"""
allowed_types: ClassVar[Tuple[type, ...]] = (int, float, Decimal, date, datetime)
def prepare(self) -> None:
"""Prepare and validate constraint parameters."""
def bounds_checks(self, other: Union[int, float, Decimal]) -> bool:
"""
Perform range bound checking.
Args:
other: Numeric value to check
Returns:
bool: True if value satisfies all bound constraints
"""
def approx_equals(
self,
other: Union[int, float, Decimal],
delta: Union[int, float, Decimal]
) -> bool:
"""
Check approximate equality within tolerance.
Args:
other: Value to compare
delta: Maximum allowed difference
Returns:
bool: True if values are within delta of each other
"""
def equals(self, other: Any) -> bool:
"""
Check if value matches numeric constraints.
Args:
other: Value to validate
Returns:
bool: True if value satisfies all conditions
"""from dirty_equals import IsNumeric
from decimal import Decimal
# Exact value matching
assert 42 == IsNumeric(exactly=42)
assert 3.14 == IsNumeric(exactly=3.14)
# Approximate matching
assert 3.14159 == IsNumeric(approx=3.14, delta=0.01)
assert 100 == IsNumeric(approx=99, delta=2)
# Range constraints
assert 50 == IsNumeric(gt=0, lt=100)
assert 25 == IsNumeric(ge=25, le=75)
# Combining constraints
assert 42 == IsNumeric(gt=0, le=100, approx=40, delta=5)
# Works with Decimal for high precision
big_decimal = Decimal('123.456789')
assert big_decimal == IsNumeric(approx=Decimal('123.45'), delta=Decimal('0.01'))
# Date and datetime support
from datetime import date, datetime
today = date.today()
assert today == IsNumeric(ge=date(2023, 1, 1))
now = datetime.now()
assert now == IsNumeric(gt=datetime(2023, 1, 1))Base class specifically for numeric types (int, float, Decimal), excluding date/datetime objects that IsNumeric supports.
class IsNumber(IsNumeric):
"""
Base class for pure number types (excludes dates).
Inherits all functionality from IsNumeric but restricts
allowed types to int, float, and Decimal only.
"""
allowed_types: ClassVar[Tuple[type, ...]] = (int, float, Decimal)from dirty_equals import IsNumber
# Number validation (excludes dates)
assert 42 == IsNumber
assert 3.14 == IsNumber
assert Decimal('123.45') == IsNumber
# Range constraints for numbers only
assert 25 == IsNumber(gt=0, lt=100)
assert 50 == IsNumber(approx=48, delta=5)
# Dates/datetimes won't match
from datetime import date
# assert date.today() == IsNumber # Would fail - dates not allowedSpecialized class for approximate number comparison with configurable tolerance.
class IsApprox(IsNumber):
"""
Approximate number comparison with tolerance.
Validates that a number is within a specified delta
of the expected approximate value.
"""
def __init__(
self,
approx: Union[int, float, Decimal],
*,
delta: Optional[Union[int, float, Decimal]] = None
):
"""
Initialize approximate comparison.
Args:
approx: Expected approximate value
delta: Maximum allowed difference (default based on value magnitude)
"""from dirty_equals import IsApprox
# Basic approximate matching
assert 3.14159 == IsApprox(3.14, delta=0.01)
assert 100 == IsApprox(99, delta=2)
# Default delta (implementation specific)
assert 42.001 == IsApprox(42) # Uses default tolerance
# Mathematical calculations with floating point precision
import math
calculated_pi = 22/7 # Approximation of pi
assert calculated_pi == IsApprox(math.pi, delta=0.01)
# API numeric responses
api_response = {
'cpu_usage': 85.7,
'memory_usage': 67.3,
'disk_usage': 23.8
}
expected_metrics = {
'cpu_usage': IsApprox(85, delta=5),
'memory_usage': IsApprox(70, delta=5),
'disk_usage': IsApprox(25, delta=5)
}
assert api_response == expected_metricsTypes for validating the sign (positive/negative/zero) of numeric values.
class IsPositive(IsNumber):
"""Checks value > 0."""
class IsNegative(IsNumber):
"""Checks value < 0."""
class IsNonNegative(IsNumber):
"""Checks value >= 0."""
class IsNonPositive(IsNumber):
"""Checks value <= 0."""from dirty_equals import IsPositive, IsNegative, IsNonNegative, IsNonPositive
# Positive values (> 0)
assert 42 == IsPositive
assert 3.14 == IsPositive
assert 0.001 == IsPositive
# Negative values (< 0)
assert -42 == IsNegative
assert -3.14 == IsNegative
assert -0.001 == IsNegative
# Non-negative values (>= 0)
assert 42 == IsNonNegative
assert 0 == IsNonNegative # Zero is non-negative
# Non-positive values (<= 0)
assert -42 == IsNonPositive
assert 0 == IsNonPositive # Zero is non-positive
# In data validation
user_stats = {
'points': 150,
'balance': -25.50,
'login_count': 0,
'penalty_score': -10
}
assert user_stats == {
'points': IsPositive,
'balance': IsNegative,
'login_count': IsNonNegative, # Can be zero
'penalty_score': IsNonPositive # Zero or negative
}Specialized integer validation types with sign constraints.
class IsInt(IsNumber):
"""Integer type checking."""
allowed_types: ClassVar[type] = int
class IsPositiveInt(IsInt):
"""Positive integer checking (> 0)."""
class IsNegativeInt(IsInt):
"""Negative integer checking (< 0)."""from dirty_equals import IsInt, IsPositiveInt, IsNegativeInt
# Basic integer checking
assert 42 == IsInt
assert 0 == IsInt
assert -15 == IsInt
# Floats won't match
# assert 3.14 == IsInt # Would fail
# Positive integers
assert 42 == IsPositiveInt
assert 1 == IsPositiveInt
# assert 0 == IsPositiveInt # Would fail - zero is not positive
# assert -5 == IsPositiveInt # Would fail
# Negative integers
assert -42 == IsNegativeInt
assert -1 == IsNegativeInt
# assert 0 == IsNegativeInt # Would fail - zero is not negative
# assert 5 == IsNegativeInt # Would fail
# Database ID validation
user_record = {
'id': 123,
'parent_id': 456,
'sort_order': -1 # Negative for reverse order
}
assert user_record == {
'id': IsPositiveInt, # IDs should be positive
'parent_id': IsPositiveInt,
'sort_order': IsNegativeInt # Negative sort order
}
# API pagination parameters
pagination = {
'page': 1,
'per_page': 25,
'offset': 0,
'total_count': 150
}
assert pagination == {
'page': IsPositiveInt,
'per_page': IsPositiveInt,
'offset': IsInt, # Can be zero
'total_count': IsInt # Can be zero
}Specialized float validation including special float values (infinity, NaN).
class IsFloat(IsNumber):
"""Float type checking."""
allowed_types: ClassVar[type] = float
class IsPositiveFloat(IsFloat):
"""Positive float checking (> 0)."""
class IsNegativeFloat(IsFloat):
"""Negative float checking (< 0)."""
class IsFloatInf(IsFloat):
"""Infinite float checking (positive or negative)."""
class IsFloatInfPos(IsFloat):
"""Positive infinite float checking."""
class IsFloatInfNeg(IsFloat):
"""Negative infinite float checking."""
class IsFloatNan(IsFloat):
"""NaN (Not a Number) float checking."""from dirty_equals import (
IsFloat, IsPositiveFloat, IsNegativeFloat,
IsFloatInf, IsFloatInfPos, IsFloatInfNeg, IsFloatNan
)
import math
# Basic float checking
assert 3.14 == IsFloat
assert 0.0 == IsFloat
assert -2.718 == IsFloat
# Integers won't match
# assert 42 == IsFloat # Would fail
# Positive floats
assert 3.14 == IsPositiveFloat
assert 0.001 == IsPositiveFloat
# assert 0.0 == IsPositiveFloat # Would fail - zero not positive
# assert -1.5 == IsPositiveFloat # Would fail
# Negative floats
assert -3.14 == IsNegativeFloat
assert -0.001 == IsNegativeFloat
# assert 0.0 == IsNegativeFloat # Would fail - zero not negative
# assert 1.5 == IsNegativeFloat # Would fail
# Special float values
assert float('inf') == IsFloatInf
assert float('-inf') == IsFloatInf
assert float('inf') == IsFloatInfPos
assert float('-inf') == IsFloatInfNeg
assert float('nan') == IsFloatNan
# Mathematical calculations with special values
calculation_results = {
'result1': 1.0 / 0.0, # Positive infinity
'result2': -1.0 / 0.0, # Negative infinity
'result3': 0.0 / 0.0, # NaN
'result4': math.sqrt(2.0) # Normal float
}
assert calculation_results == {
'result1': IsFloatInfPos,
'result2': IsFloatInfNeg,
'result3': IsFloatNan,
'result4': IsPositiveFloat
}
# Scientific computation validation
scientific_data = {
'temperature': 273.15,
'pressure': -1.5, # Negative relative pressure
'infinity_check': float('inf'),
'error_value': float('nan')
}
assert scientific_data == {
'temperature': IsPositiveFloat,
'pressure': IsNegativeFloat,
'infinity_check': IsFloatInf,
'error_value': IsFloatNan
}
# Numeric analysis results
analysis = {
'mean': 45.7,
'std_dev': 12.3,
'min_value': -infinity_placeholder, # Could be negative infinity
'max_value': infinity_placeholder, # Could be positive infinity
'invalid_calculations': [float('nan'), float('nan')]
}
# Validate statistical outputs
assert analysis == {
'mean': IsFloat,
'std_dev': IsPositiveFloat, # Standard deviation is always positive
'min_value': IsFloatInfNeg | IsFloat, # Could be normal float or -inf
'max_value': IsFloatInfPos | IsFloat, # Could be normal float or +inf
'invalid_calculations': [IsFloatNan, IsFloatNan]
}from typing import Any, ClassVar, Optional, Tuple, Union
from decimal import Decimal
from datetime import date, datetime
# All numeric types inherit from IsNumeric/IsNumber which inherit from DirtyEquals
# They work with Python's standard numeric types and the decimal moduleInstall with Tessl CLI
npx tessl i tessl/pypi-dirty-equals