云计算百科
云计算领域专业知识百科平台

《Python 项目配置的终极方案:config.py + pyproject.toml 深度解读》

《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 与所有配置文件的关系

数据来源如何进入 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 项目的所有配置维度。希望这个系列能帮助你搭建出优雅、高效、可维护的后端项目。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 《Python 项目配置的终极方案:config.py + pyproject.toml 深度解读》
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!