Coverage for src/debputy/plugin/api/experimental.py: 59%

26 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-09-07 09:27 +0000

1import re 

2from typing import Union, Callable, Optional, Iterable, Iterator, Any 

3 

4from debputy.exceptions import PluginInitializationError 

5from debputy.plugin.api import VirtualPath 

6from debputy.plugin.api.impl import DebputyPluginInitializerProvider 

7from debputy.plugin.api.spec import ( 

8 DebputyPluginDefinition, 

9 PackageProcessor, 

10 PackageTypeSelector, 

11) 

12 

13 

14class ExperimentalDebputyPluginDefinition(DebputyPluginDefinition): 

15 """Plugin definition entity for plugins that use experimental API 

16 

17 The plugin definition provides a way for the plugin code to register the features it provides via 

18 accessors. This particular instance also provides accessors for experimental or yet-to-be-final 

19 APIs. These APIs generally requires the plugin to be pre-registered with `debputy`, so `debputy` 

20 can add `Breaks` on the plugin provider if/when the API changes. 

21 """ 

22 

23 def package_processor( 

24 self, 

25 func: Optional[PackageProcessor] = None, 

26 *, 

27 processor_id: Optional[str] = None, 

28 depends_on_processor: Iterable[str] = tuple(), 

29 package_type: PackageTypeSelector = "deb", 

30 ) -> Union[ 

31 Callable[[PackageProcessor], PackageProcessor], 

32 PackageProcessor, 

33 ]: 

34 """Provide a pre-assembly hook that can affect the contents of the binary ("deb") packages 

35 

36 The provided hook will be run once per binary package to be assembled, and it can see all the content 

37 ("data.tar") planned to be included in the deb. It has read-write access to the data contents. 

38 

39 The hook will be run unconditionally for all binary packages built. When the hook does not apply to all 

40 packages, it must provide its own (internal) logic for detecting whether it is relevant and reduce itself 

41 to a no-op if it should not apply to the current package. 

42 

43 Hooks between plugins run in "some implementation defined order" and should not rely on being run before 

44 or after any other hook. However, the hooks can depend on other hooks from the same plugin by using 

45 the `depends_on_processor`. 

46 

47 The hooks are only applied to packages defined in `debian/control`. Notably, the processor will 

48 not apply to auto-generated `-dbgsym` packages (as those are not listed explicitly in `debian/control`). 

49 

50 >>> plugin_definition = define_debputy_plugin_experimental_api() 

51 >>> def _la_files(fs_root: VirtualPath) -> Iterator[VirtualPath]: 

52 ... ... # Implementation skipped here for brevity 

53 >>> 

54 >>> @plugin_definition.package_processor 

55 ... def clean_la_files( 

56 ... fs_root: "VirtualPath", 

57 ... _: Any, # Not yet defined/provided 

58 ... context: "PackageProcessingContext", 

59 ... ) -> None: 

60 ... for path in _la_files(fs_root): 

61 ... buffer = [] 

62 ... with path.open(byte_io=True) as fd: 

63 ... replace_file = False 

64 ... for line in fd: 

65 ... if line.startswith(b"dependency_libs"): 

66 ... replacement = re.sub(b"<real_pattern_here>", b"''", line) 

67 ... if replacement != line: 

68 ... replace_file = True 

69 ... line = replacement 

70 ... buffer.append(line) 

71 ... if not replace_file: 

72 ... continue 

73 ... with path.replace_fs_path_content() as fs_path, open(fs_path, "wb") as wfd: 

74 ... wfd.writelines(buffer) 

75 

76 :param func: The function to be decorated/registered. 

77 :param processor_id: A plugin-wide unique ID for this detector. Packagers may use this ID for disabling 

78 the detector and accordingly the ID is part of the plugin's API toward the packager. 

79 :param depends_on_processor: Denote processors from the same plugin that must run before this processor. 

80 It is not possible to depend on processors from other plugins. 

81 :param package_type: Which kind of packages this metadata detector applies to. The package type is generally 

82 defined by `Package-Type` field in the binary package. The default is to only run for regular `deb` packages 

83 and ignore `udeb` packages. 

84 """ 

85 

86 def _decorate(f: PackageProcessor) -> PackageProcessor: 

87 

88 final_id = self._name2id(processor_id, f.__name__) 

89 

90 def _init(api: "DebputyPluginInitializerProvider") -> None: 

91 api.package_processor( 

92 final_id, 

93 f, 

94 depends_on_processor=depends_on_processor, 

95 package_type=package_type, 

96 ) 

97 

98 self._generic_initializers.append(_init) 

99 

100 return f 

101 

102 if func: 102 ↛ 104line 102 didn't jump to line 104 because the condition on line 102 was always true

103 return _decorate(func) 

104 return _decorate 

105 

106 def initialize(self, api: "DebputyPluginInitializerProvider") -> None: 

107 """Initialize the plugin from this definition 

108 

109 Most plugins will not need this function as the plugin loading will call this function 

110 when relevant. However, a plugin can call this manually from a function-based initializer. 

111 This is mostly useful if the function-based initializer need to set up a few features 

112 that the `DebputyPluginDefinition` cannot do and the mix/match approach is not too 

113 distracting for plugin maintenance. 

114 

115 :param api: The plugin initializer provided by the `debputy` plugin system 

116 """ 

117 

118 initializers = self._generic_initializers 

119 if not initializers: 

120 plugin_name = api.plugin_metadata.plugin_name 

121 raise PluginInitializationError( 

122 f"Initialization of {plugin_name}: The plugin definition was never used to register any features." 

123 " If you want to conditionally register features, please use an initializer functon instead." 

124 ) 

125 

126 for initializer in initializers: 

127 initializer(api) 

128 

129 

130def define_debputy_plugin_experimental_api() -> ExperimentalDebputyPluginDefinition: 

131 return ExperimentalDebputyPluginDefinition()