wangdongxu

jira:NYJ-1460 desc:init

@@ -206,4 +206,4 @@ test/ @@ -206,4 +206,4 @@ test/
206 # OS 206 # OS
207 .DS_Store 207 .DS_Store
208 Thumbs.db 208 Thumbs.db
209 - 209 +logs/
  1 +# 应用镜像 - 基于基础镜像部署代码
  2 +FROM aigc-embedding-service-base:latest
  3 +
  4 +# 设置工作目录
  5 +WORKDIR /app
  6 +
  7 +# 复制应用代码(包含配置文件)
  8 +COPY app/ ./app/
  9 +
  10 +# 暴露端口
  11 +EXPOSE 8000
  12 +
  13 +# 启动命令
  14 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
  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
  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)
1 -from aabd.base.cfg_loader import load_yaml_by_name_with_env  
2 -from aabd.base.enhance_dict import EnhanceDict, read_prefixed_env_vars  
3 -  
4 -settings = EnhanceDict(load_yaml_by_name_with_env('config'))  
5 -settings.update(read_prefixed_env_vars('APP_'))  
6 -  
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")  
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 - )  
1 -from app.db import es_client  
2 -  
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"
  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(
  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]"
  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" }