Skip to content

Commit d45be7e

Browse files
authored
feat: add worker (#34)
1 parent bbe1390 commit d45be7e

File tree

10 files changed

+340
-5
lines changed

10 files changed

+340
-5
lines changed

.gitlab-ci.yml.jinja

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# YAML objects named with dot is anchors, which are not recognized as jobs
22
.run_bot: &run_bot
33
- docker rm -f ${CONTAINER_NAME} || true
4+
{% if add_worker -%}- docker rm -f ${CONTAINER_NAME}-worker || true{%- endif %}
45
- docker pull ${CONTAINER_RELEASE_IMAGE}
56
# Add new envs here. Don't forget to add them in example.env and docker-compose files.
67
- docker run
@@ -17,6 +18,21 @@
1718
-e BOT_CREDENTIALS="${BOT_CREDENTIALS}"
1819
-e DEBUG="${DEBUG:-false}"
1920
$CONTAINER_RELEASE_IMAGE
21+
{% if add_worker -%}
22+
# Add envs for worker here
23+
- docker run
24+
-d
25+
--name ${CONTAINER_NAME}-worker
26+
--restart always
27+
--log-opt max-size=10m
28+
--log-opt max-file=5
29+
-e POSTGRES_DSN="${POSTGRES_DSN}"
30+
-e REDIS_DSN="${REDIS_DSN}"
31+
-e BOT_CREDENTIALS="${BOT_CREDENTIALS}"
32+
-e DEBUG="${DEBUG:-false}"
33+
${CONTAINER_RELEASE_IMAGE}
34+
bash -c 'PYTHONPATH="$PYTHONPATH:$PWD" saq app.worker.worker.settings'
35+
{%- endif %}
2036

2137
.create_db: &create_db
2238
- psql -c "create user \"${POSTGRES_USER}\"" postgres || true
@@ -147,6 +163,9 @@ deploy.botstest.stop:
147163
extends: deploy.botstest
148164
script:
149165
- docker rm -f ${CONTAINER_NAME} || true
166+
{% if add_worker -%}
167+
- docker rm -f ${CONTAINER_NAME} ${CONTAINER_NAME}-worker || true
168+
{%- endif %}
150169
- psql -c "select pg_terminate_backend(pid) from pg_stat_activity \
151170
where datname = '${POSTGRES_DB}';" postgres || true
152171
- psql -c "drop database \"${POSTGRES_DB}\"" postgres || true

app/api/dependencies/healthcheck.py renamed to app/api/dependencies/healthcheck.py.jinja

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
"""Bot dependency for healthcheck."""
22

3+
{% if add_worker -%}
4+
from asyncio.exceptions import TimeoutError
5+
{%- endif %}
36
from typing import Optional
47

58
from fastapi import Depends, Request
69
from pybotx import Bot
710
from sqlalchemy.sql import text
811

12+
{% if add_worker -%}
13+
from app.settings import settings
14+
from app.worker.worker import queue
15+
{%- endif %}
16+
917

1018
async def check_db_connection(request: Request) -> Optional[str]:
1119
assert isinstance(request.app.state.bot, Bot)
@@ -33,3 +41,24 @@ async def check_redis_connection(request: Request) -> Optional[str]:
3341

3442

3543
check_redis_connection_dependency = Depends(check_redis_connection)
44+
{%- if add_worker %}
45+
46+
47+
async def check_worker_status() -> Optional[str]:
48+
job = await queue.enqueue("healthcheck")
49+
50+
if not job:
51+
return None
52+
53+
try:
54+
await job.refresh(settings.WORKER_TIMEOUT_SEC)
55+
except TimeoutError:
56+
return "Worker is overloaded or not launched"
57+
except Exception as exc:
58+
return str(exc)
59+
60+
return None
61+
62+
63+
check_worker_status_dependency = Depends(check_worker_status)
64+
{%- endif %}

app/api/endpoints/healthcheck.py renamed to app/api/endpoints/healthcheck.py.jinja

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from app.api.dependencies.healthcheck import (
88
check_db_connection_dependency,
99
check_redis_connection_dependency,
10+
{% if add_worker -%}
11+
check_worker_status_dependency,
12+
{%- endif %}
1013
)
1114
from app.services.healthcheck import (
1215
HealthCheckResponse,
@@ -21,6 +24,9 @@
2124
async def healthcheck(
2225
redis_connection_error: Optional[str] = check_redis_connection_dependency,
2326
db_connection_error: Optional[str] = check_db_connection_dependency,
27+
{% if add_worker -%}
28+
worker_status_error: Optional[str] = check_worker_status_dependency,
29+
{%- endif %}
2430
) -> HealthCheckResponse:
2531
"""Check the health of the bot and services."""
2632
healthcheck_builder = HealthCheckResponseBuilder()
@@ -30,5 +36,10 @@ async def healthcheck(
3036
healthcheck_builder.add_healthcheck_result(
3137
HealthCheckServiceResult(name="redis", error=redis_connection_error)
3238
)
39+
{% if add_worker -%}
40+
healthcheck_builder.add_healthcheck_result(
41+
HealthCheckServiceResult(name="worker", error=worker_status_error)
42+
)
43+
{%- endif %}
3344

3445
return healthcheck_builder.build()

app/settings.py renamed to app/settings.py.jinja

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ class Config: # noqa: WPS431
3030
# redis
3131
REDIS_DSN: str
3232

33+
{% if add_worker -%}
34+
# healthcheck
35+
WORKER_TIMEOUT_SEC: float = 4
36+
{%- endif %}
37+
3338
@validator("BOT_CREDENTIALS", pre=True)
3439
@classmethod
3540
def parse_bot_credentials(cls, raw_credentials: Any) -> List[BotAccountWithSecret]:

app/{% if add_worker %}worker{% endif %}/__init__.py

Whitespace-only changes.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""Tasks worker configuration."""
2+
3+
from typing import Any, Dict, Literal
4+
5+
from pybotx import Bot
6+
from redis import asyncio as aioredis
7+
from saq import Queue
8+
9+
from app.caching.callback_redis_repo import CallbackRedisRepo
10+
from app.logger import logger
11+
12+
# `saq` import its own settings and hides our module
13+
from app.settings import settings as app_settings
14+
15+
SaqCtx = Dict[str, Any]
16+
17+
18+
async def startup(ctx: SaqCtx) -> None:
19+
from app.bot.bot import get_bot # noqa: WPS433
20+
21+
callback_repo = CallbackRedisRepo(aioredis.from_url(app_settings.REDIS_DSN))
22+
bot = get_bot(callback_repo)
23+
24+
await bot.startup(fetch_tokens=False)
25+
26+
ctx["bot"] = bot
27+
28+
logger.info("Worker started")
29+
30+
31+
async def shutdown(ctx: SaqCtx) -> None:
32+
bot: Bot = ctx["bot"]
33+
await bot.shutdown()
34+
35+
logger.info("Worker stopped")
36+
37+
38+
async def healthcheck(_: SaqCtx) -> Literal[True]:
39+
return True
40+
41+
42+
queue = Queue(aioredis.from_url(app_settings.REDIS_DSN), name="{{bot_project_name}}")
43+
44+
settings = {
45+
"queue": queue,
46+
"functions": [healthcheck],
47+
"cron_jobs": [],
48+
"concurrency": 8,
49+
"startup": startup,
50+
"shutdown": shutdown,
51+
}

copier.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ bot_description:
1111
help: Description for README.md. First line will be added pyproject.toml
1212
default: TODO
1313

14+
add_worker:
15+
help: Include tasks worker in `docker-compose.yml`
16+
type: bool
17+
default: yes
18+
1419

1520
bot_name_underscored:
1621
default: "{{bot_project_name|replace('-', '_')}}"

docker-compose.yml.jinja

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,41 @@ services:
44
{{bot_project_name}}:
55
build: .
66
container_name: {{bot_project_name}}
7-
environment:
7+
environment: &environment
88
- BOT_CREDENTIALS=cts_host@secret_key@bot_id
99
- POSTGRES_DSN=postgres://postgres:postgres@{{bot_project_name}}-postgres/{{bot_name_underscored}}_db
1010
- REDIS_DSN=redis://{{bot_project_name}}-redis/0
1111
- DEBUG=true
1212
ports:
1313
- "8000:8000" # Отредактируйте порт хоста (первый), если он уже занят
1414
restart: always
15-
depends_on:
15+
depends_on: &depends_on
1616
- postgres
1717
- redis
18-
logging:
18+
logging: &logging
1919
driver: "json-file"
2020
options:
2121
max-size: "10m"
2222
max-file: "10"
23-
ulimits:
23+
ulimits: &ulimits
2424
nproc: 65535
2525
nofile:
2626
soft: 20000
2727
hard: 40000
2828

29+
{% if add_worker -%}
30+
{{bot_project_name}}-worker:
31+
build: .
32+
container_name: {{bot_project_name}}-worker
33+
# '$$' prevents docker-compose from interpolating a value
34+
command: /bin/sh -c 'PYTHONPATH="$$PYTHONPATH:$$PWD" saq app.worker.worker.settings'
35+
environment: *environment
36+
restart: always
37+
depends_on: *depends_on
38+
logging: *logging
39+
ulimits: *ulimits
40+
41+
{% endif -%}
2942
postgres:
3043
image: postgres:15.3-alpine
3144
container_name: {{bot_project_name}}-postgres

0 commit comments

Comments
 (0)