"""
Contracts Compatibility Shim
Provides a compatibility layer for PyContracts functionality while PyContracts
has compatibility issues with Python 3.11. This provides the same API but
with simplified implementations that don't enforce contracts in production.
For development and testing, contracts are logged but don't raise exceptions.
This allows the codebase to run while maintaining the contract-based design.
"""
import functools
import logging
from typing import Any, Callable, TypeVar, Union
logger = logging.getLogger(__name__)
F = TypeVar("F", bound=Callable[..., Any])
# Contract violations will be logged but not enforced for compatibility
CONTRACTS_ENABLED = False
class ContractNotRespected(Exception):
"""Exception raised when a contract is violated."""
pass
def require(condition: Union[str, Callable], *args, **kwargs) -> Callable[[F], F]:
"""
Precondition decorator - validates function inputs.
In compatibility mode, logs violations instead of raising exceptions.
"""
def decorator(func: F) -> F:
if not CONTRACTS_ENABLED:
return func
@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
try:
if callable(condition):
# Handle lambda functions
if not condition(*func_args, **func_kwargs):
logger.warning(f"Precondition violated in {func.__name__}")
else:
# Handle string conditions (simplified)
logger.debug(
f"Checking precondition for {func.__name__}: {condition}"
)
return func(*func_args, **func_kwargs)
except Exception as e:
logger.warning(f"Contract check failed in {func.__name__}: {e}")
return func(*func_args, **func_kwargs)
return wrapper
return decorator
def ensure(condition: Union[str, Callable], *args, **kwargs) -> Callable[[F], F]:
"""
Postcondition decorator - validates function outputs.
In compatibility mode, logs violations instead of raising exceptions.
"""
def decorator(func: F) -> F:
if not CONTRACTS_ENABLED:
return func
@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
try:
result = func(*func_args, **func_kwargs)
if callable(condition):
# Handle lambda functions
if not condition(result):
logger.warning(f"Postcondition violated in {func.__name__}")
else:
# Handle string conditions (simplified)
logger.debug(
f"Checking postcondition for {func.__name__}: {condition}"
)
return result
except Exception as e:
logger.warning(f"Contract check failed in {func.__name__}: {e}")
return func(*func_args, **func_kwargs)
return wrapper
return decorator
def contract(*args, **kwargs) -> Callable[[F], F]:
"""
General contract decorator.
In compatibility mode, this is a no-op decorator.
"""
def decorator(func: F) -> F:
if not CONTRACTS_ENABLED:
return func
@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
logger.debug(f"Contract check for {func.__name__}")
return func(*func_args, **func_kwargs)
return wrapper
if len(args) == 1 and callable(args[0]) and not kwargs:
# Used as @contract without parentheses
return decorator(args[0])
else:
# Used as @contract(...) with arguments
return decorator
def check(contract_string: str, *args, **kwargs) -> Any:
"""
Runtime contract checking function.
In compatibility mode, logs but doesn't enforce.
"""
if CONTRACTS_ENABLED:
logger.debug(f"Checking contract: {contract_string}")
return True
def fail(message: str = "Contract violation") -> None:
"""
Explicit contract failure.
In compatibility mode, logs instead of raising.
"""
if CONTRACTS_ENABLED:
logger.error(f"Contract failure: {message}")
raise ContractNotRespected(message)
else:
logger.warning(f"Contract would fail: {message}")
def check_multiple(contracts: list, *args, **kwargs) -> bool:
"""
Check multiple contracts at once.
In compatibility mode, logs but doesn't enforce.
"""
if CONTRACTS_ENABLED:
logger.debug(f"Checking multiple contracts: {contracts}")
return True
def invariant(condition: Union[str, Callable], *args, **kwargs) -> Callable[[F], F]:
"""
Invariant decorator for class methods.
Checks that a condition holds before and after method execution.
In compatibility mode, logs violations instead of raising exceptions.
"""
def decorator(func: F) -> F:
if not CONTRACTS_ENABLED:
return func
@functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
try:
# Pre-condition check
if callable(condition) and len(func_args) > 0: # Has self
if not condition(func_args[0]):
logger.warning(f"Invariant pre-check failed on {func.__name__}")
result = func(*func_args, **func_kwargs)
# Post-condition check
if callable(condition) and len(func_args) > 0: # Has self
if not condition(func_args[0]):
logger.warning(
f"Invariant post-check failed on {func.__name__}"
)
return result
except Exception as e:
logger.warning(f"Invariant error in {func.__name__}: {e}")
return func(*func_args, **func_kwargs)
return wrapper
return decorator
# Export the main functions that are imported throughout the codebase
__all__ = [
"require",
"ensure",
"contract",
"check",
"fail",
"check_multiple",
"invariant",
"ContractNotRespected",
"CONTRACTS_ENABLED",
]