Hi everyone! I’m sorry I’m a bit late for async-orm discussion
I have an idea how to make a version that supports both sync and async. The main idea is to extract all I/O into drivers. The code should not be allowed to do any I/O. Instead it should yield SQL, that is to be executed later somehow (in a sync or async way):
SQL = namedtuple("SQL", ["sql", "params"])
For example, as you know, we have a save
method in models.Model
. Let’s rewrite it in the following way:
def save(...):
...
yield from self.save_base(...)
Yes, a generator. What it yields? SQL instances, of course. A couple more edits should be made, of course.
def _save_table(...):
...
yield from self._do_update(...)
def _update(self, values):
...
yield from query.get_compiler(self.db).execute_sql(CURSOR)
def execute_sql(self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE):
sql, params = self.as_sql()
yield sql, params
Given that generators can return values, not only yield, this is a very effective way to work with existing codebase
Now, our models.Model
is incompatible with the initial version (which is not a generator). How do we fix that? Easy: we should rename our class with a generator into AbstractModel, and then make
class Model(AbstractModel):
def save(...):
queries = super().save(...)
sync_driver.execute(queries)
class AsyncModel(AbstractModel):
async def save(...):
queries = super().save(...)
await async_driver.execute(queries)
What regarding lazy attributes: I’ve found out my point of view (among many others) has already been expressed in the Asynchronous ORM post
I consider that we should keep current syntax for the case when all objects have already been prefetched, that means, we do have them in our cache. When we do want to make a query, we should use another syntax, like this:
await obj.R("related_objects").all()
Now the most interesting part: if I ever wanted to go ahead with my proposal, then I wouldn’t actually need any approval from Django. The success of the project will be defined by how good the async version will be. Because that’s what’s actually missing from Django. For the sync usecase people will be able to use the traditional django (and will be suggested to do so, for some period of time).
But then, if the async version will be successful, then there are high chances that some day that will become the new version of django (because it will contain the sync version too). And in that case we probably want to make less breaking changes. How Django Foundation does usually treat such cases?
And also, please tell me what you think about my aproach/