andrewbokh #6

Merged
Arxip222 merged 4 commits from andrewbokh into main 2025-12-24 10:36:03 +03:00
17 changed files with 859 additions and 789 deletions
Showing only changes of commit dfc188e179 - Show all commits

View File

@ -9,7 +9,7 @@ python-multipart==0.0.6
httpx==0.25.2
PyMuPDF==1.23.8
Pillow==10.2.0
dishka==1.7.2
dishka==0.7.0
numpy==1.26.4
sentence-transformers==2.7.0
qdrant-client==1.9.0

View File

@ -26,7 +26,7 @@ class CacheService:
"question": question,
"answer": answer
}
await self.reids_client.set_json(key, value, ttl or self.default_ttl)
await self.redis_client.set_json(key, value, ttl or self.default_ttl)
async def invalidate_collection_cache(self, collection_id: UUID):
pattern = f"rag:answer:{collection_id}:*"

View File

@ -2,12 +2,12 @@
Админ-панель - упрощенная версия через API эндпоинты
В будущем можно интегрировать полноценную админ-панель
"""
from __future__ import annotations
from fastapi import APIRouter, HTTPException
from typing import List
from fastapi import APIRouter, HTTPException, Request
from typing import List, Annotated
from uuid import UUID
from dishka.integrations.fastapi import FromDishka
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.user_schemas import UserResponse
from src.presentation.schemas.collection_schemas import CollectionResponse
from src.presentation.schemas.document_schemas import DocumentResponse
@ -21,13 +21,16 @@ router = APIRouter(prefix="/admin", tags=["admin"])
@router.get("/users", response_model=List[UserResponse])
@inject
async def admin_list_users(
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[UserUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
current_user: User = FromDishka(),
use_cases: UserUseCases = FromDishka()
limit: int = 100
):
"""Получить список всех пользователей (только для админов)"""
current_user = await get_current_user(request, user_repo)
if not current_user.is_admin():
raise HTTPException(status_code=403, detail="Требуются права администратора")
users = await use_cases.list_users(skip=skip, limit=limit)
@ -35,13 +38,16 @@ async def admin_list_users(
@router.get("/collections", response_model=List[CollectionResponse])
@inject
async def admin_list_collections(
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
limit: int = 100
):
"""Получить список всех коллекций (только для админов)"""
current_user = await get_current_user(request, user_repo)
from src.infrastructure.database.base import AsyncSessionLocal
from src.infrastructure.repositories.postgresql.collection_repository import PostgreSQLCollectionRepository
from sqlalchemy import select

View File

@ -1,13 +1,13 @@
"""
API роутеры для работы с коллекциями
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status
from fastapi import APIRouter, status, Depends, Request
from fastapi.responses import JSONResponse
from typing import List
from dishka.integrations.fastapi import FromDishka
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.collection_schemas import (
CollectionCreate,
CollectionUpdate,
@ -22,12 +22,15 @@ router = APIRouter(prefix="/collections", tags=["collections"])
@router.post("", response_model=CollectionResponse, status_code=status.HTTP_201_CREATED)
@inject
async def create_collection(
collection_data: CollectionCreate,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Создать коллекцию"""
current_user = await get_current_user(request, user_repo)
collection = await use_cases.create_collection(
name=collection_data.name,
owner_id=current_user.user_id,
@ -38,9 +41,10 @@ async def create_collection(
@router.get("/{collection_id}", response_model=CollectionResponse)
@inject
async def get_collection(
collection_id: UUID,
use_cases: CollectionUseCases = FromDishka()
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Получить коллекцию по ID"""
collection = await use_cases.get_collection(collection_id)
@ -48,13 +52,16 @@ async def get_collection(
@router.put("/{collection_id}", response_model=CollectionResponse)
@inject
async def update_collection(
collection_id: UUID,
collection_data: CollectionUpdate,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Обновить коллекцию"""
current_user = await get_current_user(request, user_repo)
collection = await use_cases.update_collection(
collection_id=collection_id,
user_id=current_user.user_id,
@ -66,24 +73,30 @@ async def update_collection(
@router.delete("/{collection_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def delete_collection(
collection_id: UUID,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Удалить коллекцию"""
current_user = await get_current_user(request, user_repo)
await use_cases.delete_collection(collection_id, current_user.user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
@router.get("", response_model=List[CollectionResponse])
@inject
async def list_collections(
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
limit: int = 100
):
"""Получить список коллекций, доступных пользователю"""
current_user = await get_current_user(request, user_repo)
collections = await use_cases.list_user_collections(
user_id=current_user.user_id,
skip=skip,
@ -93,13 +106,16 @@ async def list_collections(
@router.post("/{collection_id}/access", response_model=CollectionAccessResponse, status_code=status.HTTP_201_CREATED)
@inject
async def grant_access(
collection_id: UUID,
access_data: CollectionAccessGrant,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Предоставить доступ пользователю к коллекции"""
current_user = await get_current_user(request, user_repo)
access = await use_cases.grant_access(
collection_id=collection_id,
user_id=access_data.user_id,
@ -109,13 +125,16 @@ async def grant_access(
@router.delete("/{collection_id}/access/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def revoke_access(
collection_id: UUID,
user_id: UUID,
current_user: User = FromDishka(),
use_cases: CollectionUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[CollectionUseCases, FromDishka()]
):
"""Отозвать доступ пользователя к коллекции"""
current_user = await get_current_user(request, user_repo)
await use_cases.revoke_access(collection_id, user_id, current_user.user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)

View File

@ -1,13 +1,13 @@
"""
API роутеры для работы с беседами
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status
from fastapi import APIRouter, status, Depends, Request
from fastapi.responses import JSONResponse
from typing import List
from dishka.integrations.fastapi import FromDishka
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.conversation_schemas import (
ConversationCreate,
ConversationResponse
@ -19,12 +19,15 @@ router = APIRouter(prefix="/conversations", tags=["conversations"])
@router.post("", response_model=ConversationResponse, status_code=status.HTTP_201_CREATED)
@inject
async def create_conversation(
conversation_data: ConversationCreate,
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[ConversationUseCases, FromDishka()]
):
"""Создать беседу"""
current_user = await get_current_user(request, user_repo)
conversation = await use_cases.create_conversation(
user_id=current_user.user_id,
collection_id=conversation_data.collection_id
@ -33,35 +36,44 @@ async def create_conversation(
@router.get("/{conversation_id}", response_model=ConversationResponse)
@inject
async def get_conversation(
conversation_id: UUID,
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[ConversationUseCases, FromDishka()]
):
"""Получить беседу по ID"""
current_user = await get_current_user(request, user_repo)
conversation = await use_cases.get_conversation(conversation_id, current_user.user_id)
return ConversationResponse.from_entity(conversation)
@router.delete("/{conversation_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def delete_conversation(
conversation_id: UUID,
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[ConversationUseCases, FromDishka()]
):
"""Удалить беседу"""
current_user = await get_current_user(request, user_repo)
await use_cases.delete_conversation(conversation_id, current_user.user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
@router.get("", response_model=List[ConversationResponse])
@inject
async def list_conversations(
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[ConversationUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
current_user: User = FromDishka(),
use_cases: ConversationUseCases = FromDishka()
limit: int = 100
):
"""Получить список бесед пользователя"""
current_user = await get_current_user(request, user_repo)
conversations = await use_cases.list_user_conversations(
user_id=current_user.user_id,
skip=skip,

View File

@ -1,13 +1,13 @@
"""
API роутеры для работы с документами
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status, UploadFile, File
from fastapi import APIRouter, status, UploadFile, File, Depends, Request
from fastapi.responses import JSONResponse
from typing import List
from dishka.integrations.fastapi import FromDishka
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.document_schemas import (
DocumentCreate,
DocumentUpdate,
@ -20,12 +20,15 @@ router = APIRouter(prefix="/documents", tags=["documents"])
@router.post("", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
@inject
async def create_document(
document_data: DocumentCreate,
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[DocumentUseCases, FromDishka()]
):
"""Создать документ"""
current_user = await get_current_user(request, user_repo)
document = await use_cases.create_document(
collection_id=document_data.collection_id,
title=document_data.title,
@ -36,13 +39,16 @@ async def create_document(
@router.post("/upload", response_model=DocumentResponse, status_code=status.HTTP_201_CREATED)
@inject
async def upload_document(
collection_id: UUID,
file: UploadFile = File(...),
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[DocumentUseCases, FromDishka()],
file: UploadFile = File(...)
):
"""Загрузить и распарсить PDF документ или изображение"""
current_user = await get_current_user(request, user_repo)
if not file.filename:
raise JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
@ -68,9 +74,10 @@ async def upload_document(
@router.get("/{document_id}", response_model=DocumentResponse)
@inject
async def get_document(
document_id: UUID,
use_cases: DocumentUseCases = FromDishka()
use_cases: Annotated[DocumentUseCases, FromDishka()]
):
"""Получить документ по ID"""
document = await use_cases.get_document(document_id)
@ -78,13 +85,16 @@ async def get_document(
@router.put("/{document_id}", response_model=DocumentResponse)
@inject
async def update_document(
document_id: UUID,
document_data: DocumentUpdate,
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[DocumentUseCases, FromDishka()]
):
"""Обновить документ"""
current_user = await get_current_user(request, user_repo)
document = await use_cases.update_document(
document_id=document_id,
user_id=current_user.user_id,
@ -96,22 +106,26 @@ async def update_document(
@router.delete("/{document_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def delete_document(
document_id: UUID,
current_user: User = FromDishka(),
use_cases: DocumentUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[DocumentUseCases, FromDishka()]
):
"""Удалить документ"""
current_user = await get_current_user(request, user_repo)
await use_cases.delete_document(document_id, current_user.user_id)
return JSONResponse(status_code=status.HTTP_204_NO_CONTENT, content=None)
@router.get("/collection/{collection_id}", response_model=List[DocumentResponse])
@inject
async def list_collection_documents(
collection_id: UUID,
use_cases: Annotated[DocumentUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
use_cases: DocumentUseCases = FromDishka()
limit: int = 100
):
"""Получить документы коллекции"""
documents = await use_cases.list_collection_documents(

View File

@ -1,13 +1,13 @@
"""
API роутеры для работы с сообщениями
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status
from fastapi import APIRouter, status, Depends, Request
from fastapi.responses import JSONResponse
from typing import List
from dishka.integrations.fastapi import FromDishka
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.message_schemas import (
MessageCreate,
MessageUpdate,
@ -20,12 +20,15 @@ router = APIRouter(prefix="/messages", tags=["messages"])
@router.post("", response_model=MessageResponse, status_code=status.HTTP_201_CREATED)
@inject
async def create_message(
message_data: MessageCreate,
current_user: User = FromDishka(),
use_cases: MessageUseCases = FromDishka()
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[MessageUseCases, FromDishka()]
):
"""Создать сообщение"""
current_user = await get_current_user(request, user_repo)
message = await use_cases.create_message(
conversation_id=message_data.conversation_id,
content=message_data.content,
@ -37,9 +40,10 @@ async def create_message(
@router.get("/{message_id}", response_model=MessageResponse)
@inject
async def get_message(
message_id: UUID,
use_cases: MessageUseCases = FromDishka()
use_cases: Annotated[MessageUseCases, FromDishka()]
):
"""Получить сообщение по ID"""
message = await use_cases.get_message(message_id)
@ -47,10 +51,11 @@ async def get_message(
@router.put("/{message_id}", response_model=MessageResponse)
@inject
async def update_message(
message_id: UUID,
message_data: MessageUpdate,
use_cases: MessageUseCases = FromDishka()
use_cases: Annotated[MessageUseCases, FromDishka()]
):
"""Обновить сообщение"""
message = await use_cases.update_message(
@ -62,9 +67,10 @@ async def update_message(
@router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT)
@inject
async def delete_message(
message_id: UUID,
use_cases: MessageUseCases = FromDishka()
use_cases: Annotated[MessageUseCases, FromDishka()]
):
"""Удалить сообщение"""
await use_cases.delete_message(message_id)
@ -72,14 +78,17 @@ async def delete_message(
@router.get("/conversation/{conversation_id}", response_model=List[MessageResponse])
@inject
async def list_conversation_messages(
conversation_id: UUID,
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[MessageUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
current_user: User = FromDishka(),
use_cases: MessageUseCases = FromDishka()
limit: int = 100
):
"""Получить сообщения беседы"""
current_user = await get_current_user(request, user_repo)
messages = await use_cases.list_conversation_messages(
conversation_id=conversation_id,
user_id=current_user.user_id,

View File

@ -1,10 +1,11 @@
"""
API для RAG: индексация документов и ответы на вопросы
"""
from __future__ import annotations
from fastapi import APIRouter, status
from dishka.integrations.fastapi import FromDishka
from fastapi import APIRouter, status, Request
from typing import Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.rag_schemas import (
QuestionRequest,
RAGAnswer,
@ -19,23 +20,29 @@ router = APIRouter(prefix="/rag", tags=["rag"])
@router.post("/index", response_model=IndexDocumentResponse, status_code=status.HTTP_200_OK)
@inject
async def index_document(
body: IndexDocumentRequest,
use_cases: RAGUseCases = FromDishka(),
current_user: User = FromDishka(),
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[RAGUseCases, FromDishka()],
):
"""Индексирование идет через чанкирование, далее эмбеддинг и загрузка в векторную бд"""
current_user = await get_current_user(request, user_repo)
result = await use_cases.index_document(body.document_id)
return IndexDocumentResponse(**result)
@router.post("/question", response_model=RAGAnswer, status_code=status.HTTP_200_OK)
@inject
async def ask_question(
body: QuestionRequest,
use_cases: RAGUseCases = FromDishka(),
current_user: User = FromDishka(),
request: Request,
user_repo: Annotated[IUserRepository, FromDishka()],
use_cases: Annotated[RAGUseCases, FromDishka()],
):
"""Отвечает на вопрос, используя RAG в рамках беседы"""
current_user = await get_current_user(request, user_repo)
result = await use_cases.ask_question(
conversation_id=body.conversation_id,
user_id=current_user.user_id,

View File

@ -1,13 +1,13 @@
"""
API роутеры для работы с пользователями
"""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, status, Depends
from fastapi import APIRouter, status, Depends, Request
from fastapi.responses import JSONResponse
from typing import List, Annotated
from dishka.integrations.fastapi import FromDishka, inject
from src.domain.repositories.user_repository import IUserRepository
from src.presentation.middleware.auth_middleware import get_current_user
from src.presentation.schemas.user_schemas import UserCreate, UserUpdate, UserResponse
from src.application.use_cases.user_use_cases import UserUseCases
from src.domain.entities.user import User
@ -19,8 +19,8 @@ router = APIRouter(prefix="/users", tags=["users"])
@inject
async def create_user(
user_data: UserCreate,
use_cases: UserUseCases = FromDishka()
) -> UserResponse:
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Создать пользователя"""
user = await use_cases.create_user(
telegram_id=user_data.telegram_id,
@ -32,9 +32,11 @@ async def create_user(
@router.get("/me", response_model=UserResponse)
@inject
async def get_current_user_info(
current_user: User = FromDishka()
request,
user_repo: Annotated[IUserRepository, FromDishka()]
):
"""Получить информацию о текущем пользователе"""
current_user = await get_current_user(request, user_repo)
return UserResponse.from_entity(current_user)
@ -42,7 +44,7 @@ async def get_current_user_info(
@inject
async def get_user_by_telegram_id(
telegram_id: str,
use_cases: UserUseCases = FromDishka()
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Получить пользователя по Telegram ID"""
user = await use_cases.get_user_by_telegram_id(telegram_id)
@ -56,7 +58,7 @@ async def get_user_by_telegram_id(
@inject
async def increment_questions(
telegram_id: str,
use_cases: UserUseCases = FromDishka()
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Увеличить счетчик использованных вопросов"""
user = await use_cases.increment_questions_used(telegram_id)
@ -66,9 +68,10 @@ async def increment_questions(
@router.post("/telegram/{telegram_id}/activate-premium", response_model=UserResponse)
@inject
async def activate_premium(
use_cases: Annotated[UserUseCases, FromDishka()],
telegram_id: str,
days: int = 30,
use_cases: UserUseCases = FromDishka()
):
"""Активировать premium статус"""
user = await use_cases.activate_premium(telegram_id, days=days)
@ -79,7 +82,7 @@ async def activate_premium(
@inject
async def get_user(
user_id: UUID,
use_cases: UserUseCases = FromDishka()
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Получить пользователя по ID"""
user = await use_cases.get_user(user_id)
@ -91,7 +94,7 @@ async def get_user(
async def update_user(
user_id: UUID,
user_data: UserUpdate,
use_cases: UserUseCases = FromDishka()
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Обновить пользователя"""
user = await use_cases.update_user(
@ -106,7 +109,7 @@ async def update_user(
@inject
async def delete_user(
user_id: UUID,
use_cases: UserUseCases = FromDishka()
use_cases: Annotated[UserUseCases, FromDishka()]
):
"""Удалить пользователя"""
await use_cases.delete_user(user_id)
@ -116,9 +119,9 @@ async def delete_user(
@router.get("", response_model=List[UserResponse])
@inject
async def list_users(
use_cases: Annotated[UserUseCases, FromDishka()],
skip: int = 0,
limit: int = 100,
use_cases: UserUseCases = FromDishka()
limit: int = 100
):
"""Получить список пользователей"""
users = await use_cases.list_users(skip=skip, limit=limit)

View File

@ -1,7 +1,6 @@
from __future__ import annotations
import sys
import os
import asyncio
from pathlib import Path
backend_dir = Path(__file__).parent.parent.parent
@ -24,15 +23,18 @@ from src.infrastructure.database.base import engine, Base
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Управление жизненным циклом приложения"""
container = create_container()
setup_dishka(container, app)
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
except Exception as e:
print(f"Примечание при создании таблиц: {e}")
yield
await container.close()
# Cleanup container if needed
if hasattr(app.state, 'container') and hasattr(app.state.container, 'close'):
if asyncio.iscoroutinefunction(app.state.container.close):
await app.state.container.close()
else:
app.state.container.close()
await engine.dispose()
@ -43,6 +45,11 @@ app = FastAPI(
lifespan=lifespan
)
# Настройка Dishka ДО добавления middleware
container = create_container()
setup_dishka(container, app)
app.state.container = container
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,

View File

@ -7,18 +7,19 @@ from typing import Optional
class Settings(BaseSettings):
"""Настройки (загружаются из .env автоматически)"""
POSTGRES_HOST: str
POSTGRES_PORT: int
POSTGRES_USER: str
POSTGRES_PASSWORD: str
POSTGRES_DB: str
POSTGRES_HOST: str = "localhost"
POSTGRES_PORT: int = 5432
POSTGRES_USER: str = "postgres"
POSTGRES_PASSWORD: str = "postgres"
POSTGRES_DB: str = "lawyer_ai"
QDRANT_HOST: str
QDRANT_PORT: int
QDRANT_HOST: str = "localhost"
QDRANT_PORT: int = 6333
REDIS_HOST: str
REDIS_PORT: int
REDIS_HOST: str = "localhost"
REDIS_PORT: int = 6379
TELEGRAM_BOT_TOKEN: Optional[str] = None
YANDEX_OCR_API_KEY: Optional[str] = None
@ -29,11 +30,12 @@ class Settings(BaseSettings):
APP_NAME: str = "ИИ-юрист"
DEBUG: bool = False
SECRET_KEY: str
SECRET_KEY: str = "your-secret-key-change-in-production"
CORS_ORIGINS: list[str] = ["*"]
@property
def database_url(self) -> str:
"""Вычисляемый URL подключения"""
return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"
class Config:

View File

@ -1,7 +1,7 @@
from dishka import Container, Provider, Scope, provide
from dishka import Container, Provider, Scope, provide, make_async_container
from fastapi import Request
from sqlalchemy.ext.asyncio import AsyncSession
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from src.infrastructure.database.base import AsyncSessionLocal
from src.infrastructure.repositories.postgresql.user_repository import PostgreSQLUserRepository
@ -39,7 +39,8 @@ from src.application.use_cases.rag_use_cases import RAGUseCases
class DatabaseProvider(Provider):
@provide(scope=Scope.REQUEST)
async def get_db(self) -> AsyncIterator[AsyncSession]:
@asynccontextmanager
async def get_db(self) -> AsyncSession:
async with AsyncSessionLocal() as session:
try:
yield session
@ -94,8 +95,6 @@ class ServiceProvider(Provider):
def get_parser_service(self, ocr_service: YandexOCRService) -> DocumentParserService:
return DocumentParserService(ocr_service)
class VectorServiceProvider(Provider):
@provide(scope=Scope.APP)
def get_qdrant_client(self) -> QdrantClient:
return QdrantClient(host=settings.QDRANT_HOST, port=settings.QDRANT_PORT)
@ -133,12 +132,6 @@ class VectorServiceProvider(Provider):
splitter=text_splitter,
)
class AuthProvider(Provider):
@provide(scope=Scope.REQUEST)
async def get_current_user(self, request: Request, user_repo: IUserRepository) -> User:
from src.presentation.middleware.auth_middleware import get_current_user
return await get_current_user(request, user_repo)
class UseCaseProvider(Provider):
@provide(scope=Scope.REQUEST)
@ -196,12 +189,10 @@ class UseCaseProvider(Provider):
def create_container() -> Container:
container = Container()
container.add_provider(DatabaseProvider())
container.add_provider(RepositoryProvider())
container.add_provider(ServiceProvider())
container.add_provider(AuthProvider())
container.add_provider(UseCaseProvider())
container.add_provider(VectorServiceProvider())
return container
return make_async_container(
DatabaseProvider(),
RepositoryProvider(),
ServiceProvider(),
UseCaseProvider()
)