import asyncio
import logging
import sanic
from cached_property import cached_property
from sanic_cors import CORS
from tinydb import TinyDB
from caspia.meadow.client import ConsumerConnection, ServiceBrowser
from . import authorization, exceptions, services, views
logger = logging.getLogger(__name__)
ROUTE_TABLE = {
'/services': views.ServicesView,
'/services/<service_name>': views.ServiceView,
'/services/<service_name>/characteristics/<characteristic_name>/value':
views.CharacteristicValueView,
'/notifications': views.NotificationsView,
'/walls': views.WallsView,
'/walls/<identifier:int>': views.WallView,
'/walls/<wall_identifier:int>/tiles': views.TilesView,
'/walls/<wall_identifier:int>/tiles/<tile_identifier>': views.TileView,
}
[docs]class Homeserver:
def __init__(self,
*,
host,
port,
broker_url,
name,
cors_origins=[r'.*'],
storage,
db: TinyDB,
config: dict,
loop):
self.host = host
self.port = port
self.name = name
self.broker_url = broker_url
self.storage = storage
self.db = db
self.config = config
self.loop = loop
self.cors_origins = cors_origins
[docs] def create_service(self, service_cls, **kwargs):
base_kwargs = dict(loop=self.loop, db=self.db, config=self.config)
return service_cls(**base_kwargs, **kwargs)
@cached_property
def consumer_conn(self):
conn = ConsumerConnection(self.broker_url, name=self.name)
asyncio.ensure_future(conn.run_forever(), loop=self.loop)
return conn
@cached_property
def service_browser(self):
return ServiceBrowser(connection=self.consumer_conn, loop=self.loop)
@cached_property
def meadow_service(self) -> services.MeadowService:
return self.create_service(services.MeadowService,
browser=self.service_browser,
consumer_conn=self.consumer_conn)
@cached_property
def wall_service(self) -> services.WallService:
return self.create_service(services.WallService)
@cached_property
def tile_service(self) -> services.TileService:
return self.create_service(services.TileService, wall_service=self.wall_service)
@cached_property
def user_service(self) -> services.UserService:
return self.create_service(services.UserService)
@cached_property
def metadata_service(self) -> services.MetadataService:
return self.create_service(services.MetadataService,
storage=self.storage,
broker_url=self.broker_url,
consumer_conn=self.consumer_conn)
@cached_property
def services(self):
return {
services.MeadowService: self.meadow_service,
services.WallService: self.wall_service,
services.TileService: self.tile_service,
services.UserService: self.user_service,
services.MetadataService: self.metadata_service,
}
[docs] def setup_routes(self, app: sanic.app.Sanic):
for uri, view in ROUTE_TABLE.items():
app.add_route(view.as_view(self.services, self.loop), uri=uri, version=1)
@cached_property
def app(self):
app = sanic.app.Sanic('homeserver')
app.enable_websocket(True)
self.setup_routes(app)
app.listener('before_server_start')(self.before_server_start)
authorization.setup(self, app)
CORS(app, supports_credentials=True, always_send=True, origins=self.cors_origins or '*')
app.exception(sanic.exceptions.SanicException)(self.handle_server_exception)
app.exception(exceptions.HomeserverException)(self.handle_server_exception)
self.metadata_service # ensure the service exists pylint: disable=pointless-statement
return app
[docs] async def handle_server_exception(self, request, exception):
status = getattr(exception, 'status_code', 500)
message = 'internal server error'
if isinstance(exception, sanic.exceptions.SanicException):
status = exception.status_code
message = str(exception)
if isinstance(exception, exceptions.HomeserverException):
status = exception.status_code
message = exception.message
return sanic.response.json({'message': message}, status=status)
[docs] async def before_server_start(self, app, loop):
await self.service_browser.prepare()