Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/traitlets/utils/decorators.py: 22%
37 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""Useful decorators for Traitlets users."""
2from __future__ import annotations
4import copy
5from inspect import Parameter, Signature, signature
6from typing import Any, Type, TypeVar
8from ..traitlets import HasTraits, Undefined
11def _get_default(value: Any) -> Any:
12 """Get default argument value, given the trait default value."""
13 return Parameter.empty if value == Undefined else value
16T = TypeVar("T", bound=HasTraits)
19def signature_has_traits(cls: Type[T]) -> Type[T]:
20 """Return a decorated class with a constructor signature that contain Trait names as kwargs."""
21 traits = [
22 (name, _get_default(value.default_value))
23 for name, value in cls.class_traits().items()
24 if not name.startswith("_")
25 ]
27 # Taking the __init__ signature, as the cls signature is not initialized yet
28 old_signature = signature(cls.__init__)
29 old_parameter_names = list(old_signature.parameters)
31 old_positional_parameters = []
32 old_var_positional_parameter = None # This won't be None if the old signature contains *args
33 old_keyword_only_parameters = []
34 old_var_keyword_parameter = None # This won't be None if the old signature contains **kwargs
36 for parameter_name in old_signature.parameters:
37 # Copy the parameter
38 parameter = copy.copy(old_signature.parameters[parameter_name])
40 if (
41 parameter.kind is Parameter.POSITIONAL_ONLY
42 or parameter.kind is Parameter.POSITIONAL_OR_KEYWORD
43 ):
44 old_positional_parameters.append(parameter)
46 elif parameter.kind is Parameter.VAR_POSITIONAL:
47 old_var_positional_parameter = parameter
49 elif parameter.kind is Parameter.KEYWORD_ONLY:
50 old_keyword_only_parameters.append(parameter)
52 elif parameter.kind is Parameter.VAR_KEYWORD:
53 old_var_keyword_parameter = parameter
55 # Unfortunately, if the old signature does not contain **kwargs, we can't do anything,
56 # because it can't accept traits as keyword arguments
57 if old_var_keyword_parameter is None:
58 raise RuntimeError(
59 f"The {cls} constructor does not take **kwargs, which means that the signature can not be expanded with trait names"
60 )
62 new_parameters = []
64 # Append the old positional parameters (except `self` which is the first parameter)
65 new_parameters += old_positional_parameters[1:]
67 # Append *args if the old signature had it
68 if old_var_positional_parameter is not None:
69 new_parameters.append(old_var_positional_parameter)
71 # Append the old keyword only parameters
72 new_parameters += old_keyword_only_parameters
74 # Append trait names as keyword only parameters in the signature
75 new_parameters += [
76 Parameter(name, kind=Parameter.KEYWORD_ONLY, default=default)
77 for name, default in traits
78 if name not in old_parameter_names
79 ]
81 # Append **kwargs
82 new_parameters.append(old_var_keyword_parameter)
84 cls.__signature__ = Signature(new_parameters) # type:ignore[attr-defined]
86 return cls