Using nested Django transaction.atomic() to handle exceptions? - python

Django's docs say this about transaction.atomic() and exceptions:
https://docs.djangoproject.com/en/1.10/topics/db/transactions/#django.db.transaction.atomic
Avoid catching exceptions inside atomic!
...
The correct way to catch database errors is around an atomic block as shown above. If necessary, add an extra atomic block for this purpose. This pattern has another advantage: it delimits explicitly which operations will be rolled back if an exception occurs.
...
What does "If necessary, add an extra atomic block for this purpose." look like? Can I do this or does this cause "unexpected behavior"?
valid = True
errors = []
objects = MyModel.objects.all()
try:
with transaction.atomic():
for obj in objects:
try:
# Update and save obj here...
except:
errors.append("obj {} had errors".format(obj.pk))
valid = False
if not valid:
raise Exception('batch update failed.')
except Exception as ex:
# Handle it..
Do they mean to write it like this? If so, why is this different?
valid = True
errors = []
objects = MyModel.objects.all()
try:
with transaction.atomic():
for obj in objects:
try:
with transaction.atomic(): # Here's my 'extra atomic block'
# Update and save obj here...
except:
errors.append("obj {} had errors".format(obj.pk))
valid = False
if not valid:
raise Exception('batch update failed.')
except Exception as ex:
# Handle it..

Django triggers rolback only when transaction block catches DatabaseError (or its subclass), So you shouldn't catch it before it. If you add second transaction block (your 2nd example), error is caught, transaction is marked to rollbacked, and then you can do whatever you want.
I think that you should be good it yo re-raise exactly the same error, but this is just a guess.

Related

How to gain program control back after handling the exception?

 am developing a python application . I have validated customer id from database. Means if the entered custid is present in database, i am raising an exception. In exception class i am printing the message . So far it is printing the message. But i am not sure how to get control back to the statement where i am taking the input. 
main app
Custid=input("enter custid)
Validate_custid(Custid)
Print(Custid)
validate_custid module
From connections import cursor
From customExceptions import invalidcustidException
Def validate_custid(custid):
Cursor.execute("select count(custid) from customer where custid=:custid",{"custid":custid})
For row in cursor:
Count=row[0]
If Count==0:
Raise invalidcustidException
So far its printing the message in exception.now i want my program to take custid as input whenever this exception occurs. The process should iterate until user enters valid custid.
You should use a try-except block with else statement:
while True:
custid = input('Input custom Id: ')
try:
# Put your code that may be throw an exception here
validate_custid(custid)
except InvalidcustidException as err:
# Handle the exception here
print(err.strerror)
continue # start a new loop
else:
# The part of code that will execute when no exceptions thrown
print('Your custom id {} is valid.'.format(custid))
break # escape the while loop
Take a look at here: https://docs.python.org/3.4/tutorial/errors.html#handling-exceptions
You'll want a try except block.
try:
# portion of code that may throw exception
except invalidcuspidError:
# stuff you want to do when exception thrown
See https://docs.python.org/2/tutorial/errors.html for more.
What you are trying to do is called exception handling. I think the Python docs explain this better than me, so here you go: https://docs.python.org/2/tutorial/errors.html#handling-exceptions

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 Generic Exception to Catch Rest Of Errors

This is a python script that runs in a Django environment by Celery. I need to create a catch the 'rest of the errors' and raise an exception so Celery will send a email on that exception.
Would this be the best way?
for thread in thread_batch:
try:
obj = query_set.get(thread_id=thread['thread_id'])
for key, value in thread.iteritems():
setattr(obj, key, value)
obj.unanswered = True
except ThreadVault.DoesNotExist:
obj = ThreadVault(**thread)
except:
raise Exception("There has been a unknown error in the database")
obj.save()
Yes, and empty except will catch any exception different from ThreadVault.DoesNotExist (in this case). But you can improve your code a little more.
Try always to put the less code possible inside the try block. You code could be:
for thread in thread_batch:
try:
obj = query_set.get(thread_id=thread['thread_id'])
except ThreadVault.DoesNotExist:
obj = ThreadVault(**thread)
except:
raise Exception("There has been a unknown error in the database")
else: # Note we add the else statement here.
for key, value in thread.iteritems():
setattr(obj, key, value)
obj.unanswered = True
# Since save function also hits the database
# it should be within a try block as well.
try:
obj.save()
except:
raise Exception("There has been a unknown error in the database")

Python error exception handling with less return statements?

I have a function with a try catch block where I have:
def testfunction():
try:
good = myfunction()
return good
except ExceptionOne:
error="Error ExceptionOne".
return error
except ExceptionTwo:
error="Error ExceptionTwo".
return error
except ExceptionThree:
error="Error ExceptionThree".
return error
Is there a way such that I could structure this in such a way where have a single return statement for all the exceptions?
What about something like:
def testfunction():
try:
good = myfunction() # or `return myfunction()` if the case is actually this simple
except (ExceptionOne, ExceptionTwo, ExceptionThree) as err:
error = 'Error %s' % err.__class__.__name__
return error
return good
Of course, I'm not exactly sure what the purpose of this is. Why not just let the exception propogate and handle it higher up? As it is, you need logic to check the return value to see if it's good or not anyway, I think it would be cleaner if that logic was bound up in exception handling rather than in type checking or string value checking ...

Try two expressions with `try except`

I have two expressions. I need to try one expression, if it is raise an exception try another, but if the second raises an exception too - to raise the exception.
I tried this, but it is looks ugly and I am not sure it is the best way to solve this issue:
try:
image = self.images.order_by(func.random()).limit(1)
except:
try:
image = self.images.order_by(func.rand()).limit(1)
except ProgrammingError:
raise ProgrammingError(
'The database engine must be PostgtreSQL or MySQL')
How do you do it?
Making a separate function is very helpful.
def get_random_image(self):
for rand in func.random, func.rand:
try:
return self.images.order_by(rand()).limit(1)
except ProgrammingError:
pass
raise ProgrammingError('This database engine is not supported')
Use a loop:
for methname in ("random", "rand"):
try:
image = self.images.order_by(getattr(func, methname)()).limit(1)
break
except ProgrammingError:
continue
else:
raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")
The loop's else clause is executed only if the loop terminates normally (i.e., without a break) which is why we break after doing the image assignment. If you consider this too tricksy, because the else clause is so infrequently used with for, then this would also work:
image = None
for methname in ("random", "rand"):
try:
image = self.images.order_by(getattr(func, methname)()).limit(1)
except ProgrammingError:
continue
if not image:
raise ProgrammingError("The database engine must be PostgtreSQL or MySQL")
In this particular case, I'd actually try and detect the database before selecting the function. Can you reach the database connection from your code? If so, just switch on the drivername:
random = None
if drivername == 'postgres':
random = func.random
elif drivername == 'mysql':
random = func.rand
else:
raise ValueError('This module requires that you use PostgreSQL or MySQL')
Then, when selecting images, use the random value:
image = self.images.order_by(random()).limit(1)
Actually it MIGHT be a design flaw. Raising exceptions is to act on an event that should normally not occur. If you want to do something functionally into an except (except for handling the exception), than it looks like the first statement that you want to try is not a statement that should get an exception at all.
So instead of:
try:
do statement 1
except ...
try:
do statement 2
except:
think about :
if (statement_1 result == ...)
try:
do statement 2
except:
If you want to check if rand or random is a function of a class, you also could use
if 'rand' in dir(some object of a class)

Categories

Resources