254 lines
9.7 KiB
Django/Jinja
254 lines
9.7 KiB
Django/Jinja
{% if is_first_by_category %}
|
|
##
|
|
## service types (aka "APIs")
|
|
##
|
|
from uuid import UUID
|
|
from typing import List
|
|
|
|
import txaio
|
|
from txaio.interfaces import ILogger
|
|
|
|
from autobahn.wamp.types import PublishOptions, SubscribeOptions, EventDetails, CallOptions, CallDetails, RegisterOptions
|
|
from autobahn.wamp.request import Publication, Subscription, Registration
|
|
from autobahn.wamp.interfaces import ISession
|
|
from autobahn.xbr import IDelegate
|
|
|
|
Oid = UUID
|
|
Oids = List[UUID]
|
|
Void = type(None)
|
|
|
|
|
|
{% endif %}
|
|
class {{ metadata.classname }}(object):
|
|
"""
|
|
{{ metadata.docs }}
|
|
|
|
Interface UUID: ``{{ metadata.attrs.uuid }}``
|
|
"""
|
|
__slots__ = [
|
|
'log',
|
|
'_x_api_id',
|
|
'_x_prefix',
|
|
'_x_session',
|
|
'_x_delegate',
|
|
'_x_regs',
|
|
'_x_subs',
|
|
]
|
|
|
|
def __init__(self, prefix: str, log: Optional[ILogger]=None):
|
|
"""
|
|
|
|
:param prefix: The URI prefix under which this API will be instantiated under on the realm joined.
|
|
:param log: If provided, log to this logger, else create a new one internally.
|
|
"""
|
|
if log:
|
|
self.log = log
|
|
else:
|
|
import txaio
|
|
self.log = txaio.make_logger()
|
|
self._x_api_id = uuid.UUID('{{ metadata.attrs.uuid }}')
|
|
self._x_prefix = prefix
|
|
self._x_session = None
|
|
self._x_delegate = None
|
|
self._x_regs = None
|
|
self._x_subs = None
|
|
|
|
@property
|
|
def api(self) -> uuid.UUID:
|
|
"""
|
|
Interface UUID of this API (``{{ metadata.attrs.uuid }}``).
|
|
"""
|
|
return self._x_api_id
|
|
|
|
@property
|
|
def prefix(self) -> str:
|
|
"""
|
|
WAMP URI prefix under which this API is instantiated.
|
|
"""
|
|
return self._x_prefix
|
|
|
|
# WAMP PubSub part of the API:
|
|
|
|
{% for call_name in metadata.calls_by_id %}
|
|
{% if metadata.calls[call_name].attrs['type'] == 'topic' %}
|
|
async def publish_{{ call_name }}(self, evt: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, options: Optional[PublishOptions] = None) -> Optional[Publication]:
|
|
"""
|
|
As an **interface provider**, publish event:
|
|
|
|
{{ metadata.calls[call_name].docs }}
|
|
|
|
:param evt: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
|
:returns: When doing an acknowledged publish, the WAMP publication is returned.
|
|
"""
|
|
assert self._x_session and self._x_session.is_attached()
|
|
|
|
topic = '{}.{{ call_name }}'.format(self._x_prefix)
|
|
payload = evt.marshal()
|
|
if self._x_delegate:
|
|
key_id, enc_ser, ciphertext = await self._x_delegate.wrap(self._x_api_id, topic, payload)
|
|
if options.acknowledge:
|
|
pub = await self._x_session.publish(topic, key_id, enc_ser, ciphertext, options=options)
|
|
else:
|
|
self._x_session.publish(topic, key_id, enc_ser, ciphertext, options=options)
|
|
pub = None
|
|
else:
|
|
if options.acknowledge:
|
|
pub = await self._x_session.publish(topic, payload, options=options)
|
|
else:
|
|
self._x_session.publish(topic, payload, options=options)
|
|
pub = None
|
|
return pub
|
|
|
|
def receive_{{ call_name }}(self, evt: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, details: Optional[EventDetails] = None):
|
|
"""
|
|
As an **interface consumer**, receive event:
|
|
|
|
{{ metadata.calls[call_name].docs }}
|
|
|
|
:param evt: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
|
"""
|
|
raise NotImplementedError('event handler for "{{ call_name }}" not implemented')
|
|
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
# WAMP RPC part of the API:
|
|
|
|
{% for call_name in metadata.calls_by_id %}
|
|
{% if metadata.calls[call_name].attrs['type'] == 'procedure' %}
|
|
async def call_{{ call_name }}(self, req: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, options: Optional[CallOptions] = None) -> {{ repo.objs[metadata.calls[call_name].response.name].map('python', required=False, objtype_as_string=True) }}:
|
|
"""
|
|
As an **interface consumer**, call procedure:
|
|
|
|
{{ metadata.calls[call_name].docs }}
|
|
|
|
:param req: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
|
:returns: {{ repo.objs[metadata.calls[call_name].response.name].docs }}
|
|
"""
|
|
assert self._x_session and self._x_session.is_attached()
|
|
|
|
procedure = '{}.{{ call_name }}'.format(self._x_prefix)
|
|
payload = req.marshal()
|
|
if self._x_delegate:
|
|
key_id, enc_ser, ciphertext = await self._x_delegate.wrap(self._x_api_id, procedure, payload)
|
|
result = await self._x_session.call(procedure, key_id, enc_ser, ciphertext, options=options)
|
|
else:
|
|
result = await self._x_session.call(procedure, payload, options=options)
|
|
return result
|
|
|
|
def invoke_{{ call_name }}(self, req: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, details: Optional[CallDetails] = None) -> {{ repo.objs[metadata.calls[call_name].response.name].map('python', required=False, objtype_as_string=True) }}:
|
|
"""
|
|
As an **interface provider**, process call invocation:
|
|
|
|
{{ metadata.calls[call_name].docs }}
|
|
|
|
:param req: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
|
:returns: {{ repo.objs[metadata.calls[call_name].response.name].docs }}
|
|
"""
|
|
raise NotImplementedError('call invocation handler for "{{ call_name }}" not implemented')
|
|
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
@property
|
|
def session(self) -> Optional[ISession]:
|
|
"""
|
|
WAMP session this API is attached to.
|
|
"""
|
|
return self._x_session
|
|
|
|
@property
|
|
def delegate(self) -> Optional[IDelegate]:
|
|
"""
|
|
XBR (buyer/seller) delegate this API is attached to.
|
|
"""
|
|
return self._x_delegate
|
|
|
|
async def attach(self, session: ISession, delegate: Optional[IDelegate]):
|
|
"""
|
|
Attach this API instance with the given session and delegate, and under the given WAMP URI prefix.
|
|
|
|
:param session: WAMP session this API instance is attached to.
|
|
:param delegate: If using end-to-end data encryption, XBR ("buyer/seller") delegate used by this API instance.
|
|
"""
|
|
assert self._x_session is None and session.is_attached()
|
|
|
|
self._x_session = session
|
|
self._x_delegate = delegate
|
|
|
|
# WAMP PubSub part of the API:
|
|
|
|
subscriptions = []
|
|
{% for call_name in metadata.calls_by_id %}
|
|
{% if metadata.calls[call_name].attrs['type'] == 'topic' %}
|
|
if self._x_delegate:
|
|
async def do_receive_{{ call_name }}(key_id, enc_ser, ciphertext, details=None):
|
|
try:
|
|
payload = await self._x_delegate.unwrap(key_id, enc_ser, ciphertext)
|
|
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(payload)
|
|
except:
|
|
self.log.failure()
|
|
else:
|
|
self.receive_{{ call_name }}(obj, details=details)
|
|
else:
|
|
def do_receive_{{ call_name }}(evt, details=None):
|
|
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(evt)
|
|
self.receive_{{ call_name }}(obj, details=details)
|
|
|
|
topic = '{}.{{ call_name }}'.format(self._x_prefix)
|
|
sub = await self._x_session.subscribe(do_receive_{{ call_name }}, topic, options=SubscribeOptions(details=True))
|
|
subscriptions.append(sub)
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
for sub in subscriptions:
|
|
self.log.info('Subscription {} created for "{}"'.format(sub.id, sub.topic))
|
|
self._x_subs = subscriptions
|
|
|
|
# WAMP RPC part of the API:
|
|
|
|
registrations = []
|
|
{% for call_name in metadata.calls_by_id %}
|
|
{% if metadata.calls[call_name].attrs['type'] == 'procedure' %}
|
|
if self._x_delegate:
|
|
async def do_invoke_{{ call_name }}(key_id, enc_ser, ciphertext, details=None):
|
|
try:
|
|
payload = await self._x_delegate.unwrap(key_id, enc_ser, ciphertext)
|
|
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(payload)
|
|
except:
|
|
self.log.failure()
|
|
else:
|
|
self.invoke_{{ call_name }}(obj, details=details)
|
|
else:
|
|
def do_invoke_{{ call_name }}(req, details=None):
|
|
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(req)
|
|
self.invoke_{{ call_name }}(obj, details=details)
|
|
|
|
procedure = '{}.{{ call_name }}'.format(self._x_prefix)
|
|
reg = await self._x_session.register(do_invoke_{{ call_name }}, procedure, options=RegisterOptions(details=True))
|
|
registrations.append(reg)
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
for reg in registrations:
|
|
self.log.info('Registration {} created for "{}"'.format(reg.id, reg.procedure))
|
|
self._x_regs = registrations
|
|
|
|
def detach(self):
|
|
"""
|
|
Detach this API instance from the session and delegate.
|
|
"""
|
|
assert self._x_session is not None
|
|
|
|
dl = []
|
|
if self._x_session and self._x_session.is_attached():
|
|
for reg in self._x_regs:
|
|
dl.append(reg.unregister())
|
|
for sub in self._x_subs:
|
|
dl.append(sub.unsubscribe())
|
|
|
|
self._x_session = None
|
|
self._x_delegate = None
|
|
|
|
return txaio.gather(dl, consume_exceptions=True)
|