Web app

Example Webapp
# This is code for the tutorial in README.md
from typing import Any, List, MutableMapping, Optional

from aiohttp.web import Application

from mode import Service
from mode.threads import ServiceThread
from mode.utils.objects import cached_property


class User:
    ...  # implement yourself


def remove_expired_users(d):
    print("REMOVING EXPIRED USERS")
    ...  # implement yourself


async def run_websocket_server():
    print("STARTING WEBSOCKET SERVER")
    ...  # implement yourself


class Websockets(Service):
    def __init__(self, port: int = 8081, **kwargs: Any) -> None:
        self.port = 8081
        self._server = None
        super().__init__(**kwargs)

    async def on_start(self) -> None:
        self._server = await run_websocket_server()

    async def on_stop(self) -> None:
        if self._server is not None:
            self._server.close()


class Webserver(ServiceThread):
    def __init__(self, port: int = 8000, bind: str = None, **kwargs: Any) -> None:
        self._app = Application()
        self.port = port
        self.bind = bind
        self._handler = None
        self._srv = None
        super().__init__(**kwargs)

    async def on_start(self) -> None:
        handler = self._handler = self._app.make_handler()
        # self.loop is the event loop in this thread
        #   self.parent_loop is the loop that created this thread.
        self._srv = await self.loop.create_server(handler, self.bind, self.port)
        self.log.info("Serving on port %s", self.port)

    async def on_thread_stop(self) -> None:
        # on_thread_stop() executes in the thread.
        # on_stop() executes in parent thread.

        # quite a few steps required to stop the aiohttp server:
        if self._srv is not None:
            self.log.info("Closing server")
            self._srv.close()
            self.log.info("Waiting for server to close handle")
            await self._srv.wait_closed()
        if self._app is not None:
            self.log.info("Shutting down web application")
            await self._app.shutdown()
        if self._handler is not None:
            self.log.info("Waiting for handler to shut down")
            await self._handler.shutdown(60.0)
        if self._app is not None:
            self.log.info("Cleanup")
            await self._app.cleanup()


class UserCache(Service):
    _cache: MutableMapping[str, User]

    def __post_init__(self):
        self._cache = {}

    async def lookup(self, user_id: str) -> User:
        try:
            return self._cache[user_id]
        except KeyError:
            user = self._cache[user_id] = await User.objects.get(user_id)
            return user

    @Service.timer(10)  # execute every 10 seconds.
    async def _remove_expired(self):
        remove_expired_users(self._cache)


class App(Service):
    def __init__(
        self,
        web_port: int = 8000,
        web_bind: Optional[str] = None,
        websocket_port: int = 8001,
        **kwargs: Any
    ) -> None:
        self.web_port = web_port
        self.web_bind = web_bind
        self.websocket_port = websocket_port
        super().__init__(**kwargs)

    def on_init_dependencies(self) -> List:
        return [
            self.websockets,
            self.webserver,
            self.user_cache,
        ]

    async def on_start(self) -> None:
        import io

        import pydot

        o = io.StringIO()
        beacon = self.beacon.root or self.beacon
        beacon.as_graph().to_dot(o)
        (graph,) = pydot.graph_from_dot_data(o.getvalue())
        print("WRITING GRAPH TO image.png")
        with open("image.png", "wb") as fh:
            fh.write(graph.create_png())

    @cached_property
    def websockets(self) -> Websockets:
        return Websockets(
            port=self.websocket_port,
            loop=self.loop,
            beacon=self.beacon,
        )

    @cached_property
    def webserver(self) -> Webserver:
        return Webserver(
            port=self.web_port,
            bind=self.web_bind,
            loop=self.loop,
            beacon=self.beacon,
        )

    @cached_property
    def user_cache(self) -> UserCache:
        return UserCache(loop=self.loop, beacon=self.beacon)


app = App()

if __name__ == "__main__":
    from mode.worker import Worker

    Worker(app, loglevel="info", daemon=True).execute_from_commandline()