在接口测试、自动化脚本和多环境部署场景里,环境配置通常是最先要解决的一层:数据库地址、服务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]。
- import os
- from typing import Union
- BASE_DIR: Union[bytes, str] = os.path.dirname(os.path.abspath(__file__))
- def get_file_path(folder: str, env: str, filename: str) -> str:
- """
- :param folder: 文件夹名称
- :param env: 环境命名文件夹名称
- :param filename: yaml文件名
- :return: 返回文件路径
- """
- file_path = os.path.join(BASE_DIR, folder, env, filename)
- 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 对象。
- import yaml
- def get_custompath_yam(config_file):
- """读取根据自定义路径的yaml文件获得文件中的数据
- :param config_file:文件路径
- :return:
- """
- with open(config_file, 'r') as f:
- config = yaml.safe_load(f)
- 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) 元组返回。
- def ConfigLoader(env):
- """
- :param env: 传入环境
- :return:
- """
- if env == 'dev' or env == 'DEV':
- # datapath = os.path.join(BASE_DIR, 'datas', env, 'datas.yaml')
- datapath = get_file_path('datas', env, 'datas.yaml')
- elif env == 'test' or env == 'TEST':
- datapath = get_file_path('datas', env, 'datas.yaml')
- elif env == 'pre' or env == 'PRE':
- datapath = get_file_path('datas', env, 'datas.yaml')
- else:
- raise EnvironmentError(f'Please set up your environment ! Current Context:{env}')
- datas = get_custompath_yam(datapath)
- 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'] 的等价实现。
- def get_ConfigLoader():
- datapath = os.path.join(BASE_DIR, 'datas', 'application_env.yaml')
- data = get_custompath_yam(datapath)
- # with open(datapath, 'r') as f:
- # data = yaml.safe_load(f)
- # print(data['profiles']['env'])
- env = data['profiles']['env']
- return ConfigLoader(env)
- # 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 文件,读取函数只依赖路径,便于被其他模块复用。 |