django-lambda-tasks 0.4.11__tar.gz → 0.5.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.
Files changed (101) hide show
  1. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/steering/product.md +4 -4
  2. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/PKG-INFO +19 -19
  3. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/README.md +18 -18
  4. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/environment_loader.py +17 -25
  5. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/models.py +2 -2
  6. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/secret_loader.py +30 -25
  7. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/pyproject.toml +1 -1
  8. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_environment_loader.py +11 -20
  9. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_secret_loader.py +15 -20
  10. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.github/workflows/ci.yml +0 -0
  11. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.github/workflows/release.yml +0 -0
  12. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.gitignore +0 -0
  13. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  14. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/async-local-execution/design.md +0 -0
  15. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
  16. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
  17. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  18. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  19. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  20. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  21. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  22. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  23. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  24. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  25. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  26. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  27. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  28. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  29. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  30. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  31. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  32. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  33. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
  34. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/retry-delay/design.md +0 -0
  35. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/retry-delay/requirements.md +0 -0
  36. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/retry-delay/tasks.md +0 -0
  37. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  38. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
  39. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  40. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  41. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  42. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  43. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  44. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  45. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
  46. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/singleton-task/design.md +0 -0
  47. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/singleton-task/requirements.md +0 -0
  48. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/singleton-task/tasks.md +0 -0
  49. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  50. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  51. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  52. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  53. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/task-retry/.config.kiro +0 -0
  54. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/task-retry/design.md +0 -0
  55. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/task-retry/requirements.md +0 -0
  56. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/specs/task-retry/tasks.md +0 -0
  57. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/steering/structure.md +0 -0
  58. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.kiro/steering/tech.md +0 -0
  59. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.pre-commit-config.yaml +0 -0
  60. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/.vscode/settings.json +0 -0
  61. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/README.md +0 -0
  62. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_app/__init__.py +0 -0
  63. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_app/apps.py +0 -0
  64. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_app/tasks.py +0 -0
  65. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_app/urls.py +0 -0
  66. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_app/views.py +0 -0
  67. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_project/__init__.py +0 -0
  68. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_project/settings.py +0 -0
  69. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_project/urls.py +0 -0
  70. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/example_project/wsgi.py +0 -0
  71. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/example/manage.py +0 -0
  72. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/__init__.py +0 -0
  73. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/admin.py +0 -0
  74. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/apps.py +0 -0
  75. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/decorators.py +0 -0
  76. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/handler.py +0 -0
  77. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/local_executor.py +0 -0
  78. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/logging.py +0 -0
  79. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/migrations/0001_initial.py +0 -0
  80. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  81. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/migrations/__init__.py +0 -0
  82. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/settings.py +0 -0
  83. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/tasks.py +0 -0
  84. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/lambda_tasks/timeouts.py +0 -0
  85. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/conftest.py +0 -0
  86. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/settings.py +0 -0
  87. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_admin.py +0 -0
  88. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_decorator.py +0 -0
  89. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_decorators.py +0 -0
  90. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_deferred_enqueue.py +0 -0
  91. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_handler.py +0 -0
  92. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_kwargs_only.py +0 -0
  93. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_local_executor.py +0 -0
  94. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_logging.py +0 -0
  95. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_memory_limit.py +0 -0
  96. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_models.py +0 -0
  97. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_serializer.py +0 -0
  98. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_settings.py +0 -0
  99. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_tasks.py +0 -0
  100. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_timeout_validation.py +0 -0
  101. {django_lambda_tasks-0.4.11 → django_lambda_tasks-0.5.0}/tests/test_timeouts.py +0 -0
@@ -138,12 +138,12 @@ class SQSLambdaTaskMessage(BaseModel):
138
138
 
139
139
  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
140
 
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.
141
+ 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
142
 
143
143
  Behaviour:
144
144
  - If `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` is not set, does nothing (no AWS API calls)
145
145
  - 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=...)`
146
+ - Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
147
147
  - Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
148
148
  - Sets each key-value pair in `os.environ` — existing env vars are overridden (no conflict detection)
149
149
  - Idempotent via a module-level `_loaded` sentinel — subsequent calls are free no-ops
@@ -155,12 +155,12 @@ Behaviour:
155
155
 
156
156
  Any env var prefixed `LAMBDA_TASKS_SECRET_` is treated as a Secrets Manager reference. The unprefixed name is the target env var.
157
157
 
158
- Required format: `<arn>:<json-key>:<version-stage>:<version-id>` (10 colon-separated segments, all fields non-empty).
158
+ 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
159
 
160
160
  Behaviour:
161
161
  - All references are validated before any AWS call — malformed references raise `ValueError` immediately
162
162
  - 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
163
+ - Calls are batched by `(ARN, version-id)` — one `GetSecretValue` per unique combination
164
164
  - Fetched secrets are cached in-process; warm invocations pay no extra cost
165
165
 
166
166
  ## Models
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-lambda-tasks
3
- Version: 0.4.11
3
+ Version: 0.5.0
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: awslambdaric
@@ -150,7 +150,7 @@ LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT = 270
150
150
 
151
151
  | Setting | Type | Description |
152
152
  |---|---|---|
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. |
153
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at Lambda cold start. |
154
154
  | `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
155
 
156
156
  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 +203,6 @@ Key characteristics:
203
203
 
204
204
  ```python
205
205
  @lambda_task(
206
- delay=0, # seconds — SQS DelaySeconds before message becomes visible
207
206
  soft_timeout=60, # seconds — overrides global default for this task
208
207
  hard_timeout=90, # seconds — overrides global default for this task
209
208
  queue="default", # named queue from LAMBDA_TASKS_QUEUES
@@ -493,20 +492,20 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
493
492
  | Environment Variable | Required | Description |
494
493
  |---|---|---|
495
494
  | `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). |
495
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at cold start (runs first). |
497
496
  | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into individual env vars at cold start (runs second, after environment loading). |
498
497
 
499
498
  ### Loading environment variables from Secrets Manager
500
499
 
501
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.
502
501
 
503
- Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
502
+ Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version ID:
504
503
 
505
504
  ```
506
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
505
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:v1
507
506
  ```
508
507
 
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.
508
+ 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
509
 
511
510
  The secret value must be a flat JSON object where all keys and values are strings:
512
511
 
@@ -521,8 +520,8 @@ The secret value must be a flat JSON object where all keys and values are string
521
520
  At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
522
521
 
523
522
  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=...)`
523
+ 2. Parses and validates the reference format (8 segments, non-empty version-id)
524
+ 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
526
525
  4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
527
526
  5. Sets each key-value pair in `os.environ` — existing env vars are overridden
528
527
  6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
@@ -533,7 +532,7 @@ Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_
533
532
 
534
533
  The following raise `ValueError` at cold start, preventing the Lambda container from starting:
535
534
 
536
- - Reference format is invalid (wrong segment count, empty version-stage or version-id)
535
+ - Reference format is invalid (wrong segment count, empty version-id)
537
536
  - Secret value is not valid JSON
538
537
  - JSON is not a flat object (contains non-string values) — error message lists the offending keys
539
538
  - JSON contains an empty string key
@@ -547,14 +546,14 @@ The Lambda handler supports loading individual secret values from AWS Secrets Ma
547
546
  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
547
 
549
548
  ```
550
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
549
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
551
550
  ```
552
551
 
553
552
  At cold start (on the first handler invocation), after `resolve_environment()` and before `django.setup()`, the handler calls `resolve_secrets_into_env()` which:
554
553
 
555
554
  1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
556
555
  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
556
+ 3. Groups references by `(ARN, version-id)` and makes one `GetSecretValue` call per unique combination
558
557
  4. Extracts the named JSON key from the secret and writes it into `os.environ`
559
558
  5. Caches fetched secrets in-process — warm invocations pay no extra cost
560
559
 
@@ -566,18 +565,18 @@ Every value must follow the full dynamic reference syntax:
566
565
  <arn>:<json-key>:<version-stage>:<version-id>
567
566
  ```
568
567
 
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.
568
+ 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
569
 
571
570
  ```
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
571
+ # arn (7 segments) : json-key : version-stage (empty) : version-id
572
+ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
574
573
  ```
575
574
 
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:
575
+ Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-id)` combination:
577
576
 
578
577
  ```
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
578
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL::v1
579
+ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY::v1
581
580
  ```
582
581
 
583
582
  #### Validation errors
@@ -585,7 +584,8 @@ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
585
584
  The following all raise `ValueError` at cold start, preventing the Lambda container from starting with a misconfigured environment:
586
585
 
587
586
  - Wrong number of colon-separated segments (must be exactly 10)
588
- - Empty `json-key`, `version-stage`, or `version-id`
587
+ - Empty `json-key` or `version-id`
588
+ - Non-empty `version-stage` (version-stage is not supported)
589
589
  - Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
590
590
  - The named JSON key does not exist in the fetched secret
591
591
  - The secret value is not valid JSON
@@ -138,7 +138,7 @@ LAMBDA_TASKS_DEFAULT_HARD_TIMEOUT = 270
138
138
 
139
139
  | Setting | Type | Description |
140
140
  |---|---|---|
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. |
141
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | env var | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at Lambda cold start. |
142
142
  | `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
143
 
144
144
  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 +191,6 @@ Key characteristics:
191
191
 
192
192
  ```python
193
193
  @lambda_task(
194
- delay=0, # seconds — SQS DelaySeconds before message becomes visible
195
194
  soft_timeout=60, # seconds — overrides global default for this task
196
195
  hard_timeout=90, # seconds — overrides global default for this task
197
196
  queue="default", # named queue from LAMBDA_TASKS_QUEUES
@@ -481,20 +480,20 @@ Ensure the Lambda execution environment has `DJANGO_SETTINGS_MODULE` set and tha
481
480
  | Environment Variable | Required | Description |
482
481
  |---|---|---|
483
482
  | `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). |
483
+ | `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` | No | Secrets Manager reference (`<arn>:<version-id>`) to load as environment variables at cold start (runs first). |
485
484
  | `LAMBDA_TASKS_SECRET_*` | No | Secrets Manager references resolved into individual env vars at cold start (runs second, after environment loading). |
486
485
 
487
486
  ### Loading environment variables from Secrets Manager
488
487
 
489
488
  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
489
 
491
- Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version stage and version ID:
490
+ Set the `LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN` environment variable to a full reference including version ID:
492
491
 
493
492
  ```
494
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:AWSCURRENT:v1
493
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod/environment:v1
495
494
  ```
496
495
 
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.
496
+ 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
497
 
499
498
  The secret value must be a flat JSON object where all keys and values are strings:
500
499
 
@@ -509,8 +508,8 @@ The secret value must be a flat JSON object where all keys and values are string
509
508
  At cold start (on the first handler invocation), before `resolve_secrets_into_env()` and `django.setup()`, the handler calls `resolve_environment()` which:
510
509
 
511
510
  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=...)`
511
+ 2. Parses and validates the reference format (8 segments, non-empty version-id)
512
+ 3. Fetches the secret via `secretsmanager.get_secret_value(SecretId=..., VersionId=...)`
514
513
  4. Validates the secret value is a flat JSON object (all values must be strings, no empty keys)
515
514
  5. Sets each key-value pair in `os.environ` — existing env vars are overridden
516
515
  6. Caches the result via a module-level sentinel — subsequent calls are free no-ops
@@ -521,7 +520,7 @@ Because environment loading runs first, the secret can provide `DJANGO_SETTINGS_
521
520
 
522
521
  The following raise `ValueError` at cold start, preventing the Lambda container from starting:
523
522
 
524
- - Reference format is invalid (wrong segment count, empty version-stage or version-id)
523
+ - Reference format is invalid (wrong segment count, empty version-id)
525
524
  - Secret value is not valid JSON
526
525
  - JSON is not a flat object (contains non-string values) — error message lists the offending keys
527
526
  - JSON contains an empty string key
@@ -535,14 +534,14 @@ The Lambda handler supports loading individual secret values from AWS Secrets Ma
535
534
  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
535
 
537
536
  ```
538
- LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL:AWSCURRENT:v1
537
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
539
538
  ```
540
539
 
541
540
  At cold start (on the first handler invocation), after `resolve_environment()` and before `django.setup()`, the handler calls `resolve_secrets_into_env()` which:
542
541
 
543
542
  1. Scans all env vars for the `LAMBDA_TASKS_SECRET_` prefix
544
543
  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
544
+ 3. Groups references by `(ARN, version-id)` and makes one `GetSecretValue` call per unique combination
546
545
  4. Extracts the named JSON key from the secret and writes it into `os.environ`
547
546
  5. Caches fetched secrets in-process — warm invocations pay no extra cost
548
547
 
@@ -554,18 +553,18 @@ Every value must follow the full dynamic reference syntax:
554
553
  <arn>:<json-key>:<version-stage>:<version-id>
555
554
  ```
556
555
 
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.
556
+ 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
557
 
559
558
  ```
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
559
+ # arn (7 segments) : json-key : version-stage (empty) : version-id
560
+ arn:aws:secretsmanager:eu-west-1:123456789012:secret:myapp/prod:DATABASE_URL::v1
562
561
  ```
563
562
 
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:
563
+ Multiple env vars can reference different keys from the same secret — only one `GetSecretValue` call is made for that `(ARN, version-id)` combination:
565
564
 
566
565
  ```
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
566
+ LAMBDA_TASKS_SECRET_DATABASE_URL=arn:...:myapp/prod:DATABASE_URL::v1
567
+ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY::v1
569
568
  ```
570
569
 
571
570
  #### Validation errors
@@ -573,7 +572,8 @@ LAMBDA_TASKS_SECRET_SECRET_KEY=arn:...:myapp/prod:SECRET_KEY:AWSCURRENT:v1
573
572
  The following all raise `ValueError` at cold start, preventing the Lambda container from starting with a misconfigured environment:
574
573
 
575
574
  - Wrong number of colon-separated segments (must be exactly 10)
576
- - Empty `json-key`, `version-stage`, or `version-id`
575
+ - Empty `json-key` or `version-id`
576
+ - Non-empty `version-stage` (version-stage is not supported)
577
577
  - Both `LAMBDA_TASKS_SECRET_FOO` and `FOO` are set — use one or the other
578
578
  - The named JSON key does not exist in the fetched secret
579
579
  - The secret value is not valid JSON
@@ -8,10 +8,10 @@ object (all keys and values must be strings), and sets each key-value pair in
8
8
 
9
9
  Required value format::
10
10
 
11
- LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123:secret:my-env:AWSCURRENT:v1
11
+ LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN=arn:aws:secretsmanager:eu-west-1:123:secret:my-env:v1
12
12
 
13
- That is: ``<arn>:<version-stage>:<version-id>`` (9 colon-separated segments).
14
- The ARN is 7 segments, plus version-stage and version-id.
13
+ That is: ``<arn>:<version-id>`` (8 colon-separated segments).
14
+ The ARN is 7 segments, plus the version-id.
15
15
 
16
16
  This is called by the handler on the first invocation (cold start) — before
17
17
  ``resolve_secrets_into_env()`` and before ``django.setup()`` — so that
@@ -36,7 +36,6 @@ _loaded: bool = False
36
36
 
37
37
  class _EnvironmentSecretReference(NamedTuple):
38
38
  arn: str
39
- version_stage: str
40
39
  version_id: str
41
40
 
42
41
 
@@ -76,40 +75,34 @@ def _parse_reference(*, value: str) -> _EnvironmentSecretReference:
76
75
 
77
76
  Expected format::
78
77
 
79
- <arn>:<version-stage>:<version-id>
78
+ <arn>:<version-id>
80
79
 
81
- The ARN itself is 7 colon-separated segments, plus 2 suffix segments
82
- (version-stage, version-id) = 9 total.
83
- Both suffix fields must be non-empty.
80
+ The ARN itself is 7 colon-separated segments, plus 1 suffix segment
81
+ (version-id) = 8 total.
82
+ The version-id field must be non-empty.
84
83
 
85
84
  Raises ``ValueError`` if the format is invalid.
86
85
  """
87
86
  parts = value.split(":")
88
87
 
89
- if len(parts) != 9:
88
+ if len(parts) != 8:
90
89
  raise ValueError(
91
90
  "LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN has an invalid format. "
92
- "Expected <arn>:<version-stage>:<version-id> "
93
- f"(9 colon-separated segments), got {len(parts)}: {value!r}"
91
+ "Expected <arn>:<version-id> "
92
+ f"(8 colon-separated segments), got {len(parts)}: {value!r}"
94
93
  )
95
94
 
96
95
  arn = ":".join(parts[:7])
97
- version_stage = parts[7]
98
- version_id = parts[8]
99
-
100
- for field, field_value in (
101
- ("version-stage", version_stage),
102
- ("version-id", version_id),
103
- ):
104
- if not field_value:
105
- raise ValueError(
106
- f"LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN is missing the "
107
- f"{field} segment: {value!r}"
108
- )
96
+ version_id = parts[7]
97
+
98
+ if not version_id:
99
+ raise ValueError(
100
+ f"LAMBDA_TASKS_ENVIRONMENT_SECRETS_MANAGER_ARN is missing the "
101
+ f"version-id segment: {value!r}"
102
+ )
109
103
 
110
104
  return _EnvironmentSecretReference(
111
105
  arn=arn,
112
- version_stage=version_stage,
113
106
  version_id=version_id,
114
107
  )
115
108
 
@@ -119,7 +112,6 @@ def _fetch_secret(*, ref: _EnvironmentSecretReference) -> str:
119
112
  client = boto3.client("secretsmanager")
120
113
  response = client.get_secret_value(
121
114
  SecretId=ref.arn,
122
- VersionStage=ref.version_stage,
123
115
  VersionId=ref.version_id,
124
116
  )
125
117
  return response["SecretString"]
@@ -18,7 +18,7 @@ from redis.exceptions import LockError
18
18
 
19
19
  from lambda_tasks.local_executor import submit_task
20
20
  from lambda_tasks.logging import task_logger
21
- from lambda_tasks.settings import LambdaTasksSettings
21
+ from lambda_tasks.settings import MAX_DELAY, LambdaTasksSettings
22
22
  from lambda_tasks.timeouts import TimeoutContext
23
23
 
24
24
 
@@ -188,7 +188,7 @@ class SQSLambdaTaskMessage(BaseModel):
188
188
  )
189
189
 
190
190
  delay = min(
191
- wrapper.retry_delay + round(random.uniform(1, 5)), 900
191
+ wrapper.retry_delay + round(random.uniform(1, 5)), MAX_DELAY
192
192
  )
193
193
 
194
194
  retry_task = SQSLambdaTask(
@@ -8,11 +8,12 @@ Required value format
8
8
  ---------------------
9
9
  Every reference must follow the full dynamic reference syntax::
10
10
 
11
- LAMBDA_TASKS_SECRET_DJANGO_ADMIN_URL=arn:aws:secretsmanager:eu-west-1:123:secret:my-secret:DJANGO_ADMIN_URL:AWSCURRENT:v1
11
+ LAMBDA_TASKS_SECRET_DJANGO_ADMIN_URL=arn:aws:secretsmanager:eu-west-1:123:secret:my-secret:DJANGO_ADMIN_URL::v1
12
12
 
13
13
  That is: ``<arn>:<json-key>:<version-stage>:<version-id>``
14
14
 
15
- All four suffix segments must be present and non-empty.
15
+ The version-stage segment must be empty (version-stage is not supported).
16
+ All other suffix segments must be present and non-empty.
16
17
  A malformed reference raises ``ValueError`` immediately so the Lambda
17
18
  container fails at cold start rather than silently misconfiguring Django.
18
19
 
@@ -20,7 +21,7 @@ It is a configuration error to set both ``LAMBDA_TASKS_SECRET_FOO`` and
20
21
  ``FOO`` — use one or the other. Having both raises ``ValueError`` at cold
21
22
  start so the misconfiguration is caught immediately.
22
23
 
23
- Calls are batched by unique (ARN, version-stage, version-id) combination —
24
+ Calls are batched by unique (ARN, version-id) combination —
24
25
  one ``GetSecretValue`` call per unique combination, regardless of how many
25
26
  env vars reference it. Results are cached in-process so repeated calls to
26
27
  ``resolve_secrets_into_env`` are free after the first.
@@ -41,15 +42,14 @@ _PREFIX = "LAMBDA_TASKS_SECRET_"
41
42
  # subsequent calls return immediately without re-scanning or re-resolving.
42
43
  _loaded: bool = False
43
44
 
44
- # Module-level cache: (arn, version_stage, version_id) → raw secret string.
45
+ # Module-level cache: (arn, version_id) → raw secret string.
45
46
  # Populated on first call; reused for the lifetime of the Lambda container.
46
- _secret_cache: dict[tuple[str, str, str], dict[str, str]] = {}
47
+ _secret_cache: dict[tuple[str, str], dict[str, str]] = {}
47
48
 
48
49
 
49
50
  class _SecretReference(NamedTuple):
50
51
  arn: str
51
52
  json_key: str
52
- version_stage: str
53
53
  version_id: str
54
54
 
55
55
 
@@ -62,7 +62,8 @@ def _parse_reference(*, env_var: str, value: str) -> _SecretReference:
62
62
 
63
63
  The ARN itself is 7 colon-separated segments, plus 3 suffix segments
64
64
  (json-key, version-stage, version-id) = 10 total.
65
- All four suffix fields must be non-empty.
65
+ The version-stage segment must be empty. The json-key and version-id
66
+ segments must be non-empty.
66
67
 
67
68
  Raises ``ValueError`` if the format is invalid — this is intentional so
68
69
  the Lambda container fails at cold start rather than starting with a
@@ -83,21 +84,28 @@ def _parse_reference(*, env_var: str, value: str) -> _SecretReference:
83
84
  arn = ":".join(parts[:7])
84
85
  json_key, version_stage, version_id = parts[7], parts[8], parts[9]
85
86
 
86
- for field, field_value in (
87
- ("json-key", json_key),
88
- ("version-stage", version_stage),
89
- ("version-id", version_id),
90
- ):
91
- if not field_value:
92
- raise ValueError(
93
- f"{env_var} is missing the {field} segment in its Secrets Manager "
94
- f"reference: {value!r}"
95
- )
87
+ if not json_key:
88
+ raise ValueError(
89
+ f"{env_var} is missing the json-key segment in its Secrets Manager "
90
+ f"reference: {value!r}"
91
+ )
92
+
93
+ if version_stage:
94
+ raise ValueError(
95
+ f"{env_var} has a non-empty version-stage segment in its Secrets Manager "
96
+ f"reference: {value!r}. The version-stage must be empty "
97
+ f"(format: <arn>:<json-key>::<version-id>)."
98
+ )
99
+
100
+ if not version_id:
101
+ raise ValueError(
102
+ f"{env_var} is missing the version-id segment in its Secrets Manager "
103
+ f"reference: {value!r}"
104
+ )
96
105
 
97
106
  return _SecretReference(
98
107
  arn=arn,
99
108
  json_key=json_key,
100
- version_stage=version_stage,
101
109
  version_id=version_id,
102
110
  )
103
111
 
@@ -105,10 +113,10 @@ def _parse_reference(*, env_var: str, value: str) -> _SecretReference:
105
113
  def _fetch_secret(*, client: Any, ref: _SecretReference) -> dict[str, str]:
106
114
  """Fetch a secret string from Secrets Manager, using the module cache.
107
115
 
108
- The cache key is ``(arn, version_stage, version_id)`` so that references
116
+ The cache key is ``(arn, version_id)`` so that references
109
117
  to different versions of the same secret are fetched independently.
110
118
  """
111
- cache_key = (ref.arn, ref.version_stage, ref.version_id)
119
+ cache_key = (ref.arn, ref.version_id)
112
120
 
113
121
  if cache_key in _secret_cache:
114
122
  return _secret_cache[cache_key]
@@ -117,7 +125,6 @@ def _fetch_secret(*, client: Any, ref: _SecretReference) -> dict[str, str]:
117
125
 
118
126
  response = client.get_secret_value(
119
127
  SecretId=ref.arn,
120
- VersionStage=ref.version_stage,
121
128
  VersionId=ref.version_id,
122
129
  )
123
130
 
@@ -171,10 +178,8 @@ def resolve_secrets_into_env() -> None:
171
178
  "Use one or the other, not both."
172
179
  )
173
180
 
174
- # Unique (arn, version_stage, version_id) combinations to minimise API calls.
175
- unique_cache_keys = {
176
- (ref.arn, ref.version_stage, ref.version_id) for ref in references.values()
177
- }
181
+ # Unique (arn, version_id) combinations to minimise API calls.
182
+ unique_cache_keys = {(ref.arn, ref.version_id) for ref in references.values()}
178
183
  uncached = unique_cache_keys - _secret_cache.keys()
179
184
  logger.info(
180
185
  f"Resolving {len(references)} secret reference(s) from"
@@ -7,7 +7,7 @@ packages = ["lambda_tasks"]
7
7
 
8
8
  [project]
9
9
  name = "django-lambda-tasks"
10
- version = "0.4.11"
10
+ version = "0.5.0"
11
11
  description = "Run async tasks in a lambda function"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.10"
@@ -19,8 +19,8 @@ from lambda_tasks.environment_loader import (
19
19
  resolve_environment,
20
20
  )
21
21
 
22
- # A valid 9-segment reference used throughout tests
23
- _VALID_REF = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-env:AWSCURRENT:v1"
22
+ # A valid 8-segment reference used throughout tests
23
+ _VALID_REF = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-env:v1"
24
24
  _VALID_ARN = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-env"
25
25
 
26
26
 
@@ -83,36 +83,28 @@ class TestParseReference:
83
83
  """Unit tests for _parse_reference covering format validation."""
84
84
 
85
85
  def test_valid_reference_returns_named_tuple(self) -> None:
86
- """A valid 9-segment reference is parsed correctly."""
86
+ """A valid 8-segment reference is parsed correctly."""
87
87
  ref = _parse_reference(value=_VALID_REF)
88
88
  assert ref.arn == _VALID_ARN
89
- assert ref.version_stage == "AWSCURRENT"
90
89
  assert ref.version_id == "v1"
91
90
 
92
91
  def test_too_few_segments_raises_value_error(self) -> None:
93
- """Fewer than 9 segments raises ValueError."""
94
- with pytest.raises(ValueError, match="9 colon-separated segments"):
92
+ """Fewer than 8 segments raises ValueError."""
93
+ with pytest.raises(ValueError, match="8 colon-separated segments"):
95
94
  _parse_reference(value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env")
96
95
 
97
96
  def test_too_many_segments_raises_value_error(self) -> None:
98
- """More than 9 segments raises ValueError."""
99
- with pytest.raises(ValueError, match="9 colon-separated segments"):
97
+ """More than 8 segments raises ValueError."""
98
+ with pytest.raises(ValueError, match="8 colon-separated segments"):
100
99
  _parse_reference(
101
- value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env:AWSCURRENT:v1:extra"
102
- )
103
-
104
- def test_empty_version_stage_raises_value_error(self) -> None:
105
- """Empty version-stage raises ValueError."""
106
- with pytest.raises(ValueError, match="version-stage"):
107
- _parse_reference(
108
- value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env::v1"
100
+ value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env:v1:extra"
109
101
  )
110
102
 
111
103
  def test_empty_version_id_raises_value_error(self) -> None:
112
104
  """Empty version-id raises ValueError."""
113
105
  with pytest.raises(ValueError, match="version-id"):
114
106
  _parse_reference(
115
- value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env:AWSCURRENT:"
107
+ value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env:"
116
108
  )
117
109
 
118
110
 
@@ -344,7 +336,7 @@ class TestResolveHappyPath:
344
336
  mock_secretsmanager_client: MagicMock,
345
337
  monkeypatch,
346
338
  ) -> None:
347
- """_fetch_secret calls get_secret_value with ARN, VersionStage, and VersionId."""
339
+ """_fetch_secret calls get_secret_value with ARN and VersionId only."""
348
340
  set_env_arn(value=_VALID_REF)
349
341
  mock_secretsmanager_client.get_secret_value.return_value = {
350
342
  "SecretString": json.dumps({"KEY": "value"})
@@ -354,7 +346,6 @@ class TestResolveHappyPath:
354
346
 
355
347
  mock_secretsmanager_client.get_secret_value.assert_called_once_with(
356
348
  SecretId=_VALID_ARN,
357
- VersionStage="AWSCURRENT",
358
349
  VersionId="v1",
359
350
  )
360
351
 
@@ -369,7 +360,7 @@ class TestResolveHappyPath:
369
360
  """An env var with wrong segment count raises ValueError before any API call."""
370
361
  set_env_arn(value="arn:aws:secretsmanager:eu-west-1:123:secret:my-env")
371
362
 
372
- with pytest.raises(ValueError, match="9 colon-separated segments"):
363
+ with pytest.raises(ValueError, match="8 colon-separated segments"):
373
364
  resolve_environment()
374
365
 
375
366
  mock_secretsmanager_client.get_secret_value.assert_not_called()
@@ -23,9 +23,9 @@ ARN = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-secret"
23
23
  ARN2 = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:other-secret"
24
24
 
25
25
  # A valid full reference: <arn>:<json-key>:<version-stage>:<version-id>
26
- # ARN = 7 segments, + 3 suffix = 10 total. version-id may be empty string.
27
- VALID_REF = f"{ARN}:MY_KEY:AWSCURRENT:AWSCURRENT"
28
- VALID_REF2 = f"{ARN2}:OTHER_KEY:AWSCURRENT:AWSCURRENT"
26
+ # ARN = 7 segments, + 3 suffix = 10 total. version-stage must be empty.
27
+ VALID_REF = f"{ARN}:MY_KEY::v1"
28
+ VALID_REF2 = f"{ARN2}:OTHER_KEY::v1"
29
29
 
30
30
 
31
31
  @pytest.fixture(autouse=True)
@@ -48,15 +48,13 @@ class TestParseReferenceValid:
48
48
  ref = _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=VALID_REF)
49
49
  assert ref.arn == ARN
50
50
  assert ref.json_key == "MY_KEY"
51
- assert ref.version_stage == "AWSCURRENT"
52
- assert ref.version_id == "AWSCURRENT"
51
+ assert ref.version_id == "v1"
53
52
 
54
53
  def test_version_id_populated(self):
55
- value = f"{ARN}:DJANGO_ADMIN_URL:AWSCURRENT:abc123"
54
+ value = f"{ARN}:DJANGO_ADMIN_URL::abc123"
56
55
  ref = _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
57
56
  assert ref.arn == ARN
58
57
  assert ref.json_key == "DJANGO_ADMIN_URL"
59
- assert ref.version_stage == "AWSCURRENT"
60
58
  assert ref.version_id == "abc123"
61
59
 
62
60
 
@@ -72,17 +70,17 @@ class TestParseReferenceInvalid:
72
70
 
73
71
  def test_empty_json_key_rejected(self):
74
72
  # 10 segments but key is empty
75
- value = f"{ARN}::AWSCURRENT:abc123"
73
+ value = f"{ARN}:::abc123"
76
74
  with pytest.raises(ValueError, match="missing the json-key"):
77
75
  _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
78
76
 
79
- def test_empty_version_stage_rejected(self):
80
- value = f"{ARN}:MY_KEY::abc123"
81
- with pytest.raises(ValueError, match="missing the version-stage"):
77
+ def test_non_empty_version_stage_rejected(self):
78
+ value = f"{ARN}:MY_KEY:AWSCURRENT:abc123"
79
+ with pytest.raises(ValueError, match="version-stage must be empty"):
82
80
  _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
83
81
 
84
82
  def test_empty_version_id_rejected(self):
85
- value = f"{ARN}:MY_KEY:AWSCURRENT:"
83
+ value = f"{ARN}:MY_KEY::"
86
84
  with pytest.raises(ValueError, match="missing the version-id"):
87
85
  _parse_reference(env_var="LAMBDA_TASKS_SECRET_X", value=value)
88
86
 
@@ -140,7 +138,7 @@ class TestMalformedReference:
140
138
  mock_boto3.client.assert_not_called()
141
139
 
142
140
  def test_empty_key_raises_before_any_boto3_call(self, monkeypatch):
143
- monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", f"{ARN}::AWSCURRENT:abc123")
141
+ monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", f"{ARN}:::abc123")
144
142
  with patch("lambda_tasks.secret_loader.boto3") as mock_boto3:
145
143
  with pytest.raises(ValueError, match="missing the json-key"):
146
144
  resolve_secrets_into_env()
@@ -239,8 +237,8 @@ class TestResolution:
239
237
 
240
238
  class TestBatching:
241
239
  def test_single_api_call_for_same_arn(self, monkeypatch):
242
- ref_a = f"{ARN}:KEY_A:AWSCURRENT:AWSCURRENT"
243
- ref_b = f"{ARN}:KEY_B:AWSCURRENT:AWSCURRENT"
240
+ ref_a = f"{ARN}:KEY_A::v1"
241
+ ref_b = f"{ARN}:KEY_B::v1"
244
242
  monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_A", ref_a)
245
243
  monkeypatch.setenv("LAMBDA_TASKS_SECRET_VAR_B", ref_b)
246
244
  monkeypatch.delenv("VAR_A", raising=False)
@@ -256,8 +254,7 @@ class TestBatching:
256
254
 
257
255
  client.get_secret_value.assert_called_once_with(
258
256
  SecretId=ARN,
259
- VersionStage="AWSCURRENT",
260
- VersionId="AWSCURRENT",
257
+ VersionId="v1",
261
258
  )
262
259
  assert os.environ["VAR_A"] == "val-a"
263
260
  assert os.environ["VAR_B"] == "val-b"
@@ -315,9 +312,7 @@ class TestCaching:
315
312
  mock_boto3.client.assert_not_called()
316
313
 
317
314
  def test_no_boto3_client_created_when_all_cached(self, monkeypatch):
318
- secret_loader._secret_cache[(ARN, "AWSCURRENT", "AWSCURRENT")] = {
319
- "MY_KEY": "pre-cached"
320
- }
315
+ secret_loader._secret_cache[(ARN, "v1")] = {"MY_KEY": "pre-cached"}
321
316
  monkeypatch.setenv("LAMBDA_TASKS_SECRET_MY_VAR", VALID_REF)
322
317
  monkeypatch.delenv("MY_VAR", raising=False)
323
318