django-lambda-tasks 0.1.0__tar.gz → 0.1.1__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.
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/steering/product.md +2 -2
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/steering/structure.md +2 -2
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/PKG-INFO +7 -7
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/README.md +6 -6
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/handler.py +6 -4
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/secret_loader.py +7 -7
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/pyproject.toml +1 -1
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_handler.py +2 -3
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_secret_loader.py +29 -29
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.github/workflows/ci.yml +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.github/workflows/release.yml +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.gitignore +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/import-string-task-resolution/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/task-retry/.config.kiro +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/task-retry/design.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/task-retry/requirements.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/task-retry/tasks.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/steering/tech.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.pre-commit-config.yaml +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.vscode/settings.json +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/README.md +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_app/__init__.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_app/apps.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_app/tasks.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_app/urls.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_app/views.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_project/__init__.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_project/settings.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_project/urls.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/example_project/wsgi.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/example/manage.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/__init__.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/admin.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/apps.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/decorators.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/logging.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/migrations/0001_initial.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/migrations/__init__.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/models.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/settings.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/timeouts.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/conftest.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/settings.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_admin.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_decorator.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_decorators.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_deferred_enqueue.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_kwargs_only.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_logging.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_models.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_serializer.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_settings.py +0 -0
- {django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/tests/test_timeouts.py +0 -0
|
@@ -121,13 +121,13 @@ class SQSLambdaTaskMessage(BaseModel):
|
|
|
121
121
|
|
|
122
122
|
`resolve_secrets_into_env()` in `secret_loader.py` runs once at Lambda cold start, before `django.setup()`.
|
|
123
123
|
|
|
124
|
-
Any env var prefixed `
|
|
124
|
+
Any env var prefixed `LAMBDA_TASKS_SECRET_` is treated as a Secrets Manager reference. The unprefixed name is the target env var.
|
|
125
125
|
|
|
126
126
|
Required format: `<arn>:<json-key>:<version-stage>:<version-id>` (10 colon-separated segments, all fields non-empty).
|
|
127
127
|
|
|
128
128
|
Behaviour:
|
|
129
129
|
- All references are validated before any AWS call — malformed references raise `ValueError` immediately
|
|
130
|
-
- Setting both `
|
|
130
|
+
- Setting both `LAMBDA_TASKS_SECRET_FOO` and `FOO` is a configuration error and raises `ValueError`
|
|
131
131
|
- Calls are batched by `(ARN, version-stage, version-id)` — one `GetSecretValue` per unique combination
|
|
132
132
|
- Fetched secrets are cached in-process; warm invocations pay no extra cost
|
|
133
133
|
|
|
@@ -17,7 +17,7 @@ django-lambda-tasks/
|
|
|
17
17
|
│ ├── logging.py # task_logger — invocation-scoped LoggerAdapter
|
|
18
18
|
│ ├── models.py # TaskRecord, SQSLambdaTaskMessage, SQSLambdaTask
|
|
19
19
|
│ ├── settings.py # LambdaTasksSettings (lazy Django settings reader)
|
|
20
|
-
│ ├── secret_loader.py # Resolves
|
|
20
|
+
│ ├── secret_loader.py # Resolves LAMBDA_TASKS_SECRET_* env vars at cold start
|
|
21
21
|
│ ├── timeouts.py # TimeoutContext implementation
|
|
22
22
|
│ └── migrations/ # Django migrations for TaskRecord
|
|
23
23
|
├── tests/ # pytest test suite
|
|
@@ -39,7 +39,7 @@ django-lambda-tasks/
|
|
|
39
39
|
- `decorators.py` — defines `@lambda_task`; enforces kwargs-only at decoration time
|
|
40
40
|
- `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic), `SQSLambdaTask` (Pydantic, holds message + routing; `_execute()` publishes to SQS or executes eagerly; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
|
|
41
41
|
- `handler.py` — Lambda entry point; calls `resolve_secrets_into_env()` then `django.setup()` at cold start; processes SQS records independently; returns `batchItemFailures`
|
|
42
|
-
- `secret_loader.py` — resolves `
|
|
42
|
+
- `secret_loader.py` — resolves `LAMBDA_TASKS_SECRET_*` env vars from Secrets Manager before Django starts; validates format, detects conflicts, batches API calls, caches results in-process
|
|
43
43
|
- `logging.py` — `task_logger` singleton; `invocation_id` set/cleared around each task execution
|
|
44
44
|
- `settings.py` — `LambdaTasksSettings` instantiated fresh per use (reads live Django settings)
|
|
45
45
|
- `admin.py` — Django admin registration for `TaskRecord`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-lambda-tasks
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Run async tasks in a lambda function
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: boto3
|
|
@@ -403,15 +403,15 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
|
|
|
403
403
|
|
|
404
404
|
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.
|
|
405
405
|
|
|
406
|
-
Set any env var with the prefix `
|
|
406
|
+
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:
|
|
407
407
|
|
|
408
408
|
```
|
|
409
|
-
|
|
409
|
+
LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
|
|
410
410
|
```
|
|
411
411
|
|
|
412
412
|
At cold start, before `django.setup()` is called, the handler calls `resolve_secrets_into_env()` which:
|
|
413
413
|
|
|
414
|
-
1. Scans all env vars for the `
|
|
414
|
+
1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
|
|
415
415
|
2. Validates every reference — malformed references raise immediately so the container fails to start rather than misconfiguring Django silently
|
|
416
416
|
3. Groups references by `(ARN, version-stage, version-id)` and makes one `GetSecretValue` call per unique combination
|
|
417
417
|
4. Extracts the named JSON key from the secret and writes it into `os.environ`
|
|
@@ -435,8 +435,8 @@ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWS
|
|
|
435
435
|
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:
|
|
436
436
|
|
|
437
437
|
```
|
|
438
|
-
|
|
439
|
-
|
|
438
|
+
LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL:AWSCURRENT:v1
|
|
439
|
+
LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
|
|
440
440
|
```
|
|
441
441
|
|
|
442
442
|
#### Validation errors
|
|
@@ -445,7 +445,7 @@ The following all raise `ValueError` at cold start, preventing the Lambda contai
|
|
|
445
445
|
|
|
446
446
|
- Wrong number of colon-separated segments (must be exactly 10)
|
|
447
447
|
- Empty `json-key`, `version-stage`, or `version-id`
|
|
448
|
-
- Both `
|
|
448
|
+
- Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
|
|
449
449
|
- The named JSON key does not exist in the fetched secret
|
|
450
450
|
- The secret value is not valid JSON
|
|
451
451
|
|
|
@@ -393,15 +393,15 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
|
|
|
393
393
|
|
|
394
394
|
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.
|
|
395
395
|
|
|
396
|
-
Set any env var with the prefix `
|
|
396
|
+
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:
|
|
397
397
|
|
|
398
398
|
```
|
|
399
|
-
|
|
399
|
+
LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
|
|
400
400
|
```
|
|
401
401
|
|
|
402
402
|
At cold start, before `django.setup()` is called, the handler calls `resolve_secrets_into_env()` which:
|
|
403
403
|
|
|
404
|
-
1. Scans all env vars for the `
|
|
404
|
+
1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
|
|
405
405
|
2. Validates every reference — malformed references raise immediately so the container fails to start rather than misconfiguring Django silently
|
|
406
406
|
3. Groups references by `(ARN, version-stage, version-id)` and makes one `GetSecretValue` call per unique combination
|
|
407
407
|
4. Extracts the named JSON key from the secret and writes it into `os.environ`
|
|
@@ -425,8 +425,8 @@ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWS
|
|
|
425
425
|
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:
|
|
426
426
|
|
|
427
427
|
```
|
|
428
|
-
|
|
429
|
-
|
|
428
|
+
LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL:AWSCURRENT:v1
|
|
429
|
+
LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
|
|
430
430
|
```
|
|
431
431
|
|
|
432
432
|
#### Validation errors
|
|
@@ -435,7 +435,7 @@ The following all raise `ValueError` at cold start, preventing the Lambda contai
|
|
|
435
435
|
|
|
436
436
|
- Wrong number of colon-separated segments (must be exactly 10)
|
|
437
437
|
- Empty `json-key`, `version-stage`, or `version-id`
|
|
438
|
-
- Both `
|
|
438
|
+
- Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
|
|
439
439
|
- The named JSON key does not exist in the fetched secret
|
|
440
440
|
- The secret value is not valid JSON
|
|
441
441
|
|
|
@@ -10,16 +10,15 @@ import logging
|
|
|
10
10
|
import os
|
|
11
11
|
|
|
12
12
|
import django
|
|
13
|
-
from django.apps import apps
|
|
13
|
+
from django.apps import apps
|
|
14
14
|
|
|
15
|
-
from lambda_tasks.models import SQSLambdaTaskMessage
|
|
16
15
|
from lambda_tasks.secret_loader import resolve_secrets_into_env
|
|
17
16
|
|
|
18
17
|
# Cold-start Django setup — runs once per Lambda container.
|
|
19
18
|
# Secrets are resolved first so Django settings can reference the populated
|
|
20
|
-
# env vars.
|
|
19
|
+
# env vars. resolve_secrets_into_env() is idempotent and caches fetched
|
|
21
20
|
# secrets in-process, so subsequent invocations pay no extra cost.
|
|
22
|
-
if os.environ.get("DJANGO_SETTINGS_MODULE") and not
|
|
21
|
+
if os.environ.get("DJANGO_SETTINGS_MODULE") and not apps.ready:
|
|
23
22
|
resolve_secrets_into_env()
|
|
24
23
|
django.setup()
|
|
25
24
|
|
|
@@ -32,6 +31,9 @@ def handler(*, event: dict, context: object) -> dict:
|
|
|
32
31
|
|
|
33
32
|
Returns a partial-batch failure report so AWS only re-drives failed records.
|
|
34
33
|
"""
|
|
34
|
+
# Local import due to AppRegistryNotReady
|
|
35
|
+
from lambda_tasks.models import SQSLambdaTaskMessage
|
|
36
|
+
|
|
35
37
|
batch_item_failures: list[dict] = []
|
|
36
38
|
|
|
37
39
|
for record in event["Records"]:
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Resolves environment variables that reference AWS Secrets Manager ARNs.
|
|
3
3
|
|
|
4
|
-
Any env var prefixed with ``
|
|
4
|
+
Any env var prefixed with ``LAMBDA_TASKS_SECRET_`` is treated as a pointer
|
|
5
5
|
to a secret value. The unprefixed name is the target env var to populate.
|
|
6
6
|
|
|
7
7
|
Required value format
|
|
8
8
|
---------------------
|
|
9
9
|
Every reference must follow the full dynamic reference syntax::
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
LAMBDA_TASKS_SECRET_DJANGO_ADMIN_URL=arn:aws:secretsmanager:eu-west-1:123:secret:my-secret:DJANGO_ADMIN_URL:AWSCURRENT:v1
|
|
12
12
|
|
|
13
13
|
That is: ``<arn>:<json-key>:<version-stage>:<version-id>``
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@ All four suffix segments must be present and non-empty.
|
|
|
16
16
|
A malformed reference raises ``ValueError`` immediately so the Lambda
|
|
17
17
|
container fails at cold start rather than silently misconfiguring Django.
|
|
18
18
|
|
|
19
|
-
It is a configuration error to set both ``
|
|
19
|
+
It is a configuration error to set both ``LAMBDA_TASKS_SECRET_FOO`` and
|
|
20
20
|
``FOO`` — use one or the other. Having both raises ``ValueError`` at cold
|
|
21
21
|
start so the misconfiguration is caught immediately.
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ import boto3
|
|
|
35
35
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
37
37
|
|
|
38
|
-
_PREFIX = "
|
|
38
|
+
_PREFIX = "LAMBDA_TASKS_SECRET_"
|
|
39
39
|
|
|
40
40
|
# Module-level cache: (arn, version_stage, version_id) → raw secret string.
|
|
41
41
|
# Populated on first call; reused for the lifetime of the Lambda container.
|
|
@@ -128,14 +128,14 @@ def _fetch_secret(*, client: object, ref: _SecretReference) -> dict[str, str]:
|
|
|
128
128
|
|
|
129
129
|
|
|
130
130
|
def resolve_secrets_into_env() -> None:
|
|
131
|
-
"""Scan env vars for ``
|
|
131
|
+
"""Scan env vars for ``LAMBDA_TASKS_SECRET_*`` references and resolve them.
|
|
132
132
|
|
|
133
133
|
For each matching env var the resolved value is written back into
|
|
134
134
|
``os.environ`` under the unprefixed name.
|
|
135
135
|
|
|
136
136
|
Raises ``ValueError`` at cold start if:
|
|
137
137
|
- A reference is malformed (wrong segment count, any empty field)
|
|
138
|
-
- The target env var is already set — use ``
|
|
138
|
+
- The target env var is already set — use ``LAMBDA_TASKS_SECRET_FOO`` or
|
|
139
139
|
``FOO``, not both
|
|
140
140
|
|
|
141
141
|
This function is idempotent — calling it multiple times is safe and cheap
|
|
@@ -157,7 +157,7 @@ def resolve_secrets_into_env() -> None:
|
|
|
157
157
|
if conflicts:
|
|
158
158
|
raise ValueError(
|
|
159
159
|
"The following environment variables are set both directly and via "
|
|
160
|
-
f"
|
|
160
|
+
f"LAMBDA_TASKS_SECRET_*: {', '.join(sorted(conflicts))}. "
|
|
161
161
|
"Use one or the other, not both."
|
|
162
162
|
)
|
|
163
163
|
|
|
@@ -46,7 +46,7 @@ def _valid_body(task_name: str = "my_module.my_task", **kwargs) -> str:
|
|
|
46
46
|
def _patch_model_validate(side_effect):
|
|
47
47
|
"""Patch SQSLambdaTaskMessage.model_validate_json in the handler module."""
|
|
48
48
|
return patch(
|
|
49
|
-
"lambda_tasks.
|
|
49
|
+
"lambda_tasks.models.SQSLambdaTaskMessage.model_validate_json",
|
|
50
50
|
side_effect=side_effect,
|
|
51
51
|
)
|
|
52
52
|
|
|
@@ -252,7 +252,6 @@ def test_property_11_batch_records_processed_independently(flags):
|
|
|
252
252
|
def test_property_4_django_setup_before_execute_task(monkeypatch):
|
|
253
253
|
import importlib
|
|
254
254
|
|
|
255
|
-
import django
|
|
256
255
|
import django.apps
|
|
257
256
|
|
|
258
257
|
import lambda_tasks.handler as handler_module
|
|
@@ -272,7 +271,7 @@ def test_property_4_django_setup_before_execute_task(monkeypatch):
|
|
|
272
271
|
return MagicMock()
|
|
273
272
|
|
|
274
273
|
monkeypatch.setattr(
|
|
275
|
-
"lambda_tasks.
|
|
274
|
+
"lambda_tasks.models.SQSLambdaTaskMessage.model_validate_json",
|
|
276
275
|
spy_model_validate,
|
|
277
276
|
)
|
|
278
277
|
|
|
@@ -43,7 +43,7 @@ def clear_cache():
|
|
|
43
43
|
|
|
44
44
|
class TestParseReferenceValid:
|
|
45
45
|
def test_returns_named_tuple_with_arn_and_key(self):
|
|
46
|
-
ref = _parse_reference(env_var="
|
|
46
|
+
ref = _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=VALID_REF)
|
|
47
47
|
assert ref.arn == ARN
|
|
48
48
|
assert ref.json_key == "MY_KEY"
|
|
49
49
|
assert ref.version_stage == "AWSCURRENT"
|
|
@@ -51,7 +51,7 @@ class TestParseReferenceValid:
|
|
|
51
51
|
|
|
52
52
|
def test_version_id_populated(self):
|
|
53
53
|
value = f"{ARN}:DJANGO_ADMIN_URL:AWSCURRENT:abc123"
|
|
54
|
-
ref = _parse_reference(env_var="
|
|
54
|
+
ref = _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
|
|
55
55
|
assert ref.arn == ARN
|
|
56
56
|
assert ref.json_key == "DJANGO_ADMIN_URL"
|
|
57
57
|
assert ref.version_stage == "AWSCURRENT"
|
|
@@ -66,43 +66,43 @@ class TestParseReferenceValid:
|
|
|
66
66
|
class TestParseReferenceInvalid:
|
|
67
67
|
def test_plain_arn_rejected(self):
|
|
68
68
|
with pytest.raises(ValueError, match="10 colon-separated segments"):
|
|
69
|
-
_parse_reference(env_var="
|
|
69
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=ARN)
|
|
70
70
|
|
|
71
71
|
def test_empty_json_key_rejected(self):
|
|
72
72
|
# 10 segments but key is empty
|
|
73
73
|
value = f"{ARN}::AWSCURRENT:abc123"
|
|
74
74
|
with pytest.raises(ValueError, match="missing the json-key"):
|
|
75
|
-
_parse_reference(env_var="
|
|
75
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
|
|
76
76
|
|
|
77
77
|
def test_empty_version_stage_rejected(self):
|
|
78
78
|
value = f"{ARN}:MY_KEY::abc123"
|
|
79
79
|
with pytest.raises(ValueError, match="missing the version-stage"):
|
|
80
|
-
_parse_reference(env_var="
|
|
80
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
|
|
81
81
|
|
|
82
82
|
def test_empty_version_id_rejected(self):
|
|
83
83
|
value = f"{ARN}:MY_KEY:AWSCURRENT:"
|
|
84
84
|
with pytest.raises(ValueError, match="missing the version-id"):
|
|
85
|
-
_parse_reference(env_var="
|
|
85
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
|
|
86
86
|
|
|
87
87
|
def test_too_few_segments_rejected(self):
|
|
88
88
|
with pytest.raises(ValueError, match="10 colon-separated segments"):
|
|
89
|
-
_parse_reference(env_var="
|
|
89
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=f"{ARN}:KEY:")
|
|
90
90
|
|
|
91
91
|
def test_too_many_segments_rejected(self):
|
|
92
92
|
with pytest.raises(ValueError, match="10 colon-separated segments"):
|
|
93
93
|
_parse_reference(
|
|
94
|
-
env_var="
|
|
94
|
+
env_var="LAMBDA_TASKS_SECRET_X",
|
|
95
95
|
value=f"{ARN}:KEY:STAGE:VER:EXTRA",
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
def test_error_message_includes_env_var_name(self):
|
|
99
|
-
with pytest.raises(ValueError, match="
|
|
100
|
-
_parse_reference(env_var="
|
|
99
|
+
with pytest.raises(ValueError, match="LAMBDA_TASKS_SECRET_MY_VAR"):
|
|
100
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_MY_VAR", value=ARN)
|
|
101
101
|
|
|
102
102
|
@given(st.text(min_size=1).filter(lambda s: s.count(":") != 9))
|
|
103
103
|
def test_any_non_10_segment_value_rejected(self, value: str):
|
|
104
104
|
with pytest.raises(ValueError):
|
|
105
|
-
_parse_reference(env_var="
|
|
105
|
+
_parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
# ---------------------------------------------------------------------------
|
|
@@ -112,13 +112,13 @@ class TestParseReferenceInvalid:
|
|
|
112
112
|
|
|
113
113
|
class TestNoReferences:
|
|
114
114
|
def test_no_prefixed_vars_makes_no_boto3_call(self, monkeypatch):
|
|
115
|
-
monkeypatch.delenv("
|
|
115
|
+
monkeypatch.delenv("LAMBDA_TASKS_SECRET_ANYTHING", raising=False)
|
|
116
116
|
with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
|
|
117
117
|
resolve_secrets_into_env()
|
|
118
118
|
mock_boto3.client.assert_not_called()
|
|
119
119
|
|
|
120
120
|
def test_no_prefixed_vars_leaves_env_unchanged(self, monkeypatch):
|
|
121
|
-
monkeypatch.delenv("
|
|
121
|
+
monkeypatch.delenv("LAMBDA_TASKS_SECRET_ANYTHING", raising=False)
|
|
122
122
|
before = dict(os.environ)
|
|
123
123
|
resolve_secrets_into_env()
|
|
124
124
|
assert dict(os.environ) == before
|
|
@@ -131,14 +131,14 @@ class TestNoReferences:
|
|
|
131
131
|
|
|
132
132
|
class TestMalformedReference:
|
|
133
133
|
def test_plain_arn_raises_before_any_boto3_call(self, monkeypatch):
|
|
134
|
-
monkeypatch.setenv("
|
|
134
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", ARN)
|
|
135
135
|
with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
|
|
136
|
-
with pytest.raises(ValueError, match="
|
|
136
|
+
with pytest.raises(ValueError, match="LAMBDA_TASKS_SECRET_MY_VAR"):
|
|
137
137
|
resolve_secrets_into_env()
|
|
138
138
|
mock_boto3.client.assert_not_called()
|
|
139
139
|
|
|
140
140
|
def test_empty_key_raises_before_any_boto3_call(self, monkeypatch):
|
|
141
|
-
monkeypatch.setenv("
|
|
141
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", f"{ARN}::AWSCURRENT:abc123")
|
|
142
142
|
with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
|
|
143
143
|
with pytest.raises(ValueError, match="missing the json-key"):
|
|
144
144
|
resolve_secrets_into_env()
|
|
@@ -152,7 +152,7 @@ class TestMalformedReference:
|
|
|
152
152
|
|
|
153
153
|
class TestConflict:
|
|
154
154
|
def test_raises_when_target_already_set(self, monkeypatch):
|
|
155
|
-
monkeypatch.setenv("
|
|
155
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
|
|
156
156
|
monkeypatch.setenv("MY_VAR", "already-set")
|
|
157
157
|
|
|
158
158
|
with patch("lambda_tasks.secret_loader.boto3"):
|
|
@@ -160,8 +160,8 @@ class TestConflict:
|
|
|
160
160
|
resolve_secrets_into_env()
|
|
161
161
|
|
|
162
162
|
def test_error_message_lists_all_conflicts(self, monkeypatch):
|
|
163
|
-
monkeypatch.setenv("
|
|
164
|
-
monkeypatch.setenv("
|
|
163
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_A", VALID_REF)
|
|
164
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_B", VALID_REF2)
|
|
165
165
|
monkeypatch.setenv("VAR_A", "x")
|
|
166
166
|
monkeypatch.setenv("VAR_B", "y")
|
|
167
167
|
|
|
@@ -174,7 +174,7 @@ class TestConflict:
|
|
|
174
174
|
assert "VAR_B" in msg
|
|
175
175
|
|
|
176
176
|
def test_conflict_raises_before_any_boto3_call(self, monkeypatch):
|
|
177
|
-
monkeypatch.setenv("
|
|
177
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
|
|
178
178
|
monkeypatch.setenv("MY_VAR", "already-set")
|
|
179
179
|
|
|
180
180
|
with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
|
|
@@ -190,7 +190,7 @@ class TestConflict:
|
|
|
190
190
|
|
|
191
191
|
class TestResolution:
|
|
192
192
|
def test_sets_target_env_var(self, monkeypatch):
|
|
193
|
-
monkeypatch.setenv("
|
|
193
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
|
|
194
194
|
monkeypatch.delenv("MY_VAR", raising=False)
|
|
195
195
|
|
|
196
196
|
payload = json.dumps({"MY_KEY": "supersecret"})
|
|
@@ -204,7 +204,7 @@ class TestResolution:
|
|
|
204
204
|
assert os.environ["MY_VAR"] == "supersecret"
|
|
205
205
|
|
|
206
206
|
def test_missing_key_in_secret_raises(self, monkeypatch):
|
|
207
|
-
monkeypatch.setenv("
|
|
207
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_SOME_VAR", VALID_REF)
|
|
208
208
|
monkeypatch.delenv("SOME_VAR", raising=False)
|
|
209
209
|
|
|
210
210
|
client = MagicMock()
|
|
@@ -218,7 +218,7 @@ class TestResolution:
|
|
|
218
218
|
resolve_secrets_into_env()
|
|
219
219
|
|
|
220
220
|
def test_invalid_json_in_secret_raises(self, monkeypatch):
|
|
221
|
-
monkeypatch.setenv("
|
|
221
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_SOME_VAR", VALID_REF)
|
|
222
222
|
monkeypatch.delenv("SOME_VAR", raising=False)
|
|
223
223
|
|
|
224
224
|
client = MagicMock()
|
|
@@ -239,8 +239,8 @@ class TestBatching:
|
|
|
239
239
|
def test_single_api_call_for_same_arn(self, monkeypatch):
|
|
240
240
|
ref_a = f"{ARN}:KEY_A:AWSCURRENT:AWSCURRENT"
|
|
241
241
|
ref_b = f"{ARN}:KEY_B:AWSCURRENT:AWSCURRENT"
|
|
242
|
-
monkeypatch.setenv("
|
|
243
|
-
monkeypatch.setenv("
|
|
242
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_A", ref_a)
|
|
243
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_B", ref_b)
|
|
244
244
|
monkeypatch.delenv("VAR_A", raising=False)
|
|
245
245
|
monkeypatch.delenv("VAR_B", raising=False)
|
|
246
246
|
|
|
@@ -261,8 +261,8 @@ class TestBatching:
|
|
|
261
261
|
assert os.environ["VAR_B"] == "val-b"
|
|
262
262
|
|
|
263
263
|
def test_separate_api_calls_for_different_arns(self, monkeypatch):
|
|
264
|
-
monkeypatch.setenv("
|
|
265
|
-
monkeypatch.setenv("
|
|
264
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_A", VALID_REF)
|
|
265
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_B", VALID_REF2)
|
|
266
266
|
monkeypatch.delenv("VAR_A", raising=False)
|
|
267
267
|
monkeypatch.delenv("VAR_B", raising=False)
|
|
268
268
|
|
|
@@ -288,7 +288,7 @@ class TestBatching:
|
|
|
288
288
|
|
|
289
289
|
class TestCaching:
|
|
290
290
|
def test_second_call_makes_no_api_call(self, monkeypatch):
|
|
291
|
-
monkeypatch.setenv("
|
|
291
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
|
|
292
292
|
monkeypatch.delenv("MY_VAR", raising=False)
|
|
293
293
|
|
|
294
294
|
payload = json.dumps({"MY_KEY": "cached-value"})
|
|
@@ -307,7 +307,7 @@ class TestCaching:
|
|
|
307
307
|
secret_loader._secret_cache[(ARN, "AWSCURRENT", "AWSCURRENT")] = {
|
|
308
308
|
"MY_KEY": "pre-cached"
|
|
309
309
|
}
|
|
310
|
-
monkeypatch.setenv("
|
|
310
|
+
monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
|
|
311
311
|
monkeypatch.delenv("MY_VAR", raising=False)
|
|
312
312
|
|
|
313
313
|
with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/design.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/deferred-task-enqueue/tasks.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/design.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/eager-mode-example-app/tasks.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/design.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/rse-background-tasks/tasks.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/.kiro/specs/task-retry/requirements.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_lambda_tasks-0.1.0 → django_lambda_tasks-0.1.1}/lambda_tasks/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|