Coverage for sources/librovore/xtnsmgr/configuration.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.10.4, created at 2025-08-20 22:48 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Extension configuration loading and validation. ''' 

22 

23 

24from . import __ 

25 

26 

27ExtensionArguments: __.typx.TypeAlias = __.typx.Annotated[ 

28 __.cabc.Mapping[ str, __.typx.Any ], 

29 __.ddoc.Doc( ''' Arguments to pass to extension/processor. ''' ) 

30] 

31ExtensionConfig: __.typx.TypeAlias = __.typx.Annotated[ 

32 __.cabc.Mapping[ str, __.typx.Any ], 

33 __.ddoc.Doc( ''' Configuration for a single extension/processor. ''' ) 

34] 

35 

36 

37def validate_extension( config: ExtensionConfig ) -> None: 

38 ''' Validates single extension configuration. ''' 

39 name = config.get( 'name' ) 

40 if not name or not isinstance( name, str ): 

41 raise __.ExtensionConfigurationInvalidity( 

42 name or '<unnamed>', 

43 "Required field 'name' must be a non-empty string" ) 

44 enabled = config.get( 'enabled', True ) 

45 if not isinstance( enabled, bool ): 

46 raise __.ExtensionConfigurationInvalidity( 

47 name, "Field 'enabled' must be a boolean" ) 

48 package = config.get( 'package' ) 

49 if package is not None and not isinstance( package, str ): 

50 raise __.ExtensionConfigurationInvalidity( 

51 name, "Field 'package' must be a string" ) 

52 arguments = config.get( 'arguments', { } ) 

53 if not isinstance( arguments, dict ): 

54 raise __.ExtensionConfigurationInvalidity( 

55 name, "Field 'arguments' must be a dictionary" ) 

56 

57 

58def extract_inventory_extensions( 

59 auxdata: __.Globals 

60) -> tuple[ ExtensionConfig, ... ]: 

61 ''' Loads and validates inventory extensions configuration. ''' 

62 return _extract_extension_type( auxdata, 'inventory-extensions' ) 

63 

64 

65def extract_structure_extensions( 

66 auxdata: __.Globals 

67) -> tuple[ ExtensionConfig, ... ]: 

68 ''' Loads and validates structure extensions configuration. ''' 

69 return _extract_extension_type( auxdata, 'structure-extensions' ) 

70 

71 

72def extract_extensions( 

73 auxdata: __.Globals 

74) -> tuple[ ExtensionConfig, ... ]: 

75 ''' Loads and validates all extensions configuration (legacy). ''' 

76 inventory_extensions = extract_inventory_extensions( auxdata ) 

77 structure_extensions = extract_structure_extensions( auxdata ) 

78 legacy_extensions = _extract_extension_type( auxdata, 'extensions' ) 

79 return inventory_extensions + structure_extensions + legacy_extensions 

80 

81 

82def _extract_extension_type( 

83 auxdata: __.Globals, 

84 extension_type: str 

85) -> tuple[ ExtensionConfig, ... ]: 

86 ''' Loads and validates extensions of specific type. ''' 

87 configuration = auxdata.configuration 

88 if not configuration: return ( ) 

89 raw = configuration.get( extension_type, [ ] ) 

90 if not isinstance( raw, list ): 

91 raise __.ExtensionConfigurationInvalidity( 

92 '<root>', f"Configuration '{extension_type}' must be a list" ) 

93 raw = __.typx.cast( list[ __.typx.Any ], raw ) 

94 extensions: list[ ExtensionConfig ] = [ ] 

95 for i, config in enumerate( raw ): 

96 if not isinstance( config, dict ): 

97 raise __.ExtensionConfigurationInvalidity( 

98 f'<{extension_type}[{i}]>', 

99 f"{extension_type.title()} configuration must be dict" ) 

100 typed_config = __.typx.cast( ExtensionConfig, config ) 

101 validate_extension( typed_config ) 

102 extensions.append( typed_config ) 

103 return tuple( extensions ) 

104 

105 

106def select_active_extensions( 

107 extensions: __.cabc.Sequence[ ExtensionConfig ] 

108) -> tuple[ ExtensionConfig, ... ]: 

109 ''' Filters extensions to only enabled ones. ''' 

110 return tuple( ext for ext in extensions if ext.get( 'enabled', True ) ) 

111 

112 

113def select_intrinsic_extensions( 

114 extensions: __.cabc.Sequence[ ExtensionConfig ] 

115) -> tuple[ ExtensionConfig, ... ]: 

116 ''' Filters extensions to only built-in ones (no package field). ''' 

117 return tuple( ext for ext in extensions if ext.get( 'package' ) is None ) 

118 

119 

120def select_external_extensions( 

121 extensions: __.cabc.Sequence[ ExtensionConfig ] 

122) -> tuple[ ExtensionConfig, ... ]: 

123 ''' Filters extensions to only external ones (has package field). ''' 

124 return tuple( 

125 ext for ext in extensions if ext.get( 'package' ) is not None ) 

126 

127 

128def extract_extension_arguments( 

129 extension: ExtensionConfig 

130) -> ExtensionArguments: 

131 ''' Extracts arguments dictionary from extension configuration. ''' 

132 return extension.get( 'arguments', { } )