Skip to content

Commit

Permalink
move to class based handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
Fr0stFree committed Jun 14, 2024
1 parent 4245ded commit f8dfc40
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 137 deletions.
14 changes: 12 additions & 2 deletions server/main.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import asyncio

from server.src.settings import ServerSettings
from server.src.handlers import SendMessageHandler, BroadcastMessageHandler, UnknownActionHandler, LogoutHandler, \
BaseErrorHandler
from server.src.server import Server
from server.src.settings import ServerSettings
from shared.schemas.actions import ActionTypes


async def main():
settings = ServerSettings()
server = Server(settings)
server = Server(settings) \
.on_action(ActionTypes.SEND_MESSAGE, SendMessageHandler) \
.on_action(ActionTypes.BROADCAST_MESSAGE, BroadcastMessageHandler) \
.on_action(ActionTypes.HELP, UnknownActionHandler) \
.on_action(ActionTypes.LOGOUT, LogoutHandler) \
.on_unknown_action(UnknownActionHandler) \
.on_exception(Exception, BaseErrorHandler)

try:
await server.start()
await server.serve()
Expand Down
79 changes: 21 additions & 58 deletions server/src/handlers.py
Original file line number Diff line number Diff line change
@@ -1,67 +1,30 @@
import asyncio
import datetime as dt
from typing import override

from server.src.models.client import ClientManager
from pydantic import BaseModel

from server.src.models.client import ClientManager, Client, LoggerLike
from server.src.models.request import Request
from shared.schemas.actions import BroadcastMessagePayload, SendMessagePayload
from shared.schemas.notifications import BroadcastMessageNotificationPayload, \
BroadcastMessageNotificationFrame, ErrorNotificationFrame, ErrorNotificationPayload


async def broadcast_message_handler(request: Request) -> None:
incoming_payload = BroadcastMessagePayload.model_validate(request.frame.payload)
outgoing_payload = BroadcastMessageNotificationPayload(
text=incoming_payload.text,
sender=request.client.user.id,
created_at=dt.datetime.now(dt.UTC),
)
frame = BroadcastMessageNotificationFrame(payload=outgoing_payload)
clients = ClientManager.get_current()
tasks = [client.send(frame) for client in clients.all()]
await asyncio.gather(*tasks)
request.logger.info("Message broadcasted successfully")


async def send_message_handler(request: Request) -> None:
incoming_payload = SendMessagePayload.model_validate(request.frame.payload)
clients = ClientManager.get_current()
client = clients.get(incoming_payload.to)
if client is None:
outgoing_payload = ErrorNotificationPayload(
text=f"User '{incoming_payload.to}' not found",
created_at=dt.datetime.now(dt.UTC),
)
await request.reply(ErrorNotificationFrame(payload=outgoing_payload))
request.logger.info("User '%s' not found", incoming_payload.to)
else:
outgoing_payload = BroadcastMessageNotificationPayload(
text=incoming_payload.text,
sender=request.client.user.id,
created_at=dt.datetime.now(dt.UTC),
)
frame = BroadcastMessageNotificationFrame(payload=outgoing_payload)
await client.send(frame)
request.logger.info("Message sent to '%s'", incoming_payload.to)


async def error_handler(request: Request, error: Exception) -> None:
payload = ErrorNotificationPayload(
text=f"Something went wrong: {error}",
created_at=dt.datetime.now(dt.UTC),
)
frame = ErrorNotificationFrame(payload=payload)
request.logger.exception("Error occurred while handling request")
await request.reply(frame)


async def logout_handler(request: Request) -> None:
clients = ClientManager.get_current()
await clients.drop(request.client)
request.logger.info("Client '%s' dropped", request.client.user.id)


async def unknown_action_handler(request: Request) -> None:
payload = ErrorNotificationPayload(text=f"Unknown command", created_at=dt.datetime.now(dt.UTC))
frame = ErrorNotificationFrame(payload=payload)
await request.reply(frame)
request.logger.info("Unknown command received from '%s'", request.client.user.id)
# async def error_handler(request: Request, error: Exception) -> None:
# payload = ErrorNotificationPayload(
# text=f"Something went wrong: {error}",
# created_at=dt.datetime.now(dt.UTC),
# )
# frame = ErrorNotificationFrame(payload=payload)
# request.logger.exception("Error occurred while handling request")
# await request.reply(frame)
#
#

#
# async def unknown_action_handler(request: Request) -> None:
# payload = ErrorNotificationPayload(text=f"Unknown command", created_at=dt.datetime.now(dt.UTC))
# frame = ErrorNotificationFrame(payload=payload)
# await request.reply(frame)
# request.logger.info("Unknown command received from '%s'", request.client.user.id)
6 changes: 6 additions & 0 deletions server/src/handlers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .base_handler import BaseHandler
from .message_handler import SendMessageHandler
from .broadcast_handler import BroadcastMessageHandler
from .logout_handler import LogoutHandler
from .unknown_handler import UnknownActionHandler
from .error_handler import BaseErrorHandler
29 changes: 29 additions & 0 deletions server/src/handlers/base_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydantic import BaseModel

from server.src.models.client import ClientManager, Client, LoggerLike


class BaseHandler:
clients: ClientManager = ClientManager.get_current()
payload_validator: BaseModel | None = None

def __init__(self, payload: dict, client: Client, logger: LoggerLike) -> None:
self.payload = self._get_payload(payload)
self.client = client
self.logger = logger

async def __call__(self, *args, **kwargs) -> None:
self.logger.info(f"Handling request '{self.__class__.__name__}' from '{self.client.user.id}'...")
await self.handle()
self.logger.info(f"Request '{self.__class__.__name__}' from '{self.client.user.id}' handled successfully")

def _get_payload(self, payload: dict) -> BaseModel | None:
try:
if self.payload_validator is not None:
return self.payload_validator.model_validate(payload)
return None
except AttributeError as error:
raise NotImplementedError(f"Payload validator is not defined for '{self.__class__.__name__}'") from error

async def handle(self) -> None:
raise NotImplementedError
23 changes: 23 additions & 0 deletions server/src/handlers/broadcast_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import asyncio
import datetime as dt
from typing import override

from server.src.handlers.base_handler import BaseHandler
from shared.schemas.actions import BroadcastMessagePayload
from shared.schemas.notifications import BroadcastMessageNotificationPayload, \
BroadcastMessageNotificationFrame


class BroadcastMessageHandler(BaseHandler):
payload_validator = BroadcastMessagePayload

@override
async def handle(self) -> None:
payload = BroadcastMessageNotificationPayload(
text=self.payload.text,
sender=self.client.user.id,
created_at=dt.datetime.now(dt.UTC),
)
frame = BroadcastMessageNotificationFrame(payload=payload)
tasks = [client.send(frame) for client in self.clients.all()]
await asyncio.gather(*tasks)
27 changes: 27 additions & 0 deletions server/src/handlers/error_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import datetime as dt

from server.src.models.client import ClientManager, Client, LoggerLike
from shared.schemas.notifications import ErrorNotificationPayload, ErrorNotificationFrame


class BaseErrorHandler:
clients: ClientManager = ClientManager.get_current()

def __init__(self, payload: dict, client: Client, error: Exception, logger: LoggerLike) -> None:
self.payload = payload
self.client = client
self.logger = logger
self.error = error

async def __call__(self, *args, **kwargs) -> None:
self.logger.info(f"Handling error from '{self.client.user.id}'...")
await self.handle()
self.logger.info(f"Error from '{self.client.user.id}' handled successfully")

async def handle(self) -> None:
payload = ErrorNotificationPayload(
text=f"Something went wrong: {self.error}",
created_at=dt.datetime.now(dt.UTC),
)
frame = ErrorNotificationFrame(payload=payload)
await self.client.send(frame)
17 changes: 17 additions & 0 deletions server/src/handlers/logout_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import asyncio
import datetime as dt
from typing import override

from server.src.handlers.base_handler import BaseHandler
from shared.schemas.actions import BroadcastMessagePayload
from shared.schemas.notifications import BroadcastMessageNotificationPayload, \
BroadcastMessageNotificationFrame


class LogoutHandler(BaseHandler):
payload_validator = None

@override
async def handle(self) -> None:
await self.clients.drop(self.client)

39 changes: 39 additions & 0 deletions server/src/handlers/message_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import datetime as dt
from typing import override

from server.src.handlers.base_handler import BaseHandler
from server.src.models.client import Client
from shared.schemas.actions import SendMessagePayload
from shared.schemas.notifications import BroadcastMessageNotificationPayload, \
BroadcastMessageNotificationFrame, ErrorNotificationFrame, ErrorNotificationPayload


class SendMessageHandler(BaseHandler):
validator = SendMessagePayload

@override
async def handle(self) -> None:
receiver = self.clients.get(self.payload.to)
if receiver is None:
await self._receiver_not_found()
else:
await self._send_message(receiver)

async def _receiver_not_found(self) -> None:
self.logger.info(f"User '{self.payload.to}' not found")
payload = ErrorNotificationPayload(
text=f"User '{self.payload.to}' not found",
created_at=dt.datetime.now(dt.UTC),
)
frame = ErrorNotificationFrame(payload=payload)
await self.client.send(frame)

async def _send_message(self, client: Client) -> None:
self.logger.info(f"Sending message to '{self.payload.to}'...")
payload = BroadcastMessageNotificationPayload(
text=self.payload.text,
sender=self.client.user.id,
created_at=dt.datetime.now(dt.UTC),
)
frame = BroadcastMessageNotificationFrame(payload=payload)
await client.send(frame)
18 changes: 18 additions & 0 deletions server/src/handlers/unknown_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import datetime as dt
from typing import override

from pydantic import TypeAdapter

from server.src.handlers.base_handler import BaseHandler
from shared.schemas.notifications import ErrorNotificationFrame, ErrorNotificationPayload


class UnknownActionHandler(BaseHandler):
validator = TypeAdapter(dict)

@override
async def handle(self) -> None:
payload = ErrorNotificationPayload(text=f"Unknown command", created_at=dt.datetime.now(dt.UTC))
frame = ErrorNotificationFrame(payload=payload)
await self.client.send(frame)
self.logger.info("Unknown command received from '%s'", self.client.user.id)
2 changes: 1 addition & 1 deletion server/src/models/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def __new__(cls) -> 'ClientManager':
@classmethod
def get_current(cls) -> Self:
if cls._instance is None:
raise RuntimeError("ClientManager is not initialized")
return cls()
return cls._instance

def create(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> Client:
Expand Down
36 changes: 0 additions & 36 deletions server/src/router.py

This file was deleted.

Loading

0 comments on commit f8dfc40

Please sign in to comment.