Writing clean code: Nested For/If/Elif in Pyramid - python

Goal:
Trying to print pending results (Assessment.name, e.g. 'Becoming a Leader', 'Something Else', etc...--print whatever a user has NOT completed in all_assessments) and completed results(assessment.name from Assessment_Results.assessment.name, e.g. 'Becoming a Leader' --print what a user has completed as shown in user_results) using a clean and quick for loop with necessary conditionals.
Problem:
Current code is not achieving the aforementioned goal.
Any suggestions are highly appreciated! I'm still a newbie, so any guidance is truly welcomed!
Views.py
def view_assessments(request):
owner = authenticated_userid(request)
print 'login owner', owner
if owner is None:
raise HTTPForbidden()
all_assessments = api.retrieve_assessments()
print 'these are all the assessments:', all_assessments
print 'and type:', type(all_assessments)
all_results = api.retrieve_assessment_results() # all the assessment results in a list
for x in all_assessments:
alls = x.name
if alls is not None:
for x in all_results: #found user based on all results
assessment = x.assessment.name
user = x.owner.username
if user == owner:
print 'completed', assessment
elif assessment != alls: # DOES NOT WORK
alls.index(assessment)
return {'assessments': all_assessments, 'assessment_results': all_results, 'loggedin': owner, 'user_results': user_results}
A breakdown of what the api does:
Currently all_assessments prints out a list of all the existing assessment names and text.
all_assessments = [<Assessment(name='Becoming a Leader', text='better decisions')>, <Assessment(name='Good work', text='working on these skills')>, <Assessment(name='Teaching NTS', text='Series 1.1')>]
while all_results prints out all results of every user in a list. Shown here:
all_results [<Assessment_Result(owner='<User(username ='baseball', password='...', firstname ='Jenny', lastname ='Jen', email='dance#aol.com')>', assessment='<Assessment(name='Becoming a Leader', text='better decisions')>')>, <Assessment_Result(owner='<User(username ='donald', password='...', firstname ='Drew', lastname ='James', email='cool#gmail.com')>', assessment='<Assessment(name='Good work', text='working on these skills')>')>]
and finally, user_results prints results found by username (which is based on whomever is logged in).
retrieved by username: [<Assessment_Result(owner='<User(username ='dorisday', password='..', firstname ='Doris', lastname ='Day', email='dorisday#gmail.com')>', assessment='<Assessment(name='Becoming a Leader', text='better decisions')>')>, <Assessment_Result(owner='<User(username ='dorisday', password='..', firstname ='Doris', lastname ='Day', email='dorisday#gmail.com')>', assessment='<Assessment(name='Good work', text='working on these skills')>')>]

I would start with something like this:
def view_assessments(request):
logged_in_userid = authenticated_userid(request)
if logged_in_userid is None:
raise HTTPForbidden()
all_assessments = api.retrieve_assessments()
all_results = api.retrieve_assessment_results()
completed_assessments = []
pending_assessments = []
for assessment in all_assessments:
if assessment.name is None:
continue
found_assessment_result = False
for result in all_results:
if result.owner.username == logged_in_userid and result.assessment == assessment:
found_assessment_result = True
break # no need to check further
if found_assessment_result:
compleded_assessments.append(assessment)
else:
pending_assessments.append(assessment)
return {'completed_assessments': completed_assessments, 'pending_assessments': pending_assessments, 'loggedin': owner, 'user_results': user_results}
The trick here, when iterating over two nested lists, is to have a "found" boolean, which you set to False before entering the inner loop - after the inner loop finishes you can check the variable and, depending on its value, push the assessment into one of two lists.
As you suspected, this code will probably be quite inefficient because it has to iterate over a product of all assessments and all results, so if you have, say, 10 assessments and 10 results it would require 100 iterations, but if you have 100 assessments and 100 results it'll be 10.000 iterations. But it'll do as a learning exercise.

Related

Get nested LDAP group members with python-ldap

I'm trying to find the best way to get a list of all LDAP user accounts that belong to groups which are members of a groupOfNames using python-ldap. This is on an OpenLDAP server, not AD. I wrote the function below, which does the job but takes forever to run. I'm hoping either python-ldap has some builtin function that I'm not aware of, or there's something I can modify to make this run more quickly. If not, hopefully someone else will find this code useful. Thanks in advance for any help!
def get_nested_members(con, dn):
"""
Parameters
----------
con : LDAPObject
An authenticated python-ldap connection object
dn : string
The dn of the groupOfNames to be checked
Returns
-------
members : list
A list of all accounts that are members of the given dn
"""
members = []
searched = []
to_search = [dn]
while len(to_search) > 0:
current_dn = to_search.pop()
cn = current_dn.split(',')[0]
r = con.search_s(base_dn, ldap.SCOPE_SUBTREE, cn, [])[0][1]
if 'groupOfNames' in r['objectClass']:
if 'member' in r:
for i in r['member']:
if((i != current_dn) and (i not in searched)):
to_search.append(i)
searched.append(current_dn)
elif 'posixGroup' in r['objectClass']:
if 'memberUid' in r:
for i in r['memberUid']:
members.append(i)
searched.append(current_dn)
elif 'posixAccount' in r['objectClass']:
if 'uid' in r:
members.append(r['uid'][0])
else:
print('ERROR: encountered record of unknown type:')
pprint(str([current_dn, r]))
return list(set(members))
I realized that running ldapsearch repeatedly was the limiting factor, so I made a new version which builds a dictionary of ALL group and groupOfNames records first. It takes up a bit more memory than the old solution, but is less taxing on the LDAP server and runs significantly faster (down from ~15 minutes to <1 second for my application). I'll leave the original code below the new version for a reference of what not to do. Credit for the merge_dicts() function goes to Aaron Hall.
import ldap
def merge_dicts(*dict_args):
"""Given any number of dicts, shallow copy and merge into a new dict,
precedence goes to key value pairs in latter dicts.
"""
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
def get_nested_members(con, dn, base_dn='dc=example'):
"""Search a groupOfNames and return all posixAccount members from all its subgroups
Parameters
----------
con: LDAPObject
An authenticated LDAP connection object
dn: string
The dn of the groupOfNames to be searched for members
(optional) base_dn: string
The base dn to search on. Make sure to change the default value to fit your LDAP server
Returns
-------
members: list
A list of all nested members from the provided groupOfNames
"""
logging.info('Getting nested members of ' + str(dn))
print('Getting nested members of ' + str(dn))
if type(dn) is list:
to_search = [] + dn
elif type(dn) is str:
to_search = [dn]
else:
print('ERROR: Invalid dn value. Please supply either a sting or list of strings.')
return []
members = []
searched = []
groupOfNames_list = con.search_s(base_dn, ldap.SCOPE_SUBTREE, 'objectClass=groupOfNames', ['dn', 'member', 'cn'])
groupOfNames_dict = {}
for g in range(len(groupOfNames_list)):
groupOfNames_dict[groupOfNames_list[g][0]] = groupOfNames_list[g][1]
groupOfNames_list = None #To free up memory
group_list = con.search_s(base_dn, ldap.SCOPE_SUBTREE, 'objectClass=posixGroup', ['dn', 'memberUid', 'cn'])
group_dict = {}
for g in range(len(group_list)):
group_dict[group_list[g][0]] = group_list[g][1]
group_list = None #To free up memory
all_groups = merge_dicts(groupOfNames_dict, group_dict)
group_dict = None #To free up memory
groupOfNamesdict = None #To free up memory
while len(to_search) > 0:
search_dn = to_search.pop()
try:
g = all_groups[search_dn]
if 'memberUid' in g:
members += g['memberUid']
searched.append(search_dn)
elif 'member' in g:
m = g['member']
for i in m:
if i.startswith('uid='):
members.append((i.split(',')[0]).split('=')[1])
elif i.startswith('cn='):
if i not in searched:
to_search.append(i)
searched.append(search_dn)
else:
searched.append(search_dn)
except:
searched.append(search_dn)
return list(set(members))

How to change a global variable in python?

I am accessing data from different accounts from an online platform over their API. I have created a class called Account that holds all the information necessary to access this API. I want to be able to set the account (and the necessary info to gain access) each time before I make an API request. I tried to make a function that will set a global variable Acct to the proper account class instance but after I call choose_account(), Acct continues to return '', is there a better way to handle this type of procedure?
Acct = ''
def choose_account():
global Acct
get = raw_input(r'Adap1, Adap2, Adap3, or Adap4? ')
if get == 'Adap1':
Acct = Adap1
elif get == 'Adap2':
Acct = Adap2
elif get == 'Adap3':
Acct = Adap3
elif get == 'Adap4':
Acct = Adap4
else:
print ("Please type Adap1, Adap2, Adap3, or Adap4 ")
Edit: show Account and Adap1 etc
class Account():
def __init__(self, name, username, password, org_id):
self.name = name
self.username = username
self.password = password
self.org_id = org_id
def update_pw(self, pw):
self.password = pw
Adap1 = Account('Adap1', 'username', 'password', 'org_id')
Sorry, but use of global variables in that way is not usually a good way to go. You are probably new to programming, so I don't want you to feel you are being "told off", but it would be much more sensible to have the function return a value, and then set the global variable with a statement like
Acct = choose_account()
In which case your function ought to look more like this (untested code):
def choose_acct():
while True:
get = raw_input(r'Adap1, Adap2, Adap3, or Adap4? ')
if get == "Adap1":
return Adap1
elif get == "Adap2":
return Adap2
elif get == "Adap3":
return Adap3
elif get == "Adap4":
return Adap4
Better still, you could consider a data-driven approach to the problem, and define a dictionary like
adict = {"Adap1": Adap1, "Adap2": Adap2, "Adap3": Adap3, "Adap4": Adap4}
Then your function could read (again, untested)
def choose_acct():
while True:
get = raw_input(r'Adap1, Adap2, Adap3, or Adap4? ')
result = adict.get(get, None)
if result:
return result
As your experience level grows you will start to recognise the difference between good and bad code, but you made a pretty good attempt.

Python: Append to list owned by an instance stored in shelved dictionary

So I'm writing a script to keep track of my correspondence.
It takes the name of someone who emailed me, looks for its associated 'Friend object' in a shelved dictionary of Friend instances (or creates itself a new instance and stores it in the dictionary), then appends the current time to that instance's list of timestamps.
So for example, if the script runs with 'John Citizen' as the input, it finds the key 'John Citizen' in the dictionary, gets the instance associated with that key, goes to that instance's list and appends to that list a timestamp.
This is all working as I want it to, except for the appending.
Here's the friend object:
class Friend():
def __init__(self, name):
self.name = name
self.timestamps = []
def add_timestamp(self, timestamp):
self.timestamps.append(timestamp)
Here's the global function that processes the input into a name string. (The input comes from AppleScript and is always in this format:
Input: "First [Middles] Last <emailaddress#email.com>"
def process_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("sender", help="The output from AppleScript")
args = parser.parse_args()
full_name = args.sender
## gets just the name
words = full_name.split(' ')
words.pop()
full_name = ' '.join(words)
## takes away all the non alpha characters, newlines etc.
alpha_characters = []
for character in full_name:
if character.isalpha() or character == " ":
alpha_characters.append(character)
full_name = ''.join(alpha_characters)
return full_name
And then here's the script handling that full_name string.
## Get the timestamp and name
now = datetime.datetime.now()
full_name = process_arguments()
## open the shelf to store and access all the friend information
shelf = shelve.open('/Users/Perrin/Library/Scripts/friend_shelf.db')
if full_name in shelf:
shelf[full_name].add_timestamp(now)
shelf.close
else:
shelf[full_name] = Friend(full_name)
shelf.close
I've tried to debug it and full_name in shelf evaluates to True and the problem is still happening.
I just can't seem to get that self.timestamps list to populate. I would really appreciate any help!
You need to extract, mutate and store the object back in the shelf to persist it.
e.g
# extract
this_friend = shelf[full_name]
# mutate
this_friend.add_timestamp(now)
# re-add
shelf[full_name] = this_friend
shelf.close()
You can see an example of this in the python docs.
The other option is pass the writeback parameter as True to shelve.open and it will allow you to write to the keys directly.
#paulrooney answered this.
Objects in the shelf need to be removed from the shelf, assigned to a name, mutated (to add time stamp), then put back in the shelf. This code works fine.
shelf = shelve.open('/Users/Perrin/Library/Scripts/friend_shelf.db')
if full_name in shelf:
this_friend = shelf[full_name]
this_friend.add_timestamp(now)
shelf[full_name] = this_friend
print shelf[full_name].timestamps
shelf.close
else:
shelf[full_name] = Friend(full_name)
this_friend = shelf[full_name]
this_friend.add_timestamp(now)
shelf[full_name] = this_friend
shelf.close

Nested List Comparison

Python noob here so please bear with me! I have a list that looks like this:
bookList = [("Wuthering Heights", "fred"), ("Everville", "fred"), ("Wuthering Heights", "dan")]
What I’m trying to do is write a function that looks at each nested list and sees who shares books in common with who, depending who is logged in. For example, if dan was logged in, the system would say “fred also has plums”.
I have a dictionary set up the holds usernames as keys and passwords as their value.
I’m kind of struggling with list comprehension when they involve anything nested, and help would be greatly appreciated!
I don't think your existing data structure is really ideal for this. What I would do would be to pre-process it into a dictionary whose keys are the usernames and the values are sets of books. Then you can do a loop or list comprehension to compare the logged-in user with all the other users and see if there is anything in common. So:
from collections import defaultdict
bookdict = defaultdict(set)
for book, name in bookList:
bookdict[name].add(book)
logged_in_user = 'fred'
for person, books in bookdict.items():
if person == logged_in_user:
continue
common = books.intersection(bookdict[logged_in_user])
if common:
print '%s also has %s' % (person, ', '.join(common))
def common_books(user):
user_books = {b for b, u in bookList if u == user}
for b, u in bookList:
if b in user_books and u != user:
print '{0} also has {1}'.format(u,b)
If you're trying to get the books that fred has in the list
filter(lambda x: x[1] == "fred", bookList)
Another version as per Bakuriu's comment.
class Session:
def __init__(self):
self.books = ["Wuthering Heights", "Everville"]
self.username = "fred"
bookList = [("Wuthering Heights", "fred"), ("Everville", "fred"), ("Wuthering Heights", "dan")]
if __name__ == "__main__":
session = Session()
for book in bookList:
if book[1] != session.username and book[0] in session.books:
print "{} also has {}".format(book[1], book[0])

IndexError: list index out of range (in query results)

I am having problems understanding how to work with query results. I asked about half a dozen questions about this but I still do not understand. I copy from previous code and I make it work somehow but since I don't understand the underlying concept the code breaks down if I make a minor change. I would really appreciate if you could tell me how you visualize what is happenning here and explain it to me. Thank you.
class ReceiveEmail(InboundMailHandler):
def receive(self, message):
logging.info("Received email from %s" % message.sender)
plaintext = message.bodies(content_type='text/plain')
for text in plaintext:
txtmsg = ""
txtmsg = text[1].decode()
logging.info("Body is %s" % txtmsg)
logging.info("CC email is %s" % ((message.cc).split(",")[1]))
query = User.all()
query.filter("userEmail =", ((message.cc).split(",")[1]))
results = query.fetch(1)
for result in results:
result.userScore += 1
um = results[0]
um.userScore = result.userScore
um.put()
In this code, as I understand it, the query takes the second email address from the cc list and fetches the result.
Then I increment the userScore by 1.
Next, I want to update this item in Datastore so I say
um = results[0]
um.userScore = result.userScore
um.put()
But this gives an index out of range error:
um = results[0]
IndexError: list index out of range
Why? I am imagining that results[0] is the zeroeth item of the results. Why is it out of range? Only thing I can think of is that, the list may be None. But I don't understand why. It must have the 1 item that was fetched.
Also, if I try to test for the first email address by changing the index from [1] to [0]
query.filter("userEmail =", ((message.cc).split(",")[0]))
then I don't get the IndexError.
What am I doing wrong here?
Thanks!
EDIT
See comments:
(message.cc).split(",")[0])
left a space in front of the emails (starting with the second email), so the query was not matching them;
>>> cc.split(",")
['cc12#example.com', ' cc13#example.com', ' cc13#example.com']
adding a space after comma fixed the problem:
>>> listcc = cc.split(", ")
>>> listcc
['cc12#example.com', 'cc13#example.com', 'cc13#example.com']
>>>
To understand the code break it down and look at it piece by piece:
class ReceiveEmail(InboundMailHandler):
def receive(self, message):
logging.info("Received email from %s" % message.sender)
# Get a list of CC addresses. This is basically a for loop.
cc_addresses = [address.strip() for address in message.cc.split(",")]
# The CC list goes with the message, not the bodies.
logging.info("CC email is %s" % (cc_addresses))
# Get and iterate over all of the *plain-text* bodies in the email.
plaintext = message.bodies(content_type='text/plain')
for text in plaintext:
txtmsg = ""
txtmsg = text[1].decode()
logging.info("Body is %s" % txtmsg)
# Setup a query object.
query = User.all()
# Filter the user objects to get only the emails in the CC list.
query.filter("userEmail IN", cc_addresses)
# But, only get at most 10 users.
users = query.fetch(10)
logging.info('Got %d user entities from the datastore.' % len(users))
# Iterate over each of the users increasing their score by one.
for user in users:
user.userScore += 1
# Now, write the users back to the datastore.
db.put(users)
logging.info('Wrote %d user entities.' % len(users))
I would make an adjustment to your model structure. When you create the User entity, I would set the key_name to the email address. You will be able to make your queries much more efficient.
Some references:
List Comprehension.
Query Object.
db.put().

Categories

Resources