Skeleton ready

This commit is contained in:
2026-07-08 23:01:55 +04:00
commit d9e5effdce
75 changed files with 1989 additions and 0 deletions

View File

View File

View File

@@ -0,0 +1,10 @@
from typing import List
from kernel.feature.provider import Provider
from shared.application.rabbitmq_application import RabbitMQApplication
from shared.application.rabbitmq_kernel import RabbitMQKernel
class Application:
def create_rabbitmq(self, features: List[type[Provider]]) -> RabbitMQApplication:
return RabbitMQApplication(RabbitMQKernel(), features)

View File

@@ -0,0 +1,27 @@
import logging
from sys import stdout
class Logger:
def create_stdout(
self,
name: str,
level: int = logging.INFO
) -> logging.Logger:
logger = logging.getLogger(name)
logger.setLevel(level)
handler = logging.StreamHandler(stdout)
handler.setLevel(level)
handler.setFormatter(
logging.Formatter(
"%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
)
logger.addHandler(handler)
return logger

View File

View File

@@ -0,0 +1,40 @@
from typing import Any, Sequence
from argparse import ArgumentParser, HelpFormatter
class ListenerArgumentsParser(ArgumentParser):
def __init__(
self,
prog: str | None = None,
usage: str | None = None,
description: str | None = None,
epilog: str | None = None,
parents: Sequence[ArgumentParser] = [],
formatter_class: type[HelpFormatter] = HelpFormatter,
prefix_chars: str = '-',
fromfile_prefix_chars: str | None = None,
argument_default: Any = None,
conflict_handler: str = 'error',
add_help: bool = True,
allow_abbrev: bool = True,
exit_on_error: bool = True
) -> None:
super().__init__(
prog=prog,
usage=usage,
description=description,
epilog=epilog,
parents=parents,
formatter_class=formatter_class,
prefix_chars=prefix_chars,
fromfile_prefix_chars=fromfile_prefix_chars,
argument_default=argument_default,
conflict_handler=conflict_handler,
add_help=add_help,
allow_abbrev=allow_abbrev,
exit_on_error=exit_on_error
)
self.add_argument('--queue', type=str, required=True)
self.add_argument('--exchange', type=str, required=True)
self.add_argument('--routing-key', type=str, default="")

View File

@@ -0,0 +1,56 @@
from typing import Callable, Awaitable, override
from asyncio import CancelledError
from logging import Logger
from punq import Container
from aio_pika.abc import AbstractIncomingMessage
from kernel.application.stop_event import StopEvent
from kernel.application.view.configuration import Configuration
from kernel.application.application import Application
from kernel.rabbitmq.client import Client
from shared.application.view.listener_arguments import ListenerArguments
class RabbitMQApplication(Application):
@override
async def do(
self,
callback: Callable[[Container, AbstractIncomingMessage], Awaitable[None]]
) -> None:
rabbitmq = await self.__connect_rabbitmq_client()
arguments = self.get_container().resolve(ListenerArguments)
async def consumer(message: AbstractIncomingMessage) -> None:
await callback(self.get_container(), message)
queue = arguments.queue
exchange = arguments.exchange
routing_key = arguments.routing_key
await rabbitmq.consume(queue, exchange, consumer, routing_key)
logger = self.get_container().resolve(Logger)
logger.info(f"Queue: {queue}")
logger.info(f"Exchange: `{exchange}`")
if routing_key != "":
logger.info(f"Routing key: {routing_key}")
try:
await self.get_container().resolve(StopEvent).wait()
except CancelledError as e:
logger.exception(e)
async def __connect_rabbitmq_client(self) -> Client:
rabbitmq_client = self.get_container().resolve(Client)
stop_event = self.get_container().resolve(StopEvent)
configuration = self.get_container().resolve(Configuration)
await rabbitmq_client\
.connect(stop_event, configuration.rabbitmq().timeout)
return rabbitmq_client

View File

@@ -0,0 +1,139 @@
from os import getenv
from logging import Logger
from punq import Container
from pyee import EventEmitter
from pyee.asyncio import AsyncIOEventEmitter
from aioredlock import Aioredlock
from redis.asyncio import Redis as AsyncRedis
from kernel.application.manager.stop_event import StopEvent as StopEventManager
from kernel.application.stop_event import StopEvent
from kernel.application.view.application import Application
from kernel.application.view.configuration import Configuration
from kernel.application.view.rabbitmq import RabbitMQ
from kernel.application.view.redis import Redis as RedisConfig
from kernel.application.kernel import Kernel
from shared.application.service.environment_loader import EnvironmentLoader
from shared.application.manager.listener_arguments_parser import ListenerArgumentsParser
from shared.application.factory.logger import Logger as LoggerFactory
from shared.application.view.listener_arguments import ListenerArguments
from kernel.rabbitmq.client import Client
from kernel.rabbitmq.view.authentication_params import AuthenticationParams
from kernel.rabbitmq.view.connection_params import ConnectionParams
class RabbitMQKernel(Kernel):
__container: Container
def __init__(self) -> None:
self.__load_env()
self.__init_container()
self.__register_config()
self.__register_arguments()
self.__register_stop_event()
self.__register_logger()
self.__register_rabbitmq_client()
self.__register_redis()
self.__register_lock_provider()
self.__register_event_emitter()
def get_container(self) -> Container:
return self.__container
def __load_env(self) -> None:
env_loader = EnvironmentLoader(override=False)
env_loader.load()
def __init_container(self) -> None:
self.__container = Container()
def __register_config(self) -> None:
application = Application(
name=getenv("APP_NAME", ""),
env=getenv("APP_ENV", ""),
)
rabbitmq = RabbitMQ(
host=getenv("RABBITMQ_HOST", "localhost"),
port=int(getenv("RABBITMQ_PORT", "5672")),
user=getenv("RABBITMQ_USER", "guest"),
password=getenv("RABBITMQ_PASS", "guest"),
vhost=getenv("RABBITMQ_VHOST", "/")
)
redis = RedisConfig(
host=getenv("REDIS_HOST", "localhost"),
port=int(getenv("REDIS_PORT", "6379")),
db=int(getenv("REDIS_DB", "0"))
)
config = Configuration(application, rabbitmq, redis)
self.__container.register(Configuration, instance=config)
def __register_arguments(self) -> None:
argument_parser = ListenerArgumentsParser()
arguments_parsed = argument_parser.parse_args()
arguments = ListenerArguments(
queue=arguments_parsed.queue,
exchange=arguments_parsed.exchange,
routing_key=arguments_parsed.routing_key
)
self.__container.register(ListenerArguments, instance=arguments)
def __register_stop_event(self) -> None:
stop_event = StopEvent()
stop_event_manager = StopEventManager(stop_event)
stop_event_manager.setup_stop_signals()
self.__container.register(StopEvent, instance=stop_event)
def __register_logger(self) -> None:
logger_factory = LoggerFactory()
logger = logger_factory.create_stdout(name="rbmq.listener")
self.__container.register(Logger, instance=logger)
def __register_rabbitmq_client(self) -> None:
config = self.__container.resolve(Configuration).rabbitmq()
rabbitmq_client = Client(
AuthenticationParams(config.user, config.password),
ConnectionParams(config.host, config.port, config.vhost),
self.__container.resolve(Logger)
)
self.__container.register(Client, instance=rabbitmq_client)
def __register_redis(self) -> None:
config = self.__container.resolve(Configuration).redis()
client = AsyncRedis(
host=config.host,
port=config.port,
db=config.db,
decode_responses=True
)
self.__container.register(AsyncRedis, instance=client)
def __register_lock_provider(self) -> None:
config = self.__container.resolve(Configuration).redis()
aioredlock = Aioredlock([
{
"host": config.host,
"port": config.port,
"db": config.db
}
])
self.__container.register(Aioredlock, instance=aioredlock)
def __register_event_emitter(self) -> None:
event_emitter = AsyncIOEventEmitter()
self.__container.register(EventEmitter, instance=event_emitter)

View File

View File

@@ -0,0 +1,29 @@
from dotenv import find_dotenv, load_dotenv
class EnvironmentLoader:
__name: str
__usecwd: bool
__override: bool
def __init__(
self,
name: str = ".env",
usecwd: bool = True,
override: bool = True
) -> None:
self.__name = name
self.__usecwd = usecwd
self.__override = override
def load(self) -> None:
dotenv = find_dotenv(
filename=self.__name,
raise_error_if_not_found=True,
usecwd=self.__usecwd
)
load_dotenv(
dotenv_path=dotenv,
override=self.__override
)

View File

View File

@@ -0,0 +1,8 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class ListenerArguments:
queue: str
exchange: str
routing_key: str