python yaml文件操作

一:yaml简介及基础语法

 yaml是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。

1.1 yaml基础语法规则

  • 大小写敏感
  • 使用缩进表示层级关系
  • 不允许使用 TAB 键来缩进,只允许使用空格键来缩进
  • 缩进的空格数量不重要
  • 使用"#"来表示注释

1.2 yaml 支持的数据结构有三种

  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

  1.2.1 对象

对象的一组键值对,使用:(冒号)结构表示。冒号之后必须有一个空格

#test.yml文件

animal: pets

#转为python 如下:

{'animal': 'pets'}

yaml 也允许另一种写法,将所有键值对写成一个行内对象。

#test.yml文件
 
hash: { name: Steve, foo: bar } 

#转为python如下:

{'hash': {'name': 'Steve', 'foo': 'bar'}}

  1.2.2 数组

一组连词线开头的行,构成一个数组。

#test.yml文件
- Cat
- Gog
- Goldfis

#转为python 如下:

['Cat', 'Gog', 'Goldfis']

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。

#test.yml 文件
-
 - Cat
 - Dog
 - Goldfish

#转为 python 如下:

[['Cat', 'Dog', 'Goldfish']]

数组也可以采用行内表示法。

#test.yml文件
animal: [Cat, Dog]

#转为 python 如下;

{'animal': ['Cat', 'Dog']}

  1.2.3 复合结构

对象和数组可以结合使用,形成复合结构。

#test.yml文件
languahes:
  - JavaScript
  - java
  - Python
websites:
 YAML: yaml.org
 Ruby: ruby-lang.org
 Python: python.org
 Perl: use.perl.org

#转为 python 如下;
{
    'languahes': ['JavaScript', 'java', 'Python'], 
    'websites': {'YAML': 'yaml.org', 'Ruby': 'ruby-lang.org', 'Python': 'python.org', 'Perl': 'use.perl.org'}
}

  1.2.4 纯量

纯量是最基本的、不可再分的值。

纯量包含:字符串 整数 浮点数 布尔值 Null(用~表示) 时间 日期   其中:用~表示null ; 时间、日期采用IS08601格式 

#test.yml文件

name: 'xiaoli'
age: 22
weight: 57.30
isStudent: true
address: ~ 
time: 2001-12-14t21:59:43.10-05:00 
date: 1976-07-31

#转为 python 如下:
{'name': 'xiaoli', 'age': 22, 'weight': 57.3, 'isStudent': True, 'address': None, 
'time': datetime.datetime(2001, 12, 14, 21, 59, 43, 100000, tzinfo=datetime.timezone(datetime.timedelta(-1, 68400))),
'date': datetime.date(1976, 7, 31)}

 yaml允许使用两个感叹号,强制转换数据类型。

#test.yml文件

e: !!str 22
f: !!str true

#转为 python 如下:
{'e': '22', 'f': 'true'}

  1.2.5 锚点&和引用*

在yaml文件中如何引用变量?当我们在一个yaml文件中写很多测试数据时候,比如一些配置信息像用户名,邮箱,数据库配置等很多地方都会重复用到。
重复的数据,如果不设置变量,后续维护起来就很困难。
yaml文件里面也可以设置变量(锚点&),其它地方重复用到的话,可以用*引用 

#test.yml文件
defaults: &defaults
adapter: postgres
host: localhost

development:
database: myapp_development
<<: *defaults

test:
database: myapp_test
<<: *defaults

 等同于下面的代码:

 
defaults:
  adapter: postgres
  host: localhost
 
development:
  database: myapp_development
  adapter: postgres
  host: localhost
 
test:
  database: myapp_test
  adapter: postgres
  host: localhost

&用来建立锚点(defaults),<<表示合并到当前数据,*用来引用锚点。

  1.2.6 *引用value值

上面的例子是对defaults整体的数据,引用到其它地方了,有时候我们只想引用其中的一个值

#test.yml 文件
defaults:
  adapter: postgres
  host: &host 127.0.0.1

development:
  database: myapp_development
  myhost: *host

#等同于下面的数据
defaults:
  adapter: postgres
  host: &host 127.0.0.1

development:
  database: myapp_development
  myhost: 127.0.0.1

二:yaml文件的读写

python本身并没有自带的处理yaml文件的库,需要下载并安装第三方库PyYAML 或 ruamel.yaml ,这里我们先安装PyYAML。

#安装命令
 pip install pyyaml
# 下载速度慢的话加上清华镜像源
pip install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple

2.1 从yaml中读取数据

# yaml文件,文件名为test.yml

os: Android
osVersion: 10
account:
  username: xiaoqq
  password: 123456
deviceName: null
appPackage: ~
bool1: True

读取代码:

import yaml
import os

file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
def read_yaml_data():
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.load(f, Loader=yaml.FullLoader)
        print(f'读取的数据:{data}')
        print(f'数据类型为:{type(data)}')

if __name__ == '__main__':
    read_yaml_data()

读取结果:

读取的数据:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
数据类型为:<class 'dict'>

从读取结果可以看出:

1,读取出来的数据不会改变原数据类型,即yaml里是什么数据类型,读出来就是什么类型。

2,Loader=yaml.FullLoader参数不写的话对结果不会有影响,但运行时会出现警告信息。

3,yaml.load(f.read(), Loader=yaml.FullLoader)也可以写成yaml.load(f, Loader=yaml.FullLoader),读取出来的结果相同。

注意:

pyyaml模块在python中用于处理yaml格式数据,主要使用yaml.safe_dump()、yaml.safe_load()函数将python值和yaml格式数据相互转换。当然也存在yaml.dump()、yaml.load()函数,同样能实现数据转换功能,只是官方不太推荐使用。官方给出的解释,因为yaml.safe_dump()、yaml.safe_load() 能够,而且yaml.safe_dump()、yaml.safe_load()比yaml.dump()、yaml.load()安全:

2.2 从yaml中读取多组数据

yaml多组数据时,每组数据之间需要用3横杠分隔’—’,如下:

#test.yml文件
os: Android
osVersion: 10
account:
  username: xiaoqq
  password: 123456
deviceName: null
appPackage: ~
bool1: True

---
os: ios
osVersion: 12
account1:
  username2: Lilei
  password2: 888888

从yaml中读取多组数据时需要使用yaml.load_all()或者yaml.safe_load_all()方法,返回结果为一个生成器,需要使用for循环语句获取每组数据。代码如下:

def read_yaml_data():
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.safe_load_all(f)
        print(f'读取的数据:{data}')
        print(f'数据类型为:{type(data)}')
        for i in data:
            print(i)


if __name__ == '__main__':
    read_yaml_data()

读取结果:

读取的数据:<generator object load_all at 0x102b28258>
数据类型为:<class 'generator'>
{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
{'os': 'ios', 'osVersion': 12, 'account1': {'username2': 'Lilei', 'password2': 888888}}

2.3 单组数据写入yaml文件

使用yaml.dump()或者yaml.safe_dump()方法,加入allow_unicode=True参数防止写入的中文乱码,如下

def write_yaml_data():
    data = {'hash': {'name': 'Steve', 'foo': '公寓'}}

    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'w', encoding='utf-8') as f:
        yaml.safe_dump(data, f, allow_unicode=True)


if __name__ == '__main__':
    write_yaml_data()

执行结果:

2.4 多组数据写入yaml文件

使用yaml.dump_all()或者yaml.safe_dump_all()方法,如下:

def write_yaml_data():
    # 写入多组数据
    data = {'hash': {'name': 'Steve', 'foo': '公寓'}}
    data1 = {'hash1': {'name1': 'admin',  "name": "流动人口社区"}}
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'w', encoding='utf-8') as f:
        yaml.safe_dump_all(documents=[data,data1], stream=f, allow_unicode=True)

if __name__ == '__main__':
    write_yaml_data()

执行结果:

 2.5 ruamel.yaml 读写yaml文件

用yaml模块写入字典嵌套字典这种复杂的数据,会出现大括号{ },不是真正的yaml文件数据,可以用ruamel模块就解决。

参考:https://www.cnblogs.com/yoyoketang/p/9255109.html
安装方法:

pip install ruamel.yaml

2.5.1 用原生的yaml模块写入这种字典嵌套字典的复杂数据

import os
import yaml

# 将字典写入到yaml
desired_caps = {
                'platformName': 'Android',
                'platformVersion': '7.0',
                'deviceName': 'A5RNW18316011440',
                'appPackage': 'com.tencent.mm',
                'appActivity': '.ui.LauncherUI',
                'automationName': 'Uiautomator2',
                'unicodeKeyboard': [True,"hh"],
                'resetKeyboard': True,
                'noReset': True,
                'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'}
                }

curpath = os.path.dirname(os.path.realpath(__file__))
yamlpath = os.path.join(curpath, "caps.yaml")

# 写入到yaml文件
with open(yamlpath, "w", encoding="utf-8") as f:
    yaml.dump(desired_caps, f)

运行结果:

由运转结果,发现字典嵌套的字典,出现了大括号:{androidProcess: 'com.tencent.mm:tools'},(在pyyaml版本为6.0时 已经不存在该问题)这不是真正的yaml数据,不是我们想要的,解决办法看下文

2.5.2 使用ruamel模块 方法跟yaml差不多,只是在使用dump方法多个一个参数:Dumper=yaml.RoundTripDumper

import os
from ruamel import yaml


# 将字典写入到yaml
desired_caps = {
                'platformName': 'Android',
                'platformVersion': '7.0',
                'deviceName': 'A5RNW18316011440',
                'appPackage': 'com.tencent.mm',
                'appActivity': '.ui.LauncherUI',
                'automationName': 'Uiautomator2',
                'unicodeKeyboard': True,
                'resetKeyboard': True,
                'noReset': True,
                'chromeOptions': {'androidProcess': 'com.tencent.mm:tools'}
                }

curpath = os.path.dirname(os.path.realpath(__file__))
yamlpath = os.path.join(curpath, "caps.yaml")

# 写入到yaml文件
with open(yamlpath, "w", encoding="utf-8") as f:
    yaml.dump(desired_caps, f, Dumper=yaml.RoundTripDumper)

执行结果:

 2.6 ruamel.yaml 读yaml文件

2.6.1.使用ruamel.yaml模块也能读yaml文件,使用方法相对于之前的yaml.load方法多加一个参数:Loader=yaml.Loader

def read_yaml_data():
    # 读取单组数据
    file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'test.yml'))
    with open(file_path, 'r', encoding='utf-8') as f:
        data = yaml.load(f, Loader=yaml.Loader)
        print(f'读取的数据:{data}')
        print(f'数据类型为:{type(data)}')
      
if __name__ == '__main__':
    read_yaml_data()

读取结果:

读取的数据:{'os': 'Android', 'osVersion': 10, 'account': {'username': 'xiaoqq', 'password': 123456}, 'deviceName': None, 'appPackage': None, 'bool1': True}
数据类型为:<class 'dict'>

三:使用template替换yaml文件中的变量

在接口自动化测试的时候,yaml 文件一般放测试的数据或当配置文件使用,yaml 文件存放静态的数据是没问题的,python的数据类型基本上都是支持的。
有时候我们想在 yaml 文件中引用变量来读取 python 代码的设置值

3.1 template 使用

template 是字符串模板,用于替换字符串中的变量,是 string 的一个类引用变量有 2 种格式

  • $variable 使用 $变量名 引用变量
  • ${variable} 使用 ${变量名} 大括号包起来

第一种 $variable

from string import Template

tempTem = Template("My name is $name , i like $fancy")
d = {'name': 'admin', 'fancy': 'python'}

print(tempTem.substitute(d))

执行结果:

My name is admin, i like python

第二种 ${variable}

from string import Template

tempTemp1 = Template("My name is ${name} , i like ${fancy}")
d = {'name': 'admin', 'fancy': 'python'}
print(tempTemp1.substitute(d))

执行结果:

My name is admin, i like python

3.2 safe_substitute使用

上面的方式只能严格的匹配变量,当字符串中有$符号,不想匹配变量的时候,会报错

from string import Template

tempTemplate = Template("$My name is ${name} , i like ${fancy}")
d = {'name': 'admin', 'fancy': 'python'}
print(tempTemplate.substitute(d))

这段,$符号加在My的前面,我只想让它是一个普通的字符串,不想引用变量,就出现了报错说找不到这个key

Traceback (most recent call last):
  File "E:/PycharmScripts/test_selenium_wire_yaml/test_yaml.py", line 25, in <module>
    print(tempTemp2.substitute(d))
  File "C:\Python37\lib\string.py", line 132, in substitute
    return self.pattern.sub(convert, self.template)
  File "C:\Python37\lib\string.py", line 125, in convert
    return str(mapping[named])
KeyError: 'My'

虽然字符串定义了多个变量,但是引用的时候只给了name这个值,也不影响运行,没给值的当普通字符串出来,这样就很完美了

tempTemplate = Template("$My name is ${name} , i like ${fancy}")
d = {'name': 'admin','fancy': 'python'}
print(tempTemplate.safe_substitute(d))

执行结果;

$My name is admin , i like python

3.3 yaml 文件引用变量

通过前面 Template 的基础使用,已经掌握了基本的用法了,接下来在 yaml 文件中引用变量

request:
    headers:
         Content-Type: application/json
         User-Agent: python-requests/2.18.4
     json:
         username: $user
         password: $psw

python读yaml文件代码:

from string import Template
import yaml
with open("test.yml", encoding='utf-8') as fp:
    read_yml_str = fp.read()
    # print(read_yml_str)

    tempTemplate1 = Template(read_yml_str)
    new_yaml = tempTemplate1.safe_substitute({"user": "admin", "psw": "123456 "})
    print(f'Template补全的数据:\n{new_yaml}')

# yml 文件数据,转 python 类型
yaml_data = yaml.safe_load(new_yaml)
print(f'yml文件转python的数据:\n{yaml_data}')

执行结果:

'''
Template补全的数据:
request:
    headers:
         Content-Type: application/json
         User-Agent: python-requests/2.18.4
    json:
         username: admin
         password: 123456 

yml文件转python的数据:
{
  'request': {'headers': {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4'},
  'json': {'username': 'admin', 'password': 123456}}
}
'''

四:yaml文件调用python外部函数

如何在 yaml文件中引用一个 python 的函数?

问题分析
其实yaml 和 json 文件本质上是一样的,都是静态的文件,是不能直接引用 python 的函数。
那这时候就有人问到了,那为什么 httprunner 框架可以在yaml文件中引用函数呢?

这是因为 httprunner 框架封装过对 yaml 文件的读取了,它是先读取文件内容,正则提取到 ${} 括号里面的函数内容,再把函数的值替换过去
那么我们能不能实现这种效果呢?

当然是可以的,可以参考httprunner的实现,也可以用到 python 的模板 jinja2 来实现。
使用模板可以编写出可读性更好,更容易理解和维护的代码,并且使用范围非常广泛,因此怎么使用模板主要取决于我们的想象力和创造力。
python的模板库jinja2 功能是非常强大的

4.1 jinja2 模板库

pip install jinja2

4.2 render 函数实现

在yaml文件中,通过 {{ 函数名称() }} 来引用函数

写个 render 函数读取 yaml 文件内容

import jinja2
import os,random
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 创建一个包加载器对象
    template = env.get_template(filename) # 获取一个模板文件
    return template.render(**kwargs) # 进行模板渲染

读取到的yaml文件本质上都是字符串来读取的,通过jinja2 模板来读取,会先把函数的值替换进去。最后再转成python的dict结构

import jinja2
import os,random,yaml
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 创建一个包加载器对象
    template = env.get_template(filename) # 获取一个模板文件
    return template.render(**kwargs) # 进行模板渲染

# yaml 文件调用以下函数
def rand_str():
    return str(random.randint(1000000, 2000000))

if __name__ == '__main__':
    r = jinja2_template_render('test.yml',**{"user":"admin","pwd":1234,"rand_str":rand_str})
    print(f'jinja2渲染后的数据\n{r}')
    print(yaml.safe_load(r))

执行的结果:

'''
jinja2渲染后的数据:
username: admin
password: 1234
func: 1980646

{'username': 'admin', 'password': 1234, 'func': 1980646}
'''

上面读取函数是写死的,我们希望能自动加载类似于debugtalk.py的文件来自动加载函数

4.3 自动加载debug.py里面的函数

写一个debug.py 文件,实现 yaml 文件里面定义的函数去替换值

#debug.py文件

import random

# yaml 文件调用以下函数
def rand_str():
    return str(random.randint(1000000, 2000000))

在run.py里面定义一个函数自动读取debug.py里面的函数,生成dict 键值对格式

关键动态导入对象请参考:https://blog.csdn.net/edward_zcl/article/details/88809212

def all_functions():
    """加载debug.py模块"""
    debug_module = importlib.import_module("debug")
    all_function = inspect.getmembers(debug_module, inspect.isfunction)
    print(dict(all_function))
    return dict(all_function)

函数返回 {'rand_str': <function rand_str at 0x0000000002E3F798>}

完整的run.py文件内容

import jinja2
import os,random,yaml
import importlib,inspect
def jinja2_template_render(tpl_path, **kwargs):
    path, filename = os.path.split(tpl_path)
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(path or './')) # 创建一个包加载器对象
    template = env.get_template(filename) # 获取一个模板文件
    return template.render(**kwargs) # 进行模板渲染


def all_functions():
    """加载debug.py模块"""
    debug_module = importlib.import_module("debug")
    all_function = inspect.getmembers(debug_module, inspect.isfunction)
    print(dict(all_function))
    return dict(all_function)



if __name__ == '__main__':
    temlplate_params = all_functions() #获取调用函数
    temlplate_params["user"] = "admin"  #获取user变量值
    temlplate_params["pwd"] = "1234" #获取pwd变量值
    r = jinja2_template_render('test.yml',**temlplate_params)
    print(f'jinja2渲染后的数据\n{r}')
    print(yaml.safe_load(r))

执行结果:

'''
#all_functions()返回值
{'rand_str': <function rand_str at 0x0000000002E3F708>}

jinja2渲染后的数据
username: admin
password: 1234
func: 1730704

{'username': 'admin', 'password': 1234, 'func': 1730704}

'''

 

posted @ 2022-04-08 22:28  浩浩学习  阅读(2739)  评论(0编辑  收藏  举报