does an update function in the API not raise exceptions - python

Before updating, I am running a query to get the town i want to update using an id. Now my assumptions are if the id does not exist I am supposed to have an error, but I am getting none.
def update_town(session, town_dict):
try:
town = session.query(Town).where(Town.id == town_dict["id"] )
print(town)
#setting new values
town.update(town_dict)
session.commit()
except NoResultFound:
return "not results found", 404
return "ok"

to check if this id exists, you could use .first()
town = session.query(Town).where(Town.id == town_dict["id"] ).first()
from the docs:
Return the first result of this Query or None if the result doesn’t contain any row.
you can then do a check, and only update town if this id exists:
if town:
town.update()
session.commit()
return "ok"
else:
return "not results found", 404
.first() is only one of many approaches for this, but it's pretty simple in practice. I wouldnt use a try-except block unless that's for some reason your preferred way

Related

Elegant design to report status on whether a function returned exception or values as status

I have this code structure:
#########file1.py#############
def newsCSVwriter(fileName):
try:
newsCleaner(fileName)
except Exception as e:
print "Exception: ", e
########file1.py#############
def newsCleaner(newsFile):
....
#########file2.py###########
try:
df1['newsFile'].apply(newsCSVwriter)
except Exception as e:
print "exception:",e
I want to write a csv that has a status column value of yes or no depending on whether newsCleaner(fileName) returns a value or exception. Should I implement the logic in file1 or file2? Also, an example will be great.
Assuming you don't actually need the returned value, in your newCSVWriter function do this:
try:
newsCleaner(fileName)
except:
return 'no'
else:
return 'yes'
How you structure your code in terms of files is dependant on what all of it does, but you've only posted part of it.
Stylistically I would rename them to something more informative than 'file1' and 'file2'. I would also have the function return a bool (True or False) instead, but that's up to you.

web2py: How to execute instructions before delete using SQLFORM.smartgrid

I use SQLFORM.smartgrid to show a list of records from a table (service_types). In each row of the smartgrid there is a delete link/button to delete the record. I want to executive some code before smartgrid/web2py actually deletes the record, for example I want to know if there are child records (services table) referencing this record, and if any, flash a message telling user that record cannot be deleted. How is this done?
db.py
db.define_table('service_types',
Field('type_name', requires=[IS_NOT_EMPTY(), IS_ALPHANUMERIC()]),
format='%(type_name)s',
)
db.define_table('services',
Field('service_name',requires=[IS_NOT_EMPTY(),IS_NOT_IN_DB(db,'services.service_name')]),
Field('service_type','reference service_types',requires=IS_IN_DB(db,db.service_types.id,
'%(type_name)s',
error_message='not in table',
zero=None),
ondelete='RESTRICT',
),
Field('interest_rate','decimal(15,2)',requires=IS_DECIMAL_IN_RANGE(0,100)),
Field('max_term','integer'),
auth.signature,
format='%(service_name)s',
)
db.services._plural='Services'
db.services._singular='Service'
if db(db.service_types).count() < 1:
db.service_types.insert(type_name='Loan')
db.service_types.insert(type_name='Contribution')
db.service_types.insert(type_name='Other')
controller
def list_services():
grid = SQLFORM.smartgrid(db.services
, fields = [db.services.service_name,db.services.service_type]
)
return locals()
view
{{extend 'layout.html'}}
{{=grid}}
There are two options. First, the deletable argument can be a function that takes the Row object of a given record and returns True or False to indicate whether the record is deletable. If it returns False, the "Delete" button will not be shown for that record, nor the delete operation be allowed on the server.
def can_delete(row):
return True if [some condition involving row] else False
grid = SQLFORM.smartgrid(..., deletable=can_delete)
Second, there is an ondelete argument that takes the db Table object and the record ID. It is called right before the delete operation, so to prevent the delete, you can do a redirect within that function:
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
session.flash = 'Cannot delete this record'
redirect(URL())
grid = SQLFORM.smartgrid(..., ondelete=ondelete)
Note, if the grid is loaded via an Ajax component and its actions are therefore performed via Ajax, using redirect within the ondelete method as shown above will not work well, as the redirect will have no effect and the table row will still be deleted from the grid in the browser (even though the database record was not deleted). In that case, an alternative approach is to return a non-200 HTTP response to the browser, which will prevent the client-side Javascript from deleting the row from the table (the delete happens only on success of the Ajax request). We should also set response.flash instead of session.flash (because we are not redirecting/reloading the whole page):
def ondelete(table, record_id):
record = table(record_id)
if [some condition]:
response.flash = 'Cannot delete this record'
raise HTTP(403)
Note, both the deletable and ondelete arguments can be dictionaries with table names as keys, so you can specify different values for different tables that might be linked from the smartgrid.
Finally, notice the delete URLs look like /appname/list_services/services/delete/services/[record ID]. So, in the controller, you can determine if a delete is being requested by checking if 'delete' in request.args. In that case, request.args[-2:] represents the table name and record ID, which you can use to do any checks.
From Anthony's answer I chose the second option and came up with the following:
def ondelete_service_type(service_type_table, service_type_id):
count = db(db.services.service_type == service_type_id).count()
if count > 0:
session.flash = T("Cant delete")
#redirect(URL('default','list_service_types#'))
else:
pass
return locals()
def list_service_types():
grid = SQLFORM.smartgrid(db.service_types
, fields = [db.service_types.type_name, db.services.service_name]
, ondelete = ondelete_service_type
)
return locals()
But, if I do this...
if count > 0:
session.flash = T("Cant delete")
else:
pass
return locals()
I get this error:
And if I do this:
if count > 0:
session.flash = T("Cant delete")
redirect(URL('default','list_service_types#')) <== please take note
else:
pass
return locals()
I get the flash error message Cant delete but the record appears deleted from the list, and reappears after a page refresh with F5 (apparently because the delete was not allowed in the database, which is intended).
Which one should I fix and how?
Note
If any of these issue is resolved I can accept Anthony's answer.

Python: Break-up large function into segments

I am creating a Bot for Reddit. I currently only have 1 very large function and I am looking to create sub-functions to make it more readable.
Here is a rough break-down of what it does
def replybot():
submissions = reversed(list(subreddit.get_new(limit=MAXPOSTS)))
for post in submissions:
try:
author = post.author.name
except AttributeError:
print "AttributeError: Author is deleted"
continue # Author is deleted. We don't care about this post.
# DOES PID EXIST IN DB? IF NOT ADD IT
cur.execute('SELECT * FROM oldposts WHERE ID=?', [pid])
sql.commit()
if cur.fetchone(): # Post is already in the database
continue
cur.execute('INSERT INTO oldposts VALUES(?)', [pid])
sql.commit()
...
I am looking to break the code up into segments i.e. put
try:
author = post.author.name
except AttributeError:
print "AttributeError: Author is deleted"
continue # Author is deleted. We don't care about this post.
in it's own function and call it from within replybot() but I run into the issue of calling continue. I get SyntaxError: 'continue' not properly in loop
Is there a way for me to do this?
If you take the inner part of a loop and convert it to its own function, it's no longer in a loop. The equivalent of continue in a loop, for a function, is return (i.e. terminate this iteration (which is now a function call) early).
Raise the error again instead of trying to continue. Either simply let it bubble to the main loop, or if you need better error handling, make your own custom error. For instance:
class MyNotFatalError(Exception):
pass
def do_something():
try:
a, b, c = 1, 2
except ValueError:
raise MyNotFatalError('Something went wrong')
# In your main function
for post in submissions:
try:
do_something()
do_some_other_thing()
do_some_more()
except MyNotFatalError as err:
continue # we could print out the error text here
do_some_last_thing()
It is probably better that way because you only catch errors you know you want to catch, and still let the program crash when there are actual bugs.
If you had simply caught ValueError that would also intercept and hide all other possible sources of the same kind of error.
as Claudiu said, when you broke inner commands into it's own function; It's not no longer in the loop and your code will be look like this:
def isNotAuthorDeleted(post):
try:
author = post.author.name
return author
except AttributeError:
print "AttributeError: Author is deleted"
return false
and your loop will be:
for post in submissions:
if not isNotAuthorDeleted(post):
continue

Python class variable not updating

I have a class that is taking in an Id and trying to update the variable current_account but when I print out the details of the current_account it hasn't updated.
Anyone got any ideas for this? New to python so might be doing something stupid that I can't see.
class UserData:
def __init__(self, db_conn=None):
if None == db_conn:
raise Exception("DB Connection Required.")
self.db = db_conn
self.set_my_account()
self.set_accounts()
self.set_current_account()
def set_current_account(self, account_id=None):
print account_id
if None == account_id:
self.current_account = self.my_account
else:
if len(self.accounts) > 0:
for account in self.accounts:
if account['_id'] == account_id:
self.current_account = account
print self.current_account['_id']
else:
raise Exception("No accounts available.")
Assume that set_my_account() gets a dictionary of account data and that set_accounts() get a list of dictionaries of account data.
So when I do the following:
user_data = UserData(db_conn=db_conn)
user_data.set_current_account(account_id=account_id)
Where db_conn is a valid database connection and account_id is a valid account id.
I get the following out of the above two lines.
None
518a310356c02c0756764b4e
512754cfc1f3d16c25c350b7
So the None value is from the declaration of the class and then the next two are from the call to set_current_account(). The first id value is what I'm trying to set. The second id value is what was already set from the class __init__() method.
There were a lot of redundancies an un-Pythonic constructions. I cleaned up the code to help me understand what you trying to do.
class UserData(object):
def __init__(self, db_conn):
self.db = db_conn
self.set_my_account()
self.set_accounts()
self.set_current_account()
def set_current_account(self, account_id=None):
print account_id
if account_id is None:
self.current_account = self.my_account
else:
if not self.accounts:
raise Exception("No accounts available.")
for account in self.accounts:
if account['_id'] == account_id:
self.current_account = account
print self.current_account['_id']
user_data = UserData(db_conn)
user_data.set_current_account(account_id)
You used default arguments (db_conn=None) when a call without an explicit argument is invalid. Yes, you can now call __init__(None) but you could also call __init__('Nalum'); you can't protect against everything.
By moving the "No accounts" exception the block fast-fails and you save one level of indention.
The call UserData(db_conn=db_conn) is valid but unecessarily repetitive.
Unfortunately, I still can't figure out what you are trying to accomplish and this is perhaps the largest flaw. Variable names are terribly important for help the reader (which may be the future you) make sense of code. current_account, my_account, account_id and current_account['_id'] so obscure the intention that you should really consider more distinct, informative names.
Figured out what it was.
The data was being changed else where in the code base. It is now working as expected.
Thanks guys for pointing out the Python centric things that I was doing wrong, good to get it.

Mysterious behavior in try-except-finally

Excuse the vague title, I didn't know how else to state this.
I have a task worker request handler that fetches data from a URL and writes it to blobstore and saves the data's blob_key to a ListProperty in datastore. I've tried to simplifly the code for clarity here:
class Fetch(webapp2.RequestHandler):
def get(self):
url = self.request.get('url')
itemKey = self.request.get('itemKey')
item = MyModel.get(itemKey)
try:
result = urlfetch.fetch(url=url)
if result.status_code == 200:
saveDataResult = save_data(result.content, itemKey)
if saveDataResult is False:
raise Exception('error saving data')
else:
raise Exception('error fetching data: %s' % result.status_code)
item.status = 'success'
except Exception:
item.status = 'failed'
finally:
item.put()
def save_data(data, itemKey)
try:
#write data to blobstore and get its blob_key...
blob_key = files.blobstore.get_blob_key(file_name)
item = MyModel.get(itemKey)
item.blobKey.append(blob_key)
item.put()
return True
except:
return False
Now the problem I'm having is, when saveDataResult returns True, its status is set to 'success' but its blobKey property contains no value, even though a blob_key was generated and the data successfully written. I can't see what's causing this to save my life, please help.
Without much more information it is very difficult to determine what's happening. Here's my educated guess:
MyModel.get(itemKey) is called both in get() and save_data(). I surmise that it's returning two different objects representing the item. When the blobKey gets updated in save_data, the update is occurring only in the object fetched in save_data. When you later examine it outside that scope, you're looking at a different object.
Whether this is correct or not will depend on the implementation of MyModel.get().
Also, you do realize that you're calling item.put() twice, right?
The problem is here
finally:
item.put()
this single call overrides the data saved by save_data() because it references an older object of item.
My suggestion would be you do the status updates from save_data() i.e item.status = 'success'
or move item = MyModel.get(itemKey) to come after save_data() so you can fetch the updated object.
The problem is that when you call save_data() with item = MyModel.get(itemKey)
which is again called from the class Fetch you end up having two different objects and therefore overwriting the one in save_data() and hence when you go to your model datastore no data for blobkey is stored as its overwritten.
Try doing everything in the class or you do not use item = MyModel.get(itemKey) twice.

Categories

Resources