django-lambda-tasks 0.8.0__tar.gz → 0.9.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 (113) hide show
  1. django_lambda_tasks-0.9.0/.github/dependabot.yml +19 -0
  2. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.github/workflows/ci.yml +6 -6
  3. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.github/workflows/release.yml +6 -6
  4. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/steering/product.md +20 -0
  5. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/steering/structure.md +3 -1
  6. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/steering/tech.md +13 -0
  7. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.pre-commit-config.yaml +5 -5
  8. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/PKG-INFO +9 -2
  9. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/handler.py +18 -2
  10. django_lambda_tasks-0.9.0/lambda_tasks/migrations/0004_remove_taskrecord_taskrecord_kwargs_trgm_and_more.py +48 -0
  11. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/models.py +10 -2
  12. django_lambda_tasks-0.9.0/lambda_tasks/tracing.py +255 -0
  13. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/pyproject.toml +13 -2
  14. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_admin.py +27 -0
  15. django_lambda_tasks-0.9.0/tests/test_batch_worker.py +138 -0
  16. django_lambda_tasks-0.8.0/tests/test_batch_worker.py +0 -77
  17. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.gitignore +0 -0
  18. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  19. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/async-local-execution/design.md +0 -0
  20. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
  21. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
  22. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  23. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  24. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  25. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  26. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  27. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  28. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  29. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  30. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  31. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  32. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  33. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  34. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  35. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  36. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  37. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  38. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
  39. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/retry-delay/design.md +0 -0
  40. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/retry-delay/requirements.md +0 -0
  41. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/retry-delay/tasks.md +0 -0
  42. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  43. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
  44. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  45. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  46. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  47. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  48. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  49. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  50. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
  51. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/singleton-task/design.md +0 -0
  52. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/singleton-task/requirements.md +0 -0
  53. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/singleton-task/tasks.md +0 -0
  54. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  55. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  56. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  57. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  58. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/task-retry/.config.kiro +0 -0
  59. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/task-retry/design.md +0 -0
  60. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/task-retry/requirements.md +0 -0
  61. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.kiro/specs/task-retry/tasks.md +0 -0
  62. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/.vscode/settings.json +0 -0
  63. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/README.md +0 -0
  64. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/docker-compose.yml +0 -0
  65. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/README.md +0 -0
  66. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/docker-compose.yml +0 -0
  67. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_app/__init__.py +0 -0
  68. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_app/apps.py +0 -0
  69. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_app/tasks.py +0 -0
  70. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_app/urls.py +0 -0
  71. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_app/views.py +0 -0
  72. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_project/__init__.py +0 -0
  73. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_project/settings.py +0 -0
  74. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_project/urls.py +0 -0
  75. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/example_project/wsgi.py +0 -0
  76. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/example/manage.py +0 -0
  77. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/__init__.py +0 -0
  78. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/admin.py +0 -0
  79. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/apps.py +0 -0
  80. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/checks.py +0 -0
  81. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/decorators.py +0 -0
  82. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/environment_loader.py +0 -0
  83. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/local_executor.py +0 -0
  84. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/logging.py +0 -0
  85. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/migrations/0001_initial.py +0 -0
  86. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  87. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/migrations/0003_taskrecord_taskrecord_kwargs_trgm.py +0 -0
  88. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/migrations/__init__.py +0 -0
  89. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/secret_loader.py +0 -0
  90. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/settings.py +0 -0
  91. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/tasks.py +0 -0
  92. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/lambda_tasks/timeouts.py +0 -0
  93. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/conftest.py +0 -0
  94. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/settings.py +0 -0
  95. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_batch.py +0 -0
  96. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_checks.py +0 -0
  97. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_decorator.py +0 -0
  98. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_decorators.py +0 -0
  99. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_deferred_enqueue.py +0 -0
  100. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_environment_loader.py +0 -0
  101. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_handler.py +0 -0
  102. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_kwargs_only.py +0 -0
  103. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_local_executor.py +0 -0
  104. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_logging.py +0 -0
  105. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_memory_limit.py +0 -0
  106. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_models.py +0 -0
  107. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_noop_execution.py +0 -0
  108. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_secret_loader.py +0 -0
  109. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_serializer.py +0 -0
  110. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_settings.py +0 -0
  111. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_tasks.py +0 -0
  112. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_timeout_validation.py +0 -0
  113. {django_lambda_tasks-0.8.0 → django_lambda_tasks-0.9.0}/tests/test_timeouts.py +0 -0
@@ -0,0 +1,19 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "monthly"
7
+ groups:
8
+ actions:
9
+ patterns:
10
+ - "*"
11
+
12
+ - package-ecosystem: "pre-commit"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "monthly"
16
+ groups:
17
+ pre-commit:
18
+ patterns:
19
+ - "*"
@@ -6,7 +6,7 @@ on:
6
6
  workflow_call:
7
7
 
8
8
  env:
9
- MINIMUM_PYTHON_VERSION: '3.10'
9
+ MINIMUM_PYTHON_VERSION: '3.11'
10
10
 
11
11
  concurrency:
12
12
  group: ${{ github.head_ref || github.run_id }}
@@ -17,9 +17,9 @@ jobs:
17
17
  precommit:
18
18
  runs-on: ubuntu-latest
19
19
  steps:
20
- - uses: actions/checkout@v4
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
21
  - name: Install uv and set the python version
22
- uses: astral-sh/setup-uv@v6
22
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
23
23
  with:
24
24
  python-version: ${{ env.MINIMUM_PYTHON_VERSION }}
25
25
  - name: Run static code inspections
@@ -29,7 +29,7 @@ jobs:
29
29
  runs-on: ubuntu-latest
30
30
  strategy:
31
31
  matrix:
32
- python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
32
+ python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
33
33
  services:
34
34
  postgres:
35
35
  image: postgres:17
@@ -45,9 +45,9 @@ jobs:
45
45
  --health-timeout 5s
46
46
  --health-retries 5
47
47
  steps:
48
- - uses: actions/checkout@v4
48
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
49
49
  - name: Install uv and set the python version
50
- uses: astral-sh/setup-uv@v6
50
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
51
51
  with:
52
52
  python-version: ${{ matrix.python-version }}
53
53
  - name: Run tests
@@ -6,7 +6,7 @@ on:
6
6
  - published
7
7
 
8
8
  env:
9
- MINIMUM_PYTHON_VERSION: '3.10'
9
+ MINIMUM_PYTHON_VERSION: '3.11'
10
10
 
11
11
  jobs:
12
12
  ci:
@@ -18,15 +18,15 @@ jobs:
18
18
  permissions:
19
19
  contents: read
20
20
  steps:
21
- - uses: actions/checkout@v4
21
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
22
  - name: Install Python ${{ env.MINIMUM_PYTHON_VERSION }}
23
- uses: astral-sh/setup-uv@v6
23
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
24
24
  with:
25
25
  python-version: ${{ env.MINIMUM_PYTHON_VERSION }}
26
26
  - name: Build the package
27
27
  run: uv build
28
28
  - name: 'Upload Artifact'
29
- uses: actions/upload-artifact@v4
29
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
30
30
  with:
31
31
  name: python-dist
32
32
  path: dist/
@@ -43,9 +43,9 @@ jobs:
43
43
  id-token: write
44
44
  steps:
45
45
  - name: Download build artifacts
46
- uses: actions/download-artifact@v4
46
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
47
47
  with:
48
48
  name: python-dist
49
49
  path: dist
50
50
  - name: Publish package distributions
51
- uses: pypa/gh-action-pypi-publish@release/v1
51
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
@@ -31,6 +31,7 @@ Key modules:
31
31
  - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (SQS schema + execution), `SQSLambdaTask` (routing + SQS publish, Batch submit, or local pool submit)
32
32
  - `local_executor.py` — `ProcessPoolExecutor`-based async local execution for development
33
33
  - `handler.py` — AWS Lambda entry point + AWS Batch container entry point (`main()`)
34
+ - `tracing.py` — optional OpenTelemetry tracing (import-time setup, direct X-Ray OTLP export, SigV4-signed)
34
35
  - `logging.py` — `task_logger` for invocation-scoped log output
35
36
  - `settings.py` — lazy `LambdaTasksSettings` reading from Django settings, queue type helpers
36
37
 
@@ -158,6 +159,7 @@ class SQSLambdaTaskMessage(BaseModel):
158
159
  - Reads `LAMBDA_TASKS_MESSAGE` env var (the serialized task JSON, set by `submit_batch_job` via container overrides)
159
160
  - Uses `AWS_BATCH_JOB_ID` as the `message_id` (falls back to UUID4 if not set)
160
161
  - Constructs a synthetic SQS event and delegates to `handler()` — reuses the full cold-start and execution path
162
+ - Before returning, force-flushes and shuts down the global `TracerProvider` (if OTEL tracing is configured) so short-lived Batch jobs do not drop buffered spans
161
163
  - Returns exit code 0 on success, 1 on failure
162
164
 
163
165
  ## Environment Loader
@@ -297,6 +299,24 @@ Relying on `atexit` alone loses this race in applications with a heavy shutdown
297
299
 
298
300
  The checks are registered via `AppConfig.ready()` and tagged with `deploy=True` so they only fire during `--deploy` checks.
299
301
 
302
+ ## OpenTelemetry Tracing (optional)
303
+
304
+ `lambda_tasks/tracing.py` adds OpenTelemetry tracing to the Lambda and Batch execution paths without the managed ADOT Lambda layer — the OTEL packages installed in the application image are the single source of instrumentation (reproducible-build friendly).
305
+
306
+ Setup lives in the handler module, **not** Django settings. `AwsLambdaInstrumentor` must wrap the handler named by `_HANDLER`/`ORIG_HANDLER` **before** the runtime invokes it, which is earlier than the cold-start `django.setup()` inside `handler()`. So `lambda_tasks.handler` calls `configure_tracing()` and `instrument_lambda_handler()` at **import time**, and the setup is driven purely by standard OTEL environment variables (settings are not configured yet at import time).
307
+
308
+ Behaviour:
309
+ - Gated on `OTEL_EXPORTER_OTLP_ENDPOINT` (or the per-signal `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`). Unset → everything is a no-op (local dev, tests, un-opted-in deployments).
310
+ - Builds a `TracerProvider` with an OTLP/HTTP span exporter via a `BatchSpanProcessor`. Spans go **directly** to the AWS X-Ray OTLP traces endpoint (`https://xray.<region>.amazonaws.com/v1/traces`) — no collector, no daemon, no Lambda layer.
311
+ - The exporter's `requests` session SigV4-signs each export with the ambient AWS credentials (task / execution role) via `botocore` — no API key to manage. Unsigned when no credentials are resolvable.
312
+ - Uses the AWS X-Ray id generator (`AwsXRayIdGenerator`) so the X-Ray backend accepts the trace IDs, and the X-Ray propagator (`AwsXRayLambdaPropagator` on Lambda, else `AwsXRayPropagator`). The X-Ray packages (`opentelemetry-sdk-extension-aws`, `opentelemetry-propagator-aws-xray`) are part of the `otel` extra.
313
+ - The OpenTelemetry / `requests` imports are **deferred into the functions** (module scope imports only `os`), so the OTEL packages remain a genuinely optional extra: importing `lambda_tasks.handler` with them uninstalled is a no-op, not an `ImportError`. The disabled path (endpoint unset) never reaches any OTEL import.
314
+ - Lambda path: also calls `AwsLambdaInstrumentor().instrument()` (only when `AWS_LAMBDA_FUNCTION_NAME` is set). Batch path (`main()`): the instrumentor is skipped (no handler to wrap); the provider alone is configured.
315
+ - Flush on shutdown: `main()` calls `TracerProvider.force_flush()` / `shutdown()` before the one-shot process exits so short-lived Batch jobs do not drop buffered spans. The Lambda path needs no manual flush — `AwsLambdaInstrumentor` force-flushes per invocation.
316
+ - Best-effort and observable: tracing must never take down the worker. `configure_tracing()` and `instrument_lambda_handler()` catch setup failures (missing OTEL packages, provider build errors) and log a warning instead of raising — important because they run at the handler module's import time. The `lambda_tasks.tracing` logger also surfaces otherwise-silent conditions at `INFO`/`WARNING`: tracing configured / already configured / disabled, handler instrumented or skipped (not on Lambda), no resolvable AWS region, and no resolvable AWS credentials (which would make exports unsigned and rejected by X-Ray).
317
+
318
+ `configure_tracing()` returns the installed provider (or `None` when disabled or already configured — it is idempotent). Install the OTEL packages via the `otel` extra; the consuming application sets `OTEL_EXPORTER_OTLP_ENDPOINT` and declares the X-Ray packages it wants.
319
+
300
320
  ## Logging
301
321
 
302
322
  Import `task_logger` to emit log records that are automatically prefixed with the active `message_id`:
@@ -23,6 +23,7 @@ django-lambda-tasks/
23
23
  │ ├── local_executor.py # ProcessPoolExecutor for async local task execution
24
24
  │ ├── tasks.py # Built-in tasks (cleanup_task_records, submit_batch_job)
25
25
  │ ├── timeouts.py # TimeoutContext implementation
26
+ │ ├── tracing.py # Optional OpenTelemetry tracing (import-time setup, X-Ray OTLP export)
26
27
  │ └── migrations/ # Django migrations for TaskRecord
27
28
  ├── tests/ # pytest test suite
28
29
  │ ├── conftest.py
@@ -43,7 +44,8 @@ django-lambda-tasks/
43
44
  - `decorators.py` — defines `LambdaTaskWrapper` and `@lambda_task` decorator factory; enforces kwargs-only at decoration time; validates timeouts (> 0, soft < hard) at decoration time; upper-bound timeout validation deferred to execution time based on queue type
44
45
  - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic), `SQSLambdaTask` (Pydantic, holds message + routing; `_execute()` inspects queue config to publish to SQS, submit to Batch via `submit_batch_job`, execute eagerly, or submit to the local process pool; `execute_on_commit()` registers `_execute` with `transaction.on_commit`)
45
46
  - `local_executor.py` — `ProcessPoolExecutor`-based async local execution; `get_pool()` lazily creates a module-level pool; `submit_task()` fire-and-forget submission; `_execute_in_worker()` deserializes and runs the task in a child process; `_pool_initializer()` calls `django.setup()` per worker (and sets `SIGINT` to `SIG_IGN` so workers ignore Ctrl+C); `_install_shutdown_handlers()` installs main-thread `SIGINT`/`SIGTERM` handlers that release the pool before chaining to the previous handler
46
- - `handler.py` — Lambda entry point (`handler(event, context)`); cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; calls `db.close_old_connections()` before each SQS record to handle stale connections on warm starts; processes SQS records independently; returns `batchItemFailures`. Also provides `main()` as the AWS Batch container entry point (`python -m lambda_tasks.handler`) — reads `LAMBDA_TASKS_MESSAGE` env var, constructs a synthetic SQS event, and delegates to `handler()`
47
+ - `handler.py` — Lambda entry point (`handler(event, context)`); cold-start init (memory limit → temporary log handler → `resolve_environment()` → `resolve_secrets_into_env()` → handler removed → conditional `django.setup()`) runs inside the handler on first invocation, guarded by `_cold_start_done` sentinel; calls `db.close_old_connections()` before each SQS record to handle stale connections on warm starts; processes SQS records independently; returns `batchItemFailures`. At **import time** (before any invocation) it calls `configure_tracing()` and `instrument_lambda_handler()` from `tracing.py` so the Lambda instrumentor can wrap the handler before the runtime invokes it. Also provides `main()` as the AWS Batch container entry point (`python -m lambda_tasks.handler`) — reads `LAMBDA_TASKS_MESSAGE` env var, constructs a synthetic SQS event, delegates to `handler()`, then force-flushes and shuts down the global `TracerProvider` (if any) before the one-shot process exits
48
+ - `tracing.py` — optional OpenTelemetry tracing, driven purely by standard OTEL env vars (no Django settings) so the package stays generic; no-op unless `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`) is set. All OpenTelemetry / `requests` imports are **deferred into the functions** (only `os` is imported at module scope) so the OTEL packages are a genuinely optional extra — importing `lambda_tasks.handler` without them is a no-op, not an `ImportError`. `configure_tracing()` installs a global `TracerProvider` using the X-Ray id generator (`AwsXRayIdGenerator`) with an OTLP/HTTP exporter whose `requests` session SigV4-signs each export to the AWS X-Ray traces endpoint (unsigned when no credentials resolve), and sets the X-Ray propagator (`AwsXRayLambdaPropagator` on Lambda, else `AwsXRayPropagator`). `instrument_lambda_handler()` calls `AwsLambdaInstrumentor().instrument()` only when tracing is enabled and `AWS_LAMBDA_FUNCTION_NAME` is set (Lambda path); skipped on the Batch path. `flush_tracing()` force-flushes and shuts down the provider (gated; used by the Batch entry point)
47
49
  - `environment_loader.py` — loads env vars from a Secrets Manager secret at cold start; validates flat JSON format; idempotent via `_loaded` sentinel
48
50
  - `secret_loader.py` — resolves `LAMBDA_TASKS_SECRET_*` env vars from Secrets Manager before Django starts; validates format, detects conflicts, batches API calls; idempotent via `_loaded` sentinel
49
51
  - `logging.py` — `task_logger` singleton; `message_id` set/cleared around each task execution
@@ -17,6 +17,19 @@ inclusion: always
17
17
  | `boto3` | `>= 1.42.69` | AWS SDK — SQS publishing, Lambda integration |
18
18
  | `pydantic` | `>= 2.12.5` | Task argument serialization via `SQSLambdaTaskMessage` model |
19
19
 
20
+ ## Optional Dependencies
21
+
22
+ Installed via the `otel` extra (`pip install django-lambda-tasks[otel]`). Enables OpenTelemetry tracing in `lambda_tasks/tracing.py`; the library stays generic without them. SigV4 signing reuses `botocore` (already a boto3 transitive dependency) — no extra signing package.
23
+
24
+ | Package | Purpose |
25
+ |---|---|
26
+ | `opentelemetry-sdk` | `TracerProvider`, `BatchSpanProcessor` |
27
+ | `opentelemetry-exporter-otlp-proto-http` | OTLP/HTTP span exporter (no gRPC, no meta-package) |
28
+ | `opentelemetry-instrumentation-aws-lambda` | Wraps the Lambda handler (`AwsLambdaInstrumentor`) |
29
+ | `opentelemetry-sdk-extension-aws` | X-Ray id generator (`AwsXRayIdGenerator`) |
30
+ | `opentelemetry-propagator-aws-xray` | X-Ray trace propagator |
31
+ | `requests` | OTLP/HTTP transport session (SigV4-signed via `botocore`) |
32
+
20
33
  ## Dev Dependencies
21
34
 
22
35
  | Package | Version | Purpose |
@@ -1,6 +1,6 @@
1
1
  repos:
2
2
  - repo: https://github.com/adamchainz/django-upgrade
3
- rev: 1.30.0
3
+ rev: 1.33.0
4
4
  hooks:
5
5
  - id: django-upgrade
6
6
  args: [--target-version, "5.2"]
@@ -13,17 +13,17 @@ repos:
13
13
  - id: mixed-line-ending
14
14
  - id: trailing-whitespace
15
15
  - repo: https://github.com/pycqa/isort
16
- rev: 8.0.1
16
+ rev: 9.0.2
17
17
  hooks:
18
18
  - id: isort
19
19
  - repo: https://github.com/asottile/pyupgrade
20
- rev: v3.21.2
20
+ rev: v3.22.0
21
21
  hooks:
22
22
  - id: pyupgrade
23
23
  language: python
24
- args: [--py310-plus]
24
+ args: [--py311-plus]
25
25
  - repo: https://github.com/ambv/black
26
- rev: 26.3.1
26
+ rev: 26.10.1
27
27
  hooks:
28
28
  - id: black
29
29
  language: python
@@ -1,13 +1,20 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: django-lambda-tasks
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Run async tasks in a lambda function
5
- Requires-Python: >=3.10
5
+ Requires-Python: >=3.11
6
6
  Requires-Dist: awslambdaric
7
7
  Requires-Dist: boto3
8
8
  Requires-Dist: django
9
9
  Requires-Dist: pydantic
10
10
  Requires-Dist: redis
11
+ Provides-Extra: otel
12
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http; extra == 'otel'
13
+ Requires-Dist: opentelemetry-instrumentation-aws-lambda; extra == 'otel'
14
+ Requires-Dist: opentelemetry-propagator-aws-xray; extra == 'otel'
15
+ Requires-Dist: opentelemetry-sdk; extra == 'otel'
16
+ Requires-Dist: opentelemetry-sdk-extension-aws; extra == 'otel'
17
+ Requires-Dist: requests; extra == 'otel'
11
18
  Description-Content-Type: text/markdown
12
19
 
13
20
  # Django Lambda Tasks
@@ -26,9 +26,21 @@ from django.apps import apps
26
26
 
27
27
  from lambda_tasks.environment_loader import resolve_environment
28
28
  from lambda_tasks.secret_loader import resolve_secrets_into_env
29
+ from lambda_tasks.tracing import (
30
+ configure_tracing,
31
+ flush_tracing,
32
+ instrument_lambda_handler,
33
+ )
29
34
 
30
35
  logger = logging.getLogger(__name__)
31
36
 
37
+ # Initialise tracing at import time so the Lambda instrumentor can wrap the
38
+ # handler before the first invocation (which is earlier than the cold-start
39
+ # django.setup() inside handler()). No-op unless OTEL_EXPORTER_OTLP_ENDPOINT
40
+ # is set.
41
+ configure_tracing()
42
+ instrument_lambda_handler()
43
+
32
44
  _cold_start_done: bool = False
33
45
 
34
46
 
@@ -169,10 +181,14 @@ def main() -> int:
169
181
  context=None,
170
182
  )
171
183
 
184
+ # Flush any buffered spans before the one-shot process exits (no-op unless
185
+ # tracing is enabled — this never imports OTEL otherwise).
186
+ flush_tracing()
187
+
172
188
  if result["batchItemFailures"]:
173
189
  return 1
174
-
175
- return 0
190
+ else:
191
+ return 0
176
192
 
177
193
 
178
194
  if __name__ == "__main__":
@@ -0,0 +1,48 @@
1
+ # Generated by Django 6.0.3 on 2026-08-18 09:47
2
+
3
+ import django.contrib.postgres.indexes
4
+ import django.db.models.functions.comparison
5
+ import django.db.models.functions.text
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ("lambda_tasks", "0003_taskrecord_taskrecord_kwargs_trgm"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.RemoveIndex(
17
+ model_name="taskrecord",
18
+ name="taskrecord_kwargs_trgm",
19
+ ),
20
+ migrations.AddIndex(
21
+ model_name="taskrecord",
22
+ index=django.contrib.postgres.indexes.GinIndex(
23
+ django.contrib.postgres.indexes.OpClass(
24
+ django.db.models.functions.text.Upper(
25
+ django.db.models.functions.comparison.Cast(
26
+ "id", output_field=models.TextField()
27
+ )
28
+ ),
29
+ name="gin_trgm_ops",
30
+ ),
31
+ name="taskrecord_id_trgm",
32
+ ),
33
+ ),
34
+ migrations.AddIndex(
35
+ model_name="taskrecord",
36
+ index=django.contrib.postgres.indexes.GinIndex(
37
+ django.contrib.postgres.indexes.OpClass(
38
+ django.db.models.functions.text.Upper(
39
+ django.db.models.functions.comparison.Cast(
40
+ "kwargs", output_field=models.TextField()
41
+ )
42
+ ),
43
+ name="gin_trgm_ops",
44
+ ),
45
+ name="taskrecord_kwargs_trgm",
46
+ ),
47
+ ),
48
+ ]
@@ -13,7 +13,7 @@ from django.core.cache import caches
13
13
  from django.core.exceptions import ImproperlyConfigured
14
14
  from django.db import models, transaction
15
15
  from django.db.models import Q
16
- from django.db.models.functions import Cast
16
+ from django.db.models.functions import Cast, Upper
17
17
  from django.utils.module_loading import import_string
18
18
  from django.utils.timezone import now
19
19
  from pydantic import BaseModel, ConfigDict, Field
@@ -78,7 +78,15 @@ class TaskRecord(models.Model):
78
78
  models.Index(fields=["-start_time"]),
79
79
  GinIndex(
80
80
  OpClass(
81
- Cast("kwargs", output_field=models.TextField()), name="gin_trgm_ops"
81
+ Upper(Cast("id", output_field=models.TextField())),
82
+ name="gin_trgm_ops",
83
+ ),
84
+ name="taskrecord_id_trgm",
85
+ ),
86
+ GinIndex(
87
+ OpClass(
88
+ Upper(Cast("kwargs", output_field=models.TextField())),
89
+ name="gin_trgm_ops",
82
90
  ),
83
91
  name="taskrecord_kwargs_trgm",
84
92
  ),
@@ -0,0 +1,255 @@
1
+ """Optional OpenTelemetry tracing for the Lambda and Batch execution paths.
2
+
3
+ Initialised at import time of ``lambda_tasks.handler`` (before the Lambda
4
+ runtime invokes the handler, which is earlier than ``django.setup()``). Driven
5
+ entirely by standard OTEL environment variables so this package stays generic:
6
+ the consuming application declares the OTEL dependencies and sets
7
+ ``OTEL_EXPORTER_OTLP_ENDPOINT`` to the regional AWS X-Ray OTLP traces endpoint
8
+ (``https://xray.<region>.amazonaws.com/v1/traces``).
9
+
10
+ Spans are exported directly to the X-Ray endpoint — no collector, no daemon,
11
+ no Lambda layer. Requests are SigV4-signed with the ambient AWS credentials
12
+ (the task / execution role), so there is no API key to manage. When no
13
+ credentials are resolvable (local dev / tests) the request is left unsigned.
14
+
15
+ The OpenTelemetry and ``requests`` imports are deferred into the functions
16
+ below so they are only reached when tracing is enabled (a non-empty
17
+ ``OTEL_EXPORTER_OTLP_ENDPOINT`` / ``OTEL_EXPORTER_OTLP_TRACES_ENDPOINT``).
18
+ This keeps the OTEL packages a genuinely optional extra: importing
19
+ ``lambda_tasks.handler`` without them installed is a no-op, not an
20
+ ``ImportError``.
21
+ """
22
+
23
+ import logging
24
+ import os
25
+ from typing import TYPE_CHECKING
26
+
27
+ if TYPE_CHECKING:
28
+ import requests
29
+ from opentelemetry.sdk.trace import TracerProvider
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ _XRAY_SIGV4_SERVICE_NAME = "xray"
34
+
35
+
36
+ def tracing_is_enabled() -> bool:
37
+ """Whether an OTLP traces endpoint has been configured.
38
+
39
+ Either the base ``OTEL_EXPORTER_OTLP_ENDPOINT`` or the per-signal
40
+ ``OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`` turns it on.
41
+ """
42
+ return bool(
43
+ os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "").strip()
44
+ or os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "").strip()
45
+ )
46
+
47
+
48
+ def _running_on_lambda() -> bool:
49
+ return bool(os.environ.get("AWS_LAMBDA_FUNCTION_NAME"))
50
+
51
+
52
+ def _build_signing_session() -> "requests.Session":
53
+ import requests
54
+ import requests.auth
55
+ from botocore.auth import SigV4Auth
56
+ from botocore.awsrequest import AWSRequest
57
+ from botocore.session import Session as BotocoreSession
58
+
59
+ class _SigV4Auth(requests.auth.AuthBase):
60
+ """SigV4-signs OTLP exports to the X-Ray endpoint."""
61
+
62
+ def __init__(
63
+ self, *, service_name: str, region: str, session: BotocoreSession
64
+ ) -> None:
65
+ self._service_name = service_name
66
+ self._region = region
67
+ self._session = session
68
+
69
+ def __call__(self, r: "requests.PreparedRequest") -> "requests.PreparedRequest":
70
+ credentials = self._session.get_credentials()
71
+
72
+ if credentials is None:
73
+ logger.warning(
74
+ "No AWS credentials resolvable; sending OTLP span export to "
75
+ "%s unsigned. The X-Ray endpoint will reject unsigned "
76
+ "requests, so spans will be dropped.",
77
+ r.url,
78
+ )
79
+ return r
80
+
81
+ aws_request = AWSRequest(
82
+ method=r.method,
83
+ url=r.url,
84
+ data=r.body,
85
+ headers={"Content-Type": "application/x-protobuf"},
86
+ )
87
+ SigV4Auth(credentials, self._service_name, self._region).add_auth(
88
+ aws_request
89
+ )
90
+
91
+ r.headers.update(dict(aws_request.headers))
92
+
93
+ return r
94
+
95
+ region = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", ""))
96
+
97
+ if not region:
98
+ logger.warning(
99
+ "No AWS region resolvable from AWS_REGION / AWS_DEFAULT_REGION; "
100
+ "SigV4 signing of OTLP span exports may fail."
101
+ )
102
+
103
+ session = requests.Session()
104
+ session.auth = _SigV4Auth(
105
+ service_name=_XRAY_SIGV4_SERVICE_NAME,
106
+ region=region,
107
+ session=BotocoreSession(),
108
+ )
109
+
110
+ return session
111
+
112
+
113
+ def _build_tracer_provider() -> "TracerProvider":
114
+ from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
115
+ OTLPSpanExporter,
116
+ )
117
+ from opentelemetry.sdk.extension.aws.trace import AwsXRayIdGenerator
118
+ from opentelemetry.sdk.resources import Resource
119
+ from opentelemetry.sdk.trace import TracerProvider
120
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
121
+
122
+ service_name = os.environ.get(
123
+ "OTEL_SERVICE_NAME",
124
+ os.environ.get("AWS_LAMBDA_FUNCTION_NAME", "lambda-tasks"),
125
+ )
126
+
127
+ tracer_provider = TracerProvider(
128
+ resource=Resource.create(attributes={"service.name": service_name}),
129
+ id_generator=AwsXRayIdGenerator(),
130
+ )
131
+
132
+ tracer_provider.add_span_processor(
133
+ BatchSpanProcessor(
134
+ span_exporter=OTLPSpanExporter(session=_build_signing_session())
135
+ )
136
+ )
137
+ return tracer_provider
138
+
139
+
140
+ def _set_xray_propagator() -> None:
141
+ from opentelemetry import propagate
142
+ from opentelemetry.propagators.aws import (
143
+ AwsXRayLambdaPropagator,
144
+ AwsXRayPropagator,
145
+ )
146
+
147
+ propagate.set_global_textmap(
148
+ AwsXRayLambdaPropagator() if _running_on_lambda() else AwsXRayPropagator()
149
+ )
150
+
151
+
152
+ def configure_tracing() -> "TracerProvider | None":
153
+ """Install the global tracer provider. No-op unless enabled.
154
+
155
+ Returns the provider so callers (e.g. the Batch entry point) can flush it
156
+ on shutdown, or ``None`` when tracing is disabled or already configured.
157
+
158
+ Tracing is best-effort: if it is enabled but the OTEL packages are missing
159
+ or the provider cannot be built, the failure is logged and ``None`` is
160
+ returned rather than propagated — a broken observability setup must not
161
+ take down the worker (this runs at import time of the handler module).
162
+ """
163
+ if not tracing_is_enabled():
164
+ logger.debug("OTEL tracing disabled (no OTLP endpoint configured).")
165
+ return None
166
+
167
+ try:
168
+ from opentelemetry import trace
169
+ from opentelemetry.sdk.trace import TracerProvider
170
+
171
+ if isinstance(trace.get_tracer_provider(), TracerProvider):
172
+ logger.info(
173
+ "A TracerProvider is already installed; skipping configuration."
174
+ )
175
+ return None
176
+
177
+ tracer_provider = _build_tracer_provider()
178
+ trace.set_tracer_provider(tracer_provider)
179
+ _set_xray_propagator()
180
+ except ImportError:
181
+ logger.warning(
182
+ "OTEL tracing is enabled but the OpenTelemetry packages are not "
183
+ "installed; tracing is disabled. Install the 'otel' extra to "
184
+ "enable it.",
185
+ exc_info=True,
186
+ )
187
+ return None
188
+ except Exception:
189
+ logger.warning(
190
+ "Failed to configure OTEL tracing; continuing without it.",
191
+ exc_info=True,
192
+ )
193
+ return None
194
+
195
+ logger.info("OTEL tracing configured (exporting spans to the OTLP endpoint).")
196
+ return tracer_provider
197
+
198
+
199
+ def instrument_lambda_handler() -> None:
200
+ """Wrap the Lambda handler for tracing. No-op unless enabled.
201
+
202
+ Must be called at import time of the handler module, before the runtime
203
+ invokes the handler.
204
+ """
205
+ if not tracing_is_enabled():
206
+ return
207
+
208
+ if not _running_on_lambda():
209
+ logger.debug(
210
+ "Not running on Lambda (AWS_LAMBDA_FUNCTION_NAME unset); "
211
+ "skipping AwsLambdaInstrumentor."
212
+ )
213
+ return
214
+
215
+ try:
216
+ from opentelemetry.instrumentation.aws_lambda import (
217
+ AwsLambdaInstrumentor,
218
+ )
219
+
220
+ AwsLambdaInstrumentor().instrument()
221
+ except Exception:
222
+ logger.warning(
223
+ "Failed to instrument the Lambda handler for tracing; "
224
+ "continuing without handler spans.",
225
+ exc_info=True,
226
+ )
227
+ return
228
+
229
+ logger.info("AwsLambdaInstrumentor installed; Lambda handler is traced.")
230
+
231
+
232
+ def flush_tracing() -> None:
233
+ """Force-flush and shut down the global tracer provider. No-op unless enabled.
234
+
235
+ Called at the end of the one-shot Batch entry point so short-lived jobs do
236
+ not drop buffered spans. Gated on ``tracing_is_enabled()`` so the OTEL
237
+ imports are never reached when tracing is disabled (or the packages are not
238
+ installed).
239
+ """
240
+ if not tracing_is_enabled():
241
+ return
242
+
243
+ from opentelemetry import trace
244
+ from opentelemetry.sdk.trace import TracerProvider
245
+
246
+ provider = trace.get_tracer_provider()
247
+ if isinstance(provider, TracerProvider):
248
+ logger.debug("Flushing and shutting down the TracerProvider before exit.")
249
+ provider.force_flush()
250
+ provider.shutdown()
251
+ else:
252
+ logger.debug(
253
+ "Tracing is enabled but no TracerProvider is installed; "
254
+ "nothing to flush."
255
+ )
@@ -7,10 +7,10 @@ packages = ["lambda_tasks"]
7
7
 
8
8
  [project]
9
9
  name = "django-lambda-tasks"
10
- version = "0.8.0"
10
+ version = "0.9.0"
11
11
  description = "Run async tasks in a lambda function"
12
12
  readme = "README.md"
13
- requires-python = ">=3.10"
13
+ requires-python = ">=3.11"
14
14
  dependencies = [
15
15
  "boto3",
16
16
  "django",
@@ -19,6 +19,16 @@ dependencies = [
19
19
  "awslambdaric",
20
20
  ]
21
21
 
22
+ [project.optional-dependencies]
23
+ otel = [
24
+ "requests",
25
+ "opentelemetry-sdk",
26
+ "opentelemetry-exporter-otlp-proto-http",
27
+ "opentelemetry-instrumentation-aws-lambda",
28
+ "opentelemetry-sdk-extension-aws",
29
+ "opentelemetry-propagator-aws-xray",
30
+ ]
31
+
22
32
  [dependency-groups]
23
33
  dev = [
24
34
  "hypothesis",
@@ -31,6 +41,7 @@ dev = [
31
41
  "pytest-cov",
32
42
  "ty",
33
43
  "psycopg",
44
+ "django-lambda-tasks[otel]",
34
45
  ]
35
46
 
36
47
  [tool.ty.rules]