Skip to content

Commit

Permalink
Add XML hooks to the client
Browse files Browse the repository at this point in the history
Requested in #131
  • Loading branch information
stan-janssen committed Nov 23, 2022
1 parent e41b0f7 commit 6e7c8cd
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 0 deletions.
31 changes: 31 additions & 0 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,34 @@ The OpenADR polling mechanism is very robust; there is very little chance that t
To mitigate the last point, the OpenLEADR VEN will, by default, 'jitter' the pollings by up to +/- 10% or +/- 5 minutes (whichever is smallest). The same goes for delivering the reports (the data collection will still happen on synchronized moments).

If you don't want to jitter the polling requests on your VEN, you can disable this by passing ``allow_jitter=False`` to your ``OpenADRClient`` constructor.


Hooks
=====

If you need to inspect the XML messages at certain points in the request/response chain, you can add hooks like this:

.. code-block:: python3
def log_outgoing_xml(content):
print(content)
def log_incoming_xml(content):
print(content)
client.add_hook('before_send_xml', log_outgoing_xml)
client.add_hook('after_receive_xml', log_incoming_xml)
The handlers can be normal functions or coroutines.

The following hooks are available:

- ``before_send_xml``: Called with one argument: ``content`` (XML string). This is the outgoing message in XML format.
- ``after_receive_xml``: Called with one argument: ``content`` (XML string, or any other error message that the server returned).
- ``before_schema_validation``: Called with one argument: ``content`` (XML string). This is the incoming message in XML format.
- ``before_parse_xml``: Called with one argument: ``content`` (XML string). This is the incoming message in XML format after it has been validated to be correct.
- ``after_parse_xml``: Called with two arguments: ``message_type`` (string) and ``message_payload`` (dict). This is the message in the OpenLEADR dict format after parsing.

Use these hooks at your own risk, an be aware that the client will wait for your hook(s) to finish before continuing.
33 changes: 33 additions & 0 deletions openleadr/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ def __init__(self, ven_name, vtn_url, debug=False, cert=None, key=None,
key=key,
passphrase=passphrase,
disable_signature=disable_signature)
self.hooks = {'before_send_xml': [],
'after_receive_xml': [],
'before_schema_validation': [],
'before_parse_xml': [],
'after_parse_xml': []}

async def run(self):
"""
Expand Down Expand Up @@ -340,6 +345,21 @@ def add_report(self, callback, resource_id, measurement=None,
report.report_descriptions.append(report_description)
return report_specifier_id, r_id

def add_hook(self, hook_name, handler):
"""
You can add a hook at specific points in the request/reponse chain.
Your choices are:
before_send_xml: you get the actual XML message just before it is sent over the wire
after_receive_xml: you get the actual XML immediately after it is received over the wire
before_parse_xml: you get the actual XML after its schema has been validated, but before parsing
after_parse_xml: you get the message name and message payload after parsing the message
"""
if hook_name not in self.hooks:
raise ValueError(f"The hook_name should be one of {', '.join(self.hooks.keys())}. "
f"You provided: {hook_name}.")
self.hooks[hook_name].append(handler)

###########################################################################
# #
# POLLING METHODS #
Expand Down Expand Up @@ -780,8 +800,10 @@ async def _perform_request(self, service, message):
await self._ensure_client_session()
url = f"{self.vtn_url}/{service}"
try:
await self._execute_hooks('before_send_xml', utils.ensure_str(message))
async with self.client_session.post(url, data=message) as req:
content = await req.read()
await self._execute_hooks('after_receive_xml', utils.ensure_str(content))
if req.status != HTTPStatus.OK:
logger.warning(f"Non-OK status {req.status} when performing a request to {url} "
f"with data {message}: {req.status} {content.decode('utf-8')}")
Expand All @@ -797,10 +819,13 @@ async def _perform_request(self, service, message):
if len(content) == 0:
return None
try:
await self._execute_hooks('before_schema_validation', utils.ensure_str(content))
tree = validate_xml_schema(content)
if self.vtn_fingerprint:
validate_xml_signature(tree, cert_fingerprint=self.vtn_fingerprint)
await self._execute_hooks('before_parse_xml', utils.ensure_str(content))
message_type, message_payload = parse_message(content)
await self._execute_hooks('after_parse_xml', message_type, message_payload)
except XMLSyntaxError as err:
logger.warning(f"Incoming message did not pass XML schema validation: {err}")
return None, {}
Expand All @@ -820,6 +845,14 @@ async def _perform_request(self, service, message):
f"{message_payload['response']['response_description']}")
return message_type, message_payload

async def _execute_hooks(self, hook_name, *args, **kwargs):
for hook in self.hooks[hook_name]:
try:
await utils.await_if_required(hook(*args, **kwargs))
except Exception as err:
logger.error(f"An error occurred while executing your '{hook_name}': {hook}:"
f"{err.__class__.__name__}: {err}")

async def _on_event(self, message):
logger.debug("The VEN received an event")
events = message['events']
Expand Down

0 comments on commit 6e7c8cd

Please sign in to comment.