Forum Discussion

Jeff_W_'s avatar
2 years ago

Python SDK and creating multiple customProperties in one go

I’m trying to be able to supply a script to add some CustomProperties and I was wondering so that I don’t have to reinvent the wheel, if anyone has a quick method to building what is required to set in the BODY of the post.

Let’s say I have some different custom properties I want to add:

corp.building = “Some Building Name”

corp.department = “Some Department Name”

corp.role = “Some Role”

Can I wrap all these up into a single array/dictionary to pass into the “customProperties” attribute in the “body” of the post?
 

The idea could be I feed a CSV file into the script and the amount of customProperties can be 1 or 10 for any given line being processed.

Thanks and let me know if I need to shed more light on what I’m expecting to happen.

  • Anonymous's avatar
    Anonymous

    You’d have a couple different things going on. I suggest trying to break down your code into separate functions.

    1. Bring the data into a dictionary from your input CSV - several examples on the internet. Reply here if your google-fu turns up nothing useful
    2. Convert the dictionary to a list of dictionaries - example below
    3. POST/PUT/PATCH the object in LM using the SDK (I only mention that because it’s in the title of this post) - Documentation here.

    Example to translate properties into something that can be patched in:

    myDictionary = {"corp.building":"Some Building Name","corp.department":"Some Department Name","corp.role":"Some Role"}
    myListofDicts = [{"name":k,"value":v} for k,v in myDictionary.items()]
    pprint(myListofDicts)
    [{'name': 'corp.building', 'value': 'Some Building Name'},
    {'name': 'corp.department', 'value': 'Some Department Name'},
    {'name': 'corp.role', 'value': 'Some Role'}]
  • Anonymous's avatar
    Anonymous

    It’s not actually a Python problem It’s an LM problem. LM is the one that decided to use a list of dictionaries instead of a dictionary.

  • Thanks Stuart. One of the libraries I was using is “argparse” from the CLI.

    My test script right now looks something like:
     

    ./script.py --primary ”Folder Name” --location ”123 somewhere ln, here, there 11111” --custom “building:Some Building” --custom “department:Some Department” --custom “role:Some Role”

    When I push via the SDK to the api.add_device_group() function, I would basically take the --location and all the --custom ones set via the CLI and “build” the customProperties that I want to POST in the body of the api call (along with some more common , one off variables I scrape).

    I’ll mess with the listofDicts as you illustrated and go from there. 

    Thanks for your help.

  • Anonymous's avatar
    Anonymous

    Yep, I do something very similar, except that I’ve started using environment variables for the credentials (see input_method below). This function should facilitate creating your groups. Actually, it’s idempotent, so if you call it once, it’ll create the group. The second time you call it, it should just update the existing one:

    import logicmonitor_sdk, argparse, os
    from datetime import datetime

    input_method = "envs"

    if input_method == "args":
    parser = argparse.ArgumentParser()
    for arg,help in {
    "id":"LM API Access ID",
    "key": "LM API Access Key",
    "company": "LM API Company",
    }.items():
    parser.add_argument(arg, help=help)
    args = parser.parse_args()

    lm_creds = {
    "AccessId": args.id,
    "AccessKey": args.key,
    "Company": args.company
    }
    elif input_method == "envs":
    lm_creds = {
    "AccessId": os.environ['accessid'],
    "AccessKey": os.environ['accesskey'],
    "Company": os.environ['company']
    }
    else:
    print(f"Invalid input method chosen: {input_method}")
    quit()

    configuration = logicmonitor_sdk.Configuration()
    configuration.access_id = lm_creds['AccessId']
    configuration.access_key = lm_creds['AccessKey']
    configuration.company = lm_creds['Company']
    lm = logicmonitor_sdk.LMApi(logicmonitor_sdk.ApiClient(configuration))

    debug = False
    info = False

    def log_msg(msg, severity="INFO", end="\n"):
    if (severity == "DEBUG" and debug) or (severity == "INFO" and info) or severity not in ("DEBUG", "INFO"):
    print(f"{datetime.now().strftime('[%Y-%m-%d %H:%M:%S]')} {severity}: {msg}", end=end)

    def create_group(name,parent,customProperties={},appliesTo=""): # shortcut function to the sdk that creates device groups with less fuss
    parsedProperties = [{"name": k, "value": v} for k,v in customProperties.items()]
    device_groups = []
    end_found = False
    offset = 0
    size = 1000
    while not end_found:
    current = lm.get_device_group_list(size=size,offset=offset,filter=f"parentId:\"{parent}\",name:\"{name}\"").items
    device_groups += current
    offset += len(current)
    end_found = len(current) != size
    if len(device_groups) == 0:
    try:
    result = lm.add_device_group({"name":name, "parentId":parent, "customProperties":parsedProperties, "appliesTo":appliesTo})
    log_msg(f" Created resource group {result.full_path} ({result.id})", "API SDK POST")
    return result
    except lm.ApiException as e:
    log_msg("Exception when calling LMApi->addDeviceGroup: %s\n" % e)
    else:
    existing_group = device_groups[0]
    mismatches = 0
    log_msg(f" Group {name} exists in LM ({existing_group.id}), checking if attributes match...", "DEBUG")
    existing_properties = {x.name:x.value for x in existing_group.custom_properties}
    for k,v in customProperties.items(): #check each of the custom attributes we want to pass in to see if they match
    if k in existing_properties.keys() and v==existing_properties.get(k,None):
    log_msg(f" Property {k} matches attribute already in LM", "DEBUG")
    else:
    log_msg(f" Property {k} doesn't match attribute already in LM")
    mismatches += 1
    log_msg(f" {'='*80}", "DEBUG")
    log_msg(f" {k}: {v}", "DEBUG")
    log_msg(f" {k}: {existing_properties.get(k)}", "DEBUG")
    log_msg(f" {'='*80}", "DEBUG")
    if len(appliesTo) > 0:
    if appliesTo == existing_group.applies_to:
    log_msg(f" AppliesTo we want to patch in matches the AppliesTo already on the device", "DEBUG")
    else:
    log_msg(f" AppliesTo we want to patch in doesn't match the AppliesTo already on the device")
    mismatches += 1
    log_msg(f" {'='*80}", "DEBUG")
    formatted_appliesTo = appliesTo.replace('\n','\n ')
    log_msg(f" {formatted_appliesTo}", "DEBUG")
    log_msg(f" {'='*80}", "DEBUG")
    formatted_appliesTo = existing_group.applies_to.replace('\n','\n ')
    log_msg(f" {formatted_appliesTo}", "DEBUG")
    log_msg(f" {'='*80}", "DEBUG")
    else:
    log_msg(f" There is no AppliesTo that we want to patch in.", "DEBUG")
    if mismatches > 0:
    log_msg(f" Number of attribute mismatches: {mismatches}")
    existing_group.applies_to = appliesTo
    existing_properties.update(customProperties)
    existing_group.custom_properties = [{"name":k,"value":v} for k,v in existing_properties.items()]
    result = lm.patch_device_group_by_id(id=existing_group.id, body=existing_group) # , op_type="replace")
    log_msg(f" Patched device group {existing_group.name}", "API SDK PATCH")
    else:
    log_msg(f" {existing_group.full_path} already exists in LM with all the correct properties, moving on...", "SUMMARY")
    result = existing_group
    log_msg(f" {result.id}: {result.full_path}: {result.custom_properties}", "DEBUG")
    return result

    Example usage:

    >>> response = create_group("SDK Test",812,{"prop1":"1","prop2":"2"})
    [2023-02-21 16:30:11] API SDK POST: Created resource group Customers/Longhorn Smokers/SDK Test (854)
    >>> response = create_group("SDK Test",812,{"prop1":"1","prop2":"2"})
    [2023-02-21 16:33:06] SUMMARY: Customers/Longhorn Smokers/SDK Test already exists in LM with all the correct properties, moving on...
    >>> response = create_group("SDK Test",812,{"prop1":"1","prop2":"2"})
    [2023-02-21 16:33:43] API SDK PATCH: Patched device group SDK Test
    >>> debug = True
    >>> info = True
    >>> response = create_group("SDK Test",812,{"prop1":"1","prop2":"2"})
    [2023-02-21 16:35:57] DEBUG: Group SDK Test exists in LM (854), checking if attributes match...
    [2023-02-21 16:35:57] DEBUG: Property prop1 matches attribute already in LM
    [2023-02-21 16:35:57] DEBUG: ================================================================================
    [2023-02-21 16:35:57] DEBUG: prop1: 1
    [2023-02-21 16:35:57] DEBUG: prop1: 1
    [2023-02-21 16:35:57] DEBUG: ================================================================================
    [2023-02-21 16:35:57] DEBUG: Property prop2 matches attribute already in LM
    [2023-02-21 16:35:57] DEBUG: ================================================================================
    [2023-02-21 16:35:57] DEBUG: prop2: 2
    [2023-02-21 16:35:57] DEBUG: prop2: 2
    [2023-02-21 16:35:57] DEBUG: ================================================================================
    [2023-02-21 16:35:57] DEBUG: There is no AppliesTo that we want to patch in.
    [2023-02-21 16:35:57] SUMMARY: Customers/Longhorn Smokers/SDK Test already exists in LM with all the correct properties, moving on...
    [2023-02-21 16:35:57] DEBUG: 854: Customers/Longhorn Smokers/SDK Test: [{'name': 'prop2', 'value': '2'}, {'name': 'prop1', 'value': '1'}]
    >>>
  • Anonymous's avatar
    Anonymous

    FWIW, I wouldn’t use argparse to pull in the csv data. I’d actually use the built in CSV handling capabilities of Python:

    def read_csv(filename):
    import csv
    result = []
    with open(filename, mode='r') as csv_file:
    csv_reader = csv.DictReader(csv_file)
    result = [
    {"name":row["name"], "department":row["department"], "bday":row["birthday month"]}
    for row
    in csv_reader
    ]
    return result

    Given the following CSV file called “example.csv” in the current working directory:

    name,department,birthday month
    John Smith,Accounting,November
    Erica Meyers,IT,March

    The usage would look like this:

    >>> from pprint import pprint
    >>> def read_csv(filename):
    ... import csv
    ... result = []
    ... with open(filename, mode='r') as csv_file:
    ... csv_reader = csv.DictReader(csv_file)
    ... result = [
    ... {"name":row["name"], "department":row["department"], "bday":row["birthday month"]}
    ... for row
    ... in csv_reader
    ... ]
    ... return result
    ...
    >>> data = read_csv("example.csv")
    >>> pprint(data)
    [{'bday': 'November', 'department': 'Accounting', 'name': 'John Smith'},
    {'bday': 'March', 'department': 'IT', 'name': 'Erica Meyers'}]
    >>>
  • This should be helpful for me. Once I set up my customProps as a list of dictionaries it worked like a charm (this is where my python lacks, coming more from a PHP background). I was close, just slightly off ;) 

    Thanks for the tips and snippets.