Skip to content

API reference

fvattrs works by decorating a function with @define_io and defining how to convert and validate the function's argument(s) and output. It also offer define to convert/validate inline.

Core

define(value, converter=None, validator=None)

Apply converters and validators to a single value.

Parameters:

Name Type Description Default
value object

Value to transform.

required
converter None | Callable | Iterable[Callable]

Converter(s) to apply.

None
validator None | Callable | Iterable[Callable]

Validator(s) to apply after conversion.

None

Returns:

Name Type Description
object object

The transformed value.

Source code in src/fvattrs/_core.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
def define(
    value: object,
    converter: None | Callable | Iterable[Callable] = None,
    validator: None | Callable | Iterable[Callable] = None,
) -> object:
    """Apply converters and validators to a single value.

    Args:
        value: Value to transform.
        converter: Converter(s) to apply.
        validator: Validator(s) to apply after conversion.

    Returns:
        object: The transformed value.

    """
    variable = _Variable(converter=converter, validator=validator)
    return variable(value)

define_io(*parameters)

Decorate a callable to apply argument conversion and validation.

Parameters:

Name Type Description Default
*parameters PositionalArgument | KeywordArgument | Output

Positional arguments, keyword arguments, or output descriptors describing how to process the wrapped callable.

()

Returns:

Name Type Description
callable Callable

A decorator that wraps the target callable.

Raises:

Type Description
ValueError

If duplicate indices, duplicate names, or multiple outputs are provided, or if any argument index is invalid or conflicts with keyword names.

NotImplementedError

If the signature contains variable positional arguments or variable keyword arguments.

Source code in src/fvattrs/_core.py
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
def define_io(
    *parameters: PositionalArgument | KeywordArgument | Output,
) -> Callable:
    """Decorate a callable to apply argument conversion and validation.

    Args:
        *parameters: Positional arguments, keyword arguments, or output
            descriptors describing how to process the wrapped callable.

    Returns:
        callable: A decorator that wraps the target callable.

    Raises:
        ValueError: If duplicate indices, duplicate names, or multiple outputs
            are provided, or if any argument index is invalid or conflicts
            with keyword names.
        NotImplementedError: If the signature contains variable positional
            arguments or variable keyword arguments.

    """
    arguments, kwarguments, outputs = _split_parameters(parameters)
    _preliminary_parameters_check(arguments, kwarguments, outputs)

    def decorator(func: Callable) -> Callable:
        signature = inspect.signature(func)
        names = tuple(signature.parameters)
        _secondary_parameters_check(arguments, kwarguments, signature)

        @functools.wraps(func)
        def wrapper(*args: object, **kwargs: object) -> object:
            args, kwargs = list(args), {**kwargs}

            for variable in (*arguments, *kwarguments):
                if (index := getattr(variable, "index", None)) is not None:
                    name = names[index]
                else:
                    name = variable.name
                    index = names.index(name)

                # Named argument.
                if name in kwargs:
                    kwargs[name] = variable(kwargs[name])

                # Positional argument.
                elif (index is not None) and (0 <= index < len(args)):
                    args[index] = variable(args[index])

                # Default argument.
                elif (
                    (default := signature.parameters[name].default)
                    is not inspect._empty  # noqa: SLF001  # No other way.
                ):
                    kwargs[name] = variable(default)

                else:
                    # Safe to not test.
                    raise NotImplementedError  # pragma: no cover

            if outputs:
                return outputs[0](func(*args, **kwargs))
            return func(*args, **kwargs)

        return wrapper

    return decorator

PositionalArgument

Bases: _Variable

Represent a positional argument in a callable signature.

Parameters:

Name Type Description Default
index int

Zero-based position of the argument in the wrapped callable. Defaults to 0.

required
converter callable or iterable of callables

Converter(s) applied in order to the input value.

required
validator callable or iterable of callables

Validator(s) applied to the transformed value.

required
Source code in src/fvattrs/_core.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@attrs.define(frozen=True)
class PositionalArgument(_Variable):
    """Represent a positional argument in a callable signature.

    Args:
        index (int): Zero-based position of the argument in the wrapped
            callable. Defaults to 0.
        converter (callable or iterable of callables, optional): Converter(s)
            applied in order to the input value.
        validator (callable or iterable of callables, optional): Validator(s)
            applied to the transformed value.

    """

    index: int = attrs.field(
        default=0,
        validator=(attrs.validators.instance_of(int), attrs.validators.ge(0)),
    )

    def __attrs_post_init__(self) -> None:
        object.__setattr__(self, "name", f"arg[{self.index}]")

KeywordArgument

Bases: _Variable

Represent a keyword argument in a callable signature.

Parameters:

Name Type Description Default
name str

Name of the keyword argument.

required
converter callable or iterable of callables

Converter(s) applied in order to the input value.

required
validator callable or iterable of callables

Validator(s) applied to the transformed value.

required
Source code in src/fvattrs/_core.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
@attrs.define(frozen=True)
class KeywordArgument(_Variable):
    """Represent a keyword argument in a callable signature.

    Args:
        name (str): Name of the keyword argument.
        converter (callable or iterable of callables, optional): Converter(s)
            applied in order to the input value.
        validator (callable or iterable of callables, optional): Validator(s)
            applied to the transformed value.

    """

    name: str = attrs.field(
        default="",
        validator=attrs.validators.instance_of(str),
    )

Output

Bases: _Variable

Represent the output value produced by a callable.

Parameters:

Name Type Description Default
converter callable or iterable of callables

Converter(s) applied in order to the input value.

required
validator callable or iterable of callables

Validator(s) applied to the transformed value.

required
Source code in src/fvattrs/_core.py
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@attrs.define(frozen=True)
class Output(_Variable):
    """Represent the output value produced by a callable.

    Args:
        converter (callable or iterable of callables, optional): Converter(s)
            applied in order to the input value.
        validator (callable or iterable of callables, optional): Validator(s)
            applied to the transformed value.

    """

    def __attrs_post_init__(self) -> None:
        object.__setattr__(self, "name", "output")

Converters

to_path(value)

Convert a value to a filesystem path.

Parameters:

Name Type Description Default
value object

Value to convert. Must be a string or any object accepted by os.PathLike.

required

Returns:

Name Type Description
path Path

A Path instance created from value.

Raises:

Type Description
TypeError

If value is not a string or path-like object.

Source code in src/fvattrs/converters.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def to_path(value: object) -> Path:
    """Convert a value to a filesystem path.

    Args:
        value: Value to convert. Must be a string or any object accepted by
            `os.PathLike`.

    Returns:
        path: A `Path` instance created from `value`.

    Raises:
        TypeError: If `value` is not a string or path-like object.

    """
    if not isinstance(value, (str, PathLike)):
        error_msg = (
            "to_path() expects str or os.PathLike,"
            f" got {value} of type {type(value).__name__}"
        )
        raise TypeError(error_msg)
    return Path(value)

to_date(value)

Convert a value to a datetime object.

Parameters:

Name Type Description Default
value object

Value to convert. If this is already a datetime, it is returned unchanged. Otherwise, it must be a string in ISO 8601 format.

required

Returns:

Name Type Description
datetime datetime

Parsed datetime value.

Raises:

Type Description
TypeError

If value is neither a string nor a datetime.

ValueError

If value is a string that cannot be parsed by datetime.fromisoformat.

Source code in src/fvattrs/converters.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def to_date(value: object) -> datetime:
    """Convert a value to a `datetime` object.

    Args:
        value: Value to convert. If this is already a `datetime`, it is
            returned unchanged. Otherwise, it must be a string in ISO 8601
            format.

    Returns:
        datetime: Parsed datetime value.

    Raises:
        TypeError: If `value` is neither a string nor a `datetime`.
        ValueError: If `value` is a string that cannot be parsed by
            `datetime.fromisoformat`.

    """
    if isinstance(value, datetime):
        return value

    if not isinstance(value, str):
        error_msg = (
            "to_date() expects str or datetime,"
            f" got {value} of type {type(value).__name__}",
        )
        raise TypeError(error_msg)

    return datetime.fromisoformat(value)

Validators

is_file(instance, attribute, value)

Validate that a value points to an existing file.

Parameters:

Name Type Description Default
instance object

The instance being validated.

required
attribute object

The attribute descriptor associated with the value.

required
value object

Candidate value. Must be a pathlib.Path that exists and is a file.

required

Raises:

Type Description
TypeError

If value is not a Path.

ValueError

If value does not exist or is not a file.

Source code in src/fvattrs/validators.py
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def is_file(instance: object, attribute: object, value: object) -> None:
    """Validate that a value points to an existing file.

    Args:
        instance: The instance being validated.
        attribute: The attribute descriptor associated with the value.
        value: Candidate value. Must be a `pathlib.Path` that exists and is a
            file.

    Raises:
        TypeError: If `value` is not a `Path`.
        ValueError: If `value` does not exist or is not a file.

    """
    if not isinstance(value, Path):
        error_msg = f"{attribute.name} must be a path, got {value}"
        raise TypeError(error_msg)

    if not value.is_file():
        error_msg = f"{attribute.name} must be a file, got {value}"
        raise ValueError(error_msg)

is_folder(instance, attribute, value)

Validate that a value points to an existing directory.

Parameters:

Name Type Description Default
instance object

The instance being validated.

required
attribute object

The attribute descriptor associated with the value.

required
value object

Candidate value. Must be a pathlib.Path that exists and is a directory.

required

Raises:

Type Description
TypeError

If value is not a Path.

ValueError

If value does not exist or is not a directory.

Source code in src/fvattrs/validators.py
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def is_folder(instance: object, attribute: object, value: object) -> None:
    """Validate that a value points to an existing directory.

    Args:
        instance: The instance being validated.
        attribute: The attribute descriptor associated with the value.
        value: Candidate value. Must be a `pathlib.Path` that exists and is a
            directory.

    Raises:
        TypeError: If `value` is not a `Path`.
        ValueError: If `value` does not exist or is not a directory.

    """
    if not isinstance(value, Path):
        error_msg = f"{attribute.name} must be a path, got {value}"
        raise TypeError(error_msg)

    if not value.is_dir():
        error_msg = f"{attribute.name} must be a folder, got {value}"
        raise ValueError(error_msg)