Asynchronous ORM

(1) Okay let me then summarize the syntaxes in async context that are the best candidates so far:

  • Single-Model Returning Methods
    • await objects.filter(...).order_by(...).first_async()
    • await Question.objects.get_async(id=...)
  • Multiple-Model Returning Methods
    • choices = await question.choice_set.all_async() # need not be prefetched
    • choices = question.choice_set.all() # raises if not prefetched
  • Model Field Get
    • q = await choice.question_async
  • Model Field Set (Deferred)
    • choice.question = q
  • Single-Model Save
    • await question.save_async()
  • Multiple-Model Save
    • await choice_set.update_async(...)
    • await bulk_update_async(...)
    • await bulk_create_async(...)

(2) A user who creates their own model class which overrides save who wants their model to also be used in an async context should also override save_async:

class ProjectTextFile(models.Model):
    name = models.CharField(max_length=50)
    content = models.TextField(blank=True)

    def save(self, *args, **kwargs):
        if ProjectTextFile.is_content_too_big(self.name, self.content):
            raise ValidationError(...)
        super().save(*args, **kwargs)
    
    async def save_async(self, *args, **kwargs):
        if ProjectTextFile.is_content_too_big(self.name, self.content):
            raise ValidationError(...)
        await super().save_async(*args, **kwargs)

If only one of save or save_async is overridden, I’d have to think more about the consequences…

  • The built-in admin app currently would always use the synchronous save and ignore any save_async.
  • User code that was familiar with the model class would presumably invoke whichever save method was implemented.

(3) It feels a bit weird to have a method’s return type vary depending on whether it is being invoked from a sync context vs. an async context, but perhaps it could work…

I could see this trick working for methods like .get() and .save(). For model fields the getter would have to return a proxy if invoked from an async context, but the setter would continue to defer any actual set operation regardless of whether it is invoked from a sync/async context.


(4) Ick. Inspecting the current stack frame is almost certainly quite slow. Better stick with querying the event loop.