django-lambda-tasks 0.5.0__tar.gz → 0.6.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.

Potentially problematic release.


This version of django-lambda-tasks might be problematic. Click here for more details.

Files changed (105) hide show
  1. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/steering/product.md +35 -6
  2. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/steering/structure.md +10 -10
  3. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/PKG-INFO +77 -1
  4. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/README.md +76 -0
  5. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/admin.py +18 -2
  6. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/decorators.py +125 -59
  7. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/handler.py +34 -0
  8. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/models.py +13 -5
  9. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/settings.py +32 -0
  10. django_lambda_tasks-0.6.0/lambda_tasks/tasks.py +74 -0
  11. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/pyproject.toml +1 -1
  12. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_admin.py +5 -0
  13. django_lambda_tasks-0.6.0/tests/test_batch.py +210 -0
  14. django_lambda_tasks-0.6.0/tests/test_batch_decorators.py +158 -0
  15. django_lambda_tasks-0.6.0/tests/test_batch_worker.py +66 -0
  16. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_local_executor.py +6 -6
  17. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_models.py +21 -25
  18. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_settings.py +78 -0
  19. django_lambda_tasks-0.5.0/lambda_tasks/tasks.py +0 -23
  20. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.github/workflows/ci.yml +0 -0
  21. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.github/workflows/release.yml +0 -0
  22. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.gitignore +0 -0
  23. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  24. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/design.md +0 -0
  25. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
  26. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
  27. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  28. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  29. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  30. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  31. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  32. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  33. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  34. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  35. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  36. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  37. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  38. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  39. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  40. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  41. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  42. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  43. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
  44. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/design.md +0 -0
  45. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/requirements.md +0 -0
  46. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/tasks.md +0 -0
  47. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  48. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
  49. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  50. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  51. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  52. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  53. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  54. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  55. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
  56. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/design.md +0 -0
  57. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/requirements.md +0 -0
  58. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/tasks.md +0 -0
  59. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  60. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  61. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  62. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  63. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/.config.kiro +0 -0
  64. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/design.md +0 -0
  65. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/requirements.md +0 -0
  66. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/tasks.md +0 -0
  67. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.kiro/steering/tech.md +0 -0
  68. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.pre-commit-config.yaml +0 -0
  69. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/.vscode/settings.json +0 -0
  70. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/README.md +0 -0
  71. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_app/__init__.py +0 -0
  72. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_app/apps.py +0 -0
  73. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_app/tasks.py +0 -0
  74. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_app/urls.py +0 -0
  75. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_app/views.py +0 -0
  76. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_project/__init__.py +0 -0
  77. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_project/settings.py +0 -0
  78. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_project/urls.py +0 -0
  79. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/example_project/wsgi.py +0 -0
  80. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/example/manage.py +0 -0
  81. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/__init__.py +0 -0
  82. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/apps.py +0 -0
  83. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/environment_loader.py +0 -0
  84. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/local_executor.py +0 -0
  85. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/logging.py +0 -0
  86. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/0001_initial.py +0 -0
  87. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  88. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/__init__.py +0 -0
  89. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/secret_loader.py +0 -0
  90. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/lambda_tasks/timeouts.py +0 -0
  91. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/conftest.py +0 -0
  92. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/settings.py +0 -0
  93. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_decorator.py +0 -0
  94. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_decorators.py +0 -0
  95. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_deferred_enqueue.py +0 -0
  96. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_environment_loader.py +0 -0
  97. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_handler.py +0 -0
  98. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_kwargs_only.py +0 -0
  99. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_logging.py +0 -0
  100. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_memory_limit.py +0 -0
  101. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_secret_loader.py +0 -0
  102. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_serializer.py +0 -0
  103. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_tasks.py +0 -0
  104. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_timeout_validation.py +0 -0
  105. {django_lambda_tasks-0.5.0 → django_lambda_tasks-0.6.0}/tests/test_timeouts.py +0 -0
@@ -4,37 +4,58 @@ inclusion: always
4
4
 
5
5
  # django-lambda-tasks
6
6
 
7
- A Django library for offloading tasks to AWS Lambda outside of the request-response cycle. Tasks are defined with a decorator, enqueued to SQS on transaction commit, and executed by a Lambda worker.
7
+ A Django library for offloading tasks to AWS Lambda or AWS Batch outside of the request-response cycle. Tasks are defined with a decorator, enqueued to SQS on transaction commit, and executed by a Lambda worker or Batch container.
8
8
 
9
9
  ## Architecture
10
10
 
11
+ ### Lambda tasks
12
+
11
13
  ```
12
14
  View → @lambda_task.execute_on_commit() → SQS → Lambda handler → SQSLambdaTaskMessage.execute_immediately() → TaskRecord
13
15
  ```
14
16
 
17
+ ### Batch tasks
18
+
19
+ ```
20
+ View → @batch_task.execute_on_commit() → SQS → Lambda handler → submit_batch_job → batch.submit_job()
21
+ → Container → python -m lambda_tasks.handler → handler() → execute_immediately() → TaskRecord
22
+ ```
23
+
24
+ Retry path (Batch):
25
+ ```
26
+ Container (retry_on exception) → execute_on_commit() → SQS → Lambda → submit_batch_job → new Batch job
27
+ ```
28
+
15
29
  Key modules:
16
- - `decorators.py` — `@lambda_task` decorator and `LambdaTaskWrapper`
17
- - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (SQS schema + execution), `SQSLambdaTask` (routing + SQS publish or local pool submit)
30
+ - `decorators.py` — `BaseTaskWrapper`, `LambdaTaskWrapper`, `BatchTaskWrapper`, `@lambda_task`, `@batch_task`
31
+ - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (SQS schema + execution), `SQSLambdaTask` (routing + SQS publish, Batch submit, or local pool submit)
18
32
  - `local_executor.py` — `ProcessPoolExecutor`-based async local execution for development
19
- - `handler.py` — AWS Lambda entry point; cold-start init runs on first invocation (not at import time) with partial-batch failure reporting
33
+ - `handler.py` — AWS Lambda entry point + AWS Batch container entry point (`main()`)
20
34
  - `logging.py` — `task_logger` for invocation-scoped log output
21
- - `settings.py` — lazy `LambdaTasksSettings` reading from Django settings
35
+ - `settings.py` — `TaskBackend` enum, lazy `LambdaTasksSettings` reading from Django settings
22
36
 
23
37
  ## Task Definition
24
38
 
25
39
  ```python
26
- # Always kwargs-only — positional args raise TypeError at decoration time
40
+ # Lambda task — max timeout 900s
27
41
  @lambda_task(retry_delay=30, soft_timeout=60, hard_timeout=120, queue="default",
28
42
  retry_on=(TransientError,),
29
43
  singleton=True, retry_singleton=True)
30
44
  def my_task(*, user_id: int, action: str) -> None:
31
45
  ...
46
+
47
+ # Batch task — max timeout 3600s, for long-running or storage-heavy workloads
48
+ @batch_task(soft_timeout=1800, hard_timeout=3500, queue="default",
49
+ retry_on=(TransientError,))
50
+ def heavy_task(*, file_id: int) -> None:
51
+ ...
32
52
  ```
33
53
 
34
54
  - Tasks are resolved at execution time via `import_string` on the fully-qualified task name
35
55
  - Task files should be named `tasks.py` within the Django app
36
56
  - Direct call `my_task(user_id=1, action="x")` runs synchronously
37
57
  - `my_task.execute_on_commit(user_id=1, action="x")` enqueues after transaction commit
58
+ - Both decorators share `BaseTaskWrapper` — same validation, kwargs building, retry, and singleton logic
38
59
 
39
60
  ## Enqueuing
40
61
 
@@ -132,6 +153,12 @@ class SQSLambdaTaskMessage(BaseModel):
132
153
  - A temporary `StreamHandler` is attached to the `lambda_tasks` logger for the duration of the loaders so their log output is visible before Django's `LOGGING` dictConfig has run; it is removed immediately after so that Django's configuration is the sole authority on logging from that point on
133
154
  - `resource.setrlimit(RLIMIT_DATA)` is set from `AWS_LAMBDA_FUNCTION_MEMORY_SIZE` so that excessive allocation raises `MemoryError` instead of triggering the OOM killer
134
155
 
156
+ `main()` in `handler.py` — AWS Batch container entry point (`python -m lambda_tasks.handler`):
157
+ - Reads `LAMBDA_TASKS_MESSAGE` env var (the serialized task JSON, set by `submit_batch_job` via container overrides)
158
+ - Uses `AWS_BATCH_JOB_ID` as the `message_id` (falls back to UUID4 if not set)
159
+ - Constructs a synthetic SQS event and delegates to `handler()` — reuses the full cold-start and execution path
160
+ - Returns exit code 0 on success, 1 on failure
161
+
135
162
  ## Environment Loader
136
163
 
137
164
  `resolve_environment()` in `environment_loader.py` runs once at Lambda cold start, before `resolve_secrets_into_env()` and `django.setup()`.
@@ -184,6 +211,7 @@ Statuses: `RUNNING`, `SUCCESS`, `FAILED`, `RETRYING`
184
211
  | Setting | Default | Description |
185
212
  |---|---|---|
186
213
  | `LAMBDA_TASKS_QUEUES` | — | Dict of `{queue_name: sqs_url}`, must include `"default"` |
214
+ | `LAMBDA_TASKS_BATCH_QUEUES` | `{}` | Dict of `{queue_name: {"job_queue": ..., "job_definition": ...}}`, must include `"default"` when set |
187
215
  | `LAMBDA_TASKS_DEFAULT_SOFT_TIMEOUT` | `270` | Soft timeout in seconds |
188
216
  | `LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT` | `300` | Hard timeout in seconds |
189
217
  | `LAMBDA_TASKS_EAGER` | `False` | Run tasks synchronously in-process (no SQS) |
@@ -261,6 +289,7 @@ def my_task(*, user_id: int) -> None:
261
289
  `lambda_tasks.tasks` provides maintenance tasks that ship with the library:
262
290
 
263
291
  - `cleanup_task_records(*, retention_days: int = 7) -> int` — deletes `TaskRecord` rows whose `start_time` is strictly older than `retention_days`. Returns the number of deleted rows. Decorated with `@lambda_task` so it can be enqueued via `cleanup_task_records.execute_on_commit()` or called directly. Users are responsible for scheduling (e.g. EventBridge rule, cron, management command).
292
+ - `submit_batch_job(*, message_json: str, batch_queue: str) -> str` — submits a batch task to AWS Batch. Reads `job_queue` and `job_definition` from `LAMBDA_TASKS_BATCH_QUEUES[batch_queue]`, sanitizes the task name for use as `jobName`, and calls `batch.submit_job()` with the message passed as a `LAMBDA_TASKS_MESSAGE` environment variable override. Returns the Batch job ID. This task is enqueued automatically by `SQSLambdaTask._execute()` when `backend == TaskBackend.BATCH` — users do not call it directly.
264
293
 
265
294
  ## Conventions
266
295
 
@@ -12,15 +12,15 @@ django-lambda-tasks/
12
12
  │ ├── __init__.py
13
13
  │ ├── admin.py # Django admin for TaskRecord
14
14
  │ ├── apps.py # Django AppConfig
15
- │ ├── decorators.py # @lambda_task decorator + LambdaTaskWrapper
16
- │ ├── handler.py # AWS Lambda entry point
15
+ │ ├── decorators.py # BaseTaskWrapper, LambdaTaskWrapper, BatchTaskWrapper, @lambda_task, @batch_task
16
+ │ ├── handler.py # AWS Lambda entry point + AWS Batch container entry point (main)
17
17
  │ ├── logging.py # task_logger — invocation-scoped LoggerAdapter
18
18
  │ ├── models.py # TaskRecord, SQSLambdaTaskMessage, SQSLambdaTask
19
- │ ├── settings.py # LambdaTasksSettings (lazy Django settings reader)
19
+ │ ├── settings.py # LambdaTasksSettings, TaskBackend enum, constants
20
20
  │ ├── secret_loader.py # Resolves LAMBDA_TASKS_SECRET_* env vars at cold start
21
21
  │ ├── environment_loader.py # Loads env vars from Secrets Manager at cold start
22
22
  │ ├── local_executor.py # ProcessPoolExecutor for async local task execution
23
- │ ├── tasks.py # Built-in maintenance tasks (cleanup_task_records)
23
+ │ ├── tasks.py # Built-in tasks (cleanup_task_records, submit_batch_job)
24
24
  │ ├── timeouts.py # TimeoutContext implementation
25
25
  │ └── migrations/ # Django migrations for TaskRecord
26
26
  ├── tests/ # pytest test suite
@@ -39,23 +39,23 @@ django-lambda-tasks/
39
39
 
40
40
  ## Module Responsibilities
41
41
 
42
- - `decorators.py` — defines `@lambda_task`; enforces kwargs-only at decoration time
43
- - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic), `SQSLambdaTask` (Pydantic, holds message + routing; `_execute()` publishes to SQS, executes eagerly, or submits to the local process pool; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
42
+ - `decorators.py` — defines `BaseTaskWrapper` (shared validation, kwargs building, execution logic), `LambdaTaskWrapper` and `BatchTaskWrapper` (subclasses that set `_max_timeout` and `_backend`), plus `@lambda_task` and `@batch_task` decorator factories; enforces kwargs-only at decoration time
43
+ - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic), `SQSLambdaTask` (Pydantic, holds message + routing + `backend` field; `_execute()` publishes to SQS, submits to Batch via `submit_batch_job`, executes eagerly, or submits to the local process pool; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
44
44
  - `local_executor.py` — `ProcessPoolExecutor`-based async local execution; `get_pool()` lazily creates a module-level pool; `submit_task()` fire-and-forget submission; `_execute_in_worker()` deserializes and runs the task in a child process; `_pool_initializer()` calls `django.setup()` per worker (and sets `SIGINT` to `SIG_IGN` so workers ignore Ctrl+C); `_install_shutdown_handlers()` installs main-thread `SIGINT`/`SIGTERM` handlers that release the pool before chaining to the previous handler
45
- - `handler.py` — Lambda entry point; cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; processes SQS records independently; returns `batchItemFailures`
45
+ - `handler.py` — Lambda entry point (`handler(event, context)`); cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; processes SQS records independently; returns `batchItemFailures`. Also provides `main()` as the AWS Batch container entry point (`python -m lambda_tasks.handler`) — reads `LAMBDA_TASKS_MESSAGE` env var, constructs a synthetic SQS event, and delegates to `handler()`
46
46
  - `environment_loader.py` — loads env vars from a Secrets Manager secret at cold start; validates flat JSON format; idempotent via `_loaded` sentinel
47
47
  - `secret_loader.py` — resolves `LAMBDA_TASKS_SECRET_*` env vars from Secrets Manager before Django starts; validates format, detects conflicts, batches API calls; idempotent via `_loaded` sentinel
48
48
  - `logging.py` — `task_logger` singleton; `message_id` set/cleared around each task execution
49
- - `settings.py` — `LambdaTasksSettings` instantiated fresh per use (reads live Django settings)
49
+ - `settings.py` — `TaskBackend` StrEnum (`LAMBDA`, `BATCH`); constants (`MAX_DELAY`, `MAX_TIMEOUT`, `MAX_BATCH_TIMEOUT`); `LambdaTasksSettings` instantiated fresh per use (reads live Django settings including `BATCH_QUEUES`)
50
50
  - `admin.py` — Django admin registration for `TaskRecord`
51
51
  - `apps.py` — Django `AppConfig`; `ready()` installs the local-executor shutdown signal handlers when `LOCAL_WORKERS > 0`
52
- - `tasks.py` — built-in maintenance tasks; `cleanup_task_records` deletes old `TaskRecord` rows
52
+ - `tasks.py` — built-in tasks; `cleanup_task_records` deletes old `TaskRecord` rows; `submit_batch_job` receives a serialized task message and submits it to AWS Batch via `batch.submit_job()`
53
53
 
54
54
  ## Conventions
55
55
 
56
56
  - Task definitions belong in `tasks.py` within each Django app
57
57
  - All task functions must use keyword-only arguments — positional args raise `TypeError` at decoration time
58
- - Task logic must be self-contained; no Django request context is available in Lambda
58
+ - Task logic must be self-contained; no Django request context is available in Lambda or Batch
59
59
  - New modules go inside `lambda_tasks/`; add a corresponding `tests/test_<module>.py`
60
60
  - Tests for `models.py` (including `SQSLambdaTask._execute` and `SQSLambdaTask.execute_on_commit`) live in `tests/test_models.py`
61
61
  - Do not add application-level code to `main.py` — it is a placeholder only
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lambda-tasks
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: awslambdaric
@@ -110,12 +110,20 @@ All settings are read from your Django settings module and prefixed with `LAMBDA
110
110
  | Setting | Type | Description |
111
111
  |---|---|---|
112
112
  | `LAMBDA_TASKS_QUEUES` | `dict[str, str]` | Map of queue name → SQS queue URL. Must include a `"default"` key. |
113
+ | `LAMBDA_TASKS_BATCH_QUEUES` | `dict[str, dict]` | Map of queue name → `{"job_queue": ..., "job_definition": ...}`. Must include a `"default"` key when set. Default `{}`. |
113
114
 
114
115
  ```python
115
116
  LAMBDA_TASKS_QUEUES = {
116
117
  "default": "https://sqs.us-east-1.amazonaws.com/123456789/default",
117
118
  "high_memory": "https://sqs.us-east-1.amazonaws.com/123456789/high-memory",
118
119
  }
120
+
121
+ LAMBDA_TASKS_BATCH_QUEUES = {
122
+ "default": {
123
+ "job_queue": "arn:aws:batch:eu-west-1:123456789:job-queue/my-queue",
124
+ "job_definition": "arn:aws:batch:eu-west-1:123456789:job-definition/my-def:1",
125
+ },
126
+ }
119
127
  ```
120
128
 
121
129
  `LAMBDA_TASKS_QUEUES` must be set and must include a `"default"` key, otherwise `ImproperlyConfigured` is raised on first use.
@@ -623,3 +631,71 @@ result = send_welcome_email(user_id=1, template="welcome")
623
631
  ```
624
632
 
625
633
  This bypasses the queue entirely and runs the function in the current process. No `TaskRecord` is created, no `transaction.atomic()` block is used, and no timeout enforcement applies — it behaves exactly like calling the underlying function directly. Kwargs are still validated against the task's type annotations via Pydantic.
634
+
635
+ ---
636
+
637
+ ## AWS Batch tasks
638
+
639
+ For tasks that exceed Lambda's 15-minute timeout or 10GB ephemeral storage limit, use `@batch_task` to route execution to AWS Batch containers.
640
+
641
+ ### 1. Define a batch task
642
+
643
+ ```python
644
+ # myapp/tasks.py
645
+ from lambda_tasks.decorators import batch_task
646
+
647
+ @batch_task(soft_timeout=1800, hard_timeout=3500)
648
+ def process_large_file(*, file_id: int) -> str:
649
+ # long-running work with large disk usage
650
+ return "done"
651
+ ```
652
+
653
+ ### 2. Configure AWS Batch queues
654
+
655
+ ```python
656
+ # settings.py
657
+ LAMBDA_TASKS_BATCH_QUEUES = {
658
+ "default": {
659
+ "job_queue": "arn:aws:batch:eu-west-1:123456789:job-queue/my-queue",
660
+ "job_definition": "arn:aws:batch:eu-west-1:123456789:job-definition/my-def:1",
661
+ },
662
+ }
663
+ ```
664
+
665
+ ### 3. Enqueue from a view
666
+
667
+ ```python
668
+ process_large_file.execute_on_commit(file_id=42)
669
+ ```
670
+
671
+ ### How it works
672
+
673
+ 1. `execute_on_commit()` enqueues a message to SQS (same as Lambda tasks)
674
+ 2. The Lambda handler picks up the message and runs the built-in `submit_batch_job` task
675
+ 3. `submit_batch_job` calls `batch.submit_job()` with the task message as a `LAMBDA_TASKS_MESSAGE` environment variable override
676
+ 4. The Batch container runs `python -m lambda_tasks.handler`, which reads the env var, performs cold-start init, and calls `execute_immediately()`
677
+ 5. The task runs with full timeout enforcement (`SIGALRM`), retry support, and `TaskRecord` tracking
678
+
679
+ ### Batch task timeouts
680
+
681
+ Batch tasks support the same two-phase timeout mechanism as Lambda tasks (soft + hard), but with a maximum of **3600 seconds** (1 hour) instead of 900.
682
+
683
+ ### Retries
684
+
685
+ Batch tasks support `retry_on` — when a retryable exception is raised, the retry goes back through SQS → Lambda → `submit_batch_job` → new Batch job. The same `LAMBDA_TASKS_MAX_RETRIES` limit applies.
686
+
687
+ ### Batch job definition setup
688
+
689
+ Your Batch job definition should:
690
+ - Use the same Docker image as your application
691
+ - Set `DJANGO_SETTINGS_MODULE` as an environment variable
692
+ - Configure secrets via the job definition's `secrets` field or via `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` / `LAMBDA_TASKS_SECRET_*` env vars (same mechanism as Lambda)
693
+ - Set the default command to `["python", "-m", "lambda_tasks.handler"]`
694
+
695
+ ### Container overrides limit
696
+
697
+ The total `containerOverrides` payload (including JSON formatting) is limited to 8192 characters by AWS Batch. For typical task kwargs (IDs, short strings) this is not a concern.
698
+
699
+ ### Eager and local worker modes
700
+
701
+ Batch tasks respect `LAMBDA_TASKS_EAGER` and `LAMBDA_TASKS_LOCAL_WORKERS` — in development, they run in-process or in the local process pool just like Lambda tasks, without requiring AWS infrastructure.
@@ -98,12 +98,20 @@ All settings are read from your Django settings module and prefixed with `LAMBDA
98
98
  | Setting | Type | Description |
99
99
  |---|---|---|
100
100
  | `LAMBDA_TASKS_QUEUES` | `dict[str, str]` | Map of queue name → SQS queue URL. Must include a `"default"` key. |
101
+ | `LAMBDA_TASKS_BATCH_QUEUES` | `dict[str, dict]` | Map of queue name → `{"job_queue": ..., "job_definition": ...}`. Must include a `"default"` key when set. Default `{}`. |
101
102
 
102
103
  ```python
103
104
  LAMBDA_TASKS_QUEUES = {
104
105
  "default": "https://sqs.us-east-1.amazonaws.com/123456789/default",
105
106
  "high_memory": "https://sqs.us-east-1.amazonaws.com/123456789/high-memory",
106
107
  }
108
+
109
+ LAMBDA_TASKS_BATCH_QUEUES = {
110
+ "default": {
111
+ "job_queue": "arn:aws:batch:eu-west-1:123456789:job-queue/my-queue",
112
+ "job_definition": "arn:aws:batch:eu-west-1:123456789:job-definition/my-def:1",
113
+ },
114
+ }
107
115
  ```
108
116
 
109
117
  `LAMBDA_TASKS_QUEUES` must be set and must include a `"default"` key, otherwise `ImproperlyConfigured` is raised on first use.
@@ -611,3 +619,71 @@ result = send_welcome_email(user_id=1, template="welcome")
611
619
  ```
612
620
 
613
621
  This bypasses the queue entirely and runs the function in the current process. No `TaskRecord` is created, no `transaction.atomic()` block is used, and no timeout enforcement applies — it behaves exactly like calling the underlying function directly. Kwargs are still validated against the task's type annotations via Pydantic.
622
+
623
+ ---
624
+
625
+ ## AWS Batch tasks
626
+
627
+ For tasks that exceed Lambda's 15-minute timeout or 10GB ephemeral storage limit, use `@batch_task` to route execution to AWS Batch containers.
628
+
629
+ ### 1. Define a batch task
630
+
631
+ ```python
632
+ # myapp/tasks.py
633
+ from lambda_tasks.decorators import batch_task
634
+
635
+ @batch_task(soft_timeout=1800, hard_timeout=3500)
636
+ def process_large_file(*, file_id: int) -> str:
637
+ # long-running work with large disk usage
638
+ return "done"
639
+ ```
640
+
641
+ ### 2. Configure AWS Batch queues
642
+
643
+ ```python
644
+ # settings.py
645
+ LAMBDA_TASKS_BATCH_QUEUES = {
646
+ "default": {
647
+ "job_queue": "arn:aws:batch:eu-west-1:123456789:job-queue/my-queue",
648
+ "job_definition": "arn:aws:batch:eu-west-1:123456789:job-definition/my-def:1",
649
+ },
650
+ }
651
+ ```
652
+
653
+ ### 3. Enqueue from a view
654
+
655
+ ```python
656
+ process_large_file.execute_on_commit(file_id=42)
657
+ ```
658
+
659
+ ### How it works
660
+
661
+ 1. `execute_on_commit()` enqueues a message to SQS (same as Lambda tasks)
662
+ 2. The Lambda handler picks up the message and runs the built-in `submit_batch_job` task
663
+ 3. `submit_batch_job` calls `batch.submit_job()` with the task message as a `LAMBDA_TASKS_MESSAGE` environment variable override
664
+ 4. The Batch container runs `python -m lambda_tasks.handler`, which reads the env var, performs cold-start init, and calls `execute_immediately()`
665
+ 5. The task runs with full timeout enforcement (`SIGALRM`), retry support, and `TaskRecord` tracking
666
+
667
+ ### Batch task timeouts
668
+
669
+ Batch tasks support the same two-phase timeout mechanism as Lambda tasks (soft + hard), but with a maximum of **3600 seconds** (1 hour) instead of 900.
670
+
671
+ ### Retries
672
+
673
+ Batch tasks support `retry_on` — when a retryable exception is raised, the retry goes back through SQS → Lambda → `submit_batch_job` → new Batch job. The same `LAMBDA_TASKS_MAX_RETRIES` limit applies.
674
+
675
+ ### Batch job definition setup
676
+
677
+ Your Batch job definition should:
678
+ - Use the same Docker image as your application
679
+ - Set `DJANGO_SETTINGS_MODULE` as an environment variable
680
+ - Configure secrets via the job definition's `secrets` field or via `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` / `LAMBDA_TASKS_SECRET_*` env vars (same mechanism as Lambda)
681
+ - Set the default command to `["python", "-m", "lambda_tasks.handler"]`
682
+
683
+ ### Container overrides limit
684
+
685
+ The total `containerOverrides` payload (including JSON formatting) is limited to 8192 characters by AWS Batch. For typical task kwargs (IDs, short strings) this is not a concern.
686
+
687
+ ### Eager and local worker modes
688
+
689
+ Batch tasks respect `LAMBDA_TASKS_EAGER` and `LAMBDA_TASKS_LOCAL_WORKERS` — in development, they run in-process or in the local process pool just like Lambda tasks, without requiring AWS infrastructure.
@@ -17,6 +17,8 @@ class TaskRecordAdmin(admin.ModelAdmin):
17
17
  list_display = (
18
18
  "pk",
19
19
  "task_name",
20
+ "backend",
21
+ "queue",
20
22
  "status",
21
23
  "start_time",
22
24
  "end_time",
@@ -59,10 +61,24 @@ class TaskRecordAdmin(admin.ModelAdmin):
59
61
  total_seconds = d.total_seconds()
60
62
  return f"{total_seconds:.3f}s"
61
63
 
64
+ @admin.display(description="Backend")
65
+ def backend(self, obj: TaskRecord) -> str:
66
+ try:
67
+ return import_string(obj.task_name).backend.value
68
+ except Exception:
69
+ return "?"
70
+
71
+ @admin.display(description="Queue")
72
+ def queue(self, obj: TaskRecord) -> str:
73
+ try:
74
+ return import_string(obj.task_name).queue
75
+ except Exception:
76
+ return "?"
77
+
62
78
  @admin.action(description="Replay selected tasks", permissions=("change",))
63
79
  def replay_tasks(self, request: HttpRequest, queryset: QuerySet) -> None:
64
80
  for record in queryset:
65
- queue = import_string(record.task_name).queue
81
+ wrapper = import_string(record.task_name)
66
82
  task = SQSLambdaTask(
67
83
  message=SQSLambdaTaskMessage(
68
84
  task_name=record.task_name,
@@ -70,6 +86,6 @@ class TaskRecordAdmin(admin.ModelAdmin):
70
86
  n_retries=0,
71
87
  ),
72
88
  delay=0,
73
- queue=queue,
89
+ queue=wrapper.queue,
74
90
  )
75
91
  task.execute_on_commit()