摘要
首先说明,以下几类读者请自行对号入座:
- 对CMDB很了解但对于Python还没有上手的读者,强烈建议阅读前面几篇;
- 对Python了解较少只能写出简单脚本的读者,强烈建议阅读此篇;
- 已经可以熟练写出Python脚本,但对CMDB不是很了解的读者,建议阅读此篇;
- 即了解Python,又了解CMDB的读者,可以出门左转,看下一篇。
前面几节我们完成了CMDBv1.0版本最难的部分的讲解,这节内容我们就带领大家一次将删除和查询功能分析完成。话不多说上干货。
上干货
代码优化
之前我们的新增和更新信息的功能中都有对attrs
做校验和解析,那么我们是不是可以将其抽象成一个新的函数,如下:
def check_parse(attrs):
if attrs is None: # 判断attrs的合法性
print("attributes is None")
return
try:
attrs = json.loads(attrs)
return attrs
except Exception:
print("attributes is not valid json string")
return
def add(path, attrs=None):
attrs = check_parse(attrs)
if not attrs:
return
...
def update(path, attrs=None):
attrs = check_parse(attrs)
if not attrs:
return
...
恭喜我们的代码又成功减少几行
删除资产信息
这一节我们就省略五步法的一些步骤,只对最关键的功能进行一下思考
- 在任何场景中一旦涉及到删除功能,就需要慎之又慎,绝不能多删误删,不然可能就要背锅走人了,那么我们在删除资产信息时需要注意什么呢,其实有时候为了保险期间我们会尽量使用更新去代替删除,但有一些多余的属性信息又不得不删除,那么如果我们删除的路径上是一个字符串或者数字还比较简单,如果是一个字典,或者是一个数组,就需要格外注意了。
- 另外就是对于我们的参数,我们是否需要同时传入
path
和attrs
。
源代码如下:
def delete(path, attrs=None):
attrs = check_parse(attrs)
path_seg = path.split("/")[1:]
data = read_file()
target_path = data
for idx, seg in enumerate(path_seg):
if seg not in target_path:
print("delete target path not in data.")
return
if idx == len(path_seg)-1:
if not attrs:
target_path.pop(seg)
break
if isinstance(attrs, list):
for attr in attrs:
if attr not in target_path[seg]:
print("attr %s not in target_path" % attr)
continue
isinstance(target_path[seg], dict):
target_path[seg].pop(attr)
if isinstance(target_path[seg], list)
target_path[seg].remove(attr)
break
target_path = target_path[seg]
write_file(data)
print(json.dumps(data, indent=2))
- 这里首先仍然是对传入的属性值做解析,我们为什么不像
add
和update
一样复用check_parse()
方法,当解析到的attrs
为None
时就退出函数呢,这里是因为我们的删除功能,可以不传attrs
参数,有时候我们的目的就是直接删除数据源中的这个路径下的所有属性,那么就只需要传入path
即可。 - 在查找指定路径的时候我们同样也做了优化,如下:
for idx, seg in enumerate(path_seg):
if seg not in target_path:
print("delete target path not in data.")
return
...
可以和之前定位路径的代码做一下对比:
for idx, seg in enumerate(path_seg):
if idx == len(path_seg)-1:
if seg not in target_path:
print("delete path is not exists in datan")
return
...
我们之前在定位路径时,对path
做了分割,只有在seg
为path_seg
的最后一个元素时才去判断是否这个seg
在target_path
上,这样就会导致程序运行很多无用的循环逻辑。
优化之后我们在每次循环的一开始就对seg
做了判断,因为如果被分割开的path_seg
中任何一段seg
不在数据源路径中时,那么整段path
就必然不可能在数据源中定位到,所以我们一旦检测到当前的seg
不在target_path
时就可以直接退出函数
- 删除功能中的核心代码块如下:
if idx == len(path_seg)-1: # 循环中定位到指定路径
if not attrs:
target_path.pop(seg)
if isinstance(attrs, list):
for attr in attrs:
if attr not in target_path[seg]:
print("attr %s not in target_path" % attr)
continue
if isinstance(target_path[seg], dict):
target_path[seg].pop(attr)
if isinstance(target_path[seg], list):
target_path[seg].remove(attr)
break
删除属性主要分为三个部分:
- 当我们没有传入要删除的
attrs
时,我们默认删除该路径下的所有内容,这里用到的操作是字典的删除功能dict.pop()
,这个方法要求传入一个字典的键值,键值如果不存在会抛出异常,但由于我们在每次循环时都判断了seg
是否在target_path
中,所以程序运行到这里的话,这个路径就必然是存在的,那么我们通过target_path.pop(seg)
就可以将该路径下面的属性全部删除
if not attrs:
target_path.pop(seg)
Tips: 安全性
其实我们考虑到数据的安全性,应该在删除指定路径的全部属性时做一个判断,因为如果是忘记了输入
attrs
而造成了误删,那可能直接就一个P1了,所以我们可以这里将attrs
传入一个all
或者类似的标志,来表示确定删除指定路径下的全部属性。
- 当我们的指定路径下是一个字典并且传入的属性
attrs
是一个数组的时候,我们就去遍历attrs
,将其元素一次从target_path
下删除,这里有注意点就是我们在上面已经提到,dict.pop()
必须传入字典中存在的键,所以我们在循环attrs
时,需要先判断这个要删除的元素是否存在,如果不存在则使用continue
跳过
if isinstance(attrs, list):
for attr in attrs:
if attr not in target_path[seg]:
print("attr %s not in target_path" % attr)
continue
if isinstance(target_path[seg], dict):
target_path[seg].pop(attr)
- 当我们的指定路径下是一个数组,并且传入的属性
attrs
也是一个数组的时候,我们仍然通过遍历attrs
的方式,将attrs
中的元素依次从指定路径的数组下面移除,从数组中删除元素使用到了方法list.remove()
,这个方法同样要求传入数组中已存在的元素,如果传入的元素不存在则会抛出异常。
if isinstance(attrs, list):
for attr in attrs:
if attr not in target_path[seg]:
print("attr %s not in target_path" % attr)
continue
if isinstance(target_path[seg], dict):
target_path[seg].remove(attr)
查询资产信息
终于到了增删改查的最后一个方法,其实查找是这四个方法中最为简单的,只需要定位到指定路径然后输出就好了,代码如下:
def get(path):
path_seg = path.split("/")[1:]
data = read_file()
target_path = data
for idx, seg in enumerate(path_seg):
if seg not in target_path:
print("get path is not exists in data")
return
if idx == len(path_seg)-1:
break
target_path = target_path[seg]
print(json.dumps(target_path, indent=2))
不知道读者朋友们有没有觉得这段代码很眼熟,有没有触动你想要重构之前代码的想法。
完整重构:
import json
from os import read
import sys
from typing import Iterable
def read_file():
with open("data.json", "r+") as f:
data = json.load(f)
return data
def write_file(data):
with open("data.json", "w+") as f:
json.dump(data, f, indent=2)
def check_parse(attrs):
if attrs is None: # 判断attrs的合法性
print("attributes is None")
return
try:
attrs = json.loads(attrs)
return attrs
except Exception:
print("attributes is not valid json string")
return
def locate_path(data, path):
target_path = data
path_seg = path.split("/")[1:]
for seg in path_seg[:-1]:
if seg not in target_path:
print("update path is not exists in data, please use add function")
return
target_path = target_path[seg]
return target_path, path_seg[-1]
def init(region):
with open("data.json", "r+") as f:
data = json.load(f)
if region in data:
print("region %s already exists" % region)
return
data[region] = {"idc": region, "switch": {}, "router": {}}
with open("data.json", "w+") as f:
json.dump(data, f, indent=2)
print(json.dumps(data, indent=2))
def add(path, attrs=None):
attrs = check_parse(attrs)
if not attrs:
return
with open("data.json", "r+") as f:
data = json.load(f)
target_path, last_seg = locate_path(data, path)
if last_seg in target_path:
print("%s already exists in %s, please use update operation" % (last_seg, path))
return
target_path[last_seg] = attrs
with open("data.json", "w+") as f:
data = json.dump(data, f, indent=2)
print(json.dumps(data, indent=2))
def update(path, attrs):
attrs = check_parse(attrs)
if not attrs:
return
data = read_file()
target_path, last_seg = locate_path(data, path)
if type(attrs) != type(target_path[last_seg]):
print("update attributes and target_path attributes are different type.")
return
if isinstance(attrs, dict):
target_path[last_seg].update(attrs)
elif isinstance(attrs, list):
target_path[last_seg].extend(attrs)
target_path[last_seg] = list(set(target_path[last_seg]))
else:
target_path[last_seg] = attrs
write_file(data)
print(json.dumps(data, indent=2))
def delete(path, attrs=None):
attrs = check_parse(attrs)
data = read_file()
target_path, last_seg = locate_path(data, path)
if not attrs:
target_path.pop(last_seg)
if isinstance(attrs, list):
for attr in attrs:
if attr not in target_path[last_seg]:
print("attr %s not in target_path" % attr)
continue
if isinstance(target_path[last_seg], dict):
target_path[last_seg].pop(attr)
if isinstance(target_path[last_seg], list):
target_path[last_seg].remove(attr)
write_file(data)
print(json.dumps(data, indent=2))
def get(path):
data = read_file()
target_path, last_seg = locate_path(data, path)
print(json.dumps(target_path[last_seg], indent=2))
if __name__ == "__main__":
operations = ["get", "update", "delete"]
args = sys.argv
if len(args) < 3:
print("please input operation and args")
else:
if args[1] == "init":
init(args[2])
elif args[1] == "add":
add(*args[2:])
elif args[1] == "get":
get(args[2])
elif args[1] == "update":
update(*args[2:])
elif args[1] == "delete":
delete(*args[2:])
else:
print("operation must be one of get,update,delete")
经过我们一起不懈的努力,终于一行一行的读完了CMDBv1.0.py的源代码,理解了对资产信息增删改查的详细逻辑,并且在阅读源码的过程中逐步培养起良好的编程规范和编程思维,这对于大家以会起到至关重要的作用。那么我们到此还没有结束,下一节我们会将CMDBv1.0利用面向对象的思想再次重构为CMDBv1.5,到时候将会是从函数式编程到面向对象编程的一个大的飞跃,敬请期待。