[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "meida-backend" version = "0.1.0" description = "美搭 (Meida) backend — AI fashion & styling assistant for HarmonyOS" readme = "README.md" requires-python = ">=3.12,<3.13" license = { text = "Proprietary" } dependencies = [ "fastapi>=0.115", "uvicorn[standard]>=0.30", "gunicorn>=22.0", "sqlalchemy[asyncio]>=2.0", "asyncpg>=0.29", "alembic>=1.13", "pydantic>=2.7", "pydantic-settings>=2.3", "celery[redis]>=5.4", "redis>=5.0", "httpx>=0.27", "structlog>=24.1", "opentelemetry-api>=1.25", "opentelemetry-sdk>=1.25", "opentelemetry-instrumentation-fastapi>=0.46b0", "opentelemetry-exporter-otlp-proto-http>=1.25", "prometheus-client>=0.20", "pgvector>=0.2.5", "tenacity>=8.3", "pyjwt[crypto]>=2.8", "passlib[bcrypt]>=1.7.4", ] [project.optional-dependencies] dev = [ "ruff>=0.6", "mypy>=1.11", "pytest>=8.3", "pytest-asyncio>=0.24", "pytest-cov>=5.0", "import-linter>=2.0", "pre-commit>=3.8", "detect-secrets>=1.5", "testcontainers[postgres,redis]>=4.8", "types-passlib>=1.7", ] [tool.hatch.build.targets.wheel] packages = ["apps", "packages", "services"] # --------------------------------------------------------------------------- # ruff — lint + format (架构文档 §16.2 静态检查防线) # --------------------------------------------------------------------------- [tool.ruff] target-version = "py312" line-length = 100 src = ["."] extend-exclude = ["migrations/versions"] [tool.ruff.lint] select = [ "E", "W", # pycodestyle "F", # pyflakes "I", # isort "N", # pep8-naming "UP", # pyupgrade "B", # bugbear "C4", # comprehensions "SIM", # simplify "ASYNC", # async correctness "S", # bandit (security) "T20", # no print "RUF", ] ignore = [ "S101", # assert is fine (pytest) "B008", # FastAPI Depends() in defaults is idiomatic # 本项目面向中文用户,注释/文案/提示词大量使用全角标点。 # RUF001-003 会把每一个中文标点都报成 "ambiguous unicode",无信号价值。 "RUF001", "RUF002", "RUF003", ] [tool.ruff.lint.per-file-ignores] "tests/**" = ["S105", "S106", "S311"] "migrations/**" = ["E501"] [tool.ruff.lint.isort] known-first-party = ["apps", "packages", "services"] [tool.ruff.format] quote-style = "double" # --------------------------------------------------------------------------- # mypy — strict 仅覆盖 packages/domain 与 packages/llm (架构文档 §16.2) # --------------------------------------------------------------------------- [tool.mypy] python_version = "3.12" plugins = ["pydantic.mypy"] ignore_missing_imports = true warn_unused_configs = true warn_redundant_casts = true show_error_codes = true namespace_packages = true explicit_package_bases = true exclude = ["migrations/versions/"] [[tool.mypy.overrides]] module = ["packages.domain.*", "packages.llm.*"] disallow_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_calls = true disallow_untyped_decorators = true disallow_any_generics = true disallow_subclassing_any = true check_untyped_defs = true no_implicit_reexport = true warn_return_any = true warn_unused_ignores = true strict_equality = true extra_checks = true # --------------------------------------------------------------------------- # pytest # --------------------------------------------------------------------------- [tool.pytest.ini_options] minversion = "8.0" testpaths = ["tests"] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" addopts = [ "-ra", "--strict-markers", "--cov=packages.llm", "--cov=packages.domain", "--cov-report=term-missing", "--cov-fail-under=80", ] markers = [ "integration: needs PostgreSQL/Redis (docker compose or testcontainers)", "golden: golden-set regression (retrieval / rules / report)", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "if TYPE_CHECKING:", "raise NotImplementedError", "\\.\\.\\.", ] # --------------------------------------------------------------------------- # import-linter — 机器化执行架构文档 §4.2 的依赖规则 # CI 失败即阻断合并 (§16.2 分层依赖检查) # --------------------------------------------------------------------------- [tool.importlinter] root_packages = ["apps", "packages", "services"] include_external_packages = true # --- §16.1: packages/domain 是纯逻辑层,不依赖 FastAPI / DB / HTTP / 队列 --- [[tool.importlinter.contracts]] name = "packages.domain is pure (no IO, no framework)" type = "forbidden" source_modules = ["packages.domain"] forbidden_modules = [ "fastapi", "starlette", "sqlalchemy", "alembic", "asyncpg", "httpx", "redis", "celery", "packages.shared", "packages.llm", "apps", "services", ] # --- §4.2 禁止的依赖 (1): compliance 不得反向依赖任何业务域 --- [[tool.importlinter.contracts]] name = "compliance must not depend on any business domain" type = "forbidden" source_modules = ["packages.compliance"] forbidden_modules = [ "packages.identity", "packages.profile", "packages.report", "packages.agent", "packages.tryon", "packages.kb", "packages.billing", "packages.task", "packages.notify", "packages.wardrobe", ] # --- §4.2 禁止的依赖 (2): identity 不得依赖其他业务域 --- [[tool.importlinter.contracts]] name = "identity must not depend on any other business domain" type = "forbidden" source_modules = ["packages.identity"] forbidden_modules = [ "packages.profile", "packages.report", "packages.agent", "packages.tryon", "packages.kb", "packages.billing", "packages.task", "packages.compliance", "packages.notify", "packages.wardrobe", ] # --- §3.2 硬性规则 1: 禁止直接查对方的表,只能走对方的 service --- [[tool.importlinter.contracts]] name = "no cross-domain repository (table) access" type = "forbidden" source_modules = [ "packages.agent", "packages.billing", "packages.task", "packages.identity", "packages.compliance", ] forbidden_modules = [ "packages.agent.repo", "packages.billing.repo", "packages.task.repo", "packages.identity.repo", ] unmatched_ignore_imports_alerting = "none" ignore_imports = [ "packages.agent.* -> packages.agent.repo", "packages.billing.* -> packages.billing.repo", "packages.task.* -> packages.task.repo", "packages.identity.* -> packages.identity.repo", ] # --- §3.1 分层视图: L3 API → L4 业务域 → L5 AI 能力 → L6 数据 --- [[tool.importlinter.contracts]] name = "layered architecture (apps -> domains -> ai/shared -> domain)" type = "layers" layers = [ "apps", "packages.agent | packages.report | packages.tryon | packages.profile | packages.notify | packages.wardrobe", "packages.task | packages.billing | packages.kb", "packages.compliance | packages.identity", "packages.llm | packages.retrieval | packages.rules | packages.memory", "packages.shared", "packages.domain", ] exhaustive = false # --- AI 能力层不得回头依赖业务域 (§4.3: llm-gateway 必须按远程调用设计) --- [[tool.importlinter.contracts]] name = "AI capability packages must not depend on business domains" type = "forbidden" source_modules = [ "packages.llm", "packages.retrieval", "packages.rules", ] forbidden_modules = [ "packages.identity", "packages.profile", "packages.report", "packages.agent", "packages.tryon", "packages.kb", "packages.billing", "packages.task", "apps", ]