c108.typing
Runtime type validation utilities.
valid_types(func=None, *, skip=None, only=None)
Decorator to validate function arguments against type hints at runtime.
Creates a wrapper around the function that checks argument types before each call.
By default, validates all annotated parameters. Use skip or only to control
which parameters are checked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
func
|
The function to wrap (automatically provided when used as @valid_types). |
None
|
|
skip
|
Iterable[str]
|
Parameter names to exclude from validation. Cannot be used with |
None
|
only
|
Iterable[str]
|
Parameter names to validate (all others ignored). Cannot be used with |
None
|
Returns:
| Type | Description |
|---|---|
|
A wrapper function that performs type validation before calling the original function. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If a validated argument doesn't match its type hint at call time. |
ValueError
|
If both |
Examples:
Validate all parameters:
@valid_types
def connect(host: str, port: int) -> None:
pass
Validate only specific parameters (gradual adoption):
@valid_types(only=("host",))
def connect(host: str, port: int, options: dict) -> None:
pass
Skip validation for specific parameters:
@valid_types(skip=("payload",))
def send(id: int, payload: dict) -> None:
pass
Notes
- This is a function decorator that wraps the original function
- Validation happens once per function call with minimal overhead
- Only validates parameters with type hints; unannotated params are ignored
typing.Anyhints are never validated- Generic types (e.g.,
List[int]) validate the container type only, not contents - Works with both positional and keyword arguments
UnionandOptionaltypes validate against all union members- Supports both
Union[X, Y]andX | Ysyntax (Python 3.10+)
Source code in c108/typing.py
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | |
validate_attr_types(obj, *, attrs=None, include_inherited=True, include_private=False, pattern=None, strict_unions=True, strict_none=True, strict_missing=True)
Validate that object attributes match their type annotations.
Supports dataclasses, attrs classes, and regular Python classes with type annotations. Performance-optimized with automatic fast path for dataclasses.
This function validates the types of object attributes. For validating function parameters, see validate_param_types() (inline) or @valid_param_types (decorator).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
obj
|
Any
|
Object instance to validate |
required |
attrs
|
list[str] | None
|
Optional list of specific attribute names to validate. If None, validates all annotated attributes. |
None
|
include_inherited
|
bool
|
If True, validates inherited attributes with type hints |
True
|
include_private
|
bool
|
If True, validates private attributes (starting with '_') |
False
|
pattern
|
str | None
|
Optional regex pattern to filter which attributes to validate |
None
|
strict_unions
|
bool
|
If True (default), raise TypeError when encountering Union types that cannot be validated with isinstance() (e.g., list[int] | dict[str, int], Callable[[int], str] | Callable[[str], int]). If False, silently skip such unions. Simple unions like int | str | None are always validated regardless of this flag. |
True
|
strict_none
|
bool
|
If True (default), None values only pass validation when explicitly allowed in the type hint via Optional or Union with None (strict enforcement mode). If False, None values pass validation for ANY type hint (lenient mode, useful for development/migration). |
True
|
strict_missing
|
bool
|
If True (default), raise TypeError when an attribute has a type annotation but is missing from the object (AttributeError when accessing it). If False, silently skip missing attributes. This is important for production validation to catch incomplete objects. |
True
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If attribute type doesn't match annotation, or if strict_unions=True and a truly unvalidatable Union type is encountered |
ValueError
|
If obj has no type annotations |
RuntimeError
|
If Python version < 3.11 |
🚀 Performance: Automatic optimization: - Fast path (~5-10µs): Used for dataclasses when attrs=None, pattern=None, and include_private=False - Slow path (~30-70µs): Used for all other cases (non-dataclasses, pattern matching, custom attr lists, private attrs) - You don't need to configure anything - the function automatically chooses the optimal path based on your parameters
The fast path is 5-10x faster and recommended for high-throughput scenarios
like validation in __post_init__ or API request handlers.
Validation Modes
Strict (default - strict_none=True, strict_missing=True): >>> class Config: ... timeout: int = None # None fails - not in type hint >>> validate_attr_types(Config()) # doctest: +SKIP >>> # ❌ Raises TypeError
>>> class Config:
... timeout: int | None = None # None passes - explicitly in hint
>>> validate_attr_types(Config()) # ✅ Passes
>>> class Config:
... timeout: int # Annotated but not set
>>> validate_attr_types(Config()) # doctest: +SKIP
>>> # ❌ Raises TypeError (missing attribute)
Lenient (strict_none=False, strict_missing=False): >>> class Config: ... timeout: int = None # None passes despite int hint >>> validate_attr_types(Config(), strict_none=False) # ✅ Passes
>>> class Config:
... timeout: int # Missing attribute ignored
>>> validate_attr_types(Config(), strict_missing=False) # ✅ Passes
See Also
validate_param_types(): Validate function parameter types (inline call) valid_param_types: Decorator for automatic parameter type validation
Examples:
>>> from dataclasses import dataclass
>>>
>>> @dataclass
... class ImageData:
... id: str = "qwkfjkqfjhkgwdjhg349893874"
... width: int = 1080
... height: int = 1080
...
... def __post_init__(self):
... validate_attr_types(self) # Auto-uses fast path
>>>
>>> obj = ImageData()
>>>
>>> # Validate with default settings (strict mode)
>>> validate_attr_types(obj)
>>>
>>> # Lenient None checking
>>> validate_attr_types(obj, strict_none=False)
>>>
>>> # Allow missing attributes
>>> validate_attr_types(obj, strict_missing=False)
>>>
>>> # Use pattern matching (automatically uses slow path)
>>> validate_attr_types(obj, pattern=r"^api_.*")
>>>
>>> # Validate after mutations
>>> obj.width = "invalid"
>>> validate_attr_types(obj) # Raises TypeError
Traceback (most recent call last):
...
TypeError: type validation failed in <ImageData>:
Attribute 'width' must be <int>, got <str>
>>> # For function parameters, use validate_param_types() or @valid_param_types
>>> def process(x: int, y: str):
... validate_param_types() # Inline validation
... # ... or use @valid_param_types decorator
Source code in c108/typing.py
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
validate_param_types(*, skip=None, only=None)
Validate function parameters against their type hints (inline validation).
Must be called from within a function to inspect its parameters and annotations. Uses the calling frame to automatically detect the function and its arguments.
This is the inline validation approach. For automatic validation via decorator, use @valid_types instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
skip
|
Iterable[str] | None
|
Parameter names to exclude from validation. Cannot be used with |
None
|
only
|
Iterable[str] | None
|
Parameter names to validate (all others ignored). Cannot be used with |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If a validated parameter doesn't match its type hint |
ValueError
|
If both |
RuntimeError
|
If called outside a function context or function cannot be found |
Union Type Support
Supported (always validated): - Simple unions: int | str | float - Optional types: str | None, int | None - Union of basic types: list | dict | tuple
Unsupported (silently skipped): - Parameterized generic unions: list[int] | dict[str, int] - Callable unions with different signatures: Callable[[int], str] | Callable[[str], int]
🚀 Performance: ~50-100µs first call, ~10-20µs subsequent calls For hot paths, consider using @valid_types decorator instead (~5-15µs)
See Also
valid_types: Decorator for automatic parameter type validation (faster) validate_attr_types(): Validate object attribute types
Examples:
>>> # Basic usage
>>> def process_data(user_id: int, name: str | None, score: float = 0.0):
... validate_param_types()
... return f"{user_id}: {name} ({score})"
...
>>> process_data(101, "Alice", 98.5)
'101: Alice (98.5)'
>>> process_data("invalid", "Alice", 98.5) # ❌ Raises TypeError
Traceback (most recent call last):
...
TypeError: type validation failed in process_data():
Parameter 'user_id' must be <int>, got <str>
>>> # Validate only specific parameters
>>> def api_endpoint(user_id: int, token: str, debug: bool = False):
... validate_param_types(only=["user_id", "token"]) # Skip 'debug'
... # ... rest of function
...
>>> # Skip certain parameters
>>> def send_message(id: int, payload: dict, metadata: dict):
... validate_param_types(skip=["metadata"]) # Skip metadata validation
... # ... rest of function
...
>>> # Works with instance methods (automatically skips 'self')
>>> class DataProcessor:
... def process(self, data: int | str, strict_mode: bool = False):
... validate_param_types() # Skips 'self' automatically
... # ... rest of method
...
>>> processor = DataProcessor()
>>> processor.process(42) # ✅ Passes
>>> processor.process(3.14) # ❌ Raises TypeError
Traceback (most recent call last):
...
TypeError: type validation failed in process():
Parameter 'data' must be <int> | <str>, got <float>
>>> # Conditional validation (advantage over decorator)
>>> def handle_request(data: dict, mode: str):
... if mode == "strict":
... validate_param_types()
... # ... rest of function
...
>>> # For standard cases, decorator is cleaner:
>>> @valid_types
... def process(data: int | str):
... return f"Processed {data}"
...
>>> process(42)
'Processed 42'
Note
Use @valid_types decorator when possible for better performance (~3x faster). Use this inline version when: - You want validation hidden from function signature (cleaner public API) - You need conditional validation based on runtime logic - You're retrofitting validation into existing code without changing signatures
Source code in c108/typing.py
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 | |