diff --git a/api/alembic/env.py b/api/alembic/env.py index b89fbff..2a949b4 100644 --- a/api/alembic/env.py +++ b/api/alembic/env.py @@ -1,12 +1,13 @@ import asyncio from logging.config import fileConfig -from sqlalchemy import engine_from_config -from sqlalchemy import pool +from sqlalchemy import engine_from_config, pool from sqlalchemy.ext.asyncio import AsyncEngine + from alembic import context from src.core.config import settings from src.models import Base # noqa: F401 — модели регистрируются в src/models/__init__.py + # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -28,9 +29,10 @@ target_metadata = Base.metadata # my_important_option = config.get_main_option("my_important_option") # ... etc. + def include_name(name, type_, parent_names): - if type_ == 'schema': - return name == 'dkb' + if type_ == "schema": + return name == "dkb" return True @@ -41,12 +43,13 @@ def do_run_migrations(connection) -> None: compare_type=True, version_table_schema="dkb", include_schemas=True, - include_name=include_name + include_name=include_name, ) with context.begin_transaction(): context.run_migrations() + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -67,7 +70,7 @@ def run_migrations_offline() -> None: dialect_opts={"paramstyle": "named"}, version_table_schema="dkb", include_schemas=True, - include_name=include_name + include_name=include_name, ) with context.begin_transaction(): @@ -83,10 +86,7 @@ async def run_migrations_online() -> None: """ connectable = AsyncEngine( engine_from_config( - config.get_section(config.config_ini_section), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - future=True + config.get_section(config.config_ini_section), prefix="sqlalchemy.", poolclass=pool.NullPool, future=True ) ) diff --git a/api/pyproject.toml b/api/pyproject.toml index 071036f..d9ed6ed 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -36,5 +36,11 @@ extra_checks = true # Сторонние пакеты без типов (или недоступные вне банка) [[tool.mypy.overrides]] -module = ["smbclient.*", "nkeys.*", "miniopy_async.*", "nats.*", "raisa_fastapi_protected_api", "raisa_fastapi_protected_api.*", "pydbml.*"] +module = ["smbclient.*", "nkeys.*", "nats.*", "raisa_fastapi_protected_api", "raisa_fastapi_protected_api.*", "pydbml.*"] +ignore_missing_imports = true + +# miniopy_async: пакет типизирован с ошибкой (Minio не виден mypy при реэкспорте) — типы не подтягиваем, Minio = Any +[[tool.mypy.overrides]] +module = ["miniopy_async", "miniopy_async.*"] +follow_imports = "skip" ignore_missing_imports = true 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 845e7c0..529b7d3 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -5,10 +5,12 @@ middleware (gzip, proxy headers, CORS, РАИСА при DEBUG=False), healthche Что добавляет Срез 0: роутеры в src/api/router.py, outbox-паблишер и consumer recognition.result (docs/context/03-architecture.md), зависимость require(permission). """ + import base64 import os from contextlib import asynccontextmanager -from datetime import datetime, timezone +from datetime import UTC, datetime +from http import HTTPStatus import nkeys import smbclient @@ -30,7 +32,7 @@ from src.core.exception_handlers import register_exception_handlers settings = get_settings() VERSION = settings.VERSION -UP_TIME = datetime.now(timezone.utc) +UP_TIME = datetime.now(UTC) # --- NATS: аутентификация по nkeys/JWT --------------------------------------------------------- @@ -51,7 +53,7 @@ def sig_cb(nonce): protected_middleware_available = False if not settings.DEBUG: try: - from raisa_fastapi_protected_api import ( # pyright: ignore[reportMissingImports] + from raisa_fastapi_protected_api import ( # noqa: F401 # pyright: ignore[reportMissingImports] AuthorizationMiddleware, OpenEndpoint, ProtectedOAuthSettings, @@ -177,7 +179,7 @@ class SPAStaticFiles(StaticFiles): try: return await super().get_response(path, scope) except (HTTPException, StarletteHTTPException) as ex: - if ex.status_code == 404: + if ex.status_code == HTTPStatus.NOT_FOUND: if "images" in path: return await super().get_response(sep_static(path, "images"), scope) if "static" in path: 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 d57c41f..f2dd1cf 100644 --- a/api/src/core/config.py +++ b/api/src/core/config.py @@ -1,42 +1,48 @@ -from pydantic_settings import BaseSettings, SettingsConfigDict +from pydantic_settings import BaseSettings class DBSettings(BaseSettings): DATABASE_URL: str + class MinioSettings(BaseSettings): MINIO_URL: str MINIO_ADMIN_LOGIN: str MINIO_ADMIN_PASSWORD: str MINIO_BUCKET: str + class NATSSettings(BaseSettings): NATS_URL: str NATS_SEED: str NATS_JWT: str + class SMBSettings(BaseSettings): SMB_HOST: str SMB_PATH: str SMB_LOGIN: str SMB_PASSWORD: str + class OpenBAOSettings(BaseSettings): DATABASE: DBSettings MINIO: MinioSettings NATS: NATSSettings SMB: SMBSettings + class ServerSettings(BaseSettings): HOST: str = "0.0.0.0" PORT: int = 8000 + class Settings(BaseSettings): OPENBAO: OpenBAOSettings DEBUG: bool = False server: ServerSettings = ServerSettings() - ROOT_PATH: str = '/' - VERSION: str = '1.0.0' + ROOT_PATH: str = "/" + VERSION: str = "1.0.0" class Config: extra = "ignore" @@ -44,7 +50,9 @@ class Settings(BaseSettings): env_nested_delimiter = "__" nested_model_default_partial_update = False + def get_settings() -> Settings: return Settings() -settings = get_settings() \ No newline at end of file + +settings = get_settings() diff --git a/api/src/core/logger.py b/api/src/core/logger.py index 2f99033..e0ad71d 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", ) @@ -74,7 +85,7 @@ def setup_logger( class InterceptHandler(logging.Handler): def emit(self, record: logging.LogRecord) -> None: try: - lvl = logger.level(record.levelname).name + lvl: str | int = logger.level(record.levelname).name except ValueError: lvl = record.levelno 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/src/db/__init__.py b/api/src/db/__init__.py index 49ce0c1..ddec262 100644 --- a/api/src/db/__init__.py +++ b/api/src/db/__init__.py @@ -1 +1,3 @@ -from .db import get_async_session \ No newline at end of file +from .db import get_async_session + +__all__ = ["get_async_session"] diff --git a/api/src/db/db.py b/api/src/db/db.py index be73f6b..44bf885 100644 --- a/api/src/db/db.py +++ b/api/src/db/db.py @@ -1,11 +1,11 @@ -from contextlib import asynccontextmanager +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine -from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession -from fastapi import Depends # from fastapi_users.db import SQLAlchemyUserDatabase, SQLAlchemyBaseUserTableUUID from src.core.config import settings -engine = create_async_engine(settings.OPENBAO.DATABASE.DATABASE_URL, connect_args={"server_settings": {"search_path": 'dkb'}}) +engine = create_async_engine( + settings.OPENBAO.DATABASE.DATABASE_URL, connect_args={"server_settings": {"search_path": "dkb"}} +) async_session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) @@ -13,4 +13,3 @@ async_session_maker = async_sessionmaker(engine, class_=AsyncSession, expire_on_ async def get_async_session(): async with async_session_maker() as session: yield session - diff --git a/api/src/models/__init__.py b/api/src/models/__init__.py index 36fbcbd..86139b4 100644 --- a/api/src/models/__init__.py +++ b/api/src/models/__init__.py @@ -1,4 +1,5 @@ """ORM-модели. Источник истины — docs/model/data-model-v5.13.dbml; заполняется в Срезе 0.""" + from .base import Base, UUIDPrimaryMixin __all__ = ["Base", "UUIDPrimaryMixin"]