Run value through function - python

I have the following management command in Django which updates a record with data from an external source:
class Command(BaseCommand):
def handle(self, *args, **options):
field_mappings = {
'first_name': 'Voornaam',
'initials': 'Voorletters',
'last_name_prefix': 'Voorvoegsel',
'last_name': 'Achternaam',
'sex': 'Geslacht',
'DOB': ['Geboortedatum', 'convert_DOB'],
'street': 'Straat',
'house_number': 'Huisnummer',
'zipcode': 'Postcode',
'city': 'Woonplaats',
'country': 'Land',
'phone_number': 'Telefoonnummer',
'phone_number_mobile': 'MobielTelefoonnummer',
'email_address': 'Emailadres',
'insurer_name': 'NaamVerzekeraar',
'insurance_policy_number': 'PolisnummerVerzekering',
'gp_name': 'NaamHuisarts',
}
patients = Patient.objects.all()
for patient in patients:
result = Query(patient.pharmacy, 'patient_by_bsn', {'BSN': patient.BSN}).run()
for x, y in field_mappings.items():
if type(y) == list:
pass
else:
setattr(patient, x, result[y]['0'])
patient.save()
print('Patient {}-{} updated'.format(patient.pharmacy.vv_id, patient.vv_id))
#staticmethod
def convert_DOB(value):
return timezone.datetime.utcfromtimestamp(value).date()
Most fields can be saved without converting the data first, but some fields like the DOB need converting (in this case from a UNIX timestamp to a Python datetime.date). Where it currently says pass underneath if type(y) == list I want to run the value through the listed function first so that it would save convert_DOB(value) instead of the original value - how can I do this?

First in your mapping don't use the name of the conversion function but the function itself, ie:
def convert_DOB(dob):
# your code here
# ...
field_mappings = {
# ...
'DOB': ['Geboortedatum', convert_DOB],
# ...
}
Then you just have to pass your value to the function and retrieve the result:
for attrname, sourcekey in field_mappings.items():
if isinstance(sourcekey, list):
# unpack the list (assumes length 2)
sourcekey, converter = key
value = result[sourcekey]['0']
# convert the value
value = converter(value)
else:
# no conversion needed
value = result[sourcekey]['0']
# done
setattr(patient, attrname, value)
Note that from a purely semantic POV you should be using a tuple not a list - a list is supposed to be an homogenous collection where position is not significant, a tuple is an heterogenous collection where poistion is significant. In your case the position is indeed significant since the first item is the key to the results dict and the second the conversion function.
Also the way you use field_mappings suggests you could replace it with a list of (attrname, key_or_key_converter) tuples since you only iterate over the dict's (key, value) pairs.

Related

Cannot Append new list data to a dict in a for loop

I have a list of IPs being returned from a POST in listip. I want to iterate over the list of IPs and store data in a dictionary so i can render it on a webpage. But the dictionary is overriding the values for the last IP only. How can i solve this ? Currently there are 3 IPs in the listip but dict is only storing the last passed IPs data.
def healthcheckresults(request):
if not listip:
return render(request, "home/homepage.html",)
for ip in range(len(listip)):
conn = manager.connect(
host= listip[ip],
port='22',
username='XXX',
password = 'XXX',
timeout=10
)
result = conn.get_ospf_neighbor_information()
hostnameresult = conn.get_software_information()
hostname = hostnameresult.xpath('//software-information/host-name/text()')
ospfneighboraddress = result.xpath('//ospf-neighbor/neighbor-address/text()')
ospfneighborinterface = result.xpath('//ospf-neighbor/interface-name/text()')
ospfneighborstate= result.xpath('//ospf-neighbor/ospf-neighbor-state/text()')
ospfneighborID = result.xpath('//ospf-neighbor/neighbor-id/text()')
##METHOD1
ospfdictkey = {"hostname":[],"ospfneighboraddress":[],"ospfneighborinterface":[],"ospfneighborstate":[],"ospfneighborID":[]}
ospfmetalist = [hostname,ospfneighboraddress,ospfneighborinterface,ospfneighborstate,ospfneighborID]
for key, value in zip(ospfdictkey, ospfmetalist):
ospfdictkey[key].append(value)
##METHOD2
ospfdict={"hostname":hostname,"ospfneighboraddress":ospfneighboraddress,"ospfneighborinterface":ospfneighborinterface, "ospfneighborstate":ospfneighborstate,"ospfneighborID":ospfneighborID }
context = {'LUnique': zip(ospfneighboraddress, ospfneighborinterface, ospfneighborstate,ospfneighborID)}
conn.close_session()
listip.clear()
return render(request, "healthcheck/healthcheckresults.html",{
"ospfneighboraddress":ospfneighboraddress,
"ospfneighborinterface":ospfneighborinterface,
"ospfneighborstate":ospfneighborstate,
"ospfneighborID":ospfneighborID,
"context":context,
"hostname":hostname,
"listip":listip,
"ospfdict":ospfdict,
"ospfdictkey":ospfdictkey,
})
Both mentioned methods are returning the same data when i check the data in the dictionary.
{'hostname': ['R3-ISP'], 'ospfneighboraddress': ['192.168.5.34', '192.168.5.5', '192.168.5.10'], 'ospfneighborinterface': ['ae10.0', 'ae2.0', 'ae3.0'], 'ospfneighborstate': ['Full', 'Full', 'Full'], 'ospfneighborID': ['172.0.0.6', '172.0.0.2', '172.0.0.4']}
{'hostname': [['R3-ISP']], 'ospfneighboraddress': [['192.168.5.34', '192.168.5.5', '192.168.5.10']], 'ospfneighborinterface': [['ae10.0', 'ae2.0', 'ae3.0']], 'ospfneighborstate': [['Full', 'Full', 'Full']], 'ospfneighborID': [['172.0.0.6', '172.0.0.2', '172.0.0.4']]}
['R3-ISP']
In each method you are overwriting the dict. Remember that the code is repeating in each iteration of
for ip in range(len(listip)):
Which means that when you set the keys and values in the first line of each method you are overwriting any dict of that name that has gone before.
One way to avoid this would be to create an empty list, and append each new dict to it upon creation. Then you can cycle through the list to see each dict.
aList = []
for ip in range(len(listip)):
...
#Method1
...
aList.append(ospfdictkey )
#Method2
...
aList.append(ospfdict)

How to assign Python Dict Keys to corresponding Python Object Attributes

Suppose I have a python class like:
class User:
name = None
id = None
dob = None
def __init__(self, id):
self.id = id
Now I am doing something like this:
userObj = User(id=12) # suppose I don't have values for name and dob yet
## some code here and this code gives me name and dob data in dictionary, suppose a function call
user = get_user_data() # this returns the dictionary like {'name': 'John', 'dob': '1992-07-12'}
Now, the way to assign data to user object is userObj.name = user['name'] and userObj.dob = user['dob']. Suppose, User has 100 attributes. I will have to explicitly assign these attributes. Is there an efficient way in Python which I can use to assign the values from a dictionary to the corresponding attributes in the object? Like, name key in the dictionary is assigned to the name attribute in the object.
1. Modify the Class definition
class User():
def __init__(self, id):
self.data = {"id":id}
userObj = User(id=12)
2. Update the dict()
user = {"name":"Frank", "dob":"Whatever"} # Get the remaining data from elsewhere
userObj.data.update(user) # Update the dict in your userObj
print(userObj.data)
Here you go !
Instead of mapping a dict to the variable keys. You can use setattr to set variables in an object.
class User:
name = None
id = None
dob = None
def __init__(self, id):
self.id = id
def map_dict(self, user_info):
for k, v in user_info.items():
setattr(self, k, v)
Then for boiler code to use it.
userObj = User(id=12)
user_dict = {
'name': 'Bob',
'dob': '11-20-1993',
'something': 'blah'
}
userObj.map_dict(user_dict)
First, there is no need to predeclare properties in python.
class Foo:
bar: int # This actually creates a class member, not an instance member
...
If you want to add values to a class instance just use setattr()
d = {
'prop1': 'value1',
'prop2': 'value2',
'prop2': 'value2'
}
x = Foo()
for prop in d.keys():
setattr(x, prop, d[prop])
class User(dict):
def __init__(self, *args, **kwargs):
super(User, self).__init__(*args, **kwargs)
self.__dict__ = self
and then just get your dictionary and do:
userObj = User(dictionary)
EDIT:
user the function setattr() then
[setattr(userObj, key, item) for key,item in dict.items()]
In Case you REALLY need to
This solution is for the case, other solutions dont work for you and you cannot change your class.
Issue
In case you cannot modify your class in any way and you have a dictionary, that contains the information you want to put in your object, you can first get the custom members of your class by using the inspect module:
import inspect
import numpy as np
members = inspect.getmembers(User)
Extract your custom attributes from all members by:
allowed = ["__" not in a[0] for a in members]
and use numpy list comprehention for the extraction itself:
members = np.array(members)["__" not in a[0] for a in members]
Modify the user
So lets say you have the following user and dict and you want to change the users attributes to the values in the dictionary (behaviour for creating a new user is the same)
user = User(1)
dic = {"name":"test", "id": 2, "dob" : "any"}
then you simply use setattr():
for m in members:
setattr(user, m[0], dic[m[0]])
For sure there are better solutins, but this might come in handy in case other things dont work for you
Update
This solution uses the attribute definitions based on your class you use. So in case the dictionary has missing values, this solution might be helpful. Else Rashids solution will work well for you too

Dataframe.to_json() not rendering all arguments (pandas)

I have a problem with the .to_json() function in pandas. I have a dataframe that includes a python object, strings and integers but when I want to convert this dataframe into a JSON document, it simply does not include all parameters of the python object. Here is my code:
for i in range(len(self.pre_company)):
id_ = self.pre_company.index.values[i]
code = self.pre_company.iloc[i][0]
name = self.pre_company.iloc[i][1]
group = self.pre_company.iloc[i][2]
try:
cdf = self.country.loc[self.pre_company.iloc[i][5]]
country = Country(self.pre_company.iloc[i][5], cdf[0], cdf[1], cdf[2], cdf[3])
except KeyError:
country = None
town = self.pre_company.iloc[i][4]
company = {'Country': country, 'Code': code, 'Name': name, 'Group': group, 'Town': town}
new_row = pd.DataFrame(company, index=[id_])
if i == 0:
self.company = new_row
else:
self.company = pd.concat([company, new_row])
del new_row
del self.pre_company
data2 = self.company.to_json(orient="records")
with open('test2.json', 'w') as out:
for i in data2:
out.write(str(i))
And for the first line, I get this result:
{"Country":{"a2code":"FR","ccTLD":".fr","officialName":"The French Republic"},"Code":"blah","Name":"blahblah","Group":"GROUP","Town":"town"}
Do you have any idea why is the function not completely working ?
PS: I use CSV input file to get my data into dataframes
I found the answer to my problem: I just had to add a to_dict() function to my python objects which just writes the object in a dict (the way I wanted) and the I add this class at the end of my python object file:
class CompanyEncoder(json.JSONEncoder):
def default(self, obj):
"""
Encodes the object
:param self: the encoder itself
:param obj: the object to encode
:return: the encoded object
"""
if isinstance(obj, Company):
return obj.to_dict()
else:
return json.JSONEncoder.default(self, obj)
And to convert my object to a dict I used this lines (btw I need to create the object before using this:
company = Company(id_, code, name, group, country, town)
self.company.append(company.to_dict())
with open(path, 'w') as out:
out.write(InvoiceLineEncoder().encode(self.company))

Python, django, edit table with string

I don't know how set name for this question.. sorry.
I have function:
myFunction(request, {'Username': 'MyNewUsername', 'Sex': 'Woman', 'SexWant': 'Man'})
def myFunction(self, data):
dataquery = UserData.objects.get(Username = "Patrycja")
for name, key in data.items():
dataquery.name = key
dataquery.save()
Generally speaking this line: dataquery.name
name is 'Username', if I set dataquery.Username = good. But I have to do it as above
From what I understand, your query should be
dataquery = UserData.objects.get(username="Patrycja")
then be aware that the line
dataquery.name = key
sets the attribute name of the object.
In order to set the attributes whose name is specified in data you need to use setattr
for name, value in data.items():
setattr(dataquery, name, value)
and since you seem to want to update only such fields, call save specifing which fields should be updated
dataquery.save(update_fields=data.keys())
Note: please refer to #Sayse's answer in case you need to update more than one record at a time

Multi-valued data in DynamoDB using boto

After scouring the documentation and various tutorials, I cannot figure out how to set or update an attribute on a dynamo Item that is a multi-valued data type (number or string set). I'm using boto (boto.dynamodb2, to be specific -- not boto.dynamodb).
Trying something like this (where 'id' is the hash key):
Item(Table('test'), data={'id': '123', 'content': 'test', 'list': [1,2,3,4]}).save()
Results in this error:
TypeError: Unsupported type "<type 'list'>" for value "[1, 2, 3, 4]"
I feel like this must be possible in boto.dynamodb2, but it's odd that I can't find any examples of people doing this. (Everyone is just setting number or string attributes, not number set or string set attributes.)
Any insight on this topic and how I might get this to work with boto would be very much appreciated! I'm guessing I'm overlooking something simple. Thanks!
Okay, we were able to figure this out on our own. The problem with my example above is that I'm using a list instead of a set. The value of a multi-value attribute MUST be a set.
For example, this works:
Item(Table('test'), data={'id': '123', 'content': 'test', 'list': set([1,2,3,4])}).save()
DnyamoDB now supports Dict/List directly. Boto doesn't have support for it yet, but it's a small patch until it's supported in production.
############################################################
# Patch Dynamizer to support dict/list
############################################################
from boto.dynamodb.types import Dynamizer, get_dynamodb_type
def _get_dynamodb_type(self, attr):
if isinstance(attr, dict):
return 'M'
if isinstance(attr, list):
return 'L'
return get_dynamodb_type(attr)
def _encode_m(self, attr):
result = {}
for k, v in attr.items():
result[k] = self.encode(v)
return result
def _decode_m(self, attr):
result = {}
for k, v in attr.items():
result[k] = self.decode(v)
return result
def _encode_l(self, attr):
return [self.encode(v) for v in attr]
def _decode_l(self, attr):
return [self.decode(v) for v in attr]
Dynamizer._get_dynamodb_type = _get_dynamodb_type
Dynamizer._encode_m = _encode_m
Dynamizer._decode_m = _decode_m
Dynamizer._encode_l = _encode_l
Dynamizer._decode_l = _decode_l
############################################################
# End patch Dynamizer to support dict/list
############################################################
This works normally with boto3:
session = boto3.Session(
aws_access_key_id=AWS_ACCESS_KEY_ID,
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
)
dynamodb = session.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('table')
list = ['1','2','3']
table.put_item(
Item={
'id': 01,
'message': list,
'timestamp': '2019-05-01 22:14:00'
}
)
Your data will be saved like the following:

Categories

Resources