查看: 187|回复: 3

Python用YAML实现多环境配置的目录结构与加载函数

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在接口测试、自动化脚本和多环境部署场景里,环境配置通常是最先要解决的一层:数据库地址、服务host、端口等参数会随 dev、test、pre 变化。若把这些值硬编码进脚本,切换环境就要改代码。本文按一组现成的实践代码,梳理如何用 YAML 组织环境数据,并用几个函数完成“路径拼接—文件读取—环境分派”的加载链路,最后由一个 application_env.yaml 统一控制当前环境。

一、先约定目录层级
代码假定项目内存在固定相对目录:<项目根>/<folder>/<env>/<filename>。例如 folder=datas、env=dev、filename=datas.yaml,对应 datas/dev/datas.yaml。所有环境配置都按“业务目录 + 环境名 + 文件名”三段拼接,新增环境只需要新增目录和文件,读取逻辑不用动。

二、BASE_DIR 与 get_file_path 的实现
get_file_path() 的路径基准取自 BASE_DIR:先用 os.path.abspath(__file__) 拿到当前 Python 文件的绝对路径,再用 os.path.dirname() 取该文件所在目录。这样得到的是“本文件所在目录”,而不是进程的当前工作目录,脚本被从任意路径调用时相对路径不会失效。

函数接受三个参数:folder 为文件夹名称,env 为环境命名文件夹名称,filename 为 YAML 文件名;函数内部用 os.path.join() 把三段与 BASE_DIR 拼成完整路径并返回。类型标注上 folder、env、filename 为 str,返回 str,而 BASE_DIR 标注为 Union[bytes, str]。
  1. import os
  2. from typing import Union
  3. BASE_DIR: Union[bytes, str] = os.path.dirname(os.path.abspath(__file__))
  4. def get_file_path(folder: str, env: str, filename: str) -> str:
  5.     """
  6.     :param folder: 文件夹名称
  7.     :param env: 环境命名文件夹名称
  8.     :param filename: yaml文件名
  9.     :return: 返回文件路径
  10.     """
  11.     file_path = os.path.join(BASE_DIR, folder, env, filename)
  12.     return file_path
复制代码

按示例调用 get_file_path('config', 'dev', 'config.yml'),输出形如 /home/user/project/config/dev/config.yml。

三、读取 YAML:open 加 yaml.safe_load
get_custompath_yam(config_file) 只做一件事:以只读方式打开传入路径,交给 yaml.safe_load 解析并返回数据。这里用 safe_load 而不是 load,是为了避免从配置文件里构造任意 Python 对象。
  1. import yaml
  2. def get_custompath_yam(config_file):
  3.     """读取根据自定义路径的yaml文件获得文件中的数据
  4.     :param config_file:文件路径
  5.     :return:
  6.     """
  7.     with open(config_file, 'r') as f:
  8.         config = yaml.safe_load(f)
  9.     return config
复制代码

需要注意原文“最终代码”版里这一行被写成了 return config_data,而函数体内的变量名是 config,这会直接触发 NameError;实际应以 return config 为准。这类笔误在复制粘贴式重构中很常见,排错时优先核对返回变量名与赋值变量名是否一致。

四、ConfigLoader:按 env 分派配置
ConfigLoader(env) 只接受一个环境参数,分支判断 dev/DEV、test/TEST、pre/PRE 三种取值(大小写各枚举一次),每个分支都调用 get_file_path('datas', env, 'datas.yaml') 取得路径,注释里保留了等价的 os.path.join(BASE_DIR, 'datas', env, 'datas.yaml') 写法。三个分支之后的 else 抛出 EnvironmentError,并把当前传入值拼进消息,便于快速定位是哪个环境名写错。拿到路径后再调用 get_custompath_yam() 读取内容,最终以 (env, datas) 元组返回。
  1. def ConfigLoader(env):
  2.     """
  3.     :param env: 传入环境
  4.     :return:
  5.     """
  6.     if env == 'dev' or env == 'DEV':
  7.         # datapath = os.path.join(BASE_DIR, 'datas', env, 'datas.yaml')
  8.         datapath = get_file_path('datas', env, 'datas.yaml')
  9.     elif env == 'test' or env == 'TEST':
  10.         datapath = get_file_path('datas', env, 'datas.yaml')
  11.     elif env == 'pre' or env == 'PRE':
  12.         datapath = get_file_path('datas', env, 'datas.yaml')
  13.     else:
  14.         raise EnvironmentError(f'Please set up your environment ! Current Context:{env}')
  15.     datas = get_custompath_yam(datapath)
  16.     return env, datas
复制代码

调用 env, datas = ConfigLoader('dev'),返回的第一项是 'dev',第二项是 YAML 解析出的字典,例如包含 database 的 host、port、user、password、database 等字段,输出类似:

dev
{'database': {'host': 'localhost', 'port': 3306, 'user': 'root', 'password': '123456', 'database': 'mydb'}}

五、用 application_env.yaml 控制环境变量
最后一步是让脚本不依赖手工传参。get_ConfigLoader() 固定读取 datas/application_env.yaml,取 data['profiles']['env'] 作为环境名,再转交给 ConfigLoader()。注释同样保留了直接 open + yaml.safe_load + data['profiles']['env'] 的等价实现。
  1. def get_ConfigLoader():
  2.     datapath = os.path.join(BASE_DIR, 'datas', 'application_env.yaml')
  3.     data = get_custompath_yam(datapath)
  4.     # with open(datapath, 'r') as f:
  5.     #     data = yaml.safe_load(f)
  6.     # print(data['profiles']['env'])
  7.     env = data['profiles']['env']
  8.     return ConfigLoader(env)
  9. # print(get_ConfigLoader())
复制代码

这样切换环境不需要改任何 Python 代码,只改 YAML 中 profiles.env 的值即可。示例执行输出为:

('test', {'TEST': {'app': {'host': 'http://192.168.**.***', 'port': None}, 'console': {'host': 'http://192.168.**.***', 'port': None}}})

说明当前 application_env.yaml 指向 test,读取到的是 datas/test/datas.yaml 的内容。

六、落地时容易踩的几个点
1. 目录必须真实存在。os.path.join 只做字符串拼接,既不创建目录也不校验存在性,拼出来的路径直接交给 open 会抛 FileNotFoundError,所以 datas/dev、datas/test 等目录与 YAML 文件要提前就位。
2. 环境名大小写敏感。分支只覆盖 dev/DEV、test/TEST、pre/PRE,传入 'Dev' 之类会落到 else 抛 EnvironmentError,错误消息里的 Current Context 就是排查线索。
3. 空 YAML 文件会被 safe_load 解析为 None,后续按下标取 profiles.env 会失败,配置文件至少要保证有 profiles 层级。
4. BASE_DIR 取自定义该变量的文件所在目录,跨模块引用时先确认它属于哪个模块,否则多拼一层或少拼一层都会指向错误路径。
5. ConfigLoader 的三个分支逻辑完全一致,只有 folder 和 env 参与拼路径,实际项目中可以合并;保留分支写法的意义在于后续可按环境做差异化处理,例如 test 环境叠加覆盖配置。

七、适用场景
这套结构适合接口测试数据管理、脚本多环境切换、自动化用例的配置外置等需求:配置随环境分目录存放,环境入口收敛到一个 YAML 文件,读取函数只依赖路径,便于被其他模块复用。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python用YAML实现多环境配置的目录结构与加载函数

这套按“业务目录 + 环境名 + 文件名”组织配置的思路很实用,新增环境时只要补目录和 YAML 文件,加载逻辑不用动,扩展性很好。BASE_DIR 取当前文件所在目录而不是工作目录这一点也很关键,能避免脚本从不同路径启动时相对路径失效;safe_load 的使用也比 load 更稳妥。 另外文中对 get_custompath_yam 里 return config_data 和 config 变量名不一致的提醒很及时,这种笔误确实容易在复制改代码时出现。ConfigLoader 里 dev/DEV、test/TEST、pre/PRE 的分支略重复,后面可以考虑先统一做一次小写转换再判断,减少重复代码。还有标题提到由 application_env.yaml 统一控制当前环境,但正文目前主要展示的是手动传入 env 的分派逻辑,如果后续能补上“从 application_env.yaml 读取当前环境再调用 ConfigLoader”的完整链路,会更完整。整体结构清晰,排错提示也写得比较友好。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python用YAML实现多环境配置的目录结构与加载函数

这篇整理得挺清楚,目录层级、BASE_DIR 的取法、safe_load 的安全考虑以及 ConfigLoader 的分派链路都讲明白了。用 __file__ 做基准确实比依赖当前工作目录稳,脚本从不同路径调用时不容易踩坑;返回变量名 config_data 和 config 不一致这个提醒也很实用,复制重构时很容易忽略。 有个小建议是 ConfigLoader 里 dev/test/pre 三个分支目前重复度比较高,后面可以考虑先把 env 统一转成小写再判断,或者用环境名到文件路径的映射来减少重复,新增环境会更省事。另外读 YAML 时如果能显式加上 encoding='utf-8' 会更稳妥一些,safe_load 遇到空文件返回 None 的情况也值得留意。正文最后提到 application_env.yaml 统一控制当前环境,但代码部分还没展开,如果后续能补上这层如何读取并传给 ConfigLoader 就更完整了。整体思路很实用,适合直接拿来改成项目里的配置加载模块。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python用YAML实现多环境配置的目录结构与加载函数

这个整理挺实用,路径拼接、YAML 读取和环境分派三层拆得比较清楚,safe_load 的选用以及返回变量名笔误的提醒也很到位。实际落地时有两个小点可以再确认:一是 BASE_DIR 取的是当前 Python 文件所在目录,如果这几个函数放在 utils 目录,而 datas 在项目根目录下,get_file_path 拼出来的路径可能会偏到 utils/datas/dev/datas.yaml,最好确认加载模块和 datas 的相对位置,或者把 BASE_DIR 统一定义在项目根目录。二是 ConfigLoader 虽然枚举了大小写,但拼路径时用的还是原始 env,传入 DEV 会去找 datas/DEV/datas.yaml;如果目录只有小写 dev,就会读不到,建议在分支前统一转成小写再参与路径拼接,或明确目录也用大写。另外 application_env.yaml 统一控制当前环境的部分正文只提了思路,可以补一个读取当前 env 再传给 ConfigLoader 的示例,整条链路会更完整。open 时加 encoding='utf-8' 也会更稳一些。整体思路适合接口测试和多环境脚本,值得参考。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-10 04:48 , Processed in 0.023531 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部