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
« 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
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)
14class ExperimentalDebputyPluginDefinition(DebputyPluginDefinition):
15 """Plugin definition entity for plugins that use experimental API
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 """
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
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.
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.
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`.
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`).
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)
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 """
86 def _decorate(f: PackageProcessor) -> PackageProcessor:
88 final_id = self._name2id(processor_id, f.__name__)
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 )
98 self._generic_initializers.append(_init)
100 return f
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
106 def initialize(self, api: "DebputyPluginInitializerProvider") -> None:
107 """Initialize the plugin from this definition
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.
115 :param api: The plugin initializer provided by the `debputy` plugin system
116 """
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 )
126 for initializer in initializers:
127 initializer(api)
130def define_debputy_plugin_experimental_api() -> ExperimentalDebputyPluginDefinition:
131 return ExperimentalDebputyPluginDefinition()