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")
Related
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.
I was wondering if its possible to write a handling exceptions like with 2 or more except with different task to do.
I'm using Django==1.6.1 and Python 2.7
try:
foo_instance = foo.objects.get(field_name='unknown')
except foo.DoesNotExist:
new_rec = foo.objects.create(field_name='unknown')
new_rec.save()
foo_instance = foo.objects.get(field_name='unknown')
except foo.MultipleObjectsReturned:
foo_list = foo.objects.filter(field_name='unknown')
for record in foo_list[1:]:
print 'Deleting foo id: ', record.id
record.delete()
foo_instance = foo.objects.get(field_name='unknown')
You could use multiple try: except: but in your current scenario Why don't you use get_or_create ?
try: expect: contain all errors on 'Exception'. for this syntax is all
except Exception as e:
get_or_create(defaults=None, **kwargs)
A convenience method for looking up an object with the given kwargs
(may be empty if your model has defaults for all fields), creating one
if necessary.
Returns a tuple of (object, created), where object is the retrieved or
created object and created is a boolean specifying whether a new
object was created.
This reduces your above code to -
obj, created = foo.objects.get_or_create(field_name='unknown')
if created:
obj.save()
I think get_or_create raises IntegrityError or MultipleObjectsReturned, to handle those simply wrap it in a try:
try:
obj, created = foo.objects.get_or_create(field_name='unknown')
if created:
obj.save()
except IntegrityError:
#do something
except MultipleObjectsReturned:
#do something else
except Exception as e:
raise e
If I catch a KeyError, how can I tell what lookup failed?
def poijson2xml(location_node, POI_JSON):
try:
man_json = POI_JSON["FastestMan"]
woman_json = POI_JSON["FastestWoman"]
except KeyError:
# How can I tell what key ("FastestMan" or "FastestWoman") caused the error?
LogErrorMessage ("POIJSON2XML", "Can't find mandatory key in JSON")
Take the current exception (I used it as e in this case); then for a KeyError the first argument is the key that raised the exception. Therefore we can do:
except KeyError as e: # One would do it as 'KeyError, e:' in Python 2.
cause = e.args[0]
With that, you have the offending key stored in cause.
Expanding your sample code, your log might look like this:
def poijson2xml(location_node, POI_JSON):
try:
man_json = POI_JSON["FastestMan"]
woman_json = POI_JSON["FastestWoman"]
except KeyError as e:
LogErrorMessage ("POIJSON2XML", "Can't find mandatory key '"
e.args[0]
"' in JSON")
It should be noted that e.message works in Python 2 but not Python 3, so it shouldn't be used.
Not sure if you're using any modules to assist you - if the JSON is coming in as a dict, one can use dict.get() towards a useful end.
def POIJSON2DOM (location_node, POI_JSON):
man_JSON = POI_JSON.get("FastestMan", 'No Data for fastest man')
woman_JSON = POI_JSON.get("FastestWoman", 'No Data for fastest woman')
#work with the answers as you see fit
dict.get() takes two arguments - the first being the key you want, the second being the value to return if that key does not exist.
If you import the sys module you can get exception info with sys.exc_info()
like this:
def POIJSON2DOM (location_node, POI_JSON):
try:
man_JSON = POI_JSON["FastestMan"]
woman_JSON = POI_JSON["FastestWoman"]
except KeyError:
# you can inspect these variables for error information
err_type, err_value, err_traceback = sys.exc_info()
REDI.LogErrorMessage ("POIJSON2DOM", "Can't find mandatory key in JSON")
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 ...
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)