• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

patrickboateng / func-validator / 18208668759

02 Oct 2025 11:51PM UTC coverage: 98.714% (-0.4%) from 99.116%
18208668759

push

github

patrickboateng
refactored code

32 of 33 branches covered (96.97%)

Branch coverage included in aggregate %.

39 of 41 new or added lines in 4 files covered. (95.12%)

1 existing line in 1 file now uncovered.

659 of 667 relevant lines covered (98.8%)

1.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.01
/func_validator/_func_arg_validator.py
1
import inspect
2✔
2
from functools import wraps
2✔
3
from math import nan
2✔
4
from typing import (
2✔
5
    Annotated,
6
    Callable,
7
    ParamSpec,
8
    TypeAlias,
9
    TypeVar,
10
    get_args,
11
    get_origin,
12
    get_type_hints,
13
    Union,
14
)
15

16
from .validators import DependsOn, MustBeA
2✔
17

18
P = ParamSpec("P")
2✔
19
R = TypeVar("R")
2✔
20
T = TypeVar("T")
2✔
21
DecoratorOrWrapper: TypeAlias = (
2✔
22
        Callable[[Callable[P, R]], Callable[P, R]] | Callable[P, R]
23
)
24

25
ALLOWED_OPTIONAL_VALUES = (None, nan)
2✔
26

27

28
def _is_arg_type_optional(arg_type: T) -> bool:
2✔
29
    is_optional = False
2✔
30
    if get_origin(arg_type) is Union:
2✔
31
        is_optional = get_args(arg_type)[1] is type(None)
2✔
32
    return is_optional
2✔
33

34

35
def _skip_validation(
2✔
36
        arg_name: str,
37
        arg_val: T,
38
        arg_annotation: T,
39
) -> bool:
40
    if arg_name == "return":
2✔
NEW
41
        return True
×
42
    if get_origin(arg_annotation) is not Annotated:
2✔
43
        return True
2✔
44
    arg_type, *_ = get_args(arg_annotation)
2✔
45
    if _is_arg_type_optional(arg_type) and arg_val in ALLOWED_OPTIONAL_VALUES:
2✔
46
        return True
2✔
47
    return False
2✔
48

49

50
def validate_params(
2✔
51
        func: Callable[P, R] | None = None,
52
        /,
53
        *,
54
        check_arg_types: bool = False,
55
) -> DecoratorOrWrapper:
56
    """Decorator to validate function arguments at runtime based on their
57
    type annotations using `typing.Annotated` and custom validators. This
58
    ensures that each argument passes any attached validators and
59
    optionally checks type correctness if `check_arg_types` is True.
60

61
    :param func: The function to be decorated. If None, the decorator is
62
                 returned for later application. Default is None.
63

64
    :param check_arg_types: If True, checks that all argument types match.
65
                            Default is False.
66

67
    :raises TypeError: If `func` is not callable or None, or if a validator
68
                       is not callable.
69

70
    :return: The decorated function with argument validation, or the
71
             decorator itself if `func` is None.
72
    """
73

74
    def dec(fn: Callable[P, R]) -> Callable[P, R]:
2✔
75
        @wraps(fn)
2✔
76
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
2✔
77
            sig = inspect.signature(fn)
2✔
78
            bound_args = sig.bind(*args, **kwargs)
2✔
79
            bound_args.apply_defaults()
2✔
80
            arguments = bound_args.arguments
2✔
81
            func_type_hints = get_type_hints(fn, include_extras=True)
2✔
82

83
            for arg_name, arg_annotation in func_type_hints.items():
2✔
84
                arg_value = arguments[arg_name]
2✔
85
                if _skip_validation(arg_name, arg_value, arg_annotation):
2✔
86
                    continue
2✔
87

88
                arg_type, *arg_validator_funcs = get_args(arg_annotation)
2✔
89

90
                if check_arg_types:
2✔
91
                    type_checker = MustBeA(arg_type)
2✔
92
                    type_checker(arg_value, arg_name)
2✔
93

94
                for arg_validator_fn in arg_validator_funcs:
2✔
95
                    if isinstance(arg_validator_fn, DependsOn):
2✔
96
                        arg_validator_fn.arguments = arguments
2✔
97
                    if callable(arg_validator_fn):
2✔
98
                        arg_validator_fn(arg_value, arg_name)
2✔
99

100
            return fn(*args, **kwargs)
2✔
101

102
        return wrapper
2✔
103

104
    # If no function is provided, return the decorator
105
    if func is None:
2✔
106
        return dec
2✔
107

108
    # If a function is provided, apply the decorator directly and return
109
    # the wrapper function
110
    if callable(func):
2✔
111
        return dec(func)
2✔
112

UNCOV
113
    raise TypeError("The first argument must be a callable function or None.")
×
114

115

116
validate_func_args_at_runtime = validate_params
2✔
117
validate_func_args = validate_params
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc