django-lambda-tasks 0.9.2__tar.gz → 0.11.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 (120) hide show
  1. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/product.md +19 -23
  2. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/structure.md +4 -4
  3. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/steering/tech.md +1 -18
  4. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/PKG-INFO +27 -13
  5. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/README.md +26 -0
  6. django_lambda_tasks-0.11.0/example/example_app/tasks.py +31 -0
  7. django_lambda_tasks-0.11.0/example/example_app/urls.py +7 -0
  8. django_lambda_tasks-0.11.0/example/example_app/views.py +17 -0
  9. django_lambda_tasks-0.11.0/example/example_project/sentry.py +50 -0
  10. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/settings.py +7 -0
  11. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/handler.py +0 -19
  12. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/models.py +138 -118
  13. django_lambda_tasks-0.11.0/lambda_tasks/sentry.py +143 -0
  14. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/tasks.py +5 -1
  15. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/pyproject.toml +2 -18
  16. django_lambda_tasks-0.11.0/tests/test_batch_worker.py +77 -0
  17. django_lambda_tasks-0.11.0/tests/test_sentry.py +448 -0
  18. django_lambda_tasks-0.9.2/example/example_app/tasks.py +0 -18
  19. django_lambda_tasks-0.9.2/example/example_app/urls.py +0 -6
  20. django_lambda_tasks-0.9.2/example/example_app/views.py +0 -8
  21. django_lambda_tasks-0.9.2/lambda_tasks/tracing.py +0 -325
  22. django_lambda_tasks-0.9.2/tests/test_batch_worker.py +0 -138
  23. django_lambda_tasks-0.9.2/tests/test_tracing.py +0 -539
  24. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/dependabot.yml +0 -0
  25. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/workflows/ci.yml +0 -0
  26. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.github/workflows/release.yml +0 -0
  27. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.gitignore +0 -0
  28. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  29. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/design.md +0 -0
  30. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/requirements.md +0 -0
  31. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/async-local-execution/tasks.md +0 -0
  32. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  33. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  34. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  35. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  36. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  37. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  38. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  39. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  40. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  41. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  42. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  43. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  44. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  45. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  46. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  47. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  48. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/.config.kiro +0 -0
  49. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/design.md +0 -0
  50. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/requirements.md +0 -0
  51. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/retry-delay/tasks.md +0 -0
  52. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  53. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/design.md +0 -0
  54. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  55. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  56. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  57. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  58. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  59. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  60. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/.config.kiro +0 -0
  61. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/design.md +0 -0
  62. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/requirements.md +0 -0
  63. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/singleton-task/tasks.md +0 -0
  64. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  65. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  66. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  67. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  68. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/.config.kiro +0 -0
  69. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/design.md +0 -0
  70. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/requirements.md +0 -0
  71. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.kiro/specs/task-retry/tasks.md +0 -0
  72. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.pre-commit-config.yaml +0 -0
  73. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/.vscode/settings.json +0 -0
  74. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/docker-compose.yml +0 -0
  75. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/README.md +0 -0
  76. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/docker-compose.yml +0 -0
  77. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_app/__init__.py +0 -0
  78. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_app/apps.py +0 -0
  79. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/__init__.py +0 -0
  80. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/urls.py +0 -0
  81. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/example_project/wsgi.py +0 -0
  82. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/example/manage.py +0 -0
  83. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/__init__.py +0 -0
  84. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/admin.py +0 -0
  85. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/apps.py +0 -0
  86. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/checks.py +0 -0
  87. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/decorators.py +0 -0
  88. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/environment_loader.py +0 -0
  89. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/local_executor.py +0 -0
  90. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/logging.py +0 -0
  91. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0001_initial.py +0 -0
  92. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  93. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0003_taskrecord_taskrecord_kwargs_trgm.py +0 -0
  94. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/0004_remove_taskrecord_taskrecord_kwargs_trgm_and_more.py +0 -0
  95. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/migrations/__init__.py +0 -0
  96. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/secret_loader.py +0 -0
  97. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/settings.py +0 -0
  98. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/lambda_tasks/timeouts.py +0 -0
  99. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/conftest.py +0 -0
  100. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/settings.py +0 -0
  101. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_admin.py +0 -0
  102. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_batch.py +0 -0
  103. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_checks.py +0 -0
  104. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_decorator.py +0 -0
  105. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_decorators.py +0 -0
  106. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_deferred_enqueue.py +0 -0
  107. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_environment_loader.py +0 -0
  108. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_handler.py +0 -0
  109. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_kwargs_only.py +0 -0
  110. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_local_executor.py +0 -0
  111. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_logging.py +0 -0
  112. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_memory_limit.py +0 -0
  113. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_models.py +0 -0
  114. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_noop_execution.py +0 -0
  115. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_secret_loader.py +0 -0
  116. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_serializer.py +0 -0
  117. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_settings.py +0 -0
  118. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_tasks.py +0 -0
  119. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_timeout_validation.py +0 -0
  120. {django_lambda_tasks-0.9.2 → django_lambda_tasks-0.11.0}/tests/test_timeouts.py +0 -0
@@ -31,7 +31,6 @@ 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)
35
34
  - `logging.py` — `task_logger` for invocation-scoped log output
36
35
  - `settings.py` — lazy `LambdaTasksSettings` reading from Django settings, queue type helpers
37
36
 
@@ -128,7 +127,7 @@ class SQSLambdaTaskMessage(BaseModel):
128
127
 
129
128
  ## Execution
130
129
 
131
- `SQSLambdaTaskMessage.execute_immediately(*, message_id: str)` in `models.py`:
130
+ `SQSLambdaTaskMessage.execute_immediately(*, message_id: str)` in `models.py` sets the `task_logger` `message_id` and enters `task_scope(...)` (Sentry transaction name + tags), then delegates the run to the private `_run(*, message_id)` method which:
132
131
  1. Checks for an existing `TaskRecord` with the same `pk` (`message_id`) via `get_or_create`
133
132
  2. If a record already exists (any status), logs and returns immediately — duplicate deliveries are silently skipped
134
133
  3. Resolves timeouts: decorator default → settings defaults (soft=270s, hard=300s), validated against queue max (900 for SQS, 3600 for Batch)
@@ -159,7 +158,6 @@ class SQSLambdaTaskMessage(BaseModel):
159
158
  - Reads `LAMBDA_TASKS_MESSAGE` env var (the serialized task JSON, set by `submit_batch_job` via container overrides)
160
159
  - Uses `AWS_BATCH_JOB_ID` as the `message_id` (falls back to UUID4 if not set)
161
160
  - 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
163
161
  - Returns exit code 0 on success, 1 on failure
164
162
 
165
163
  ## Environment Loader
@@ -299,26 +297,6 @@ Relying on `atexit` alone loses this race in applications with a heavy shutdown
299
297
 
300
298
  The checks are registered via `AppConfig.ready()` and tagged with `deploy=True` so they only fire during `--deploy` checks.
301
299
 
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
- - Library instrumentation: after the provider and propagator are installed, `configure_tracing()` calls `_instrument_libraries()`, which attaches the instrumentors for the libraries the tasks use — **psycopg 3** (SQL queries), **botocore** (every boto3 / aioboto3 call), **requests** and **httpx** (outbound HTTP), and **redis** (cache ops via `django_redis` → `redis-py`). Each instrumentor is wrapped independently and is best-effort: a missing or failing one is logged and skipped, never raised, so one broken package cannot stop the others or take down the worker. These are process-global and idempotent, and emit spans into the global provider (so it must exist first). Django request instrumentation (`DjangoInstrumentor`) is **not** installed here — a task has no Django request; the API (ECS) consumer installs that itself. sqlcommenter (`enable_commenter=True` on the psycopg instrumentor) is left off because it modifies the SQL text sent to PostgreSQL.
314
- - Exporter self-trace suppression: the OTLP exporter POSTs spans over `requests` to the X-Ray endpoint, so the `requests` instrumentor is configured with `excluded_urls="xray\\..*\\.amazonaws\\.com"` to avoid tracing its own export calls (self-referential noise). The instrumentor also honours the `OTEL_PYTHON_REQUESTS_EXCLUDED_URLS` env var for per-environment overrides. botocore is not used by the exporter, so it needs no exclusion.
315
- - 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.
316
- - 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.
317
- - 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.
318
- - 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).
319
-
320
- `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.
321
-
322
300
  ## Logging
323
301
 
324
302
  Import `task_logger` to emit log records that are automatically prefixed with the active `message_id`:
@@ -335,6 +313,24 @@ def my_task(*, user_id: int) -> None:
335
313
 
336
314
  `task_logger` is a `LoggerAdapter` wrapping the `lambda_tasks.task` logger. The executor sets the `message_id` before each task runs and clears it in a `finally` block. Using your own `logging.getLogger(__name__)` is fine — those records just won't carry the prefix.
337
315
 
316
+ ## Sentry
317
+
318
+ The library does not call `sentry_sdk.init(...)` — the host application owns Sentry setup (typically `DjangoIntegration` + `AwsLambdaIntegration`). The problem those integrations leave is attribution: on Lambda, `AwsLambdaIntegration` names every event's transaction after the Lambda function, so all tasks share one transaction (and a single invocation-level transaction is shared across a batch of SQS records); on AWS Batch there is no request and no Lambda integration, so the transaction and URL are `"--"` and `traces_sample_rate` produces no trace for the task body. Either way you cannot tell which task ran or raised.
319
+
320
+ `lambda_tasks.sentry.task_scope(*, task_name, message_id, n_retries)` fixes this. `execute_immediately` wraps the task body in it, which — when a Sentry client is active — opens an isolated `sentry_sdk.new_scope()` **and a per-task root span/transaction** named after the task. This gives one trace per task on both Lambda and Batch. It records, both on the span and as scope tags:
321
+
322
+ - transaction/span **name** = the fully-qualified task name (e.g. `myapp.tasks.my_task`)
323
+ - `lambda_task` = task name
324
+ - `lambda_task.message_id` = the `TaskRecord` primary key
325
+ - `lambda_task.n_retries` = the retry counter
326
+ - `lambda_task.runtime` = the physical execution environment, read from the AWS environment: `AWS_LAMBDA_FUNCTION_NAME` on Lambda or `AWS_BATCH_JQ_NAME` (the Batch job queue name) on Batch; omitted when neither is present
327
+
328
+ The scope covers the success, retry, and failure paths, so task-logic exceptions (which the executor records as `FAILED` and does not re-raise to the handler) still carry the task name when captured by Sentry. The scope tags are kept in addition to span metadata so error events are attributed even when the performance span is not sampled.
329
+
330
+ **SDK version compatibility:** `task_scope` is future-proof across the sentry-sdk 2.x→3.0 boundary. On 2.x it uses `start_transaction` + `set_tag` (a root span must be a transaction to be sent); on 3.0 (stream mode, where transactions are removed and tags no longer propagate to spans) it uses `start_span` + `set_attribute`. The correct API is detected at runtime via `hasattr`, so no code change is needed when the host upgrades the SDK.
331
+
332
+ `task_scope` is a no-op when `sentry_sdk` is not installed or no client is initialised, so Sentry is not a dependency of the library. `sentry-sdk` is a dev dependency only, for the test suite. No custom Sentry integration is needed — per-task tracing at the one execution entry point covers both Lambda and Batch.
333
+
338
334
  ## Built-in Tasks
339
335
 
340
336
  `lambda_tasks.tasks` provides maintenance tasks that ship with the library:
@@ -16,6 +16,7 @@ django-lambda-tasks/
16
16
  │ ├── decorators.py # LambdaTaskWrapper, @lambda_task
17
17
  │ ├── handler.py # AWS Lambda entry point + AWS Batch container entry point (main)
18
18
  │ ├── logging.py # task_logger — invocation-scoped LoggerAdapter
19
+ │ ├── sentry.py # task_scope — per-task Sentry transaction name + tags
19
20
  │ ├── models.py # TaskRecord, SQSLambdaTaskMessage, SQSLambdaTask
20
21
  │ ├── settings.py # LambdaTasksSettings, constants, queue type helpers
21
22
  │ ├── secret_loader.py # Resolves LAMBDA_TASKS_SECRET_* env vars at cold start
@@ -23,7 +24,6 @@ django-lambda-tasks/
23
24
  │ ├── local_executor.py # ProcessPoolExecutor for async local task execution
24
25
  │ ├── tasks.py # Built-in tasks (cleanup_task_records, submit_batch_job)
25
26
  │ ├── timeouts.py # TimeoutContext implementation
26
- │ ├── tracing.py # Optional OpenTelemetry tracing (import-time setup, X-Ray OTLP export)
27
27
  │ └── migrations/ # Django migrations for TaskRecord
28
28
  ├── tests/ # pytest test suite
29
29
  │ ├── conftest.py
@@ -42,13 +42,13 @@ django-lambda-tasks/
42
42
  ## Module Responsibilities
43
43
 
44
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
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
+ - `models.py` — `TaskRecord` (Django ORM), `SQSLambdaTaskMessage` (Pydantic, SQS schema + execution logic; `execute_immediately` owns the logging + Sentry context and delegates the run to a private `_run` method), `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`)
46
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
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`), then calls `_instrument_libraries()` to attach the library instrumentors (psycopg 3, botocore, requests, httpx, redis) — each wrapped independently and best-effort (a missing or failing instrumentor is logged, not raised), and the `requests` instrumentor is given an `excluded_urls` regex for the X-Ray OTLP endpoint so the exporter's own export POSTs are not self-traced. `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
+ - `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()`
49
48
  - `environment_loader.py` — loads env vars from a Secrets Manager secret at cold start; validates flat JSON format; idempotent via `_loaded` sentinel
50
49
  - `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
51
50
  - `logging.py` — `task_logger` singleton; `message_id` set/cleared around each task execution
51
+ - `sentry.py` — `task_scope` context manager; opens an isolated `new_scope` and a per-task root span/transaction named after the task, so both errors and performance traces are attributed per task (one trace per task on Lambda and Batch, not one invocation-level transaction shared across an SQS batch). Records `lambda_task` / `lambda_task.message_id` / `lambda_task.n_retries` / `lambda_task.runtime` as span metadata and scope tags; `lambda_task.runtime` is the execution environment read from `AWS_LAMBDA_FUNCTION_NAME` (Lambda) or `AWS_BATCH_JQ_NAME` (Batch). Future-proof across the SDK 2.x→3.0 boundary: it uses `start_transaction`+`set_tag` on 2.x and `start_span`+`set_attribute` on 3.0 (stream mode), detected at runtime via `hasattr`. A no-op when `sentry_sdk` is absent or uninitialised, so the library has no hard dependency on Sentry (`sentry-sdk` is a dev dependency only). Wrapped around the task body in `SQSLambdaTaskMessage.execute_immediately`
52
52
  - `settings.py` — constants (`MAX_DELAY`, `MAX_TIMEOUT`, `MAX_BATCH_TIMEOUT`); `_is_sqs_queue()` and `_is_batch_queue()` helpers; `LambdaTasksSettings` instantiated fresh per use (reads live Django settings); `queue_max_timeout()` method returns 900 or 3600 based on queue type
53
53
  - `admin.py` — Django admin registration for `TaskRecord`
54
54
  - `apps.py` — Django `AppConfig`; `ready()` imports `checks` to register deployment checks and installs the local-executor shutdown signal handlers when `LOCAL_WORKERS > 0`
@@ -17,30 +17,13 @@ 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
- | `opentelemetry-instrumentation-psycopg` | SQL query spans (psycopg 3) |
32
- | `opentelemetry-instrumentation-botocore` | AWS SDK spans — every boto3 / aioboto3 call |
33
- | `opentelemetry-instrumentation-requests` | Outbound HTTP spans via `requests` |
34
- | `opentelemetry-instrumentation-httpx` | Outbound HTTP spans via `httpx` |
35
- | `opentelemetry-instrumentation-redis` | Cache operation spans (`django_redis` → `redis-py`) |
36
- | `requests` | OTLP/HTTP transport session (SigV4-signed via `botocore`) |
37
-
38
20
  ## Dev Dependencies
39
21
 
40
22
  | Package | Version | Purpose |
41
23
  |---|---|---|
42
24
  | `pytest` | `>= 9.0.2` | Test runner |
43
25
  | `hypothesis` | present | Property-based testing (`.hypothesis/` directory exists) |
26
+ | `sentry-sdk` | present | Testing the `task_scope` Sentry integration; not a runtime dependency |
44
27
 
45
28
  ## Package Manager
46
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: django-lambda-tasks
3
- Version: 0.9.2
3
+ Version: 0.11.0
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: awslambdaric
@@ -8,18 +8,6 @@ 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-instrumentation-botocore; extra == 'otel'
15
- Requires-Dist: opentelemetry-instrumentation-httpx; extra == 'otel'
16
- Requires-Dist: opentelemetry-instrumentation-psycopg; extra == 'otel'
17
- Requires-Dist: opentelemetry-instrumentation-redis; extra == 'otel'
18
- Requires-Dist: opentelemetry-instrumentation-requests; extra == 'otel'
19
- Requires-Dist: opentelemetry-propagator-aws-xray; extra == 'otel'
20
- Requires-Dist: opentelemetry-sdk; extra == 'otel'
21
- Requires-Dist: opentelemetry-sdk-extension-aws; extra == 'otel'
22
- Requires-Dist: requests; extra == 'otel'
23
11
  Description-Content-Type: text/markdown
24
12
 
25
13
  # Django Lambda Tasks
@@ -456,6 +444,32 @@ fields @timestamp, @message
456
444
  | sort @timestamp asc
457
445
  ```
458
446
 
447
+ ### Sentry
448
+
449
+ If your application uses Sentry, tasks are automatically attributed to their task name. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks share one transaction), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task raised an error.
450
+
451
+ You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
452
+
453
+ ```python
454
+ sentry_sdk.init(
455
+ dsn=SENTRY_DSN,
456
+ integrations=[DjangoIntegration(), AwsLambdaIntegration()],
457
+ # ...
458
+ )
459
+ ```
460
+
461
+ When a task runs, `execute_immediately()` opens an isolated Sentry scope **and a per-task trace** (a root span/transaction named after the task). With `traces_sample_rate` set, you get one trace per task on both Lambda and Batch — rather than a single invocation-level transaction shared across a batch of SQS records. It sets, on both the span and the scope:
462
+
463
+ - the **transaction/span name** to the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`), overriding the Lambda function name on Lambda and replacing `"--"` on Batch
464
+ - `lambda_task` (task name), `lambda_task.message_id`, and `lambda_task.n_retries`
465
+ - `lambda_task.runtime` — the physical execution environment, derived from the AWS environment: the Lambda function name (`AWS_LAMBDA_FUNCTION_NAME`) on Lambda, or the Batch job queue name (`AWS_BATCH_JQ_NAME`) on Batch. Omitted when neither is set (e.g. local/eager execution).
466
+
467
+ This covers task failures too: the executor records failures as `FAILED` `TaskRecord`s rather than re-raising, so the trace wraps the task body to ensure Sentry error events still carry the task name.
468
+
469
+ The integration is future-proof across the sentry-sdk 2.x→3.0 boundary: it uses `start_transaction`/`set_tag` on 2.x and `start_span`/`set_attribute` on 3.0 (stream mode), chosen automatically at runtime — no change needed when you upgrade the SDK.
470
+
471
+ `sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, the enrichment is a silent no-op.
472
+
459
473
  ---
460
474
 
461
475
  ## Automatic retries
@@ -432,6 +432,32 @@ fields @timestamp, @message
432
432
  | sort @timestamp asc
433
433
  ```
434
434
 
435
+ ### Sentry
436
+
437
+ If your application uses Sentry, tasks are automatically attributed to their task name. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks share one transaction), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task raised an error.
438
+
439
+ You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
440
+
441
+ ```python
442
+ sentry_sdk.init(
443
+ dsn=SENTRY_DSN,
444
+ integrations=[DjangoIntegration(), AwsLambdaIntegration()],
445
+ # ...
446
+ )
447
+ ```
448
+
449
+ When a task runs, `execute_immediately()` opens an isolated Sentry scope **and a per-task trace** (a root span/transaction named after the task). With `traces_sample_rate` set, you get one trace per task on both Lambda and Batch — rather than a single invocation-level transaction shared across a batch of SQS records. It sets, on both the span and the scope:
450
+
451
+ - the **transaction/span name** to the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`), overriding the Lambda function name on Lambda and replacing `"--"` on Batch
452
+ - `lambda_task` (task name), `lambda_task.message_id`, and `lambda_task.n_retries`
453
+ - `lambda_task.runtime` — the physical execution environment, derived from the AWS environment: the Lambda function name (`AWS_LAMBDA_FUNCTION_NAME`) on Lambda, or the Batch job queue name (`AWS_BATCH_JQ_NAME`) on Batch. Omitted when neither is set (e.g. local/eager execution).
454
+
455
+ This covers task failures too: the executor records failures as `FAILED` `TaskRecord`s rather than re-raising, so the trace wraps the task body to ensure Sentry error events still carry the task name.
456
+
457
+ The integration is future-proof across the sentry-sdk 2.x→3.0 boundary: it uses `start_transaction`/`set_tag` on 2.x and `start_span`/`set_attribute` on 3.0 (stream mode), chosen automatically at runtime — no change needed when you upgrade the SDK.
458
+
459
+ `sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, the enrichment is a silent no-op.
460
+
435
461
  ---
436
462
 
437
463
  ## Automatic retries
@@ -0,0 +1,31 @@
1
+ from time import sleep
2
+
3
+ from lambda_tasks.decorators import lambda_task
4
+ from lambda_tasks.logging import task_logger
5
+
6
+
7
+ @lambda_task()
8
+ def greet(*, name: str) -> str:
9
+ """A successful task — produces one per-task Sentry transaction named
10
+ ``example_app.tasks.greet``."""
11
+ task_logger.info(f"Running greet with: {name=}")
12
+
13
+ for ii in range(3):
14
+ task_logger.info(f"Sleeping: {ii=}")
15
+ sleep(1)
16
+
17
+ try:
18
+ raise RuntimeError("testing")
19
+ except Exception:
20
+ task_logger.error("Raising", exc_info=True)
21
+
22
+ return f"Hello, {name}!"
23
+
24
+
25
+ @lambda_task()
26
+ def boom(*, name: str) -> str:
27
+ """A failing task — the executor records it as a FAILED TaskRecord and the
28
+ error is reported to Sentry attributed to ``example_app.tasks.boom`` (not
29
+ the Lambda function name), inside its own per-task trace."""
30
+ task_logger.info(f"Running boom with: {name=}")
31
+ raise RuntimeError(f"boom for {name}")
@@ -0,0 +1,7 @@
1
+ from django.urls import path
2
+ from example_app.views import trigger, trigger_error
3
+
4
+ urlpatterns = [
5
+ path("trigger/", trigger),
6
+ path("trigger-error/", trigger_error),
7
+ ]
@@ -0,0 +1,17 @@
1
+ from django.http import HttpRequest, HttpResponse
2
+ from example_app.tasks import boom, greet
3
+
4
+
5
+ def trigger(request: HttpRequest) -> HttpResponse:
6
+ """Enqueue the successful task — produces a per-task Sentry transaction."""
7
+ name = request.GET.get("name", "world")
8
+ greet.execute_on_commit(name=name)
9
+ return HttpResponse(f"greet task triggered for: {name}")
10
+
11
+
12
+ def trigger_error(request: HttpRequest) -> HttpResponse:
13
+ """Enqueue the failing task — produces a Sentry error attributed to the
14
+ task name, inside its own per-task trace."""
15
+ name = request.GET.get("name", "world")
16
+ boom.execute_on_commit(name=name)
17
+ return HttpResponse(f"boom task triggered for: {name}")
@@ -0,0 +1,50 @@
1
+ """Optional Sentry initialisation for the example app.
2
+
3
+ Demonstrates how a host application wires Sentry so that
4
+ ``django-lambda-tasks`` can attribute per-task traces and errors. This mirrors
5
+ a typical production ``sentry_sdk.init(...)`` (Django + AWS Lambda integrations
6
+ and tracing), but is driven entirely by environment variables so the example
7
+ runs with or without Sentry configured.
8
+
9
+ ``init_sentry()`` is called from ``settings.py`` at import time. Because the
10
+ local process-pool workers call ``django.setup()`` (which imports settings) in
11
+ each child process, this runs in the web process *and* in every task worker —
12
+ which is exactly what is needed for the per-task trace to be captured where the
13
+ task actually executes.
14
+ """
15
+
16
+ import logging
17
+ import os
18
+
19
+
20
+ def init_sentry() -> None:
21
+ """Initialise Sentry if ``SENTRY_DSN`` is set; otherwise do nothing.
22
+
23
+ Idempotent enough for the example's purposes: ``sentry_sdk.init`` is safe to
24
+ call once per process, and this is only invoked from settings import.
25
+ """
26
+ dsn = os.environ.get("SENTRY_DSN")
27
+ if not dsn:
28
+ return
29
+
30
+ import sentry_sdk
31
+ from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
32
+ from sentry_sdk.integrations.django import DjangoIntegration
33
+ from sentry_sdk.integrations.logging import LoggingIntegration
34
+
35
+ sentry_sdk.init(
36
+ dsn=dsn,
37
+ integrations=[
38
+ DjangoIntegration(),
39
+ AwsLambdaIntegration(),
40
+ # On sentry-sdk 2.x the logging integration defaulted to
41
+ # event_level=ERROR, so logger.error(...) calls (e.g. task_logger)
42
+ # became Sentry events automatically. On 3.0 the default is None, so
43
+ # error logs no longer surface as events. Set it explicitly so
44
+ # task_logger.error output appears in Sentry on both SDK versions.
45
+ LoggingIntegration(event_level=logging.ERROR),
46
+ ],
47
+ # Sample every transaction so the per-task trace is always sent while
48
+ # you are testing. Lower this in a real deployment.
49
+ traces_sample_rate=float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "1.0")),
50
+ )
@@ -88,3 +88,10 @@ LOGGING = {
88
88
  "level": "INFO",
89
89
  },
90
90
  }
91
+
92
+ # Optional Sentry integration — set SENTRY_DSN to enable. Initialised here (at
93
+ # settings import) so it also runs inside each local process-pool worker, which
94
+ # imports settings via django.setup(). See example_project/sentry.py.
95
+ from example_project.sentry import init_sentry # noqa: E402
96
+
97
+ init_sentry()
@@ -26,11 +26,6 @@ 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
- )
34
29
 
35
30
  logger = logging.getLogger(__name__)
36
31
 
@@ -174,25 +169,11 @@ def main() -> int:
174
169
  context=None,
175
170
  )
176
171
 
177
- # Flush any buffered spans before the one-shot process exits (no-op unless
178
- # tracing is enabled — this never imports OTEL otherwise).
179
- flush_tracing()
180
-
181
172
  if result["batchItemFailures"]:
182
173
  return 1
183
174
  else:
184
175
  return 0
185
176
 
186
177
 
187
- # Initialise tracing at import time so the Lambda instrumentor can wrap the
188
- # handler before the runtime invokes it (which is earlier than the cold-start
189
- # django.setup() inside handler()). This MUST run after `handler` and `main`
190
- # are defined: wrapt resolves the handler by attribute lookup on this module,
191
- # and wrapping a name that does not exist yet raises AttributeError. No-op
192
- # unless OTEL_EXPORTER_OTLP_ENDPOINT is set.
193
- configure_tracing()
194
- instrument_lambda_handler()
195
-
196
-
197
178
  if __name__ == "__main__":
198
179
  sys.exit(main())