Find the differences between nested dictionaries which contain lists - python

I want to extract the differences between two nested dictionaries and I want the result to include the full dictionary keypath. I have installed Python2.7 and DeepDiff, which appears to be the best option for what I am trying to achieve. I am trying to determine how to change the output of DeepDiff so it provides the full dictionary path and values rather than a set which I cannot index. Is there a better way to alter the output (rather than converting the output back to a dictionary)?
Code:
from __future__ import print_function
from deepdiff import DeepDiff
knownAPs = {'WLC1': {'10.1.1.1': {'72.6': ['AP22', 'city'], '55.1': ['AP102', 'office']}}, 'WLC2': {'10.1.1.2': {}}}
discoveredAPs = {'WLC1': {'10.1.1.1': {}}, 'WLC2': {'10.1.1.2': {}}}
ddiff = DeepDiff(knownAPs, discoveredAPs)
if 'dic_item_added' in ddiff.keys():
print('Item added to known: ' + str((ddiff['dic_item_added'])))
if 'dic_item_removed' in ddiff.keys():
DisAssociatedAPs = (list(list(ddiff['dic_item_removed'])))
for i in DisAssociatedAPs:
fullkeypath = (str(i).strip('root'))
ControllerName = (fullkeypath[0])
ControllerIP = (fullkeypath[1])
AccessPointIndex = (fullkeypath[2])
print('AP: ' + str(knownAPs + fullkeypath) + ' on controller: ' + str(ControllerName) + ' was removed from the known database')
if 'values_changed' in ddiff.keys():
print('Item changed: ' + str((ddiff['values_changed'])))
Output
Traceback (most recent call last):
File "C:/xxx/testdic4.py", line 15, in <module>
print('AP: ' + str(knownAPs + fullkeypath) + ' on controller: ' + str(ControllerName) + ' was removed from the known database')
TypeError: unsupported operand type(s) for +: 'dict' and 'str'
Process finished with exit code 1
Preferred Output
AP: ['AP22', 'city'] on controller: ['WLC1'] was removed from the known database
AP: ['AP102', 'office'] on controller: ['WLC1'] was removed from the known database

The issue is exactly what the traceback tells you: you are trying to add a dictionary to a string, which is of course not what you want. Specifically, when you add knownAPs (type dict) to fullkeypath (type str) you get an error, because dict doesn't know how to add itself to a str.
But that doesn't answer your more general question of how to output the diffs in a way you want. Try this:
diffs = deepdiff.DeepDiff(knownAPs, discoveredAPs)
def get_value_from_string(d, s):
s = list(filter(None, (piece[2:-1] for piece in s.split(']'))))
for piece in s:
d = d[piece]
return d
if 'dic_item_removed' in diffs:
for item in diffs['dic_item_removed']:
item = item.strip('root')
base = item[2:item.find(']') - 1]
print('AP:', get_value_from_string(knownAPs, item),
'on controller: \'' + base + '\' was removed from the known '
'database')

Related

Combining values with text in python function

I have a python script that scrapes a webpage and downloads the mp3s found on it.
I am trying to name the files using elements that I have successfully captured in a separate function.
I am having trouble naming the downloaded files, this is what I have so far:
def make_safe_filename(text: str) -> str:
"""convert forbidden chars to underscores"""
return ''.join(c if (c.isalnum() or c.isspace()) else '_' for c in text).strip('_ ')
filename = make_safe_filename(a['artist'] + a['title'] + a['date'] + a['article_url'])
I am trying to save the file name as "Artist - Title - Date - Article_url" however I am struggling to do this. At the moment the variables are all mashed together without spaces, eg. ArtistTitleDateArticle_url.mp3
I've tried
filename = make_safe_filename(a['artist'] + "-" + a['title'] + "-" + a['date'] + "-" +
a['article_url'])
but this throws up errors.
Can anyone shed some light on where I am going wrong? I know it's something to do with combining variables but I am stuck. Thanks in advance.
I am guessing your a is a dictionary? Maybe you could clarify this in your question? Also what do you typically have in a['article_url']? Could you also post the traceback?
This is my attempt (note: no changes to the function):
def make_safe_filename(text: str) -> str:
"""convert forbidden chars to underscores"""
return ''.join(c if (c.isalnum() or c.isspace()) else '_' for c in text).strip('_ ')
a = {
'artist': 'Metallica',
'title': 'Nothing Else Matters',
'date': '1991',
'article_url': 'unknown',
}
filename = make_safe_filename(a['artist'] + '-' + a['title'] + '-' + a['date'] + '-' + a['article_url'])
print(filename)
Which produced the following output:
Metallica_Nothing Else Matters_1991_unknown
You code should actually work, but if you add the - before passing the joined string to the function, it will just replace those with _ as well. Instead, you could pass the individual fields and then join those in the function, after replacing the "illegal" characters for each field individually. Also, you could regular expressions and re.sub for the actual replacing:
import re
def safe_filename(*fields):
return " - ".join(re.sub("[^\w\s]", "_", s) for s in fields)
>>> safe_filename("Art!st", "S()ng", "ยง$%")
'Art_st - S__ng - ___'
Of course, if your a is a dictionary and you always want the same fields from that dict (artist, title, etc.) you could also just pass the dict itself and extract the fields within the function.
I had a similar problem recently, the best solution is probably to use regex, but I'm too lazy to learn that, so I wrote a replaceAll function:
def replaceAll(string, characters, replacement):
s = string
for i in characters:
s = s.replace(i, replacement)
return s
and then I used it to make a usable filename:
fName = replaceAll(name, '*<>?|"/\\.,\':', "")
in your case it would be:
filename = replaceAll(a['artist'] + a['title'] + a['date'] + a['article_url'], '*<>?|"/\\.,\':', "-")

Removing Single Quotes from a String Stored in an Array

I wrote code to append a json response into a list for some API work I am doing, but it stores the single quotes around the alphanumerical value I desire. I would like to get rid of the single quotes. Here is what I have so far:
i = 0
deviceID = []
while i < deviceCount:
deviceID.append(devicesRanOn['resources'][i])
deviceID[i] = re.sub('[\W_]', '', deviceID[i])
i += 1
if i >= deviceCount:
break
if (deviceCount == 1):
print ('Device ID: ', deviceID)
elif (deviceCount > 1):
print ('Device IDs: ', deviceID)
the desired input should look like this:
input Device IDs:
['14*************************00b29', '58*************************c3df4']
Output:
['14*************************00b29', '58*************************c3df4']
Desired Output:
[14*************************00b29, 58*************************c3df4]
As you can see, I am trying to use RegEx to filter non Alphanumeric and replace those with nothing. It is not giving me an error nor is it preforming the actions I am looking for. Does anyone have a recommendation on how to fix this?
Thank you,
xOm3ga
You won't be able to use the default print. You'll need to use your own means of making a representation for the list. But this is easy with string formatting.
'[' + ', '.join(f'{id!s}' for id in ids) + ']'
The f'{id:!s} is an f-string which formats the variable id using it's __str__ method. If you're on a version pre-3.6 which doesn't use f-strings, you can also use
'%s' % id
'{!s}'.format(id)
PS:
You can simplify you're code significantly by using a list comprehension and custom formatting instead of regexes.
ids = [device for device in devicesRanOn['resources'][:deviceCount]]
if deviceCount == 1:
label = 'Device ID:'
elif deviceCount > 1:
label = 'Device IDs:'
print(label, '[' + ', '.join(f'{id!s}' for id in ids) + ']')

Trouble inputting interger values into an SQL statement within ArcGIS

So I am defining a function for use in a ArcGIS tool that will verify attributes, catch errors, and obtain user input to rectify those error. I want the tool to select and zoom to the segment that is being currently assessed so that they can make an informed decision. This is what I have been using, and it works well. But the CONVWGID is the variable that will be changing, and I'm not sure how to input that variable into an SQL statement without causing errors.
This is how I had tested the logic:
def selectzoom():
arcpy.SelectLayerByAttribute_management(Convwks, "NEW_SELECTION", " [CONVWGID] = 10000001")
mxd = arcpy.mapping.MapDocument('CURRENT')
df = arcpy.mapping.ListDataFrames(mxd, "Layers") [0]
df.zoomToSelectedFeatures()
arcpy.RefreshActiveView()
Then I needed to work the variable into the function in order to accept different CONVWGID values, which gives me a Runtime/TypeError that I should have known would happen.
Runtime error -
Traceback (most recent call last): - File "string", line 1, in module - TypeError: cannot concatenate 'str' and 'int' objects
def selectzoom(convwkgid):
delimfield = '" [CONVWGID] = ' + convwkgid + ' "'
arcpy.SelectLayerByAttribute_management(Convwks, "NEW_SELECTION", delimfield)
mxd = arcpy.mapping.MapDocument('CURRENT')
df = arcpy.mapping.ListDataFrames(mxd, "Layers") [0]
df.zoomToSelectedFeatures()
arcpy.RefreshActiveView()
And when I alter the delimfield line to change the integer into a string, it selects all of the attributes in the entire feature class. Not just the one that had been passed via the function call.
delimfield = '"[CONVWGID] = ' + str(convwkgid) + '"'
I'm not amazing with SQL and maybe I'm missing something basic with this statement, but I can't figure out why it won't work when I'm basically giving it the same information:
"[CONVWGID] = 10000001"
'"[CONVWGID] = ' + str(convwkgid) + '"'
It turned out to be the extra inclusion of Double quotes inside of my single quotes that raised this problem.
Thanks to #Emil Brundage for the help!
Let's say convwkgid = 10000001
'"[CONVWGID] = ' + str(convwkgid) + '"' doesn't equal "[CONVWGID] = 10000001"
'"[CONVWGID] = ' + str(convwkgid) + '"' would actually be '"CONVWGID] = 10000001"'
Try instead:
'[CONVWGID] = ' + str(convwkgid)

How to get data from json using python

json:
{"id":"1","name":"Smokey Mountain Ski Club","terrain_park":"Unknown","night_skiing":"Unknown","operating_status":"Unknown","latitude":52.977947,"longitude":-66.92094,"user":{"id":"7","username":"skier"},"tags":[{"id":"1","name":"Downhill"}],"ski_maps":[{"id":"902"}],"open_ski_maps":[],"created":"2008-04-13T00:11:59+00:00","regions":[{"id":"335","name":"Newfoundland and Labrador"}]}
I've done so that this data stores in a "data" variable..
I am trying to output all the data like: "key" : "value" list
for q in data:
print q + ': ' data[q]
This code outputs:
night_skiing: Unknown
name: Smokey Mountain Ski Club
Traceback (most recent call last):
TypeError: coercing to Unicode: need string or buffer, list found
I understand what's this error about but I don't know how to solve it..
So my question is how to put all the data from this array into variables?
When e.g. q == tags, your print line becomes:
print "tags" + ': ' + [{"id":"1","name":"Downhill"}]
Python is strongly typed, so you can't implicitly concatenate a (unicode) string with a list. You need to be explicit about what you want to happen, i.e. that the list (data[q]) should be converted to a string:
print q + ': ' str(data[q])
or, much better, use str.format rather than string concatenation (or the old-fashioned % formatting):
print "{0}: {1}".format(q, data[q])
Here you have a small example:
import json
try:
# parse JSON string from socket
p = json.loads(msg)
except (ValueError, KeyError, TypeError):
logging.debug("JSON format error: " + msg.strip() )
else:
# remote control is about controlling the model (thrust and attitude)
com = "%d,%d,%d,%d" % (p['r'], p['p'], p['t'], p['y'])
Problem is that not all your data values are strings, and you are treating them as strings.
Using a str format will convert the values to strings. Try this:
for q in data.iteritems():
print '%s: %s' % q
import json
data = '{"id":"1","name":"Smokey Mountain Ski Club","terrain_park":"Unknown","night_skiing":"Unknown","operating_status":"Unknown","latitude":52.977947,"longitude":-66.92094,"user":{"id":"7","username":"skier"},"tags":[{"id":"1","name":"Downhill"}],"ski_maps":[{"id":"902"}],"open_ski_maps":[],"created":"2008-04-13T00:11:59+00:00","regions":[{"id":"335","name":"Newfoundland and Labrador"}]}'
dataDict = json.loads(data)
for key in dataDict.keys():
print key + ': ' + str(dataDict[key])

Getting a bootstrap-calendar component to work with Django

I don't know if this question is too specific but I'll give it a shot anyway:
I found a very nice bootstrap calendar that is very useful. The component is found here: http://bootstrap-calendar.azurewebsites.net/
I am trying to populate that calendar with events. I am using django. I made a function to return the elements on JSON format. I am making up the dates for now, just so its easier to view on the calendar for now:
def view_list_json(request):
i = 0
json_string = '{"sucess": 1, result: ['
for run in Model.objects.all():
start = datetime.datetime.now() - datetime.timedelta(days = i)
end = datetime.datetime.now() - datetime.timedelta(days = i) + datetime.timedelta(minutes = 40)
start_str = str(int(time.mktime(start.timetuple())))
end_str = str(int(time.mktime(end.timetuple())))
json_string += '{ "id": "' + str(run.id) + '"'
json_string += ', "title": "Foo"'
json_string += ', "url":"#"'
json_string += ', "class": "event-success"'
json_string += ', "start":"' + start_str + '"'
json_string += ', "end":"' + end_str + '"},'
i += 1
json_string = json_string[:-1]
json_string += ']}'
return HttpResponse(json_string, content_type="application/json")
JSON format I am trying to create:
https://github.com/Serhioromano/bootstrap-calendar/blob/master/events.json.php
The component seems to be reading this function correctly. I don't get any errors in this sense on Firebug console...
... Yet the data doesn't seem to load.
Any ideas on how I can approach the problem?
This JSON is not valid at all. There are quite a few problems, like extra close braces after each element, missing commas between elements, and missing open square brackets at the beginning of the next element.
But really you shouldn't be trying to build up a JSON string like that - it's too prone to errors. Even if you're hard-coding them, you should still build it up using standard Python lists and dicts, then serialize using the built-in json library.

Categories