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

1"""Useful decorators for Traitlets users.""" 

2from __future__ import annotations 

3 

4import copy 

5from inspect import Parameter, Signature, signature 

6from typing import Any, Type, TypeVar 

7 

8from ..traitlets import HasTraits, Undefined 

9 

10 

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 

14 

15 

16T = TypeVar("T", bound=HasTraits) 

17 

18 

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 ] 

26 

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) 

30 

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 

35 

36 for parameter_name in old_signature.parameters: 

37 # Copy the parameter 

38 parameter = copy.copy(old_signature.parameters[parameter_name]) 

39 

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) 

45 

46 elif parameter.kind is Parameter.VAR_POSITIONAL: 

47 old_var_positional_parameter = parameter 

48 

49 elif parameter.kind is Parameter.KEYWORD_ONLY: 

50 old_keyword_only_parameters.append(parameter) 

51 

52 elif parameter.kind is Parameter.VAR_KEYWORD: 

53 old_var_keyword_parameter = parameter 

54 

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 ) 

61 

62 new_parameters = [] 

63 

64 # Append the old positional parameters (except `self` which is the first parameter) 

65 new_parameters += old_positional_parameters[1:] 

66 

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) 

70 

71 # Append the old keyword only parameters 

72 new_parameters += old_keyword_only_parameters 

73 

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 ] 

80 

81 # Append **kwargs 

82 new_parameters.append(old_var_keyword_parameter) 

83 

84 cls.__signature__ = Signature(new_parameters) # type:ignore[attr-defined] 

85 

86 return cls