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

patrickboateng / func-validator / 18209449144

03 Oct 2025 12:38AM UTC coverage: 98.714%. Remained the same
18209449144

push

github

patrickboateng
fixed bug in code

32 of 33 branches covered (96.97%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 2 files covered. (85.71%)

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.06
/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(arg_value: T, arg_annotation: T) -> bool:
2✔
36
    if get_origin(arg_annotation) is not Annotated:
2✔
37
        return True
2✔
38
    arg_type, *_ = get_args(arg_annotation)
2✔
39
    is_arg_optional = _is_arg_type_optional(arg_type)
2✔
40
    if is_arg_optional and arg_value in ALLOWED_OPTIONAL_VALUES:
2✔
41
        return True
2✔
42
    return False
2✔
43

44

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

56
    :param func: The function to be decorated. If None, the decorator is
57
                 returned for later application. Default is None.
58

59
    :param check_arg_types: If True, checks that all argument types match.
60
                            Default is False.
61

62
    :raises TypeError: If `func` is not callable or None, or if a validator
63
                       is not callable.
64

65
    :return: The decorated function with argument validation, or the
66
             decorator itself if `func` is None.
67
    """
68

69
    def dec(fn: Callable[P, R]) -> Callable[P, R]:
2✔
70
        @wraps(fn)
2✔
71
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
2✔
72
            sig = inspect.signature(fn)
2✔
73
            bound_args = sig.bind(*args, **kwargs)
2✔
74
            bound_args.apply_defaults()
2✔
75
            arguments = bound_args.arguments
2✔
76
            func_type_hints = get_type_hints(fn, include_extras=True)
2✔
77

78
            if "return" in func_type_hints:
2✔
NEW
79
                del func_type_hints["return"]
×
80

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

86
                arg_type, *arg_validator_funcs = get_args(arg_annotation)
2✔
87

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

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

98
            return fn(*args, **kwargs)
2✔
99

100
        return wrapper
2✔
101

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

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

111
    raise TypeError("The first argument must be a callable function or None.")
×
112

113

114
validate_func_args_at_runtime = validate_params
2✔
115
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