django-database-task 0.2.1__tar.gz → 0.2.3__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 (37) hide show
  1. {django_database_task-0.2.1/django_database_task.egg-info → django_database_task-0.2.3}/PKG-INFO +41 -2
  2. {django_database_task-0.2.1 → django_database_task-0.2.3}/README.md +40 -1
  3. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/__init__.py +1 -1
  4. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/backends.py +28 -6
  5. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/cloudtasks/auth.py +28 -1
  6. {django_database_task-0.2.1 → django_database_task-0.2.3/django_database_task.egg-info}/PKG-INFO +41 -2
  7. {django_database_task-0.2.1 → django_database_task-0.2.3}/pyproject.toml +1 -1
  8. {django_database_task-0.2.1 → django_database_task-0.2.3}/LICENSE +0 -0
  9. {django_database_task-0.2.1 → django_database_task-0.2.3}/MANIFEST.in +0 -0
  10. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/admin.py +0 -0
  11. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/apps.py +0 -0
  12. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/cloudtasks/__init__.py +0 -0
  13. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/cloudtasks/backend.py +0 -0
  14. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/cloudtasks/detection.py +1 -1
  15. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/executor.py +0 -0
  16. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/locale/ja/LC_MESSAGES/django.mo +0 -0
  17. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/locale/ja/LC_MESSAGES/django.po +0 -0
  18. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/management/__init__.py +0 -0
  19. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/management/commands/__init__.py +0 -0
  20. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/management/commands/purge_completed_database_tasks.py +0 -0
  21. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/management/commands/run_database_tasks.py +0 -0
  22. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/migrations/0001_initial.py +0 -0
  23. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/migrations/__init__.py +0 -0
  24. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/models.py +0 -0
  25. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/urls.py +0 -0
  26. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task/views.py +0 -0
  27. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task.egg-info/SOURCES.txt +0 -0
  28. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task.egg-info/dependency_links.txt +0 -0
  29. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task.egg-info/requires.txt +0 -0
  30. {django_database_task-0.2.1 → django_database_task-0.2.3}/django_database_task.egg-info/top_level.txt +0 -0
  31. {django_database_task-0.2.1 → django_database_task-0.2.3}/setup.cfg +0 -0
  32. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_admin.py +0 -0
  33. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_backend.py +0 -0
  34. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_commands.py +0 -0
  35. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_executor.py +0 -0
  36. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_models.py +0 -0
  37. {django_database_task-0.2.1 → django_database_task-0.2.3}/tests/test_views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-database-task
3
- Version: 0.2.1
3
+ Version: 0.2.3
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>
@@ -36,6 +36,11 @@ Dynamic: license-file
36
36
 
37
37
  # django-database-task
38
38
 
39
+ [![CI](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml/badge.svg)](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml)
40
+ [![PyPI version](https://badge.fury.io/py/django-database-task.svg)](https://badge.fury.io/py/django-database-task)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-database-task.svg)](https://pypi.org/project/django-database-task/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+
39
44
  A database-backed task queue backend for Django 6.0's built-in task framework.
40
45
 
41
46
  ## Features
@@ -712,6 +717,40 @@ TASKS = {
712
717
 
713
718
  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.
714
719
 
720
+ #### Required IAM Roles
721
+
722
+ To use OIDC authentication, the following IAM roles are required:
723
+
724
+ | Role | Description |
725
+ |------|-------------|
726
+ | `roles/cloudtasks.enqueuer` | Required to create tasks in Cloud Tasks queues |
727
+ | `roles/iam.serviceAccountUser` | Required to specify the OIDC service account when creating tasks |
728
+
729
+ **Setup:**
730
+
731
+ 1. Create a service account for OIDC token generation:
732
+ ```bash
733
+ gcloud iam service-accounts create cloud-tasks-invoker \
734
+ --display-name="Cloud Tasks Invoker"
735
+ ```
736
+
737
+ 2. Grant the Cloud Tasks Enqueuer role to the service account running your application (e.g., App Engine default service account):
738
+ ```bash
739
+ gcloud projects add-iam-policy-binding PROJECT_ID \
740
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
741
+ --role="roles/cloudtasks.enqueuer"
742
+ ```
743
+
744
+ 3. Grant the Service Account User role to allow impersonation of the OIDC service account:
745
+ ```bash
746
+ gcloud iam service-accounts add-iam-policy-binding \
747
+ cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com \
748
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
749
+ --role="roles/iam.serviceAccountUser"
750
+ ```
751
+
752
+ **Note:** The OIDC service account specified in `OIDC_SERVICE_ACCOUNT_EMAIL` does not need any additional roles. It is only used to generate the OIDC token that is included in the HTTP request to your task handler.
753
+
715
754
  ```python
716
755
  # settings.py - Automatic OIDC verification
717
756
  TASKS = {
@@ -719,7 +758,7 @@ TASKS = {
719
758
  "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
720
759
  "QUEUES": [], # Allow all queue names
721
760
  "OPTIONS": {
722
- "OIDC_SERVICE_ACCOUNT_EMAIL": "my-sa@project.iam.gserviceaccount.com",
761
+ "OIDC_SERVICE_ACCOUNT_EMAIL": "cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com",
723
762
  # OIDC_AUDIENCE is auto-detected from handler URL if not set
724
763
  },
725
764
  },
@@ -1,5 +1,10 @@
1
1
  # django-database-task
2
2
 
3
+ [![CI](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml/badge.svg)](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml)
4
+ [![PyPI version](https://badge.fury.io/py/django-database-task.svg)](https://badge.fury.io/py/django-database-task)
5
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-database-task.svg)](https://pypi.org/project/django-database-task/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
3
8
  A database-backed task queue backend for Django 6.0's built-in task framework.
4
9
 
5
10
  ## Features
@@ -676,6 +681,40 @@ TASKS = {
676
681
 
677
682
  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.
678
683
 
684
+ #### Required IAM Roles
685
+
686
+ To use OIDC authentication, the following IAM roles are required:
687
+
688
+ | Role | Description |
689
+ |------|-------------|
690
+ | `roles/cloudtasks.enqueuer` | Required to create tasks in Cloud Tasks queues |
691
+ | `roles/iam.serviceAccountUser` | Required to specify the OIDC service account when creating tasks |
692
+
693
+ **Setup:**
694
+
695
+ 1. Create a service account for OIDC token generation:
696
+ ```bash
697
+ gcloud iam service-accounts create cloud-tasks-invoker \
698
+ --display-name="Cloud Tasks Invoker"
699
+ ```
700
+
701
+ 2. Grant the Cloud Tasks Enqueuer role to the service account running your application (e.g., App Engine default service account):
702
+ ```bash
703
+ gcloud projects add-iam-policy-binding PROJECT_ID \
704
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
705
+ --role="roles/cloudtasks.enqueuer"
706
+ ```
707
+
708
+ 3. Grant the Service Account User role to allow impersonation of the OIDC service account:
709
+ ```bash
710
+ gcloud iam service-accounts add-iam-policy-binding \
711
+ cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com \
712
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
713
+ --role="roles/iam.serviceAccountUser"
714
+ ```
715
+
716
+ **Note:** The OIDC service account specified in `OIDC_SERVICE_ACCOUNT_EMAIL` does not need any additional roles. It is only used to generate the OIDC token that is included in the HTTP request to your task handler.
717
+
679
718
  ```python
680
719
  # settings.py - Automatic OIDC verification
681
720
  TASKS = {
@@ -683,7 +722,7 @@ TASKS = {
683
722
  "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
684
723
  "QUEUES": [], # Allow all queue names
685
724
  "OPTIONS": {
686
- "OIDC_SERVICE_ACCOUNT_EMAIL": "my-sa@project.iam.gserviceaccount.com",
725
+ "OIDC_SERVICE_ACCOUNT_EMAIL": "cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com",
687
726
  # OIDC_AUDIENCE is auto-detected from handler URL if not set
688
727
  },
689
728
  },
@@ -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.2.1"
5
+ __version__ = "0.2.3"
6
6
 
7
7
 
8
8
  def __getattr__(name):
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import logging
2
3
  import traceback
3
4
  from importlib import import_module
4
5
  from inspect import iscoroutinefunction
@@ -10,6 +11,8 @@ from django.tasks.signals import task_enqueued, task_finished, task_started
10
11
  from django.utils import timezone
11
12
  from django.utils.json import normalize_json
12
13
 
14
+ logger = logging.getLogger("django_database_task")
15
+
13
16
 
14
17
  class DatabaseTaskBackend(BaseTaskBackend):
15
18
  """A task backend that persists tasks in the database."""
@@ -200,6 +203,20 @@ class DatabaseTaskBackend(BaseTaskBackend):
200
203
  ]
201
204
  )
202
205
 
206
+ # Send signal for success
207
+ db_task.refresh_from_db()
208
+ final_result = self._db_task_to_result(db_task, task)
209
+ # Note: Django's task_finished handler logs "NoneType: None" after this
210
+ # due to exc_info=sys.exc_info() being called outside exception context.
211
+ # This is a known Django issue and will be fixed upstream.
212
+ logger.info(
213
+ "Task completed successfully: id=%s path=%s",
214
+ final_result.id,
215
+ db_task.task_path,
216
+ )
217
+ task_finished.send(sender=self.__class__, task_result=final_result)
218
+ return final_result
219
+
203
220
  except Exception as e:
204
221
  # Failure
205
222
  error = TaskError(
@@ -221,9 +238,14 @@ class DatabaseTaskBackend(BaseTaskBackend):
221
238
  update_fields=["status", "errors_json", "finished_at", "updated_at"]
222
239
  )
223
240
 
224
- # Get final result and send signal
225
- db_task.refresh_from_db()
226
- final_result = self._db_task_to_result(db_task, task)
227
- task_finished.send(sender=self.__class__, task_result=final_result)
228
-
229
- return final_result
241
+ # Send signal for failure (with exception context for Django's logging)
242
+ db_task.refresh_from_db()
243
+ final_result = self._db_task_to_result(db_task, task)
244
+ logger.error(
245
+ "Task failed: id=%s path=%s error=%s",
246
+ final_result.id,
247
+ db_task.task_path,
248
+ error.exception_class_path,
249
+ )
250
+ task_finished.send(sender=self.__class__, task_result=final_result)
251
+ return final_result
@@ -5,12 +5,16 @@ Provides authentication handler and decorator for verifying OIDC tokens
5
5
  sent by Cloud Tasks.
6
6
  """
7
7
 
8
+ import logging
9
+ import traceback
8
10
  from functools import wraps
9
11
 
10
12
  from django.http import JsonResponse
11
13
  from google.auth.transport import requests as google_requests
12
14
  from google.oauth2 import id_token
13
15
 
16
+ logger = logging.getLogger("django_database_task.cloudtasks.auth")
17
+
14
18
 
15
19
  def create_oidc_auth_handler(audience):
16
20
  """
@@ -64,8 +68,31 @@ def create_oidc_auth_handler(audience):
64
68
  # Attach claims to request for use in view
65
69
  request.cloud_tasks_claims = claims
66
70
 
71
+ # Log successful verification
72
+ service_account = claims.get("email", "unknown")
73
+ logger.info(
74
+ "OIDC token verified successfully: service_account=%s audience=%s",
75
+ service_account,
76
+ audience,
77
+ )
78
+
67
79
  except Exception as e:
68
- return JsonResponse({"error": f"Invalid token: {e}"}, status=401)
80
+ # Log the full exception for debugging
81
+ # Use print as fallback since logging config may not capture this logger
82
+ error_type = type(e).__name__
83
+ error_msg = str(e) if str(e) else "No details available"
84
+ tb = traceback.format_exc()
85
+ full_error = (
86
+ f"OIDC token verification failed: {error_type}: {error_msg}\n{tb}"
87
+ )
88
+ logger.error(full_error)
89
+ # Also print to stderr for environments where logging is not configured
90
+ import sys
91
+
92
+ print(full_error, file=sys.stderr)
93
+ return JsonResponse(
94
+ {"error": f"Invalid token: {error_type}: {error_msg}"}, status=401
95
+ )
69
96
 
70
97
  return None
71
98
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-database-task
3
- Version: 0.2.1
3
+ Version: 0.2.3
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>
@@ -36,6 +36,11 @@ Dynamic: license-file
36
36
 
37
37
  # django-database-task
38
38
 
39
+ [![CI](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml/badge.svg)](https://github.com/tokibito/django-database-task/actions/workflows/ci.yml)
40
+ [![PyPI version](https://badge.fury.io/py/django-database-task.svg)](https://badge.fury.io/py/django-database-task)
41
+ [![Python versions](https://img.shields.io/pypi/pyversions/django-database-task.svg)](https://pypi.org/project/django-database-task/)
42
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
43
+
39
44
  A database-backed task queue backend for Django 6.0's built-in task framework.
40
45
 
41
46
  ## Features
@@ -712,6 +717,40 @@ TASKS = {
712
717
 
713
718
  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.
714
719
 
720
+ #### Required IAM Roles
721
+
722
+ To use OIDC authentication, the following IAM roles are required:
723
+
724
+ | Role | Description |
725
+ |------|-------------|
726
+ | `roles/cloudtasks.enqueuer` | Required to create tasks in Cloud Tasks queues |
727
+ | `roles/iam.serviceAccountUser` | Required to specify the OIDC service account when creating tasks |
728
+
729
+ **Setup:**
730
+
731
+ 1. Create a service account for OIDC token generation:
732
+ ```bash
733
+ gcloud iam service-accounts create cloud-tasks-invoker \
734
+ --display-name="Cloud Tasks Invoker"
735
+ ```
736
+
737
+ 2. Grant the Cloud Tasks Enqueuer role to the service account running your application (e.g., App Engine default service account):
738
+ ```bash
739
+ gcloud projects add-iam-policy-binding PROJECT_ID \
740
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
741
+ --role="roles/cloudtasks.enqueuer"
742
+ ```
743
+
744
+ 3. Grant the Service Account User role to allow impersonation of the OIDC service account:
745
+ ```bash
746
+ gcloud iam service-accounts add-iam-policy-binding \
747
+ cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com \
748
+ --member="serviceAccount:PROJECT_ID@appspot.gserviceaccount.com" \
749
+ --role="roles/iam.serviceAccountUser"
750
+ ```
751
+
752
+ **Note:** The OIDC service account specified in `OIDC_SERVICE_ACCOUNT_EMAIL` does not need any additional roles. It is only used to generate the OIDC token that is included in the HTTP request to your task handler.
753
+
715
754
  ```python
716
755
  # settings.py - Automatic OIDC verification
717
756
  TASKS = {
@@ -719,7 +758,7 @@ TASKS = {
719
758
  "BACKEND": "django_database_task.cloudtasks.CloudTasksDatabaseBackend",
720
759
  "QUEUES": [], # Allow all queue names
721
760
  "OPTIONS": {
722
- "OIDC_SERVICE_ACCOUNT_EMAIL": "my-sa@project.iam.gserviceaccount.com",
761
+ "OIDC_SERVICE_ACCOUNT_EMAIL": "cloud-tasks-invoker@PROJECT_ID.iam.gserviceaccount.com",
723
762
  # OIDC_AUDIENCE is auto-detected from handler URL if not set
724
763
  },
725
764
  },
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "django-database-task"
7
- version = "0.2.1"
7
+ version = "0.2.3"
8
8
  description = "A database-backed task queue backend for Django 6.0's built-in task framework"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -66,8 +66,8 @@ def _detect_region_from_metadata():
66
66
  Returns:
67
67
  Region string or None if not available.
68
68
  """
69
- import urllib.request
70
69
  import urllib.error
70
+ import urllib.request
71
71
 
72
72
  metadata_url = "http://metadata.google.internal/computeMetadata/v1/instance/zone"
73
73
  headers = {"Metadata-Flavor": "Google"}