Skip to content

confit.errors

MissingReference

Bases: Exception

Raised when one or multiple references cannot be resolved.

Source code in confit/errors.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class MissingReference(Exception):
    """
    Raised when one or multiple references cannot be resolved.
    """

    def __init__(self, ref: Reference):
        """
        Parameters
        ----------
        ref: Reference
            The reference that could not be resolved.
        """
        self.ref = ref
        super().__init__()

    def __str__(self):
        """
        String representation of the exception
        """
        return "Could not interpolate the following reference: {}".format(self.ref)

__init__(ref)

PARAMETER DESCRIPTION
ref

The reference that could not be resolved.

TYPE: Reference

Source code in confit/errors.py
38
39
40
41
42
43
44
45
46
def __init__(self, ref: Reference):
    """
    Parameters
    ----------
    ref: Reference
        The reference that could not be resolved.
    """
    self.ref = ref
    super().__init__()

__str__()

String representation of the exception

Source code in confit/errors.py
48
49
50
51
52
def __str__(self):
    """
    String representation of the exception
    """
    return "Could not interpolate the following reference: {}".format(self.ref)

CyclicReferenceError

Bases: Exception

Raised when a cyclic reference is detected.

Source code in confit/errors.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
class CyclicReferenceError(Exception):
    """
    Raised when a cyclic reference is detected.
    """

    def __init__(self, path: Loc):
        """
        Parameters
        ----------
        path: Loc
            The path of the cyclic reference
        """
        self.path = path
        super().__init__()

    def __str__(self):
        """
        String representation of the exception
        """
        return "Cyclic reference detected at {}".format(join_path(self.path))

__init__(path)

PARAMETER DESCRIPTION
path

The path of the cyclic reference

TYPE: Loc

Source code in confit/errors.py
60
61
62
63
64
65
66
67
68
def __init__(self, path: Loc):
    """
    Parameters
    ----------
    path: Loc
        The path of the cyclic reference
    """
    self.path = path
    super().__init__()

__str__()

String representation of the exception

Source code in confit/errors.py
70
71
72
73
74
def __str__(self):
    """
    String representation of the exception
    """
    return "Cyclic reference detected at {}".format(join_path(self.path))

remove_lib_from_traceback(tb)

Remove the lib folder from the traceback

Source code in confit/errors.py
80
81
82
83
84
85
86
87
88
89
90
91
92
def remove_lib_from_traceback(tb):
    """
    Remove the lib folder from the traceback
    """
    # compare package to module in f_globals
    if is_debug():
        return tb
    if tb is not None and tb.tb_frame.f_globals.get("__package__") == __package__:
        return remove_lib_from_traceback(tb.tb_next)
    if tb is None or tb.tb_next is None:
        return tb
    tb.tb_next = remove_lib_from_traceback(tb.tb_next)
    return tb

to_legacy_error(err, model)

Decorator to convert a Pydantic ValidationError into a ConfitValidationError

Source code in confit/errors.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
def to_legacy_error(err: pydantic.ValidationError, model: Any) -> LegacyValidationError:
    """
    Decorator to convert a Pydantic ValidationError into a ConfitValidationError
    """
    if isinstance(err, LegacyValidationError):
        return err
    errors = err.errors(include_url=False)
    raw_errors = []
    for err in errors:
        vrepr = repr(err["input"])
        vrepr = vrepr[:50] + "..." if len(vrepr) > 50 else vrepr
        err = dict(err)
        msg = err.pop("msg", "")
        msg = (msg[0].lower() + msg[1:]) if msg else msg
        raw_errors.append(
            ErrorWrapper(
                exc=err["ctx"]["error"]
                if "ctx" in err
                and "error" in err["ctx"]
                and isinstance(err["ctx"]["error"], BaseException)
                else PydanticNewStyleError(
                    **err,
                    msg=msg,
                    actual_value=vrepr,
                    actual_type=type(err["input"]).__name__,
                ),
                loc=err["loc"],
            )
        )
    return ConfitValidationError(raw_errors, model=model)

patch_errors(errors, path=(), values=None, model=None, special_names=(), drop_names=())

Patch the location of the errors to add the path prefix and complete the errors with the actual value if it is available. This is useful when the errors are raised in a sub-dict of the config.

PARAMETER DESCRIPTION
errors

The pydantic errors to patch

TYPE: T

path

The path to add to the errors

TYPE: Loc DEFAULT: ()

values

The values of the config

TYPE: Dict DEFAULT: None

special_names

The names of the special keys of the model signature, to replace with a wildcard when encountered in the error path

TYPE: Sequence[str] DEFAULT: ()

model

The model of the config

TYPE: Optional[BaseModel] DEFAULT: None

drop_names

The names of the keys to drop from the error path

TYPE: Sequence[str] DEFAULT: ()

RETURNS DESCRIPTION
Union[ValidationError, Sequence[ErrorWrapper], ErrorWrapper]

The patched errors

Source code in confit/errors.py
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
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
326
327
328
329
330
331
332
333
334
335
def patch_errors(
    errors: T,
    path: Loc = (),
    values: Dict = None,
    model: Optional[pydantic.BaseModel] = None,
    special_names: Sequence[str] = (),
    drop_names: Sequence[str] = (),
) -> T:
    """
    Patch the location of the errors to add the `path` prefix and complete
    the errors with the actual value if it is available.
    This is useful when the errors are raised in a sub-dict of the config.

    Parameters
    ----------
    errors: Union[LegacyValidationError, Sequence[ErrorWrapper], ErrorWrapper]
        The pydantic errors to patch
    path: Loc
        The path to add to the errors
    values: Dict
        The values of the config
    special_names: Sequence[str]
        The names of the special keys of the model signature, to replace with a wildcard
        when encountered in the error path
    model: Optional[pydantic.BaseModel]
        The model of the config
    drop_names: Sequence[str]
        The names of the keys to drop from the error path

    Returns
    -------
    Union[LegacyValidationError, Sequence[ErrorWrapper], ErrorWrapper]
        The patched errors
    """
    if isinstance(errors, pydantic.ValidationError):
        errors = to_legacy_error(errors, model).raw_errors
        errors = patch_errors(errors, path, values, model, special_names, drop_names)
        return ConfitValidationError(errors, model=model)
    if isinstance(errors, list):
        res = []
        for error in errors:
            res.extend(
                patch_errors(error, path, values, model, special_names, drop_names)
            )
        return res
    if isinstance(errors, ErrorWrapper) and isinstance(
        errors.exc, LegacyValidationError
    ):
        try:
            field_model = model
            for part in errors.loc_tuple():
                # if not issubclass(field_model, pydantic.BaseModel) and issubclass(
                #     field_model.vd.model, pydantic.BaseModel
                # ):
                #     field_model = field_model.vd.model
                if hasattr(field_model, "model_fields"):
                    field_model = field_model.model_fields[part]
                else:
                    field_model = field_model.__fields__[part]
                if hasattr(field_model, "type_"):
                    field_model = field_model.type_
                else:
                    field_model = field_model.annotation
            if (
                field_model is errors.exc.model
                or field_model.vd.model is errors.exc.model
            ):
                return patch_errors(
                    errors.exc.raw_errors,
                    (*path, *errors.loc_tuple()),
                    values,
                    model,
                    special_names,
                    drop_names,
                )
        except (KeyError, AttributeError):  # pragma: no cover
            print("Could not find model for", errors.loc_tuple())

    if (
        isinstance(errors.exc, PydanticErrorMixin)
        and values is not None
        and errors.loc_tuple()
        and errors.loc_tuple()[0] in values
    ):
        if "actual_value" not in errors.exc.__dict__:
            actual_value = values
            for key in errors.loc_tuple():
                actual_value = actual_value[key]
            vrepr = repr(actual_value)
            errors.exc.actual_value = vrepr[:50] + "..." if len(vrepr) > 50 else vrepr
            errors.exc.actual_type = type(actual_value).__name__

        cls = errors.exc.__class__
        if cls not in PATCHED_ERRORS_CLS:

            def error_str(self):
                s = cls.__str__(self)
                s = (
                    s + f", got {self.actual_value} ({self.actual_type})"
                    if hasattr(self, "actual_value")
                    else s
                )
                return s

            new_cls = type(
                cls.__name__,
                (cls,),
                {
                    "msg_template": cls.msg_template
                    + ", got {actual_value} ({actual_type})"
                }
                if hasattr(cls, "msg_template")
                else {
                    "__str__": error_str,
                },
            )
            PATCHED_ERRORS_CLS[cls] = new_cls
            PATCHED_ERRORS_CLS[new_cls] = new_cls
        errors.exc.__class__ = PATCHED_ERRORS_CLS[cls]

    if (
        isinstance(errors.exc, TypeError)
        and str(errors.exc).startswith("unexpected keyword argument")
        and ":" in errors.exc.args[0]
    ):
        extra_keys = errors.exc.args[0].split(": ")[1].split(", ")
        return [
            ErrorWrapper(
                TypeError("unexpected keyword argument"),
                (*path, *errors.loc_tuple()[:-1], key.strip("'")),
            )
            for key in extra_keys
        ]

    loc_tuple = errors.loc_tuple()
    if loc_tuple and loc_tuple[-1] in special_names:
        loc_tuple = (*loc_tuple[:-1], "[signature]")
    loc_tuple = tuple(part for part in loc_tuple if part not in drop_names)

    return [
        ErrorWrapper(
            errors.exc,
            (*path, *loc_tuple),
        )
    ]