Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Django cookbook #129

Merged
merged 7 commits into from
Nov 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions docs/cookbook/django.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@

Integrate Django (or Django Rest Framework)
===========================================

This cookbook will use DRF (Django REST Framework) as an example, but the syntax is exactly
the same for Django.

First, let's `create a new command <https://docs.djangoproject.com/en/4.1/howto/custom-management-commands>`_ for setting up our jobs (we will call it ``inittasks.py``):

.. code-block:: python

# Create Rocketry app
from rocketry import Rocketry
app = Rocketry(config={"task_execution": "async"})

# import BaseCommand
from django.core.management.base import BaseCommand

# Create some tasks

@app.task('every 5 seconds')
async def do_things():
...

class Command(BaseCommand):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the BaseCommand from? I suppose it should be also imported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been resolved in the latest commit

help = "Setup the periodic tasks runner"

def handle(self, *args, **options):
app.run()


Next, you can just run in your ``entrypoint`` script (or in your shell) the following command:

.. code-block:: bash

... # connect to the database, set PRODUCTION=1, etc...

# if you deploy on Docker, it's probably better to run this in the background,
# so you can run your HTTP server afterwards
python manage.py inittasks &


And you're set ! It's really as simple as that!

Using the Asynchronous ORM
--------------------------

Now, you might have the following error if you use Querysets, or try to use the ORM in your task:

.. code-block:: bash

Django: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async

.. warning::

Some guides might suggest setting the ``DJANGO_ALLOW_ASYNC_UNSAFE`` environment value to ``True``.
This is **not** the recommended way. The UNSAFE keyword is here for a reason.

For our example, we will use the same file for simplicity, but it's fine to move your tasks to another file
(as long as you don't forget to import them !).

.. code-block:: python

# Create Rocketry app
from rocketry import Rocketry
app = Rocketry(config={"task_execution": "async"})

# import BaseCommand
from django.core.management.base import BaseCommand

# this is a synchronous operation that will work in an async context!
def do_things_with_users(name):
for user in User.objects.filter(name=name):
print(f'I did something with {user} !')

@app.task('every 5 seconds')
async def do_things():
await sync_to_async(do_things_with_users)(name='John Doe')
...

class Command(BaseCommand):
help = "Setup the periodic tasks runner"

def handle(self, *args, **options):
app.run()

You can even manually run the ``do_things_with_users`` function from a view now,
if that's something you would want. Let's add this view in our ``views.py`` file:

.. code-block:: python

# this could be any path where the code you want to run is stored
from api.commands import tasks as tasklist
import asyncio

...

class TaskView(APIView):
def get(self, request):

"""
This function is not ran by our scheduler, and runs in a synchronous context in our example
"""

name = request.GET.get('name')
if not name :
return Response({
'error': 'missing a parameter (expected something like ?name=job_name )'
},
status=HTTP_400_BAD_REQUEST,
)

try:

task = getattr(tasklist, name)

loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

loop.run_until_complete(task())

except Exception as err:

return Response({
'error': 'task failure',
'logs': f'Failed with: {str(err)}',
},
status=HTTP_500_INTERNAL_SERVER_ERROR,
)

return Response({
'message': 'successfully ran the task',
},
status=HTTP_200_OK,
)


.. note ::
You will only need to use ``sync_to_async`` if you use the asynchronous ORM. The usage is well documented in
`Django's documentation <https://docs.djangoproject.com/en/4.1/topics/async/>`_.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.djangoproject.com/en/latest/topics/async/ doesn't point to anything, sadly

3 changes: 2 additions & 1 deletion docs/cookbook/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ do more specific things.
robust_applications
controlling_runtime
fastapi
testing
django
testing