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.
- {sentry_sdk-2.46.0/sentry_sdk.egg-info → sentry_sdk-2.48.0}/PKG-INFO +1 -1
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_compat.py +4 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_init_implementation.py +5 -10
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_log_batcher.py +29 -26
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_lru_cache.py +5 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_metrics_batcher.py +16 -26
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_queue.py +1 -1
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_types.py +10 -15
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/_werkzeug.py +2 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/monitoring.py +13 -21
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/utils.py +68 -19
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/api.py +80 -110
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/attachments.py +9 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/client.py +119 -163
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/consts.py +108 -91
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/api.py +15 -17
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/decorator.py +24 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/debug.py +4 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/envelope.py +57 -90
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/feature_flags.py +6 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/hub.py +126 -132
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/__init__.py +28 -34
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/_asgi_common.py +11 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/_wsgi_common.py +26 -39
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/aiohttp.py +32 -30
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/anthropic.py +68 -51
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/argv.py +2 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/ariadne.py +17 -18
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/arq.py +20 -31
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asgi.py +27 -32
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asyncio.py +11 -15
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/asyncpg.py +15 -13
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/atexit.py +5 -10
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/aws_lambda.py +31 -43
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/beam.py +11 -19
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/boto3.py +17 -16
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/bottle.py +26 -36
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/__init__.py +40 -58
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/beat.py +23 -26
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/celery/utils.py +4 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/chalice.py +9 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/clickhouse_driver.py +5 -5
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/cloud_resource_context.py +8 -16
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/cohere.py +11 -21
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/dedupe.py +4 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/__init__.py +105 -79
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/asgi.py +32 -33
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/caching.py +28 -21
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/middleware.py +15 -20
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/signals_handlers.py +9 -10
- sentry_sdk-2.48.0/sentry_sdk/integrations/django/tasks.py +41 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/templates.py +18 -18
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/transactions.py +12 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/django/views.py +12 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/dramatiq.py +25 -25
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/excepthook.py +9 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/executing.py +4 -5
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/falcon.py +32 -45
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/fastapi.py +12 -17
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/flask.py +30 -40
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gcp.py +11 -16
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gnu_backtrace.py +3 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/__init__.py +99 -30
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/streaming.py +11 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/utils.py +115 -42
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/gql.py +18 -17
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/graphene.py +18 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/__init__.py +24 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/client.py +10 -10
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/server.py +15 -15
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/client.py +16 -7
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/server.py +12 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/httpx.py +10 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/huey.py +14 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/huggingface_hub.py +12 -18
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/langchain.py +147 -117
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/langgraph.py +83 -34
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/launchdarkly.py +12 -10
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/litellm.py +16 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/litestar.py +33 -37
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/logging.py +23 -35
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/loguru.py +15 -19
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/mcp.py +156 -60
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/modules.py +2 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai.py +60 -73
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/__init__.py +4 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/agent_run.py +46 -29
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/error_tracing.py +6 -14
- sentry_sdk-2.48.0/sentry_sdk/integrations/openai_agents/patches/models.py +78 -0
- sentry_sdk-2.48.0/sentry_sdk/integrations/openai_agents/patches/runner.py +66 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/tools.py +14 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/__init__.py +5 -1
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/agent_workflow.py +1 -3
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/ai_client.py +15 -5
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/execute_tool.py +10 -5
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/handoff.py +3 -2
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/spans/invoke_agent.py +38 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/utils.py +45 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openfeature.py +5 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/integration.py +3 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/propagator.py +13 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/span_processor.py +33 -40
- sentry_sdk-2.48.0/sentry_sdk/integrations/otlp.py +217 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pure_eval.py +7 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/__init__.py +7 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/agent_run.py +30 -39
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/graph_nodes.py +7 -10
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/model_request.py +4 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/tools.py +21 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/ai_client.py +11 -26
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/execute_tool.py +4 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/invoke_agent.py +37 -5
- sentry_sdk-2.48.0/sentry_sdk/integrations/pydantic_ai/spans/utils.py +35 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/utils.py +16 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pymongo.py +13 -18
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pyramid.py +30 -36
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/quart.py +22 -31
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/ray.py +23 -17
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/__init__.py +6 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/_async_common.py +14 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/_sync_common.py +12 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/caches.py +19 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/queries.py +8 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/rb.py +1 -2
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis.py +2 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis_cluster.py +10 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/redis_py_cluster_legacy.py +1 -2
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/utils.py +16 -19
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/rq.py +13 -14
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/rust_tracing.py +23 -24
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sanic.py +33 -47
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/serverless.py +11 -22
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/socket.py +18 -16
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/spark_driver.py +45 -83
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/spark_worker.py +6 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sqlalchemy.py +28 -17
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/starlette.py +98 -96
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/starlite.py +31 -33
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/statsig.py +4 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/stdlib.py +29 -24
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/strawberry.py +64 -52
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/sys_exit.py +6 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/threading.py +23 -20
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/tornado.py +35 -32
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/trytond.py +3 -3
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/typer.py +7 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/unleash.py +4 -4
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/unraisablehook.py +5 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/wsgi.py +42 -52
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/logger.py +8 -9
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/metrics.py +24 -28
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/monitor.py +16 -23
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/continuous_profiler.py +88 -104
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/transaction_profiler.py +73 -103
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/utils.py +10 -16
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/scope.py +273 -350
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/scrubber.py +15 -21
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/serializer.py +41 -56
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/session.py +45 -53
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/sessions.py +30 -36
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/spotlight.py +124 -35
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/tracing.py +200 -251
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/tracing_utils.py +253 -193
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/transport.py +121 -146
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/utils.py +242 -282
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/worker.py +14 -25
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0/sentry_sdk.egg-info}/PKG-INFO +1 -1
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/SOURCES.txt +3 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/setup.py +1 -1
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_ai_integration_deactivation.py +33 -3
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_ai_monitoring.py +29 -2
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_basics.py +2 -2
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_client.py +9 -11
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_dsc.py +54 -7
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_logs.py +89 -12
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_metrics.py +23 -5
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_propagationcontext.py +3 -3
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_scope.py +5 -1
- sentry_sdk-2.48.0/tests/test_shadowed_module.py +118 -0
- sentry_sdk-2.48.0/tests/test_spotlight.py +123 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_tracing_utils.py +138 -6
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_transport.py +123 -8
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_utils.py +1 -2
- sentry_sdk-2.46.0/sentry_sdk/integrations/openai_agents/patches/models.py +0 -50
- sentry_sdk-2.46.0/sentry_sdk/integrations/openai_agents/patches/runner.py +0 -45
- sentry_sdk-2.46.0/sentry_sdk/integrations/otlp.py +0 -82
- sentry_sdk-2.46.0/tests/test_spotlight.py +0 -56
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/LICENSE +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/MANIFEST.in +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/README.md +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/pyproject.toml +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/ai/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/crons/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/google_genai/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/aio/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/grpc/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/openai_agents/patches/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/opentelemetry/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/patches/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/pydantic_ai/spans/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/consts.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/redis/modules/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/integrations/spark/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/profiler/__init__.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/py.typed +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk/types.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/dependency_links.txt +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/entry_points.txt +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/not-zip-safe +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/requires.txt +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/sentry_sdk.egg-info/top_level.txt +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/setup.cfg +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_api.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_conftest.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_crons.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_envelope.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_exceptiongroup.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_feature_flags.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_full_stack_frames.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_gevent.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_import.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_lru_cache.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_monitor.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_scrubber.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_serializer.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_sessions.py +0 -0
- {sentry_sdk-2.46.0 → sentry_sdk-2.48.0}/tests/test_types.py +0 -0
|
@@ -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
|
|
22
|
-
record_lost_func
|
|
23
|
-
):
|
|
24
|
-
|
|
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
|
|
30
|
+
self._flush_event: "threading.Event" = threading.Event()
|
|
32
31
|
|
|
33
|
-
self._flusher
|
|
34
|
-
self._flusher_pid
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
22
|
-
record_lost_func
|
|
23
|
-
):
|
|
24
|
-
|
|
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
|
|
30
|
+
self._flush_event: "threading.Event" = threading.Event()
|
|
32
31
|
|
|
33
|
-
self._flusher
|
|
34
|
-
self._flusher_pid
|
|
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
|
|
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
|
-
|
|
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
|
)
|
|
@@ -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
|
-
|
|
32
|
-
|
|
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
|
|
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(
|
|
59
|
-
|
|
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
|
|
105
|
-
|
|
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(
|
|
122
|
-
|
|
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
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|