Here's my code:
def update_tags_with_value(tags, many_to_many_class):
if tags:
many_to_many_class.objects.filter(
personne=self.instance,
date_v_fin=None
).update(date_v_fin=django_datetime.now())
for idx_tag_with_value in tags:
pl = many_to_many_class.objects.create(
personne=self.instance,
langue=TagWithValue.objects.get(
pk=idx_tag_with_value
)
)
pl.save()
update_tags_with_value(self.cleaned_data.get('known_languages'),
PersonneLangue)
update_tags_with_value(self.cleaned_data.get('types_permis'),
PersonneTypesPermis)
So I found out I can easily pass a class as a parameter. But the last problem is about the named argument. If you watch my code, I do a langue=TagWithValue..[blabla]. The problem is that it's a "named" parameter, and I'd like to be able to pass it like that:
update_tags_with_value(self.cleaned_data.get('known_languages'),
PersonneLangue, 'langue')
update_tags_with_value(self.cleaned_data.get('types_permis'),
PersonneTypesPermis, 'permis')
And then to call it somehow like that (it doesn't work yet):
def update_tags_with_value(tags, many_to_many_class, champ):
if tags:
many_to_many_class.objects.filter(
personne=self.instance,
date_v_fin=None
).update(date_v_fin=django_datetime.now())
for idx_tag_with_value in tags:
pl = many_to_many_class.objects.create(
personne=self.instance,
champ=TagWithValue.objects.get(
pk=idx_tag_with_value
)
)
pl.save()
For now I get this error:
'champ' is an invalid keyword argument for this function
To be more precise, I need to call many_to_many_class.objects.create() one time with known_languages=blabla and another time with types_permis=blabla which, in other words, should call once many_to_many_class.objects.create(known_languages=blabla) and many_to_many_class.objects.create(types_permis=blabla) and I would like to know if there's a way to precise only the name of the parameter, not blabla
How to solve this?
Seems like normal keyword unpacking would work . . .
kwargs = {champ: TagWithValue.objects.get(
pk=idx_tag_with_value)}
pl = many_to_many_class.objects.create(
personne=self.instance,
**kwargs)
Of course, now champ might not be the best name for the function parameter, but hopefully you get the idea.
Here's my working solution, I don't know if it's the best "pythonic" way but it works like a charm:
def update_tags_with_value(tags, many_to_many_class, champ):
if tags:
many_to_many_class.objects.filter(
personne=self.instance,
date_v_fin=None
).update(date_v_fin=django_datetime.now())
for idx_tag_with_value in tags:
args = {
'personne': self.instance,
champ: TagWithValue.objects.get(
pk=idx_tag_with_value
)}
pl = many_to_many_class.objects.create(**args)
pl.save()
update_tags_with_value(self.cleaned_data.get('known_languages'),
PersonneLangue, 'langue')
update_tags_with_value(self.cleaned_data.get('types_permis'),
PersonneTypePermis, 'type_permis')
update_tags_with_value(self.cleaned_data.get('diplomes'),
PersonneDiplome, 'diplome')
update_tags_with_value(self.cleaned_data.get('centres_dinteret'),
PersonneCentreDInteret, 'centre_dinteret')
update_tags_with_value(self.cleaned_data.get('hobbies'),
PersonneHobby, 'hobby')
Related
The following function is used within a module to query network devices and is called from multiple scripts I use. The arguments it takes are a nested dictionary (the device IP and creds etc) and a string (the command to run on the device):
def query_devices(hosts, cmd):
results = {
'success' : [],
'failed' : [],
}
for host in hosts:
device = hosts[host]
try:
swp = ConnectHandler(device_type=device['dev_type'],
ip=device['ip'],
username=device['user'],
password=device['pwd'],
secret=device['secret'])
swp.enable()
results[host] = swp.send_command(cmd)
results['success'].append(host)
swp.disconnect()
except (NetMikoTimeoutException, NetMikoAuthenticationException) as e:
results['failed'].append(host)
results[host] = e
return results
I want to reuse all of the code to update a device and the only changes would be:
The function would take the same dictionary but the cmd argument would now be a list of commands.
The following line:
results[host] = swp.send_command(cmd)
would be replaced by:
results[host] = swp.send_config_set(cmd)
I could obviously just replicate the function making those two changes and as it is in a module I reuse, I am only having to do it once but I am still basically repeating a lot of the same code.
Is there a better way to do this as I seem to come across the same issue quite often in my code.
You could just add a check on the changed line:
...
if isinstance(cmd, str):
results[host] = swp.send_command(cmd)
else:
results[host] = swp.send_config_set(cmd)
...
The rest of the function can stay the same and now you can simply call it with either a string or a list of strings...
You could use the unpacking operator to always make cmds an iterable (a tuple, actually) even if it is a single value. That way you could always call send_config_set. Here is a super simplified example to illustrate the concept.
def query_devices(hosts, *cmds):
for one_cmd in cmds:
print(one_cmd)
print('query_devices("hosts", "cmd_1")')
query_devices("hosts", "cmd_1")
print('\nquery_devices("hosts", "cmd_1", "cmd_2", "cmd_3")')
query_devices("hosts", "cmd_1", "cmd_2", "cmd_3")
print('\nquery_devices("hosts", *["cmd_1", "cmd_2", "cmd_3"])')
query_devices("hosts", *["cmd_1", "cmd_2", "cmd_3"])
Output:
query_devices("hosts", "cmd_1")
cmd_1
query_devices("hosts", "cmd_1", "cmd_2", "cmd_3")
cmd_1
cmd_2
cmd_3
query_devices("hosts", *["cmd_1", "cmd_2", "cmd_3"])
cmd_1
cmd_2
cmd_3
I made a small web-crawler in one function, upso_final.
If I print(upso_final()), I get 15 lists that include title, address, phone #. However, I want to print out only title, so I made variable title a global string. When I print it, I get only 1 title, the last one in the run. I want to get all 15 titles.
from __future__ import unicode_literals
import requests
from scrapy.selector import Selector
import scrapy
import pymysql
def upso_final(page=1):
def upso_from_page(url):
html = fetch_page(url)
sel = Selector(text=html)
global title,address,phone
title = sel.css('h1::text').extract()
address = sel.css('address::text').extract()
phone = sel.css('.mt1::text').extract()
return {
'title' : title,
'address' : address,
'phone' : phone
}
def upso_list_from_listpage(url):
html = fetch_page(url)
sel = Selector(text=html)
upso_list = sel.css('.title_list::attr(href)').extract()
return upso_list
def fetch_page(url):
r = requests.get(url)
return r.text
list_url = "http://yp.koreadaily.com/list/list.asp?page={0}&bra_code=LA&cat_code=L020502&strChar=&searchField=&txtAddr=&txtState=&txtZip=&txtSearch=&sort=N".format(page)
upso_lists = upso_list_from_listpage(list_url)
upsos = [upso_from_page(url) for url in upso_lists]
return upsos
upso_final()
print (title,address,phone)
The basic problem is that you're confused about passing values back from a function.
upso_from_page finds each of the 15 records in turn, placing the desired information in the global variables (generally a bad design). However, the only time you print any results is after you've found all 15. Since your logic has each record overwriting the previous one, you print only the last one you found.
It appears that upso_final accumulates the list and returns it, but you ignore that return value. Instead, try this in your main program:
upso_list = upso_final()
for upso in upso.list:
print (upso)
This should give you a 3-item dictionary for each upso record; from there, you can learn the referencing and format to your taste.
AN alternate solution is to print each record as you find it, from within upso_from_page, but your overall design suggests that's not what you want.
I wrote some code to connect with FileMaker Server and ask for a new record to be created on the backend server. This portion works fine and the result contains unexpected text that does not show up in a browser. I couldn't get the find operations to work by looking at the tree in a browser so I ended up printing the tags and attributes out and every tag attribute pair has this format:
Tag: {http://www.filemaker.com/xml/fmresultset}error Attrib: {'code': '0'}
In my code below I'm having to put the fully qualified tag into the code to get the first find to work. This makes it harder to figure out the xPath to the object that I want to get from the XML. The second find below doesn't work since I can't figure out the path. The path should be /resultset/record/ and the attribute is record-id. Anybody have an idea of what is happening. Why is the fmresultset document prepended to every tag?
url = self.getBaseURL( machineID, file, lay ) + fields + "&-new"
result = self.sendURL( url )
import xml.etree.ElementTree as ET
root = ET.fromstring(result)
self.printXML( root )
base = "{http://www.filemaker.com/xml/fmresultset}"
find = root.find( base + "error" )
error = find.attrib[ 'code' ]
recID = 0
if ( error == '0' ):
find = root.find( base + "resultset" + base + "/record" )
recID = find.attrib[ 'record-id' ]
Keith
OK... You mentioned the word namespace and I figured it out. This uses the element version of root.
def getXMLValue( self, root, path, value ):
ns = {'fmrs': 'http://www.filemaker.com/xml/fmresultset' }
find = root.find( path, ns )
value = find.attrib[ value ]
return value
def getError ( self, root ):
return self.getXMLValue ( root, "fmrs:error", 'code' )
def getRecID ( self, root ):
return self.getXMLValue ( root, "fmrs:resultset/fmrs:record", 'record-id' )
This code correctly returns the correct value. I was expecting something like. Create an instance of the class then call setNamespace(). I'm having to put all these references to fmrs which I would rather not do but this solves the issues and the code isn't horrible to read.
Thanks for the assist.
-keith
I am working with images that have multiple layer which are described in their meta data that looks like this..
print layers
Cube1[visible:true, mode:Normal]{r:Cube1.R, g:Cube1.G, b:Cube1.B, a:Cube1.A}, Ground[visible:true, mode:Lighten, opacity:186]{r:Ground.R, g:Ground.G, b:Ground.B, a:Ground.A}, Cube3[visible:true, mode:Normal]{r:Cube3.R, g:Cube3.G, b:Cube3.B, a:Cube3.A}
I'm wondering if this formatting could be recognizable by Python as more then a string. Ideally I would like to call up the properties of any one for the layers. For example:
print layers[0].mode
"Normal"
On another post someone showed me how to get the names of each layer, which was very helpful, but now I'm looking to use the other info.
PS: if it helps I don't care about any of the info inside the {}
Thanks
print type(layers)
<type 'str'>"
In case you don't want to deal with regex ...
layers = "Cube1[visible:true, mode:Normal]{r:Cube1.R, g:Cube1.G, b:Cube1.B, a:Cube1.A}, Ground[visible:true, mode:Lighten, opacity:186]{r:Ground.R, g:Ground.G, b:Ground.B, a:Ground.A}, Cube3[visible:true, mode:Normal]{r:Cube3.R, g:Cube3.G, b:Cube3.B, a:Cube3.A}"
layer_dict = {}
parts = layers.split('}')
for part in parts:
part = part.strip(', ')
name_end = part.find('[')
if name_end < 1:
continue
name = part[:name_end]
attrs_end = part.find(']')
attrs = part[name_end+1:attrs_end].split(', ')
layer_dict[name] = {}
for attr in attrs:
attr_parts = attr.split(':')
layer_dict[name][attr_parts[0]] = attr_parts[1]
print 'Cube1 ... mode:', layer_dict.get('Cube1').get('mode')
print 'Ground ... opacity:', layer_dict.get('Ground').get('opacity')
print 'Cube3', layer_dict.get('Cube3')
output ...
Cube1 ... mode: Normal
Ground ... opacity: 186
Cube3 {'visible': 'true', 'mode': 'Normal'}
Parsing (Pyparsing et al) is surely the correct and extensible way to go, but here's a fast-and-dirty object and constructors using regexes and comprehensions to parse properties and bolt them on with setattr(). All constructive criticisms welcome!
import re
#import string
class Layer(object):
#classmethod
def make_list_from_string(cls,s):
all_layers_params = re.findall(r'(\w+)\[([^\]]+)\]',s)
return [cls(lname,largs) for (lname, largs) in all_layers_params]
def __init__(self,name,args):
self.name = name
for (larg,lval) in re.findall(r'(\w+):(\w+)(?:,\w*)?', args):
setattr(self,larg,lval)
def __str__(self):
return self.name + '[' + ','.join('%s:%s' % (k,v) for k,v in self.__dict__.iteritems() if k!='name') + ']'
def __repr__(self):
return self.__str__()
t = 'Cube1[visible:true, mode:Normal]{r:Cube1.R, g:Cube1.G, b:Cube1.B, a:Cube1.A}, Ground[visible:true, mode:Lighten, opacity:186]{r:Ground.R, g:Ground.G, b:Ground.B, a:Ground.A}, Cube3[visible:true, mode:Normal]{r:Cube3.R, g:Cube3.G, b:Cube3.B, a:Cube3.A}'
layers = Layer.make_list_from_string(t)
I moved all the imperative code into __init__() or the classmethod Layers.make_list_from_string().
Currently it stores all args as string, it doesn't figure opacity is int/float, but that's just an extra try...except block.
Hey, it does the job you wanted. And as a bonus it throws in mutability:
print layers[0].mode
'Normal'
print layers[1].opacity
'186'
print layers[2]
Cube3[visible:true,mode:Normal]
layers[0].mode = 'Weird'
print layers[0].mode
'Weird'
"I'm wondering if this formatting could be recognizable by Python as more then a string."
Alternatively, I was thinking if you tweaked the format a little, eval()/exec() could be used, but that's yukkier, slower and a security risk.
I have a python class which reads a config file using ConfigParser:
Config file:
[geography]
Xmin=6.6
Xmax=18.6
Ymin=36.6
YMax=47.1
Python code:
class Slicer:
def __init__(self, config_file_name):
config = ConfigParser.ConfigParser()
config.read(config_file_name)
# Rad the lines from the file
self.x_min = config.getfloat('geography', 'xmin')
self.x_max = config.getfloat('geography', 'xmax')
self.y_min = config.getfloat('geography', 'ymin')
self.y_max = config.getfloat('geography', 'ymax')
I feel that the last four lines are repetitive, and should somehow be compressed to one Pythonic line that would create a self.item variable for each item in the section.
Any ideas?
Adam
UPDATE:
Following your answers, I've modified my code to:
for item in config.items('geography'):
setattr(self, '_'+item[0], float(item[1]))
Now,
print self.__dict__
>>> {'_xmax': 18.600000000000001, '_ymax': 47.100000000000001,
'_ymin': 36.600000000000001, '_xmin': 6.5999999999999996}
I usually try to avoid external interactions in a constructor - makes it hard to test the code. Better pass a config parser instance or a fp-like object instead of a filename.
for line in ['x_min', 'x_max', 'y_min', 'y_max']:
setattr(self, line, config.getfloat('geography', line.replace('_', '')))
How about something like:
for key in ['xmin','xmax','ymin','ymax']:
self.__dict__[key] = config.getfloat('geography',key);
Note that the above will assign it to self.xmin instead of self.x_min... however, if you are fine with that naming, then this should work... otherwise, mapping between names would be more code than the original.