Showing
27 changed files
with
272 additions
and
276 deletions
Dockerfile
0 → 100644
api/http_config.py
0 → 100644
| 1 | +from fastapi.middleware.cors import CORSMiddleware | ||
| 2 | +from starlette.responses import JSONResponse | ||
| 3 | +from api.http_log import LoggingMiddleware | ||
| 4 | +from fastapi import FastAPI | ||
| 5 | +from api.router import api_router | ||
| 6 | +from config import settings | ||
| 7 | + | ||
| 8 | + | ||
| 9 | +def config_fastapi(fastapi_app: FastAPI): | ||
| 10 | + # 配置请求/响应日志中间件(需在CORS之前添加) | ||
| 11 | + fastapi_app.add_middleware(LoggingMiddleware) | ||
| 12 | + | ||
| 13 | + # 配置CORS | ||
| 14 | + fastapi_app.add_middleware( | ||
| 15 | + CORSMiddleware, | ||
| 16 | + allow_origins=["*"], | ||
| 17 | + allow_credentials=True, | ||
| 18 | + allow_methods=["*"], | ||
| 19 | + allow_headers=["*"], | ||
| 20 | + ) | ||
| 21 | + | ||
| 22 | + # 注册路由 | ||
| 23 | + fastapi_app.include_router(api_router) | ||
| 24 | + | ||
| 25 | + @fastapi_app.exception_handler(Exception) | ||
| 26 | + async def generic_exception_handler(request, exc: Exception): | ||
| 27 | + # 返回自定义的错误响应 | ||
| 28 | + error_detail = str(exc) | ||
| 29 | + return JSONResponse( | ||
| 30 | + status_code=200, | ||
| 31 | + content={"code": "error", "message": error_detail}, | ||
| 32 | + ) | ||
| 33 | + | ||
| 34 | + @fastapi_app.get("/", tags=["根路径"]) | ||
| 35 | + async def root(): | ||
| 36 | + """根路径.""" | ||
| 37 | + return { | ||
| 38 | + "name": settings.app_name, | ||
| 39 | + "version": settings.app_version, | ||
| 40 | + "docs": "/docs", | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + return fastapi_app |
api/http_log.py
0 → 100644
| 1 | +import json | ||
| 2 | +import time | ||
| 3 | +import logging | ||
| 4 | +logger = logging.getLogger(__name__) | ||
| 5 | +from fastapi import FastAPI, Request | ||
| 6 | +from starlette.middleware.base import BaseHTTPMiddleware | ||
| 7 | +from starlette.responses import StreamingResponse | ||
| 8 | + | ||
| 9 | +class LoggingMiddleware(BaseHTTPMiddleware): | ||
| 10 | + """HTTP请求/响应日志中间件.""" | ||
| 11 | + | ||
| 12 | + async def dispatch(self, request: Request, call_next): | ||
| 13 | + # 记录请求开始时间 | ||
| 14 | + start_time = time.time() | ||
| 15 | + | ||
| 16 | + # 获取请求信息 | ||
| 17 | + method = request.method | ||
| 18 | + url = str(request.url) | ||
| 19 | + client_host = request.client.host if request.client else "unknown" | ||
| 20 | + | ||
| 21 | + # 读取请求体 | ||
| 22 | + request_body = None | ||
| 23 | + if method in ("POST", "PUT", "PATCH"): | ||
| 24 | + try: | ||
| 25 | + body = await request.body() | ||
| 26 | + if body: | ||
| 27 | + request_body = body.decode("utf-8", errors="replace") | ||
| 28 | + # 尝试格式化为JSON | ||
| 29 | + try: | ||
| 30 | + request_body = json.loads(request_body) | ||
| 31 | + except json.JSONDecodeError: | ||
| 32 | + pass # 保持原始字符串 | ||
| 33 | + except Exception: | ||
| 34 | + pass | ||
| 35 | + | ||
| 36 | + # 记录请求日志 | ||
| 37 | + logger.info(f"→ HTTP Request | {method} {url} | Client: {client_host}") | ||
| 38 | + if request_body: | ||
| 39 | + logger.info(f" Request Body: {request_body}") | ||
| 40 | + | ||
| 41 | + # 处理请求 | ||
| 42 | + response = await call_next(request) | ||
| 43 | + | ||
| 44 | + # 计算处理时间 | ||
| 45 | + process_time = time.time() - start_time | ||
| 46 | + | ||
| 47 | + # 读取响应体(仅处理非流式响应) | ||
| 48 | + response_body = None | ||
| 49 | + if not isinstance(response, StreamingResponse): | ||
| 50 | + try: | ||
| 51 | + response_body_bytes = b"" | ||
| 52 | + async for chunk in response.body_iterator: | ||
| 53 | + response_body_bytes += chunk | ||
| 54 | + | ||
| 55 | + # 重新构建响应 | ||
| 56 | + response_body = response_body_bytes.decode("utf-8", errors="replace") | ||
| 57 | + # 尝试格式化为JSON | ||
| 58 | + try: | ||
| 59 | + response_body = json.loads(response_body) | ||
| 60 | + except json.JSONDecodeError: | ||
| 61 | + pass # 保持原始字符串 | ||
| 62 | + | ||
| 63 | + # 重建响应对象 | ||
| 64 | + response = StreamingResponse( | ||
| 65 | + iter([response_body_bytes]), | ||
| 66 | + status_code=response.status_code, | ||
| 67 | + headers=dict(response.headers), | ||
| 68 | + media_type=response.media_type, | ||
| 69 | + ) | ||
| 70 | + except Exception: | ||
| 71 | + pass | ||
| 72 | + | ||
| 73 | + # 记录响应日志 | ||
| 74 | + status_code = response.status_code | ||
| 75 | + logger.info(f"← HTTP Response | {method} {url} | Status: {status_code} | Time: {process_time:.3f}s") | ||
| 76 | + if response_body: | ||
| 77 | + logger.info(f" Response Body: {response_body}") | ||
| 78 | + | ||
| 79 | + return response |
| @@ -3,9 +3,7 @@ | @@ -3,9 +3,7 @@ | ||
| 3 | from fastapi import APIRouter | 3 | from fastapi import APIRouter |
| 4 | 4 | ||
| 5 | # 创建主路由 | 5 | # 创建主路由 |
| 6 | -api_router = APIRouter(prefix="/api/v1") | ||
| 7 | - | ||
| 8 | -# 注册各场景路由 | 6 | +api_router = APIRouter(prefix="/aigc-embedding/api") |
| 9 | 7 | ||
| 10 | def register_router(router: APIRouter, prefix: str): | 8 | def register_router(router: APIRouter, prefix: str): |
| 11 | api_router.include_router(router, prefix=prefix) | 9 | api_router.include_router(router, prefix=prefix) |
app/core/config.py
deleted
100644 → 0
app/core/exceptions.py
deleted
100644 → 0
| 1 | -"""自定义异常模块.""" | ||
| 2 | - | ||
| 3 | - | ||
| 4 | -class EmbeddingServiceError(Exception): | ||
| 5 | - """Embedding服务基础异常.""" | ||
| 6 | - | ||
| 7 | - def __init__(self, message: str, code: str = "EMBEDDING_ERROR"): | ||
| 8 | - self.message = message | ||
| 9 | - self.code = code | ||
| 10 | - super().__init__(self.message) | ||
| 11 | - | ||
| 12 | - | ||
| 13 | -class EmbeddingAPIError(EmbeddingServiceError): | ||
| 14 | - """Embedding API调用异常.""" | ||
| 15 | - | ||
| 16 | - def __init__(self, message: str, status_code: int = 500): | ||
| 17 | - super().__init__(message, code="EMBEDDING_API_ERROR") | ||
| 18 | - self.status_code = status_code | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -class DatabaseError(EmbeddingServiceError): | ||
| 22 | - """数据库操作异常.""" | ||
| 23 | - | ||
| 24 | - def __init__(self, message: str): | ||
| 25 | - super().__init__(message, code="DATABASE_ERROR") | ||
| 26 | - | ||
| 27 | - | ||
| 28 | -class NotFoundError(EmbeddingServiceError): | ||
| 29 | - """资源不存在异常.""" | ||
| 30 | - | ||
| 31 | - def __init__(self, message: str): | ||
| 32 | - super().__init__(message, code="NOT_FOUND") | ||
| 33 | - | ||
| 34 | - | ||
| 35 | -class ValidationError(EmbeddingServiceError): | ||
| 36 | - """参数校验异常.""" | ||
| 37 | - | ||
| 38 | - def __init__(self, message: str): | ||
| 39 | - super().__init__(message, code="VALIDATION_ERROR") |
app/main.py
deleted
100644 → 0
| 1 | -"""FastAPI应用入口.""" | ||
| 2 | - | ||
| 3 | -from contextlib import asynccontextmanager | ||
| 4 | -from typing import AsyncGenerator | ||
| 5 | - | ||
| 6 | -from fastapi import FastAPI | ||
| 7 | -from fastapi.middleware.cors import CORSMiddleware | ||
| 8 | - | ||
| 9 | -from app.api.router import api_router | ||
| 10 | -from app.core.exceptions import EmbeddingServiceError | ||
| 11 | -from app.core.config import settings | ||
| 12 | -from app.db import connect as db_client_connect, disconnect as db_client_disconnect | ||
| 13 | - | ||
| 14 | -@asynccontextmanager | ||
| 15 | -async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: | ||
| 16 | - """应用生命周期管理.""" | ||
| 17 | - # 启动时建立数据库连接 | ||
| 18 | - try: | ||
| 19 | - await db_client_connect() | ||
| 20 | - db_type = settings.get("db_type", "es") | ||
| 21 | - print(f"✓ {db_type.upper()} database connected") | ||
| 22 | - except Exception as e: | ||
| 23 | - print(f"✗ Failed to connect to database: {e}") | ||
| 24 | - raise | ||
| 25 | - | ||
| 26 | - yield | ||
| 27 | - | ||
| 28 | - # 关闭时断开数据库连接 | ||
| 29 | - try: | ||
| 30 | - await db_client_disconnect() | ||
| 31 | - db_type = settings.get("db_type", "es") | ||
| 32 | - print(f"✓ {db_type.upper()} database disconnected") | ||
| 33 | - except Exception as e: | ||
| 34 | - print(f"✗ Error disconnecting from database: {e}") | ||
| 35 | - | ||
| 36 | - | ||
| 37 | -def create_app() -> FastAPI: | ||
| 38 | - """创建FastAPI应用.""" | ||
| 39 | - | ||
| 40 | - app = FastAPI( | ||
| 41 | - title=settings.app_name, | ||
| 42 | - version=settings.app_version, | ||
| 43 | - description="基于向量的CRUD服务,支持多种Embedding场景", | ||
| 44 | - lifespan=lifespan, | ||
| 45 | - docs_url="/docs", | ||
| 46 | - redoc_url="/redoc", | ||
| 47 | - ) | ||
| 48 | - | ||
| 49 | - # 配置CORS | ||
| 50 | - app.add_middleware( | ||
| 51 | - CORSMiddleware, | ||
| 52 | - allow_origins=["*"], | ||
| 53 | - allow_credentials=True, | ||
| 54 | - allow_methods=["*"], | ||
| 55 | - allow_headers=["*"], | ||
| 56 | - ) | ||
| 57 | - | ||
| 58 | - # 注册路由 | ||
| 59 | - app.include_router(api_router) | ||
| 60 | - | ||
| 61 | - # 异常处理 | ||
| 62 | - @app.exception_handler(EmbeddingServiceError) | ||
| 63 | - async def embedding_service_exception_handler(request, exc): | ||
| 64 | - from fastapi.responses import JSONResponse | ||
| 65 | - | ||
| 66 | - return JSONResponse( | ||
| 67 | - status_code=500, | ||
| 68 | - content={"code": exc.code, "message": exc.message}, | ||
| 69 | - ) | ||
| 70 | - | ||
| 71 | - # @app.get("/health", tags=["健康检查"]) | ||
| 72 | - # async def health_check(): | ||
| 73 | - # """健康检查端点.""" | ||
| 74 | - # db_healthy = await db_client.health_check() | ||
| 75 | - # db_type = settings.get("db_type", "es") | ||
| 76 | - # return { | ||
| 77 | - # "status": "healthy" if db_healthy else "unhealthy", | ||
| 78 | - # "database": { | ||
| 79 | - # "type": db_type, | ||
| 80 | - # "status": "connected" if db_healthy else "disconnected", | ||
| 81 | - # }, | ||
| 82 | - # } | ||
| 83 | - | ||
| 84 | - @app.get("/", tags=["根路径"]) | ||
| 85 | - async def root(): | ||
| 86 | - """根路径.""" | ||
| 87 | - return { | ||
| 88 | - "name": settings.app_name, | ||
| 89 | - "version": settings.app_version, | ||
| 90 | - "docs": "/docs", | ||
| 91 | - } | ||
| 92 | - | ||
| 93 | - return app | ||
| 94 | - | ||
| 95 | - | ||
| 96 | -# 创建应用实例 | ||
| 97 | -app = create_app() | ||
| 98 | - | ||
| 99 | -if __name__ == "__main__": | ||
| 100 | - import uvicorn | ||
| 101 | - | ||
| 102 | - uvicorn.run( | ||
| 103 | - "app.main:app", | ||
| 104 | - host='0.0.0.0', | ||
| 105 | - port=settings.port, | ||
| 106 | - reload=settings.debug, | ||
| 107 | - ) |
app/modules/__init__.py
deleted
100644 → 0
app/modules/face/__init__.py
deleted
100644 → 0
app/modules/face/es_db.py
deleted
100644 → 0
app/modules/face/router.py
deleted
100644 → 0
app/modules/face/service.py
deleted
100644 → 0
| 1 | app_name: "Embedding Service" | 1 | app_name: "Embedding Service" |
| 2 | app_version: "1.0.0" | 2 | app_version: "1.0.0" |
| 3 | port: 8000 | 3 | port: 8000 |
| 4 | -debug: true | 4 | +debug: false |
| 5 | 5 | ||
| 6 | db_es_enable: true | 6 | db_es_enable: true |
| 7 | db_es_url: "http://localhost:9200" | 7 | db_es_url: "http://localhost:9200" |
| 8 | db_postgres_enable: false | 8 | db_postgres_enable: false |
| 9 | db_postgres_url: "postgresql://postgres:postgres@localhost:5432/postgres" | 9 | db_postgres_url: "postgresql://postgres:postgres@localhost:5432/postgres" |
| 10 | + | ||
| 11 | +service: | ||
| 12 | + face: | ||
| 13 | + db_client: "es" |
config.py
0 → 100644
| 1 | +from aabd.base.cfg_loader import load_yaml_by_file_with_env | ||
| 2 | +from aabd.base.enhance_dict import EnhanceDict, read_prefixed_env_vars | ||
| 3 | +from pathlib import Path | ||
| 4 | + | ||
| 5 | +cfg_dir = Path(__file__).parent.absolute() / 'cfg' | ||
| 6 | +settings = EnhanceDict(load_yaml_by_file_with_env((cfg_dir / 'config.yaml').as_posix())) | ||
| 7 | +settings.update(read_prefixed_env_vars('APP_')) |
| 1 | -"""数据库模块,提供统一的向量数据库抽象层. | ||
| 2 | - | ||
| 3 | -支持Elasticsearch和PostgreSQL两种后端,通过配置自动切换。 | ||
| 4 | -使用示例: | ||
| 5 | - from app.db import db_client | ||
| 6 | - | ||
| 7 | - # 连接数据库 | ||
| 8 | - await db_client.connect() | ||
| 9 | - | ||
| 10 | - # 创建集合 | ||
| 11 | - await db_client.create_collection("face_vectors", vector_dim=512) | ||
| 12 | - | ||
| 13 | - # 插入数据 | ||
| 14 | - await db_client.insert("face_vectors", "user_001", vector, {"name": "张三"}) | ||
| 15 | - | ||
| 16 | - # 向量搜索 | ||
| 17 | - results = await db_client.search("face_vectors", query_vector, top_k=10) | ||
| 18 | - | ||
| 19 | - # 断开连接 | ||
| 20 | - await db_client.disconnect() | ||
| 21 | -""" | ||
| 22 | - | ||
| 23 | -from typing import Optional | ||
| 24 | - | ||
| 25 | -from app.core.config import settings | 1 | +from config import settings |
| 26 | 2 | ||
| 27 | es_client = None | 3 | es_client = None |
| 28 | pg_client = None | 4 | pg_client = None |
| 1 | """Elasticsearch向量数据库客户端实现.""" | 1 | """Elasticsearch向量数据库客户端实现.""" |
| 2 | 2 | ||
| 3 | -from typing import Any, Dict, List, Optional | 3 | +from typing import Any, Optional |
| 4 | 4 | ||
| 5 | -from app.core.config import settings | ||
| 6 | -from app.db.base import VectorDBClient | 5 | +from config import settings |
| 6 | +from db.base import VectorDBClient | ||
| 7 | 7 | ||
| 8 | 8 | ||
| 9 | class ElasticsearchClient(VectorDBClient): | 9 | class ElasticsearchClient(VectorDBClient): |
| @@ -18,7 +18,7 @@ class ElasticsearchClient(VectorDBClient): | @@ -18,7 +18,7 @@ class ElasticsearchClient(VectorDBClient): | ||
| 18 | Args: | 18 | Args: |
| 19 | es_url: Elasticsearch连接URL,默认从配置读取 | 19 | es_url: Elasticsearch连接URL,默认从配置读取 |
| 20 | """ | 20 | """ |
| 21 | - self.es_url = es_url or settings.get("db_es_url", "http://localhost:9200") | 21 | + self.es_url = es_url |
| 22 | self._client: Optional[Any] = None | 22 | self._client: Optional[Any] = None |
| 23 | 23 | ||
| 24 | async def connect(self) -> None: | 24 | async def connect(self) -> None: |
| 1 | """PostgreSQL向量数据库客户端实现.""" | 1 | """PostgreSQL向量数据库客户端实现.""" |
| 2 | 2 | ||
| 3 | -import json | ||
| 4 | -from typing import Any, Dict, List, Optional | 3 | +from typing import Any, Optional |
| 5 | 4 | ||
| 6 | -from app.db.base import VectorDBClient | 5 | +from db.base import VectorDBClient |
| 7 | 6 | ||
| 8 | 7 | ||
| 9 | class PostgresClient(VectorDBClient): | 8 | class PostgresClient(VectorDBClient): |
| @@ -24,7 +23,6 @@ class PostgresClient(VectorDBClient): | @@ -24,7 +23,6 @@ class PostgresClient(VectorDBClient): | ||
| 24 | 23 | ||
| 25 | async def connect(self) -> None: | 24 | async def connect(self) -> None: |
| 26 | """建立PG连接池.""" | 25 | """建立PG连接池.""" |
| 27 | - import psycopg | ||
| 28 | from psycopg_pool import AsyncConnectionPool | 26 | from psycopg_pool import AsyncConnectionPool |
| 29 | 27 | ||
| 30 | self._pool = AsyncConnectionPool( | 28 | self._pool = AsyncConnectionPool( |
dockerfiles/Dockerfile_base260402
0 → 100644
| 1 | +# 基础镜像 - 安装依赖 | ||
| 2 | +FROM python:3.12-slim | ||
| 3 | + | ||
| 4 | +ARG DEBIAN_FRONTEND=noninteractive | ||
| 5 | +ENV TZ=Asia/Shanghai | ||
| 6 | + | ||
| 7 | +RUN cat > /etc/apt/sources.list <<EOF | ||
| 8 | +deb http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse | ||
| 9 | +deb-src http://mirrors.aliyun.com/ubuntu/ jammy main restricted universe multiverse | ||
| 10 | +deb http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse | ||
| 11 | +deb-src http://mirrors.aliyun.com/ubuntu/ jammy-updates main restricted universe multiverse | ||
| 12 | +deb http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse | ||
| 13 | +deb-src http://mirrors.aliyun.com/ubuntu/ jammy-backports main restricted universe multiverse | ||
| 14 | +deb http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse | ||
| 15 | +deb-src http://mirrors.aliyun.com/ubuntu/ jammy-security main restricted universe multiverse | ||
| 16 | +EOF | ||
| 17 | + | ||
| 18 | +# 安装系统依赖 | ||
| 19 | +RUN apt-get update && apt-get install -y --no-install-recommends gcc curl vim && rm -rf /var/lib/apt/lists/* | ||
| 20 | + | ||
| 21 | +# 安装uv工具 | ||
| 22 | +RUN pip install --no-cache-dir uv | ||
| 23 | +WORKDIR /app | ||
| 24 | +# 复制依赖文件 | ||
| 25 | +COPY ../pyproject.toml uv.lock ./ | ||
| 26 | + | ||
| 27 | +# 使用uv安装项目依赖(包含es和postgres可选依赖) | ||
| 28 | +RUN uv pip install --system --no-cache -e ".[es,postgres]" |
main.py
0 → 100644
| 1 | +import os | ||
| 2 | +from pathlib import Path | ||
| 3 | + | ||
| 4 | +os.environ["APP_LOG_TYPE"] = "file" | ||
| 5 | +os.environ['PROJECT_ROOT'] = Path(__file__).parent.absolute().as_posix() | ||
| 6 | +from aabd.base.patched_logging import set_global_logger, get_logger | ||
| 7 | + | ||
| 8 | +set_global_logger() | ||
| 9 | +logger = get_logger(__name__) | ||
| 10 | + | ||
| 11 | +from contextlib import asynccontextmanager | ||
| 12 | +from typing import AsyncGenerator | ||
| 13 | +from fastapi import FastAPI | ||
| 14 | +from api.http_config import config_fastapi | ||
| 15 | +from config import settings | ||
| 16 | +from db import connect as db_client_connect, disconnect as db_client_disconnect | ||
| 17 | + | ||
| 18 | + | ||
| 19 | +@asynccontextmanager | ||
| 20 | +async def lifespan(fastapi_app: FastAPI) -> AsyncGenerator[None, None]: | ||
| 21 | + """应用生命周期管理.""" | ||
| 22 | + # 启动时建立数据库连接 | ||
| 23 | + try: | ||
| 24 | + await db_client_connect() | ||
| 25 | + logger.info(f"✓ database connected") | ||
| 26 | + except Exception as e: | ||
| 27 | + logger.error(f"✗ Failed to connect to database: {e}") | ||
| 28 | + raise | ||
| 29 | + | ||
| 30 | + yield | ||
| 31 | + | ||
| 32 | + # 关闭时断开数据库连接 | ||
| 33 | + try: | ||
| 34 | + await db_client_disconnect() | ||
| 35 | + logger.info(f"✓ database disconnected") | ||
| 36 | + except Exception as e: | ||
| 37 | + logger.error(f"✗ Error disconnecting from database: {e}") | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +# 创建应用实例 | ||
| 41 | +app = FastAPI( | ||
| 42 | + title=settings.app_name, | ||
| 43 | + version=settings.app_version, | ||
| 44 | + description="基于向量的CRUD服务,支持多种Embedding场景", | ||
| 45 | + lifespan=lifespan, | ||
| 46 | + docs_url="/docs", | ||
| 47 | + redoc_url="/redoc", | ||
| 48 | +) | ||
| 49 | +config_fastapi(app) | ||
| 50 | + | ||
| 51 | +if __name__ == "__main__": | ||
| 52 | + import uvicorn | ||
| 53 | + | ||
| 54 | + uvicorn.run( | ||
| 55 | + "main:app", | ||
| 56 | + host='0.0.0.0', | ||
| 57 | + port=settings.port, | ||
| 58 | + reload=settings.debug, | ||
| 59 | + log_config=None | ||
| 60 | + ) |
| @@ -4,15 +4,13 @@ version = "0.1.0" | @@ -4,15 +4,13 @@ version = "0.1.0" | ||
| 4 | description = "基于向量的CRUD服务,支持多种Embedding场景" | 4 | description = "基于向量的CRUD服务,支持多种Embedding场景" |
| 5 | requires-python = ">=3.12" | 5 | requires-python = ">=3.12" |
| 6 | dependencies = [ | 6 | dependencies = [ |
| 7 | - "fastapi>=0.115.0", | ||
| 8 | - "uvicorn[standard]>=0.32.0", | ||
| 9 | - "pydantic>=2.9.0", | ||
| 10 | - "pydantic-settings>=2.6.0", | ||
| 11 | - "httpx>=0.27.0", | ||
| 12 | - "python-multipart>=0.0.12", | ||
| 13 | - "aabd>=0.4.1", | ||
| 14 | - "path>=17.1.1", | ||
| 15 | - "omegaconf>=2.3.0", | 7 | + "fastapi==0.135.3", |
| 8 | + "uvicorn[standard]==0.42.0", | ||
| 9 | + "pydantic==2.12.5", | ||
| 10 | + "aabd==0.4.3", | ||
| 11 | + "path==17.1.1", | ||
| 12 | + "omegaconf==2.3.0", | ||
| 13 | + "elasticsearch>=9.3.0", | ||
| 16 | ] | 14 | ] |
| 17 | 15 | ||
| 18 | [project.optional-dependencies] | 16 | [project.optional-dependencies] |
| @@ -24,11 +22,11 @@ dev = [ | @@ -24,11 +22,11 @@ dev = [ | ||
| 24 | "mypy>=1.13.0", | 22 | "mypy>=1.13.0", |
| 25 | ] | 23 | ] |
| 26 | es = [ | 24 | es = [ |
| 27 | - "elasticsearch>=8.15.0", | 25 | + "elasticsearch==9.3.0", |
| 28 | ] | 26 | ] |
| 29 | postgres = [ | 27 | postgres = [ |
| 30 | - "psycopg[binary,pool]>=3.2.0", | ||
| 31 | - "psycopg-pool>=3.3.0", | 28 | + "psycopg[binary,pool]==3.3.3", |
| 29 | + "psycopg-pool==3.3.0", | ||
| 32 | ] | 30 | ] |
| 33 | 31 | ||
| 34 | [tool.black] | 32 | [tool.black] |
| @@ -4,11 +4,11 @@ requires-python = ">=3.12" | @@ -4,11 +4,11 @@ requires-python = ">=3.12" | ||
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "aabd" | 6 | name = "aabd" |
| 7 | -version = "0.4.1" | 7 | +version = "0.4.3" |
| 8 | source = { registry = "https://pypi.org/simple" } | 8 | source = { registry = "https://pypi.org/simple" } |
| 9 | -sdist = { url = "https://files.pythonhosted.org/packages/ce/cf/e3098e0bf36aa4181c280bae6e72998ac6a38ad9c622f0a44019e79892bf/aabd-0.4.1.tar.gz", hash = "sha256:9a1143e5c55e31e935d3a27ba4c576af46cf53fa112115ef3e374b83a199409c", size = 5797147, upload-time = "2026-01-06T06:12:33.511Z" } | 9 | +sdist = { url = "https://files.pythonhosted.org/packages/24/85/29114249674f75d11ea3e8945ee62bc3399beff7462a02176f98bd4e6c45/aabd-0.4.3.tar.gz", hash = "sha256:7b905fd3b3913e6a4ff0bbe97225e043f35d68fb0a4a27165f4cfc3bba909ed4", size = 5800143, upload-time = "2026-04-02T06:19:08.509Z" } |
| 10 | wheels = [ | 10 | wheels = [ |
| 11 | - { url = "https://files.pythonhosted.org/packages/2a/36/fd16dfc2f57b4ebe3560e40b4b1329726f95548d94f0d66a0195aee0b3ab/aabd-0.4.1-py3-none-any.whl", hash = "sha256:ddb9c652223b7ef793552c59ed1909e140c85aac66f8704ed605f649ec704d04", size = 5821241, upload-time = "2026-01-06T06:12:18.452Z" }, | 11 | + { url = "https://files.pythonhosted.org/packages/6a/09/6bcb559910bf2285a5fffb1f9cf1e73ff8ac988b23a55639cc222a635c7a/aabd-0.4.3-py3-none-any.whl", hash = "sha256:82efa8e32510b9039a80454af4476b13376f20ed20731727d2e380937e251ce2", size = 5824954, upload-time = "2026-04-02T06:19:06.604Z" }, |
| 12 | ] | 12 | ] |
| 13 | 13 | ||
| 14 | [[package]] | 14 | [[package]] |
| @@ -17,13 +17,11 @@ version = "0.1.0" | @@ -17,13 +17,11 @@ version = "0.1.0" | ||
| 17 | source = { virtual = "." } | 17 | source = { virtual = "." } |
| 18 | dependencies = [ | 18 | dependencies = [ |
| 19 | { name = "aabd" }, | 19 | { name = "aabd" }, |
| 20 | + { name = "elasticsearch" }, | ||
| 20 | { name = "fastapi" }, | 21 | { name = "fastapi" }, |
| 21 | - { name = "httpx" }, | ||
| 22 | { name = "omegaconf" }, | 22 | { name = "omegaconf" }, |
| 23 | { name = "path" }, | 23 | { name = "path" }, |
| 24 | { name = "pydantic" }, | 24 | { name = "pydantic" }, |
| 25 | - { name = "pydantic-settings" }, | ||
| 26 | - { name = "python-multipart" }, | ||
| 27 | { name = "uvicorn", extra = ["standard"] }, | 25 | { name = "uvicorn", extra = ["standard"] }, |
| 28 | ] | 26 | ] |
| 29 | 27 | ||
| @@ -45,23 +43,21 @@ postgres = [ | @@ -45,23 +43,21 @@ postgres = [ | ||
| 45 | 43 | ||
| 46 | [package.metadata] | 44 | [package.metadata] |
| 47 | requires-dist = [ | 45 | requires-dist = [ |
| 48 | - { name = "aabd", specifier = ">=0.4.1" }, | 46 | + { name = "aabd", specifier = "==0.4.3" }, |
| 49 | { name = "black", marker = "extra == 'dev'", specifier = ">=24.10.0" }, | 47 | { name = "black", marker = "extra == 'dev'", specifier = ">=24.10.0" }, |
| 50 | - { name = "elasticsearch", marker = "extra == 'es'", specifier = ">=8.15.0" }, | ||
| 51 | - { name = "fastapi", specifier = ">=0.115.0" }, | ||
| 52 | - { name = "httpx", specifier = ">=0.27.0" }, | 48 | + { name = "elasticsearch", specifier = ">=9.3.0" }, |
| 49 | + { name = "elasticsearch", marker = "extra == 'es'", specifier = "==9.3.0" }, | ||
| 50 | + { name = "fastapi", specifier = "==0.135.3" }, | ||
| 53 | { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.13.0" }, | 51 | { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.13.0" }, |
| 54 | - { name = "omegaconf", specifier = ">=2.3.0" }, | ||
| 55 | - { name = "path", specifier = ">=17.1.1" }, | ||
| 56 | - { name = "psycopg", extras = ["binary", "pool"], marker = "extra == 'postgres'", specifier = ">=3.2.0" }, | ||
| 57 | - { name = "psycopg-pool", marker = "extra == 'postgres'", specifier = ">=3.3.0" }, | ||
| 58 | - { name = "pydantic", specifier = ">=2.9.0" }, | ||
| 59 | - { name = "pydantic-settings", specifier = ">=2.6.0" }, | 52 | + { name = "omegaconf", specifier = "==2.3.0" }, |
| 53 | + { name = "path", specifier = "==17.1.1" }, | ||
| 54 | + { name = "psycopg", extras = ["binary", "pool"], marker = "extra == 'postgres'", specifier = "==3.3.3" }, | ||
| 55 | + { name = "psycopg-pool", marker = "extra == 'postgres'", specifier = "==3.3.0" }, | ||
| 56 | + { name = "pydantic", specifier = "==2.12.5" }, | ||
| 60 | { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.0" }, | 57 | { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.0" }, |
| 61 | { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, | 58 | { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.24.0" }, |
| 62 | - { name = "python-multipart", specifier = ">=0.0.12" }, | ||
| 63 | { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.7.0" }, | 59 | { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.7.0" }, |
| 64 | - { name = "uvicorn", extras = ["standard"], specifier = ">=0.32.0" }, | 60 | + { name = "uvicorn", extras = ["standard"], specifier = "==0.42.0" }, |
| 65 | ] | 61 | ] |
| 66 | provides-extras = ["dev", "es", "postgres"] | 62 | provides-extras = ["dev", "es", "postgres"] |
| 67 | 63 | ||
| @@ -196,7 +192,7 @@ wheels = [ | @@ -196,7 +192,7 @@ wheels = [ | ||
| 196 | 192 | ||
| 197 | [[package]] | 193 | [[package]] |
| 198 | name = "fastapi" | 194 | name = "fastapi" |
| 199 | -version = "0.135.2" | 195 | +version = "0.135.3" |
| 200 | source = { registry = "https://pypi.org/simple" } | 196 | source = { registry = "https://pypi.org/simple" } |
| 201 | dependencies = [ | 197 | dependencies = [ |
| 202 | { name = "annotated-doc" }, | 198 | { name = "annotated-doc" }, |
| @@ -205,9 +201,9 @@ dependencies = [ | @@ -205,9 +201,9 @@ dependencies = [ | ||
| 205 | { name = "typing-extensions" }, | 201 | { name = "typing-extensions" }, |
| 206 | { name = "typing-inspection" }, | 202 | { name = "typing-inspection" }, |
| 207 | ] | 203 | ] |
| 208 | -sdist = { url = "https://files.pythonhosted.org/packages/c4/73/5903c4b13beae98618d64eb9870c3fac4f605523dd0312ca5c80dadbd5b9/fastapi-0.135.2.tar.gz", hash = "sha256:88a832095359755527b7f63bb4c6bc9edb8329a026189eed83d6c1afcf419d56", size = 395833, upload-time = "2026-03-23T14:12:41.697Z" } | 204 | +sdist = { url = "https://files.pythonhosted.org/packages/f7/e6/7adb4c5fa231e82c35b8f5741a9f2d055f520c29af5546fd70d3e8e1cd2e/fastapi-0.135.3.tar.gz", hash = "sha256:bd6d7caf1a2bdd8d676843cdcd2287729572a1ef524fc4d65c17ae002a1be654", size = 396524, upload-time = "2026-04-01T16:23:58.188Z" } |
| 209 | wheels = [ | 205 | wheels = [ |
| 210 | - { url = "https://files.pythonhosted.org/packages/8f/ea/18f6d0457f9efb2fc6fa594857f92810cadb03024975726db6546b3d6fcf/fastapi-0.135.2-py3-none-any.whl", hash = "sha256:0af0447d541867e8db2a6a25c23a8c4bd80e2394ac5529bd87501bbb9e240ca5", size = 117407, upload-time = "2026-03-23T14:12:43.284Z" }, | 206 | + { url = "https://files.pythonhosted.org/packages/84/a4/5caa2de7f917a04ada20018eccf60d6cc6145b0199d55ca3711b0fc08312/fastapi-0.135.3-py3-none-any.whl", hash = "sha256:9b0f590c813acd13d0ab43dd8494138eb58e484bfac405db1f3187cfc5810d98", size = 117734, upload-time = "2026-04-01T16:23:59.328Z" }, |
| 211 | ] | 207 | ] |
| 212 | 208 | ||
| 213 | [[package]] | 209 | [[package]] |
| @@ -220,19 +216,6 @@ wheels = [ | @@ -220,19 +216,6 @@ wheels = [ | ||
| 220 | ] | 216 | ] |
| 221 | 217 | ||
| 222 | [[package]] | 218 | [[package]] |
| 223 | -name = "httpcore" | ||
| 224 | -version = "1.0.9" | ||
| 225 | -source = { registry = "https://pypi.org/simple" } | ||
| 226 | -dependencies = [ | ||
| 227 | - { name = "certifi" }, | ||
| 228 | - { name = "h11" }, | ||
| 229 | -] | ||
| 230 | -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } | ||
| 231 | -wheels = [ | ||
| 232 | - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, | ||
| 233 | -] | ||
| 234 | - | ||
| 235 | -[[package]] | ||
| 236 | name = "httptools" | 219 | name = "httptools" |
| 237 | version = "0.7.1" | 220 | version = "0.7.1" |
| 238 | source = { registry = "https://pypi.org/simple" } | 221 | source = { registry = "https://pypi.org/simple" } |
| @@ -262,21 +245,6 @@ wheels = [ | @@ -262,21 +245,6 @@ wheels = [ | ||
| 262 | ] | 245 | ] |
| 263 | 246 | ||
| 264 | [[package]] | 247 | [[package]] |
| 265 | -name = "httpx" | ||
| 266 | -version = "0.28.1" | ||
| 267 | -source = { registry = "https://pypi.org/simple" } | ||
| 268 | -dependencies = [ | ||
| 269 | - { name = "anyio" }, | ||
| 270 | - { name = "certifi" }, | ||
| 271 | - { name = "httpcore" }, | ||
| 272 | - { name = "idna" }, | ||
| 273 | -] | ||
| 274 | -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } | ||
| 275 | -wheels = [ | ||
| 276 | - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, | ||
| 277 | -] | ||
| 278 | - | ||
| 279 | -[[package]] | ||
| 280 | name = "idna" | 248 | name = "idna" |
| 281 | version = "3.11" | 249 | version = "3.11" |
| 282 | source = { registry = "https://pypi.org/simple" } | 250 | source = { registry = "https://pypi.org/simple" } |
| @@ -624,20 +592,6 @@ wheels = [ | @@ -624,20 +592,6 @@ wheels = [ | ||
| 624 | ] | 592 | ] |
| 625 | 593 | ||
| 626 | [[package]] | 594 | [[package]] |
| 627 | -name = "pydantic-settings" | ||
| 628 | -version = "2.13.1" | ||
| 629 | -source = { registry = "https://pypi.org/simple" } | ||
| 630 | -dependencies = [ | ||
| 631 | - { name = "pydantic" }, | ||
| 632 | - { name = "python-dotenv" }, | ||
| 633 | - { name = "typing-inspection" }, | ||
| 634 | -] | ||
| 635 | -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } | ||
| 636 | -wheels = [ | ||
| 637 | - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, | ||
| 638 | -] | ||
| 639 | - | ||
| 640 | -[[package]] | ||
| 641 | name = "pygments" | 595 | name = "pygments" |
| 642 | version = "2.20.0" | 596 | version = "2.20.0" |
| 643 | source = { registry = "https://pypi.org/simple" } | 597 | source = { registry = "https://pypi.org/simple" } |
| @@ -697,15 +651,6 @@ wheels = [ | @@ -697,15 +651,6 @@ wheels = [ | ||
| 697 | ] | 651 | ] |
| 698 | 652 | ||
| 699 | [[package]] | 653 | [[package]] |
| 700 | -name = "python-multipart" | ||
| 701 | -version = "0.0.22" | ||
| 702 | -source = { registry = "https://pypi.org/simple" } | ||
| 703 | -sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } | ||
| 704 | -wheels = [ | ||
| 705 | - { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, | ||
| 706 | -] | ||
| 707 | - | ||
| 708 | -[[package]] | ||
| 709 | name = "pytokens" | 654 | name = "pytokens" |
| 710 | version = "0.4.1" | 655 | version = "0.4.1" |
| 711 | source = { registry = "https://pypi.org/simple" } | 656 | source = { registry = "https://pypi.org/simple" } |
-
Please register or login to post a comment