Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/app/app.py → app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@


class Application(AiohttpApplication):
database: Database | None = None
config: Config | None = None
store: Store | None = None
database: Database
config: Config
store: Store


app = Application()
Expand Down
23 changes: 19 additions & 4 deletions app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from sqlalchemy.engine.url import URL

if typing.TYPE_CHECKING:
from app.app.app import Application
from app.app import Application


class BotConfig(BaseModel):
Expand All @@ -32,12 +32,27 @@ def url(self) -> URL:
)


class RabbitmqConfig(BaseModel):
host: str = "localhost"
port: int = 5672
user: str = "guest"
password: str = "guest"
input_queue: str = "input_queue"
output_queue: str = "output_queue"

@cached_property
def url(self) -> str:
return f"amqp://{self.user}:{self.password}@{self.host}:{self.port}"


class Config(BaseSettings):
bot: BotConfig | None = None
database: DatabaseConfig | None = None
bot: BotConfig
database: DatabaseConfig
rabbitmq: RabbitmqConfig

model_config = SettingsConfigDict(
env_file=(".env", "../../../../.env"), # Если main / если миграции
# Если main / если poller / если миграции
env_file=(".env", "../../.env", "../../../../.env"),
env_nested_delimiter="__",
)

Expand Down
2 changes: 1 addition & 1 deletion app/core/database/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from app.core.database.sqlalchemy_base import BaseModel

if TYPE_CHECKING:
from app.app.app import Application
from app.app import Application


class Database:
Expand Down
2 changes: 1 addition & 1 deletion app/core/database/migrations/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sqlalchemy.ext.asyncio import async_engine_from_config

import app
from app.app.app import app as application, setup_app
from app.app import app as application, setup_app
from app.core.database.sqlalchemy_base import BaseModel

setup_app()
Expand Down
39 changes: 39 additions & 0 deletions app/core/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from collections.abc import Awaitable, Callable

import aio_pika
from aio_pika import Message
from aio_pika.abc import AbstractIncomingMessage


class RabbitMQManager:
def __init__(self, amqp_url: str, queue_name: str):
self._url = amqp_url
self._queue_name = queue_name
self._connection: aio_pika.abc.AbstractRobustConnection | None = None
self._channel: aio_pika.abc.AbstractChannel | None = None
self._queue: aio_pika.abc.AbstractQueue | None = None

async def connect(self) -> None:
self._connection = await aio_pika.connect_robust(self._url)
self._channel = await self._connection.channel()
self._queue = await self._channel.declare_queue(self._queue_name, durable=True)

async def send(self, body: bytes) -> None:
if self._channel is None:
raise RuntimeError("RabbitMQManager is not connected")
await self._channel.default_exchange.publish(
Message(body=body),
routing_key=self._queue_name,
)

async def consume(
self,
handler: Callable[[AbstractIncomingMessage], Awaitable[None]],
) -> None:
if self._queue is None:
raise RuntimeError("RabbitMQManager is not connected")
await self._queue.consume(handler)

async def close(self) -> None:
if self._connection:
await self._connection.close()
2 changes: 1 addition & 1 deletion app/core/store.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import typing

if typing.TYPE_CHECKING:
from app.app.app import Application
from app.app import Application


class Store: # noqa: B903
Expand Down
13 changes: 13 additions & 0 deletions app/poller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.12-alpine
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
Comment thread
Gray-Advantage marked this conversation as resolved.

WORKDIR /project
COPY pyproject.toml uv.lock ./

RUN uv sync --compile-bytecode --no-cache --no-dev
ENV PATH="/project/.venv/bin:$PATH"

COPY . .

WORKDIR /project
CMD python -m app.poller.poller
Comment thread
Gray-Advantage marked this conversation as resolved.
File renamed without changes.
34 changes: 34 additions & 0 deletions app/poller/poller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import asyncio
import json
from typing import Any, cast

import aiohttp

from app.app import app, setup_app
from app.core.manager import RabbitMQManager


async def get_updates(offset: int) -> dict[str, Any]:
url = f"https://api.telegram.org/bot{app.config.bot.token}/getUpdates" # поменяю
async with aiohttp.ClientSession() as session:
Comment thread
Gray-Advantage marked this conversation as resolved.
async with session.get(url, params={"offset": offset, "timeout": 30}) as resp:
return cast(dict[str, Any], await resp.json())


async def poll_and_push() -> None:
rabbit = RabbitMQManager(
amqp_url=app.config.rabbitmq.url,
queue_name=app.config.rabbitmq.input_queue,
)
await rabbit.connect()

offset = 0
while True:
Comment thread
Gray-Advantage marked this conversation as resolved.
data = await get_updates(offset)
for update in data.get("result", []):
offset = update["update_id"] + 1
await rabbit.send(json.dumps(update).encode())


setup_app()
asyncio.run(poll_and_push())
31 changes: 31 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,35 @@ services:
interval: 2s
timeout: 5s
retries: 10
restart: always
command: -p ${DATABASE__PORT}

Comment thread
Gray-Advantage marked this conversation as resolved.
rabbitmq:
image: rabbitmq:4.0.8-management-alpine # временно
ports:
- ${RABBITMQ__PORT}:${RABBITMQ__PORT}
- 15672:15672 # временно
env_file:
- .env
environment:
- RABBITMQ_DEFAULT_USER=${RABBITMQ__USER}
- RABBITMQ_DEFAULT_PASS=${RABBITMQ__PASSWORD}
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "ping"]
interval: 2s
timeout: 5s
retries: 10
Comment thread
Gray-Advantage marked this conversation as resolved.
restart: always

poller:
build:
context: .
dockerfile: app/poller/Dockerfile
depends_on:
postgres:
condition: service_healthy
rabbitmq:
condition: service_healthy
env_file:
Comment thread
Gray-Advantage marked this conversation as resolved.
- .env
restart: always
Comment thread
Gray-Advantage marked this conversation as resolved.
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from aiohttp.web import run_app

from app.app.app import setup_app
from app.app import setup_app

if __name__ == "__main__":
run_app(setup_app())
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ dependencies = [
"pycparser==2.22",
"typing_extensions==4.13.2",
"webargs==8.6.0",
"yarl==1.19.0"
"yarl==1.19.0",
"aio-pika==9.5.5",
]

[dependency-groups]
Expand Down Expand Up @@ -95,6 +96,7 @@ extend-select = [
extend-ignore = [
"D1",
"CPY001",
"SIM117",
# По рекомендации https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
"W191",
"E111",
Expand Down Expand Up @@ -138,4 +140,4 @@ mark-parentheses = false
[tool.mypy]
strict = true # Ещё строже!
python_version = "3.12"
plugins = ['pydantic.mypy']
plugins = ['pydantic.mypy']
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ aiohttp==3.11.16
aiohttp_apispec==3.0.0b2
aiohttp_cors==0.8.1
aiohttp_session==2.12.1
aio-pika==9.5.5
alembic==1.15.2
asyncpg==0.30.0
cryptography==44.0.1
Expand Down
12 changes: 12 additions & 0 deletions template.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
BOT__TOKEN=0123456789:a1b2c3d4e5f6g7h8i9jklmnopqrstuvwxyz
Comment thread
Gray-Advantage marked this conversation as resolved.

DATABASE__HOST=localhost
DATABASE__PORT=5432
DATABASE__USER=postgres
DATABASE__PASSWORD=postgres
DATABASE__DATABASE=postgres

RABBITMQ__HOST=localhost
RABBITMQ__PORT=5672
RABBITMQ__USER=guest
RABBITMQ__PASSWORD=guest
47 changes: 47 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.