diff --git a/api/run_server.py b/api/run_server.py index f2449e1..37a9775 100644 --- a/api/run_server.py +++ b/api/run_server.py @@ -1,4 +1,5 @@ import uvicorn + from src import app, get_settings if __name__ == "__main__": @@ -9,4 +10,3 @@ if __name__ == "__main__": port=settings.server.PORT, lifespan="on", ) - diff --git a/api/src/__init__.py b/api/src/__init__.py index 61c3faa..97b1796 100644 --- a/api/src/__init__.py +++ b/api/src/__init__.py @@ -2,6 +2,6 @@ from .app import app from .core.config import get_settings __all__ = [ - 'app', - 'get_settings', -] \ No newline at end of file + "app", + "get_settings", +] diff --git a/api/src/app.py b/api/src/app.py index 940e53e..cd9ab60 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -5,8 +5,9 @@ headers), обработчики ошибок, `GET /healthcheck` (`uptime`, `ve Чего здесь пока нет: NATS-воркера и S3-клиента (подключаются в lifespan задачами 5.2/5.3 change slice-0-skeleton), полей `nats`/`hotfolder` в healthcheck. """ + from contextlib import asynccontextmanager -from datetime import datetime, timezone +from datetime import UTC, datetime import smbclient from fastapi import FastAPI @@ -31,7 +32,7 @@ def _normalize_root_path(value: str) -> str: def create_app(settings: Settings | None = None) -> FastAPI: settings = settings or get_settings() version = settings.VERSION - up_time = datetime.now(timezone.utc) + up_time = datetime.now(UTC) @asynccontextmanager async def lifespan(app: FastAPI): diff --git a/api/src/common/exceptions.py b/api/src/common/exceptions.py index 2eca689..3eaa18e 100644 --- a/api/src/common/exceptions.py +++ b/api/src/common/exceptions.py @@ -1,5 +1,6 @@ class NotFoundError(Exception): pass + class CouldntCreateError(Exception): - pass \ No newline at end of file + pass diff --git a/api/src/core/clients.py b/api/src/core/clients.py index 8f07d24..ceb87e5 100644 --- a/api/src/core/clients.py +++ b/api/src/core/clients.py @@ -1,6 +1,7 @@ """Клиенты инфраструктуры. Каркас: оставлен S3Storage; outbox-паблишер (PollWorker) реализуется в Срезе 0 по docs/context/03-architecture.md (референс — reference-old-arm/api/src/core/clients.py). """ + from io import BytesIO from miniopy_async import Minio @@ -10,11 +11,11 @@ class S3Storage: def __init__(self, client: Minio, bucket_name: str): self.client = client self.bucket = bucket_name - + async def put_object(self, obj_name, file_data): try: stream = BytesIO(file_data) - await self.client.put_object(self.bucket, obj_name, stream, length=len(file_data)) + await self.client.put_object(self.bucket, obj_name, stream, length=len(file_data)) except: raise @@ -23,5 +24,5 @@ class S3Storage: response = await self.client.get_object(self.bucket, obj_name) # file_bytes = await response.content.read() return response - except: + except: raise diff --git a/api/src/core/config.py b/api/src/core/config.py index cd7881a..b54822a 100644 --- a/api/src/core/config.py +++ b/api/src/core/config.py @@ -6,6 +6,7 @@ Файл окружения выбирается переменной `ENV_FILE` (по умолчанию `.env`, путь считается от текущего каталога — `api/`); переменные окружения приоритетнее файла. Настройки читаются при первом вызове `get_settings()`, не при импорте. """ + import os from functools import lru_cache from typing import Literal diff --git a/api/src/core/logger.py b/api/src/core/logger.py index 2f99033..4d1ae4c 100644 --- a/api/src/core/logger.py +++ b/api/src/core/logger.py @@ -5,7 +5,6 @@ import sys from loguru import logger - # Периодические пробы мониторинга/оркестратора и служебные doc-эндпоинты. NOISE_PATHS: tuple[str, ...] = ( "/healthcheck", @@ -22,8 +21,20 @@ _NOISE_MARKERS: tuple[str, ...] = ("/static/", "/static-swagger/") # Расширения статических ассетов (картинки, стили, скрипты, шрифты). _STATIC_SUFFIXES: tuple[str, ...] = ( - ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".webp", - ".css", ".js", ".map", ".woff", ".woff2", ".ttf", ".eot", + ".png", + ".jpg", + ".jpeg", + ".gif", + ".svg", + ".ico", + ".webp", + ".css", + ".js", + ".map", + ".woff", + ".woff2", + ".ttf", + ".eot", ) @@ -73,6 +84,7 @@ def setup_logger( class InterceptHandler(logging.Handler): def emit(self, record: logging.LogRecord) -> None: + lvl: str | int try: lvl = logger.level(record.levelname).name except ValueError: diff --git a/api/src/core/nats_callback.py b/api/src/core/nats_callback.py index 2f2e05b..e3420b5 100644 --- a/api/src/core/nats_callback.py +++ b/api/src/core/nats_callback.py @@ -4,6 +4,7 @@ маппинг JSON из S3 в document/osv_entry/financial_statement_*) реализуется в Срезе 0–2 по docs/context/03-architecture.md. Для референса — reference-old-arm/api/src/core/nats_callback.py. """ + from loguru import logger from nats.aio.client import Client as NATS diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 446666c..79ca8ed 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -1,5 +1,6 @@ """Общие фикстуры. Минимальное окружение local-режима задаётся здесь, чтобы `import src` (он создаёт приложение) работал без `.env.test` и без инфраструктуры; значения можно переопределить переменными окружения.""" + import os import pytest @@ -20,7 +21,8 @@ for _key, _value in _MINIMAL_ENV.items(): @pytest.fixture(autouse=True) def _fresh_settings(): """get_settings кэшируется (lru_cache): сбрасываем кэш до и после каждого теста.""" - from src.core.config import get_settings + # Импорт отложен намеренно: `import src` создаёт приложение и требует окружения, заданного выше + from src.core.config import get_settings # noqa: PLC0415 get_settings.cache_clear() yield diff --git a/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md b/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md index 09323a8..3cb442c 100644 --- a/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md +++ b/docs/superpowers/plans/2026-10-03-slice-0-skeleton.md @@ -32,7 +32,7 @@ ### Task 4: [2.1] `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt -- [ ] `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest, pytest-asyncio, pytest-env, httpx, jsonschema; `Makefile`: `venv` (python3.11, без `raisa_*`), `lint`, `test`, `test-integration`, `check`, `check-full`, `up`, `emulator`; проверка: `make venv && make lint` зелёные +- [x] `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest, pytest-asyncio, pytest-env, httpx, jsonschema; `Makefile`: `venv` (python3.11, без `raisa_*`), `lint`, `test`, `test-integration`, `check`, `check-full`, `up`, `emulator`; проверка: `make venv && make lint` зелёные ### Task 5: [2.2] `Dockerfile` (`python diff --git a/openspec/changes/slice-0-skeleton/tasks.md b/openspec/changes/slice-0-skeleton/tasks.md index 01e2d92..666bba3 100644 --- a/openspec/changes/slice-0-skeleton/tasks.md +++ b/openspec/changes/slice-0-skeleton/tasks.md @@ -19,7 +19,7 @@ ## 2. Инструменты -- [ ] 2.1 `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest, +- [x] 2.1 `api/pyproject.toml` (ruff, mypy), `requirements-dev.txt` = `-r requirements.txt` + ruff, mypy, pytest, pytest-asyncio, pytest-env, httpx, jsonschema; `Makefile`: `venv` (python3.11, без `raisa_*`), `lint`, `test`, `test-integration`, `check`, `check-full`, `up`, `emulator`; проверка: `make venv && make lint` зелёные - [ ] 2.2 `Dockerfile` (`python:3.11-slim`; `CMD python run_server.py`, эмулятор — через `command` в compose),