Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

At what point are exceptions raised when using Django's ORM

Simple enough example - I have a bit of Django code that starts with a queryset...

queryset = MyModel.objects.all()

Later on it performs various filtering, depending on some configurable options...

if something:
    queryset = self.queryset.filter(foo=some_foo)

if another_thing:
    queryset = self.queryset.filter(bar=some_bar)

And finally it performs the lookup...

try:
    obj = queryset.get()
except ObjectDoesNotExist:
    raise ValidationError('Does not exist')

Now, because of the flexible way that the filtering needs to occur, it's possible that the some_foo or some_bar variables might not be of the correct type (eg. we could end up with an empty string attempting to filter against an integer field.) so it's possible for this code to end up raising a TypeError or a ValueError.

That's fine, and I can handle the case appropriately, but what's not clear to me from the ORM contract, is at what point should I expect those exceptions to be raised.

  • Will it occur on the .filter() statement?...
  • ...or on the .get() statement?...
  • ...or is in underspecified, and I handle it as able to occur on either? (Eg perhaps depending on the implementation of the database backend?)
like image 877
Tom Christie Avatar asked Jan 04 '13 13:01

Tom Christie


1 Answers

To answer the original question, a FieldError and ValueError are raised on the call to filter, when a new queryset is built:

>>> a = Account.objects.all()
>>> a = a.filter(id=3)
>>> a = a.filter(no_exist=3)
<snip>
FieldError: Cannot resolve keyword 'no_exist' into field. Choices are: active, created_on, group, id, ...

>>> a = Account.objects.all()
>>> a = a.filter(id='abc')
ValueError: invalid literal for int() with base 10: 'abc'

I'll also add that this pattern seems misleading to me, in that filter is normally used to return a list/iterable of models, rather than one as with get. For clarity and easier handling of the exceptions, I'd suggest this pattern:

kwargs = {}
if something:
    kwargs['foo'] = some_foo
if another_thing:
    kwargs['bar'] = some_bar

# handle if kwargs is empty
try:
    obj = MyModel.objects.get(**kwargs)
except (FieldError, ValueError, ObjectDoesNotExist):
    raise ValidationError('Does not exist')

The other added benefit is that, IIRC, the work of cloning querysets is relatively expensive, so you ignore that overhead, while at the same time making the code cleaner. Going back to your question, with this pattern there's no question where the exception will be raised.

like image 96
brianz Avatar answered Oct 10 '22 07:10

brianz