《Python 项目配置的终极方案:config.py + pyproject.toml 深度解读》
引言
前两篇文章解决了容器部署和开发自动化的问题。但应用内部是如何读取这些环境变量的?依赖库又是如何管理的?
本文深入到 Python 代码层面,解析两个核心文件:
-
app/core/config.py:配置总控中心,智能加载 .env 文件并解析为 Python 对象。
-
pyproject.toml:项目身份证明,统一管理依赖和代码规范。
读完本文,你将彻底理解从“环境变量”到“Python 代码”的完整数据流。
一、config.py:配置总控中心
1.1 环境识别器 get_environment()
python
class Environment(str, Enum):
DEVELOPMENT = "development"
STAGING = "staging"
PRODUCTION = "production"
TEST = "test"
def get_environment() -> Environment:
match os.getenv("APP_ENV", "development").lower():
case "production" | "prod":
return Environment.PRODUCTION
case "staging" | "stage":
return Environment.STAGING
case "test":
return Environment.TEST
case _:
return Environment.DEVELOPMENT
-
读取 APP_ENV 环境变量,支持模糊匹配(prod 和 production 都能识别)。
-
返回标准化的枚举类型,供后续逻辑判断使用。
1.2 智能文件加载器 load_env_file()
python
def load_env_file():
env = get_environment()
env_files = [
os.path.join(base_dir, f".env.{env.value}.local"), # 最高优先级
os.path.join(base_dir, f".env.{env.value}"), # 环境专属
os.path.join(base_dir, ".env.local"), # 通用本地覆盖
os.path.join(base_dir, ".env"), # 兜底
]
for env_file in env_files:
if os.path.isfile(env_file):
load_dotenv(dotenv_path=env_file)
return env_file
return None
ENV_FILE = load_env_file() # 模块加载时立即执行
优先级链:
.env.development.local > .env.development > .env.local > .env
-
.local 文件通常被 .gitignore 忽略,允许开发者在本地覆盖团队配置,而不影响他人。
-
该函数在模块导入时立即执行,确保 Settings 类实例化前环境变量已被注入。
1.3 辅助解析器 parse_list_from_env()
python
def parse_list_from_env(env_key, default=None):
value = os.getenv(env_key)
if not value:
return default or []
value = value.strip("\\"'")
if "," not in value:
return [value]
return [item.strip() for item in value.split(",") if item.strip()]
作用:将环境变量中的 "http://a.com,http://b.com" 解析为 Python 列表 ['http://a.com', 'http://b.com']。
使用场景:CORS 跨域白名单 ALLOWED_ORIGINS。
1.4 Settings 类 —— 配置映射中心
python
class Settings:
def __init__(self):
self.ENVIRONMENT = get_environment()
# 应用配置
self.PROJECT_NAME = os.getenv("PROJECT_NAME", "FastAPI LangGraph Template")
self.DEBUG = os.getenv("DEBUG", "false").lower() in ("true", "1", "t", "yes")
# 数据库配置
self.POSTGRES_HOST = os.getenv("POSTGRES_HOST", "localhost")
self.POSTGRES_PORT = int(os.getenv("POSTGRES_PORT", "5432"))
self.POSTGRES_DB = os.getenv("POSTGRES_DB", "food_order_db")
# 应用环境专属覆盖
self.apply_environment_settings()
-
类型转换:bool、int、float、Path 自动转换。
-
默认值兜底:如果环境变量未设置,使用代码中的硬编码默认值(如数据库默认 localhost)。
-
apply_environment_settings():根据 ENVIRONMENT 自动设置 DEBUG、LOG_LEVEL 等。
1.5 环境专属覆盖的防覆盖设计
python
def apply_environment_settings(self):
env_settings = {
Environment.DEVELOPMENT: {"DEBUG": True, "LOG_LEVEL": "DEBUG"},
Environment.PRODUCTION: {"DEBUG": False, "LOG_LEVEL": "WARNING"},
}
for key, value in env_settings.get(self.ENVIRONMENT, {}).items():
env_var_name = key.upper()
if env_var_name not in os.environ: # 关键判断
setattr(self, key, value)
核心哲学:只有当系统环境变量没有显式设置该值时,才会覆盖。这意味着 Docker 传入的变量或 Shell 手动导出的变量拥有最高优先级。
二、config.py 与所有配置文件的关系
| 系统环境变量(export POSTGRES_HOST=1.2.3.4) | os.getenv() 直接读取 | 最高 |
| Docker Compose env_file 注入 | 容器启动时注入 os.environ | 高 |
| set_env.sh source 加载的 .env | Shell 导出后,Python 继承 | 中 |
| load_env_file() 加载的 .env 文件 | load_dotenv() 写入 os.environ | 低 |
| 代码中的硬编码默认值 | os.getenv("KEY", "default") | 最低(兜底) |
实战推演:本地执行 make dev,POSTGRES_HOST 的值来自 .env.development 中的 db;若想临时连接其他数据库,可直接 POSTGRES_HOST=192.168.1.100 make dev,此时系统环境变量优先级最高,覆盖文件配置。
三、pyproject.toml:项目身份与依赖管理
3.1 项目元数据与核心依赖 [project]
toml
[project]
name = "langgraph-fastapi-template"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"fastapi>=0.121.0",
"langchain>=1.0.5",
"langgraph>=1.0.2",
"psycopg[binary]>=3.3.2",
"python-dotenv>=1.1.0",
"uvicorn>=0.34.0",
# … 共 30+ 个库
]
-
身份信息:项目名称、版本、最低 Python 版本。
-
核心依赖:所有运行时必需的库。config.py 中的 from dotenv import load_dotenv 就来源于此处的 python-dotenv。
3.2 可选依赖与分组
toml
[project.optional-dependencies]
dev = ["black", "isort", "flake8", "ruff"]
cache = ["redis>=7.4.0", "valkey[libvalkey]>=6.1.0"]
[dependency-groups]
dev = ["detect-secrets", "pre-commit", "pyright"]
test = ["httpx", "pytest"]
-
开发依赖(ruff、pyright)与生产依赖分离。
-
cache 组包含 valkey,对应 config.py 中的 VALKEY_HOST 配置,缓存模块可插拔。
3.3 工具统一配置 [tool.*]
toml
[tool.ruff]
line-length = 119
exclude = ["migrations", "venv"]
[tool.ruff.lint]
select = ["E", "F", "B", "ERA", "D"]
ignore = ["E501", "D203"]
[tool.pyright]
typeCheckingMode = "standard"
reportDuplicateImport = "error"
-
Ruff:行长度 119,启用 Google 风格 Docstring 检查。
-
Pyright:标准严格模式,reportDuplicateImport 视为错误。
-
统一配置:所有代码检查工具(Ruff、Black、Pyright、Pytest)的配置都集中在此,根目录不再有 .flake8、.isort.cfg 等零散文件。
3.4 与 Dockerfile 的关系
dockerfile
COPY pyproject.toml uv.lock ./
RUN uv sync –frozen –no-install-project
Dockerfile 直接复制 pyproject.toml,uv sync 读取 dependencies 列表安装依赖。如果 pyproject.toml 缺少 psycopg,容器中的程序就连接不上数据库。
3.5 与 Makefile 的关系
makefile
lint: uv run ruff check .
typecheck: uv run pyright
Makefile 调用 uv run 执行工具,而具体检查规则(如行长度 119、忽略哪些错误)全部来源于 pyproject.toml 中的 [tool.ruff] 配置。
四、完整配置流转闭环
text
┌─────────────────────────────────────────────────────────────────┐
│ 1. 开发者克隆项目 │
│ ↓ │
│ 2. 执行 source set_env.sh development │
│ → 自动从 .env.example 复制为 .env.development │
│ → 填入真实 OPENAI_API_KEY、POSTGRES_PASSWORD │
│ ↓ │
│ 3. 执行 make dev │
│ → Makefile 调用 run_with_env,source set_env.sh │
│ → set_env.sh 导出所有变量到 Shell │
│ → 启动 uvicorn,Python 进程继承环境变量 │
│ ↓ │
│ 4. config.py 模块加载 │
│ → load_env_file() 优先加载 .env.development.local │
│ → 若不存在,加载 .env.development │
│ → Settings 类读取 os.getenv(),类型转换 │
│ → apply_environment_settings() 根据环境补全默认值 │
│ ↓ │
│ 5. 业务代码使用 settings.POSTGRES_HOST │
└─────────────────────────────────────────────────────────────────┘
五、总结
-
config.py 是应用层的“终点站”,无论配置来自 .env 文件、Docker 注入还是 Shell 导出,都在这里被统一为 Python 对象。
-
pyproject.toml 是项目的“物料清单”,定义了所有依赖库和代码规范,被 Dockerfile 和 Makefile 共同引用。
-
整个配置体系遵循 12-Factor App 原则:配置与代码严格分离,改变配置无需重新构建镜像。
至此,我们从容器部署(第一篇)→ 开发自动化(第二篇)→ 应用配置(第三篇),完整覆盖了一个生产级 FastAPI + LangGraph 项目的所有配置维度。希望这个系列能帮助你搭建出优雅、高效、可维护的后端项目。
网硕互联帮助中心




评论前必须登录!
注册