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

41 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-09-28 22:09 +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 

72 

73 

74def _extract_extension_type( 

75 auxdata: __.Globals, 

76 extension_type: str 

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

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

79 configuration = auxdata.configuration 

80 if not configuration: return ( ) 

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

82 if not isinstance( raw, list ): 

83 raise __.ExtensionConfigurationInvalidity( 

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

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

86 extensions: list[ ExtensionConfig ] = [ ] 

87 for i, config in enumerate( raw ): 

88 if not isinstance( config, dict ): 

89 raise __.ExtensionConfigurationInvalidity( 

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

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

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

93 validate_extension( typed_config ) 

94 extensions.append( typed_config ) 

95 return tuple( extensions ) 

96 

97 

98def select_active_extensions( 

99 extensions: __.cabc.Sequence[ ExtensionConfig ] 

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

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

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

103 

104 

105def select_intrinsic_extensions( 

106 extensions: __.cabc.Sequence[ ExtensionConfig ] 

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

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

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

110 

111 

112def extract_extension_arguments( 

113 extension: ExtensionConfig 

114) -> ExtensionArguments: 

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

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