Elexam Core является библиотекой для микро сервисов с базовым функционалом и форматированием.
Go to file
Returner_org ebdfa07c1f docs(core): уточнить тип event_dict в docstring add_trace_id
EventDict указан как фактический тип параметра из аннотации,
MutableMapping[str, Any] оставлен как пояснение эквивалентности.
2026-06-28 20:43:33 +03:00
src/elexam_core docs(core): уточнить тип event_dict в docstring add_trace_id 2026-06-28 20:43:33 +03:00
.gitignore Инициализировать проект elexam-core 2026-06-27 22:44:07 +03:00
.python-version Инициализировать проект elexam-core 2026-06-27 22:44:07 +03:00
pyproject.toml feat(core): добавить структурированное логирование на structlog 2026-06-28 20:40:46 +03:00
README.md Инициализировать проект elexam-core 2026-06-27 22:44:07 +03:00
uv.lock feat(core): добавить структурированное логирование на structlog 2026-06-28 20:40:46 +03:00

elexam-core

Общая библиотека платформы Elexam — единый источник правды для envelope ответа, trace_id, structlog, каталога ошибок, ULID и http-клиента. Все сервисы зависят от конкретной версии этой библиотеки, чтобы поведение было гарантированно одинаковым.


Что внутри

Модуль Назначение
elexam_core.context ContextVar[str] для хранения trace_id в пределах async-запроса (async-safe). Используется остальными модулями внутри.
elexam_core.middleware TraceMiddleware для FastAPI/Starlette — генерирует trace_id (ULID) или подхватывает его из входящего заголовка X-Trace-Id, кладёт в ContextVar.
elexam_core.envelope success() / error() — сборка стандартного конверта ответа API с trace_id, event, processed_at.
elexam_core.errors ErrorCode — Enum-каталог кодов ошибок вида exam.not_found; ERROR_META — HTTP-статус и severity для каждого кода.
elexam_core.logging Настройка structlog: JSON в stdout, trace_id автоматически в каждой строке лога.
elexam_core.ulid Утилита генерации ULID — монотонный, URL-safe, лексикографически сортируемый идентификатор. Используется везде вместо UUID.
elexam_core.http_client httpx-обёртка для межсервисных вызовов: автоматически пробрасывает текущий trace_id из ContextVar в заголовок X-Trace-Id исходящего запроса.

Установка

Через uv с приватным Gitea PyPI-индексом

Сначала добавь индекс в pyproject.toml своего сервиса (или убедись, что он уже объявлен):

[[tool.uv.index]]
name = "elexam"
url = "https://git.dregomor.ru/api/packages/Returner_org/pypi/simple"

Затем установи пакет:

uv add elexam-core==0.1.0

Напрямую из git по тегу

До того, как пакет залит в registry, или для точного закрепления на конкретном коммите:

uv add "elexam-core @ git+https://git.dregomor.ru/Returner_org/elexam-core.git@v0.1.0"

Быстрый пример

from fastapi import FastAPI
from fastapi.responses import JSONResponse
import structlog

import elexam_core.logging  # инициализирует structlog один раз при импорте

from elexam_core.middleware import TraceMiddleware
from elexam_core.envelope import success, error
from elexam_core.errors import ErrorCode, ERROR_META

app = FastAPI()
app.add_middleware(TraceMiddleware)  # с этого момента trace_id есть в каждом запросе

log = structlog.get_logger()


@app.get("/exams/{exam_id}")
async def get_exam(exam_id: str):
    exam = None  # здесь был бы запрос к БД

    if exam is None:
        log.warning("exam not found", exam_id=exam_id)
        # structlog выведет в stdout:
        # {"level": "warning", "trace_id": "01J7XR...", "exam_id": "...", "event": "exam not found", ...}

        meta = ERROR_META[ErrorCode.EXAM_NOT_FOUND]
        return JSONResponse(
            status_code=meta["http"],
            content=error(
                message="Экзамен не найден",
                errors=[{"field": "exam_id", "code": ErrorCode.EXAM_NOT_FOUND}],
                event_type="exam.get",
            ),
        )

    return success(data=exam, event_type="exam.get")

Ответ при успехе:

{
  "status": "success",
  "data": { "id": "01J7XR...", "title": "Математика" },
  "message": "OK",
  "errors": null,
  "meta": null,
  "trace_id": "01J7XRQP2E...",
  "event": { "id": "01J7XRQP2F...", "type": "exam.get" },
  "processed_at": "2026-06-27T12:34:56.789Z"
}

Один и тот же trace_id попадает в заголовок X-Trace-Id ответа, в тело JSON, в каждую строку лога. Найти по нему всю цепочку запроса через все сервисы можно одним грепом по stdout.