django-lambda-tasks 0.2.2__py3-none-any.whl → 0.2.3__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lambda-tasks
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: awslambdaric
@@ -127,13 +127,32 @@ LAMBDA_TASKS_DEFAULT_SOFT_TIMEOUT = 240
127
127
  LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT = 270
128
128
  ```
129
129
 
130
+ ### Retry settings
131
+
132
+ | Setting | Type | Default | Description |
133
+ |---|---|---|---|
134
+ | `LAMBDA_TASKS_MAX_RETRIES` | `int` | `2880` | Maximum retry attempts before `MaxRetriesExceededError` is raised (default is 60 × 24 × 2). |
135
+
136
+ ### Singleton settings
137
+
138
+ | Setting | Type | Default | Description |
139
+ |---|---|---|---|
140
+ | `LAMBDA_TASKS_SINGLETON_CACHE` | `str` | `"default"` | Django cache backend used for singleton task locks. |
141
+
142
+ ### Environment and secrets
143
+
144
+ | Setting | Type | Description |
145
+ |---|---|---|
146
+ | `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. |
147
+ | `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. |
148
+
149
+ 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.
150
+
130
151
  ### Eager execution (development / testing)
131
152
 
132
153
  | Setting | Type | Default | Description |
133
154
  |---|---|---|---|
134
155
  | `LAMBDA_TASKS_EAGER` | `bool` | `False` | When `True`, tasks run synchronously in-process instead of being sent to SQS. |
135
- | `LAMBDA_TASKS_MAX_RETRIES` | `int` | `2880` | Maximum retry attempts before `MaxRetriesExceededError` is raised. |
136
- | `LAMBDA_TASKS_SINGLETON_CACHE` | `str` | `"default"` | Django cache backend used for singleton task locks. |
137
156
 
138
157
  ```python
139
158
  # settings/local.py
@@ -142,7 +161,7 @@ LAMBDA_TASKS_EAGER = True
142
161
 
143
162
  With eager mode enabled, `.execute_on_commit()` executes the task immediately without touching SQS. Useful for local development and test suites where you don't want to mock AWS infrastructure.
144
163
 
145
- > **Note:** Timeouts are not enforced in eager mode. `soft_timeout` and `hard_timeout` values are accepted and stored but `TimeoutContext` is never enteredthe task runs without any time limit. This is intentional: `SIGALRM`-based timeouts require a Lambda/Unix worker process, not a Django dev server thread.
164
+ > **Note:** Timeouts are not enforced in eager mode. `soft_timeout` and `hard_timeout` values are accepted and stored but `TimeoutContext` becomes a no-opit checks `LAMBDA_TASKS_EAGER` internally and skips `SIGALRM` setup. This is intentional: `SIGALRM`-based timeouts require a Lambda/Unix worker process, not a Django dev server thread.
146
165
 
147
166
  ---
148
167
 
@@ -150,6 +169,7 @@ With eager mode enabled, `.execute_on_commit()` executes the task immediately wi
150
169
 
151
170
  ```python
152
171
  @lambda_task(
172
+ delay=0, # seconds — SQS DelaySeconds before message becomes visible
153
173
  soft_timeout=60, # seconds — overrides global default for this task
154
174
  hard_timeout=90, # seconds — overrides global default for this task
155
175
  queue="default", # named queue from LAMBDA_TASKS_QUEUES
@@ -164,6 +184,7 @@ def my_task(*, arg: str) -> None:
164
184
 
165
185
  | Parameter | Type | Default | Description |
166
186
  |---|---|---|---|
187
+ | `delay` | `int` | `0` | Seconds to delay the SQS message before it becomes visible to consumers (max 900). |
167
188
  | `soft_timeout` | `int \| None` | `None` (uses global default) | Per-task soft timeout in seconds (max 900). |
168
189
  | `hard_timeout` | `int \| None` | `None` (uses global default) | Per-task hard timeout in seconds (max 900). |
169
190
  | `queue` | `str` | `"default"` | Named queue to route this task to. |
@@ -190,7 +211,7 @@ payload = send_welcome_email.serialize(user_id=42, template="welcome")
190
211
  # }
191
212
  ```
192
213
 
193
- The returned dict matches the `SQSLambdaTask` schema. To reconstruct and enqueue it later:
214
+ The returned dict matches the `SQSLambdaTask` schema (`message`, `delay`, `queue`). The `delay` value comes from the decorator's `delay` parameter. To reconstruct and enqueue it later:
194
215
 
195
216
  ```python
196
217
  from lambda_tasks.models import SQSLambdaTask
@@ -270,8 +291,10 @@ TaskRecord.objects.get(pk="<uuid>")
270
291
 
271
292
  | Field | Type | Description |
272
293
  |---|---|---|
294
+ | `id` | `UUID` | Primary key — set to the SQS `messageId` for deduplication. |
273
295
  | `task_name` | `str` | Fully-qualified function name (e.g. `myapp.tasks.send_welcome_email`). |
274
296
  | `kwargs` | `dict` | Serialized task arguments. |
297
+ | `n_retries` | `int` | Number of retries attempted so far (starts at 0). |
275
298
  | `status` | `str` | One of `RUNNING`, `SUCCESS`, `FAILED`, `RETRYING`. |
276
299
  | `start_time` | `datetime \| None` | When the worker began executing the task. |
277
300
  | `end_time` | `datetime \| None` | When the task completed or failed. |
@@ -297,6 +320,8 @@ def send_welcome_email(*, user_id: int, template: str) -> str:
297
320
 
298
321
  `task_logger` is a `LoggerAdapter` wrapping the `lambda_tasks.task` logger. `SQSLambdaTaskMessage.execute_immediately()` sets the `message_id` before each task runs and clears it afterwards — you don't need to manage it yourself.
299
322
 
323
+ The Lambda handler configures the `lambda_tasks` logger hierarchy to `INFO` at cold start so that `task_logger` lines appear in CloudWatch. You can override the level by setting the `LAMBDA_TASKS_LOG_LEVEL` environment variable on your Lambda function (e.g. `DEBUG`, `WARNING`).
324
+
300
325
  Using your own `logging.getLogger(__name__)` is fine too; those records just won't carry the `message_id` prefix.
301
326
 
302
327
  To filter by invocation in CloudWatch Logs Insights:
@@ -442,12 +467,57 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
442
467
  | Environment Variable | Required | Description |
443
468
  |---|---|---|
444
469
  | `DJANGO_SETTINGS_MODULE` | Yes | Django settings module path (e.g. `myapp.settings.production`). |
445
- | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-stage>:<version-id>`) to load as environment variables at cold start. |
446
- | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into env vars at cold start (see below). |
470
+ | `LAMBDA_TASKS_LOG_LEVEL` | No | Log level for the `lambda_tasks` logger hierarchy (default `INFO`). Set to `DEBUG`, `WARNING`, etc. as needed. |
471
+ | `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). |
472
+ | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into individual env vars at cold start (runs second, after environment loading). |
473
+
474
+ ### Loading environment variables from Secrets Manager
475
+
476
+ 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.
477
+
478
+ Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
479
+
480
+ ```
481
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
482
+ ```
483
+
484
+ 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.
485
+
486
+ The secret value must be a flat JSON object where all keys and values are strings:
487
+
488
+ ```json
489
+ {
490
+ "DATABASE_URL": "postgres://user:pass@host:5432/db",
491
+ "REDIS_URL": "redis://host:6379/0",
492
+ "DJANGO_SETTINGS_MODULE": "myapp.settings.production"
493
+ }
494
+ ```
495
+
496
+ At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
497
+
498
+ 1. Checks for the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` env var — if not set, does nothing
499
+ 2. Parses and validates the reference format (9 segments, non-empty version-stage and version-id)
500
+ 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionStage=..., VersionId=...)`
501
+ 4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
502
+ 5. Sets each key-value pair in `os.environ` — existing env vars are overridden
503
+ 6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
504
+
505
+ Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_MODULE` itself, and individual secrets loaded by `resolve_secrets_into_env()` can reference environment-loaded values.
506
+
507
+ #### Validation errors
508
+
509
+ The following raise `ValueError` at cold start, preventing the Lambda container from starting:
447
510
 
448
- ### Resolving Django settings from AWS Secrets Manager
511
+ - Reference format is invalid (wrong segment count, empty version-stage or version-id)
512
+ - Secret value is not valid JSON
513
+ - JSON is not a flat object (contains non-string values) — error message lists the offending keys
514
+ - JSON contains an empty string key
515
+
516
+ AWS errors (secret not found, permission denied) propagate as boto3 exceptions and crash the container at cold start.
449
517
 
450
- The Lambda handler supports loading secret values from AWS Secrets Manager into the environment before Django starts. This lets your Django settings file read from `os.environ` as normal while keeping secrets out of plaintext environment variables.
518
+ ### Resolving individual secrets from AWS Secrets Manager
519
+
520
+ The Lambda handler supports loading individual secret values from AWS Secrets Manager into the environment before Django starts. This lets your Django settings file read from `os.environ` as normal while keeping secrets out of plaintext environment variables.
451
521
 
452
522
  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:
453
523
 
@@ -455,7 +525,7 @@ Set any env var with the prefix `LAMBDA_TASKS_SECRET_` to a full Secrets Manager
455
525
  LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
456
526
  ```
457
527
 
458
- At cold start (on the first handler invocation), before `django.setup()` is called, the handler calls `resolve_secrets_into_env()` which:
528
+ At cold start (on the first handler invocation), after `resolve_environment()` and before `django.setup()`, the handler calls `resolve_secrets_into_env()` which:
459
529
 
460
530
  1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
461
531
  2. Validates every reference — malformed references raise immediately so the container fails to start rather than misconfiguring Django silently
@@ -495,50 +565,6 @@ The following all raise `ValueError` at cold start, preventing the Lambda contai
495
565
  - The named JSON key does not exist in the fetched secret
496
566
  - The secret value is not valid JSON
497
567
 
498
- ### Loading environment variables from Secrets Manager
499
-
500
- 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.
501
-
502
- Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
503
-
504
- ```
505
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
506
- ```
507
-
508
- 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.
509
-
510
- The secret value must be a flat JSON object where all keys and values are strings:
511
-
512
- ```json
513
- {
514
- "DATABASE_URL": "postgres://user:pass@host:5432/db",
515
- "REDIS_URL": "redis://host:6379/0",
516
- "DJANGO_SETTINGS_MODULE": "myapp.settings.production"
517
- }
518
- ```
519
-
520
- At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
521
-
522
- 1. Checks for the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` env var — if not set, does nothing
523
- 2. Parses and validates the reference format (9 segments, non-empty version-stage and version-id)
524
- 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionStage=..., VersionId=...)`
525
- 4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
526
- 5. Sets each key-value pair in `os.environ` — existing env vars are overridden
527
- 6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
528
-
529
- Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_MODULE` itself, and individual secrets loaded by `resolve_secrets_into_env()` can reference environment-loaded values.
530
-
531
- #### Validation errors
532
-
533
- The following raise `ValueError` at cold start, preventing the Lambda container from starting:
534
-
535
- - Reference format is invalid (wrong segment count, empty version-stage or version-id)
536
- - Secret value is not valid JSON
537
- - JSON is not a flat object (contains non-string values) — error message lists the offending keys
538
- - JSON contains an empty string key
539
-
540
- AWS errors (secret not found, permission denied) propagate as boto3 exceptions and crash the container at cold start.
541
-
542
568
  ---
543
569
 
544
570
  ## Built-in tasks
@@ -571,4 +597,4 @@ You can call a decorated task directly like a normal function — useful in test
571
597
  result = send_welcome_email(user_id=1, template="welcome")
572
598
  ```
573
599
 
574
- This bypasses the queue entirely and runs the function in the current process and transaction.
600
+ 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.
@@ -3,7 +3,7 @@ lambda_tasks/admin.py,sha256=QEg6urQPIGF74Ef8v3cAMJn5ax-UzhWpfSdBU3YEvZ8,1211
3
3
  lambda_tasks/apps.py,sha256=WwIpa2eQQujLgdAq1HXVRSwIppRej14O9oU3NFygN6g,186
4
4
  lambda_tasks/decorators.py,sha256=lM4PXu6AnVVQ5yJSoomXi0hCmcq_oLQYxhCefKIUhtQ,15982
5
5
  lambda_tasks/environment_loader.py,sha256=9KW7lFWCf20x9DQ80Oqjj3Xnohfy8CnyUuyCFo95CyU,4757
6
- lambda_tasks/handler.py,sha256=8s7EUlkRNXVyVPuI6obcKD8SiBKcesD1HeME7ontZik,2327
6
+ lambda_tasks/handler.py,sha256=j-tjk8PHU5RgjQKWv-b0d8nUGQgk9ZRnfrkZA-BUqwE,3307
7
7
  lambda_tasks/logging.py,sha256=vAFQ4fdKetdZ6GJhMRFd8gZxAfNuBI1mDbGJ_luKs30,1020
8
8
  lambda_tasks/models.py,sha256=OKftkn8ykbiINKPzTiTSpIHZdo0CFzS-T4uHeAWvUuo,9809
9
9
  lambda_tasks/secret_loader.py,sha256=8OMNMc_C2X6wOwohMkEJakjflNiNxNyQ7fZacFCvMmE,6657
@@ -12,6 +12,6 @@ lambda_tasks/tasks.py,sha256=wFUnreosmK8wuYJCDSlL1ndbw3w4JG1hqoWC4G3lcYo,651
12
12
  lambda_tasks/timeouts.py,sha256=vO-0-gmcRPhMbWRmshd2TM5dy_RCdI6oW_WpR50ohNI,2593
13
13
  lambda_tasks/migrations/0001_initial.py,sha256=73evCSkciOn3e9h8dPp88m4Y_nhHzYC9cz3Fv0WcuUc,2356
14
14
  lambda_tasks/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- django_lambda_tasks-0.2.2.dist-info/METADATA,sha256=_L8xOTZIxJhVJy8Lur0IpRlGRl8gtJoW29lN5KXT2MU,24963
16
- django_lambda_tasks-0.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
17
- django_lambda_tasks-0.2.2.dist-info/RECORD,,
15
+ django_lambda_tasks-0.2.3.dist-info/METADATA,sha256=OEK_CxPCqcFQpuTv0-kAhqWKYhPjkR_EXwqZodl7aBI,27142
16
+ django_lambda_tasks-0.2.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
17
+ django_lambda_tasks-0.2.3.dist-info/RECORD,,
lambda_tasks/handler.py CHANGED
@@ -15,6 +15,9 @@ warm invocations skip it.
15
15
  import logging
16
16
  import os
17
17
 
18
+ import django
19
+ from django.apps import apps
20
+
18
21
  from lambda_tasks.environment_loader import resolve_environment
19
22
  from lambda_tasks.secret_loader import resolve_secrets_into_env
20
23
 
@@ -38,16 +41,36 @@ def _perform_cold_start() -> None:
38
41
  resolve_environment()
39
42
  resolve_secrets_into_env()
40
43
 
41
- if os.environ.get("DJANGO_SETTINGS_MODULE"):
42
- import django
43
- from django.apps import apps
44
+ if os.environ.get("DJANGO_SETTINGS_MODULE") and not apps.ready:
45
+ django.setup()
44
46
 
45
- if not apps.ready:
46
- django.setup()
47
+ _configure_logging()
47
48
 
48
49
  _cold_start_done = True
49
50
 
50
51
 
52
+ def _configure_logging() -> None:
53
+ """Ensure the lambda_tasks logger hierarchy emits at INFO so task log lines
54
+ appear in CloudWatch.
55
+
56
+ The AWS Lambda runtime pre-configures the root logger, but child loggers
57
+ default to WARNING unless explicitly configured. If Django's LOGGING
58
+ dictConfig has already set a level on the ``lambda_tasks`` logger (i.e. the
59
+ user explicitly configured it), we leave it alone. Otherwise we default to
60
+ INFO (or the value of the LAMBDA_TASKS_LOG_LEVEL env var).
61
+ """
62
+ lambda_tasks_logger = logging.getLogger("lambda_tasks")
63
+
64
+ # level == NOTSET means nobody (neither dictConfig nor user code) has
65
+ # explicitly configured this logger — safe to apply our default.
66
+ if lambda_tasks_logger.level != logging.NOTSET:
67
+ return
68
+
69
+ log_level_name = os.environ.get("LAMBDA_TASKS_LOG_LEVEL", "INFO").upper()
70
+ log_level = getattr(logging, log_level_name, logging.INFO)
71
+ lambda_tasks_logger.setLevel(log_level)
72
+
73
+
51
74
  def handler(event: dict, context: object) -> dict:
52
75
  """AWS Lambda entry point. Processes a batch of SQS records.
53
76