Hooks¶
Overview¶
pubtools projects can be extended via a hook/event/callback system based on pluggy.
This system is mainly intended for use as a publish-subscribe event dispatcher and enables the following scenarios:
When something happens in
pubtools-foo
, trigger some code inpubtools-bar
, withoutpubtools-foo
having a direct dependency onpubtools-bar
.When something happens in
pubtools-foo
, trigger some code in the hosting service if the task is running hosted; otherwise, do nothing.
All features provided by pluggy may be used as normal. The next few sections provide a crash course on basic usage of pluggy.
Guide: hook providers¶
If you are implementing a pubtools
project and you want to provide hooks
(which can also be considered “emitting events”), you should do the following:
1. Define hookspecs¶
You need to specify your hooks before using them. This can be done by defining
some functions with @hookspec
decorator.
# Import the pluggy objects bound to 'pubtools' namespace
from pubtools.pluggy import pm, hookspec
import sys
# Define a hookspec. This is just a plain old function, typically
# with an empty implementation (only doc string).
#
# The doc string should explain the circumstances under which this
# hook is invoked.
@hookspec
def kettle_on(kettle):
"""Called immediately before a kettle is turned on."""
@hookspec
def kettle_off(kettle):
"""Called after a kettle has been turned off."""
# Add these hookspecs onto the plugin manager.
pm.add_hookspecs(sys.modules[__name__])
Be aware that:
There is one flat namespace for hooks across all
pubtools
projects, so you must take care to avoid name clashesYou must ensure that the module containing your hookspecs is imported when your library is imported. If you forget to import the module, your hooks won’t be available.
2. Invoke hooks¶
At the appropriate times according to the documented behavior of your hooks,
invoke them via pm.hook
.
from pubtools.pluggy import pm
def make_tea(...):
kettle.fill_water()
# notify anyone interested that we're turning the kettle on
pm.hook.kettle_on(kettle=kettle)
kettle.start_boil()
kettle.wait_boil()
# notify again
pm.hook.kettle_off(kettle=kettle)
# Proceed as normal
cup.fill_water(source=kettle)
# (...)
When a hook is called, any plugins with matching registered @hookimpls
will be
invoked in LIFO order. If no plugins have registered, nothing will happen.
The above example shows a pure event emitter. More advanced patterns are possible, such as making use of values returned by hooks. See the pluggy documentation for more information.
Guide: hook implementers¶
If you want to implement hooks (which can also be considered as “receiving events”),
you need to declare hook implementations in your project and register them with
the pubtools plugin manager. Note that it is not necessary for a project to belong
to the pubtools
project family in order to do this.
Hook implementations can be defined using the @hookimpl
decorator. The function
names and signatures should match exactly the names of the corresponding hookspecs.
# Import the pluggy objects bound to 'pubtools' namespace
from pubtools.pluggy import pm, hookimpl
import sys
# Define a hookimpl. Match exactly the hookspecs declared
# in the previous example.
@hookimpl
def kettle_on(kettle):
# Kettle causes huge spike in power consumption, we'd better
# enable the power reserves.
# https://en.wikipedia.org/wiki/TV_pickup
powerbank.enable()
@hookimpl
def kettle_off(kettle):
# Don't need the extra power any more
powerbank.disable()
# Register this module as a plugin.
pm.register(sys.modules[__name__])
Your module must be imported for your hookimpls to take effect.
If you’re unsure whether your module will be imported, you may declare entry
points in the pubtools.hooks
group; pubtools will enforce that all modules
in this group are imported when task_context
is invoked.
For example, in setup.py, one may declare:
entry_points={
"pubtools.hooks": [
# Left-hand side can be anything.
# Right-hand side is the name of your module containing a
# call to "pm.register".
"hooks = pubtools.foo._impl.hooks",
]
},
Be aware that:
Your hookimpl could be invoked by any thread. Blocking the current thread may be inappropriate. It’s best to do only small amounts of work within a hookimpl.
If your hookimpl raises an exception, it will propagate upwards through to the hook caller.
Guide: managing context¶
In the above naive example, hookimpls are standalone functions within a module, with no mechanism for passing context between themselves. For instance, the above example can’t maintain a count of boiling kettles (excluding antipatterns such as mutating globals).
A more realistic approach of registering hookimpls which allows passing around some context is to register your plugins in a two-stage process:
First, attach to some initial event allowing you to establish context. pubtools provides
task_start()
for this purpose.When that hook is invoked, set up desired context and then register additional hooks.
# Import the pluggy objects bound to 'pubtools' namespace
from pubtools.pluggy import pm, hookimpl
import sys
class KettleSpikeHandler:
def __init__(self):
# Do anything I like here - configure myself from
# settings, etc.
self.powerbank = (...)
# We can now maintain state between hookimpls.
self.kettlecount = 0
# Instance methods work OK for hookimpls.
@hookimpl
def kettle_on(self, kettle):
self.kettlecount += 1
self.powerbank.enable()
@hookimpl
def kettle_off(self, kettle):
self.kettlecount -= 1
if not self.kettlecount:
self.powerbank.disable()
@hookimpl
def task_start():
# When task starts, we register an instance of this object
# as an additional plugin. This allows the object to keep
# its own state between calls to different hookimpls.
pm.register(KettleSpikeHandler())
# Register this module as a plugin.
# This will only register the top-level 'task_start' hookimpl.
pm.register(sys.modules[__name__])
task_start()
and task_stop()
are two standard hooks intended for
use in setting up or tearing down a plugin context. Note that these are only
conventions and may not be used uniformly across all pubtools task libraries.
API reference¶
- pubtools.pluggy.pm¶
- Type:
A PluginManager configured for the
pubtools
namespace.
- pubtools.pluggy.hookspec¶
A hookspec decorator configured for the
pubtools
namespace.
- pubtools.pluggy.hookimpl¶
A hookimpl decorator configured for the
pubtools
namespace.
- pubtools.pluggy.task_context()[source]¶
Run a block of code within a task context, ensuring task lifecycle hooks are invoked when appropriate.
This is a context manager for use within pubtools task libraries where hooks shall be provided. It can be used in a
with
statement, as in example:>>> with task_context(): >>> self.do_task()
The context manager will ensure that:
hookspecs/hookimpls are resolved across all installed libraries.
task_start()
is invoked when the block is entered.task_stop()
is invoked when the block is exited.
Hook reference¶
This section lists all known hooks defined in the pubtools namespace.
Library |
Hook |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- task_start()¶
Called when a task starts.
This hook can be used to register additional hook implementations with desired context.
- task_stop(failed)¶
Called when a task ends.
If
task_start()
was used to register additional hook implementations, this hook should be used to unregister them.- Parameters:
failed (bool) – True if the task is failing (i.e. exiting with non-zero exit code, or raising an exception).
- get_cert_key_paths(server_url)¶
Get location of SSL certificates used to authenticate with a given service.
If there are multiple hook implementations and multiple values are returned, the first non-empty answer is considered canonical. The first answer is returned by the hook implementation which was registered last.
The certificates are expected to be in PEM format. It’s permitted for paths to cert and key to be the same. Callers of this hook should be prepared to receive no result, and should implement a reasonable fallback strategy in that case.
- otel_exporter()¶
Return an OTEL exporter, used by OTEL instrumentation.
If OTEL tracing is enabled and this hook is not implemented, a default ConsoleSpanExporter will be used.
- Returns:
Instance of SpanExporter.
- Return type:
opentelemetry.sdk.trace.export.SpanExporter
- pulp_repository_pre_publish(repository, options)¶
Invoked as the first step in publishing a Pulp repository.
If a hookimpl returns a non-None value, that value will be used to replace the options for this publish. This can be used to adjust publish options from within a hook.
- Args:
- repository (
Repository
): The repository to be published.
- options (
PublishOptions
): The options to use in publishing.
- repository (
- Returns:
- options (
PublishOptions
): The potentially adjusted options used for this publish.
- options (
- pulp_repository_published(repository, options)¶
Invoked after a Pulp repository has been successfully published.
- Args:
- repository (
Repository
): The repository which has been published.
- options (
PublishOptions
): The options used for this publish.
- repository (
- task_pulp_flush()¶
Invoked during task execution after successful completion of all Pulp publishes.
This hook is invoked a maximum of once per task, to indicate that all Pulp content associated with the task is considered fully up-to-date. The intended usage is to flush Pulp-derived caches or to notify systems that Pulp content may have recently changed.
- pulp_item_push_finished(pulp_units, push_item)¶
Invoked during push tasks after each item has been processed fully.
By the time this hook is invoked, the referenced item and unit is expected to be fully uploaded into Pulp and published onto the CDN.
- Args:
- pulp_units (list[
Unit
]) A list of zero or more Pulp unit(s) created/updated for this item. Note that this information may not be available for every content type, and may only contain a subset of the Pulp fields.
- push_item (
PushItem
) The item which has been pushed.
- pulp_units (list[
- quay_repositories_cleared(repository_ids: List[str]) None ¶
Invoked after repositories have been cleared on Quay.
- quay_repositories_removed(repository_ids: List[str]) None ¶
Invoked after repositories have been removed from Quay.
- quay_images_tagged(source_ref: str, dest_refs: List[str]) None ¶
Invoked after tagging image(s) on Quay.