File: _compat.py

package info (click to toggle)
sphinx-external-toc 1.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 796 kB
  • sloc: python: 1,502; xml: 20; makefile: 11
file content (149 lines) | stat: -rw-r--r-- 4,448 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
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
"""Compatibility for using dataclasses instead of attrs."""
from __future__ import annotations

import dataclasses as dc
import re
import sys
from typing import Any, Callable, Pattern, Type

from docutils.nodes import Element

if sys.version_info >= (3, 10):
    DC_SLOTS: dict = {"slots": True}
else:
    DC_SLOTS: dict = {}


def field(**kwargs: Any):
    if sys.version_info < (3, 10):
        kwargs.pop("kw_only", None)
    if "validator" in kwargs:
        kwargs.setdefault("metadata", {})["validator"] = kwargs.pop("validator")
    return dc.field(**kwargs)


field.__doc__ = dc.field.__doc__


def validate_fields(inst):
    """Validate the fields of a dataclass,
    according to `validator` functions set in the field metadata.

    This function should be called in the `__post_init__` of the dataclass.

    The validator function should take as input (inst, field, value) and
    raise an exception if the value is invalid.
    """
    for field in dc.fields(inst):
        if "validator" not in field.metadata:
            continue
        if isinstance(field.metadata["validator"], list):
            for validator in field.metadata["validator"]:
                validator(inst, field, getattr(inst, field.name))
        else:
            field.metadata["validator"](inst, field, getattr(inst, field.name))


ValidatorType = Callable[[Any, dc.Field, Any], None]


def instance_of(type: Type[Any] | tuple[Type[Any], ...]) -> ValidatorType:
    """
    A validator that raises a `TypeError` if the initializer is called
    with a wrong type for this particular attribute (checks are performed using
    `isinstance` therefore it's also valid to pass a tuple of types).

    :param type: The type to check for.
    """

    def _validator(inst, attr, value):
        """
        We use a callable class to be able to change the ``__repr__``.
        """
        if not isinstance(value, type):
            raise TypeError(
                f"'{attr.name}' must be {type!r} (got {value!r} that is a {value.__class__!r})."
            )

    return _validator


def matches_re(regex: str | Pattern, flags: int = 0) -> ValidatorType:
    r"""
    A validator that raises `ValueError` if the initializer is called
    with a string that doesn't match *regex*.

    :param regex: a regex string or precompiled pattern to match against
    :param flags: flags that will be passed to the underlying re function (default 0)

    """
    fullmatch = getattr(re, "fullmatch", None)

    if isinstance(regex, Pattern):
        if flags:
            raise TypeError(
                "'flags' can only be used with a string pattern; "
                "pass flags to re.compile() instead"
            )
        pattern = regex
    else:
        pattern = re.compile(regex, flags)

    if fullmatch:
        match_func = pattern.fullmatch
    else:  # Python 2 fullmatch emulation (https://bugs.python.org/issue16203)
        pattern = re.compile(r"(?:{})\Z".format(pattern.pattern), pattern.flags)
        match_func = pattern.match

    def _validator(inst, attr, value):
        if not match_func(value):
            raise ValueError(
                f"'{attr.name}' must match regex {pattern!r} ({value!r} doesn't)"
            )

    return _validator


def optional(validator: ValidatorType) -> ValidatorType:
    """
    A validator that makes an attribute optional.  An optional attribute is one
    which can be set to ``None`` in addition to satisfying the requirements of
    the sub-validator.
    """

    def _validator(inst, attr, value):
        if value is None:
            return

        validator(inst, attr, value)

    return _validator


def deep_iterable(
    member_validator: ValidatorType, iterable_validator: ValidatorType | None = None
) -> ValidatorType:
    """
    A validator that performs deep validation of an iterable.

    :param member_validator: Validator to apply to iterable members
    :param iterable_validator: Validator to apply to iterable itself
    """

    def _validator(inst, attr, value):
        if iterable_validator is not None:
            iterable_validator(inst, attr, value)

        for member in value:
            member_validator(inst, attr, member)

    return _validator


# Docutils compatibility


def findall(node: Element):
    # findall replaces traverse in docutils v0.18
    # note a difference is that findall is an iterator
    return getattr(node, "findall", node.traverse)