[project] name = "payroll-backend" version = "0.1.0" description = "衍水集团营销人员工资管理系统 —— 后端服务" readme = "README.md" # 锁定 3.12:系统默认是 3.14,第三方库轮子覆盖不如 3.12 完备 requires-python = ">=3.12,<3.13" dependencies = [ "fastapi>=0.115", # starlette 已进入 1.x(当前 1.7.0)。FastAPI 对它的约束是 `starlette>=0.46.0`, # **没有上界** —— uv lock --upgrade 时可能漂移到未验证的大版本。 # 我们的异常处理器与 TestClient 直接依赖其 API,故显式限定主版本。 # 当前组合(fastapi 0.143 + starlette 1.7.0)已实测通过全部用例。 "starlette>=1.7,<2", "uvicorn[standard]>=0.32", "pydantic>=2.9", "pydantic-settings>=2.6", # ORM 与驱动:采用同步 —— 本项目并发极低,异步无吞吐收益; # 而重活(绩效批量核算、pandas 报表、Excel 导出)全是同步阻塞代码, # 在异步路由里跑会卡死事件循环。同步路由由 FastAPI 自动放入线程池执行。 "sqlalchemy>=2.0.36", # 必须带 [rsa]:MySQL 8.0 默认 caching_sha2_password,容器重启后首次连接走 # 全量认证需要 cryptography 做 RSA 交换,缺失时 PyMySQL 直接抛 RuntimeError # (报错出现在建连阶段,极易被误读成"密码错误") "pymysql[rsa]>=1.2.3", "alembic>=1.14", # 验证码、登录限流、Token 黑名单 "redis[hiredis]>=5.2", # 鉴权与密码哈希 # 注意:不用 passlib —— 它与 bcrypt>=4.1 存在 __about__ 属性缺失的不兼容问题 "pyjwt>=2.10", "pwdlib[bcrypt]>=0.2", # 图形验证码(纯 Python,依赖 Pillow) "captcha>=0.6", "pillow>=11.0", "openpyxl>=3.1.5", ] [dependency-groups] dev = [ "pytest>=8.3", "pytest-cov>=7", "httpx>=0.28,<1.0", # 用假 Redis 做单测,无需起容器即可覆盖 GETDEL 一次性语义 "fakeredis>=2.30", # lint + format 二合一,替代 flake8 + isort + black "ruff>=0.14", ] # 本项目按应用而非库来管理,不打包安装自身 [tool.uv] package = false [tool.pytest.ini_options] testpaths = ["tests"] markers = [ "integration: 需要 MySQL/Redis 容器或完整应用实例的测试", ] # ==================== 代码检查与格式化 ==================== [tool.ruff] # 与项目根 .editorconfig 保持一致:Python 用 4 空格(缩进由 formatter 控制) target-version = "py312" line-length = 100 src = ["app", "tests"] [tool.ruff.lint] select = [ "E", # pycodestyle 错误 "W", # pycodestyle 警告 "F", # pyflakes:未使用的导入、未定义的名称等 "I", # isort:导入顺序 "B", # flake8-bugbear:常见陷阱(可变默认参数、循环引用等) "C4", # flake8-comprehensions:推导式可简化处 "UP", # pyupgrade:按目标 Python 版本升级旧写法 "SIM", # flake8-simplify:可简化的分支与判断 "RUF", # ruff 自有规则 ] ignore = [ # 行宽已由 formatter 控制,lint 不再重复报——长中文注释很容易触发 "E501", # FastAPI 的依赖注入要求在参数默认值处调用 Depends(),是框架约定而非缺陷。 # 项目内已尽量改用 Annotated 形式(见 api/deps.py),此处兜底。 "B008", # RUF001/002/003 检查「易混淆的 Unicode 字符」,分别针对字符串字面量、 # 文档字符串、注释 —— 这套规则的默认前提是英文文本。 # 对中文会把全角逗号、句号、括号、冒号全部判定为混淆字符(本项目实测 800+ 条), # 而中文里全角标点正是规范写法,故一并关闭。 "RUF001", "RUF002", "RUF003", ] [tool.ruff.lint.isort] # 必须显式声明:本项目 `[tool.uv] package = false`(不作为包安装), # ruff 无法从项目布局推断出 `app` 是第一方包,否则会把第三方依赖 # 排到 `app.*` 之后(按字母序把两者当成同一组)。 known-first-party = ["app", "tests"] [tool.ruff.lint.per-file-ignores] # Alembic 自动生成的迁移脚本不按手写代码的标准要求。 # 这些文件由 `alembic revision --autogenerate` 产出,手工修 lint 会在下次生成时被覆盖。 "alembic/versions/*.py" = ["E501", "I001", "W291"] [tool.ruff.format] quote-style = "double"