django-lambda-tasks 0.4.11__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.4.11 → django_lambda_tasks-0.6.0}/.kiro/steering/product.md +39 -10
  2. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/steering/structure.md +10 -10
  3. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/PKG-INFO +95 -19
  4. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/README.md +94 -18
  5. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/admin.py +18 -2
  6. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/decorators.py +125 -59
  7. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/environment_loader.py +17 -25
  8. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/handler.py +34 -0
  9. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/models.py +14 -6
  10. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/secret_loader.py +30 -25
  11. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/settings.py +32 -0
  12. django_lambda_tasks-0.6.0/lambda_tasks/tasks.py +74 -0
  13. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/pyproject.toml +1 -1
  14. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_admin.py +5 -0
  15. django_lambda_tasks-0.6.0/tests/test_batch.py +210 -0
  16. django_lambda_tasks-0.6.0/tests/test_batch_decorators.py +158 -0
  17. django_lambda_tasks-0.6.0/tests/test_batch_worker.py +66 -0
  18. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_environment_loader.py +11 -20
  19. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_local_executor.py +6 -6
  20. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_models.py +21 -25
  21. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_secret_loader.py +15 -20
  22. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_settings.py +78 -0
  23. django_lambda_tasks-0.4.11/lambda_tasks/tasks.py +0 -23
  24. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.github/workflows/ci.yml +0 -0
  25. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.github/workflows/release.yml +0 -0
  26. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.gitignore +0 -0
  27. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  28. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/design.md +0 -0
  29. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
  30. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
  31. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  32. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  33. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  34. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  35. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  36. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  37. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  38. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  39. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  40. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  41. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  42. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  43. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  44. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  45. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  46. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  47. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
  48. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/design.md +0 -0
  49. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/requirements.md +0 -0
  50. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/retry-delay/tasks.md +0 -0
  51. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  52. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
  53. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  54. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  55. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  56. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  57. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  58. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  59. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
  60. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/design.md +0 -0
  61. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/requirements.md +0 -0
  62. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/singleton-task/tasks.md +0 -0
  63. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  64. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  65. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  66. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  67. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/.config.kiro +0 -0
  68. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/design.md +0 -0
  69. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/requirements.md +0 -0
  70. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/specs/task-retry/tasks.md +0 -0
  71. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.kiro/steering/tech.md +0 -0
  72. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.pre-commit-config.yaml +0 -0
  73. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/.vscode/settings.json +0 -0
  74. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/README.md +0 -0
  75. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_app/__init__.py +0 -0
  76. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_app/apps.py +0 -0
  77. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_app/tasks.py +0 -0
  78. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_app/urls.py +0 -0
  79. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_app/views.py +0 -0
  80. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_project/__init__.py +0 -0
  81. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_project/settings.py +0 -0
  82. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_project/urls.py +0 -0
  83. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/example_project/wsgi.py +0 -0
  84. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/example/manage.py +0 -0
  85. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/__init__.py +0 -0
  86. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/apps.py +0 -0
  87. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/local_executor.py +0 -0
  88. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/logging.py +0 -0
  89. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/0001_initial.py +0 -0
  90. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  91. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/migrations/__init__.py +0 -0
  92. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/lambda_tasks/timeouts.py +0 -0
  93. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/conftest.py +0 -0
  94. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/settings.py +0 -0
  95. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_decorator.py +0 -0
  96. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_decorators.py +0 -0
  97. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_deferred_enqueue.py +0 -0
  98. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_handler.py +0 -0
  99. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_kwargs_only.py +0 -0
  100. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_logging.py +0 -0
  101. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_memory_limit.py +0 -0
  102. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_serializer.py +0 -0
  103. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_tasks.py +0 -0
  104. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.6.0}/tests/test_timeout_validation.py +0 -0
  105. {django_lambda_tasks-0.4.11 → 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,18 +153,24 @@ 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()`.
138
165
 
139
166
  When the environment variable `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` is set, the loader parses the reference, fetches the named Secrets Manager secret, parses its JSON content as a flat key-value mapping, and sets the resulting pairs as environment variables.
140
167
 
141
- Required format: `<arn>:<version-stage>:<version-id>` (9 colon-separated segments — the ARN is 7 segments, plus version-stage and version-id). Both suffix fields must be non-empty.
168
+ Required format: `<arn>:<version-id>` (8 colon-separated segments — the ARN is 7 segments, plus the version-id). The version-id must be non-empty.
142
169
 
143
170
  Behaviour:
144
171
  - If `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` is not set, does nothing (no AWS API calls)
145
172
  - Validates the reference format before any AWS call — malformed references raise `ValueError` immediately
146
- - Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionStage=..., VersionId=...)`
173
+ - Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
147
174
  - Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
148
175
  - Sets each key-value pair in `os.environ` — existing env vars are overridden (no conflict detection)
149
176
  - Idempotent via a module-level `_loaded` sentinel — subsequent calls are free no-ops
@@ -155,12 +182,12 @@ Behaviour:
155
182
 
156
183
  Any env var prefixed `LAMBDA_TASKS_SECRET_` is treated as a Secrets Manager reference. The unprefixed name is the target env var.
157
184
 
158
- Required format: `<arn>:<json-key>:<version-stage>:<version-id>` (10 colon-separated segments, all fields non-empty).
185
+ Required format: `<arn>:<json-key>:<version-stage>:<version-id>` (10 colon-separated segments). The version-stage segment must be empty. The json-key and version-id must be non-empty.
159
186
 
160
187
  Behaviour:
161
188
  - All references are validated before any AWS call — malformed references raise `ValueError` immediately
162
189
  - Setting both `LAMBDA_TASKS_SECRET_FOO` and `FOO` is a configuration error and raises `ValueError`
163
- - Calls are batched by `(ARN, version-stage, version-id)` — one `GetSecretValue` per unique combination
190
+ - Calls are batched by `(ARN, version-id)` — one `GetSecretValue` per unique combination
164
191
  - Fetched secrets are cached in-process; warm invocations pay no extra cost
165
192
 
166
193
  ## Models
@@ -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.4.11
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.
@@ -150,7 +158,7 @@ LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT = 270
150
158
 
151
159
  | Setting | Type | Description |
152
160
  |---|---|---|
153
- | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-stage>:<version-id>`) to load as environment variables at Lambda cold start. |
161
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at Lambda cold start. |
154
162
  | `LAMBDA_TASKS_SECRET_*` | env var(s) | Secrets Manager references resolved into env vars at Lambda cold start. The unprefixed name becomes the target env var. |
155
163
 
156
164
  These are environment variables set on the Lambda function, not Django settings. See [Loading environment variables from Secrets Manager](#loading-environment-variables-from-secrets-manager) and [Resolving individual secrets from AWS Secrets Manager](#resolving-individual-secrets-from-aws-secrets-manager) for full details.
@@ -203,7 +211,6 @@ Key characteristics:
203
211
 
204
212
  ```python
205
213
  @lambda_task(
206
- delay=0, # seconds — SQS DelaySeconds before message becomes visible
207
214
  soft_timeout=60, # seconds — overrides global default for this task
208
215
  hard_timeout=90, # seconds — overrides global default for this task
209
216
  queue="default", # named queue from LAMBDA_TASKS_QUEUES
@@ -493,20 +500,20 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
493
500
  | Environment Variable | Required | Description |
494
501
  |---|---|---|
495
502
  | `DJANGO_SETTINGS_MODULE` | Yes | Django settings module path (e.g. `myapp.settings.production`). |
496
- | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-stage>:<version-id>`) to load as environment variables at cold start (runs first). |
503
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at cold start (runs first). |
497
504
  | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into individual env vars at cold start (runs second, after environment loading). |
498
505
 
499
506
  ### Loading environment variables from Secrets Manager
500
507
 
501
508
  The Lambda handler supports loading environment variables from an AWS Secrets Manager secret at cold start. This lets you manage environment configuration centrally in Secrets Manager without baking values into the Lambda deployment package.
502
509
 
503
- Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
510
+ Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version ID:
504
511
 
505
512
  ```
506
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
513
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:v1
507
514
  ```
508
515
 
509
- The format is `<arn>:<version-stage>:<version-id>` (9 colon-separated segments — the ARN is 7 segments, plus version-stage and version-id). Both suffix fields must be non-empty.
516
+ The format is `<arn>:<version-id>` (8 colon-separated segments — the ARN is 7 segments, plus the version-id). The version-id must be non-empty.
510
517
 
511
518
  The secret value must be a flat JSON object where all keys and values are strings:
512
519
 
@@ -521,8 +528,8 @@ The secret value must be a flat JSON object where all keys and values are string
521
528
  At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
522
529
 
523
530
  1. Checks for the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` env var — if not set, does nothing
524
- 2. Parses and validates the reference format (9 segments, non-empty version-stage and version-id)
525
- 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionStage=..., VersionId=...)`
531
+ 2. Parses and validates the reference format (8 segments, non-empty version-id)
532
+ 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
526
533
  4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
527
534
  5. Sets each key-value pair in `os.environ` — existing env vars are overridden
528
535
  6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
@@ -533,7 +540,7 @@ Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_
533
540
 
534
541
  The following raise `ValueError` at cold start, preventing the Lambda container from starting:
535
542
 
536
- - Reference format is invalid (wrong segment count, empty version-stage or version-id)
543
+ - Reference format is invalid (wrong segment count, empty version-id)
537
544
  - Secret value is not valid JSON
538
545
  - JSON is not a flat object (contains non-string values) — error message lists the offending keys
539
546
  - JSON contains an empty string key
@@ -547,14 +554,14 @@ The Lambda handler supports loading individual secret values from AWS Secrets Ma
547
554
  Set any env var with the prefix `LAMBDA_TASKS_SECRET_` to a full Secrets Manager dynamic reference. The unprefixed name becomes the target env var:
548
555
 
549
556
  ```
550
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
557
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
551
558
  ```
552
559
 
553
560
  At cold start (on the first handler invocation), after `resolve_environment()` and before `django.setup()`, the handler calls `resolve_secrets_into_env()` which:
554
561
 
555
562
  1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
556
563
  2. Validates every reference — malformed references raise immediately so the container fails to start rather than misconfiguring Django silently
557
- 3. Groups references by `(ARN, version-stage, version-id)` and makes one `GetSecretValue` call per unique combination
564
+ 3. Groups references by `(ARN, version-id)` and makes one `GetSecretValue` call per unique combination
558
565
  4. Extracts the named JSON key from the secret and writes it into `os.environ`
559
566
  5. Caches fetched secrets in-process — warm invocations pay no extra cost
560
567
 
@@ -566,18 +573,18 @@ Every value must follow the full dynamic reference syntax:
566
573
  <arn>:<json-key>:<version-stage>:<version-id>
567
574
  ```
568
575
 
569
- All four fields are required and must be non-empty. The secret value must be a JSON object; `json-key` names the field to extract.
576
+ The version-stage segment must be empty. The json-key and version-id fields are required and must be non-empty. The secret value must be a JSON object; `json-key` names the field to extract.
570
577
 
571
578
  ```
572
- # arn (7 segments) : json-key : version-stage : version-id
573
- arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
579
+ # arn (7 segments) : json-key : version-stage (empty) : version-id
580
+ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
574
581
  ```
575
582
 
576
- Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-stage, version-id)` combination:
583
+ Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-id)` combination:
577
584
 
578
585
  ```
579
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL:AWSCURRENT:v1
580
- LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
586
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL::v1
587
+ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY::v1
581
588
  ```
582
589
 
583
590
  #### Validation errors
@@ -585,7 +592,8 @@ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
585
592
  The following all raise `ValueError` at cold start, preventing the Lambda container from starting with a misconfigured environment:
586
593
 
587
594
  - Wrong number of colon-separated segments (must be exactly 10)
588
- - Empty `json-key`, `version-stage`, or `version-id`
595
+ - Empty `json-key` or `version-id`
596
+ - Non-empty `version-stage` (version-stage is not supported)
589
597
  - Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
590
598
  - The named JSON key does not exist in the fetched secret
591
599
  - The secret value is not valid JSON
@@ -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.
@@ -138,7 +146,7 @@ LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT = 270
138
146
 
139
147
  | Setting | Type | Description |
140
148
  |---|---|---|
141
- | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-stage>:<version-id>`) to load as environment variables at Lambda cold start. |
149
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at Lambda cold start. |
142
150
  | `LAMBDA_TASKS_SECRET_*` | env var(s) | Secrets Manager references resolved into env vars at Lambda cold start. The unprefixed name becomes the target env var. |
143
151
 
144
152
  These are environment variables set on the Lambda function, not Django settings. See [Loading environment variables from Secrets Manager](#loading-environment-variables-from-secrets-manager) and [Resolving individual secrets from AWS Secrets Manager](#resolving-individual-secrets-from-aws-secrets-manager) for full details.
@@ -191,7 +199,6 @@ Key characteristics:
191
199
 
192
200
  ```python
193
201
  @lambda_task(
194
- delay=0, # seconds — SQS DelaySeconds before message becomes visible
195
202
  soft_timeout=60, # seconds — overrides global default for this task
196
203
  hard_timeout=90, # seconds — overrides global default for this task
197
204
  queue="default", # named queue from LAMBDA_TASKS_QUEUES
@@ -481,20 +488,20 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
481
488
  | Environment Variable | Required | Description |
482
489
  |---|---|---|
483
490
  | `DJANGO_SETTINGS_MODULE` | Yes | Django settings module path (e.g. `myapp.settings.production`). |
484
- | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-stage>:<version-id>`) to load as environment variables at cold start (runs first). |
491
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at cold start (runs first). |
485
492
  | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into individual env vars at cold start (runs second, after environment loading). |
486
493
 
487
494
  ### Loading environment variables from Secrets Manager
488
495
 
489
496
  The Lambda handler supports loading environment variables from an AWS Secrets Manager secret at cold start. This lets you manage environment configuration centrally in Secrets Manager without baking values into the Lambda deployment package.
490
497
 
491
- Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
498
+ Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version ID:
492
499
 
493
500
  ```
494
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
501
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:v1
495
502
  ```
496
503
 
497
- The format is `<arn>:<version-stage>:<version-id>` (9 colon-separated segments — the ARN is 7 segments, plus version-stage and version-id). Both suffix fields must be non-empty.
504
+ The format is `<arn>:<version-id>` (8 colon-separated segments — the ARN is 7 segments, plus the version-id). The version-id must be non-empty.
498
505
 
499
506
  The secret value must be a flat JSON object where all keys and values are strings:
500
507
 
@@ -509,8 +516,8 @@ The secret value must be a flat JSON object where all keys and values are string
509
516
  At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
510
517
 
511
518
  1. Checks for the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` env var — if not set, does nothing
512
- 2. Parses and validates the reference format (9 segments, non-empty version-stage and version-id)
513
- 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionStage=..., VersionId=...)`
519
+ 2. Parses and validates the reference format (8 segments, non-empty version-id)
520
+ 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
514
521
  4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
515
522
  5. Sets each key-value pair in `os.environ` — existing env vars are overridden
516
523
  6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
@@ -521,7 +528,7 @@ Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_
521
528
 
522
529
  The following raise `ValueError` at cold start, preventing the Lambda container from starting:
523
530
 
524
- - Reference format is invalid (wrong segment count, empty version-stage or version-id)
531
+ - Reference format is invalid (wrong segment count, empty version-id)
525
532
  - Secret value is not valid JSON
526
533
  - JSON is not a flat object (contains non-string values) — error message lists the offending keys
527
534
  - JSON contains an empty string key
@@ -535,14 +542,14 @@ The Lambda handler supports loading individual secret values from AWS Secrets Ma
535
542
  Set any env var with the prefix `LAMBDA_TASKS_SECRET_` to a full Secrets Manager dynamic reference. The unprefixed name becomes the target env var:
536
543
 
537
544
  ```
538
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
545
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
539
546
  ```
540
547
 
541
548
  At cold start (on the first handler invocation), after `resolve_environment()` and before `django.setup()`, the handler calls `resolve_secrets_into_env()` which:
542
549
 
543
550
  1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
544
551
  2. Validates every reference — malformed references raise immediately so the container fails to start rather than misconfiguring Django silently
545
- 3. Groups references by `(ARN, version-stage, version-id)` and makes one `GetSecretValue` call per unique combination
552
+ 3. Groups references by `(ARN, version-id)` and makes one `GetSecretValue` call per unique combination
546
553
  4. Extracts the named JSON key from the secret and writes it into `os.environ`
547
554
  5. Caches fetched secrets in-process — warm invocations pay no extra cost
548
555
 
@@ -554,18 +561,18 @@ Every value must follow the full dynamic reference syntax:
554
561
  <arn>:<json-key>:<version-stage>:<version-id>
555
562
  ```
556
563
 
557
- All four fields are required and must be non-empty. The secret value must be a JSON object; `json-key` names the field to extract.
564
+ The version-stage segment must be empty. The json-key and version-id fields are required and must be non-empty. The secret value must be a JSON object; `json-key` names the field to extract.
558
565
 
559
566
  ```
560
- # arn (7 segments) : json-key : version-stage : version-id
561
- arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
567
+ # arn (7 segments) : json-key : version-stage (empty) : version-id
568
+ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
562
569
  ```
563
570
 
564
- Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-stage, version-id)` combination:
571
+ Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-id)` combination:
565
572
 
566
573
  ```
567
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL:AWSCURRENT:v1
568
- LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
574
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL::v1
575
+ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY::v1
569
576
  ```
570
577
 
571
578
  #### Validation errors
@@ -573,7 +580,8 @@ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
573
580
  The following all raise `ValueError` at cold start, preventing the Lambda container from starting with a misconfigured environment:
574
581
 
575
582
  - Wrong number of colon-separated segments (must be exactly 10)
576
- - Empty `json-key`, `version-stage`, or `version-id`
583
+ - Empty `json-key` or `version-id`
584
+ - Non-empty `version-stage` (version-stage is not supported)
577
585
  - Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
578
586
  - The named JSON key does not exist in the fetched secret
579
587
  - The secret value is not valid JSON
@@ -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()