diff --git a/django_q/tests/tasks.py b/django_q/tests/tasks.py index 1263a81..9c8f8ad 100644 --- a/django_q/tests/tasks.py +++ b/django_q/tests/tasks.py @@ -35,5 +35,9 @@ def get_task_name(task): return task.name +def get_user_id(user): + return user.id + + def result(obj): print('RESULT HOOK {} : {}'.format(obj.name, obj.result)) diff --git a/django_q/tests/test_cluster.py b/django_q/tests/test_cluster.py index 8536117..038384a 100644 --- a/django_q/tests/test_cluster.py +++ b/django_q/tests/test_cluster.py @@ -1,8 +1,9 @@ import sys -import os from multiprocessing import Queue, Event, Value import threading +from django.contrib.auth.models import User +import os import pytest myPath = os.path.dirname(os.path.abspath(__file__)) @@ -32,11 +33,13 @@ def r(): def test_redis_connection(r): assert r.ping() is True + @pytest.mark.django_db def test_sync(r): task = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, redis=r, sync=True) assert result(task) == 1506 + @pytest.mark.django_db def test_cluster_initial(r): list_key = 'initial_test:q' @@ -98,28 +101,30 @@ def test_cluster(r): @pytest.mark.django_db -def test_async(r): +def test_async(r, admin_user): list_key = 'cluster_test:q' r.delete(list_key) a = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, hook='django_q.tests.test_cluster.assert_result', - list_key=list_key) + list_key=list_key, redis=r) b = async('django_q.tests.tasks.count_letters2', WordClass(), hook='django_q.tests.test_cluster.assert_result', - list_key=list_key) + list_key=list_key, redis=r) # unknown argument c = async('django_q.tests.tasks.count_letters', DEFAULT_WORDLIST, 'oneargumentoomany', - hook='django_q.tests.test_cluster.assert_bad_result', list_key=list_key) + hook='django_q.tests.test_cluster.assert_bad_result', list_key=list_key, redis=r) # unknown function d = async('django_q.tests.tasks.does_not_exist', WordClass(), hook='django_q.tests.test_cluster.assert_bad_result', - list_key=list_key) + list_key=list_key, redis=r) # function without result - e = async('django_q.tests.tasks.countdown', 100000, list_key=list_key) + e = async('django_q.tests.tasks.countdown', 100000, list_key=list_key, redis=r) # function as instance - f = async(multiply, 753, 2, hook=assert_result, list_key=list_key) + f = async(multiply, 753, 2, hook=assert_result, list_key=list_key, redis=r) # model as argument - g = async('django_q.tests.tasks.get_task_name', Task(name='John'), list_key=list_key) + g = async('django_q.tests.tasks.get_task_name', Task(name='John'), list_key=list_key, redis=r) # args and kwargs and broken hook h = async('django_q.tests.tasks.word_multiply', 2, word='django', hook='fail.me', list_key=list_key, redis=r) - # check if everything has a task name + # args unpickle test + j = async('django_q.tests.tasks.get_user_id', admin_user, list_key=list_key, redis=r) + # check if everything has a task id assert isinstance(a, str) assert isinstance(b, str) assert isinstance(c, str) @@ -128,8 +133,9 @@ def test_async(r): assert isinstance(f, str) assert isinstance(g, str) assert isinstance(h, str) + assert isinstance(j, str) # run the cluster to execute the tasks - task_count = 8 + task_count = 9 assert r.llen(list_key) == task_count task_queue = Queue() stop_event = Event() @@ -187,6 +193,11 @@ def test_async(r): assert result_h is not None assert result_h.success is True assert result(h) == 12 + # task j + result_j = fetch(j) + assert result_j is not None + assert result_j.success is True + assert result_j.result == result_j.args[0].id r.delete(list_key) diff --git a/docs/examples.rst b/docs/examples.rst index 3c25c8f..1c984e8 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -1,10 +1,12 @@ Examples -------- -Async emails -============ +Emails +====== -Sending an email can take a while so why not queue it:: +Sending an email can take a while so why not queue it: + +.. code-block:: python # Welcome mail with follow up example from datetime import timedelta @@ -39,7 +41,9 @@ Since you're only telling Django Q to take care of the emails, you can quickly m Signals ======= -A good place to use async tasks are Django's model signals. You don't want to delay the saving or creation of objects, but sometimes you want to trigger a lot of actions:: +A good place to use async tasks are Django's model signals. You don't want to delay the saving or creation of objects, but sometimes you want to trigger a lot of actions: + +.. code-block:: python # Message on object change from django.contrib.auth.models import User @@ -60,7 +64,9 @@ A good place to use async tasks are Django's model signals. You don't want to de # tell everyone async('tasks.inform_everyone', instance) -The task will send a message to everyone else informing them that the users email address has changed. Note that this adds almost no overhead to the save action:: +The task will send a message to everyone else informing them that the users email address has changed. Note that this adds almost no overhead to the save action: + +.. code-block:: python # tasks.py def inform_everyone(user): @@ -72,6 +78,7 @@ The task will send a message to everyone else informing them that the users emai 'from@example.com', [u.email])) return send_mass_mail(mails) +.. code-block:: python # or do it async again def inform_everyone_async(user): @@ -82,8 +89,52 @@ The task will send a message to everyone else informing them that the users emai 'New email', msg, 'from@example.com', [u.email]) +Of course you can do other things beside sending emails. These are just generic examples. You can use signals with async to update fields in other objects too. +Let's say this users email address is not just on the User object, but you stored it in some other places too without a reference. +By attaching an async action to the save signal, you can now update that email address in those other places without impacting the the time it takes to return your views. +Reports +======= + +In this example the user requests a report and we let the cluster do the generating, while handling the result with a hook. + +.. code-block:: python + + # Report generation with hook example + from django_q import async + + # views.py + # user requests a report. + def create_report(request): + async('tasks.create_html_report', + request.user, + hook='tasks.email_report') + + # tasks.py + # report generator + def create_html_report(user): + html_report = 'We had a great quarter!' + return html_report + + # report mailer + def email_report(task): + if task.success: + # Email the report + async('django.core.mail.send_mail', + 'The report you requested', + task.result, + 'from@example.com', + task.args[0].email) + else: + # Tell the admins something went wrong + async('django.core.mail.mail_admins', + 'Report generation failed', + task.result) + + +The hook is practical here, cause it allows us to detach the sending task from the report generation function and to report on possible failures. + .. note:: If you have an example you want to share, please submit a pull request on `github `__.