abstract
First of all, the following types of readers should take their seats by themselves:
- Readers who know a lot about CMDB but are not familiar with Python are strongly recommended to read the previous articles;
- Readers who know little about Python and can only write simple scripts are strongly recommended to read this article;
- Readers who are proficient in writing Python scripts but do not know much about CMDB are recommended to read this article;
- Readers who know both Python and CMDB can go out and turn left to read the next article.
In the previous sections, we completed cmdbv1 The most difficult part of version 0. In this section, we will lead you to analyze the deletion and query functions at one time. Don't talk much, say dry goods.
Loading dry goods
Code optimization
In our previous functions of adding and updating information, we used to check and parse attrs. Can we abstract it into a new function, as follows:
def check_parse(attrs): if attrs is None: # Judging the legitimacy of 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 ...
Congratulations on our success in reducing a few lines of code
Delete asset information
In this section, we will omit some steps of the five steps and only think about the most critical functions
- In any scenario, once the deletion function is involved, we need to be cautious. We must not delete more or mistakenly, otherwise we may have to go back to the pot. What should we pay attention to when deleting asset information? In fact, sometimes we try to use updates to replace deletion during insurance, but some redundant attribute information has to be deleted, If the path we delete is a string or a number, it is relatively simple. If it is a dictionary or an array, we need to pay special attention.
- The other is whether we need to pass in both path and attrs for our parameters.
The source code is as follows:
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))
- Here, the first thing is still to parse the passed in attribute values. Why don't we reuse check like add and update_ The parse () method exits the function when the parsed attrs is None. This is because our deletion function does not need to pass the attrs parameter. Sometimes our purpose is to directly delete all attributes under this path in the data source, so we only need to pass in the path.
- We also optimized the specified path, as follows:
for idx, seg in enumerate(path_seg): if seg not in target_path: print("delete target path not in data.") return ...
It can be compared with the previous code for locating the path:
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 ...
We previously segmented the path when locating the path, only when seg is path_ When the last element of seg, judge whether the seg is in the target_path, which will cause the program to run a lot of useless loop logic.
After optimization, we judge seg at the beginning of each cycle, because if the divided path_ When any seg in seg is not in the data source path, the whole path cannot be located in the data source. Therefore, once we detect that the current seg is not in the target_path, you can exit the function directly
- The core code blocks in the delete function are as follows:
if idx == len(path_seg)-1: # Navigate to the specified path in the loop 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
Deleting attributes is mainly divided into three parts:
- When we do not pass in the attrs to be deleted, we will delete all contents under the path by default. The operation used here is the dictionary deletion function dict.pop(). This method requires that we pass in a dictionary key value. If the key value does not exist, an exception will be thrown, but we judge whether the seg is in the target during each cycle_ Path, so if the program runs here, the path must exist. Then we pass through target_path.pop(seg) can delete all the attributes under the path
if not attrs: target_path.pop(seg)
Tips: Security
In fact, considering the security of data, we should make a judgment when deleting all the attributes of the specified path, because if we forget to enter attrs and cause false deletion, it may directly be a P1. Therefore, we can pass attrs into an all or similar flag here to indicate that we are sure to delete all the attributes under the specified path.
- When there is a dictionary under our specified path and the attribute attrs passed in is an array, we will traverse attrs and remove its elements from target at one time_ Path. Note that we mentioned above that dict.pop() must pass in the key existing in the dictionary. Therefore, when looping attrs, we need to judge whether the element to be deleted exists. If it does not exist, we use continue to skip
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)
- When there is an array under the specified path and the passed in attribute attrs is also an array, we still remove the elements in attrs from the array under the specified path by traversing attrs, and delete the elements from the array using the method list Remove(), this method also requires the existing elements in the array to be passed in. If the passed elements do not exist, an exception will be thrown.
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)
Query asset information
Finally, the last method of adding, deleting, modifying and querying is reached. In fact, searching is the simplest of the four methods. You only need to locate the specified path and output it. The code is as follows:
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))
I don't know if readers feel that this code is familiar, and whether it touches your idea of refactoring the previous code.
Complete Refactoring:
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: # Judging the legitimacy of 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")
After our unremitting efforts, we finally finished reading cmdbv1 line by line 0.py source code, understand the detailed logic of adding, deleting, modifying and querying asset information, and gradually cultivate good programming norms and programming thinking in the process of reading the source code, which will play a vital role for everyone. So we haven't finished yet. In the next section, we will talk about cmdbv1 0 is reconstructed into CMDBv1.0 again using the idea of object-oriented 5. It will be a great leap from functional programming to object-oriented programming. Please look forward to it.
Welcome to add my personal official account [Python to play automation operation and maintenance] to join the reader exchange group to get more dry cargo content.