Skip to content

validation ¤

This module contains logic used to validate parameters passed to duties.

We validate the parameters before running the duties, effectively checking all CLI arguments and failing early if they are incorrect.

Classes:

  • ParamsCaster

    A helper class to cast parameters based on a function's signature annotations.

Functions:

  • cast_arg

    Cast an argument using a type annotation.

  • to_bool

    Convert a string to a boolean.

  • validate

    Validate positional and keyword arguments against a function.

ParamsCaster ¤

ParamsCaster(signature: Signature)

A helper class to cast parameters based on a function's signature annotations.

Parameters:

  • signature (Signature) –

    The signature to use to cast arguments.

Methods:

  • annotation_at_pos

    Give the annotation for the parameter at the given position.

  • cast

    Cast all positional and keyword arguments.

  • cast_kwarg

    Cast a keyword argument.

  • cast_posarg

    Cast a positional argument.

  • eaten_by_var_positional

    Tell if the parameter at this position is eaten by a variable positional parameter.

Attributes:

Source code in src/duty/validation.py
70
71
72
73
74
75
76
77
def __init__(self, signature: Signature) -> None:
    """Initialize the object.

    Parameters:
        signature: The signature to use to cast arguments.
    """
    self.params_dict = signature.parameters
    self.params_list = list(self.params_dict.values())

has_var_positional cached property ¤

has_var_positional: bool

Tell if there is a variable positional parameter.

Returns:

  • bool

    True or False.

var_keyword_annotation cached property ¤

var_keyword_annotation: Any

Give the variable keyword parameter (**kwargs) annotation if any.

Returns:

  • Any

    The variable keyword parameter annotation.

var_positional_annotation cached property ¤

var_positional_annotation: Any

Give the variable positional parameter (*args) annotation if any.

Returns:

  • Any

    The variable positional parameter annotation.

var_positional_position cached property ¤

var_positional_position: int

Give the position of the variable positional parameter in the signature.

Returns:

  • int

    The position of the variable positional parameter.

annotation_at_pos ¤

annotation_at_pos(pos: int) -> Any

Give the annotation for the parameter at the given position.

Parameters:

  • pos (int) –

    The position of the parameter.

Returns:

  • Any

    The positional parameter annotation.

Source code in src/duty/validation.py
121
122
123
124
125
126
127
128
129
130
def annotation_at_pos(self, pos: int) -> Any:
    """Give the annotation for the parameter at the given position.

    Parameters:
        pos: The position of the parameter.

    Returns:
        The positional parameter annotation.
    """
    return self.params_list[pos].annotation

cast ¤

cast(
    *args: Any, **kwargs: Any
) -> tuple[Sequence, dict[str, Any]]

Cast all positional and keyword arguments.

Parameters:

  • *args (Any, default: () ) –

    The positional arguments.

  • **kwargs (Any, default: {} ) –

    The keyword arguments.

Returns:

Source code in src/duty/validation.py
171
172
173
174
175
176
177
178
179
180
181
182
183
def cast(self, *args: Any, **kwargs: Any) -> tuple[Sequence, dict[str, Any]]:
    """Cast all positional and keyword arguments.

    Parameters:
        *args: The positional arguments.
        **kwargs: The keyword arguments.

    Returns:
        The cast arguments.
    """
    positional = tuple(self.cast_posarg(pos, arg) for pos, arg in enumerate(args))
    keyword = {name: self.cast_kwarg(name, value) for name, value in kwargs.items()}
    return positional, keyword

cast_kwarg ¤

cast_kwarg(name: str, value: Any) -> Any

Cast a keyword argument.

Parameters:

  • name (str) –

    The name of the argument in the signature.

  • value (Any) –

    The argument value.

Returns:

  • Any

    The cast value.

Source code in src/duty/validation.py
157
158
159
160
161
162
163
164
165
166
167
168
169
def cast_kwarg(self, name: str, value: Any) -> Any:
    """Cast a keyword argument.

    Parameters:
        name: The name of the argument in the signature.
        value: The argument value.

    Returns:
        The cast value.
    """
    if name in self.params_dict:
        return cast_arg(value, self.params_dict[name].annotation)
    return cast_arg(value, self.var_keyword_annotation)

cast_posarg ¤

cast_posarg(pos: int, arg: Any) -> Any

Cast a positional argument.

Parameters:

  • pos (int) –

    The position of the argument in the signature.

  • arg (Any) –

    The argument value.

Returns:

  • Any

    The cast value.

Source code in src/duty/validation.py
143
144
145
146
147
148
149
150
151
152
153
154
155
def cast_posarg(self, pos: int, arg: Any) -> Any:
    """Cast a positional argument.

    Parameters:
        pos: The position of the argument in the signature.
        arg: The argument value.

    Returns:
        The cast value.
    """
    if self.eaten_by_var_positional(pos):
        return cast_arg(arg, self.var_positional_annotation)
    return cast_arg(arg, self.annotation_at_pos(pos))

eaten_by_var_positional ¤

eaten_by_var_positional(pos: int) -> bool

Tell if the parameter at this position is eaten by a variable positional parameter.

Parameters:

  • pos (int) –

    The position of the parameter.

Returns:

  • bool

    Whether the parameter is eaten.

Source code in src/duty/validation.py
132
133
134
135
136
137
138
139
140
141
def eaten_by_var_positional(self, pos: int) -> bool:
    """Tell if the parameter at this position is eaten by a variable positional parameter.

    Parameters:
        pos: The position of the parameter.

    Returns:
        Whether the parameter is eaten.
    """
    return self.has_var_positional and pos >= self.var_positional_position

cast_arg ¤

cast_arg(arg: Any, annotation: Any) -> Any

Cast an argument using a type annotation.

Parameters:

  • arg (Any) –

    The argument value.

  • annotation (Any) –

    A type annotation.

Returns:

  • Any

    The cast value.

Source code in src/duty/validation.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def cast_arg(arg: Any, annotation: Any) -> Any:
    """Cast an argument using a type annotation.

    Parameters:
        arg: The argument value.
        annotation: A type annotation.

    Returns:
        The cast value.
    """
    if annotation is Parameter.empty:
        return arg
    if annotation is bool:
        annotation = to_bool
    if get_origin(annotation) in union_types:
        for sub_annotation in get_args(annotation):
            if sub_annotation is type(None):
                continue
            with suppress(Exception):
                return cast_arg(arg, sub_annotation)
    try:
        return annotation(arg)
    except Exception:  # noqa: BLE001
        return arg

to_bool ¤

to_bool(value: str) -> bool

Convert a string to a boolean.

Parameters:

  • value (str) –

    The string to convert.

Returns:

  • bool

    True or False.

Source code in src/duty/validation.py
29
30
31
32
33
34
35
36
37
38
def to_bool(value: str) -> bool:
    """Convert a string to a boolean.

    Parameters:
        value: The string to convert.

    Returns:
        True or False.
    """
    return value.lower() not in {"", "0", "no", "n", "false", "off"}

validate ¤

validate(
    func: Callable, *args: Any, **kwargs: Any
) -> tuple[Sequence, dict[str, Any]]

Validate positional and keyword arguments against a function.

First we clone the function, removing the first parameter (the context) and the body, to fail early with a TypeError if the arguments are incorrect: not enough, too much, in the wrong order, etc.

Then we cast all the arguments using the function's signature and we return them.

Parameters:

  • func (Callable) –

    The function to copy.

  • *args (Any, default: () ) –

    The positional arguments.

  • **kwargs (Any, default: {} ) –

    The keyword arguments.

Returns:

Source code in src/duty/validation.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
def validate(
    func: Callable,
    *args: Any,
    **kwargs: Any,
) -> tuple[Sequence, dict[str, Any]]:
    """Validate positional and keyword arguments against a function.

    First we clone the function, removing the first parameter (the context)
    and the body, to fail early with a `TypeError` if the arguments
    are incorrect: not enough, too much, in the wrong order, etc.

    Then we cast all the arguments using the function's signature
    and we return them.

    Parameters:
        func: The function to copy.
        *args: The positional arguments.
        **kwargs: The keyword arguments.

    Returns:
        The casted arguments.
    """
    return _get_params_caster(func, *args, **kwargs).cast(*args, **kwargs)