项目结构:
pkgname/ # 项目根目录
├── docs/ # 文档
├── charts/ # Helm Charts
├── pkgname/ # 项目主包(与项目同名)
│ ├── __init__.py # 包的入口
│ ├── common/
│ │ ├── config/
│ │ ├── consts/
│ │ ├── middlewares/
│ │ ├── dependencies.py # 依赖项,用于依赖注入
│ │ ├── logger.py
│ │ └── __init__.py
│ ├── controller/
│ ├── service/
│ ├── manager/
│ ├── repo/
│ ├── model/
│ │ ├── dto/
│ │ ├── bo/
│ │ └── entity/
│ ├── utils/ # 放置与项目完全无关的工具类函数或模块
│ │ ├── helper.py # 例如:用于帮助其他模块的工具函数
│ │ └── ...
│ └── ...
├── tests/ # 测试目录
│ ├── __init__.py
│ ├── test_module1.py
│ ├── test_module2.py
│ └── ...
├── scripts/ # 项目使用的一些辅助脚本、启动脚本等
│ │ ├── server.py # 启动命令
│ │ ├── build_plugin.sh
│ │ └── check_env.sh
├── .gitignore
├── Dockerfile
├── Jenkinsfile
├── pyproject.toml # Poetry 管理的核心配置文件
├── poetry.lock # 依赖锁定文件(自动生成,不要手动修改)
├── README.md # 项目简介
└── LICENSE # 许可证
这种项目结构下,导包很容易:
- 对于第三方包,直接通过包名导入,比如
from pydantic import BaseModel
,这个没什么说的。 - pkgname 下的子孙模块互相导入,是通过根包名一直找下去,比如 controller 中某个模块导入 service 中某个模块:
from pkgname.service.xxx import xxx
- 某个子孙包,如果包内部的文件互相导入,可以使用相对路径,比如
pkgname/common/__init__.py
中导入pkgname/common/config/xxx.py
时:from .config import xxx
- tests 中的模块这样导入主包:
from pkgname.service.xxx import xxx
这种项目结构,在运行时,当前工作目录需要是项目根目录,以及 IDE 打开时也要打开项目根目录,否则包的导入关系可能会失效。
另外,还有一种常见的目录结构是,在项目主包外加一层 src 目录:
pkgname/ # 项目根目录
├── src/
│ ├── pkgname/ # 项目主包(与项目同名)
│ │ ├── controller/
│ │ ├── service/
│ │ └── ...
├── tests/ # 测试目录
└── ...
这样没有上面那种结构方便:
- 在 IDE 中开发、调试时,需要将 src 设置为搜索路径(PYTHONPATH 环境变量)
- 启动项目时(无论是直接 python xxx.py 启动,还是启动 uvicorn 等服务器),也需要设置 PYTHONPATH
- 用 pytest 执行测试时,也需要设置 PYTHONPATH