1. 情景
src 这种项目结构下,如果业务逻辑需要一个私密的token,应该如何配置最好(考虑 git上传)
2. 做法
典型做法是:代码里永远不直接写 token,而是通过「环境变量/本地私密文件(不进 Git)」加载,再用一个示例配置文件告诉协作者需要哪些字段。下面给出一个可复制的方案。
3. 示例(适用于 src/
布局)
3.1 项目结构(关键文件)
my_project/
├── .gitignore
├── .env.example # 示例环境变量文件(提交)
├── .env # 真实密钥文件(不提交)
├── pyproject.toml
├── src/
│ └── my_package/
│ └── settings.py # 统一读取配置的入口
└── tests/
3.2 .gitignore
.gitignore内容(确保真实密钥不进仓库)
# .env 文件永不上传
.env
.env.*
!.env.example
3.3 .env.example
.env.example
(提交,说明需要哪些变量)
# Copy to .env and fill in real values
MYAPP_API_TOKEN=
MYAPP_DB_URL=
3.4 .env
.env
(本地/服务器放真实值,不提交)
dotenv复制编辑MYAPP_API_TOKEN=sk-xxxxxx
MYAPP_DB_URL=postgresql://user:pwd@host/db
3.5. 代码里读取
以 Pydantic Settings / python-dotenv 为例
安装依赖:
poetry add pydantic-settings python-dotenv
# 或者:poetry add pydantic-settings (不装dotenv也行,用Pydantic自带读取)
src/my_package/settings.py
:
python复制编辑from pydantic_settings import BaseSettings
from pydantic import SecretStr
class Settings(BaseSettings):
api_token: SecretStr
db_url: str | None = None
class Config:
env_file = ".env"
env_prefix = "MYAPP_" # 环境变量前缀,可选
settings = Settings()
业务代码使用:
from my_package.settings import settings
def call_api():
token = settings.api_token.get_secret_value()
...
优点:
.env
放在项目根,不进 Git;
.env.example
告诉同事/CI 要准备哪些变量;
Pydantic 会验证必填项、类型安全;
SecretStr
防止日志中直接打印出 Token。
4. 和直接用一个py文件硬编码对比
差别本质上在于安全性、可维护性、可迁移/部署性和团队协作成本。直接在 settings.py
里写 my_token = 'xxx'
看似简单,但一旦项目稍微复杂、多人协作或要部署到不同环境,就会出现一堆问题。
4.1 直接硬编码 vs. 配置/环境变量方案对比
维度 | 硬编码在 settings.py | .env / 环境变量 + 统一读取 |
---|---|---|
安全性 | 极易被误传到 Git、PR、日志;删了也留在历史中 | .gitignore 掉,CI/CD 使用 Secret Store,泄露概率低 |
密钥轮换 | 要改代码、发新版本、重新部署 | 改环境变量或 .env 即可,代码不动 |
多环境(dev/staging/prod) | 每个环境得改代码或分支 | 环境变量天然支持同字段不同值 |
团队协作 | 新人拿到代码却没密钥?写死就得给他仓库里那份…… | .env.example 告诉 TA 需要哪些值,自己填 |
测试/CI | 单元测试要 mock/改源码 | 测试里注入临时 env,干净可控 |
合规与审计 | 一旦泄漏需全仓库清理(git filter-repo) | 中心化管理,审计容易 |
AI 辅助写码 | AI 可能把真实 token 复制到别的地方 | AI 改代码也改不了你的环境变量值 |
4.2 常见“踩坑”例子(硬编码场景)
不小心把私有仓库转公开,密钥瞬间泄漏。
- 合作者 fork 后你撤回不了他的副本。
- 需要换 token,所有人都得更新代码再重新部署。
- 本地与服务器、Docker、CI 环境的 token 不同——只能到处改源代码。
- 日志或异常栈里打印了整个 settings 对象,token 跟着暴露。
4.4 推荐的“配置加载”方式做了什么?
- 统一入口(例如
settings.py
用 Pydantic Settings / dotenv):- 负责从
.env
/ 环境变量 / 命令行 / 配置文件读取; - 负责校验必填项(不填就报错),给默认值;
- 负责类型转换(比如把字符串的“true”“10.5”转为 bool/float);
- 避免每个地方都
os.getenv("XXX")
乱写一通。
- 负责从
- 代码与秘密解耦:业务逻辑只关心
settings.api_token
,不关心它从哪来。
4.5 什么时候可以“偷懒”硬编码?
- 一次性的小脚本、彻底个人用途、永不上传也不跟别人共享。
- 临时 demo,马上要删。
但即使如此,也建议用环境变量习惯,养成模式后不会觉得麻烦。