Thoughts on Django ORM and SimpleLazyObject

Just recently I wanted to create a custom middleware to augment the request object by adding an additional attribute to it. But I wanted this attribute to be lazy evaluated. If you have experience building with Django you should know that it offers lazy functions like reverse_lazy. Looking at the internals of how such things work, I've found that Django offers a module django.utils.functional.py that contains interesting functions and classes.

I liked SimpleLazyObject and found that it is used in the middleware called django.contrib.auth.middleware.AuthenticationMiddleware. Everything looked straightforward until I used my lazily evaluated attribute in the Django ORM query 😁

My attribute returns a list of values that was supposed to be used in the query filtering, but the main problem here is how Django resolves those values inside its ORM. The Query class has a method called resolve_lookup_value and if you look inside it you will see the following:

elif isinstance(value, (list, tuple)):
    # The items of the iterable may be expressions and therefore need
    # to be resolved independently.
    values = (
        self.resolve_lookup_value(sub_value, can_reuse, allow_joins, summarize)
        for sub_value in value
    )
    type_ = type(value)
    if hasattr(type_, "_make"):  # namedtuple
        return type_(*values)
    return type_(values)

This code means that your SimpleLazyObject instance will be transformed into a SimpleLazyObject instance of a generator producing a list of values returned by the initial instance. And it won't work as expected. My fix was quite simple, because I have a unique set of items, I replaced a list of values to a set of values (be aware that tuple won't work because of an elif condition that contains both list and tuple types).

If you have a more sophisticated scenario I would suggest to extend the LazyObject class (SimpleLazyObject is a subclass of LazyObject class) and provide your own implementation of _setup method.