0

I feel like this is a very simple problem and there are a lot of very similar questions to it here, but I still can't figure out how to get what I want. I am working with remote devices that I can connect to when they are online. I want to have a file that records the most recent uptime of a device, like this:

# device ID ......... last seen online
{'deviceID1':'Wed Nov 08 2017 06:11:27 PM',
'deviceID2':'Wed Nov 08 2017 06:11:27 PM',
'deviceID3':'Tues Nov 07 2017 03:47:01 PM'}

etc. I've gotten really close by storing this in a json file and doing json.dumps to store the data and json.load to view it. My code follows the steps: 'ping all device IDs', 'check output', and 'write result to file'. But every time I do this, the values get overwritten for devices that were online and are now not online. As in, instead of the above, I get something like:

# device ID ......... last seen online
{'deviceID1':'Wed Nov 08 2017 06:11:27 PM',
'deviceID2':'Wed Nov 08 2017 06:11:27 PM',
'deviceID3':''}

when I check at Wed Nov 08 2017 06:11:27 PM and deviceID3 is not online. But I want to preserve that value while updating the values of the devices I do see online. How can I essentially keep this dictionary / data in a file and update it for the same set of unique device IDs every time? This question gets the closest, but that's about appending entries, and I want to update the values of keys that are already there. Thanks.

Relevant code:

def write_to_file(data):
    with open(STATUS_FILE, 'w') as file:
        file.write(json.dumps(data))

def create_device_dictionary(deviceIDs):
    devices = {}
    for i in range(0, len(deviceIDs)):
        devices[deviceIDs[i]] = []
    return devices

def check_ping_output(cmd_output_lines,devices):

    for i, line in enumerate(cmd_output_lines):
        device_id = line.strip().strip(':')
        # if the device pinged back...
        if 'did not return' not in cmd_output_lines[i+1]:
            #current UNIX time
            human_readable_time = time.strftime(
                '%a %b %d %Y %I:%M:%S %p',
                time.gmtime(time.time())
            )
            devices[device_id].append(human_readable_time)
        else:
        #    do something here? I want the device ID to be in the file even if it's never appeared online
             pass

    return devices
mercer721
  • 103
  • 1
  • 9
  • To be honest I did not read a single word. Put some code there. I would be reading a book if it was for reading. – Elis Byberi Nov 08 '17 at 18:39
  • Well you are always overwriting _**all**_ the values, regardless of whether the device is online. Filter out the list of devices to only update the ones that are currently online before dumping the json. Post your code so far and we can help debug it. – Bahrom Nov 08 '17 at 18:42
  • @mercer721 `write_to_file_data` and `create_device_dictionary` functions are probably not necessary in your question, but can you also give us a sample `cmd_output_lines`? – Bahrom Nov 08 '17 at 18:53
  • @Bahrom, sure. The output for 2 devices, one that returns and one that doesn't, would be: `deviceIDa: True deviceIDb: Minion did not return. [No response]` – mercer721 Nov 08 '17 at 18:57
  • Continuing this idea, the next thing I'd be curious about would be having multiple values for the devices that are online - a dictionary that, say, stores the 5 most recent times the device has been seen online to see if it's dropping off for a while during attempted 'pings' or responding to each one. this would involve *not* overwriting the values of the online devices but rather appending to their current values. So it would look like `{'device1':'second_most_recent_time',most_recent_time';'device2':'';'device3:'second_most_recent_time',most_recent_time'}` etc. Thoughts? – mercer721 Nov 08 '17 at 19:52
  • @mercer721 instead of mapping to a single item, you can start mapping to a list of items. You'd need to merge the two lists after (a simple `existing_devices.update(online_devices)` wouldn't work). You could just add the two lists together (I'll add another small example for this case). – Bahrom Nov 08 '17 at 20:07

3 Answers3

1

Here's a basic example I came up with (removing the snippets parts that you have already addressed, such as time conversion, etc)

import json

# Suppose this is your existing json file (I'm keeping it as a string for the sake of the example):
devices_str = '{"deviceIDa":"Wed Nov 08 2017 06:11:27 PM", "deviceIDb":"Wed Nov 08 2017 06:11:27 PM", "deviceIDc":"Tues Nov 07 2017 03:47:01 PM"}'

cmd_output_lines = [
    'deviceIDa:True',
    'deviceIDb:Minion did not return. [No response]',
]
# Generate a dictionary of the devices for current update
devices = dict(
    line.strip().split(':') for line in cmd_output_lines
)
# Filter out the ones currently online, using whatever criteria needed
# In my example, I'm just checking if the response contained a True
online_devices = dict(
    (device_id, resp) for (device_id, resp) in devices.iteritems() if 'True' in resp
#               ^ of course in your case that resp would be current/last seen time
)

# Load existing entries
existing_devices = json.loads(devices_str)

# Update them only overwriting online devices
existing_devices.update(online_devices)

# Final result
print json.dumps(existing_devices)

This outputs:

"deviceIDb": "Wed Nov 08 2017 06:11:27 PM", "deviceIDc": "Tues Nov 07 2017 03:47:01 PM", "deviceIDa": "True"}

As you can see, deviceIDa is the only entry that got updated (deviceIDb still has the last seen from the existing entries)

Edit:

Taking this a step further, if you want to log the last 5 online times, you can either use a defaultdict(list), or get away with plain dictionaries like so:

>>> d = {1: [2, 3, 4, 5, 6,]}
>>> new = {1: 7, 2: 4} # only one current online time anyway (no need for list here)
>>> for _id, new_online in new.items():
...     d[_id] = (d.get(_id, []) + [new_online])[-5:] # only take the last 5 elements
...     
>>> d
{1: [3, 4, 5, 6, 7], 2: [4]}
>>> 
Bahrom
  • 4,752
  • 32
  • 41
  • thank you! I did it slightly differently in the end by adding a step to initialize a dictionary with all device IDs and no timestamps if the json file didn't already exist. But you helped a lot with the idea of having a different "online_devices" dictionary and using that to update the main dictionary - I needed that key step – mercer721 Nov 08 '17 at 19:47
  • @mercer721 No worries, glad this helped! – Bahrom Nov 08 '17 at 19:55
  • cool. Makes sense that update() wouldn't extend to this case. I feel like it's oddly over-complicated to do this - save a dictionary with lists as the values, and update specific keys over time - but I guess my situation is kinda specific. Thanks for these suggestions! – mercer721 Nov 08 '17 at 21:01
0

Instead of writing the created data directly to the file, I would suggest you follow the following steps:

  1. Read the last-seen file, and convert it to a dictionary.
  2. Check every deviceID in the dictionary, and UPDATE the value in the dictionary if the device is online.
  3. Check any devices that ARE NOT IN the last seen dictionary, and add timestamps for them to the dictionary.
  4. Replace the contents of the file with the result of running json.dumps with the current modified dictionary.
pcurry
  • 1,374
  • 11
  • 23
  • thanks! this flow helped me work through it, but @Bahrom's answer was more in depth. Also, I needed a step to make sure that all devices end up in the dictionary, even if they've never been online. – mercer721 Nov 08 '17 at 19:45
0

Basically, JSON is not a good deal for what you want. For one - you cant just append new values to a JSON data file, since it requires closing the structures.

Next, although you can build JSON data resusing a key multiple times, when converting this to a plain Python dict, only the last value will be visible anyway (or you will get an exception, if you tune your parser ok).

So, just a plain file where each record is a new line, think "CSV" - would work better for you. Or, you may opt to move everything to a SQL DB, like sqlite at once.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • I think the asker is fine using a JSON, because the key isn't reused. We only care about the most recent uptime, which is always a single value per device. – Bahrom Nov 08 '17 at 18:57
  • thanks @jsbueno, but I don't want new lines every time I look up whether or not a device is online. They all have unique IDs and I want to keep it to one key-value pair per device – mercer721 Nov 08 '17 at 18:59