Files
meida_backend/pyproject.toml
T
xsl 07e5197ec4 fix(deps): numpy is a runtime dependency of packages/retrieval, not spike-only
T8 的 DeterministicEmbedder 在主依赖里 import numpy,但它此前只随 spike
extra 安装——纯 dev 环境(也是 CI 的 uv sync --extra dev)四 个测试文件
直接 ModuleNotFoundError。移入主依赖。
2026-08-14 20:58:57 +08:00

171 lines
5.8 KiB
TOML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[project]
name = "meida-backend"
version = "0.1.0"
description = "美搭 · 智能穿搭美学助手服务端"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115",
"uvicorn[standard]>=0.32",
"pydantic>=2.9",
"pydantic-settings>=2.6",
"sqlalchemy[asyncio]>=2.0.36",
"asyncpg>=0.30",
"alembic>=1.14",
"pgvector>=0.3.6",
"redis>=5.2",
"celery>=5.4",
"httpx>=0.28",
"tenacity>=9.0",
"pyjwt[crypto]>=2.9",
"structlog>=24.4",
"orjson>=3.10",
"prometheus-client>=0.21",
"cos-python-sdk-v5>=1.9.44",
"tencentcloud-sdk-python-sms>=3.1.153",
"numpy>=2.5.2",
]
[project.optional-dependencies]
spike = [
# 算法验证 SPIKE(T-P0-10/12/16)专用;模型文件在 models/(已 gitignore)
"onnxruntime>=1.20",
"opencv-python-headless>=4.10",
]
dev = [
"pytest>=8.3",
"pytest-asyncio>=0.24",
"pytest-cov>=6.0",
"respx>=0.22",
"ruff>=0.8",
"mypy>=1.13",
"import-linter>=2.1",
"detect-secrets>=1.5",
"pre-commit>=4.0",
]
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages.find]
include = ["apps*", "packages*", "services*"]
# ── ruff ────────────────────────────────────────────────────────────────────
[tool.ruff]
line-length = 100
target-version = "py312"
extend-exclude = ["migrations/versions"]
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "SIM", "ASYNC", "C4", "RUF"]
# B008 与 FastAPI 的 Depends() 默认值写法直接冲突
ignore = ["B008", "RUF001", "RUF002", "RUF003"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["SLF001"]
# ── pytest ──────────────────────────────────────────────────────────────────
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
# 集成用例需要真实 PostgreSQL/Redis,默认不跑:`make test-integration` 显式开启。
# golden 用例(黄金集回归)同样需要真实 PG:CI 单独一步强制跑(T-P2-11),
# 本地默认跳过、`make golden` 显式开启。
addopts = "-q --strict-markers --strict-config -m 'not integration and not golden' --cov=packages --cov=apps"
markers = [
"integration: 需要真实 PostgreSQL / Redis(默认跳过)",
"golden: 黄金集回归(需要真实 PG;指标退化即失败,CI 强制跑)",
]
[tool.coverage.run]
branch = true
source = ["packages", "apps"]
omit = ["*/__init__.py"]
[tool.coverage.report]
show_missing = true
skip_covered = true
# ── mypy ────────────────────────────────────────────────────────────────────
[tool.mypy]
python_version = "3.12"
plugins = ["pydantic.mypy"]
ignore_missing_imports = true
warn_unused_configs = true
warn_redundant_casts = true
warn_unused_ignores = true
no_implicit_reexport = true
disallow_untyped_defs = true
check_untyped_defs = true
# 领域层与 LLM 网关是钱与正确性的所在,按 strict 要求;外层逐步收紧。
[[tool.mypy.overrides]]
module = ["packages.domain.*", "packages.llm.*"]
strict = true
[[tool.mypy.overrides]]
module = ["tests.*", "migrations.*"]
disallow_untyped_defs = false
# ── import-linter:分层依赖(架构文档 §4.2)────────────────────────────────
# 这些契约用机器代替 code review 来守住依赖方向。没有它,
# "domain 不许 import 框架" 这类约定会在第一次赶工时悄悄破掉。
[tool.importlinter]
root_packages = ["apps", "packages", "services"]
include_external_packages = true
[[tool.importlinter.contracts]]
name = "分层:apps → packages → domain,不得逆向"
type = "layers"
# 配额(billing)、账号(identity)、任务中心(task)是被各业务域共用的能力,
# 因此排在业务域下方一层 —— Agent 要预扣配额、报告要建任务,这些依赖是正当的。
# 反向不成立:billing 不该知道 Agent 的存在。
layers = [
"apps",
"packages.agent | packages.report | packages.tryon | packages.wardrobe",
"packages.identity | packages.billing | packages.task | packages.compliance | packages.notify | packages.analytics | packages.storage",
"packages.kb | packages.retrieval | packages.rules | packages.memory | packages.profile",
"packages.llm",
"packages.shared",
"packages.domain",
]
[[tool.importlinter.contracts]]
name = "domain 层不依赖任何框架与 I/O"
type = "forbidden"
source_modules = ["packages.domain"]
forbidden_modules = [
"fastapi",
"starlette",
"sqlalchemy",
"alembic",
"celery",
"redis",
"httpx",
"pydantic_settings",
]
[[tool.importlinter.contracts]]
name = "业务包不得绕过网关直连大模型"
type = "forbidden"
source_modules = [
"packages.agent",
"packages.report",
"packages.identity",
"packages.billing",
"packages.task",
"packages.compliance",
]
forbidden_modules = ["packages.llm.providers"]
# 只禁直接 import。经由 packages.llm.gateway 的间接依赖是正常的 ——
# 要拦的是"业务代码自己拼 HTTP 请求",那样成本归因与熔断都会被绕过。
allow_indirect_imports = "true"
[[tool.importlinter.contracts]]
name = "apps 之间互不依赖"
type = "independence"
modules = ["apps.api", "apps.worker", "apps.admin"]
# 例外:API 投递异步任务需要 worker 的任务签名。这是投递方向(api → worker),
# 反向不允许 —— worker 不得 import apps.api,否则 worker 会被迫加载整个 HTTP 层。
ignore_imports = ["apps.api.routers.tasks -> apps.worker.tasks"]