AnyValue Guide¶
Overview¶
AnyValue is a powerful matcher for Python testing that extends the functionality of unittest.mock.ANY by supporting type checking and validation constraints. It's perfect for situations where you need to verify that a value meets certain criteria without checking the exact value.
Problem Statement¶
In standard unittest.mock, the ANY matcher equals any value, which is useful but too permissive. Sometimes you need to verify that a value is of a specific type or meets certain constraints without checking the exact value.
For example, when testing a function that creates a user:
# Too permissive
mock_api.create_user.assert_called_once_with(ANY, ANY, ANY, ANY)
# Too strict - requires exact values
mock_api.create_user.assert_called_once_with(12345, "john_doe", "john@example.com", 25)
Solution¶
AnyValue provides a middle ground, allowing you to specify type and validation constraints:
from anyvalue import AnyValue
from annotated_types import Ge, Le, Len
mock_api.create_user.assert_called_once_with(
user_id=AnyValue(int, Ge(1)), # Positive user ID
username=AnyValue(str, Len(1, 50)), # Username between 1-50 chars
email=AnyValue(str), # Any string email
age=AnyValue(int, Ge(0), Le(150)) # Age between 0-150
)
Features¶
Type Checking¶
Match specific types or union of types:
from anyvalue import AnyValue
from datetime import datetime
# Single type
assert 42 == AnyValue(int)
assert "hello" == AnyValue(str)
assert datetime.now() == AnyValue(datetime)
# Union types using | operator
assert 42 == AnyValue(int | float)
assert 3.14 == AnyValue(int | float)
assert "test" == AnyValue(str | bytes)
assert b"test" == AnyValue(str | bytes)
None Support¶
Explicitly allow or disallow None values:
from anyvalue import AnyValue
# None type explicitly
assert None == AnyValue(None)
# None in union
assert None == AnyValue(str | None)
assert None == AnyValue(int | None)
assert 42 == AnyValue(int | None) # Actual values still work
# None not allowed when not specified
assert not (None == AnyValue(int))
Validation Constraints¶
Use annotated-types for advanced validation:
from anyvalue import AnyValue
from annotated_types import Ge, Le, Gt, Lt, Len, MultipleOf
# Greater than or equal (>=)
assert 42 == AnyValue(int, Ge(0)) # Non-negative integer
assert 100 == AnyValue(int, Ge(0))
assert not (-1 == AnyValue(int, Ge(0)))
# Less than or equal (<=)
assert 50 == AnyValue(int, Le(100))
assert not (101 == AnyValue(int, Le(100)))
# Range constraints
assert 50 == AnyValue(int, Ge(0), Le(100)) # Between 0 and 100
assert not (-1 == AnyValue(int, Ge(0), Le(100)))
assert not (101 == AnyValue(int, Ge(0), Le(100)))
# Length constraints
assert "hello" == AnyValue(str, Len(5, 5)) # Exact length 5
assert "test" == AnyValue(str, Len(1, 10)) # Length between 1 and 10
assert [1, 2, 3] == AnyValue(list, Len(3, 3)) # List of length 3
# Multiple of
assert 10 == AnyValue(int, MultipleOf(5))
assert not (11 == AnyValue(int, MultipleOf(5)))
Predicate Validators¶
Use Predicate for custom validation logic:
from anyvalue import AnyValue
from annotated_types import Predicate
# Even numbers
is_even = Predicate(lambda x: x % 2 == 0)
assert 42 == AnyValue(int, is_even)
assert not (43 == AnyValue(int, is_even))
# Positive numbers
is_positive = Predicate(lambda x: x > 0)
assert 100 == AnyValue(int, is_positive)
assert not (0 == AnyValue(int, is_positive))
# String patterns
starts_with_hello = Predicate(lambda x: x.startswith("hello"))
assert "hello world" == AnyValue(str, starts_with_hello)
assert not ("goodbye" == AnyValue(str, starts_with_hello))
# Combine with other constraints
assert 42 == AnyValue(int, Ge(0), is_even)
Custom Callable Validators¶
Use any callable function as a validator:
from math import isqrt
from anyvalue import AnyValue
# Palindrome checker
def is_palindrome(s: str) -> bool:
return s == s[::-1]
assert "racecar" == AnyValue(str, is_palindrome)
assert "level" == AnyValue(str, is_palindrome)
assert not ("hello" == AnyValue(str, is_palindrome))
# Prime number checker
def is_prime(n: int) -> bool:
if n < 2:
return False
for i in range(2, isqrt(n) + 1):
if n % i == 0:
return False
return True
assert 7 == AnyValue(int, is_prime)
assert 13 == AnyValue(int, is_prime)
assert not (4 == AnyValue(int, is_prime))
# Combine with annotated-types constraints
assert 7 == AnyValue(int, Ge(0), is_prime)
Mock Integration¶
AnyValue works seamlessly with unittest.mock:
from anyvalue import AnyValue
from annotated_types import Ge, Le, Len, Predicate
from datetime import datetime
from unittest.mock import Mock
# Create a mock function
mock_func = Mock()
# Call it with some values
mock_func(42, "test", datetime.now())
# Verify with AnyValue matchers
mock_func.assert_called_once_with(
AnyValue(int),
AnyValue(str),
AnyValue(datetime)
)
# Test with constraints
mock_func.reset_mock()
mock_func(100, "hello")
mock_func.assert_called_once_with(
AnyValue(int, Ge(0), Le(1000)),
AnyValue(str, Len(5, 5))
)
# Test with union types and None
mock_func.reset_mock()
mock_func(None, "test")
mock_func.assert_called_once_with(
AnyValue(int | None),
AnyValue(str)
)
Real-World Examples¶
API Response Validation¶
from anyvalue import AnyValue
from annotated_types import Ge, Le, Len
from unittest.mock import Mock
mock_api = Mock()
mock_api.create_user(
user_id=12345,
username="john_doe",
email="john@example.com",
age=25
)
mock_api.create_user.assert_called_once_with(
user_id=AnyValue(int, Ge(1)), # Positive user ID
username=AnyValue(str, Len(1, 50)), # Username between 1-50 chars
email=AnyValue(str), # Any string email
age=AnyValue(int, Ge(0), Le(150)) # Age between 0-150
)
Optional Parameters¶
from anyvalue import AnyValue
from datetime import datetime
from unittest.mock import Mock
mock_service = Mock()
mock_service.process(data="test", timestamp=datetime.now(), metadata=None)
mock_service.process.assert_called_once_with(
data=AnyValue(str),
timestamp=AnyValue(datetime),
metadata=AnyValue(dict | None) # Optional metadata
)
Email Validation¶
from anyvalue import AnyValue
from annotated_types import Predicate
from unittest.mock import Mock
is_valid_email = Predicate(lambda x: "@" in x and "." in x)
mock_validator = Mock()
mock_validator.send_email("user@example.com")
mock_validator.send_email.assert_called_once_with(
AnyValue(str, is_valid_email)
)
Error Messages¶
AnyValue provides descriptive error messages when assertions fail:
from anyvalue import AnyValue
from annotated_types import Ge
# Type mismatch
matcher = AnyValue(int)
result = matcher == "hello"
# Error: Expected type int, got str ('hello')
# Validator failure
matcher = AnyValue(int, Ge(10))
result = matcher == 5
# Error: Validator Ge(ge=10) failed: 5 is not >= 10
# Length validator
matcher = AnyValue(str, Len(5, 5))
result = matcher == "hi"
# Error: Validator Len(min_length=5, max_length=5) failed: length 2 is less than min 5
Design Decisions¶
- Class-based approach: Instantiate with parameters rather than a global constant
- Type-first API: The first argument is always the type constraint
- Union support: Use Python's
|operator for multiple types - Explicit None: None must be explicitly included in the type union
- annotated-types integration: Leverage existing validation library for constraints
- Hard dependency: annotated-types is a required dependency (not optional)
API Reference¶
For detailed API documentation, see the Reference section.