Building Pluggable Python Applications

How Pluggy works

Building a pluggable application

requests  # This is needed for the plugins
pluggy
# main.pyclass App:
def display_jokes(self, amount: int) -> None:
results = [[]]
jokes = [joke for plugin in results for joke in plugin]
for joke in jokes:
print(joke)
if __name__ == "__main__":
app = App()
app.display_jokes(2)
# hookspec.pyfrom typing import Any, Callable, List, TypeVar, castimport pluggy  # type: ignoreF = TypeVar("F", bound=Callable[..., Any])
hookspec = cast(Callable[[F], F], pluggy.HookspecMarker("python_plugin_example"))
class PluginExampleHookSpec:
"""
Python Plugin Example Hook Specification
"""
@hookspec
def retrieve_joke(self, amount: int) -> List[str]:
"""
Fired when retrieving jokes
Args:
amount: How many jokes should be returned
Returns:
A string containing a joke
"""
# hookimpl.pyfrom typing import Any, Callable, TypeVar, castimport pluggy  # type: ignoreF = TypeVar("F", bound=Callable[..., Any])hookimpl = cast(Callable[[F], F], pluggy.HookimplMarker("python_plugin_example"))
# main.pyimport pluggy  # type: ignorefrom python_plugin_example.hookspec import PluginExampleHookSpecclass App:
def __init__(self) -> None:
self.pm: pluggy.PluginManager = pluggy.PluginManager("python_plugin_example")
self.pm.add_hookspecs(PluginExampleHookSpec)
def display_jokes(self, amount: int) -> None:
results = self.pm.hook.retrieve_joke(amount=amount)
jokes = [joke for plugin in results for joke in plugin]
for joke in jokes:
print(joke)
if __name__ == "__main__":
app = App()
app.display_jokes(2)
# plugins/icanhazdadjokes.pyfrom typing import Listimport requestsfrom python_plugin_example.hookimpl import hookimplDAD_JOKE_API_ENDPOINT = "<https://icanhazdadjoke.com/>"class ICanHazDadJokePlugin:
@hookimpl
def retrieve_joke(self, amount: int) -> List[str]:
headers = {
"Accept": "application/json",
}
values = []
for i in range(amount):
response = requests.get(DAD_JOKE_API_ENDPOINT, headers=headers)
values.append(response.json().get("joke", ""))
jokes = [f"👨 Dad joke: {value}" for value in values]
return jokes
# plugins/chucknorris.pyfrom typing import Listimport requestsfrom python_plugin_example.hookimpl import hookimplCHUCK_NORRIS_API_ENDPOINT = "<https://api.chucknorris.io/jokes/random>"class ChuckNorrisPlugin:
@hookimpl
def retrieve_joke(self, amount: int) -> List[str]:
values = []
for i in range(amount):
response = requests.get(CHUCK_NORRIS_API_ENDPOINT)
values.append(response.json().get("value", ""))
jokes = [f"🤠 Chuck Norris: {value}" for value in values]
return jokes
# main.pyimport pluggy  # type: ignorefrom python_plugin_example.hookspec import PluginExampleHookSpec
from python_plugin_example.plugins.chucknorris import ChuckNorrisPlugin
from python_plugin_example.plugins.icanhazdadjoke import ICanHazDadJokePlugin
class App:
def __init__(self) -> None:
self.pm: pluggy.PluginManager = pluggy.PluginManager("plugin_example")
self.pm.add_hookspecs(PluginExampleHookSpec)
self.pm.register(ChuckNorrisPlugin())
self.pm.register(ICanHazDadJokePlugin())
def display_jokes(self, amount: int) -> None:
results = self.pm.hook.retrieve_joke(amount=amount)
jokes = [joke for plugin in results for joke in plugin]
for joke in jokes:
print(joke)
if __name__ == "__main__":
app = App()
app.display_jokes(2)
> python main.py
👨 Dad joke: Geology rocks, but Geography is where it’s at!
👨 Dad joke: My pet mouse ‘Elvis’ died last night. He was caught in a trap..
🤠 Chuck Norris: Chuck Norris only applies his car brakes for people with red hair & beards.
🤠 Chuck Norris: Chuck Norris does not need a toothbrush when he brushes his teeth

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store