django-database-task 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. {django_database_task-0.1.0/django_database_task.egg-info → django_database_task-0.2.0}/PKG-INFO +244 -4
  2. {django_database_task-0.1.0 → django_database_task-0.2.0}/README.md +240 -3
  3. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/__init__.py +3 -1
  4. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/backends.py +14 -0
  5. django_database_task-0.2.0/django_database_task/cloudtasks/__init__.py +83 -0
  6. django_database_task-0.2.0/django_database_task/cloudtasks/auth.py +128 -0
  7. django_database_task-0.2.0/django_database_task/cloudtasks/backend.py +235 -0
  8. django_database_task-0.2.0/django_database_task/cloudtasks/detection.py +123 -0
  9. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/executor.py +69 -0
  10. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/urls.py +9 -1
  11. django_database_task-0.2.0/django_database_task/views.py +477 -0
  12. {django_database_task-0.1.0 → django_database_task-0.2.0/django_database_task.egg-info}/PKG-INFO +244 -4
  13. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task.egg-info/SOURCES.txt +4 -0
  14. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task.egg-info/requires.txt +4 -0
  15. {django_database_task-0.1.0 → django_database_task-0.2.0}/pyproject.toml +5 -1
  16. django_database_task-0.2.0/tests/test_views.py +799 -0
  17. django_database_task-0.1.0/django_database_task/views.py +0 -205
  18. django_database_task-0.1.0/tests/test_views.py +0 -261
  19. {django_database_task-0.1.0 → django_database_task-0.2.0}/LICENSE +0 -0
  20. {django_database_task-0.1.0 → django_database_task-0.2.0}/MANIFEST.in +0 -0
  21. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/admin.py +0 -0
  22. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/apps.py +0 -0
  23. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/locale/ja/LC_MESSAGES/django.mo +0 -0
  24. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/locale/ja/LC_MESSAGES/django.po +0 -0
  25. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/management/__init__.py +0 -0
  26. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/management/commands/__init__.py +0 -0
  27. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/management/commands/purge_completed_database_tasks.py +0 -0
  28. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/management/commands/run_database_tasks.py +0 -0
  29. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/migrations/0001_initial.py +0 -0
  30. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/migrations/__init__.py +0 -0
  31. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task/models.py +0 -0
  32. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task.egg-info/dependency_links.txt +0 -0
  33. {django_database_task-0.1.0 → django_database_task-0.2.0}/django_database_task.egg-info/top_level.txt +0 -0
  34. {django_database_task-0.1.0 → django_database_task-0.2.0}/setup.cfg +0 -0
  35. {django_database_task-0.1.0 → django_database_task-0.2.0}/tests/test_admin.py +0 -0
  36. {django_database_task-0.1.0 → django_database_task-0.2.0}/tests/test_backend.py +0 -0
  37. {django_database_task-0.1.0 → django_database_task-0.2.0}/tests/test_commands.py +0 -0
  38. {django_database_task-0.1.0 → django_database_task-0.2.0}/tests/test_executor.py +0 -0
  39. {django_database_task-0.1.0 → django_database_task-0.2.0}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-database-task
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: A database-backed task queue backend for Django 6.0's built-in task framework
5
5
  Author-email: Shinya Okano <tokibito@gmail.com>
6
6
  Maintainer-email: Shinya Okano <tokibito@gmail.com>
@@ -25,6 +25,9 @@ Requires-Python: >=3.12
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: Django>=6.0
28
+ Provides-Extra: cloudtasks
29
+ Requires-Dist: google-cloud-tasks>=2.0.0; extra == "cloudtasks"
30
+ Requires-Dist: google-auth>=2.0.0; extra == "cloudtasks"
28
31
  Provides-Extra: dev
29
32
  Requires-Dist: pytest>=7.0; extra == "dev"
30
33
  Requires-Dist: pytest-django>=4.5; extra == "dev"
@@ -43,6 +46,7 @@ A database-backed task queue backend for Django 6.0's built-in task framework.
43
46
  - **Exclusive locking** - Prevents duplicate task execution with `SELECT FOR UPDATE SKIP LOCKED`
44
47
  - **Django Admin integration** - View and manage tasks from the admin interface
45
48
  - **Async support** - Supports async task functions
49
+ - **Google Cloud Tasks integration** - Optional backend for GAE/Cloud Run with auto-detection
46
50
 
47
51
  ## Architecture
48
52
 
@@ -291,7 +295,12 @@ python manage.py purge_completed_database_tasks [options]
291
295
  You can also process tasks programmatically without management commands:
292
296
 
293
297
  ```python
294
- from django_database_task import process_one_task, process_tasks, get_pending_task_count
298
+ from django_database_task import (
299
+ process_one_task,
300
+ process_tasks,
301
+ get_pending_task_count,
302
+ run_task_by_id,
303
+ )
295
304
 
296
305
  # Process a single task
297
306
  result = process_one_task()
@@ -308,6 +317,14 @@ results = process_tasks(queue_name="emails", max_tasks=5)
308
317
  # Get pending task count
309
318
  count = get_pending_task_count()
310
319
  print(f"Pending tasks: {count}")
320
+
321
+ # Execute a specific task by ID
322
+ result = run_task_by_id("550e8400-e29b-41d4-a716-446655440000")
323
+ if result:
324
+ print(f"Executed: {result.id}, status: {result.status}")
325
+
326
+ # Retry a failed task
327
+ result = run_task_by_id("...", allow_retry=True)
311
328
  ```
312
329
 
313
330
  ## HTTP Endpoints (Optional)
@@ -335,6 +352,8 @@ urlpatterns = [
335
352
  | `/tasks/run/` | POST | Process multiple pending tasks |
336
353
  | `/tasks/run-one/` | POST | Process a single pending task |
337
354
  | `/tasks/status/` | GET | Get pending task count |
355
+ | `/tasks/execute/<uuid>/` | POST | Execute a specific task by ID |
356
+ | `/tasks/purge/` | POST | Delete completed tasks |
338
357
 
339
358
  ### Request Parameters
340
359
 
@@ -385,6 +404,52 @@ Response:
385
404
  {"pending_count": 5}
386
405
  ```
387
406
 
407
+ #### POST `/tasks/execute/<uuid>/`
408
+
409
+ Execute a specific task by ID. This endpoint is designed for external trigger systems
410
+ (e.g., Cloud Tasks, webhooks) that need to execute a specific task.
411
+
412
+ | Parameter | Type | Default | Description |
413
+ |-----------|------|---------|-------------|
414
+ | `fail_on_error` | query string | "false" | Return HTTP 500 on task failure |
415
+ | `allow_retry` | query string | "false" | Allow re-execution of FAILED tasks |
416
+
417
+ Response (success):
418
+ ```json
419
+ {"executed": true, "result": {"id": "uuid", "status": "SUCCESSFUL", "task_path": "..."}}
420
+ ```
421
+
422
+ Response (task not in executable status):
423
+ ```json
424
+ {"executed": false, "reason": "Task is not in READY status"}
425
+ ```
426
+
427
+ Response (task not found):
428
+ ```json
429
+ {"error": "Task not found"} // HTTP 404
430
+ ```
431
+
432
+ #### POST `/tasks/purge/`
433
+
434
+ Delete completed tasks from the database. Useful for cron-based cleanup.
435
+
436
+ | Parameter | Type | Default | Description |
437
+ |-----------|------|---------|-------------|
438
+ | `days` | int | 0 | Delete tasks completed more than N days ago (0=all) |
439
+ | `status` | string | "SUCCESSFUL,FAILED" | Target statuses, comma-separated |
440
+ | `batch_size` | int | 1000 | Number of tasks to delete at once (max: 10000) |
441
+ | `dry_run` | bool | false | If true, return count without deleting |
442
+
443
+ Response:
444
+ ```json
445
+ {"deleted": 150, "dry_run": false}
446
+ ```
447
+
448
+ Response (dry run):
449
+ ```json
450
+ {"count": 150, "dry_run": true}
451
+ ```
452
+
388
453
  ### Example Usage
389
454
 
390
455
  ```bash
@@ -400,6 +465,16 @@ curl -X POST http://localhost:8000/tasks/run/ \
400
465
 
401
466
  # Get pending task count
402
467
  curl http://localhost:8000/tasks/status/
468
+
469
+ # Delete tasks completed more than 7 days ago
470
+ curl -X POST http://localhost:8000/tasks/purge/ \
471
+ -H "Content-Type: application/json" \
472
+ -d '{"days": 7}'
473
+
474
+ # Dry run to check how many tasks would be deleted
475
+ curl -X POST http://localhost:8000/tasks/purge/ \
476
+ -H "Content-Type: application/json" \
477
+ -d '{"days": 30, "dry_run": true}'
403
478
  ```
404
479
 
405
480
  ### Use Cases
@@ -446,13 +521,31 @@ if [ "$count" -gt 100 ]; then
446
521
  fi
447
522
  ```
448
523
 
524
+ #### Scheduled Cleanup
525
+
526
+ Use cron or Cloud Scheduler to delete old completed tasks:
527
+
528
+ ```bash
529
+ # Daily cleanup via cron or Cloud Scheduler
530
+ # Delete tasks completed more than 30 days ago
531
+ curl -X POST https://your-app.com/tasks/purge/ \
532
+ -H "Authorization: Bearer $TOKEN" \
533
+ -H "Content-Type: application/json" \
534
+ -d '{"days": 30}'
535
+ ```
536
+
449
537
  ### Security
450
538
 
451
539
  The endpoints are CSRF-exempt for API/webhook use. **Always add authentication in production:**
452
540
 
453
541
  ```python
454
542
  from django.contrib.admin.views.decorators import staff_member_required
455
- from django_database_task.views import RunTasksView, RunOneTaskView, TaskStatusView
543
+ from django_database_task.views import (
544
+ RunTasksView,
545
+ RunOneTaskView,
546
+ TaskStatusView,
547
+ PurgeCompletedTasksView,
548
+ )
456
549
 
457
550
  urlpatterns = [
458
551
  path(
@@ -470,6 +563,11 @@ urlpatterns = [
470
563
  staff_member_required(TaskStatusView.as_view()),
471
564
  name="task_status",
472
565
  ),
566
+ path(
567
+ "tasks/purge/",
568
+ staff_member_required(PurgeCompletedTasksView.as_view()),
569
+ name="purge_completed_tasks",
570
+ ),
473
571
  ]
474
572
  ```
475
573
 
@@ -492,15 +590,157 @@ urlpatterns = [
492
590
  ]
493
591
  ```
494
592
 
593
+ ## Google Cloud Tasks Integration
594
+
595
+ For serverless environments like Google App Engine or Cloud Run, you can use the Cloud Tasks backend to automatically create Cloud Tasks when tasks are enqueued.
596
+
597
+ ### Installation
598
+
599
+ ```bash
600
+ pip install django-database-task[cloudtasks]
601
+ ```
602
+
603
+ ### Quick Setup
604
+
605
+ ```python
606
+ # settings.py
607
+ TASKS = {
608
+ "default": {
609
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
610
+ "OPTIONS": {
611
+ "CLOUD_TASKS_QUEUE": "default", # Only required setting
612
+ },
613
+ },
614
+ }
615
+ ```
616
+
617
+ Project ID, location, and handler URL are auto-detected from GAE/Cloud Run environment variables.
618
+
619
+ ### How It Works
620
+
621
+ ```
622
+ 1. App enqueues task → Task saved to database + Cloud Task created
623
+ 2. Cloud Task triggers HTTP request → /tasks/execute/<task_id>/
624
+ 3. Handler retrieves task from database and executes it
625
+ ```
626
+
627
+ The Cloud Task only contains the task ID. All task parameters are stored in the database, ensuring:
628
+ - **Blue/Green deployment support**: Tasks execute on the same version that enqueued them
629
+ - **Database as source of truth**: Task parameters are never lost
630
+ - **Automatic retry**: Cloud Tasks handles retry with the task ID
631
+
632
+ ### Configuration Options
633
+
634
+ ```python
635
+ TASKS = {
636
+ "default": {
637
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
638
+ "OPTIONS": {
639
+ # Required
640
+ "CLOUD_TASKS_QUEUE": "default",
641
+
642
+ # Auto-detected (override if needed)
643
+ # "CLOUD_TASKS_PROJECT": "my-project",
644
+ # "CLOUD_TASKS_LOCATION": "asia-northeast1",
645
+ # "TASK_HANDLER_URL": "https://myapp.example.com/tasks/execute/{task_id}/",
646
+ # "TASK_HANDLER_PATH": "/tasks/execute/{task_id}/",
647
+
648
+ # OIDC authentication (optional)
649
+ # "OIDC_SERVICE_ACCOUNT_EMAIL": "...",
650
+ # "OIDC_AUDIENCE": "https://...",
651
+ },
652
+ },
653
+ }
654
+ ```
655
+
656
+ ### Auto-Detection
657
+
658
+ | Setting | Environment Variable | Description |
659
+ |---------|---------------------|-------------|
660
+ | Project | `GOOGLE_CLOUD_PROJECT` | GCP project ID |
661
+ | Location | `CLOUD_RUN_REGION` / `GAE_REGION` | Cloud Tasks region |
662
+ | Handler URL | Built from `K_SERVICE`, `GAE_SERVICE`, `GAE_VERSION` | Task execution endpoint |
663
+
664
+ ### OIDC Authentication
665
+
666
+ When `OIDC_SERVICE_ACCOUNT_EMAIL` is configured, Cloud Tasks will send OIDC tokens with each request. The backend automatically verifies these tokens on the `/tasks/execute/` and `/tasks/purge/` endpoints.
667
+
668
+ ```python
669
+ # settings.py - Automatic OIDC verification
670
+ TASKS = {
671
+ "default": {
672
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
673
+ "OPTIONS": {
674
+ "CLOUD_TASKS_QUEUE": "default",
675
+ "OIDC_SERVICE_ACCOUNT_EMAIL": "my-sa@project.iam.gserviceaccount.com",
676
+ # OIDC_AUDIENCE is auto-detected from handler URL if not set
677
+ },
678
+ },
679
+ }
680
+ ```
681
+
682
+ Alternatively, you can use the decorator directly on your URL configuration:
683
+
684
+ ```python
685
+ # urls.py
686
+ from django.urls import path
687
+ from django_database_task.views import ExecuteTaskView
688
+ from django_database_task.cloudtasks import verify_cloud_tasks_oidc
689
+
690
+ urlpatterns = [
691
+ path(
692
+ "tasks/execute/<uuid:task_id>/",
693
+ verify_cloud_tasks_oidc(
694
+ ExecuteTaskView.as_view(),
695
+ audience="https://myapp.example.com"
696
+ ),
697
+ name="execute_task",
698
+ ),
699
+ ]
700
+ ```
701
+
702
+ ### Detection Utilities
703
+
704
+ You can use the detection functions directly:
705
+
706
+ ```python
707
+ from django_database_task.cloudtasks import (
708
+ detect_gcp_project,
709
+ detect_gcp_location,
710
+ detect_task_handler_host,
711
+ is_cloud_run,
712
+ is_app_engine,
713
+ )
714
+
715
+ if is_cloud_run():
716
+ print(f"Running on Cloud Run in {detect_gcp_location()}")
717
+ elif is_app_engine():
718
+ print(f"Running on App Engine in project {detect_gcp_project()}")
719
+ ```
720
+
495
721
  ## Django Admin
496
722
 
497
- The package includes a Django Admin integration to view task status:
723
+ The package includes a Django Admin integration to view and manage tasks:
498
724
 
499
725
  - Task list with status badges
500
726
  - Filter by status, queue, backend
501
727
  - Search by task ID or path
502
728
  - View task arguments and results
503
729
 
730
+ ### Admin Actions
731
+
732
+ The admin interface provides the following bulk actions:
733
+
734
+ | Action | Description |
735
+ |--------|-------------|
736
+ | **Run selected tasks** | Execute selected tasks that are in READY status |
737
+ | **Retry failed tasks** | Reset FAILED tasks to READY status and re-execute them |
738
+
739
+ These actions are useful for:
740
+ - Manually triggering task execution from the admin
741
+ - Retrying failed tasks after fixing issues
742
+ - Testing task execution during development
743
+
504
744
  ## License
505
745
 
506
746
  MIT License - see [LICENSE](LICENSE) for details.
@@ -10,6 +10,7 @@ A database-backed task queue backend for Django 6.0's built-in task framework.
10
10
  - **Exclusive locking** - Prevents duplicate task execution with `SELECT FOR UPDATE SKIP LOCKED`
11
11
  - **Django Admin integration** - View and manage tasks from the admin interface
12
12
  - **Async support** - Supports async task functions
13
+ - **Google Cloud Tasks integration** - Optional backend for GAE/Cloud Run with auto-detection
13
14
 
14
15
  ## Architecture
15
16
 
@@ -258,7 +259,12 @@ python manage.py purge_completed_database_tasks [options]
258
259
  You can also process tasks programmatically without management commands:
259
260
 
260
261
  ```python
261
- from django_database_task import process_one_task, process_tasks, get_pending_task_count
262
+ from django_database_task import (
263
+ process_one_task,
264
+ process_tasks,
265
+ get_pending_task_count,
266
+ run_task_by_id,
267
+ )
262
268
 
263
269
  # Process a single task
264
270
  result = process_one_task()
@@ -275,6 +281,14 @@ results = process_tasks(queue_name="emails", max_tasks=5)
275
281
  # Get pending task count
276
282
  count = get_pending_task_count()
277
283
  print(f"Pending tasks: {count}")
284
+
285
+ # Execute a specific task by ID
286
+ result = run_task_by_id("550e8400-e29b-41d4-a716-446655440000")
287
+ if result:
288
+ print(f"Executed: {result.id}, status: {result.status}")
289
+
290
+ # Retry a failed task
291
+ result = run_task_by_id("...", allow_retry=True)
278
292
  ```
279
293
 
280
294
  ## HTTP Endpoints (Optional)
@@ -302,6 +316,8 @@ urlpatterns = [
302
316
  | `/tasks/run/` | POST | Process multiple pending tasks |
303
317
  | `/tasks/run-one/` | POST | Process a single pending task |
304
318
  | `/tasks/status/` | GET | Get pending task count |
319
+ | `/tasks/execute/<uuid>/` | POST | Execute a specific task by ID |
320
+ | `/tasks/purge/` | POST | Delete completed tasks |
305
321
 
306
322
  ### Request Parameters
307
323
 
@@ -352,6 +368,52 @@ Response:
352
368
  {"pending_count": 5}
353
369
  ```
354
370
 
371
+ #### POST `/tasks/execute/<uuid>/`
372
+
373
+ Execute a specific task by ID. This endpoint is designed for external trigger systems
374
+ (e.g., Cloud Tasks, webhooks) that need to execute a specific task.
375
+
376
+ | Parameter | Type | Default | Description |
377
+ |-----------|------|---------|-------------|
378
+ | `fail_on_error` | query string | "false" | Return HTTP 500 on task failure |
379
+ | `allow_retry` | query string | "false" | Allow re-execution of FAILED tasks |
380
+
381
+ Response (success):
382
+ ```json
383
+ {"executed": true, "result": {"id": "uuid", "status": "SUCCESSFUL", "task_path": "..."}}
384
+ ```
385
+
386
+ Response (task not in executable status):
387
+ ```json
388
+ {"executed": false, "reason": "Task is not in READY status"}
389
+ ```
390
+
391
+ Response (task not found):
392
+ ```json
393
+ {"error": "Task not found"} // HTTP 404
394
+ ```
395
+
396
+ #### POST `/tasks/purge/`
397
+
398
+ Delete completed tasks from the database. Useful for cron-based cleanup.
399
+
400
+ | Parameter | Type | Default | Description |
401
+ |-----------|------|---------|-------------|
402
+ | `days` | int | 0 | Delete tasks completed more than N days ago (0=all) |
403
+ | `status` | string | "SUCCESSFUL,FAILED" | Target statuses, comma-separated |
404
+ | `batch_size` | int | 1000 | Number of tasks to delete at once (max: 10000) |
405
+ | `dry_run` | bool | false | If true, return count without deleting |
406
+
407
+ Response:
408
+ ```json
409
+ {"deleted": 150, "dry_run": false}
410
+ ```
411
+
412
+ Response (dry run):
413
+ ```json
414
+ {"count": 150, "dry_run": true}
415
+ ```
416
+
355
417
  ### Example Usage
356
418
 
357
419
  ```bash
@@ -367,6 +429,16 @@ curl -X POST http://localhost:8000/tasks/run/ \
367
429
 
368
430
  # Get pending task count
369
431
  curl http://localhost:8000/tasks/status/
432
+
433
+ # Delete tasks completed more than 7 days ago
434
+ curl -X POST http://localhost:8000/tasks/purge/ \
435
+ -H "Content-Type: application/json" \
436
+ -d '{"days": 7}'
437
+
438
+ # Dry run to check how many tasks would be deleted
439
+ curl -X POST http://localhost:8000/tasks/purge/ \
440
+ -H "Content-Type: application/json" \
441
+ -d '{"days": 30, "dry_run": true}'
370
442
  ```
371
443
 
372
444
  ### Use Cases
@@ -413,13 +485,31 @@ if [ "$count" -gt 100 ]; then
413
485
  fi
414
486
  ```
415
487
 
488
+ #### Scheduled Cleanup
489
+
490
+ Use cron or Cloud Scheduler to delete old completed tasks:
491
+
492
+ ```bash
493
+ # Daily cleanup via cron or Cloud Scheduler
494
+ # Delete tasks completed more than 30 days ago
495
+ curl -X POST https://your-app.com/tasks/purge/ \
496
+ -H "Authorization: Bearer $TOKEN" \
497
+ -H "Content-Type: application/json" \
498
+ -d '{"days": 30}'
499
+ ```
500
+
416
501
  ### Security
417
502
 
418
503
  The endpoints are CSRF-exempt for API/webhook use. **Always add authentication in production:**
419
504
 
420
505
  ```python
421
506
  from django.contrib.admin.views.decorators import staff_member_required
422
- from django_database_task.views import RunTasksView, RunOneTaskView, TaskStatusView
507
+ from django_database_task.views import (
508
+ RunTasksView,
509
+ RunOneTaskView,
510
+ TaskStatusView,
511
+ PurgeCompletedTasksView,
512
+ )
423
513
 
424
514
  urlpatterns = [
425
515
  path(
@@ -437,6 +527,11 @@ urlpatterns = [
437
527
  staff_member_required(TaskStatusView.as_view()),
438
528
  name="task_status",
439
529
  ),
530
+ path(
531
+ "tasks/purge/",
532
+ staff_member_required(PurgeCompletedTasksView.as_view()),
533
+ name="purge_completed_tasks",
534
+ ),
440
535
  ]
441
536
  ```
442
537
 
@@ -459,15 +554,157 @@ urlpatterns = [
459
554
  ]
460
555
  ```
461
556
 
557
+ ## Google Cloud Tasks Integration
558
+
559
+ For serverless environments like Google App Engine or Cloud Run, you can use the Cloud Tasks backend to automatically create Cloud Tasks when tasks are enqueued.
560
+
561
+ ### Installation
562
+
563
+ ```bash
564
+ pip install django-database-task[cloudtasks]
565
+ ```
566
+
567
+ ### Quick Setup
568
+
569
+ ```python
570
+ # settings.py
571
+ TASKS = {
572
+ "default": {
573
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
574
+ "OPTIONS": {
575
+ "CLOUD_TASKS_QUEUE": "default", # Only required setting
576
+ },
577
+ },
578
+ }
579
+ ```
580
+
581
+ Project ID, location, and handler URL are auto-detected from GAE/Cloud Run environment variables.
582
+
583
+ ### How It Works
584
+
585
+ ```
586
+ 1. App enqueues task → Task saved to database + Cloud Task created
587
+ 2. Cloud Task triggers HTTP request → /tasks/execute/<task_id>/
588
+ 3. Handler retrieves task from database and executes it
589
+ ```
590
+
591
+ The Cloud Task only contains the task ID. All task parameters are stored in the database, ensuring:
592
+ - **Blue/Green deployment support**: Tasks execute on the same version that enqueued them
593
+ - **Database as source of truth**: Task parameters are never lost
594
+ - **Automatic retry**: Cloud Tasks handles retry with the task ID
595
+
596
+ ### Configuration Options
597
+
598
+ ```python
599
+ TASKS = {
600
+ "default": {
601
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
602
+ "OPTIONS": {
603
+ # Required
604
+ "CLOUD_TASKS_QUEUE": "default",
605
+
606
+ # Auto-detected (override if needed)
607
+ # "CLOUD_TASKS_PROJECT": "my-project",
608
+ # "CLOUD_TASKS_LOCATION": "asia-northeast1",
609
+ # "TASK_HANDLER_URL": "https://myapp.example.com/tasks/execute/{task_id}/",
610
+ # "TASK_HANDLER_PATH": "/tasks/execute/{task_id}/",
611
+
612
+ # OIDC authentication (optional)
613
+ # "OIDC_SERVICE_ACCOUNT_EMAIL": "...",
614
+ # "OIDC_AUDIENCE": "https://...",
615
+ },
616
+ },
617
+ }
618
+ ```
619
+
620
+ ### Auto-Detection
621
+
622
+ | Setting | Environment Variable | Description |
623
+ |---------|---------------------|-------------|
624
+ | Project | `GOOGLE_CLOUD_PROJECT` | GCP project ID |
625
+ | Location | `CLOUD_RUN_REGION` / `GAE_REGION` | Cloud Tasks region |
626
+ | Handler URL | Built from `K_SERVICE`, `GAE_SERVICE`, `GAE_VERSION` | Task execution endpoint |
627
+
628
+ ### OIDC Authentication
629
+
630
+ When `OIDC_SERVICE_ACCOUNT_EMAIL` is configured, Cloud Tasks will send OIDC tokens with each request. The backend automatically verifies these tokens on the `/tasks/execute/` and `/tasks/purge/` endpoints.
631
+
632
+ ```python
633
+ # settings.py - Automatic OIDC verification
634
+ TASKS = {
635
+ "default": {
636
+ "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
637
+ "OPTIONS": {
638
+ "CLOUD_TASKS_QUEUE": "default",
639
+ "OIDC_SERVICE_ACCOUNT_EMAIL": "my-sa@project.iam.gserviceaccount.com",
640
+ # OIDC_AUDIENCE is auto-detected from handler URL if not set
641
+ },
642
+ },
643
+ }
644
+ ```
645
+
646
+ Alternatively, you can use the decorator directly on your URL configuration:
647
+
648
+ ```python
649
+ # urls.py
650
+ from django.urls import path
651
+ from django_database_task.views import ExecuteTaskView
652
+ from django_database_task.cloudtasks import verify_cloud_tasks_oidc
653
+
654
+ urlpatterns = [
655
+ path(
656
+ "tasks/execute/<uuid:task_id>/",
657
+ verify_cloud_tasks_oidc(
658
+ ExecuteTaskView.as_view(),
659
+ audience="https://myapp.example.com"
660
+ ),
661
+ name="execute_task",
662
+ ),
663
+ ]
664
+ ```
665
+
666
+ ### Detection Utilities
667
+
668
+ You can use the detection functions directly:
669
+
670
+ ```python
671
+ from django_database_task.cloudtasks import (
672
+ detect_gcp_project,
673
+ detect_gcp_location,
674
+ detect_task_handler_host,
675
+ is_cloud_run,
676
+ is_app_engine,
677
+ )
678
+
679
+ if is_cloud_run():
680
+ print(f"Running on Cloud Run in {detect_gcp_location()}")
681
+ elif is_app_engine():
682
+ print(f"Running on App Engine in project {detect_gcp_project()}")
683
+ ```
684
+
462
685
  ## Django Admin
463
686
 
464
- The package includes a Django Admin integration to view task status:
687
+ The package includes a Django Admin integration to view and manage tasks:
465
688
 
466
689
  - Task list with status badges
467
690
  - Filter by status, queue, backend
468
691
  - Search by task ID or path
469
692
  - View task arguments and results
470
693
 
694
+ ### Admin Actions
695
+
696
+ The admin interface provides the following bulk actions:
697
+
698
+ | Action | Description |
699
+ |--------|-------------|
700
+ | **Run selected tasks** | Execute selected tasks that are in READY status |
701
+ | **Retry failed tasks** | Reset FAILED tasks to READY status and re-execute them |
702
+
703
+ These actions are useful for:
704
+ - Manually triggering task execution from the admin
705
+ - Retrying failed tasks after fixing issues
706
+ - Testing task execution during development
707
+
471
708
  ## License
472
709
 
473
710
  MIT License - see [LICENSE](LICENSE) for details.
@@ -2,7 +2,7 @@
2
2
  django-database-task: A database-backed task queue backend for Django 6.0's task framework.
3
3
  """
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.2.0"
6
6
 
7
7
 
8
8
  def __getattr__(name):
@@ -12,6 +12,7 @@ def __getattr__(name):
12
12
  "get_pending_task_count",
13
13
  "process_one_task",
14
14
  "process_tasks",
15
+ "run_task_by_id",
15
16
  ):
16
17
  from . import executor
17
18
 
@@ -24,4 +25,5 @@ __all__ = [
24
25
  "get_pending_task_count",
25
26
  "process_one_task",
26
27
  "process_tasks",
28
+ "run_task_by_id",
27
29
  ]
@@ -19,6 +19,20 @@ class DatabaseTaskBackend(BaseTaskBackend):
19
19
  supports_get_result = True
20
20
  supports_priority = True
21
21
 
22
+ def get_auth_handler(self):
23
+ """
24
+ Get the authentication handler for task execution endpoints.
25
+
26
+ Subclasses can override this to provide custom authentication.
27
+ The handler should be a callable that takes a request and returns:
28
+ - None if authentication succeeds
29
+ - A JsonResponse with error details if authentication fails
30
+
31
+ Returns:
32
+ Callable or None
33
+ """
34
+ return None
35
+
22
36
  def enqueue(self, task, args, kwargs):
23
37
  """Enqueue a task to the database.
24
38