sentry-sdk 2.46.0__tar.gz → 2.48.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 (232) hide show
  1. {sentry_sdk-2.46.0/sentry_sdk.egg-info → sentry_sdk-2.48.0}/PKG-INFO +1 -1
  2. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_compat.py +4 -8
  3. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_init_implementation.py +5 -10
  4. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_log_batcher.py +29 -26
  5. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_lru_cache.py +5 -9
  6. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_metrics_batcher.py +16 -26
  7. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_queue.py +1 -1
  8. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_types.py +10 -15
  9. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_werkzeug.py +2 -4
  10. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/monitoring.py +13 -21
  11. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/utils.py +68 -19
  12. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/api.py +80 -110
  13. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/attachments.py +9 -12
  14. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/client.py +119 -163
  15. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/consts.py +108 -91
  16. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/api.py +15 -17
  17. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/decorator.py +24 -22
  18. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/debug.py +4 -8
  19. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/envelope.py +57 -90
  20. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/feature_flags.py +6 -12
  21. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/hub.py +126 -132
  22. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/__init__.py +28 -34
  23. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/_asgi_common.py +11 -12
  24. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/_wsgi_common.py +26 -39
  25. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/aiohttp.py +32 -30
  26. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/anthropic.py +68 -51
  27. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/argv.py +2 -4
  28. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/ariadne.py +17 -18
  29. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/arq.py +20 -31
  30. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asgi.py +27 -32
  31. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asyncio.py +11 -15
  32. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asyncpg.py +15 -13
  33. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/atexit.py +5 -10
  34. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/aws_lambda.py +31 -43
  35. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/beam.py +11 -19
  36. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/boto3.py +17 -16
  37. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/bottle.py +26 -36
  38. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/__init__.py +40 -58
  39. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/beat.py +23 -26
  40. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/utils.py +4 -8
  41. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/chalice.py +9 -11
  42. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/clickhouse_driver.py +5 -5
  43. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/cloud_resource_context.py +8 -16
  44. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/cohere.py +11 -21
  45. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/dedupe.py +4 -8
  46. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/__init__.py +105 -79
  47. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/asgi.py +32 -33
  48. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/caching.py +28 -21
  49. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/middleware.py +15 -20
  50. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/signals_handlers.py +9 -10
  51. sentry_sdk-2.48.0/sentry_sdk/integrations/django/tasks.py +41 -0
  52. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/templates.py +18 -18
  53. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/transactions.py +12 -12
  54. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/views.py +12 -12
  55. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/dramatiq.py +25 -25
  56. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/excepthook.py +9 -11
  57. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/executing.py +4 -5
  58. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/falcon.py +32 -45
  59. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/fastapi.py +12 -17
  60. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/flask.py +30 -40
  61. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gcp.py +11 -16
  62. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gnu_backtrace.py +3 -6
  63. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/__init__.py +99 -30
  64. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/streaming.py +11 -9
  65. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/utils.py +115 -42
  66. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gql.py +18 -17
  67. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/graphene.py +18 -12
  68. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/__init__.py +24 -22
  69. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/client.py +10 -10
  70. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/server.py +15 -15
  71. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/client.py +16 -7
  72. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/server.py +12 -9
  73. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/httpx.py +10 -12
  74. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/huey.py +14 -22
  75. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/huggingface_hub.py +12 -18
  76. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/langchain.py +147 -117
  77. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/langgraph.py +83 -34
  78. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/launchdarkly.py +12 -10
  79. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/litellm.py +16 -12
  80. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/litestar.py +33 -37
  81. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/logging.py +23 -35
  82. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/loguru.py +15 -19
  83. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/mcp.py +156 -60
  84. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/modules.py +2 -4
  85. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai.py +60 -73
  86. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/__init__.py +4 -8
  87. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/agent_run.py +46 -29
  88. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/error_tracing.py +6 -14
  89. sentry_sdk-2.48.0/sentry_sdk/integrations/openai_agents/patches/models.py +78 -0
  90. sentry_sdk-2.48.0/sentry_sdk/integrations/openai_agents/patches/runner.py +66 -0
  91. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/tools.py +14 -9
  92. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -1
  93. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +1 -3
  94. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/ai_client.py +15 -5
  95. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +10 -5
  96. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/handoff.py +3 -2
  97. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +38 -12
  98. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/utils.py +45 -22
  99. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openfeature.py +5 -6
  100. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/integration.py +3 -6
  101. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/propagator.py +13 -6
  102. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/span_processor.py +33 -40
  103. sentry_sdk-2.48.0/sentry_sdk/integrations/otlp.py +217 -0
  104. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pure_eval.py +7 -11
  105. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/__init__.py +7 -4
  106. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +30 -39
  107. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +7 -10
  108. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/model_request.py +4 -4
  109. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/tools.py +21 -11
  110. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +11 -26
  111. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +4 -4
  112. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +37 -5
  113. sentry_sdk-2.48.0/sentry_sdk/integrations/pydantic_ai/spans/utils.py +35 -0
  114. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/utils.py +16 -22
  115. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pymongo.py +13 -18
  116. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pyramid.py +30 -36
  117. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/quart.py +22 -31
  118. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/ray.py +23 -17
  119. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/__init__.py +6 -4
  120. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/_async_common.py +14 -9
  121. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/_sync_common.py +12 -12
  122. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/caches.py +19 -8
  123. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/queries.py +8 -8
  124. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/rb.py +1 -2
  125. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis.py +2 -4
  126. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis_cluster.py +10 -8
  127. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +1 -2
  128. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/utils.py +16 -19
  129. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/rq.py +13 -14
  130. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/rust_tracing.py +23 -24
  131. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sanic.py +33 -47
  132. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/serverless.py +11 -22
  133. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/socket.py +18 -16
  134. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/spark_driver.py +45 -83
  135. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/spark_worker.py +6 -11
  136. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sqlalchemy.py +28 -17
  137. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/starlette.py +98 -96
  138. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/starlite.py +31 -33
  139. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/statsig.py +4 -4
  140. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/stdlib.py +29 -24
  141. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/strawberry.py +64 -52
  142. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sys_exit.py +6 -11
  143. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/threading.py +23 -20
  144. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/tornado.py +35 -32
  145. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/trytond.py +3 -3
  146. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/typer.py +7 -6
  147. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/unleash.py +4 -4
  148. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/unraisablehook.py +5 -6
  149. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/wsgi.py +42 -52
  150. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/logger.py +8 -9
  151. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/metrics.py +24 -28
  152. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/monitor.py +16 -23
  153. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/continuous_profiler.py +88 -104
  154. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/transaction_profiler.py +73 -103
  155. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/utils.py +10 -16
  156. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/scope.py +273 -350
  157. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/scrubber.py +15 -21
  158. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/serializer.py +41 -56
  159. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/session.py +45 -53
  160. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/sessions.py +30 -36
  161. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/spotlight.py +124 -35
  162. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/tracing.py +200 -251
  163. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/tracing_utils.py +253 -193
  164. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/transport.py +121 -146
  165. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/utils.py +242 -282
  166. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/worker.py +14 -25
  167. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0/sentry_sdk.egg-info}/PKG-INFO +1 -1
  168. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/SOURCES.txt +3 -0
  169. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/setup.py +1 -1
  170. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_ai_integration_deactivation.py +33 -3
  171. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_ai_monitoring.py +29 -2
  172. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_basics.py +2 -2
  173. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_client.py +9 -11
  174. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_dsc.py +54 -7
  175. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_logs.py +89 -12
  176. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_metrics.py +23 -5
  177. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_propagationcontext.py +3 -3
  178. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_scope.py +5 -1
  179. sentry_sdk-2.48.0/tests/test_shadowed_module.py +118 -0
  180. sentry_sdk-2.48.0/tests/test_spotlight.py +123 -0
  181. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_tracing_utils.py +138 -6
  182. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_transport.py +123 -8
  183. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_utils.py +1 -2
  184. sentry_sdk-2.46.0/sentry_sdk/integrations/openai_agents/patches/models.py +0 -50
  185. sentry_sdk-2.46.0/sentry_sdk/integrations/openai_agents/patches/runner.py +0 -45
  186. sentry_sdk-2.46.0/sentry_sdk/integrations/otlp.py +0 -82
  187. sentry_sdk-2.46.0/tests/test_spotlight.py +0 -56
  188. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/LICENSE +0 -0
  189. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/MANIFEST.in +0 -0
  190. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/README.md +0 -0
  191. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/pyproject.toml +0 -0
  192. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/__init__.py +0 -0
  193. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/__init__.py +0 -0
  194. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/__init__.py +0 -0
  195. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/consts.py +0 -0
  196. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/consts.py +0 -0
  197. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/__init__.py +0 -0
  198. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/consts.py +0 -0
  199. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/consts.py +0 -0
  200. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/__init__.py +0 -0
  201. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/__init__.py +0 -0
  202. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/consts.py +0 -0
  203. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/consts.py +0 -0
  204. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/__init__.py +0 -0
  205. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/__init__.py +0 -0
  206. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/consts.py +0 -0
  207. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/__init__.py +0 -0
  208. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/__init__.py +0 -0
  209. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/__init__.py +0 -0
  210. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/py.typed +0 -0
  211. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/types.py +0 -0
  212. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/dependency_links.txt +0 -0
  213. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/entry_points.txt +0 -0
  214. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/not-zip-safe +0 -0
  215. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/requires.txt +0 -0
  216. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/top_level.txt +0 -0
  217. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/setup.cfg +0 -0
  218. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_api.py +0 -0
  219. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_conftest.py +0 -0
  220. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_crons.py +0 -0
  221. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_envelope.py +0 -0
  222. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_exceptiongroup.py +0 -0
  223. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_feature_flags.py +0 -0
  224. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_full_stack_frames.py +0 -0
  225. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_gevent.py +0 -0
  226. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_import.py +0 -0
  227. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_lru_cache.py +0 -0
  228. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_monitor.py +0 -0
  229. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_scrubber.py +0 -0
  230. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_serializer.py +0 -0
  231. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_sessions.py +0 -0
  232. {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry-sdk
3
- Version: 2.46.0
3
+ Version: 2.48.0
4
4
  Summary: Python client for Sentry (https://sentry.io)
5
5
  Home-page: https://github.com/getsentry/sentry-python
6
6
  Author: Sentry Team and Contributors
@@ -15,18 +15,15 @@ PY310 = sys.version_info[0] == 3 and sys.version_info[1] >= 10
15
15
  PY311 = sys.version_info[0] == 3 and sys.version_info[1] >= 11
16
16
 
17
17
 
18
- def with_metaclass(meta, *bases):
19
- # type: (Any, *Any) -> Any
18
+ def with_metaclass(meta: "Any", *bases: "Any") -> "Any":
20
19
  class MetaClass(type):
21
- def __new__(metacls, name, this_bases, d):
22
- # type: (Any, Any, Any, Any) -> Any
20
+ def __new__(metacls: "Any", name: "Any", this_bases: "Any", d: "Any") -> "Any":
23
21
  return meta(name, bases, d)
24
22
 
25
23
  return type.__new__(MetaClass, "temporary_class", (), {})
26
24
 
27
25
 
28
- def check_uwsgi_thread_support():
29
- # type: () -> bool
26
+ def check_uwsgi_thread_support() -> bool:
30
27
  # We check two things here:
31
28
  #
32
29
  # 1. uWSGI doesn't run in threaded mode by default -- issue a warning if
@@ -46,8 +43,7 @@ def check_uwsgi_thread_support():
46
43
 
47
44
  from sentry_sdk.consts import FALSE_VALUES
48
45
 
49
- def enabled(option):
50
- # type: (str) -> bool
46
+ def enabled(option: str) -> bool:
51
47
  value = opt.get(option, False)
52
48
  if isinstance(value, bool):
53
49
  return value
@@ -18,12 +18,10 @@ class _InitGuard:
18
18
  "functionality, and we will remove it in the next major release."
19
19
  )
20
20
 
21
- def __init__(self, client):
22
- # type: (sentry_sdk.Client) -> None
21
+ def __init__(self, client: "sentry_sdk.Client") -> None:
23
22
  self._client = client
24
23
 
25
- def __enter__(self):
26
- # type: () -> _InitGuard
24
+ def __enter__(self) -> "_InitGuard":
27
25
  warnings.warn(
28
26
  self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
29
27
  stacklevel=2,
@@ -32,8 +30,7 @@ class _InitGuard:
32
30
 
33
31
  return self
34
32
 
35
- def __exit__(self, exc_type, exc_value, tb):
36
- # type: (Any, Any, Any) -> None
33
+ def __exit__(self, exc_type: "Any", exc_value: "Any", tb: "Any") -> None:
37
34
  warnings.warn(
38
35
  self._CONTEXT_MANAGER_DEPRECATION_WARNING_MESSAGE,
39
36
  stacklevel=2,
@@ -45,16 +42,14 @@ class _InitGuard:
45
42
  c.close()
46
43
 
47
44
 
48
- def _check_python_deprecations():
49
- # type: () -> None
45
+ def _check_python_deprecations() -> None:
50
46
  # Since we're likely to deprecate Python versions in the future, I'm keeping
51
47
  # this handy function around. Use this to detect the Python version used and
52
48
  # to output logger.warning()s if it's deprecated.
53
49
  pass
54
50
 
55
51
 
56
- def _init(*args, **kwargs):
57
- # type: (*Optional[str], **Any) -> ContextManager[Any]
52
+ def _init(*args: "Optional[str]", **kwargs: "Any") -> "ContextManager[Any]":
58
53
  """Initializes the SDK and optionally integrations.
59
54
 
60
55
  This takes the same arguments as the client constructor.
@@ -18,23 +18,21 @@ class LogBatcher:
18
18
 
19
19
  def __init__(
20
20
  self,
21
- capture_func, # type: Callable[[Envelope], None]
22
- record_lost_func, # type: Callable[..., None]
23
- ):
24
- # type: (...) -> None
25
- self._log_buffer = [] # type: List[Log]
21
+ capture_func: "Callable[[Envelope], None]",
22
+ record_lost_func: "Callable[..., None]",
23
+ ) -> None:
24
+ self._log_buffer: "List[Log]" = []
26
25
  self._capture_func = capture_func
27
26
  self._record_lost_func = record_lost_func
28
27
  self._running = True
29
28
  self._lock = threading.Lock()
30
29
 
31
- self._flush_event = threading.Event() # type: threading.Event
30
+ self._flush_event: "threading.Event" = threading.Event()
32
31
 
33
- self._flusher = None # type: Optional[threading.Thread]
34
- self._flusher_pid = None # type: Optional[int]
32
+ self._flusher: "Optional[threading.Thread]" = None
33
+ self._flusher_pid: "Optional[int]" = None
35
34
 
36
- def _ensure_thread(self):
37
- # type: (...) -> bool
35
+ def _ensure_thread(self) -> bool:
38
36
  """For forking processes we might need to restart this thread.
39
37
  This ensures that our process actually has that thread running.
40
38
  """
@@ -66,8 +64,7 @@ class LogBatcher:
66
64
 
67
65
  return True
68
66
 
69
- def _flush_loop(self):
70
- # type: (...) -> None
67
+ def _flush_loop(self) -> None:
71
68
  while self._running:
72
69
  self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
73
70
  self._flush_event.clear()
@@ -75,17 +72,28 @@ class LogBatcher:
75
72
 
76
73
  def add(
77
74
  self,
78
- log, # type: Log
79
- ):
80
- # type: (...) -> None
75
+ log: "Log",
76
+ ) -> None:
81
77
  if not self._ensure_thread() or self._flusher is None:
82
78
  return None
83
79
 
84
80
  with self._lock:
85
81
  if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_DROP:
82
+ # Construct log envelope item without sending it to report lost bytes
83
+ log_item = Item(
84
+ type="log",
85
+ content_type="application/vnd.sentry.items.log+json",
86
+ headers={
87
+ "item_count": 1,
88
+ },
89
+ payload=PayloadRef(
90
+ json={"items": [LogBatcher._log_to_transport_format(log)]}
91
+ ),
92
+ )
86
93
  self._record_lost_func(
87
94
  reason="queue_overflow",
88
95
  data_category="log_item",
96
+ item=log_item,
89
97
  quantity=1,
90
98
  )
91
99
  return None
@@ -94,8 +102,7 @@ class LogBatcher:
94
102
  if len(self._log_buffer) >= self.MAX_LOGS_BEFORE_FLUSH:
95
103
  self._flush_event.set()
96
104
 
97
- def kill(self):
98
- # type: (...) -> None
105
+ def kill(self) -> None:
99
106
  if self._flusher is None:
100
107
  return
101
108
 
@@ -103,15 +110,12 @@ class LogBatcher:
103
110
  self._flush_event.set()
104
111
  self._flusher = None
105
112
 
106
- def flush(self):
107
- # type: (...) -> None
113
+ def flush(self) -> None:
108
114
  self._flush()
109
115
 
110
116
  @staticmethod
111
- def _log_to_transport_format(log):
112
- # type: (Log) -> Any
113
- def format_attribute(val):
114
- # type: (int | float | str | bool) -> Any
117
+ def _log_to_transport_format(log: "Log") -> "Any":
118
+ def format_attribute(val: "int | float | str | bool") -> "Any":
115
119
  if isinstance(val, bool):
116
120
  return {"value": val, "type": "boolean"}
117
121
  if isinstance(val, int):
@@ -130,6 +134,7 @@ class LogBatcher:
130
134
  res = {
131
135
  "timestamp": int(log["time_unix_nano"]) / 1.0e9,
132
136
  "trace_id": log.get("trace_id", "00000000-0000-0000-0000-000000000000"),
137
+ "span_id": log.get("span_id"),
133
138
  "level": str(log["severity_text"]),
134
139
  "body": str(log["body"]),
135
140
  "attributes": {
@@ -139,9 +144,7 @@ class LogBatcher:
139
144
 
140
145
  return res
141
146
 
142
- def _flush(self):
143
- # type: (...) -> Optional[Envelope]
144
-
147
+ def _flush(self) -> "Optional[Envelope]":
145
148
  envelope = Envelope(
146
149
  headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
147
150
  )
@@ -8,17 +8,15 @@ _SENTINEL = object()
8
8
 
9
9
 
10
10
  class LRUCache:
11
- def __init__(self, max_size):
12
- # type: (int) -> None
11
+ def __init__(self, max_size: int) -> None:
13
12
  if max_size <= 0:
14
13
  raise AssertionError(f"invalid max_size: {max_size}")
15
14
  self.max_size = max_size
16
- self._data = {} # type: dict[Any, Any]
15
+ self._data: "dict[Any, Any]" = {}
17
16
  self.hits = self.misses = 0
18
17
  self.full = False
19
18
 
20
- def set(self, key, value):
21
- # type: (Any, Any) -> None
19
+ def set(self, key: "Any", value: "Any") -> None:
22
20
  current = self._data.pop(key, _SENTINEL)
23
21
  if current is not _SENTINEL:
24
22
  self._data[key] = value
@@ -29,8 +27,7 @@ class LRUCache:
29
27
  self._data[key] = value
30
28
  self.full = len(self._data) >= self.max_size
31
29
 
32
- def get(self, key, default=None):
33
- # type: (Any, Any) -> Any
30
+ def get(self, key: "Any", default: "Any" = None) -> "Any":
34
31
  try:
35
32
  ret = self._data.pop(key)
36
33
  except KeyError:
@@ -42,6 +39,5 @@ class LRUCache:
42
39
 
43
40
  return ret
44
41
 
45
- def get_all(self):
46
- # type: () -> list[tuple[Any, Any]]
42
+ def get_all(self) -> "list[tuple[Any, Any]]":
47
43
  return list(self._data.items())
@@ -18,23 +18,21 @@ class MetricsBatcher:
18
18
 
19
19
  def __init__(
20
20
  self,
21
- capture_func, # type: Callable[[Envelope], None]
22
- record_lost_func, # type: Callable[..., None]
23
- ):
24
- # type: (...) -> None
25
- self._metric_buffer = [] # type: List[Metric]
21
+ capture_func: "Callable[[Envelope], None]",
22
+ record_lost_func: "Callable[..., None]",
23
+ ) -> None:
24
+ self._metric_buffer: "List[Metric]" = []
26
25
  self._capture_func = capture_func
27
26
  self._record_lost_func = record_lost_func
28
27
  self._running = True
29
28
  self._lock = threading.Lock()
30
29
 
31
- self._flush_event = threading.Event() # type: threading.Event
30
+ self._flush_event: "threading.Event" = threading.Event()
32
31
 
33
- self._flusher = None # type: Optional[threading.Thread]
34
- self._flusher_pid = None # type: Optional[int]
32
+ self._flusher: "Optional[threading.Thread]" = None
33
+ self._flusher_pid: "Optional[int]" = None
35
34
 
36
- def _ensure_thread(self):
37
- # type: (...) -> bool
35
+ def _ensure_thread(self) -> bool:
38
36
  if not self._running:
39
37
  return False
40
38
 
@@ -59,8 +57,7 @@ class MetricsBatcher:
59
57
 
60
58
  return True
61
59
 
62
- def _flush_loop(self):
63
- # type: (...) -> None
60
+ def _flush_loop(self) -> None:
64
61
  while self._running:
65
62
  self._flush_event.wait(self.FLUSH_WAIT_TIME + random.random())
66
63
  self._flush_event.clear()
@@ -68,9 +65,8 @@ class MetricsBatcher:
68
65
 
69
66
  def add(
70
67
  self,
71
- metric, # type: Metric
72
- ):
73
- # type: (...) -> None
68
+ metric: "Metric",
69
+ ) -> None:
74
70
  if not self._ensure_thread() or self._flusher is None:
75
71
  return None
76
72
 
@@ -87,8 +83,7 @@ class MetricsBatcher:
87
83
  if len(self._metric_buffer) >= self.MAX_METRICS_BEFORE_FLUSH:
88
84
  self._flush_event.set()
89
85
 
90
- def kill(self):
91
- # type: (...) -> None
86
+ def kill(self) -> None:
92
87
  if self._flusher is None:
93
88
  return
94
89
 
@@ -96,15 +91,12 @@ class MetricsBatcher:
96
91
  self._flush_event.set()
97
92
  self._flusher = None
98
93
 
99
- def flush(self):
100
- # type: (...) -> None
94
+ def flush(self) -> None:
101
95
  self._flush()
102
96
 
103
97
  @staticmethod
104
- def _metric_to_transport_format(metric):
105
- # type: (Metric) -> Any
106
- def format_attribute(val):
107
- # type: (Union[int, float, str, bool]) -> Any
98
+ def _metric_to_transport_format(metric: "Metric") -> "Any":
99
+ def format_attribute(val: "Union[int, float, str, bool]") -> "Any":
108
100
  if isinstance(val, bool):
109
101
  return {"value": val, "type": "boolean"}
110
102
  if isinstance(val, int):
@@ -134,9 +126,7 @@ class MetricsBatcher:
134
126
 
135
127
  return res
136
128
 
137
- def _flush(self):
138
- # type: (...) -> Optional[Envelope]
139
-
129
+ def _flush(self) -> "Optional[Envelope]":
140
130
  envelope = Envelope(
141
131
  headers={"sent_at": format_timestamp(datetime.now(timezone.utc))}
142
132
  )
@@ -275,7 +275,7 @@ class Queue:
275
275
 
276
276
  # Initialize the queue representation
277
277
  def _init(self, maxsize):
278
- self.queue = deque() # type: Any
278
+ self.queue: "Any" = deque()
279
279
 
280
280
  def _qsize(self):
281
281
  return len(self.queue)
@@ -18,32 +18,27 @@ class AnnotatedValue:
18
18
 
19
19
  __slots__ = ("value", "metadata")
20
20
 
21
- def __init__(self, value, metadata):
22
- # type: (Optional[Any], Dict[str, Any]) -> None
21
+ def __init__(self, value: "Optional[Any]", metadata: "Dict[str, Any]") -> None:
23
22
  self.value = value
24
23
  self.metadata = metadata
25
24
 
26
- def __eq__(self, other):
27
- # type: (Any) -> bool
25
+ def __eq__(self, other: "Any") -> bool:
28
26
  if not isinstance(other, AnnotatedValue):
29
27
  return False
30
28
 
31
29
  return self.value == other.value and self.metadata == other.metadata
32
30
 
33
- def __str__(self):
34
- # type: (AnnotatedValue) -> str
31
+ def __str__(self: "AnnotatedValue") -> str:
35
32
  return str({"value": str(self.value), "metadata": str(self.metadata)})
36
33
 
37
- def __len__(self):
38
- # type: (AnnotatedValue) -> int
34
+ def __len__(self: "AnnotatedValue") -> int:
39
35
  if self.value is not None:
40
36
  return len(self.value)
41
37
  else:
42
38
  return 0
43
39
 
44
40
  @classmethod
45
- def removed_because_raw_data(cls):
46
- # type: () -> AnnotatedValue
41
+ def removed_because_raw_data(cls) -> "AnnotatedValue":
47
42
  """The value was removed because it could not be parsed. This is done for request body values that are not json nor a form."""
48
43
  return AnnotatedValue(
49
44
  value="",
@@ -58,8 +53,7 @@ class AnnotatedValue:
58
53
  )
59
54
 
60
55
  @classmethod
61
- def removed_because_over_size_limit(cls, value=""):
62
- # type: (Any) -> AnnotatedValue
56
+ def removed_because_over_size_limit(cls, value: "Any" = "") -> "AnnotatedValue":
63
57
  """
64
58
  The actual value was removed because the size of the field exceeded the configured maximum size,
65
59
  for example specified with the max_request_body_size sdk option.
@@ -77,8 +71,7 @@ class AnnotatedValue:
77
71
  )
78
72
 
79
73
  @classmethod
80
- def substituted_because_contains_sensitive_data(cls):
81
- # type: () -> AnnotatedValue
74
+ def substituted_because_contains_sensitive_data(cls) -> "AnnotatedValue":
82
75
  """The actual value was removed because it contained sensitive information."""
83
76
  return AnnotatedValue(
84
77
  value=SENSITIVE_DATA_SUBSTITUTE,
@@ -116,7 +109,7 @@ if TYPE_CHECKING:
116
109
  class SDKInfo(TypedDict):
117
110
  name: str
118
111
  version: str
119
- packages: Sequence[Mapping[str, str]]
112
+ packages: "Sequence[Mapping[str, str]]"
120
113
 
121
114
  # "critical" is an alias of "fatal" recognized by Relay
122
115
  LogLevelStr = Literal["fatal", "critical", "error", "warning", "info", "debug"]
@@ -231,6 +224,7 @@ if TYPE_CHECKING:
231
224
  "attributes": dict[str, str | bool | float | int],
232
225
  "time_unix_nano": int,
233
226
  "trace_id": Optional[str],
227
+ "span_id": Optional[str],
234
228
  },
235
229
  )
236
230
 
@@ -294,6 +288,7 @@ if TYPE_CHECKING:
294
288
  "monitor",
295
289
  "span",
296
290
  "log_item",
291
+ "log_byte",
297
292
  "trace_metric",
298
293
  ]
299
294
  SessionStatus = Literal["ok", "exited", "crashed", "abnormal"]
@@ -47,8 +47,7 @@ if TYPE_CHECKING:
47
47
  # We need this function because Django does not give us a "pure" http header
48
48
  # dict. So we might as well use it for all WSGI integrations.
49
49
  #
50
- def _get_headers(environ):
51
- # type: (Dict[str, str]) -> Iterator[Tuple[str, str]]
50
+ def _get_headers(environ: "Dict[str, str]") -> "Iterator[Tuple[str, str]]":
52
51
  """
53
52
  Returns only proper HTTP headers.
54
53
  """
@@ -67,8 +66,7 @@ def _get_headers(environ):
67
66
  # `get_host` comes from `werkzeug.wsgi.get_host`
68
67
  # https://github.com/pallets/werkzeug/blob/1.0.1/src/werkzeug/wsgi.py#L145
69
68
  #
70
- def get_host(environ, use_x_forwarded_for=False):
71
- # type: (Dict[str, str], bool) -> str
69
+ def get_host(environ: "Dict[str, str]", use_x_forwarded_for: bool = False) -> str:
72
70
  """
73
71
  Return the host for the given WSGI environment.
74
72
  """
@@ -17,22 +17,17 @@ if TYPE_CHECKING:
17
17
  _ai_pipeline_name = ContextVar("ai_pipeline_name", default=None)
18
18
 
19
19
 
20
- def set_ai_pipeline_name(name):
21
- # type: (Optional[str]) -> None
20
+ def set_ai_pipeline_name(name: "Optional[str]") -> None:
22
21
  _ai_pipeline_name.set(name)
23
22
 
24
23
 
25
- def get_ai_pipeline_name():
26
- # type: () -> Optional[str]
24
+ def get_ai_pipeline_name() -> "Optional[str]":
27
25
  return _ai_pipeline_name.get()
28
26
 
29
27
 
30
- def ai_track(description, **span_kwargs):
31
- # type: (str, Any) -> Callable[[F], F]
32
- def decorator(f):
33
- # type: (F) -> F
34
- def sync_wrapped(*args, **kwargs):
35
- # type: (Any, Any) -> Any
28
+ def ai_track(description: str, **span_kwargs: "Any") -> "Callable[[F], F]":
29
+ def decorator(f: "F") -> "F":
30
+ def sync_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
36
31
  curr_pipeline = _ai_pipeline_name.get()
37
32
  op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
38
33
 
@@ -60,8 +55,7 @@ def ai_track(description, **span_kwargs):
60
55
  _ai_pipeline_name.set(None)
61
56
  return res
62
57
 
63
- async def async_wrapped(*args, **kwargs):
64
- # type: (Any, Any) -> Any
58
+ async def async_wrapped(*args: "Any", **kwargs: "Any") -> "Any":
65
59
  curr_pipeline = _ai_pipeline_name.get()
66
60
  op = span_kwargs.pop("op", "ai.run" if curr_pipeline else "ai.pipeline")
67
61
 
@@ -98,15 +92,13 @@ def ai_track(description, **span_kwargs):
98
92
 
99
93
 
100
94
  def record_token_usage(
101
- span,
102
- input_tokens=None,
103
- input_tokens_cached=None,
104
- output_tokens=None,
105
- output_tokens_reasoning=None,
106
- total_tokens=None,
107
- ):
108
- # type: (Span, Optional[int], Optional[int], Optional[int], Optional[int], Optional[int]) -> None
109
-
95
+ span: "Span",
96
+ input_tokens: "Optional[int]" = None,
97
+ input_tokens_cached: "Optional[int]" = None,
98
+ output_tokens: "Optional[int]" = None,
99
+ output_tokens_reasoning: "Optional[int]" = None,
100
+ total_tokens: "Optional[int]" = None,
101
+ ) -> None:
110
102
  # TODO: move pipeline name elsewhere
111
103
  ai_pipeline_name = get_ai_pipeline_name()
112
104
  if ai_pipeline_name:
@@ -1,7 +1,9 @@
1
+ import inspect
1
2
  import json
2
3
  from collections import deque
3
- from typing import TYPE_CHECKING
4
+ from copy import deepcopy
4
5
  from sys import getsizeof
6
+ from typing import TYPE_CHECKING
5
7
 
6
8
  if TYPE_CHECKING:
7
9
  from typing import Any, Callable, Dict, List, Optional, Tuple
@@ -12,6 +14,8 @@ import sentry_sdk
12
14
  from sentry_sdk.utils import logger
13
15
 
14
16
  MAX_GEN_AI_MESSAGE_BYTES = 20_000 # 20KB
17
+ # Maximum characters when only a single message is left after bytes truncation
18
+ MAX_SINGLE_MESSAGE_CONTENT_CHARS = 10_000
15
19
 
16
20
 
17
21
  class GEN_AI_ALLOWED_MESSAGE_ROLES:
@@ -34,10 +38,14 @@ for target_role, source_roles in GEN_AI_MESSAGE_ROLE_REVERSE_MAPPING.items():
34
38
  GEN_AI_MESSAGE_ROLE_MAPPING[source_role] = target_role
35
39
 
36
40
 
37
- def _normalize_data(data, unpack=True):
38
- # type: (Any, bool) -> Any
41
+ def _normalize_data(data: "Any", unpack: bool = True) -> "Any":
39
42
  # convert pydantic data (e.g. OpenAI v1+) to json compatible format
40
43
  if hasattr(data, "model_dump"):
44
+ # Check if it's a class (type) rather than an instance
45
+ # Model classes can be passed as arguments (e.g., for schema definitions)
46
+ if inspect.isclass(data):
47
+ return f"<ClassType: {data.__name__}>"
48
+
41
49
  try:
42
50
  return _normalize_data(data.model_dump(), unpack=unpack)
43
51
  except Exception as e:
@@ -55,8 +63,9 @@ def _normalize_data(data, unpack=True):
55
63
  return data if isinstance(data, (int, float, bool, str)) else str(data)
56
64
 
57
65
 
58
- def set_data_normalized(span, key, value, unpack=True):
59
- # type: (Span, str, Any, bool) -> None
66
+ def set_data_normalized(
67
+ span: "Span", key: str, value: "Any", unpack: bool = True
68
+ ) -> None:
60
69
  normalized = _normalize_data(value, unpack=unpack)
61
70
  if isinstance(normalized, (int, float, bool, str)):
62
71
  span.set_data(key, normalized)
@@ -64,8 +73,7 @@ def set_data_normalized(span, key, value, unpack=True):
64
73
  span.set_data(key, json.dumps(normalized))
65
74
 
66
75
 
67
- def normalize_message_role(role):
68
- # type: (str) -> str
76
+ def normalize_message_role(role: str) -> str:
69
77
  """
70
78
  Normalize a message role to one of the 4 allowed gen_ai role values.
71
79
  Maps "ai" -> "assistant" and keeps other standard roles unchanged.
@@ -73,8 +81,7 @@ def normalize_message_role(role):
73
81
  return GEN_AI_MESSAGE_ROLE_MAPPING.get(role, role)
74
82
 
75
83
 
76
- def normalize_message_roles(messages):
77
- # type: (list[dict[str, Any]]) -> list[dict[str, Any]]
84
+ def normalize_message_roles(messages: "list[dict[str, Any]]") -> "list[dict[str, Any]]":
78
85
  """
79
86
  Normalize roles in a list of messages to use standard gen_ai role values.
80
87
  Creates a deep copy to avoid modifying the original messages.
@@ -92,8 +99,7 @@ def normalize_message_roles(messages):
92
99
  return normalized_messages
93
100
 
94
101
 
95
- def get_start_span_function():
96
- # type: () -> Callable[..., Any]
102
+ def get_start_span_function() -> "Callable[..., Any]":
97
103
  current_span = sentry_sdk.get_current_span()
98
104
  transaction_exists = (
99
105
  current_span is not None and current_span.containing_transaction is not None
@@ -101,8 +107,25 @@ def get_start_span_function():
101
107
  return sentry_sdk.start_span if transaction_exists else sentry_sdk.start_transaction
102
108
 
103
109
 
104
- def _find_truncation_index(messages, max_bytes):
105
- # type: (List[Dict[str, Any]], int) -> int
110
+ def _truncate_single_message_content_if_present(
111
+ message: "Dict[str, Any]", max_chars: int
112
+ ) -> "Dict[str, Any]":
113
+ """
114
+ Truncate a message's content to at most `max_chars` characters and append an
115
+ ellipsis if truncation occurs.
116
+ """
117
+ if not isinstance(message, dict) or "content" not in message:
118
+ return message
119
+ content = message["content"]
120
+
121
+ if not isinstance(content, str) or len(content) <= max_chars:
122
+ return message
123
+
124
+ message["content"] = content[:max_chars] + "..."
125
+ return message
126
+
127
+
128
+ def _find_truncation_index(messages: "List[Dict[str, Any]]", max_bytes: int) -> int:
106
129
  """
107
130
  Find the index of the first message that would exceed the max bytes limit.
108
131
  Compute the individual message sizes, and return the index of the first message from the back
@@ -118,8 +141,21 @@ def _find_truncation_index(messages, max_bytes):
118
141
  return 0
119
142
 
120
143
 
121
- def truncate_messages_by_size(messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES):
122
- # type: (List[Dict[str, Any]], int) -> Tuple[List[Dict[str, Any]], int]
144
+ def truncate_messages_by_size(
145
+ messages: "List[Dict[str, Any]]",
146
+ max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES,
147
+ max_single_message_chars: int = MAX_SINGLE_MESSAGE_CONTENT_CHARS,
148
+ ) -> "Tuple[List[Dict[str, Any]], int]":
149
+ """
150
+ Returns a truncated messages list, consisting of
151
+ - the last message, with its content truncated to `max_single_message_chars` characters,
152
+ if the last message's size exceeds `max_bytes` bytes; otherwise,
153
+ - the maximum number of messages, starting from the end of the `messages` list, whose total
154
+ serialized size does not exceed `max_bytes` bytes.
155
+
156
+ In the single message case, the serialized message size may exceed `max_bytes`, because
157
+ truncation is based only on character count in that case.
158
+ """
123
159
  serialized_json = json.dumps(messages, separators=(",", ":"))
124
160
  current_size = len(serialized_json.encode("utf-8"))
125
161
 
@@ -127,13 +163,26 @@ def truncate_messages_by_size(messages, max_bytes=MAX_GEN_AI_MESSAGE_BYTES):
127
163
  return messages, 0
128
164
 
129
165
  truncation_index = _find_truncation_index(messages, max_bytes)
130
- return messages[truncation_index:], truncation_index
166
+ if truncation_index < len(messages):
167
+ truncated_messages = messages[truncation_index:]
168
+ else:
169
+ truncation_index = len(messages) - 1
170
+ truncated_messages = messages[-1:]
171
+
172
+ if len(truncated_messages) == 1:
173
+ truncated_messages[0] = _truncate_single_message_content_if_present(
174
+ deepcopy(truncated_messages[0]), max_chars=max_single_message_chars
175
+ )
176
+
177
+ return truncated_messages, truncation_index
131
178
 
132
179
 
133
180
  def truncate_and_annotate_messages(
134
- messages, span, scope, max_bytes=MAX_GEN_AI_MESSAGE_BYTES
135
- ):
136
- # type: (Optional[List[Dict[str, Any]]], Any, Any, int) -> Optional[List[Dict[str, Any]]]
181
+ messages: "Optional[List[Dict[str, Any]]]",
182
+ span: "Any",
183
+ scope: "Any",
184
+ max_bytes: int = MAX_GEN_AI_MESSAGE_BYTES,
185
+ ) -> "Optional[List[Dict[str, Any]]]":
137
186
  if not messages:
138
187
  return None
139
188