Skip to content

Commit

Permalink
Merge pull request #454 from msqd/rules
Browse files Browse the repository at this point in the history
Rules & Config
  • Loading branch information
hartym authored Jul 25, 2024
2 parents 95e6265 + 73c5346 commit 0efd3d8
Show file tree
Hide file tree
Showing 152 changed files with 2,406 additions and 1,459 deletions.
14 changes: 9 additions & 5 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import builtins
import hashlib
from unittest.mock import patch

import pytest

from harp.config import defaults

builtins.__pytest__ = True


DISABLED_APPLICATIONS_FOR_TESTS = ("telemetry", "harp_apps.telemetry")

DEFAULT_STORAGE_SETTINGS = {
Expand All @@ -21,15 +23,17 @@ def test_id(request):

@pytest.fixture(scope="session", autouse=True)
def default_session_fixture():
from harp.config import Config
DEFAULT_APPLICATIONS_BACKUP = defaults.DEFAULT_APPLICATIONS

DEFAULT_APPLICATIONS_FOR_TESTS = list(Config.DEFAULT_APPLICATIONS)
DEFAULT_APPLICATIONS_FOR_TESTS = list(defaults.DEFAULT_APPLICATIONS)
for app in DISABLED_APPLICATIONS_FOR_TESTS:
if app in DEFAULT_APPLICATIONS_FOR_TESTS:
DEFAULT_APPLICATIONS_FOR_TESTS.remove(app)

with patch("harp.config.Config.DEFAULT_APPLICATIONS", DEFAULT_APPLICATIONS_FOR_TESTS):
defaults.DEFAULT_APPLICATIONS = tuple(DEFAULT_APPLICATIONS_FOR_TESTS)
try:
yield
finally:
defaults.DEFAULT_APPLICATIONS = DEFAULT_APPLICATIONS_BACKUP


# see https://github.com/GrahamDumpleton/wrapt/issues/257
Expand Down
16 changes: 5 additions & 11 deletions docs/apps/rules/on_remote_request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,15 @@ You can add a header to the outgoing request:
Forging a response
::::::::::::::::::

If a :class:`Response <httpx.Response>` is set in this event (using
:meth:`set_response(...) <harp_apps.http_client.events.HttpClientFilterEvent.set_response>`), the outgoing request
will not be sent, and your response will be used instead.
If a :class:`Response <httpx.Response>` is set in this event, the outgoing request will not be sent, and your response
will be used instead.

.. code-block:: toml
[rules."*"."*"]
on_remote_request = """
from httpx import Response
set_response(Response(200, content=b'Hello, World!'))
response = Response(200, content=b'Hello, World!')
"""
Context reference
Expand All @@ -71,11 +70,6 @@ The following variables are available in the context of the ``on_remote_request`
- ``event``: the :class:`HttpClientFilterEvent <harp_apps.http_client.events.HttpClientFilterEvent>` instance.
- ``endpoint``: the endpoint name for this transaction, as defined in your configuration.
- ``request``: the prepared :class:`httpx.Request` instance, ready to be sent.
- ``response``: an eventual :class:`httpx.Response` instance, but most probably None. It will be
set if the script (or another script that happened before this one) calls
:meth:`harp_apps.http_client.events.HttpClientFilterEvent.set_response(...) <.set_response>`. After the event is
processed, the http client will bypass sending the outgoing request this response if it is set. Please note that
you must use the setter function, setting the response value using ``response = ...`` will not work.
- ``set_response``: a function to set the :class:`httpx.Response` to be returned to the client (bypassing the outgoing
http request).
- ``response``: an eventual :class:`httpx.Response` instance, but most probably None. Set this to a
:class:`httpx.Response` to forge a response, bypassing the remote request.
- ``stop_propagation``: a function to stop the event propagation to the next event in the chain.
5 changes: 2 additions & 3 deletions docs/apps/rules/on_remote_response.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Although the reason may be debatable, if you need to, you can replace the respon
[rules."*"."*"]
on_remote_response = """
from httpx import Response
set_response(Response(200, content=b'Goodbye, World!'))
response = Response(200, content=b'Goodbye, World!')
"""
Context reference
Expand All @@ -91,8 +91,7 @@ The following variables are available in the context of the ``on_remote_response
- ``logger``: the logger instance.
- ``event``: the :class:`HttpClientFilterEvent <harp_apps.http_client.events.HttpClientFilterEvent>` instance.
- ``request``: the :class:`httpx.Request` instance.
- ``response``: the :class:`httpx.Response` instance.
- ``set_response``: a function to set the response to be sent back to the proxy controller.
- ``response``: the :class:`httpx.Response` instance. You can amend or replace it.
- ``stop_propagation``: a function to stop the event propagation to the next lifecycle event.

.. warning::
Expand Down
15 changes: 4 additions & 11 deletions docs/apps/rules/on_request.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,14 @@ You get a chance to modify the request before the proxy logic kicks in:
Forging a response
::::::::::::::::::

If a :class:`Response <harp.http.HttpResponse>` is set in this event (using
:meth:`set_response(...) <harp_apps.proxy.events.ProxyFilterEvent.set_response>`), the proxy will not be involved at
all.
If a :class:`Response <harp.http.HttpResponse>` is set in this event, the proxy will not be involved at all.

.. code-block:: toml
[rules."*"."*"]
on_request = """
from harp.http import HttpResponse
set_response(HttpResponse('Hello, World!'))
response = HttpResponse('Hello, World!')
"""
It's defined and dispatched by the ``proxy`` application, within the
Expand All @@ -69,13 +67,8 @@ The following variables are available in the context of the ``on_request`` lifec
- ``event``: the :class:`ProxyFilterEvent <harp_apps.proxy.events.ProxyFilterEvent>` instance.
- ``endpoint``: the endpoint name for this transaction, as defined in your configuration.
- ``request``: the :class:`HttpRequest <harp.http.HttpRequest>` instance.
- ``response``: an eventual :class:`HttpResponse <harp.http.HttpResponse>` instance, but most probably None. It will be
set if the script (or another script that happened before this one) calls
:meth:`set_response(...) <harp_apps.proxy.events.ProxyFilterEvent.set_response>`. After the event is processed, the
proxy controller will bypass the proxying logic and return this response to the client if it is set. Please note that
you must use the setter function, setting the response value using ``response = ...`` will not work.
- ``set_response``: a function to set the :class:`Response <harp.http.HttpResponse>` to be returned to the client
(bypassing the proxy logic).
- ``response``: an eventual :class:`HttpResponse <harp.http.HttpResponse>` instance, but most probably None. Set this to
a :class:`HttpResponse <harp.http.HttpResponse>` instance to short-circuit the proxying logic.
- ``stop_propagation``: a function to stop the event propagation to the next event in the chain.

.. warning::
Expand Down
6 changes: 2 additions & 4 deletions docs/apps/rules/on_response.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Although the reason may be debatable, if you need to, you can replace the respon
[rules."*"."*"]
on_response = """
from harp.http import HttpResponse
set_response(HttpResponse('Goodbye, World!'))
response = HttpResponse('Goodbye, World!')
"""
The event instance passed to ``on_response`` lifecycle event scripts is a :class:`ProxyFilterEvent
Expand All @@ -67,9 +67,7 @@ The following variables are available in the context of the ``on_response`` life
- ``event``: the :class:`ProxyFilterEvent <harp_apps.proxy.events.ProxyFilterEvent>` instance.
- ``endpoint``: the endpoint name for this transaction, as defined in your configuration.
- ``request``: the :class:`HttpRequest <harp.http.HttpRequest>` instance.
- ``response``: the :class:`HttpResponse <harp.http.HttpResponse>` instance.
- ``set_response``: a function to override the :class:`Response <harp.http.HttpResponse>` to be returned to the client
(bypassing the proxy logic).
- ``response``: the :class:`HttpResponse <harp.http.HttpResponse>` instance. You can amend or replace it.
- ``stop_propagation``: a function to stop the event propagation to the next event in the chain.

.. warning::
Expand Down
148 changes: 147 additions & 1 deletion docs/contribute/applications/application-protocol.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,150 @@
Application Protocol
====================

.. todo:: document this
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``:

.. code-block:: python
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 package root.

1. Define your settings class in settings.py.
2. Include this class in your application definition in ``__app__.py``.

Example ``settings.py``:

.. literalinclude:: ../../../harp_apps/acme/settings.py

Example ``__app__.py`` update:

.. code-block:: python
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 :func:`harp.config.asdict`.

Let's write a simple test to check that.

.. literalinclude:: ../../../harp_apps/acme/tests/test_settings.py


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 :class:`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.

.. code-block:: python
from harp.config import OnBindEvent
async def on_bind(event: OnBindEvent):
...
Reference: :class:`harp.config.OnBindEvent`


On Bound
--------

Occurs when the system can instantiate services. Use this to access and manipulate service instances.

.. code-block:: python
from harp.config import OnBoundEvent
async def on_bound(event: OnBoundEvent):
...
Reference: :class:`harp.config.OnBoundEvent`


On Ready
--------

Called when the system starts, after all services are ready. A good place to add ASGI middlewares.

.. code-block:: python
from harp.config import OnReadyEvent
async def on_ready(event: OnReadyEvent):
...
Reference: :class:`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.

.. code-block:: python
from harp.config import OnShutdownEvent
async def on_shutdown(event: OnShutdownEvent):
...
Reference: :class:`harp.config.OnShutdownEvent`


Full Example
::::::::::::

``__app__.py``
--------------

.. literalinclude:: ../../../harp_apps/acme/__app__.py

``settings.py``
---------------

.. literalinclude:: ../../../harp_apps/acme/settings.py

``tests/test_settings.py``
--------------------------

.. literalinclude:: ../../../harp_apps/acme/tests/test_settings.py
3 changes: 3 additions & 0 deletions docs/contribute/changelogs/unreleased.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ Changed
bootstrapping (internal) API has changed and there is no more "kernel factory" or complex "Config" object. Instead,
we have a ConfigurationBuilder and a SystemBuilder that will expose a simple and understandable output api.
* Storage: removed unused storage "type" where the only valid value in the foreseeable future was "sqlalchemy".
* Applications: simpler and cleaner interface for defining applications and their configuration.
* Rules: removed the need to call set_response in a rule to override the response. Now, you can simply return a response
object from the rule and it will be used as the response.
2 changes: 1 addition & 1 deletion docs/features/rules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Example
from harp.http import HttpResponse
if request.headers.get("Authorization") == "I'm root, let me in":
set_response(HttpResponse("Ok, then."))
response = HttpResponse("Ok, then.")
else:
request.headers["Via"] = "Ferrata"
"""
Expand Down
Loading

0 comments on commit 0efd3d8

Please sign in to comment.