django-lambda-tasks 0.11.0__tar.gz → 0.11.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.github/workflows/ci.yml +32 -0
  2. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/steering/product.md +16 -8
  3. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/steering/structure.md +2 -2
  4. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/steering/tech.md +1 -1
  5. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/PKG-INFO +37 -12
  6. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/README.md +36 -11
  7. django_lambda_tasks-0.11.1/lambda_tasks/sentry.py +186 -0
  8. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/pyproject.toml +2 -2
  9. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_sentry.py +133 -15
  10. django_lambda_tasks-0.11.0/lambda_tasks/sentry.py +0 -143
  11. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.github/dependabot.yml +0 -0
  12. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.github/workflows/release.yml +0 -0
  13. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.gitignore +0 -0
  14. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/async-local-execution/.config.kiro +0 -0
  15. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/async-local-execution/design.md +0 -0
  16. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/async-local-execution/requirements.md +0 -0
  17. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/async-local-execution/tasks.md +0 -0
  18. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/deferred-task-enqueue/.config.kiro +0 -0
  19. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/deferred-task-enqueue/design.md +0 -0
  20. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/deferred-task-enqueue/requirements.md +0 -0
  21. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/deferred-task-enqueue/tasks.md +0 -0
  22. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/eager-mode-example-app/.config.kiro +0 -0
  23. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/eager-mode-example-app/design.md +0 -0
  24. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/eager-mode-example-app/requirements.md +0 -0
  25. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/eager-mode-example-app/tasks.md +0 -0
  26. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ignore-errors-decorator-option/.config.kiro +0 -0
  27. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ignore-errors-decorator-option/design.md +0 -0
  28. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ignore-errors-decorator-option/requirements.md +0 -0
  29. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ignore-errors-decorator-option/tasks.md +0 -0
  30. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/import-string-task-resolution/.config.kiro +0 -0
  31. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/import-string-task-resolution/design.md +0 -0
  32. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/import-string-task-resolution/requirements.md +0 -0
  33. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/import-string-task-resolution/tasks.md +0 -0
  34. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/retry-delay/.config.kiro +0 -0
  35. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/retry-delay/design.md +0 -0
  36. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/retry-delay/requirements.md +0 -0
  37. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/retry-delay/tasks.md +0 -0
  38. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks/.config.kiro +0 -0
  39. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks/design.md +0 -0
  40. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks/requirements.md +0 -0
  41. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks/tasks.md +0 -0
  42. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks-bugfix/.config.kiro +0 -0
  43. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks-bugfix/bugfix.md +0 -0
  44. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks-bugfix/design.md +0 -0
  45. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/rse-background-tasks-bugfix/tasks.md +0 -0
  46. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/singleton-task/.config.kiro +0 -0
  47. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/singleton-task/design.md +0 -0
  48. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/singleton-task/requirements.md +0 -0
  49. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/singleton-task/tasks.md +0 -0
  50. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ssm-environment-loader/.config.kiro +0 -0
  51. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ssm-environment-loader/design.md +0 -0
  52. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ssm-environment-loader/requirements.md +0 -0
  53. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/ssm-environment-loader/tasks.md +0 -0
  54. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/task-retry/.config.kiro +0 -0
  55. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/task-retry/design.md +0 -0
  56. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/task-retry/requirements.md +0 -0
  57. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.kiro/specs/task-retry/tasks.md +0 -0
  58. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.pre-commit-config.yaml +0 -0
  59. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/.vscode/settings.json +0 -0
  60. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/docker-compose.yml +0 -0
  61. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/README.md +0 -0
  62. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/docker-compose.yml +0 -0
  63. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_app/__init__.py +0 -0
  64. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_app/apps.py +0 -0
  65. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_app/tasks.py +0 -0
  66. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_app/urls.py +0 -0
  67. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_app/views.py +0 -0
  68. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_project/__init__.py +0 -0
  69. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_project/sentry.py +0 -0
  70. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_project/settings.py +0 -0
  71. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_project/urls.py +0 -0
  72. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/example_project/wsgi.py +0 -0
  73. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/example/manage.py +0 -0
  74. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/__init__.py +0 -0
  75. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/admin.py +0 -0
  76. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/apps.py +0 -0
  77. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/checks.py +0 -0
  78. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/decorators.py +0 -0
  79. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/environment_loader.py +0 -0
  80. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/handler.py +0 -0
  81. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/local_executor.py +0 -0
  82. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/logging.py +0 -0
  83. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/migrations/0001_initial.py +0 -0
  84. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/migrations/0002_alter_taskrecord_status.py +0 -0
  85. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/migrations/0003_taskrecord_taskrecord_kwargs_trgm.py +0 -0
  86. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/migrations/0004_remove_taskrecord_taskrecord_kwargs_trgm_and_more.py +0 -0
  87. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/migrations/__init__.py +0 -0
  88. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/models.py +0 -0
  89. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/secret_loader.py +0 -0
  90. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/settings.py +0 -0
  91. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/tasks.py +0 -0
  92. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/lambda_tasks/timeouts.py +0 -0
  93. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/conftest.py +0 -0
  94. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/settings.py +0 -0
  95. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_admin.py +0 -0
  96. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_batch.py +0 -0
  97. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_batch_worker.py +0 -0
  98. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_checks.py +0 -0
  99. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_decorator.py +0 -0
  100. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_decorators.py +0 -0
  101. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_deferred_enqueue.py +0 -0
  102. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_environment_loader.py +0 -0
  103. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_handler.py +0 -0
  104. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_kwargs_only.py +0 -0
  105. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_local_executor.py +0 -0
  106. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_logging.py +0 -0
  107. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_memory_limit.py +0 -0
  108. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_models.py +0 -0
  109. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_noop_execution.py +0 -0
  110. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_secret_loader.py +0 -0
  111. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_serializer.py +0 -0
  112. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_settings.py +0 -0
  113. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_tasks.py +0 -0
  114. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_timeout_validation.py +0 -0
  115. {django_lambda_tasks-0.11.0 → django_lambda_tasks-0.11.1}/tests/test_timeouts.py +0 -0
@@ -53,3 +53,35 @@ jobs:
53
53
  - name: Run tests
54
54
  run: |
55
55
  uv run pytest --cov-branch --cov-report term-missing --cov=tests/ --cov=lambda_tasks/
56
+
57
+ sentry-prerelease:
58
+ # Exercise the Sentry integration against the latest 3.x pre-release of
59
+ # sentry-sdk (stream mode). The committed dev dependency is pinned to the
60
+ # stable 2.x line; this job overrides it at runtime via `uv run --with`
61
+ # without touching pyproject.toml, so regressions in the 3.x API are caught
62
+ # before the SDK goes stable.
63
+ runs-on: ubuntu-latest
64
+ services:
65
+ postgres:
66
+ image: postgres:17
67
+ env:
68
+ POSTGRES_DB: lambda_tasks_test
69
+ POSTGRES_USER: postgres
70
+ POSTGRES_PASSWORD: postgres
71
+ ports:
72
+ - 5432:5432
73
+ options: >-
74
+ --health-cmd "pg_isready -U postgres"
75
+ --health-interval 10s
76
+ --health-timeout 5s
77
+ --health-retries 5
78
+ steps:
79
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
80
+ - name: Install uv and set the python version
81
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
82
+ with:
83
+ python-version: '3.14'
84
+ - name: Run tests against the latest sentry-sdk 3.x pre-release
85
+ run: |
86
+ uv run --prerelease=allow --with "sentry-sdk>=3.0.0b1" \
87
+ pytest --cov-branch --cov-report term-missing --cov=tests/ --cov=lambda_tasks/
@@ -315,21 +315,29 @@ def my_task(*, user_id: int) -> None:
315
315
 
316
316
  ## Sentry
317
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.
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 look alike; on AWS Batch there is no request and no Lambda integration, so the transaction/URL are `"--"`. Either way you cannot tell which task ran or raised.
319
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:
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 traces the task. Trace shape depends on whether a trace is already active:
321
321
 
322
- - transaction/span **name** = the fully-qualified task name (e.g. `myapp.tasks.my_task`)
323
- - `lambda_task` = task name
322
+ - **Lambda** — `AwsLambdaIntegration` already started an invocation transaction, so the task is traced as a **child span** of it (not a second transaction). This is deliberate: a nested *transaction* per task inherited (rather than independently re-rolled) the invocation's sampling decision, doubling transaction volume and defeating `traces_sample_rate`. A child span is sent only if its parent is sampled, so sampling is respected. `_start_task_root` picks child-vs-root by checking `sentry_sdk.get_current_scope().transaction` (2.x only; on 3.0 `start_span` is automatically root-or-child).
323
+ - **Batch** (nothing active) — the task starts its **own root transaction** so it is still traced.
324
+
325
+ It records, both on the span and as scope tags:
326
+
327
+ - `lambda_task` = fully-qualified task name (e.g. `myapp.tasks.my_task`) — also the task span's name
324
328
  - `lambda_task.message_id` = the `TaskRecord` primary key
325
329
  - `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
330
+ - `lambda_task.runtime` = the execution environment, from the AWS environment: `AWS_LAMBDA_FUNCTION_NAME` on Lambda or `AWS_BATCH_JQ_NAME` (Batch job queue name); omitted when neither is present
331
+
332
+ `task_scope` deliberately does **not** call `scope.set_transaction_name(task_name)`: on Lambda that would rename the shared invocation transaction (fighting across sibling records in a batch) and, on SDK 3.0 stream mode, corrupt the root-segment naming. Error attribution relies on the `lambda_task` **tag** instead, which is reliable on both SDKs and independent of sampling.
333
+
334
+ **Reporting task failures:** the executor catches task exceptions (recording `FAILED` and not re-raising), so `lambda_tasks.sentry.capture_task_exception()` is called on the two **terminal** failure branches in `models.py` (plain failure and max-retries-exceeded) to report the error via `sentry_sdk.capture_exception()`. This is essential on 3.0, where a merely-caught exception produces no error event. The **retry** branch deliberately does not capture — transient, to-be-re-enqueued failures would flood Sentry; only terminal failures are reported.
327
335
 
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.
336
+ **Error-level log forwarding:** on sentry-sdk 2.x, `LoggingIntegration` defaulted to `event_level=ERROR`, so `task_logger.error(...)` became Sentry events automatically. On 3.0 the default is `None`, so hosts that want error logs reported must configure `LoggingIntegration(event_level=logging.ERROR)` explicitly (the example app does this). This is a host-init concern, not something the library sets.
329
337
 
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.
338
+ **SDK version compatibility:** `task_scope` is future-proof across the sentry-sdk 2.x→3.0 boundary and verified against both 2.74 and 3.0.0b4. On 2.x it uses `start_transaction` + `set_tag` for a root and `start_span` for a child; on 3.0 (stream mode) `start_span` is both root and child and metadata is attached via `set_attribute`. The correct API is detected at runtime via `hasattr`, so no code change is needed when the host upgrades the SDK.
331
339
 
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.
340
+ `task_scope` and `capture_task_exception` are no-ops 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 (pinned `>=2,<3`), 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
341
 
334
342
  ## Built-in Tasks
335
343
 
@@ -16,7 +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
+ │ ├── sentry.py # task_scope + capture_task_exception — per-task trace, tags, failure reporting
20
20
  │ ├── models.py # TaskRecord, SQSLambdaTaskMessage, SQSLambdaTask
21
21
  │ ├── settings.py # LambdaTasksSettings, constants, queue type helpers
22
22
  │ ├── secret_loader.py # Resolves LAMBDA_TASKS_SECRET_* env vars at cold start
@@ -48,7 +48,7 @@ django-lambda-tasks/
48
48
  - `environment_loader.py` — loads env vars from a Secrets Manager secret at cold start; validates flat JSON format; idempotent via `_loaded` sentinel
49
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
50
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`
51
+ - `sentry.py` — `task_scope` context manager + `capture_task_exception` helper. `task_scope` opens an isolated `new_scope` and traces the task: a **child span** of the invocation transaction on Lambda (where `AwsLambdaIntegration` already started one), or its **own root transaction** on Batch (nothing active). The child-span-on-Lambda behaviour avoids a second per-task transaction that would double trace volume and defeat `traces_sample_rate`. Records `lambda_task` / `lambda_task.message_id` / `lambda_task.n_retries` / `lambda_task.runtime` as span metadata and scope tags (runtime read from `AWS_LAMBDA_FUNCTION_NAME` or `AWS_BATCH_JQ_NAME`). Deliberately does not call `set_transaction_name` (would rename the shared invocation transaction and break 3.0 segment naming); error attribution rides on the `lambda_task` tag. `capture_task_exception` calls `sentry_sdk.capture_exception()` and is invoked on the terminal failure branches in `models.py` (plain failure + max-retries), since the executor catches exceptions without re-raising and 3.0 does not auto-report caught exceptions; the retry branch does not capture. Future-proof across the SDK 2.x→3.0 boundary (verified against 2.74 and 3.0.0b4): `start_transaction`/`set_tag`/child `start_span` on 2.x, `start_span`/`set_attribute` on 3.0, detected at runtime via `hasattr`. No-op when `sentry_sdk` is absent or uninitialised (`sentry-sdk` is a dev dependency only, pinned `>=2,<3`). Wrapped around the task body in `SQSLambdaTaskMessage.execute_immediately`. Note: `logger.error → Sentry event` needs `LoggingIntegration(event_level=logging.ERROR)` in the host init on 3.0 (default on 2.x); a host concern, demonstrated in the example app
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`
@@ -23,7 +23,7 @@ inclusion: always
23
23
  |---|---|---|
24
24
  | `pytest` | `>= 9.0.2` | Test runner |
25
25
  | `hypothesis` | present | Property-based testing (`.hypothesis/` directory exists) |
26
- | `sentry-sdk` | present | Testing the `task_scope` Sentry integration; not a runtime dependency |
26
+ | `sentry-sdk` | `>=2,<3` | Testing the Sentry integration (`task_scope`, `capture_task_exception`); verified against 2.74 and 3.0.0b4. Not a runtime dependency |
27
27
 
28
28
  ## Package Manager
29
29
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: django-lambda-tasks
3
- Version: 0.11.0
3
+ Version: 0.11.1
4
4
  Summary: Run async tasks in a lambda function
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: awslambdaric
@@ -446,29 +446,54 @@ fields @timestamp, @message
446
446
 
447
447
  ### Sentry
448
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.
449
+ If your application uses Sentry, task errors are automatically attributed to the task that raised them. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks look the same), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task failed.
450
450
 
451
- You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
451
+ Keep your existing `sentry_sdk.init(...)`. On **sentry-sdk 2.x** no change is needed. On **sentry-sdk 3.0+** one option changed default and must be set explicitly if you want error-level logs reported (see below):
452
452
 
453
453
  ```python
454
+ import logging
455
+ from sentry_sdk.integrations.logging import LoggingIntegration
456
+
454
457
  sentry_sdk.init(
455
458
  dsn=SENTRY_DSN,
456
- integrations=[DjangoIntegration(), AwsLambdaIntegration()],
457
- # ...
459
+ integrations=[
460
+ DjangoIntegration(),
461
+ AwsLambdaIntegration(),
462
+ # Only needed on sentry-sdk 3.0+: its default event_level is None, so
463
+ # logger.error(...) no longer becomes a Sentry event. On 2.x this was
464
+ # the default and setting it is harmless.
465
+ LoggingIntegration(event_level=logging.ERROR),
466
+ ],
467
+ traces_sample_rate=0.1,
458
468
  )
459
469
  ```
460
470
 
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:
471
+ #### What the library does per task
472
+
473
+ When a task runs, `execute_immediately()` opens an isolated Sentry scope and traces the task:
474
+
475
+ - **On Lambda**, the task is traced as a **child span** of the invocation transaction that `AwsLambdaIntegration` already started — not a second transaction. This keeps task tracing from doubling your transaction volume and respects `traces_sample_rate`.
476
+ - **On Batch** (no Lambda integration, nothing active), the task starts **its own root transaction** so it is still traced.
477
+
478
+ Either way it records, as both span metadata and scope tags:
479
+
480
+ - `lambda_task` — the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`). This is also the task span's name.
481
+ - `lambda_task.message_id`, `lambda_task.n_retries`
482
+ - `lambda_task.runtime` — the execution environment, 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 set (local/eager).
483
+
484
+ Error attribution relies on the `lambda_task` **tag**, not on the transaction name, so it is reliable even when the trace is not sampled and regardless of whether the task ran as a child span (Lambda) or a root (Batch).
485
+
486
+ #### Task failures are reported
487
+
488
+ The executor catches a failing task's exception (recording a `FAILED` `TaskRecord`) rather than re-raising it. So the library calls `sentry_sdk.capture_exception()` itself on the **terminal failure paths** — a plain failure and max-retries-exceeded — to report the error to Sentry. This is essential on 3.0, where a merely-caught exception produces no error event at all.
462
489
 
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).
490
+ Transient, retryable failures (a task that will be re-enqueued) are **not** reported, to avoid flooding Sentry on every retry; only the final, terminal failure is captured.
466
491
 
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.
492
+ #### SDK version compatibility
468
493
 
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.
494
+ The integration is future-proof across the sentry-sdk 2.x→3.0 boundary and verified against both 2.74 and 3.0.0b4. 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.
470
495
 
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.
496
+ `sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, all of the above is a silent no-op.
472
497
 
473
498
  ---
474
499
 
@@ -434,29 +434,54 @@ fields @timestamp, @message
434
434
 
435
435
  ### Sentry
436
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.
437
+ If your application uses Sentry, task errors are automatically attributed to the task that raised them. Without this, Sentry's `AwsLambdaIntegration` names every event's transaction after the Lambda function (so all tasks look the same), and on AWS Batch there is no transaction at all (it shows as `"--"`) — so you cannot tell which task failed.
438
438
 
439
- You do **not** need to add a custom integration or change your `sentry_sdk.init(...)`. Keep your existing setup:
439
+ Keep your existing `sentry_sdk.init(...)`. On **sentry-sdk 2.x** no change is needed. On **sentry-sdk 3.0+** one option changed default and must be set explicitly if you want error-level logs reported (see below):
440
440
 
441
441
  ```python
442
+ import logging
443
+ from sentry_sdk.integrations.logging import LoggingIntegration
444
+
442
445
  sentry_sdk.init(
443
446
  dsn=SENTRY_DSN,
444
- integrations=[DjangoIntegration(), AwsLambdaIntegration()],
445
- # ...
447
+ integrations=[
448
+ DjangoIntegration(),
449
+ AwsLambdaIntegration(),
450
+ # Only needed on sentry-sdk 3.0+: its default event_level is None, so
451
+ # logger.error(...) no longer becomes a Sentry event. On 2.x this was
452
+ # the default and setting it is harmless.
453
+ LoggingIntegration(event_level=logging.ERROR),
454
+ ],
455
+ traces_sample_rate=0.1,
446
456
  )
447
457
  ```
448
458
 
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:
459
+ #### What the library does per task
460
+
461
+ When a task runs, `execute_immediately()` opens an isolated Sentry scope and traces the task:
462
+
463
+ - **On Lambda**, the task is traced as a **child span** of the invocation transaction that `AwsLambdaIntegration` already started — not a second transaction. This keeps task tracing from doubling your transaction volume and respects `traces_sample_rate`.
464
+ - **On Batch** (no Lambda integration, nothing active), the task starts **its own root transaction** so it is still traced.
465
+
466
+ Either way it records, as both span metadata and scope tags:
467
+
468
+ - `lambda_task` — the fully-qualified task name (e.g. `myapp.tasks.send_welcome_email`). This is also the task span's name.
469
+ - `lambda_task.message_id`, `lambda_task.n_retries`
470
+ - `lambda_task.runtime` — the execution environment, 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 set (local/eager).
471
+
472
+ Error attribution relies on the `lambda_task` **tag**, not on the transaction name, so it is reliable even when the trace is not sampled and regardless of whether the task ran as a child span (Lambda) or a root (Batch).
473
+
474
+ #### Task failures are reported
475
+
476
+ The executor catches a failing task's exception (recording a `FAILED` `TaskRecord`) rather than re-raising it. So the library calls `sentry_sdk.capture_exception()` itself on the **terminal failure paths** — a plain failure and max-retries-exceeded — to report the error to Sentry. This is essential on 3.0, where a merely-caught exception produces no error event at all.
450
477
 
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).
478
+ Transient, retryable failures (a task that will be re-enqueued) are **not** reported, to avoid flooding Sentry on every retry; only the final, terminal failure is captured.
454
479
 
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.
480
+ #### SDK version compatibility
456
481
 
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.
482
+ The integration is future-proof across the sentry-sdk 2.x→3.0 boundary and verified against both 2.74 and 3.0.0b4. 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.
458
483
 
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.
484
+ `sentry_sdk` is not a dependency of this library — if it is not installed or not initialised, all of the above is a silent no-op.
460
485
 
461
486
  ---
462
487
 
@@ -0,0 +1,186 @@
1
+ """Sentry tracing/scope helpers for task execution.
2
+
3
+ This library does not initialise Sentry itself — the host application owns
4
+ ``sentry_sdk.init(...)``. These helpers trace each task and attribute errors to
5
+ the running task rather than to the Lambda function name (set by
6
+ ``AwsLambdaIntegration``) or to ``"--"`` (on AWS Batch, where no request or
7
+ Lambda integration is active).
8
+
9
+ Trace shape depends on whether a trace is already active:
10
+
11
+ * **Lambda** — ``AwsLambdaIntegration`` starts a transaction per invocation, so
12
+ the task is traced as a **child span** of it. One transaction per invocation,
13
+ with the task span nested inside — not a second, competing transaction.
14
+ * **Batch** (and anywhere nothing is active) — the task starts its **own root
15
+ transaction**, so it is still traced on its own.
16
+
17
+ Why the child span matters: starting a nested *transaction* per task produced a
18
+ second transaction that inherited (rather than independently re-rolled) the
19
+ invocation's sampling decision, roughly doubling transaction volume and
20
+ defeating ``traces_sample_rate``. A child span is sent only if its parent
21
+ transaction is sampled, so sampling is respected.
22
+
23
+ Error attribution does not depend on the trace being sampled: the ``lambda_task``
24
+ scope tag carries the task name on every error event regardless.
25
+
26
+ Future-proofing across the SDK 2.x → 3.0 boundary
27
+ --------------------------------------------------
28
+ The tracing API changed in SDK 3.0 (stream mode):
29
+
30
+ * 2.x — a root is a *transaction* (``start_transaction``); a child uses
31
+ ``start_span``. Metadata is attached with ``set_tag``; ``Span.set_attribute``
32
+ does not exist, and ``Scope`` has a ``transaction`` attribute.
33
+ * 3.0 — transaction mode is gone. ``start_span`` is both root (when nothing is
34
+ active) and child (when something is), metadata is attached via
35
+ ``set_attribute``, and ``Scope`` has no ``transaction`` attribute.
36
+
37
+ We detect which API is present at runtime (via ``hasattr``) and use the correct
38
+ one, so this module keeps working unchanged after an SDK upgrade. Metadata is
39
+ attached with whichever setter the running span exposes (``set_attribute`` when
40
+ available, otherwise ``set_tag``).
41
+
42
+ Everything here degrades to a no-op when ``sentry_sdk`` is not installed or no
43
+ client is active, so the library has no hard dependency on Sentry.
44
+ """
45
+
46
+ import contextlib
47
+ import os
48
+ from collections.abc import Iterator
49
+ from typing import Any
50
+
51
+ try:
52
+ import sentry_sdk
53
+ except ImportError:
54
+ sentry_sdk = None # ty: ignore[invalid-assignment]
55
+
56
+
57
+ def _runtime_environment() -> str | None:
58
+ """Return the environment the task is executing in, or ``None``.
59
+
60
+ ``AWS_LAMBDA_FUNCTION_NAME`` on Lambda, ``AWS_BATCH_JQ_NAME`` (the Batch job
61
+ queue the job was submitted to) on Batch. ``None`` when neither is present
62
+ (e.g. local or eager execution).
63
+ """
64
+ return os.environ.get("AWS_LAMBDA_FUNCTION_NAME") or os.environ.get(
65
+ "AWS_BATCH_JQ_NAME"
66
+ )
67
+
68
+
69
+ def _set_metadata(span: Any, *, key: str, value: Any) -> None:
70
+ """Attach *key*/*value* to *span* using whichever setter the SDK exposes.
71
+
72
+ SDK 3.0 (stream mode) spans use ``set_attribute``; SDK 2.x transactions use
73
+ ``set_tag``. Preferring ``set_attribute`` means the richer, future API is
74
+ used whenever it is available.
75
+ """
76
+ if hasattr(span, "set_attribute"):
77
+ span.set_attribute(key, value)
78
+ else:
79
+ span.set_tag(key, value)
80
+
81
+
82
+ def _start_task_root(*, task_name: str) -> Any:
83
+ """Start and return the per-task span/transaction context manager.
84
+
85
+ Crucially, this starts a **child span** when a trace is already active
86
+ (e.g. ``AwsLambdaIntegration`` starts a transaction per Lambda invocation)
87
+ and only starts a **new transaction** when nothing is active (e.g. AWS
88
+ Batch, where there is no Lambda integration).
89
+
90
+ Why this matters for volume: starting a nested *transaction* under the
91
+ invocation transaction produced a *second* transaction per task that
92
+ inherited — rather than independently re-rolled — the parent's sampling
93
+ decision, roughly doubling transaction volume and defeating
94
+ ``traces_sample_rate``. A child span carries no separate sampling decision:
95
+ it is sent only if its parent transaction is sampled, so one invocation
96
+ yields one transaction with the task span nested inside it.
97
+
98
+ The tracing API also differs across the SDK 2.x → 3.0 boundary:
99
+
100
+ * 2.x — ``start_transaction(name=, op=)`` for a root; ``start_span`` for a
101
+ child of the active transaction.
102
+ * 3.0 — transactions are gone; ``start_span(name=)`` is both the root (when
103
+ nothing is active) and a child (when something is), and no longer accepts
104
+ ``op``.
105
+ """
106
+ if not hasattr(sentry_sdk, "start_transaction"):
107
+ # SDK 3.0: start_span is automatically a root when nothing is active
108
+ # and a child otherwise — no manual active-trace check needed. (Scope
109
+ # has no ``transaction`` attribute on 3.0.)
110
+ return sentry_sdk.start_span(name=task_name)
111
+ else:
112
+ # SDK 2.x: a bare start_span with no active transaction is dropped,
113
+ # so pick child span vs. new transaction based on whether a trace is
114
+ # already active.
115
+ has_active_transaction = sentry_sdk.get_current_scope().transaction is not None
116
+
117
+ if has_active_transaction:
118
+ return sentry_sdk.start_span(op="task", name=task_name)
119
+ else:
120
+ return sentry_sdk.start_transaction(name=task_name, op="task")
121
+
122
+
123
+ def capture_task_exception() -> None:
124
+ """Report the exception currently being handled to Sentry.
125
+
126
+ The executor catches task exceptions (recording a ``FAILED`` TaskRecord)
127
+ rather than re-raising, so the exception never propagates to an integration
128
+ that would report it. This matters most on SDK 3.0 (stream mode), where a
129
+ caught exception produces no error event at all — on 2.x the exception
130
+ happened to still be reported as it passed through the active span. Calling
131
+ this on the terminal failure path gives consistent error reporting on both
132
+ SDK versions.
133
+
134
+ Must be called from within an ``except`` block while the task's
135
+ :func:`task_scope` is still active, so the error carries the task name and
136
+ tags. A no-op when ``sentry_sdk`` is unavailable or uninitialised.
137
+ """
138
+ if sentry_sdk and sentry_sdk.is_initialized():
139
+ sentry_sdk.capture_exception()
140
+
141
+
142
+ @contextlib.contextmanager
143
+ def task_scope(*, task_name: str, message_id: str, n_retries: int) -> Iterator[None]:
144
+ """Run the enclosed task body inside a per-task Sentry trace.
145
+
146
+ Opens an isolated scope and traces the task — as a child span of the active
147
+ invocation transaction on Lambda, or as its own root transaction when
148
+ nothing is active (Batch). Records ``lambda_task``, ``lambda_task.message_id``,
149
+ ``lambda_task.n_retries`` and (when derivable) ``lambda_task.runtime`` as
150
+ both span metadata and scope tags, so errors are attributed to the task even
151
+ when the trace is not sampled.
152
+
153
+ The scope is isolated via ``new_scope`` so records processed one after
154
+ another in the same Lambda invocation do not leak their task name into one
155
+ another.
156
+
157
+ A no-op when ``sentry_sdk`` is unavailable or uninitialised.
158
+ """
159
+ if not (sentry_sdk and sentry_sdk.is_initialized()):
160
+ yield
161
+ return
162
+
163
+ runtime = _runtime_environment()
164
+
165
+ with sentry_sdk.new_scope() as scope:
166
+ # Attribute error events and the trace to the task via tags/attributes.
167
+ # We deliberately do NOT call scope.set_transaction_name(task_name):
168
+ # on Lambda the task runs inside the invocation transaction, and
169
+ # renaming it (a) fights across sibling records in a batch and (b) on
170
+ # SDK 3.0 stream mode corrupts the root-segment naming. The
171
+ # ``lambda_task`` tag below is the reliable, version-independent
172
+ # attribution; the task span itself is named after the task.
173
+ scope.set_tag("lambda_task", task_name)
174
+ scope.set_tag("lambda_task.message_id", message_id)
175
+ scope.set_tag("lambda_task.n_retries", n_retries)
176
+ if runtime:
177
+ scope.set_tag("lambda_task.runtime", runtime)
178
+
179
+ with _start_task_root(task_name=task_name) as span:
180
+ _set_metadata(span, key="lambda_task", value=task_name)
181
+ _set_metadata(span, key="lambda_task.message_id", value=message_id)
182
+ _set_metadata(span, key="lambda_task.n_retries", value=n_retries)
183
+ if runtime:
184
+ _set_metadata(span, key="lambda_task.runtime", value=runtime)
185
+
186
+ yield
@@ -7,7 +7,7 @@ packages = ["lambda_tasks"]
7
7
 
8
8
  [project]
9
9
  name = "django-lambda-tasks"
10
- version = "0.11.0"
10
+ version = "0.11.1"
11
11
  description = "Run async tasks in a lambda function"
12
12
  readme = "README.md"
13
13
  requires-python = ">=3.11"
@@ -31,7 +31,7 @@ dev = [
31
31
  "pytest-cov",
32
32
  "ty",
33
33
  "psycopg",
34
- "sentry-sdk",
34
+ "sentry-sdk>=2,<3",
35
35
  ]
36
36
 
37
37
  [tool.ty.rules]