Skip to content

confit.registry

VisibleDeprecationWarning [source]

Bases: UserWarning

Visible deprecation warning.

By default, python will not show deprecation warnings, so this class can be used when a very visible warning is helpful, for example because the usage is most likely a user bug.

Copied from https://github.com/numpy/numpy/blob/965b41d418e6100c1afae0b6f818a7ef152bc25d/numpy/_globals.py#L44-L51

Registry [source]

Bases: Registry

A registry that validates the input arguments of the registered functions.

Initialize the registry.

Parameters

PARAMETER DESCRIPTION
namespace

The namespace of the registry

TYPE: Sequence[str]

entry_points

Should we use entry points to load the registered functions

TYPE: bool DEFAULT: False

Source code in confit/registry.py
302
303
304
305
306
307
308
309
310
311
312
313
314
def __init__(self, namespace: Sequence[str], entry_points: bool = False) -> None:
    """
    Initialize the registry.

    Parameters
    ----------
    namespace: Sequence[str]
        The namespace of the registry
    entry_points: bool
        Should we use entry points to load the registered functions
    """
    super().__init__(namespace, entry_points=entry_points)
    self.registry = None

register [source]

This is a convenience wrapper around catalogue.Registry.register, that additionally validates the input arguments of the registered function and saves the result of any call to a mapping to its arguments.

Parameters

PARAMETER DESCRIPTION
name

The name of the function

TYPE: str

func

The function to register

TYPE: Optional[Union[Callable[P, R], Type[R]]] DEFAULT: None

save_params

Additional parameters to save with the call arguments, defaults to the registry name when omitted or empty

TYPE: Optional[Dict[str, Any]] DEFAULT: None

skip_save_params

List of parameters to skip when saving the function parameters

TYPE: Sequence[str] DEFAULT: ()

invoker

An optional invoker to apply to the function before registering it. It is better to use this than to apply the invoker to the function to preserve the signature of the function or the class and enable validating its parameters.

TYPE: Optional[Callable] DEFAULT: None

deprecated

The deprecated registry names for the function

TYPE: Sequence[str] DEFAULT: ()

Source code in confit/registry.py
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
def register(
    self,
    name: str,
    *,
    func: Optional[Union[Callable[P, R], Type[R]]] = None,
    save_params: Optional[Dict[str, Any]] = None,
    skip_save_params: Sequence[str] = (),
    invoker: Optional[Callable] = None,
    deprecated: Sequence[str] = (),
) -> Callable[[Callable[P, R]], Union[Callable[P, R], Draftable[R, P]]]:
    """
    This is a convenience wrapper around `catalogue.Registry.register`, that
    additionally validates the input arguments of the registered function and
    saves the result of any call to a mapping to its arguments.

    Parameters
    ----------
    name:
        The name of the function
    func:
        The function to register
    save_params:
        Additional parameters to save with the call arguments, defaults to the
        registry name when omitted or empty
    skip_save_params:
        List of parameters to skip when saving the function parameters
    invoker:
        An optional invoker to apply to the function before registering it.
        It is better to use this than to apply the invoker to the function
        to preserve the signature of the function or the class and enable
        validating its parameters.
    deprecated:
        The deprecated registry names for the function
    """
    registerer = super().register

    save_params = save_params or {f"@{self.namespace[-1]}": name}

    def invoke(func, params):
        resolved = invoker(func, params) if invoker is not None else func(params)
        params_to_save = {**save_params, **params}
        for name in skip_save_params:
            params_to_save.pop(name, None)
        Config._store_resolved(resolved, params_to_save)
        return resolved

    def wrap_and_register(fn: Callable[P, R]) -> Draftable[P, R]:
        init = fn.__init__ if isinstance(fn, type) else fn
        # A class that keeps `object.__init__` takes no arguments, so there is
        # nothing to check
        if init is not object.__init__ and any(
            p.kind in (p.POSITIONAL_ONLY, p.VAR_POSITIONAL)
            for p in inspect.signature(init).parameters.values()
        ):
            raise SignatureError(fn)

        validated_fn = validate_arguments(
            fn,
            registry=self.registry,
            invoker=invoke,
        )

        registerer(name)(validated_fn)

        for deprecated_name in deprecated:

            def make_deprecated_fn(old):
                @wraps(fn, assigned=WRAPPER_ATTRIBUTES)
                def deprecated_fn(*args, **kwargs):
                    warnings.warn(
                        f'"{old}" is deprecated, please use "{name}" instead."',
                        VisibleDeprecationWarning,
                    )
                    return validated_fn(*args, **kwargs)

                return deprecated_fn

            registerer(deprecated_name)(make_deprecated_fn(deprecated_name))

        return validated_fn

    if func is not None:
        return wrap_and_register(func)
    else:
        return wrap_and_register

get_entry_points [source]

Get registered entry points from other packages for this namespace.

RETURNS (Dict[str, Any]): Entry points, keyed by name.

Source code in confit/registry.py
402
403
404
405
406
407
def get_entry_points(self):
    """Get registered entry points from other packages for this namespace.

    RETURNS (Dict[str, Any]): Entry points, keyed by name.
    """
    return importlib_metadata.entry_points(group=self.entry_point_namespace)

get [source]

Get the registered function for a given name.

Modified from catalogue.Registry.get to avoid importing all entry points when lookup fails, but rather list the available entry points.

Parameters

PARAMETER DESCRIPTION
name

The name of the function

TYPE: str

RETURNS DESCRIPTION
Func
Source code in confit/registry.py
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
def get(self, name: str):
    """
    Get the registered function for a given name.

    Modified from catalogue.Registry.get to avoid importing
    all entry points when lookup fails, but rather list the
    available entry points.

    Parameters
    ----------
    name: str
        The name of the function

    Returns
    -------
    Func
    """
    path = list(self.namespace) + [name]
    try:
        return catalogue._get(path)
    except catalogue.RegistryError:
        if self.entry_points:
            from_entry_point = self.get_entry_point(name)
            if catalogue.check_exists(*path):
                return catalogue._get(path)
            elif from_entry_point:
                return from_entry_point
        if not catalogue.check_exists(*path):
            raise catalogue.RegistryError(
                f"Can't find '{name}' in registry {' -> '.join(self.namespace)}. "
                f"Available names: "
                f"{', '.join(sorted(self.get_available())) or 'none'}"
            )
        return catalogue._get(path)

get_available [source]

Get all functions for a given namespace.

namespace (Tuple[str]): The namespace to get. RETURNS (Dict[str, Any]): The functions, keyed by name.

Source code in confit/registry.py
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
def get_available(self) -> Sequence[str]:
    """Get all functions for a given namespace.

    namespace (Tuple[str]): The namespace to get.
    RETURNS (Dict[str, Any]): The functions, keyed by name.
    """
    result = set()
    if self.entry_points:
        result.update({p.name for p in self._get_entry_points()})
    for keys in catalogue.REGISTRY.copy().keys():
        if len(self.namespace) == len(keys) - 1 and all(
            self.namespace[i] == keys[i] for i in range(len(self.namespace))
        ):
            result.add(keys[-1])
    return sorted(result)

MetaRegistryCollection [source]

Bases: type

A metaclass for the registry collection that adds it as the registry collection of all registries defined in the body of the class.

Initialize the registry collection by adding it-self as the registry collection of all registries.

Parameters

PARAMETER DESCRIPTION
name

bases

dct

Source code in confit/registry.py
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
def __init__(cls, name, bases, dct):
    """
    Initialize the registry collection by adding it-self as the registry collection
    of all registries.

    Parameters
    ----------
    name
    bases
    dct
    """
    super().__init__(name, bases, dct)
    for key, value in dct.items():
        if isinstance(value, Registry):
            value.registry = cls

RegistryCollection [source]

A collection of registries.

```python class MyRegistries(RegistryCollection): my_registry = Registry(("package_name", "my_registry"), entry_points=True) my_other_registry = Registry(("package_name", "my_other_registry"))

make_wrapper [source]

Prepare validation once, then resolve and execute each registered call

Source code in confit/registry.py
 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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def make_wrapper(raw_function, callee, config, invoker):
    """
    Prepare validation once, then resolve and execute each registered call
    """
    is_class = isinstance(callee, type)
    target = inspect.unwrap(raw_function) if is_class else raw_function
    signature = inspect.signature(target)
    parameters = signature.parameters
    self_name = next(iter(parameters)) if is_class else None
    extras_name = next(
        (name for name, p in parameters.items() if p.kind == p.VAR_KEYWORD), None
    )
    callee_name = (
        f"{callee.__module__}.{callee.__qualname__}"
        if callee.__module__
        else callee.__qualname__
    )
    # Keep Field defaults, ordinary defaults stay omitted through stacked decorators
    omitted_defaults = {
        name
        for name, p in parameters.items()
        if p.default is not inspect.Parameter.empty
        and not isinstance(p.default, FieldInfo)
    }

    @wraps(target, assigned=WRAPPER_ATTRIBUTES)
    def collect(*args, **kwargs):
        return args, kwargs

    validator = validate_call(collect, config=config)

    @wraps(raw_function, assigned=WRAPPER_ATTRIBUTES)
    def wrapper(*args, **kwargs):
        try:
            extras = {
                k: v
                for k, v in kwargs.items()
                if k not in parameters and extras_name is None
            }
            try:
                bound = signature.bind_partial(
                    *args, **{k: v for k, v in kwargs.items() if k not in extras}
                )
            except TypeError as error:
                error = TypeError(
                    str(error).replace(
                        "multiple values for argument ",
                        "multiple values for argument: ",
                    )
                )
                raise ConfitValidationError(
                    [
                        {
                            "loc": ("[signature]",),
                            "msg": str(error),
                            "type": "arguments_error",
                        }
                    ],
                    source=callee,
                    name=callee_name,
                ) from None
            values = dict(bound.arguments)
            if extras_name in values:
                values.update(values.pop(extras_name))
            values.update(extras)
            resolved = values.pop(self_name) if self_name is not None else None
            returned = None

            def invoked(kw):
                nonlocal returned, resolved
                if self_name is not None:
                    kw = {self_name: resolved, **kw}
                bound = signature.bind_partial()
                bound.arguments.update(
                    {
                        name: kw[name]
                        for name in parameters
                        if name in kw and name != extras_name
                    }
                )
                extras = {
                    k: v
                    for k, v in kw.items()
                    if k not in parameters or k == extras_name
                }
                if extras and extras_name is None:
                    raise ConfitValidationError(
                        [
                            {
                                "loc": (name,),
                                "msg": "unexpected keyword argument",
                                "type": "unexpected_keyword_argument",
                            }
                            for name in extras
                        ],
                        source=callee,
                        name=callee_name,
                    )
                try:
                    validated_args, validated_kwargs = validator(
                        *bound.args, **bound.kwargs, **extras
                    )
                except (ValidationError, ConfitValidationError) as error:
                    raise ConfitValidationError.from_exception(
                        error,
                        source=callee,
                        name=callee_name,
                        signature=signature,
                    ) from None
                # Let the function supply its ordinary defaults
                call = signature.bind_partial(*validated_args, **validated_kwargs)
                for name in omitted_defaults - bound.arguments.keys():
                    call.arguments.pop(name, None)
                returned = raw_function(*call.args, **call.kwargs)
                if self_name is None:
                    resolved = returned
                return resolved

            if invoker is None:
                invoked(values)
            else:
                invoker(invoked, values)
            return returned
        except Exception as error:
            context = error.__context__ if is_class else error.__cause__
            if not is_debug() and isinstance(
                context, (ValidationError, ConfitValidationError)
            ):
                error.__cause__ = None
                error.__suppress_context__ = True
            raise error.with_traceback(remove_lib_from_traceback(error.__traceback__))

    return wrapper

validate_arguments [source]

Validate function or constructor arguments and support deferred calls with draft

Parameters

PARAMETER DESCRIPTION
func

The function or class to call

TYPE: Optional[Callable[P, R]] DEFAULT: None

config

The validation configuration object

TYPE: Dict DEFAULT: None

invoker

An optional invoker to apply on the validated function

TYPE: Optional[Callable[[Callable, Dict[str, Any]], Any]] DEFAULT: None

registry

The registry to use to resolve the default parameters

TYPE: Any DEFAULT: None

RETURNS DESCRIPTION
Callable[[Callable[P, R]], Draftable[P, R]]:
Source code in confit/registry.py
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
def validate_arguments(
    func: Optional[Callable[P, R]] = None,
    *,
    config: Dict = None,
    invoker: Optional[Callable[[Callable, Dict[str, Any]], Any]] = None,
    registry: Any = None,
) -> Callable[[Callable[P, R]], Draftable[P, R]]:
    """
    Validate function or constructor arguments and support deferred calls with draft

    Parameters
    ----------
    func: Optional[Callable[P, R]]
        The function or class to call
    config: Dict
        The validation configuration object
    invoker: Optional[Callable]
        An optional invoker to apply on the validated function
    registry: Any
        The registry to use to resolve the default parameters

    Returns
    -------
    Callable[[Callable[P, R]], Draftable[P, R]]:
    """
    config = {**(config or {}), "arbitrary_types_allowed": True}

    def validate(_func: Callable) -> Callable:
        is_class = isinstance(_func, type)
        raw_function = _func.__init__ if is_class else _func
        if raw_function is object.__init__:
            # Since `object.__init__` has no named parameters to validate, we
            # validate against an empty constructor to reject unexpected arguments
            def raw_function(self):
                pass

        wrapper = make_wrapper(raw_function, _func, config, invoker)
        draft_type = Draft[_func] if is_class else Draft

        @wraps(raw_function, assigned=WRAPPER_ATTRIBUTES)
        def draft(**kwargs):
            return draft_type(_func, kwargs)

        if is_class:
            old_get_validators = getattr(_func, "__get_validators__", None)
            old_get_pydantic_core_schema = getattr(
                _func, "__get_pydantic_core_schema__", None
            )

            def __get_pydantic_core_schema__(*args, **kwargs):
                from pydantic_core import core_schema

                def pre_validate(value):
                    if isinstance(value, dict):
                        value = Config(value).resolve(registry=registry)
                    return value

                def post_validate(value):
                    if isinstance(value, _func):
                        return value

                    return _func(**value)

                steps = [core_schema.no_info_plain_validator_function(pre_validate)]
                if old_get_pydantic_core_schema is not None:
                    steps.append(old_get_pydantic_core_schema(*args, **kwargs))
                elif old_get_validators is not None:
                    steps.extend(
                        legacy_validator_schema(fn) for fn in old_get_validators()
                    )
                steps.append(
                    core_schema.no_info_plain_validator_function(post_validate)
                )
                return core_schema.chain_schema(steps)

            _func.__get_pydantic_core_schema__ = __get_pydantic_core_schema__
            _func.__init__ = wrapper
        result = _func if is_class else wrapper
        result.draft = draft
        return result

    return validate(func) if func is not None else validate

get_default_registry [source]

Get the default registered registry.

RETURNS DESCRIPTION
Registry
Source code in confit/registry.py
503
504
505
506
507
508
509
510
511
def get_default_registry() -> Any:
    """
    Get the default registered registry.

    Returns
    -------
    Registry
    """
    return _default_registry

set_default_registry [source]

Set the default registered registry. This is used in Config.resolve() when no registry is provided.

Parameters

PARAMETER DESCRIPTION
registry

TYPE: CustomRegistry

RETURNS DESCRIPTION
Registry
Source code in confit/registry.py
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
def set_default_registry(registry: CustomRegistry) -> CustomRegistry:
    """
    Set the default registered registry. This is used in
    [`Config.resolve()`][confit.config.Config.resolve] when no registry is provided.

    Parameters
    ----------
    registry: Registry

    Returns
    -------
    Registry
    """
    global _default_registry
    _default_registry = registry
    return registry