惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
H
Help Net Security
云风的 BLOG
云风的 BLOG
Apple Machine Learning Research
Apple Machine Learning Research
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
D
Docker
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Blog — PlanetScale
Blog — PlanetScale
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
GbyAI
GbyAI
博客园 - Franky
B
Blog RSS Feed
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
量子位
V
Visual Studio Blog
Y
Y Combinator Blog
小众软件
小众软件
N
Netflix TechBlog - Medium
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
雷峰网
雷峰网

博客园 - 一名程序媛呀

Anki插件开发必知必会:钩子函数与右键菜单定制 httpx 传参总报错?这次把 GET、POST、文件上传到响应处理的坑给你一次填平 从卡顿到丝滑:FastAPI 调用外部 API 的正确姿势(httpx 实战) 还在 XHR、Fetch 和 Axios 之间纠结?我踩过的坑,希望你一个都不用碰到 你的REST接口还在“过度投喂”数据吗?——FastAPI + GraphQL实战避坑指南 Uvicorn、Gunicorn 傻傻分不清?FastAPI 生产部署避坑指南 Termux里的二进制和脚本,到底怎么运行才不踩坑?Termux-service 保活妙招! 刚部署的 LibreTranslate 频频翻车?我掏出了 20 年前的 StarDict 词典,用 FastAPI 搭了个本地词典翻译 API 别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了 掏出手机就能搭个 WebDAV 同步服务器?这操作有点香 别只盯着GitBook了!这个文档神器让你的笔记秒变网站 写爬虫时用了代理还被封?Python 代理的那些隐藏坑,我替你踩明白了 FastAPI 身份验证总踩坑?这份 FastAPI Users “避坑指南”请收好 旧手机别扔!用 Termux 搭个私人云盘,比网盘香多了 你的FastAPI又在服务器上“跑不起来”了?来,今天咱把打包这件事彻底聊透 写页面时别再把 Element Plus 整个搬进来啦!Vue3按需加载的坑我帮你踩平了 前端包管理咋选?我从npm叛逃到pnpm的血泪史(附避坑指南) 聊聊 fetch 使用中我踩过的那些坑和正确打开方式 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 告别 Typora 后的新欢:我把所有笔记迁移到了 Obsidian 这个“第二大脑” 你的Agent API还在裸奔?从认证到沙箱,我用FastAPI搭了几道防线 让 FastAPI Agent 思考不阻塞:手把手教你实现异步任务与后台处理方案 让FastAPI Agent真正记住你:聊聊会话记忆与持久化存储的落地实践 FastAPI Agent 函数调用实战:我让 AI 学会了“自己动手查天气“ 初探:用 FastAPI 搭建你的第一个 AI Agent 接口 FastAPI 少有人提的实用技巧:把 Depends 依赖提到路由层,代码少写60% FastAPI 生产环境静态文件完全指南:从 /favicon.ico 404 到 HSTS 混合内容,一次全根治 用了loguru我才明白,Python日志还能这么写
FastAPI配置管理避坑指南:从硬编码到 .env 与 pydantic_sett...
一名程序媛呀 · 2026-04-28 · via 博客园 - 一名程序媛呀

你是不是也干过这种事:写完接口,顺手把数据库地址、密码或者 API_KEY 往代码里一写,然后高高兴兴地 git push

千万别这样偷懒。之前看到过把自己生产环境的 Redis 密码直接写在 settings.py 里,然后传到了公开仓库……结果第二天:整个库被清空,留下一行“请支付比特币”

想想就后背发凉。也从那以后我就彻底和硬编码说再见了。今天咱们就来聊聊怎么把 FastAPI 的配置管理从“裸奔”升级到“装甲车”。

🎯 本文能帮你解决什么

✔️ 敏感信息泄漏风险,再也不会把密钥传到 GitHub
✔️ 快速切换开发/测试/生产环境,改一个变量就行
✔️ 用 pydantic_settings 自动校验配置,少写一堆 try-except

📌 主要内容脉络

咱们先从一个痛点说起,然后一步步把硬编码改成环境变量,再升级到 pydantic_settings 的 BaseSettings ,顺便说清 dotenv pydantic_settings 到底怎么选。最后还会聊多环境配置覆盖和优先级那点事儿。

🚨 第一部分:硬编码的痛,你可能正在经历

很多项目一开始为了“快”,直接把数据库连接信息写成字符串。像这样:

DATABASE_URL = "postgresql://user:pass123@localhost/db"

看起来没毛病,直到你需要部署到服务器、要换一套数据库地址。

要么你手动改代码打包,要么写一堆 if-else 判断环境。更可怕的是,万一代码仓库公开,密码就等于贴在了公告栏上

你可能会问:“那我加到 .gitignore 里不就行了?” 是的,但配置本身还是散落在各处,团队一多照样乱套。

🔧 第二部分:从环境变量到 Settings 类,步步为营

好,咱们先来第一步——把秘密藏进环境变量。

在终端 export 或者在系统里设置一个 DATABASE_URL ,然后代码里用 os.getenv 去取。这样至少不会把密码写在代码里了。

但是你很快会发现,每次重启机器环境变量就丢了,而且得挨个给队友配一遍,极其麻烦。这时候就该 .env 文件 出场了。

接下来重点来了——怎么把 .env 用起来?到底是选 python-dotenv 还是 pydantic_settings

这个工具的选择,好比选螺丝刀,不是最贵的就好,而是看你的场景。
如果你只是想要一个简单的 key-value 读取,dotenv 足够轻量
但一旦你需要类型校验、字段默认值、多环境自动切换,pydantic_settings 的 BaseSettings 绝对是更聪明的选择

再说个容易翻车的点:
.env 文件一定要立刻加到 .gitignore 里,并且提供一个 .env.example 模板文件给队友。我有次就是忘了这一步,差点把密钥推上去。

👩‍💻 第三部分:实战 —— 用 BaseSettings 管好你的配置

直接上代码,一看就懂:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "MyFastAPI"
    database_url: str
    secret_key: str
    debug: bool = False    

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

settings = Settings()

然后你的 .env 文件长这样:

APP_NAME=FastAPI配置管理测试项目
DATABASE_URL=postgresql://user:pass@localhost/db
SECRET_KEY=super-secret-key
DEBUG=false

你注意到没, 字段名大小写自动匹配,而且 debug 直接当布尔值用,不用自己 int("0") 转换。
pydantic 还会帮你做校验,万一环境变量里没设 database_url ,应用启动时直接抛错,避免带着空配置跑起来。

是不是以为这样就完了?还不够。生产环境的配置通常会覆盖某些默认值,这时你可以这么做:

class Settings(BaseSettings):
    app_name: str = "MyApp"
    debug: bool = False

    class Config:
        env_file = ".env"
        # 当存在 .env.prod 时优先加载
        env_file = ".env.prod" if os.getenv("ENV") == "prod" else ".env"

官方文档虽然没强制,但根据我的经验,最好把不同环境的 .env 文件分开,再用环境变量 ENV 来决定加载哪个。

📂 .env 文件放哪里?不是无所谓,但也很灵活

既然 Settings 类已经在用了,那 .env 文件到底该扔在哪儿,绝对是个“说大不大,说小坑一堆”的问题。好,咱们把它掰扯清楚。

默认情况下,pydantic_settings 会在当前工作目录(CWD)下找你的 .env 文件。也就是说,你在哪个目录执行的 Python 命令,它就去哪个目录找。

举个例子,如果你的项目结构长这样:

 my_project/ 
 ├── app/ 
 │ ├── main.py 
 │ └── config.py 
 ├── .env 
 └── requirements.txt 

你在 my_project 目录下执行 python app/main.py 或者 uvicorn app.main:app ,当前工作目录就是 my_project,所以它能找到同级的 .env,稳稳的。

但是!一旦你换个姿势,比如在别的地方执行,或者用 Docker、systemd 启动,工作目录往往就跑偏了。
我也曾经因为这个问题在服务器上排查了一下午,配置死活不生效,最后发现是 supervisor 把工作目录切到了 /root…… 😭

🎯 最佳实践:显式指定路径,不依赖 CWD

别再猜来猜去了,直接在 Settings 类的 Config 里把路径写清楚。这里有一个我屡试不爽的写法:

from pydantic_settings import BaseSettings
import os 

class Settings(BaseSettings):
    database_url: str
    secret_key: str

    class Config:
        # 关键!用 file 定位到 config.py 同级目录下的 .env
        env_file = os.path.join(os.path.dirname(__file__), ".env")
        env_file_encoding = "utf-8"

这么一来,不管你在哪里执行,它永远去找 config.py 所在目录下的 .env 文件
也就是说,把 .env config.py 放在同一个文件夹里(通常是 app 目录下),就再也不会丢了。

你可能会问:“那多环境怎么办?比如 .env.prod 放哪儿?” 同样啊,也放 config.py 旁边,然后根据环境变量稍微改一下路径就行:

import os

env = os.getenv("APP_ENV", "dev")
env_file = os.path.join(os.path.dirname(__file__), f".env.{env}") 

🧪 查找机制的底层逻辑(一句话版)

如果你不显式指定路径,pydantic_settings 内部其实调用的是 python-dotenv 的 load_dotenv(env_file) ,它就会基于当前工作目录去解析相对路径。所以这玩意儿就像“前台的快递”,你得站在正确的工位上才能收到。
而用 __file__ 的方式则相当于你把收货地址写成了“我家”,无论快递员从哪个邮局出发,都能送到。

📣 几个血的教训

✖️ 不要放在项目的上级目录,否则部署时很容易漏掉。
✖️ 不要放在会随代码一起被静态文件服务暴露的目录下(比如 static/),别人直接通过 URL 就可能下载到。
✔️ 一律放在项目代码包内部,和 config.py 做邻居,再配合 .gitignore,最安全。

🧠 第四部分:配置优先级 —— 别被覆盖规则搞晕

这里有个很容易踩的坑。 pydantic_settings 加载配置的优先级是这样的:

🥇 系统环境变量(最高优先级)

🥈 .env 文件中的值

🥉 你在类里设置的默认值

也就是说,哪怕 .env 里有 DATABASE_URL ,如果你在启动容器时传入了同名环境变量,它会被覆盖
这其实是个好事,特别适合在 k8s docker-compose 里注入密钥而不改动镜像。

🍳 在路由里用上你的 Settings

直接上最简单粗暴的办法——导入实例,当普通对象用

假设你的配置类写在 config.py 里,并且已经实例化成了 settings,然后在路由文件里:

from fastapi import FastAPI
from config import settings

app = FastAPI()

@app.get("/info")
async def get_app_info():
    return {
        "app_name": settings.app_name,
        # ⚠️ 千万别把 secret_key 等敏感信息直接返回!这里只是演示取值
        "database_connected": bool(settings.database_url)
    }

你可能会问:“这跟直接用全局变量有啥区别?”那区别可就大了——
这里所有敏感值都来自外部,代码里没硬编码,而且 pydantic 在启动时就会校验类型,数据库地址格式不对的话,应用压根起不来,早报错早治疗

💡 进阶玩法:用依赖注入,测试更友好

上面那种直接导入的方式虽然快,但写单元测试时不好替换配置。更优雅的做法是把 settings 变成一个 FastAPI 依赖,就像这样:

from fastapi import Depends

def get_settings():
    return settings

@app.get("/db-status")
async def db_status(config: Settings = Depends(get_settings)):
    # 现在你的路由函数可以拿到 config 对象,测试时也能覆盖
    return {"db_url_prefix": config.database_url.split(":")[0]}

这里有个容易翻车的点:千万别在 Depends 里直接返回一个普通 dict,那样你会丢掉 pydantic 的校验和 IDE 自动补全。坚持用 Settings 类的实例,开发体验能好一大截。

🔐 一个特殊的常见需求:在路由里用密钥

有时候需要在视图里签发 JWT 或者调用第三方 API,自然就得用到 secret_key 或 api_token。
这时建议 把 settings 实例注入到工具函数里,而不是直接在路由里裸用字符串

from jose import jwt

def create_token(data: dict, settings: Settings):
    return jwt.encode(data, settings.secret_key, algorithm="HS256")

@app.post("/login")
async def login(config: Settings = Depends(get_settings)):
    token = create_token({"user": "test"}, config)
    return {"access_token": token}

最后啰嗦一句:无论哪种方式,.env 文件本身必须待在 .gitignore 里
我见过太多次“配置类写得完美,结果把 .env 顺手 push 上去”的惨案,每次都得连夜轮换密钥 😭。
还有就是千万别在生产环境开启 debug=true ,不然报错时所有堆栈信息全会返回给前端,又是一场事故。

💬 总结与互动

通过以上这些操作,你的 FastAPI 路由就能安全又优雅地用上配置了。从硬编码到环境变量,再到用 BaseSettings 优雅地管理多环境配置,这条路我走得跌跌撞撞,但真的投入产出比超高。
现在你新起一个 FastAPI 项目,只需要花上 10 分钟把 Settings 类搭好,后面就再也不用为“密码怎么传”这种事头疼了。

你有过配置泄露的惊险时刻吗?或者你在用别的配置管理小技巧?来评论区唠唠,咱们一起避坑 😭。或者还有什么想聊的?评论区等你,一起填坑 👷‍♀️


觉得有用就点个「赞」+ 「关注」,省得下次想找时迷路。
也欢迎转发给那个还在硬编码密码的同事,救一个是一个 👊