Coverage for src/debputy/plugin/debputy/manifest_root_rules.py: 80%

58 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-01-27 13:59 +0000

1from typing import List, Any, Dict, Tuple, TYPE_CHECKING, cast 

2 

3from debputy._manifest_constants import ( 

4 ManifestVersion, 

5 MK_MANIFEST_VERSION, 

6 MK_INSTALLATIONS, 

7 SUPPORTED_MANIFEST_VERSIONS, 

8 MK_MANIFEST_DEFINITIONS, 

9 MK_PACKAGES, 

10 MK_MANIFEST_VARIABLES, 

11) 

12from debputy.exceptions import DebputySubstitutionError 

13from debputy.installations import InstallRule 

14from debputy.manifest_parser.exceptions import ManifestParseException 

15from debputy.manifest_parser.parser_data import ParserContextData 

16from debputy.manifest_parser.tagging_types import DebputyParsedContent 

17from debputy.manifest_parser.util import AttributePath 

18from debputy.plugin.api.impl import DebputyPluginInitializerProvider 

19from debputy.plugin.api.parser_tables import ( 

20 OPARSER_MANIFEST_ROOT, 

21 OPARSER_MANIFEST_DEFINITIONS, 

22 OPARSER_PACKAGES, 

23) 

24from debputy.plugin.api.spec import ( 

25 not_integrations, 

26 INTEGRATION_MODE_DH_DEBPUTY_RRR, 

27) 

28from debputy.plugin.debputy.build_system_rules import register_build_system_rules 

29from debputy.substitution import VariableNameState, SUBST_VAR_RE 

30 

31if TYPE_CHECKING: 

32 from debputy.highlevel_manifest_parser import YAMLManifestParser 

33 

34 

35def register_manifest_root_rules(api: DebputyPluginInitializerProvider) -> None: 

36 # Registration order matters. Notably, definitions must come before anything that can 

37 # use definitions (variables), which is why it is second only to the manifest version. 

38 api.pluggable_manifest_rule( 

39 OPARSER_MANIFEST_ROOT, 

40 MK_MANIFEST_VERSION, 

41 ManifestVersionFormat, 

42 _handle_version, 

43 source_format=ManifestVersion, 

44 ) 

45 api.pluggable_object_parser( 

46 OPARSER_MANIFEST_ROOT, 

47 MK_MANIFEST_DEFINITIONS, 

48 object_parser_key=OPARSER_MANIFEST_DEFINITIONS, 

49 on_end_parse_step=lambda _a, _b, _c, mp: mp._ensure_package_states_is_initialized(), 

50 ) 

51 api.pluggable_manifest_rule( 

52 OPARSER_MANIFEST_DEFINITIONS, 

53 MK_MANIFEST_VARIABLES, 

54 ManifestVariablesParsedFormat, 

55 _handle_manifest_variables, 

56 source_format=Dict[str, str], 

57 ) 

58 api.pluggable_manifest_rule( 

59 OPARSER_MANIFEST_ROOT, 

60 MK_INSTALLATIONS, 

61 List[InstallRule], 

62 _handle_installation_rules, 

63 expected_debputy_integration_mode=not_integrations( 

64 INTEGRATION_MODE_DH_DEBPUTY_RRR 

65 ), 

66 ) 

67 api.pluggable_object_parser( 

68 OPARSER_MANIFEST_ROOT, 

69 MK_PACKAGES, 

70 object_parser_key=OPARSER_PACKAGES, 

71 on_end_parse_step=lambda _a, _b, _c, mp: mp._ensure_package_states_is_initialized(), 

72 nested_in_package_context=True, 

73 ) 

74 

75 register_build_system_rules(api) 

76 

77 

78class ManifestVersionFormat(DebputyParsedContent): 

79 manifest_version: ManifestVersion 

80 

81 

82class ListOfInstallRulesFormat(DebputyParsedContent): 

83 elements: List[InstallRule] 

84 

85 

86class DictFormat(DebputyParsedContent): 

87 mapping: Dict[str, Any] 

88 

89 

90class ManifestVariablesParsedFormat(DebputyParsedContent): 

91 variables: Dict[str, str] 

92 

93 

94def _handle_version( 

95 _name: str, 

96 parsed_data: ManifestVersionFormat, 

97 _attribute_path: AttributePath, 

98 _parser_context: ParserContextData, 

99) -> str: 

100 manifest_version = parsed_data["manifest_version"] 

101 if manifest_version not in SUPPORTED_MANIFEST_VERSIONS: 101 ↛ 102line 101 didn't jump to line 102 because the condition on line 101 was never true

102 raise ManifestParseException( 

103 "Unsupported manifest-version. This implementation supports the following versions:" 

104 f' {", ".join(repr(v) for v in SUPPORTED_MANIFEST_VERSIONS)}"' 

105 ) 

106 return manifest_version 

107 

108 

109def _handle_manifest_variables( 

110 _name: str, 

111 parsed_data: ManifestVariablesParsedFormat, 

112 variables_path: AttributePath, 

113 parser_context: ParserContextData, 

114) -> None: 

115 variables = parsed_data.get("variables", {}) 

116 resolved_vars: Dict[str, Tuple[str, AttributePath]] = {} 

117 manifest_parser: "YAMLManifestParser" = cast("YAMLManifestParser", parser_context) 

118 substitution = manifest_parser.substitution 

119 for key, value_raw in variables.items(): 

120 key_path = variables_path[key] 

121 if not SUBST_VAR_RE.match("{{" + key + "}}"): 121 ↛ 122line 121 didn't jump to line 122 because the condition on line 121 was never true

122 raise ManifestParseException( 

123 f"The variable at {key_path.path_key_lc} has an invalid name and therefore cannot" 

124 " be used." 

125 ) 

126 if substitution.variable_state(key) != VariableNameState.UNDEFINED: 

127 raise ManifestParseException( 

128 f'The variable "{key}" is already reserved/defined. Error triggered by' 

129 f" {key_path.path_key_lc}." 

130 ) 

131 try: 

132 value = substitution.substitute(value_raw, key_path.path) 

133 except DebputySubstitutionError: 

134 if not resolved_vars: 

135 raise 

136 # See if flushing the variables work 

137 substitution = manifest_parser.add_extra_substitution_variables( 

138 **resolved_vars 

139 ) 

140 resolved_vars = {} 

141 value = substitution.substitute(value_raw, key_path.path) 

142 resolved_vars[key] = (value, key_path) 

143 substitution = manifest_parser.add_extra_substitution_variables(**resolved_vars) 

144 

145 

146def _handle_installation_rules( 

147 _name: str, 

148 parsed_data: List[InstallRule], 

149 _attribute_path: AttributePath, 

150 _parser_context: ParserContextData, 

151) -> List[Any]: 

152 return parsed_data 

153 

154 

155def _handle_opaque_dict( 

156 _name: str, 

157 parsed_data: DictFormat, 

158 _attribute_path: AttributePath, 

159 _parser_context: ParserContextData, 

160) -> Dict[str, Any]: 

161 return parsed_data["mapping"]