Application Protocol¶
The HARP Application Protocol enables writing plug-and-play Python packages to enhance core functionalities.
An application is essentially a Python package with additional files that integrate it with the HARP framework.
Basic Structure¶
A standard Python package has a directory containing an __init__.py
file. To transform this package into a HARP
application, you need to add an __app__.py
file at the root. This file contains the application’s definition.
Example __app__.py
:
from harp.config import Application
application = Application()
This setup is the bare minimum. However, applications usually require more features, such as settings.
Configuring Settings¶
Applications often need custom settings. You can define these settings in a class, typically stored in a settings.py
file at the application’s package root.
Define your settings class in settings.py.
Include this class in your application definition in
__app__.py
.
Example settings.py
:
from harp.config import Configurable
class AcmeSettings(Configurable):
owner: str = "Joe"
Example __app__.py
update:
from harp.config import Application
from .settings import AcmeSettings
application = Application(
settings_type=AcmeSettings,
)
The settings class should:
Be instantiable without arguments for default settings.
Accept keyword arguments for custom settings.
Convert to a dictionary via
harp.config.asdict()
.
Let’s write a simple test to check that.
For convenience, we provide a harp.config.Configurable
class that you can inherit from to implement your
settings. It is a subclass of pydantic’s BaseModel
and provides a few additional methods.
Please refer to the pydantic’s documentation for more information on how to use it.
Application Lifecycle¶
To have a real purpose, an application should interact with the core system through lifecycle hooks.
All hooks are python coroutines, taking a specific whistle.Event
instance as argument.
Hooks must be registered in the application definition.
On Bind¶
Triggered during system setup but before service instances are created. Ideal for defining services and dependencies.
from harp.config import Application, OnBindEvent
async def on_bind(event: OnBindEvent):
...
application = Application(
...,
on_bind=on_bind,
)
Reference: harp.config.OnBindEvent
On Bound¶
Occurs when the system can instantiate services. Use this to access and manipulate service instances.
from harp.config import Application, OnBoundEvent
async def on_bound(event: OnBoundEvent):
...
application = Application(
...,
on_bound=on_bound,
)
Reference: harp.config.OnBoundEvent
On Ready¶
Called when the system starts, after all services are ready. A good place to add ASGI middlewares.
from harp.config import Application, OnReadyEvent
async def on_ready(event: OnReadyEvent):
...
application = Application(
...,
on_ready=on_ready,
)
Reference: harp.config.OnReadyEvent
On Shutdown¶
Invoked during system shutdown, allowing for cleanup and resource release.
Unlike other events, the shutdown events will be dispatched in applications reverse order, so that the first initialized application is the last to be shutdown.
from harp.config import Application, OnShutdownEvent
async def on_shutdown(event: OnShutdownEvent):
...
application = Application(
...,
on_shutdown=on_shutdown,
)
Reference: harp.config.OnShutdownEvent
Full Example¶
You can find this example in the ACME application, which sole purpose is to demonstrate the application protocol.
__app__.py
¶
from harp import get_logger
from harp.config import Application, OnBindEvent, OnBoundEvent, OnReadyEvent, OnShutdownEvent
from harp_apps.acme.settings import AcmeSettings
logger = get_logger(__name__)
async def on_bind(event: OnBindEvent):
logger.warning("🔗 Binding Acme services")
async def on_bound(event: OnBoundEvent):
logger.warning("🔗 Bound Acme services")
async def on_ready(event: OnReadyEvent):
logger.warning("🔗 Building Acme services")
async def on_shutdown(event: OnShutdownEvent):
logger.warning("🔗 Disposing Acme services")
application = Application(
on_bind=on_bind,
on_bound=on_bound,
on_ready=on_ready,
on_shutdown=on_shutdown,
settings_type=AcmeSettings,
)
settings.py
¶
from harp.config import Configurable
class AcmeSettings(Configurable):
owner: str = "Joe"
tests/test_settings.py
¶
from harp.config.asdict import asdict
from ..settings import AcmeSettings
def test_default_settings():
settings = AcmeSettings()
assert asdict(settings) == {}
assert asdict(settings, verbose=True) == {"owner": "Joe"}
def test_custom_settings():
settings = AcmeSettings(owner="Alice")
assert asdict(settings) == {"owner": "Alice"}
assert asdict(settings, verbose=True) == {"owner": "Alice"}