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
2 changes: 1 addition & 1 deletion .github/scripts/test_image
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -ex

PROJECT_NAME=smartapp-template

python -m copier --defaults . smartapp-template
python -m copier copy --trust --defaults --vcs-ref=HEAD . smartapp-template
docker build -t $PROJECT_NAME $PROJECT_NAME

# there should be added `--fail` option to curl command when healthcheck endpoint is added
Expand Down
88 changes: 46 additions & 42 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ env:
BOT_CREDENTIALS: example.ru@6d624b45b67f843322ebaa1143132a24@7a1c7744-23c9-4ba9-a7dc-5381f090a52d
REDIS_DSN: redis://localhost:6379/0
POSTGRES_DSN: postgresql://postgres:postgres@localhost:5432/postgres
poetry_version: "1.4.0"
poetry_version: "2.1.2"
project_name: "async-box-bot"
PROD_SERVER_HOST: "prod.example.com"
DEV_SERVER_HOST: "dev.example.com"
Expand All @@ -13,11 +13,11 @@ env:
jobs:
test-image:
name: Test app start
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04

services:
postgres:
image: postgres:12
image: postgres:15.3-alpine
ports:
- 5432:5432
env:
Expand All @@ -29,7 +29,7 @@ jobs:
--health-retries 5

redis:
image: redis:6
image: redis:7.0-alpine
ports:
- 6379:6379
options: >-
Expand All @@ -40,15 +40,17 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install copier
run: |
pip install copier==7.1.0
pip install copier-templates-extensions
pip install pyyaml-include==1.4.1
# hardcode pydantic version to avoid dependency conflict with copier
pip install pydantic==1.10.2
python -m pip install --upgrade pip
pip install copier==9.15.1 copier-templates-extensions==0.3.2

- name: Test image
env:
Expand All @@ -59,14 +61,14 @@ jobs:

test-template:
name: Test template
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: [ "3.8", "3.9", "3.10" ]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]

services:
postgres:
image: postgres:12
image: postgres:15.3-alpine
ports:
- 5432:5432
env:
Expand All @@ -78,7 +80,7 @@ jobs:
--health-retries 5

redis:
image: redis:6
image: redis:7.0-alpine
ports:
- 6379:6379
options: >-
Expand All @@ -89,29 +91,30 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install copier
run: |
pip install copier==7.1.0
pip install copier-templates-extensions
pip install pyyaml-include==1.4.1
# hardcode pydantic version to avoid dependency conflict with copier
pip install pydantic==1.10.2
python -m copier --defaults . async-box-bot
uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install copier
run: |
python -m pip install --upgrade pip
pip install copier==9.15.1 copier-templates-extensions==0.3.2
python -m copier copy --trust --defaults --vcs-ref=HEAD . async-box-bot

- name: Get full Python version
id: full-python-version
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
run: |
python - <<'PY' >> "$GITHUB_OUTPUT"
import sys
print("version=" + "-".join(str(v) for v in sys.version_info))
PY

- name: Set up cache
id: cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.cache/pip
Expand All @@ -137,36 +140,37 @@ jobs:

lint:
name: Lint
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: [ "3.8", "3.9", "3.10" ]
python-version: [ "3.10", "3.11", "3.12", "3.13" ]

steps:
- name: Checkout
uses: actions/checkout@v2

- name: Install copier
run: |
pip install copier==7.1.0
pip install copier-templates-extensions
pip install pyyaml-include==1.4.1
# hardcode pydantic version to avoid dependency conflict with copier
pip install pydantic==1.10.2
python -m copier --defaults . async-box-bot
uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install copier
run: |
python -m pip install --upgrade pip
pip install copier==9.15.1 copier-templates-extensions==0.3.2
python -m copier copy --trust --defaults --vcs-ref=HEAD . async-box-bot

- name: Get full Python version
id: full-python-version
run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))")
run: |
python - <<'PY' >> "$GITHUB_OUTPUT"
import sys
print("version=" + "-".join(str(v) for v in sys.version_info))
PY

- name: Set up cache
id: cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: |
~/.cache/pip
Expand Down
6 changes: 3 additions & 3 deletions .gitlab-ci.yml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

.install_dependencies: &install_dependencies
- echo -e "machine ${GIT_HOST}\nlogin gitlab-ci-token\npassword ${CI_JOB_TOKEN}" > ~/.netrc
- pip install -q poetry
- pip install -q poetry==2.1.2
- poetry config virtualenvs.in-project true
- poetry install

Expand Down Expand Up @@ -76,7 +76,7 @@ default:
interruptible: true

lint:
image: python:3.10
image: python:3.13
stage: check
tags:
- docker
Expand All @@ -87,7 +87,7 @@ lint:
- poetry run ./scripts/lint

test:
image: python:3.10
image: python:3.13
stage: check
tags:
- docker
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.jinja
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.11.5-alpine
FROM python:3.13.11-alpine

{% if from_ccsteam %}LABEL Maintainer="eXpress Unlimited Production"{% endif %}

Expand Down Expand Up @@ -31,7 +31,7 @@ ENV GUNICORN_CMD_ARGS ""
ARG CI_COMMIT_SHA=""
ENV GIT_COMMIT_SHA=${CI_COMMIT_SHA}

RUN pip install --user --no-cache-dir poetry==1.4.2 && \
RUN pip install --user --no-cache-dir poetry==2.1.2 && \
poetry config virtualenvs.in-project true

COPY poetry.lock pyproject.toml ./
Expand Down
35 changes: 8 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,22 +230,10 @@ $ ./scripts/format
$ ./scripts/lint
```

#### Описание
* [black](https://github.com/psf/black)
#### Описание
* [ruff](https://github.com/astral-sh/ruff)

Используется для форматирования кода к единому стилю: разбивает длинные строки, следит за отступами и импортами.

> :warning: Примечание
> В некоторых моментах isort конфликтует с black. Конфликт решается настройкой файла конфигурации **`setup.cfg`**.

* [isort](https://github.com/timothycrosley/isort)

Используется для сортировки импортов. Сначала импорты из стандартных библиотек python, затем из внешних библиотек и в конце из модулей данного проекта.
Между собой импорты сортируются по алфавиту.

* [autoflake](https://github.com/myint/autoflake)

Используется для удаления неиспользуемых импортов и переменных.
Используется для форматирования кода, сортировки импортов, удаления части автоматически исправимых проблем и статической проверки. Заменяет `black`, `isort`, `autoflake`, `flake8` и `wemake-python-styleguide`.

* [mypy](https://github.com/python/mypy)

Expand Down Expand Up @@ -278,22 +266,15 @@ warn_required_dynamic_aliases = True
warn_untyped_fields = True
```

* [wemake-python-styleguide](https://github.com/wemake-services/wemake-python-styleguide)

Используется для комплексной проверки. Анализирует допустимые имена перменных и их длину, сложность вложенных конструкций, правильную обработку исключений и многое другое. Для каждого типа ошибок есть свой уникальный номер, объяснение, почему так делать не стоит, и объяснение, как делать правильно. Список ошибок можно посмотреть [тут](https://wemake-python-stylegui.de/en/latest/pages/usage/violations/index.html).

> :information_source: Инфо
> В некоторых редких случаях можно игнорировать правила линтера. Для этого необходимо либо прописать комментарий с меткой `noqa` на проблемной строке:
> ```python3
> var = problem_function() # noqa: WPS999
> ```
> либо указать `ignore` ошибки в **`setup.cfg`**:
> var = problem_function() # noqa: RUF999
> ```
> [flake8]
> # ...
> ignore =
> # f-strings are useful
> WPS305,
> либо указать `ignore` ошибки в **`pyproject.toml`**:
> ```toml
> [tool.ruff.lint]
> ignore = ["RUF999"]
> ```
> Также можно исключать модули и пакеты.

Expand Down
7 changes: 3 additions & 4 deletions app/api/dependencies/healthcheck.py.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
{% if add_worker -%}
from asyncio.exceptions import TimeoutError
{%- endif %}
from typing import Optional

from fastapi import Depends, Request
from pybotx import Bot
Expand All @@ -15,7 +14,7 @@ from app.worker.worker import queue
{%- endif %}


async def check_db_connection(request: Request) -> Optional[str]:
async def check_db_connection(request: Request) -> str | None:
assert isinstance(request.app.state.bot, Bot)

bot = request.app.state.bot
Expand All @@ -33,7 +32,7 @@ async def check_db_connection(request: Request) -> Optional[str]:
check_db_connection_dependency = Depends(check_db_connection)


async def check_redis_connection(request: Request) -> Optional[str]:
async def check_redis_connection(request: Request) -> str | None:
assert isinstance(request.app.state.bot, Bot)

bot = request.app.state.bot
Expand All @@ -44,7 +43,7 @@ check_redis_connection_dependency = Depends(check_redis_connection)
{%- if add_worker %}


async def check_worker_status() -> Optional[str]:
async def check_worker_status() -> str | None:
job = await queue.enqueue("healthcheck")

if not job:
Expand Down
8 changes: 3 additions & 5 deletions app/api/endpoints/healthcheck.py.jinja
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Endpoint healthcheck."""

from typing import Optional

from fastapi import APIRouter

from app.api.dependencies.healthcheck import (
Expand All @@ -22,10 +20,10 @@ router = APIRouter()

@router.get("/healthcheck")
async def healthcheck(
redis_connection_error: Optional[str] = check_redis_connection_dependency,
db_connection_error: Optional[str] = check_db_connection_dependency,
redis_connection_error: str | None = check_redis_connection_dependency,
db_connection_error: str | None = check_db_connection_dependency,
{% if add_worker -%}
worker_status_error: Optional[str] = check_worker_status_dependency,
worker_status_error: str | None = check_worker_status_dependency,
{%- endif %}
) -> HealthCheckResponse:
"""Check the health of the bot and services."""
Expand Down
5 changes: 2 additions & 3 deletions app/api/endpoints/swagger_rpc_execute.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Execute RPC method endpoint."""

from json import JSONDecodeError

from fastapi import APIRouter, Depends, Request
Expand Down Expand Up @@ -47,9 +48,7 @@ async def rpc_execute(
smartapp = SmartApp(bot, event.bot.id, event.chat.id, event)
rpc_request = RPCRequest(method=method, type="smartapp_rpc", params=method_payload)

rpc_response = await smartapp_rpc._router.perform_rpc_request( # noqa: WPS437
smartapp, rpc_request
)
rpc_response = await smartapp_rpc._router.perform_rpc_request(smartapp, rpc_request)
if isinstance(rpc_response, RPCErrorResponse):
return JSONResponse(
status_code=HTTP_400_BAD_REQUEST,
Expand Down
7 changes: 4 additions & 3 deletions app/api/exceptions/botx.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Handlers for BotX request exceptions."""

from collections.abc import Callable
from functools import wraps
from http import HTTPStatus
from typing import Any, Callable
from typing import Any

from fastapi.responses import JSONResponse
from pybotx import (
Expand All @@ -18,10 +19,10 @@
from app.settings import settings


def handle_exceptions(func: Callable) -> Callable: # noqa: WPS212
def handle_exceptions(func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> JSONResponse:
try: # noqa: WPS225
try:
return await func(*args, **kwargs)
except ValueError:
error_label = "Bot command validation error"
Expand Down
1 change: 1 addition & 0 deletions app/api/routers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Configuration of routers for all endpoints."""

from fastapi import APIRouter

from app.api.endpoints.botx import router as bot_router
Expand Down
Loading
Loading