[automation operation and maintenance novice village] Python foundation-5

Posted by ch1326 on Wed, 05 Jan 2022 22:38:04 +0100

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:

  1. 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.

  1. 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)
  1. 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.

Topics: Python Operation & Maintenance