Sfoglia il codice sorgente

Transitioned service to aiohttp instead of python-responder.
Closes #3.

Stan Janssen 4 anni fa
parent
commit
4964b2e5e3

+ 32 - 25
pyopenadr/server.py

@@ -1,34 +1,41 @@
-#!/Users/stan/Development/ElaadNL/pyopenadr/.python/bin/python3
-# A simple Python OpenADR VTN Server.
-
-from pyopenadr.service import api
-from pyopenadr.service import PollService, EventService, PollService, RegistrationService, ReportService
+from aiohttp import web
+from pyopenadr.service import EventService, PollService, RegistrationService, ReportService, OptService
 
 class OpenADRServer:
-    def __init__(self):
-        self.api = api
+    map = {'on_created_event': EventService,
+           'on_request_event': EventService,
+
+           'on_register_report': ReportService,
+           'on_create_report': ReportService,
+           'on_created_report': ReportService,
+           'on_request_report': ReportService,
+           'on_update_report': ReportService,
+
+           'on_poll': PollService,
+
+           'on_query_registration': RegistrationService,
+           'on_create_party_registration': RegistrationService,
+           'on_cancel_party_registration': RegistrationService}
+
+    def __init__(self, vtn_id):
+        self.app = web.Application()
+        self.services = {'event_service': EventService(vtn_id),
+                         'report_service': ReportService(vtn_id),
+                         'poll_service': PollService(vtn_id),
+                         'opt_service': OptService(vtn_id),
+                         'registration_service': RegistrationService(vtn_id)}
+        self.app.add_routes([web.post(f"/OpenADR2/Simple/2.0b/{s.__service_name__}", s.handler) for s in self.services.values()])
         self.__setattr__ = self.add_handler
 
+    def run(self):
+        web.run_app(self.app)
+
     def add_handler(self, name, func):
         """
         Add a handler to the OpenADRServer.
         """
-        map = {'on_created_event': EventService,
-               'on_request_event': EventService,
-
-               'on_register_report': ReportService,
-               'on_create_report': ReportService,
-               'on_created_report': ReportService,
-               'on_request_report': ReportService,
-               'on_update_report': ReportService,
-
-               'on_poll': PollService,
-
-               'on_query_registration': RegistrationService,
-               'on_create_party_registration': RegistrationService,
-               'on_cancel_party_registration': RegistrationService}
-        if name in map:
-            setattr(map[name], name, staticmethod(func))
+        print("Called add_handler", name, func)
+        if name in self.map:
+            setattr(self.map[name], name, staticmethod(func))
         else:
-            raise NameError(f"Unknown handler {name}. Correct handler names are: {map.keys()}")
-
+            raise NameError(f"Unknown handler {name}. Correct handler names are: {self.map.keys()}")

+ 10 - 11
pyopenadr/service/__init__.py

@@ -1,12 +1,3 @@
-import responder
-from .. import config
-from ..utils import datetimeformat, timedeltaformat, booleanformat
-
-api = responder.API(templates_dir=config.TEMPLATE_DIR)
-api.templates._env.filters['datetimeformat'] = datetimeformat
-api.templates._env.filters['timedeltaformat'] = timedeltaformat
-api.templates._env.filters['booleanformat'] = booleanformat
-
 def handler(message_type):
     """
     Decorator to mark a method as the handler for a specific message type.
@@ -16,11 +7,19 @@ def handler(message_type):
         return decorated_function
     return _actual_decorator
 
+def service(service_name):
+    """
+    Decorator to mark a class as an OpenADR Service for a specific endpoint.
+    """
+    def _actual_decorator(decorated_function):
+        decorated_function.__service_name__ = service_name
+        return decorated_function
+    return _actual_decorator
+
 # The classes below all register to the api
 from .vtn_service import VTNService
 from .event_service import EventService
 from .poll_service import PollService
 from .registration_service import RegistrationService
 from .report_service import ReportService
-
-# from .opt_service import OptService
+from .opt_service import OptService

+ 2 - 3
pyopenadr/service/event_service.py

@@ -1,8 +1,8 @@
-from . import api, handler, VTNService
+from . import service, handler, VTNService
 from datetime import datetime, timedelta, timezone
 from asyncio import iscoroutine
 
-@api.route('/OpenADR2/Simple/2.0b/EiEvent')
+@service('EiEvent')
 class EventService(VTNService):
 
     @handler('oadrRequestEvent')
@@ -10,7 +10,6 @@ class EventService(VTNService):
         """
         The VEN requests us to send any events we have.
         """
-        # TODO: hook into some backend here to retrieve the appropriate events for this VEN.
         try:
             result = self.on_request_event(payload['ven_id'])
             if iscoroutine(result):

+ 2 - 3
pyopenadr/service/opt_service.py

@@ -1,6 +1,5 @@
-from .. import config
-from . import api
+from . import service, handler, VTNService
 
-@api.route('/OpenADR2/Simple/2.0b/EiOpt')
+@service('EiOpt')
 class OptService(VTNService):
     pass

+ 2 - 2
pyopenadr/service/poll_service.py

@@ -1,4 +1,4 @@
-from . import api, handler, VTNService
+from . import service, handler, VTNService
 from asyncio import iscoroutine
 
 # ╔══════════════════════════════════════════════════════════════════════════╗
@@ -78,7 +78,7 @@ from asyncio import iscoroutine
 # │                                                                          │
 # └──────────────────────────────────────────────────────────────────────────┘
 
-@api.route('/OpenADR2/Simple/2.0b/OadrPoll')
+@service('OadrPoll')
 class PollService(VTNService):
 
     @handler('oadrPoll')

+ 3 - 3
pyopenadr/service/registration_service.py

@@ -1,4 +1,4 @@
-from . import api, handler, VTNService
+from . import service, handler, VTNService
 from datetime import timedelta
 from asyncio import iscoroutine
 
@@ -42,7 +42,7 @@ from asyncio import iscoroutine
 # │                                                                          │
 # └──────────────────────────────────────────────────────────────────────────┘
 
-@api.route('/OpenADR2/Simple/2.0b/EiRegisterParty')
+@service('EiRegisterParty')
 class RegistrationService(VTNService):
 
     @handler('oadrQueryRegistration')
@@ -59,7 +59,7 @@ class RegistrationService(VTNService):
         # If you don't provide a default handler, just give out the info
         response_payload = {'response': {'response_code': 200, 'response_description': 'OK', 'request_id': payload['request_id']},
                             'request_id': payload['request_id'],
-                            'vtn_id': 'ElaadVTN',
+                            'vtn_id': self.vtn_id,
                             'profiles': [{'profile_name': '2.0b',
                                           'transports': {'transport_name': 'simpleHttp'}}],
                             'requested_oadr_poll_freq': timedelta(seconds=5)}

+ 2 - 2
pyopenadr/service/report_service.py

@@ -1,4 +1,4 @@
-from . import api, handler, VTNService
+from . import service, handler, VTNService
 
 # ╔══════════════════════════════════════════════════════════════════════════╗
 # ║                              REPORT SERVICE                              ║
@@ -38,7 +38,7 @@ from . import api, handler, VTNService
 # │                                                                          │
 # └──────────────────────────────────────────────────────────────────────────┘
 
-@api.route('/OpenADR2/Simple/2.0b/EiReport')
+@service('EiReport')
 class ReportService(VTNService):
 
     @handler('oadrRegisterReport')

+ 34 - 28
pyopenadr/service/vtn_service.py

@@ -1,38 +1,36 @@
 from asyncio import iscoroutine
 from http import HTTPStatus
-import random
-import string
+import os
 
-from . import api
-from .. import config, errors
-from ..utils import parse_message, indent_xml
+from aiohttp import web
+from jinja2 import Environment, PackageLoader, select_autoescape
+
+from .. import errors
+from ..utils import parse_message, indent_xml, datetimeformat, timedeltaformat, booleanformat
 
 class VTNService:
-    """
-    This is the default OpenADR handler. You should subclass this with your
-    specific services.
-    """
-    def __init__(self):
+    templates = Environment(loader=PackageLoader('pyopenadr', 'templates'),
+                            autoescape=select_autoescape(['html', 'xml']))
+    templates.filters['datetimeformat'] = datetimeformat
+    templates.filters['timedeltaformat'] = timedeltaformat
+    templates.filters['booleanformat'] = booleanformat
+
+    def __init__(self, vtn_id):
+        self.vtn_id = vtn_id
         self.handlers = {}
         for method in [getattr(self, attr) for attr in dir(self) if callable(getattr(self, attr))]:
             if hasattr(method, '__message_type__'):
-                print(f"Adding {method.__name__} as handler for {method.__message_type__}")
                 self.handlers[method.__message_type__] = method
 
-    async def on_request(self, request, response):
+    async def handler(self, request):
         """
-        This is the default handler that is used by python-responder. It will
-        look for a handler of the message type in one of the subclasses.
+        Handle all incoming POST requests.
         """
-        print()
-        print()
-        print("================================================================================")
-        print(f"             NEW REQUEST to {request.url.path}                                 ")
-        print("================================================================================")
-        content = await request.content
+        content = await request.read()
         print(f"Received: {content.decode('utf-8')}")
         message_type, message_payload = parse_message(content)
         print(f"Interpreted message: {message_type}: {message_payload}")
+
         if message_type in self.handlers:
             handler = self.handlers[message_type]
             result = handler(message_payload)
@@ -40,12 +38,20 @@ class VTNService:
                 response_type, response_payload = await result
             else:
                 response_type, response_payload = result
-            response.html = indent_xml(api.template(f'{response_type}.xml', **response_payload))
-            print(f"Sending {response.html}")
-        else:
-            response.html = indent_xml(api.template('oadrResponse.xml',
-                                status_code=errorcodes.COMPLIANCE_ERROR,
-                                status_description=f'A message of type {message_type} should not be sent to this endpoint'))
-            print(f"Sending {response.html}")
-            response.status_code = HTTPStatus.BAD_REQUEST
 
+            # Get the relevant template and create the XML response
+            template = self.templates.get_template(f'{response_type}.xml')
+            template.render(**response_payload)
+            response = web.Response(text=indent_xml(template.render(**response_payload)),
+                                    status=200,
+                                    content_type='application/xml')
+
+        else:
+            template = templates.get_template('oadrResponse.xml')
+            response = web.Response(
+                text=template.render(status_code=errorcodes.COMPLIANCE_ERROR,
+                                     status_description=f'A message of type {message_type} should not be sent to this endpoint'),
+                status=HTTPStatus.BAD_REQUEST,
+                content_type='application/xml')
+        print(f"Sending {response.text}")
+        return response

+ 1 - 1
setup.py

@@ -11,4 +11,4 @@ setup(name="pyopenadr",
       url="https://git.finetuned.nl/stan/pyopenadr",
       packages=['pyopenadr', 'pyopenadr.service'],
       include_package_data=True,
-      install_requires=['xmltodict', 'responder', 'apscheduler', 'uvloop==0.12'])
+      install_requires=['xmltodict', 'aiohttp', 'apscheduler'])