django-unasyncify documentation

django-unasyncify is a project to help maintain sync and async implementations of APIs, focused on code relying on Django’s asynchronous API design.

It uses code generation to create separate sets of functions, that have no runtime dependencies on django-unasyncify nor do they rely on any wrappers to manage functionality, meaning there are no runtime costs to using this tool.

Note

This project is still exploring the design space for this problem. If you encounter any difficulties using this tool, please reach out and share feedback! The hope is that this tool should work well out of the box for projects needing to manage interactions with Django’s async API layer.

It does this by relying on libCST’s code modification tooling.

What It Does

django-unasyncify generates a synchronous variant of a method based on an asynchronous implementation.

For example, consider the following snippet:

@generate_unasynced
async def acreate_model_instance(self, data):
    """
    Return a new instance of the session model object, which
    represents the current session state.
    Intended to be used for saving the
    session data to the database.
    """
    return self.model(
        session_key=await self._aget_or_create_session_key(),
        session_data=self.encode(data),
        expire_date=await self.aget_expiry_date(),
    )

The above method is annotated with @generate_unasynced, indicating we want a synchronous variant for this method.

After running django-unasyncify, the file containing this method is modified to become the following:

@from_codegen
def create_model_instance(self, data):
    """
    Return a new instance of the session model object, which
    represents the current session state.
    Intended to be used for saving the
    session data to the database.
    """
    return self.model(
        session_key=self._get_or_create_session_key(),
        session_data=self.encode(data),
        expire_date=self.get_expiry_date(),
    )

@generate_unasynced
async def acreate_model_instance(self, data):
    """
    Return a new instance of the session model object, which
    represents the current session state.
    Intended to be used for saving the
    session data to the database.
    """
    return self.model(
        session_key=await self._aget_or_create_session_key(),
        session_data=self.encode(data),
        expire_date=await self.aget_expiry_date(),
    )

From the async implementation, a synchronous variant has been generated. It maintains comments and layout, but rewrites await expressions to no longer be await. It is able to do this for Django-related code by relying on the naming conventions for sync and async variants of Django APIs.

The synchronous variant has also been annotated with @from_codegen. This decorator serves both as documentation and a way for django-unasyncify to be able to run idempotently.

The rough naming convention for a Django API is that the asynchronous variant for a synchronous API is gotten by adding an a to the name. get becomes aget, save becomes asave, etc.

Note

  • Asynchronous methods have names starting with a.

  • Their synchronous variant is found by removing the a.

  • Asynchronous private methods have names starting with _a.

  • Their synchronous variant is found by replacing _a with _.