From 3a43dc9351b38079290851d13c625e39b96a65cc Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 25 Sep 2025 12:29:00 +0200 Subject: [PATCH 01/20] feat(toxgen): Generate `TESTPATH` for integrated test suites (#4863) ### Description Make toxgen generate the `TESTPATH` env var for integrated test suites, removing a manual step when adding a new test suite. #### Issues Closes https://github.com/getsentry/sentry-python/issues/4536 #### Reminders - Please add tests to validate your changes, and lint your code using `tox -e linters`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) --- scripts/populate_tox/README.md | 3 +- scripts/populate_tox/populate_tox.py | 18 ++++++++- scripts/populate_tox/releases.jsonl | 2 +- scripts/populate_tox/tox.jinja | 59 +++------------------------- tox.ini | 25 ++++++------ 5 files changed, 39 insertions(+), 68 deletions(-) diff --git a/scripts/populate_tox/README.md b/scripts/populate_tox/README.md index 68e72a8ca7..9bdb3567b8 100644 --- a/scripts/populate_tox/README.md +++ b/scripts/populate_tox/README.md @@ -207,5 +207,4 @@ picked (this doesn't count towards `num_versions`). "Defining constraints" section for the format. 3. Add the integration to one of the groups in the `GROUPS` dictionary in `scripts/split_tox_gh_actions/split_tox_gh_actions.py`. -4. Add the `TESTPATH` for the test suite in `tox.jinja`'s `setenv` section. -5. Run `scripts/generate-test-files.sh` and commit the changes. +4. Run `scripts/generate-test-files.sh` and commit the changes. diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index f13fa6d002..4bb4d78231 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -513,7 +513,11 @@ def _render_dependencies(integration: str, releases: list[Version]) -> list[str] def write_tox_file(packages: dict) -> None: template = ENV.get_template("tox.jinja") - context = {"groups": {}} + context = { + "groups": {}, + "testpaths": [], + } + for group, integrations in packages.items(): context["groups"][group] = [] for integration in integrations: @@ -528,6 +532,14 @@ def write_tox_file(packages: dict) -> None: ), } ) + context["testpaths"].append( + ( + integration["name"], + f"tests/integrations/{integration['integration_name']}", + ) + ) + + context["testpaths"].sort() rendered = template.render(context) @@ -759,6 +771,10 @@ def main(fail_on_changes: bool = False) -> None: "package": package, "extra": extra, "releases": test_releases, + "integration_name": TEST_SUITE_CONFIG[integration].get( + "integration_name" + ) + or integration, } ) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index 96eb2da8e0..c68fe87734 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.37", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.38", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index 039b93c42e..39d2406b84 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -136,69 +136,22 @@ setenv = django: DJANGO_SETTINGS_MODULE=tests.integrations.django.myapp.settings spark-v{3.0.3,3.5.6}: JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 + # TESTPATH definitions for test suites not managed by toxgen common: TESTPATH=tests gevent: TESTPATH=tests - aiohttp: TESTPATH=tests/integrations/aiohttp - anthropic: TESTPATH=tests/integrations/anthropic - ariadne: TESTPATH=tests/integrations/ariadne - arq: TESTPATH=tests/integrations/arq asgi: TESTPATH=tests/integrations/asgi - asyncpg: TESTPATH=tests/integrations/asyncpg aws_lambda: TESTPATH=tests/integrations/aws_lambda - beam: TESTPATH=tests/integrations/beam - boto3: TESTPATH=tests/integrations/boto3 - bottle: TESTPATH=tests/integrations/bottle - celery: TESTPATH=tests/integrations/celery - chalice: TESTPATH=tests/integrations/chalice - clickhouse_driver: TESTPATH=tests/integrations/clickhouse_driver - cohere: TESTPATH=tests/integrations/cohere cloud_resource_context: TESTPATH=tests/integrations/cloud_resource_context - django: TESTPATH=tests/integrations/django - dramatiq: TESTPATH=tests/integrations/dramatiq - falcon: TESTPATH=tests/integrations/falcon - fastapi: TESTPATH=tests/integrations/fastapi - flask: TESTPATH=tests/integrations/flask gcp: TESTPATH=tests/integrations/gcp - gql: TESTPATH=tests/integrations/gql - graphene: TESTPATH=tests/integrations/graphene - grpc: TESTPATH=tests/integrations/grpc - httpx: TESTPATH=tests/integrations/httpx - huey: TESTPATH=tests/integrations/huey - huggingface_hub: TESTPATH=tests/integrations/huggingface_hub - langchain-base: TESTPATH=tests/integrations/langchain - langchain-notiktoken: TESTPATH=tests/integrations/langchain - langgraph: TESTPATH=tests/integrations/langgraph - launchdarkly: TESTPATH=tests/integrations/launchdarkly - litestar: TESTPATH=tests/integrations/litestar - loguru: TESTPATH=tests/integrations/loguru - openai-base: TESTPATH=tests/integrations/openai - openai-notiktoken: TESTPATH=tests/integrations/openai - openai_agents: TESTPATH=tests/integrations/openai_agents - openfeature: TESTPATH=tests/integrations/openfeature opentelemetry: TESTPATH=tests/integrations/opentelemetry potel: TESTPATH=tests/integrations/opentelemetry - pure_eval: TESTPATH=tests/integrations/pure_eval - pymongo: TESTPATH=tests/integrations/pymongo - pyramid: TESTPATH=tests/integrations/pyramid - quart: TESTPATH=tests/integrations/quart - ray: TESTPATH=tests/integrations/ray - redis: TESTPATH=tests/integrations/redis - redis_py_cluster_legacy: TESTPATH=tests/integrations/redis_py_cluster_legacy - requests: TESTPATH=tests/integrations/requests - rq: TESTPATH=tests/integrations/rq - sanic: TESTPATH=tests/integrations/sanic - spark: TESTPATH=tests/integrations/spark - sqlalchemy: TESTPATH=tests/integrations/sqlalchemy - starlette: TESTPATH=tests/integrations/starlette - starlite: TESTPATH=tests/integrations/starlite - statsig: TESTPATH=tests/integrations/statsig - strawberry: TESTPATH=tests/integrations/strawberry - tornado: TESTPATH=tests/integrations/tornado - trytond: TESTPATH=tests/integrations/trytond - typer: TESTPATH=tests/integrations/typer - unleash: TESTPATH=tests/integrations/unleash socket: TESTPATH=tests/integrations/socket + # These TESTPATH definitions are auto-generated by toxgen + {% for integration, testpath in testpaths %} + {{ integration }}: TESTPATH={{ testpath }} + {% endfor %} + passenv = SENTRY_PYTHON_TEST_POSTGRES_HOST SENTRY_PYTHON_TEST_POSTGRES_USER diff --git a/tox.ini b/tox.ini index 8e4c6e5a36..b993397389 100644 --- a/tox.ini +++ b/tox.ini @@ -91,7 +91,7 @@ envlist = {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.37 + {py3.9,py3.12,py3.13}-boto3-v1.40.38 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -396,7 +396,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.37: boto3==1.40.37 + boto3-v1.40.38: boto3==1.40.38 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -713,15 +713,23 @@ setenv = django: DJANGO_SETTINGS_MODULE=tests.integrations.django.myapp.settings spark-v{3.0.3,3.5.6}: JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 + # TESTPATH definitions for test suites not managed by toxgen common: TESTPATH=tests gevent: TESTPATH=tests + asgi: TESTPATH=tests/integrations/asgi + aws_lambda: TESTPATH=tests/integrations/aws_lambda + cloud_resource_context: TESTPATH=tests/integrations/cloud_resource_context + gcp: TESTPATH=tests/integrations/gcp + opentelemetry: TESTPATH=tests/integrations/opentelemetry + potel: TESTPATH=tests/integrations/opentelemetry + socket: TESTPATH=tests/integrations/socket + + # These TESTPATH definitions are auto-generated by toxgen aiohttp: TESTPATH=tests/integrations/aiohttp anthropic: TESTPATH=tests/integrations/anthropic ariadne: TESTPATH=tests/integrations/ariadne arq: TESTPATH=tests/integrations/arq - asgi: TESTPATH=tests/integrations/asgi asyncpg: TESTPATH=tests/integrations/asyncpg - aws_lambda: TESTPATH=tests/integrations/aws_lambda beam: TESTPATH=tests/integrations/beam boto3: TESTPATH=tests/integrations/boto3 bottle: TESTPATH=tests/integrations/bottle @@ -729,13 +737,11 @@ setenv = chalice: TESTPATH=tests/integrations/chalice clickhouse_driver: TESTPATH=tests/integrations/clickhouse_driver cohere: TESTPATH=tests/integrations/cohere - cloud_resource_context: TESTPATH=tests/integrations/cloud_resource_context django: TESTPATH=tests/integrations/django dramatiq: TESTPATH=tests/integrations/dramatiq falcon: TESTPATH=tests/integrations/falcon - fastapi: TESTPATH=tests/integrations/fastapi + fastapi: TESTPATH=tests/integrations/fastapi flask: TESTPATH=tests/integrations/flask - gcp: TESTPATH=tests/integrations/gcp gql: TESTPATH=tests/integrations/gql graphene: TESTPATH=tests/integrations/graphene grpc: TESTPATH=tests/integrations/grpc @@ -744,7 +750,7 @@ setenv = huggingface_hub: TESTPATH=tests/integrations/huggingface_hub langchain-base: TESTPATH=tests/integrations/langchain langchain-notiktoken: TESTPATH=tests/integrations/langchain - langgraph: TESTPATH=tests/integrations/langgraph + langgraph: TESTPATH=tests/integrations/langgraph launchdarkly: TESTPATH=tests/integrations/launchdarkly litestar: TESTPATH=tests/integrations/litestar loguru: TESTPATH=tests/integrations/loguru @@ -752,8 +758,6 @@ setenv = openai-notiktoken: TESTPATH=tests/integrations/openai openai_agents: TESTPATH=tests/integrations/openai_agents openfeature: TESTPATH=tests/integrations/openfeature - opentelemetry: TESTPATH=tests/integrations/opentelemetry - potel: TESTPATH=tests/integrations/opentelemetry pure_eval: TESTPATH=tests/integrations/pure_eval pymongo: TESTPATH=tests/integrations/pymongo pyramid: TESTPATH=tests/integrations/pyramid @@ -774,7 +778,6 @@ setenv = trytond: TESTPATH=tests/integrations/trytond typer: TESTPATH=tests/integrations/typer unleash: TESTPATH=tests/integrations/unleash - socket: TESTPATH=tests/integrations/socket passenv = SENTRY_PYTHON_TEST_POSTGRES_HOST From ceefa6902703dcaedb3454bb0894ae7b3dd1da3e Mon Sep 17 00:00:00 2001 From: Anton Pirker Date: Fri, 26 Sep 2025 12:17:09 +0200 Subject: [PATCH 02/20] ci: Replace `black` and `flake8` with `ruff`. (#4866) ### Description Replace `black` and `flake8` with `ruff`. With this change also reformat the code base (only minor changes, because the configuration is basically the same.) The commits for the reformatting are not included in `git blame` calls (see `.git-blame-ignore-revs`). Also updates the `pre-commit` config so we have exactly the same linting/formatting in CI and local dev. We now have: - Less dependencies - Less configuration - Faster #### Issues * resolves: #2527 --- .coveragerc36 | 4 +- .cursor/rules/core-architecture.mdc | 4 +- .cursor/rules/integrations-guide.mdc | 8 +- .cursor/rules/project-overview.mdc | 4 +- .cursor/rules/quick-reference.mdc | 6 +- .cursor/rules/testing-guide.mdc | 8 +- .git-blame-ignore-revs | 3 + .pre-commit-config.yaml | 32 ++------ codecov.yml | 4 +- pyproject.toml | 79 ++++++++++--------- requirements-linting.txt | 6 +- scripts/build_aws_lambda_layer.py | 3 +- scripts/populate_tox/populate_tox.py | 8 +- scripts/populate_tox/releases.jsonl | 4 +- scripts/populate_tox/tox.jinja | 12 ++- scripts/ready_yet/main.py | 2 +- scripts/ready_yet/run.sh | 4 +- scripts/test-lambda-locally/README.md | 16 ++-- .../deploy-lambda-locally.sh | 4 +- .../test-lambda-locally/lambda_function.py | 7 +- sentry_sdk/client.py | 12 +-- sentry_sdk/consts.py | 1 - sentry_sdk/envelope.py | 42 ++++++---- sentry_sdk/feature_flags.py | 1 - sentry_sdk/hub.py | 26 +++--- sentry_sdk/integrations/asgi.py | 5 +- sentry_sdk/integrations/grpc/aio/client.py | 3 +- sentry_sdk/integrations/grpc/client.py | 7 +- sentry_sdk/integrations/langchain.py | 1 - sentry_sdk/integrations/launchdarkly.py | 1 - sentry_sdk/integrations/litestar.py | 4 +- sentry_sdk/integrations/pure_eval.py | 4 +- sentry_sdk/integrations/spark/spark_driver.py | 3 +- sentry_sdk/integrations/sqlalchemy.py | 8 +- sentry_sdk/integrations/starlette.py | 4 +- sentry_sdk/integrations/starlite.py | 4 +- sentry_sdk/integrations/wsgi.py | 5 +- sentry_sdk/metrics.py | 28 ++++--- sentry_sdk/profiler/utils.py | 8 +- sentry_sdk/scope.py | 9 ++- sentry_sdk/serializer.py | 4 +- sentry_sdk/session.py | 6 +- sentry_sdk/sessions.py | 6 +- sentry_sdk/tracing.py | 6 +- sentry_sdk/tracing_utils.py | 4 +- sentry_sdk/transport.py | 17 ++-- sentry_sdk/utils.py | 8 +- tests/conftest.py | 1 - tests/integrations/asyncio/test_asyncio.py | 3 +- .../RaiseErrorPerformanceDisabled/.gitignore | 4 +- .../RaiseErrorPerformanceEnabled/.gitignore | 4 +- .../TracesSampler/.gitignore | 4 +- tests/integrations/beam/test_beam.py | 8 +- tests/integrations/celery/test_celery.py | 10 ++- tests/integrations/django/asgi/test_asgi.py | 4 +- .../integrations/django/test_cache_module.py | 18 ++--- .../excepthook/test_excepthook.py | 8 +- tests/integrations/fastapi/test_fastapi.py | 18 ++--- tests/integrations/flask/test_flask.py | 12 +-- tests/integrations/gcp/test_gcp.py | 7 +- tests/integrations/gql/test_gql.py | 24 +++--- tests/integrations/httpx/test_httpx.py | 3 +- .../integrations/langchain/test_langchain.py | 18 ++--- .../integrations/langgraph/test_langgraph.py | 5 -- .../opentelemetry/test_span_processor.py | 12 ++- tests/integrations/rq/test_rq.py | 12 ++- tests/integrations/spark/test_spark.py | 1 - tests/integrations/sys_exit/test_sys_exit.py | 6 +- .../integrations/threading/test_threading.py | 6 +- tests/integrations/unleash/testutils.py | 1 - .../unraisablehook/test_unraisablehook.py | 4 +- tests/integrations/wsgi/test_wsgi.py | 9 ++- tests/test_basics.py | 6 +- tests/test_client.py | 10 +-- tests/test_conftest.py | 8 +- tests/test_gevent.py | 6 +- tests/test_propagationcontext.py | 3 +- tests/test_transport.py | 6 +- tests/test_utils.py | 6 +- tests/tracing/test_sampling.py | 7 +- tox.ini | 20 +++-- 81 files changed, 358 insertions(+), 361 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.coveragerc36 b/.coveragerc36 index 8642882ab1..e0c19bb634 100644 --- a/.coveragerc36 +++ b/.coveragerc36 @@ -3,12 +3,12 @@ [run] branch = true -omit = +omit = /tmp/* */tests/* */.venv/* [report] -exclude_lines = +exclude_lines = if TYPE_CHECKING: diff --git a/.cursor/rules/core-architecture.mdc b/.cursor/rules/core-architecture.mdc index 885773f16d..0af65f4815 100644 --- a/.cursor/rules/core-architecture.mdc +++ b/.cursor/rules/core-architecture.mdc @@ -1,6 +1,6 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- # Core Architecture diff --git a/.cursor/rules/integrations-guide.mdc b/.cursor/rules/integrations-guide.mdc index 869a7f742a..6785b1522d 100644 --- a/.cursor/rules/integrations-guide.mdc +++ b/.cursor/rules/integrations-guide.mdc @@ -1,6 +1,6 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- # Integrations Guide @@ -133,10 +133,10 @@ from sentry_sdk.integrations import Integration class MyIntegration(Integration): identifier = "my_integration" - + def __init__(self, param=None): self.param = param - + @staticmethod def setup_once(): # Install hooks, monkey patches, etc. diff --git a/.cursor/rules/project-overview.mdc b/.cursor/rules/project-overview.mdc index 13fad83ae7..c8c0cf77b9 100644 --- a/.cursor/rules/project-overview.mdc +++ b/.cursor/rules/project-overview.mdc @@ -1,6 +1,6 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- # Sentry Python SDK - Project Overview diff --git a/.cursor/rules/quick-reference.mdc b/.cursor/rules/quick-reference.mdc index 453869fa83..ef90c22f78 100644 --- a/.cursor/rules/quick-reference.mdc +++ b/.cursor/rules/quick-reference.mdc @@ -1,6 +1,6 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- # Quick Reference @@ -44,7 +44,7 @@ tox -e py3.12-django-v5.2.3 ### Code Quality -Our `linters` tox environment runs `black` for formatting, `flake8` for linting and `mypy` for type checking. +Our `linters` tox environment runs `ruff-format` for formatting, `ruff-check` for linting and `mypy` for type checking. ```bash tox -e linters diff --git a/.cursor/rules/testing-guide.mdc b/.cursor/rules/testing-guide.mdc index e336bb337a..939f1a095b 100644 --- a/.cursor/rules/testing-guide.mdc +++ b/.cursor/rules/testing-guide.mdc @@ -1,6 +1,6 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- # Testing Guide @@ -65,10 +65,10 @@ def test_flask_integration(sentry_init, capture_events): # Test setup sentry_init(integrations=[FlaskIntegration()]) events = capture_events() - + # Test execution # ... test code ... - + # Assertions assert len(events) == 1 assert events[0]["exception"]["values"][0]["type"] == "ValueError" diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000000..147eaebfe8 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,3 @@ +# Formatting commits to ignore in git blame +afea4a017bf13f78e82f725ea9d6a56a8e02cb34 +23a340a9dca60eea36de456def70c00952a33556 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9787e136bb..7ec62a8f37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,9 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.13.2 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - -- repo: https://github.com/psf/black - rev: 24.1.0 - hooks: - - id: black - exclude: ^(.*_pb2.py|.*_pb2_grpc.py) - -- repo: https://github.com/pycqa/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - additional_dependencies: - [ - flake8-pyproject, - flake8-bugbear, - pep8-naming, - ] - -# Disabled for now, because it lists a lot of problems. -#- repo: https://github.com/pre-commit/mirrors-mypy -# rev: 'v0.931' -# hooks: -# - id: mypy + - id: ruff-check + args: [--fix] + - id: ruff-format diff --git a/codecov.yml b/codecov.yml index 086157690e..b7abcf8c86 100644 --- a/codecov.yml +++ b/codecov.yml @@ -19,9 +19,9 @@ comment: # Comments will only post when coverage changes. Furthermore, if a comment # already exists, and a newer commit results in no coverage change for the # entire pull, the comment will be deleted. - require_changes: true + require_changes: true require_base: true # must have a base report to post require_head: true # must have a head report to post github_checks: - annotations: false \ No newline at end of file + annotations: false diff --git a/pyproject.toml b/pyproject.toml index 44eded7641..8e6fe345f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,19 +1,3 @@ -# -# Tool: Black -# - -[tool.black] -# 'extend-exclude' excludes files or directories in addition to the defaults -extend-exclude = ''' -# A regex preceded with ^/ will apply only to files and directories -# in the root of the project. -( - .*_pb2.py # exclude autogenerated Protocol Buffer files anywhere in the project - | .*_pb2_grpc.py # exclude autogenerated Protocol Buffer files anywhere in the project -) -''' - - # # Tool: Coverage # @@ -196,29 +180,48 @@ module = "agents.*" ignore_missing_imports = true # -# Tool: Flake8 +# Tool: Ruff (linting and formatting) # -[tool.flake8] -extend-ignore = [ - # Handled by black (Whitespace before ':' -- handled by black) - "E203", - # Handled by black (Line too long) - "E501", - # Sometimes not possible due to execution order (Module level import is not at top of file) - "E402", - # I don't care (Do not assign a lambda expression, use a def) - "E731", - # does not apply to Python 2 (redundant exception types by flake8-bugbear) - "B014", - # I don't care (Lowercase imported as non-lowercase by pep8-naming) - "N812", - # is a worse version of and conflicts with B902 (first argument of a classmethod should be named cls) - "N804", +[tool.ruff] +# Target Python 3.7+ (minimum version supported by ruff) +target-version = "py37" + +# Exclude files and directories +extend-exclude = [ + "*_pb2.py", # Protocol Buffer files (covers all pb2 files including grpc_test_service_pb2.py) + "*_pb2_grpc.py", # Protocol Buffer files (covers all pb2_grpc files including grpc_test_service_pb2_grpc.py) + "checkouts", # From flake8 + "lol*", # From flake8 ] -extend-exclude = ["checkouts", "lol*"] -exclude = [ - # gRCP generated files - "grpc_test_service_pb2.py", - "grpc_test_service_pb2_grpc.py", + +[tool.ruff.lint] +# Match flake8's default rule selection exactly +# Flake8 by default only enables E and W (pycodestyle) + F (pyflakes) +select = [ + "E", # pycodestyle errors (same as flake8 default) + "W", # pycodestyle warnings (same as flake8 default) + "F", # Pyflakes (same as flake8 default) + # Note: B and N rules are NOT enabled by default in flake8 + # They were only active through the plugins, which may not have been fully enabled ] + +# Use ONLY the same ignores as the original flake8 config + compatibility for this codebase +ignore = [ + "E203", # Whitespace before ':' + "E501", # Line too long + "E402", # Module level import not at top of file + "E731", # Do not assign a lambda expression, use a def + "B014", # Redundant exception types + "N812", # Lowercase imported as non-lowercase + "N804", # First argument of classmethod should be named cls + + # Additional ignores for codebase compatibility + "F401", # Unused imports - many in TYPE_CHECKING blocks used for type comments + "E721", # Use isinstance instead of type() == - existing pattern in this codebase +] + +[tool.ruff.format] +# ruff format already excludes the same files as specified in extend-exclude +# Ensure Python 3.7 compatibility - avoid using Python 3.9+ syntax features +skip-magic-trailing-comma = false diff --git a/requirements-linting.txt b/requirements-linting.txt index 20db2151d0..1cc8274795 100644 --- a/requirements-linting.txt +++ b/requirements-linting.txt @@ -1,9 +1,5 @@ mypy -black -flake8==5.0.4 -flake8-pyproject # Flake8 plugin to support configuration in pyproject.toml -flake8-bugbear # Flake8 plugin -pep8-naming # Flake8 plugin +ruff types-certifi types-protobuf types-gevent diff --git a/scripts/build_aws_lambda_layer.py b/scripts/build_aws_lambda_layer.py index a7e2397546..a1078f4e19 100644 --- a/scripts/build_aws_lambda_layer.py +++ b/scripts/build_aws_lambda_layer.py @@ -75,8 +75,7 @@ def create_init_serverless_sdk_package(self): sentry-python-serverless zip """ serverless_sdk_path = ( - f"{self.python_site_packages}/sentry_sdk/" - f"integrations/init_serverless_sdk" + f"{self.python_site_packages}/sentry_sdk/integrations/init_serverless_sdk" ) if not os.path.exists(serverless_sdk_path): os.makedirs(serverless_sdk_path) diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index 4bb4d78231..d625c1da72 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -601,7 +601,7 @@ def _add_python_versions_to_release( def _transform_target_python_versions( - python_versions: Union[str, dict[str, str], None] + python_versions: Union[str, dict[str, str], None], ) -> Union[SpecifierSet, dict[SpecifierSet, SpecifierSet], None]: """Wrap the contents of the `python` key in SpecifierSets.""" if not python_versions: @@ -711,9 +711,9 @@ def main(fail_on_changes: bool = False) -> None: name = _normalize_name(release["info"]["name"]) version = release["info"]["version"] CACHE[name][version] = release - CACHE[name][version][ - "_accessed" - ] = False # for cleaning up unused cache entries + CACHE[name][version]["_accessed"] = ( + False # for cleaning up unused cache entries + ) # Process packages packages = defaultdict(list) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index c68fe87734..fa24b089cd 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.38", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.39", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} @@ -77,7 +77,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8"], "name": "grpcio", "requires_python": "", "version": "1.32.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.6", "version": "1.47.5", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.7", "version": "1.62.3", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.9", "version": "1.75.0", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.9", "version": "1.75.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.16.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.20.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.22.0", "yanked": false}} diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index 39d2406b84..2a33e7790d 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -176,11 +176,9 @@ basepython = py3.12: python3.12 py3.13: python3.13 - # Python version is pinned here because flake8 actually behaves differently - # depending on which version is used. You can patch this out to point to - # some random Python 3 binary, but then you get guaranteed mismatches with - # CI. Other tools such as mypy and black have options that pin the Python - # version. + # Python version is pinned here for consistency across environments. + # Tools like ruff and mypy have options that pin the target Python + # version (configured in pyproject.toml), ensuring consistent behavior. linters: python3.12 commands = @@ -196,6 +194,6 @@ commands = [testenv:linters] commands = - flake8 tests sentry_sdk - black --check tests sentry_sdk + ruff check tests sentry_sdk + ruff format --check tests sentry_sdk mypy sentry_sdk diff --git a/scripts/ready_yet/main.py b/scripts/ready_yet/main.py index bba97d0c98..1f94adeb70 100644 --- a/scripts/ready_yet/main.py +++ b/scripts/ready_yet/main.py @@ -56,7 +56,7 @@ def main(): Check if libraries in our tox.ini are ready for Python version defined in `PYTHON_VERSION`. """ print(f"Checking libs from tox.ini for Python {PYTHON_VERSION} compatibility:") - + ready = set() not_ready = set() not_found = set() diff --git a/scripts/ready_yet/run.sh b/scripts/ready_yet/run.sh index f32bd7bdda..f872079c87 100755 --- a/scripts/ready_yet/run.sh +++ b/scripts/ready_yet/run.sh @@ -3,7 +3,7 @@ # exit on first error set -xe -reset +reset # create and activate virtual environment python -m venv .venv @@ -13,4 +13,4 @@ source .venv/bin/activate python -m pip install -r requirements.txt # Run the script -python main.py \ No newline at end of file +python main.py diff --git a/scripts/test-lambda-locally/README.md b/scripts/test-lambda-locally/README.md index 115927cc2b..2c02d6301f 100644 --- a/scripts/test-lambda-locally/README.md +++ b/scripts/test-lambda-locally/README.md @@ -1,28 +1,28 @@ # Test AWS Lambda functions locally -An easy way to run an AWS Lambda function with the Sentry SDK locally. +An easy way to run an AWS Lambda function with the Sentry SDK locally. -This is a small helper to create a AWS Lambda function that includes the +This is a small helper to create a AWS Lambda function that includes the currently checked out Sentry SDK and runs it in a local AWS Lambda environment. -Currently only embedding the Sentry SDK into the Lambda function package -is supported. Adding the SDK as Lambda Layer is not possible at the moment. +Currently only embedding the Sentry SDK into the Lambda function package +is supported. Adding the SDK as Lambda Layer is not possible at the moment. ## Prerequisites - Set `SENTRY_DSN` environment variable. The Lambda function will use this DSN. -- You need to have Docker installed and running. +- You need to have Docker installed and running. ## Run Lambda function -- Update `lambda_function.py` to include your test code. +- Update `lambda_function.py` to include your test code. - Run `./deploy-lambda-locally.sh`. This will: - Install [AWS SAM](https://aws.amazon.com/serverless/sam/) in a virtual Python environment - Create a lambda function package in `package/` that includes - The currently checked out Sentry SDK - All dependencies of the Sentry SDK (certifi and urllib3) - - The actual function defined in `lamdba_function.py`. + - The actual function defined in `lamdba_function.py`. - Zip everything together into lambda_deployment_package.zip - Run a local Lambda environment that serves that Lambda function. - Point your browser to `http://127.0.0.1:3000` to access your Lambda function. - - Currently GET and POST requests are possible. This is defined in `template.yaml`. \ No newline at end of file + - Currently GET and POST requests are possible. This is defined in `template.yaml`. diff --git a/scripts/test-lambda-locally/deploy-lambda-locally.sh b/scripts/test-lambda-locally/deploy-lambda-locally.sh index 495c1259dc..5da4fee6ba 100755 --- a/scripts/test-lambda-locally/deploy-lambda-locally.sh +++ b/scripts/test-lambda-locally/deploy-lambda-locally.sh @@ -13,9 +13,9 @@ fi uv sync # Create a deployment package of the lambda function in `lambda_function.py`. -rm -rf package && mkdir -p package +rm -rf package && mkdir -p package pip install ../../../sentry-python -t package/ --upgrade -cp lambda_function.py package/ +cp lambda_function.py package/ cd package && zip -r ../lambda_deployment_package.zip . && cd .. # Start the local Lambda server with the new function (defined in template.yaml) diff --git a/scripts/test-lambda-locally/lambda_function.py b/scripts/test-lambda-locally/lambda_function.py index ceab090499..fdaa2160eb 100644 --- a/scripts/test-lambda-locally/lambda_function.py +++ b/scripts/test-lambda-locally/lambda_function.py @@ -5,21 +5,22 @@ from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration from sentry_sdk.integrations.logging import LoggingIntegration + def lambda_handler(event, context): sentry_sdk.init( dsn=os.environ.get("SENTRY_DSN"), attach_stacktrace=True, integrations=[ LoggingIntegration(level=logging.INFO, event_level=logging.ERROR), - AwsLambdaIntegration(timeout_warning=True) + AwsLambdaIntegration(timeout_warning=True), ], traces_sample_rate=1.0, debug=True, ) try: - my_dict = {"a" : "test"} - value = my_dict["b"] # This should raise exception + my_dict = {"a": "test"} + _ = my_dict["b"] # This should raise exception except: logging.exception("Key Does not Exists") raise diff --git a/sentry_sdk/client.py b/sentry_sdk/client.py index c45d5e2f4f..c06043ebe2 100644 --- a/sentry_sdk/client.py +++ b/sentry_sdk/client.py @@ -178,9 +178,7 @@ class BaseClient: def __init__(self, options=None): # type: (Optional[Dict[str, Any]]) -> None - self.options = ( - options if options is not None else DEFAULT_OPTIONS - ) # type: Dict[str, Any] + self.options = options if options is not None else DEFAULT_OPTIONS # type: Dict[str, Any] self.transport = None # type: Optional[Transport] self.monitor = None # type: Optional[Monitor] @@ -956,7 +954,7 @@ def _capture_experimental_log(self, log): debug = self.options.get("debug", False) if debug: logger.debug( - f'[Sentry Logs] [{log.get("severity_text")}] {log.get("body")}' + f"[Sentry Logs] [{log.get('severity_text')}] {log.get('body')}" ) before_send_log = get_before_send_log(self.options) @@ -970,7 +968,8 @@ def _capture_experimental_log(self, log): self.log_batcher.add(log) def capture_session( - self, session # type: Session + self, + session, # type: Session ): # type: (...) -> None if not session.release: @@ -991,7 +990,8 @@ def get_integration(self, name_or_class): ... def get_integration( - self, name_or_class # type: Union[str, Type[Integration]] + self, + name_or_class, # type: Union[str, Type[Integration]] ): # type: (...) -> Optional[Integration] """Returns the integration for this client by name or class. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 5b8e840f13..9e84dc3dd2 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -852,7 +852,6 @@ class OP: # This type exists to trick mypy and PyCharm into thinking `init` and `Client` # take these arguments (even though they take opaque **kwargs) class ClientConstructor: - def __init__( self, dsn=None, # type: Optional[str] diff --git a/sentry_sdk/envelope.py b/sentry_sdk/envelope.py index 7dbbdec5c8..d9b2c1629a 100644 --- a/sentry_sdk/envelope.py +++ b/sentry_sdk/envelope.py @@ -57,25 +57,29 @@ def description(self): ) def add_event( - self, event # type: Event + self, + event, # type: Event ): # type: (...) -> None self.add_item(Item(payload=PayloadRef(json=event), type="event")) def add_transaction( - self, transaction # type: Event + self, + transaction, # type: Event ): # type: (...) -> None self.add_item(Item(payload=PayloadRef(json=transaction), type="transaction")) def add_profile( - self, profile # type: Any + self, + profile, # type: Any ): # type: (...) -> None self.add_item(Item(payload=PayloadRef(json=profile), type="profile")) def add_profile_chunk( - self, profile_chunk # type: Any + self, + profile_chunk, # type: Any ): # type: (...) -> None self.add_item( @@ -87,13 +91,15 @@ def add_profile_chunk( ) def add_checkin( - self, checkin # type: Any + self, + checkin, # type: Any ): # type: (...) -> None self.add_item(Item(payload=PayloadRef(json=checkin), type="check_in")) def add_session( - self, session # type: Union[Session, Any] + self, + session, # type: Union[Session, Any] ): # type: (...) -> None if isinstance(session, Session): @@ -101,13 +107,15 @@ def add_session( self.add_item(Item(payload=PayloadRef(json=session), type="session")) def add_sessions( - self, sessions # type: Any + self, + sessions, # type: Any ): # type: (...) -> None self.add_item(Item(payload=PayloadRef(json=sessions), type="sessions")) def add_item( - self, item # type: Item + self, + item, # type: Item ): # type: (...) -> None self.items.append(item) @@ -133,7 +141,8 @@ def __iter__(self): return iter(self.items) def serialize_into( - self, f # type: Any + self, + f, # type: Any ): # type: (...) -> None f.write(json_dumps(self.headers)) @@ -149,7 +158,8 @@ def serialize(self): @classmethod def deserialize_from( - cls, f # type: Any + cls, + f, # type: Any ): # type: (...) -> Envelope headers = parse_json(f.readline()) @@ -163,7 +173,8 @@ def deserialize_from( @classmethod def deserialize( - cls, bytes # type: bytes + cls, + bytes, # type: bytes ): # type: (...) -> Envelope return cls.deserialize_from(io.BytesIO(bytes)) @@ -307,7 +318,8 @@ def get_transaction_event(self): return None def serialize_into( - self, f # type: Any + self, + f, # type: Any ): # type: (...) -> None headers = dict(self.headers) @@ -326,7 +338,8 @@ def serialize(self): @classmethod def deserialize_from( - cls, f # type: Any + cls, + f, # type: Any ): # type: (...) -> Optional[Item] line = f.readline().rstrip() @@ -349,7 +362,8 @@ def deserialize_from( @classmethod def deserialize( - cls, bytes # type: bytes + cls, + bytes, # type: bytes ): # type: (...) -> Optional[Item] return cls.deserialize_from(io.BytesIO(bytes)) diff --git a/sentry_sdk/feature_flags.py b/sentry_sdk/feature_flags.py index eb53acae5d..03fba9c53c 100644 --- a/sentry_sdk/feature_flags.py +++ b/sentry_sdk/feature_flags.py @@ -15,7 +15,6 @@ class FlagBuffer: - def __init__(self, capacity): # type: (int) -> None self.capacity = capacity diff --git a/sentry_sdk/hub.py b/sentry_sdk/hub.py index 7fda9202df..6f2d1bbf13 100644 --- a/sentry_sdk/hub.py +++ b/sentry_sdk/hub.py @@ -205,7 +205,8 @@ def __exit__( scope._isolation_scope.set(old_isolation_scope) def run( - self, callback # type: Callable[[], T] + self, + callback, # type: Callable[[], T] ): # type: (...) -> T """ @@ -219,7 +220,8 @@ def run( return callback() def get_integration( - self, name_or_class # type: Union[str, Type[Integration]] + self, + name_or_class, # type: Union[str, Type[Integration]] ): # type: (...) -> Any """ @@ -277,7 +279,8 @@ def last_event_id(self): return self._last_event_id def bind_client( - self, new # type: Optional[BaseClient] + self, + new, # type: Optional[BaseClient] ): # type: (...) -> None """ @@ -430,7 +433,7 @@ def start_transaction( transaction=None, instrumenter=INSTRUMENTER.SENTRY, custom_sampling_context=None, - **kwargs + **kwargs, ): # type: (Optional[Transaction], str, Optional[SamplingContext], Unpack[TransactionKwargs]) -> Union[Transaction, NoOpSpan] """ @@ -487,14 +490,16 @@ def continue_trace(self, environ_or_headers, op=None, name=None, source=None): @overload def push_scope( - self, callback=None # type: Optional[None] + self, + callback=None, # type: Optional[None] ): # type: (...) -> ContextManager[Scope] pass @overload def push_scope( # noqa: F811 - self, callback # type: Callable[[Scope], None] + self, + callback, # type: Callable[[Scope], None] ): # type: (...) -> None pass @@ -540,14 +545,16 @@ def pop_scope_unsafe(self): @overload def configure_scope( - self, callback=None # type: Optional[None] + self, + callback=None, # type: Optional[None] ): # type: (...) -> ContextManager[Scope] pass @overload def configure_scope( # noqa: F811 - self, callback # type: Callable[[Scope], None] + self, + callback, # type: Callable[[Scope], None] ): # type: (...) -> None pass @@ -587,7 +594,8 @@ def inner(): return inner() def start_session( - self, session_mode="application" # type: str + self, + session_mode="application", # type: str ): # type: (...) -> None """ diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index dde8128a33..28b44cc7ab 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -233,14 +233,15 @@ async def _run_app(self, scope, receive, send, asgi_version): if transaction: transaction.set_tag("asgi.type", ty) - with ( + transaction_context = ( sentry_sdk.start_transaction( transaction, custom_sampling_context={"asgi_scope": scope}, ) if transaction is not None else nullcontext() - ): + ) + with transaction_context: try: async def _sentry_wrapped_send(event): diff --git a/sentry_sdk/integrations/grpc/aio/client.py b/sentry_sdk/integrations/grpc/aio/client.py index ff3c213176..7462675a97 100644 --- a/sentry_sdk/integrations/grpc/aio/client.py +++ b/sentry_sdk/integrations/grpc/aio/client.py @@ -65,7 +65,8 @@ async def intercept_unary_unary( class SentryUnaryStreamClientInterceptor( - ClientInterceptor, UnaryStreamClientInterceptor # type: ignore + ClientInterceptor, + UnaryStreamClientInterceptor, # type: ignore ): async def intercept_unary_stream( self, diff --git a/sentry_sdk/integrations/grpc/client.py b/sentry_sdk/integrations/grpc/client.py index a5b4f9f52e..ef24821ed2 100644 --- a/sentry_sdk/integrations/grpc/client.py +++ b/sentry_sdk/integrations/grpc/client.py @@ -19,7 +19,8 @@ class ClientInterceptor( - grpc.UnaryUnaryClientInterceptor, grpc.UnaryStreamClientInterceptor # type: ignore + grpc.UnaryUnaryClientInterceptor, # type: ignore + grpc.UnaryStreamClientInterceptor, # type: ignore ): _is_intercepted = False @@ -60,9 +61,7 @@ def intercept_unary_stream(self, continuation, client_call_details, request): client_call_details ) - response = continuation( - client_call_details, request - ) # type: UnaryStreamCall + response = continuation(client_call_details, request) # type: UnaryStreamCall # Setting code on unary-stream leads to execution getting stuck # span.set_data("code", response.code().name) diff --git a/sentry_sdk/integrations/langchain.py b/sentry_sdk/integrations/langchain.py index 6bc3ceb93e..fdba26569d 100644 --- a/sentry_sdk/integrations/langchain.py +++ b/sentry_sdk/integrations/langchain.py @@ -554,7 +554,6 @@ def _simplify_langchain_tools(tools): for tool in tools: try: if isinstance(tool, dict): - if "function" in tool and isinstance(tool["function"], dict): func = tool["function"] simplified_tool = { diff --git a/sentry_sdk/integrations/launchdarkly.py b/sentry_sdk/integrations/launchdarkly.py index d3c423e7be..6dfc1958b7 100644 --- a/sentry_sdk/integrations/launchdarkly.py +++ b/sentry_sdk/integrations/launchdarkly.py @@ -44,7 +44,6 @@ def setup_once(): class LaunchDarklyHook(Hook): - @property def metadata(self): # type: () -> Metadata diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 2be4d376e0..745a00bcba 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -222,9 +222,7 @@ async def handle_wrapper(self, scope, receive, send): return await old_handle(self, scope, receive, send) sentry_scope = sentry_sdk.get_isolation_scope() - request = scope["app"].request_class( - scope=scope, receive=receive, send=send - ) # type: Request[Any, Any] + request = scope["app"].request_class(scope=scope, receive=receive, send=send) # type: Request[Any, Any] extracted_request_data = ConnectionDataExtractor( parse_body=True, parse_query=True )(request) diff --git a/sentry_sdk/integrations/pure_eval.py b/sentry_sdk/integrations/pure_eval.py index c1c3d63871..6ac10dfe1b 100644 --- a/sentry_sdk/integrations/pure_eval.py +++ b/sentry_sdk/integrations/pure_eval.py @@ -116,7 +116,9 @@ def start(n): return (n.lineno, n.col_offset) nodes_before_stmt = [ - node for node in nodes if start(node) < stmt.last_token.end # type: ignore + node + for node in nodes + if start(node) < stmt.last_token.end # type: ignore ] if nodes_before_stmt: # The position of the last node before or in the statement diff --git a/sentry_sdk/integrations/spark/spark_driver.py b/sentry_sdk/integrations/spark/spark_driver.py index fac985357f..b22dc2c807 100644 --- a/sentry_sdk/integrations/spark/spark_driver.py +++ b/sentry_sdk/integrations/spark/spark_driver.py @@ -158,7 +158,8 @@ def onExecutorBlacklisted(self, executorBlacklisted): # noqa: N802,N803 pass def onExecutorBlacklistedForStage( # noqa: N802 - self, executorBlacklistedForStage # noqa: N803 + self, + executorBlacklistedForStage, # noqa: N803 ): # type: (Any) -> None pass diff --git a/sentry_sdk/integrations/sqlalchemy.py b/sentry_sdk/integrations/sqlalchemy.py index 068d373053..0e039f93f3 100644 --- a/sentry_sdk/integrations/sqlalchemy.py +++ b/sentry_sdk/integrations/sqlalchemy.py @@ -64,9 +64,7 @@ def _before_cursor_execute( @ensure_integration_enabled(SqlalchemyIntegration) def _after_cursor_execute(conn, cursor, statement, parameters, context, *args): # type: (Any, Any, Any, Any, Any, *Any) -> None - ctx_mgr = getattr( - context, "_sentry_sql_span_manager", None - ) # type: Optional[ContextManager[Any]] + ctx_mgr = getattr(context, "_sentry_sql_span_manager", None) # type: Optional[ContextManager[Any]] if ctx_mgr is not None: context._sentry_sql_span_manager = None @@ -92,9 +90,7 @@ def _handle_error(context, *args): # _after_cursor_execute does not get called for crashing SQL stmts. Judging # from SQLAlchemy codebase it does seem like any error coming into this # handler is going to be fatal. - ctx_mgr = getattr( - execution_context, "_sentry_sql_span_manager", None - ) # type: Optional[ContextManager[Any]] + ctx_mgr = getattr(execution_context, "_sentry_sql_span_manager", None) # type: Optional[ContextManager[Any]] if ctx_mgr is not None: execution_context._sentry_sql_span_manager = None diff --git a/sentry_sdk/integrations/starlette.py b/sentry_sdk/integrations/starlette.py index c7ce40618b..f1a0e360bb 100644 --- a/sentry_sdk/integrations/starlette.py +++ b/sentry_sdk/integrations/starlette.py @@ -103,9 +103,7 @@ def __init__( self.http_methods_to_capture = tuple(map(str.upper, http_methods_to_capture)) if isinstance(failed_request_status_codes, Set): - self.failed_request_status_codes = ( - failed_request_status_codes - ) # type: Container[int] + self.failed_request_status_codes = failed_request_status_codes # type: Container[int] else: warnings.warn( "Passing a list or None for failed_request_status_codes is deprecated. " diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index b402aa2184..daab82d642 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -200,9 +200,7 @@ async def handle_wrapper(self, scope, receive, send): return await old_handle(self, scope, receive, send) sentry_scope = sentry_sdk.get_isolation_scope() - request = scope["app"].request_class( - scope=scope, receive=receive, send=send - ) # type: Request[Any, Any] + request = scope["app"].request_class(scope=scope, receive=receive, send=send) # type: Request[Any, Any] extracted_request_data = ConnectionDataExtractor( parse_body=True, parse_query=True )(request) diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index e628e50e69..fa79ec96da 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -119,14 +119,15 @@ def __call__(self, environ, start_response): origin=self.span_origin, ) - with ( + transaction_context = ( sentry_sdk.start_transaction( transaction, custom_sampling_context={"wsgi_environ": environ}, ) if transaction is not None else nullcontext() - ): + ) + with transaction_context: try: response = self.app( environ, diff --git a/sentry_sdk/metrics.py b/sentry_sdk/metrics.py index 4bdbc62253..d0041114ce 100644 --- a/sentry_sdk/metrics.py +++ b/sentry_sdk/metrics.py @@ -159,7 +159,8 @@ class CounterMetric(Metric): __slots__ = ("value",) def __init__( - self, first # type: MetricValue + self, + first, # type: MetricValue ): # type: (...) -> None self.value = float(first) @@ -170,7 +171,8 @@ def weight(self): return 1 def add( - self, value # type: MetricValue + self, + value, # type: MetricValue ): # type: (...) -> None self.value += float(value) @@ -190,7 +192,8 @@ class GaugeMetric(Metric): ) def __init__( - self, first # type: MetricValue + self, + first, # type: MetricValue ): # type: (...) -> None first = float(first) @@ -207,7 +210,8 @@ def weight(self): return 5 def add( - self, value # type: MetricValue + self, + value, # type: MetricValue ): # type: (...) -> None value = float(value) @@ -232,7 +236,8 @@ class DistributionMetric(Metric): __slots__ = ("value",) def __init__( - self, first # type: MetricValue + self, + first, # type: MetricValue ): # type(...) -> None self.value = [float(first)] @@ -243,7 +248,8 @@ def weight(self): return len(self.value) def add( - self, value # type: MetricValue + self, + value, # type: MetricValue ): # type: (...) -> None self.value.append(float(value)) @@ -257,7 +263,8 @@ class SetMetric(Metric): __slots__ = ("value",) def __init__( - self, first # type: MetricValue + self, + first, # type: MetricValue ): # type: (...) -> None self.value = {first} @@ -268,7 +275,8 @@ def weight(self): return len(self.value) def add( - self, value # type: MetricValue + self, + value, # type: MetricValue ): # type: (...) -> None self.value.add(value) @@ -373,9 +381,7 @@ class LocalAggregator: def __init__(self): # type: (...) -> None - self._measurements = ( - {} - ) # type: Dict[Tuple[str, MetricTagsInternal], Tuple[float, float, int, float]] + self._measurements = {} # type: Dict[Tuple[str, MetricTagsInternal], Tuple[float, float, int, float]] def add( self, diff --git a/sentry_sdk/profiler/utils.py b/sentry_sdk/profiler/utils.py index 3554cddb5d..7d311e91f4 100644 --- a/sentry_sdk/profiler/utils.py +++ b/sentry_sdk/profiler/utils.py @@ -85,9 +85,7 @@ def get_frame_name(frame): if ( # the co_varnames start with the frame's positional arguments # and we expect the first to be `self` if its an instance method - co_varnames - and co_varnames[0] == "self" - and "self" in frame.f_locals + co_varnames and co_varnames[0] == "self" and "self" in frame.f_locals ): for cls in type(frame.f_locals["self"]).__mro__: if name in cls.__dict__: @@ -101,9 +99,7 @@ def get_frame_name(frame): if ( # the co_varnames start with the frame's positional arguments # and we expect the first to be `cls` if its a class method - co_varnames - and co_varnames[0] == "cls" - and "cls" in frame.f_locals + co_varnames and co_varnames[0] == "cls" and "cls" in frame.f_locals ): for cls in frame.f_locals["cls"].__mro__: if name in cls.__dict__: diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index 3356de57a8..c871e6a467 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -894,7 +894,8 @@ def set_context( self._contexts[key] = value def remove_context( - self, key # type: str + self, + key, # type: str ): # type: (...) -> None """Removes a context.""" @@ -910,7 +911,8 @@ def set_extra( self._extras[key] = value def remove_extra( - self, key # type: str + self, + key, # type: str ): # type: (...) -> None """Removes a specific extra key.""" @@ -1321,7 +1323,8 @@ def resume_auto_session_tracking(self): self._force_auto_session_tracking = None def add_event_processor( - self, func # type: EventProcessor + self, + func, # type: EventProcessor ): # type: (...) -> None """Register a scope local event processor on the scope. diff --git a/sentry_sdk/serializer.py b/sentry_sdk/serializer.py index 6bde5c08bd..1775b1b555 100644 --- a/sentry_sdk/serializer.py +++ b/sentry_sdk/serializer.py @@ -128,9 +128,7 @@ def serialize(event, **kwargs): path = [] # type: List[Segment] meta_stack = [] # type: List[Dict[str, Any]] - keep_request_bodies = ( - kwargs.pop("max_request_body_size", None) == "always" - ) # type: bool + keep_request_bodies = kwargs.pop("max_request_body_size", None) == "always" # type: bool max_value_length = kwargs.pop("max_value_length", None) # type: Optional[int] is_vars = kwargs.pop("is_vars", False) custom_repr = kwargs.pop("custom_repr", None) # type: Callable[..., Optional[str]] diff --git a/sentry_sdk/session.py b/sentry_sdk/session.py index c1d422c115..af9551c56e 100644 --- a/sentry_sdk/session.py +++ b/sentry_sdk/session.py @@ -130,7 +130,8 @@ def update( self.status = status def close( - self, status=None # type: Optional[SessionStatus] + self, + status=None, # type: Optional[SessionStatus] ): # type: (...) -> Any if status is None and self.status == "ok": @@ -139,7 +140,8 @@ def close( self.update(status=status) def get_json_attrs( - self, with_user_info=True # type: Optional[bool] + self, + with_user_info=True, # type: Optional[bool] ): # type: (...) -> Any attrs = {} diff --git a/sentry_sdk/sessions.py b/sentry_sdk/sessions.py index 00fda23200..2bf4ee707a 100644 --- a/sentry_sdk/sessions.py +++ b/sentry_sdk/sessions.py @@ -228,7 +228,8 @@ def _thread(): return None def add_aggregate_session( - self, session # type: Session + self, + session, # type: Session ): # type: (...) -> None # NOTE on `session.did`: @@ -259,7 +260,8 @@ def add_aggregate_session( state["exited"] = state.get("exited", 0) + 1 def add_session( - self, session # type: Session + self, + session, # type: Session ): # type: (...) -> None if session.session_mode == "request": diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index 4edda21075..a82b99ff4d 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -998,9 +998,7 @@ def finish( # For backwards compatibility, we must handle the case where `scope` # or `hub` could both either be a `Scope` or a `Hub`. - scope = self._get_scope_from_finish_args( - scope, hub - ) # type: Optional[sentry_sdk.Scope] + scope = self._get_scope_from_finish_args(scope, hub) # type: Optional[sentry_sdk.Scope] scope = scope or self.scope or sentry_sdk.get_current_scope() client = sentry_sdk.get_client() @@ -1209,8 +1207,8 @@ def _set_initial_sampling_decision(self, sampling_context): sample_rate = ( client.options["traces_sampler"](sampling_context) if callable(client.options.get("traces_sampler")) + # default inheritance behavior else ( - # default inheritance behavior sampling_context["parent_sampled"] if sampling_context["parent_sampled"] is not None else client.options["traces_sample_rate"] diff --git a/sentry_sdk/tracing_utils.py b/sentry_sdk/tracing_utils.py index 2f3e334e3f..b81d647c6d 100644 --- a/sentry_sdk/tracing_utils.py +++ b/sentry_sdk/tracing_utils.py @@ -527,9 +527,7 @@ def _fill_sample_rand(self): ) return - self.dynamic_sampling_context["sample_rand"] = ( - f"{sample_rand:.6f}" # noqa: E231 - ) + self.dynamic_sampling_context["sample_rand"] = f"{sample_rand:.6f}" # noqa: E231 def _sample_rand(self): # type: () -> Optional[str] diff --git a/sentry_sdk/transport.py b/sentry_sdk/transport.py index e904081959..75384519e9 100644 --- a/sentry_sdk/transport.py +++ b/sentry_sdk/transport.py @@ -203,9 +203,7 @@ def __init__(self, options): self._disabled_until = {} # type: Dict[Optional[EventDataCategory], datetime] # We only use this Retry() class for the `get_retry_after` method it exposes self._retry = urllib3.util.Retry() - self._discarded_events = defaultdict( - int - ) # type: DefaultDict[Tuple[EventDataCategory, str], int] + self._discarded_events = defaultdict(int) # type: DefaultDict[Tuple[EventDataCategory, str], int] self._last_client_report_sent = time.time() self._pool = self._make_pool() @@ -549,7 +547,8 @@ def _request( raise NotImplementedError() def capture_envelope( - self, envelope # type: Envelope + self, + envelope, # type: Envelope ): # type: (...) -> None def send_envelope_wrapper(): @@ -862,14 +861,16 @@ class _FunctionTransport(Transport): """ def __init__( - self, func # type: Callable[[Event], None] + self, + func, # type: Callable[[Event], None] ): # type: (...) -> None Transport.__init__(self) self._func = func def capture_event( - self, event # type: Event + self, + event, # type: Event ): # type: (...) -> None self._func(event) @@ -891,9 +892,7 @@ def make_transport(options): use_http2_transport = options.get("_experiments", {}).get("transport_http2", False) # By default, we use the http transport class - transport_cls = ( - Http2Transport if use_http2_transport else HttpTransport - ) # type: Type[Transport] + transport_cls = Http2Transport if use_http2_transport else HttpTransport # type: Type[Transport] if isinstance(ref_transport, Transport): return ref_transport diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index 3fe3ac3eec..2083fd296c 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -389,7 +389,8 @@ def __init__( self.client = client def get_api_url( - self, type=EndpointType.ENVELOPE # type: EndpointType + self, + type=EndpointType.ENVELOPE, # type: EndpointType ): # type: (...) -> str """Returns the API url for storing events.""" @@ -850,7 +851,9 @@ def exceptions_from_error( parent_id = exception_id exception_id += 1 - should_supress_context = hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore + should_supress_context = ( + hasattr(exc_value, "__suppress_context__") and exc_value.__suppress_context__ # type: ignore + ) if should_supress_context: # Add direct cause. # The field `__cause__` is set when raised with the exception (using the `from` keyword). @@ -1845,7 +1848,6 @@ def now(): from gevent import get_hub as get_gevent_hub from gevent.monkey import is_module_patched except ImportError: - # it's not great that the signatures are different, get_hub can't return None # consider adding an if TYPE_CHECKING to change the signature to Optional[Hub] def get_gevent_hub(): # type: ignore[misc] diff --git a/tests/conftest.py b/tests/conftest.py index 01b1e9a81f..faa0251d9b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -384,7 +384,6 @@ def render_span(span): root_span = event["contexts"]["trace"] - # Return a list instead of a multiline string because black will know better how to format that return "\n".join(render_span(root_span)) return inner diff --git a/tests/integrations/asyncio/test_asyncio.py b/tests/integrations/asyncio/test_asyncio.py index fb75bfc69b..66113746bf 100644 --- a/tests/integrations/asyncio/test_asyncio.py +++ b/tests/integrations/asyncio/test_asyncio.py @@ -291,7 +291,8 @@ def test_sentry_task_factory_with_factory(mock_get_running_loop): @patch("asyncio.get_running_loop") @patch("sentry_sdk.integrations.asyncio.Task") def test_sentry_task_factory_context_no_factory( - MockTask, mock_get_running_loop # noqa: N803 + MockTask, + mock_get_running_loop, # noqa: N803 ): mock_loop = mock_get_running_loop.return_value mock_coro = MagicMock() diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceDisabled/.gitignore b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceDisabled/.gitignore index ee0b7b9305..1c56884372 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceDisabled/.gitignore +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceDisabled/.gitignore @@ -1,4 +1,4 @@ -# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies +# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies # into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry. # Ignore everything @@ -8,4 +8,4 @@ !index.py # And not .gitignore itself -!.gitignore \ No newline at end of file +!.gitignore diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceEnabled/.gitignore b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceEnabled/.gitignore index ee0b7b9305..1c56884372 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceEnabled/.gitignore +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/RaiseErrorPerformanceEnabled/.gitignore @@ -1,4 +1,4 @@ -# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies +# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies # into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry. # Ignore everything @@ -8,4 +8,4 @@ !index.py # And not .gitignore itself -!.gitignore \ No newline at end of file +!.gitignore diff --git a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/TracesSampler/.gitignore b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/TracesSampler/.gitignore index ee0b7b9305..1c56884372 100644 --- a/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/TracesSampler/.gitignore +++ b/tests/integrations/aws_lambda/lambda_functions_with_embedded_sdk/TracesSampler/.gitignore @@ -1,4 +1,4 @@ -# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies +# Need to add some ignore rules in this directory, because the unit tests will add the Sentry SDK and its dependencies # into this directory to create a Lambda function package that contains everything needed to instrument a Lambda function using Sentry. # Ignore everything @@ -8,4 +8,4 @@ !index.py # And not .gitignore itself -!.gitignore \ No newline at end of file +!.gitignore diff --git a/tests/integrations/beam/test_beam.py b/tests/integrations/beam/test_beam.py index 8c503b4c8c..809c4122e4 100644 --- a/tests/integrations/beam/test_beam.py +++ b/tests/integrations/beam/test_beam.py @@ -144,10 +144,10 @@ def test_monkey_patch_signature(f, args, kwargs): try: expected_signature = inspect.signature(f) test_signature = inspect.signature(f_temp) - assert ( - expected_signature == test_signature - ), "Failed on {}, signature {} does not match {}".format( - f, expected_signature, test_signature + assert expected_signature == test_signature, ( + "Failed on {}, signature {} does not match {}".format( + f, expected_signature, test_signature + ) ) except Exception: # expected to pass for py2.7 diff --git a/tests/integrations/celery/test_celery.py b/tests/integrations/celery/test_celery.py index 80b4a423cb..b8fc2bb3e8 100644 --- a/tests/integrations/celery/test_celery.py +++ b/tests/integrations/celery/test_celery.py @@ -374,9 +374,9 @@ def dummy_task(self): assert submit_transaction["type"] == "transaction" assert submit_transaction["transaction"] == "submit_celery" - assert len( - submit_transaction["spans"] - ), 4 # Because redis integration was auto enabled + assert len(submit_transaction["spans"]), ( + 4 + ) # Because redis integration was auto enabled span = submit_transaction["spans"][0] assert span["op"] == "queue.submit.celery" assert span["description"] == "dummy_task" @@ -439,7 +439,9 @@ def dummy_task(self, x, y): def test_traces_sampler_gets_task_info_in_sampling_context( - init_celery, celery_invocation, DictionaryContaining # noqa:N803 + init_celery, + celery_invocation, + DictionaryContaining, # noqa:N803 ): traces_sampler = mock.Mock() celery = init_celery(traces_sampler=traces_sampler) diff --git a/tests/integrations/django/asgi/test_asgi.py b/tests/integrations/django/asgi/test_asgi.py index 3c78ac3f38..8a30c7f5c0 100644 --- a/tests/integrations/django/asgi/test_asgi.py +++ b/tests/integrations/django/asgi/test_asgi.py @@ -432,9 +432,7 @@ async def test_trace_from_headers_if_performance_disabled(sentry_init, capture_e PICTURE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "image.png") BODY_FORM = """--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="username"\r\n\r\nJane\r\n--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="password"\r\n\r\nhello123\r\n--fd721ef49ea403a6\r\nContent-Disposition: form-data; name="photo"; filename="image.png"\r\nContent-Type: image/png\r\nContent-Transfer-Encoding: base64\r\n\r\n{{image_data}}\r\n--fd721ef49ea403a6--\r\n""".replace( "{{image_data}}", base64.b64encode(open(PICTURE, "rb").read()).decode("utf-8") -).encode( - "utf-8" -) +).encode("utf-8") BODY_FORM_CONTENT_LENGTH = str(len(BODY_FORM)).encode("utf-8") diff --git a/tests/integrations/django/test_cache_module.py b/tests/integrations/django/test_cache_module.py index 263f9f36f8..bc58dd8471 100644 --- a/tests/integrations/django/test_cache_module.py +++ b/tests/integrations/django/test_cache_module.py @@ -529,33 +529,33 @@ def test_cache_spans_get_many(sentry_init, capture_events, use_django_caching): from django.core.cache import cache with sentry_sdk.start_transaction(): - cache.get_many([f"S{id}", f"S{id+1}"]) + cache.get_many([f"S{id}", f"S{id + 1}"]) cache.set(f"S{id}", "Sensitive1") - cache.get_many([f"S{id}", f"S{id+1}"]) + cache.get_many([f"S{id}", f"S{id + 1}"]) (transaction,) = events assert len(transaction["spans"]) == 7 assert transaction["spans"][0]["op"] == "cache.get" - assert transaction["spans"][0]["description"] == f"S{id}, S{id+1}" + assert transaction["spans"][0]["description"] == f"S{id}, S{id + 1}" assert transaction["spans"][1]["op"] == "cache.get" assert transaction["spans"][1]["description"] == f"S{id}" assert transaction["spans"][2]["op"] == "cache.get" - assert transaction["spans"][2]["description"] == f"S{id+1}" + assert transaction["spans"][2]["description"] == f"S{id + 1}" assert transaction["spans"][3]["op"] == "cache.put" assert transaction["spans"][3]["description"] == f"S{id}" assert transaction["spans"][4]["op"] == "cache.get" - assert transaction["spans"][4]["description"] == f"S{id}, S{id+1}" + assert transaction["spans"][4]["description"] == f"S{id}, S{id + 1}" assert transaction["spans"][5]["op"] == "cache.get" assert transaction["spans"][5]["description"] == f"S{id}" assert transaction["spans"][6]["op"] == "cache.get" - assert transaction["spans"][6]["description"] == f"S{id+1}" + assert transaction["spans"][6]["description"] == f"S{id + 1}" @pytest.mark.forked @@ -578,20 +578,20 @@ def test_cache_spans_set_many(sentry_init, capture_events, use_django_caching): from django.core.cache import cache with sentry_sdk.start_transaction(): - cache.set_many({f"S{id}": "Sensitive1", f"S{id+1}": "Sensitive2"}) + cache.set_many({f"S{id}": "Sensitive1", f"S{id + 1}": "Sensitive2"}) cache.get(f"S{id}") (transaction,) = events assert len(transaction["spans"]) == 4 assert transaction["spans"][0]["op"] == "cache.put" - assert transaction["spans"][0]["description"] == f"S{id}, S{id+1}" + assert transaction["spans"][0]["description"] == f"S{id}, S{id + 1}" assert transaction["spans"][1]["op"] == "cache.put" assert transaction["spans"][1]["description"] == f"S{id}" assert transaction["spans"][2]["op"] == "cache.put" - assert transaction["spans"][2]["description"] == f"S{id+1}" + assert transaction["spans"][2]["description"] == f"S{id + 1}" assert transaction["spans"][3]["op"] == "cache.get" assert transaction["spans"][3]["description"] == f"S{id}" diff --git a/tests/integrations/excepthook/test_excepthook.py b/tests/integrations/excepthook/test_excepthook.py index 745f62d818..5a19b4f985 100644 --- a/tests/integrations/excepthook/test_excepthook.py +++ b/tests/integrations/excepthook/test_excepthook.py @@ -32,9 +32,7 @@ def capture_envelope(self, envelope): frame_value = "LOL" 1/0 - """.format( - transport=transport, options=options - ) + """.format(transport=transport, options=options) ) ) @@ -75,9 +73,7 @@ def capture_envelope(self, envelope): frame_value = "LOL" 1/0 - """.format( - transport=transport, options=options - ) + """.format(transport=transport, options=options) ) ) diff --git a/tests/integrations/fastapi/test_fastapi.py b/tests/integrations/fastapi/test_fastapi.py index 3d79da92cc..a69978ded4 100644 --- a/tests/integrations/fastapi/test_fastapi.py +++ b/tests/integrations/fastapi/test_fastapi.py @@ -271,9 +271,9 @@ def test_response_status_code_ok_in_transaction_context(sentry_init, capture_env assert transaction["type"] == "transaction" assert len(transaction["contexts"]) > 0 - assert ( - "response" in transaction["contexts"].keys() - ), "Response context not found in transaction" + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) assert transaction["contexts"]["response"]["status_code"] == 200 @@ -307,9 +307,9 @@ def test_response_status_code_error_in_transaction_context( assert transaction["type"] == "transaction" assert len(transaction["contexts"]) > 0 - assert ( - "response" in transaction["contexts"].keys() - ), "Response context not found in transaction" + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) assert transaction["contexts"]["response"]["status_code"] == 500 @@ -338,9 +338,9 @@ def test_response_status_code_not_found_in_transaction_context( assert transaction["type"] == "transaction" assert len(transaction["contexts"]) > 0 - assert ( - "response" in transaction["contexts"].keys() - ), "Response context not found in transaction" + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) assert transaction["contexts"]["response"]["status_code"] == 404 diff --git a/tests/integrations/flask/test_flask.py b/tests/integrations/flask/test_flask.py index 49ee684797..e117b98ca9 100644 --- a/tests/integrations/flask/test_flask.py +++ b/tests/integrations/flask/test_flask.py @@ -943,9 +943,9 @@ def test_response_status_code_ok_in_transaction_context( assert transaction["type"] == "transaction" assert len(transaction["contexts"]) > 0 - assert ( - "response" in transaction["contexts"].keys() - ), "Response context not found in transaction" + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) assert transaction["contexts"]["response"]["status_code"] == 200 @@ -970,9 +970,9 @@ def test_response_status_code_not_found_in_transaction_context( assert transaction["type"] == "transaction" assert len(transaction["contexts"]) > 0 - assert ( - "response" in transaction["contexts"].keys() - ), "Response context not found in transaction" + assert "response" in transaction["contexts"].keys(), ( + "Response context not found in transaction" + ) assert transaction["contexts"]["response"]["status_code"] == 404 diff --git a/tests/integrations/gcp/test_gcp.py b/tests/integrations/gcp/test_gcp.py index 22d104c817..d088f134fe 100644 --- a/tests/integrations/gcp/test_gcp.py +++ b/tests/integrations/gcp/test_gcp.py @@ -101,14 +101,14 @@ def inner(code, subprocess_kwargs=()): subprocess.check_call( [sys.executable, "setup.py", "sdist", "-d", os.path.join(tmpdir, "..")], - **subprocess_kwargs + **subprocess_kwargs, ) subprocess.check_call( "pip install ../*.tar.gz -t .", cwd=tmpdir, shell=True, - **subprocess_kwargs + **subprocess_kwargs, ) stream = os.popen("python {}/main.py".format(tmpdir)) @@ -280,7 +280,8 @@ def cloud_function(functionhandler, event): def test_traces_sampler_gets_correct_values_in_sampling_context( - run_cloud_function, DictionaryContaining # noqa:N803 + run_cloud_function, + DictionaryContaining, # noqa:N803 ): # TODO: There are some decent sized hacks below. For more context, see the # long comment in the test of the same name in the AWS integration. The diff --git a/tests/integrations/gql/test_gql.py b/tests/integrations/gql/test_gql.py index f87fb974d0..147f7a06a8 100644 --- a/tests/integrations/gql/test_gql.py +++ b/tests/integrations/gql/test_gql.py @@ -44,16 +44,16 @@ def _make_erroneous_query(capture_events): with pytest.raises(TransportQueryError): _execute_mock_query(response_json) - assert ( - len(events) == 1 - ), "the sdk captured %d events, but 1 event was expected" % len(events) + assert len(events) == 1, ( + "the sdk captured %d events, but 1 event was expected" % len(events) + ) (event,) = events (exception,) = event["exception"]["values"] - assert ( - exception["type"] == "TransportQueryError" - ), "%s was captured, but we expected a TransportQueryError" % exception(type) + assert exception["type"] == "TransportQueryError", ( + "%s was captured, but we expected a TransportQueryError" % exception(type) + ) assert "request" in event @@ -79,12 +79,12 @@ def test_real_gql_request_no_error(sentry_init, capture_events): result = _execute_mock_query(response_json) - assert ( - result == response_data - ), "client.execute returned a different value from what it received from the server" - assert ( - len(events) == 0 - ), "the sdk captured an event, even though the query was successful" + assert result == response_data, ( + "client.execute returned a different value from what it received from the server" + ) + assert len(events) == 0, ( + "the sdk captured an event, even though the query was successful" + ) def test_real_gql_request_with_error_no_pii(sentry_init, capture_events): diff --git a/tests/integrations/httpx/test_httpx.py b/tests/integrations/httpx/test_httpx.py index ba2575ce59..4fd5275fb7 100644 --- a/tests/integrations/httpx/test_httpx.py +++ b/tests/integrations/httpx/test_httpx.py @@ -328,7 +328,8 @@ def test_option_trace_propagation_targets( integrations=[HttpxIntegration()], ) - with sentry_sdk.start_transaction(): # Must be in a transaction to propagate headers + # Must be in a transaction to propagate headers + with sentry_sdk.start_transaction(): if asyncio.iscoroutinefunction(httpx_client.get): asyncio.get_event_loop().run_until_complete(httpx_client.get(url)) else: diff --git a/tests/integrations/langchain/test_langchain.py b/tests/integrations/langchain/test_langchain.py index af4b6b8c56..ba49b2e508 100644 --- a/tests/integrations/langchain/test_langchain.py +++ b/tests/integrations/langchain/test_langchain.py @@ -223,9 +223,9 @@ def test_langchain_agent( assert "5" in chat_spans[1]["data"][SPANDATA.GEN_AI_RESPONSE_TEXT] # Verify tool calls are recorded when PII is enabled - assert SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS in chat_spans[0].get( - "data", {} - ), "Tool calls should be recorded when send_default_pii=True and include_prompts=True" + assert SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS in chat_spans[0].get("data", {}), ( + "Tool calls should be recorded when send_default_pii=True and include_prompts=True" + ) tool_calls_data = chat_spans[0]["data"][SPANDATA.GEN_AI_RESPONSE_TOOL_CALLS] assert isinstance(tool_calls_data, (list, str)) # Could be serialized if isinstance(tool_calls_data, str): @@ -261,9 +261,9 @@ def test_langchain_agent( span_data = chat_span.get("data", {}) if SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS in span_data: tools_data = span_data[SPANDATA.GEN_AI_REQUEST_AVAILABLE_TOOLS] - assert ( - tools_data is not None - ), "Available tools should always be recorded regardless of PII settings" + assert tools_data is not None, ( + "Available tools should always be recorded regardless of PII settings" + ) def test_langchain_error(sentry_init, capture_events): @@ -537,9 +537,9 @@ def test_span_map_is_instance_variable(): callback2 = SentryLangchainCallback(max_span_map_size=100, include_prompts=True) # Verify they have different span_map instances - assert ( - callback1.span_map is not callback2.span_map - ), "span_map should be an instance variable, not shared between instances" + assert callback1.span_map is not callback2.span_map, ( + "span_map should be an instance variable, not shared between instances" + ) def test_langchain_callback_manager(sentry_init): diff --git a/tests/integrations/langgraph/test_langgraph.py b/tests/integrations/langgraph/test_langgraph.py index 5e35f772f5..1510305b06 100644 --- a/tests/integrations/langgraph/test_langgraph.py +++ b/tests/integrations/langgraph/test_langgraph.py @@ -333,7 +333,6 @@ async def original_ainvoke(self, *args, **kwargs): async def run_test(): with start_transaction(): - wrapped_ainvoke = _wrap_pregel_ainvoke(original_ainvoke) result = await wrapped_ainvoke(pregel, test_state) return result @@ -394,7 +393,6 @@ def original_invoke(self, *args, **kwargs): raise Exception("Graph execution failed") with start_transaction(), pytest.raises(Exception, match="Graph execution failed"): - wrapped_invoke = _wrap_pregel_invoke(original_invoke) wrapped_invoke(pregel, test_state) @@ -426,7 +424,6 @@ async def run_error_test(): with start_transaction(), pytest.raises( Exception, match="Async graph execution failed" ): - wrapped_ainvoke = _wrap_pregel_ainvoke(original_ainvoke) await wrapped_ainvoke(pregel, test_state) @@ -482,7 +479,6 @@ def test_pregel_invoke_with_different_graph_names( pregel = MockPregelInstance(graph_name) if graph_name else MockPregelInstance() if not graph_name: - delattr(pregel, "name") delattr(pregel, "graph_name") @@ -490,7 +486,6 @@ def original_invoke(self, *args, **kwargs): return {"result": "test"} with start_transaction(): - wrapped_invoke = _wrap_pregel_invoke(original_invoke) wrapped_invoke(pregel, {"messages": []}) diff --git a/tests/integrations/opentelemetry/test_span_processor.py b/tests/integrations/opentelemetry/test_span_processor.py index ec5cf6af23..cbee14f4d6 100644 --- a/tests/integrations/opentelemetry/test_span_processor.py +++ b/tests/integrations/opentelemetry/test_span_processor.py @@ -549,8 +549,10 @@ def test_pruning_old_spans_on_start(): current_time_minutes = int(time.time() / 60) span_processor.open_spans = { current_time_minutes - 3: {"111111111abcdef"}, # should stay - current_time_minutes - - 11: {"2222222222abcdef", "3333333333abcdef"}, # should go + current_time_minutes - 11: { + "2222222222abcdef", + "3333333333abcdef", + }, # should go } span_processor.on_start(otel_span, parent_context) @@ -599,8 +601,10 @@ def test_pruning_old_spans_on_end(): span_processor.open_spans = { current_time_minutes: {"1234567890abcdef"}, # should go (because it is closed) current_time_minutes - 3: {"111111111abcdef"}, # should stay - current_time_minutes - - 11: {"2222222222abcdef", "3333333333abcdef"}, # should go + current_time_minutes - 11: { + "2222222222abcdef", + "3333333333abcdef", + }, # should go } span_processor.on_end(otel_span) diff --git a/tests/integrations/rq/test_rq.py b/tests/integrations/rq/test_rq.py index e445b588be..23603ad91d 100644 --- a/tests/integrations/rq/test_rq.py +++ b/tests/integrations/rq/test_rq.py @@ -97,7 +97,9 @@ def test_transport_shutdown(sentry_init, capture_events_forksafe): def test_transaction_with_error( - sentry_init, capture_events, DictionaryContaining # noqa:N803 + sentry_init, + capture_events, + DictionaryContaining, # noqa:N803 ): sentry_init(integrations=[RqIntegration()], traces_sample_rate=1.0) events = capture_events() @@ -195,7 +197,9 @@ def test_tracing_disabled( def test_transaction_no_error( - sentry_init, capture_events, DictionaryContaining # noqa:N803 + sentry_init, + capture_events, + DictionaryContaining, # noqa:N803 ): sentry_init(integrations=[RqIntegration()], traces_sample_rate=1.0) events = capture_events() @@ -222,7 +226,9 @@ def test_transaction_no_error( def test_traces_sampler_gets_correct_values_in_sampling_context( - sentry_init, DictionaryContaining, ObjectDescribedBy # noqa:N803 + sentry_init, + DictionaryContaining, + ObjectDescribedBy, # noqa:N803 ): traces_sampler = mock.Mock(return_value=True) sentry_init(integrations=[RqIntegration()], traces_sampler=traces_sampler) diff --git a/tests/integrations/spark/test_spark.py b/tests/integrations/spark/test_spark.py index 0ea2770b89..c5bb70f4d1 100644 --- a/tests/integrations/spark/test_spark.py +++ b/tests/integrations/spark/test_spark.py @@ -90,7 +90,6 @@ def test_initialize_spark_integration_after_spark_context_init( @pytest.fixture def sentry_listener(): - listener = SentryListener() return listener diff --git a/tests/integrations/sys_exit/test_sys_exit.py b/tests/integrations/sys_exit/test_sys_exit.py index 81a950c7c0..a9909ae3c2 100644 --- a/tests/integrations/sys_exit/test_sys_exit.py +++ b/tests/integrations/sys_exit/test_sys_exit.py @@ -66,6 +66,6 @@ def test_sys_exit_integration_not_auto_enabled(sentry_init, capture_events): "sys.exit should not be patched, but it must have been because it did not raise SystemExit" ) - assert ( - len(events) == 0 - ), "No events should have been captured because sys.exit should not have been patched" + assert len(events) == 0, ( + "No events should have been captured because sys.exit should not have been patched" + ) diff --git a/tests/integrations/threading/test_threading.py b/tests/integrations/threading/test_threading.py index 4577c846d8..799298910b 100644 --- a/tests/integrations/threading/test_threading.py +++ b/tests/integrations/threading/test_threading.py @@ -208,9 +208,9 @@ def do_some_work(): t.join() # check if the initial scope data is not modified by the started thread - assert initial_iso_scope._tags == { - "initial_tag": "initial_value" - }, "The isolation scope in the main thread should not be modified by the started thread." + assert initial_iso_scope._tags == {"initial_tag": "initial_value"}, ( + "The isolation scope in the main thread should not be modified by the started thread." + ) @pytest.mark.parametrize( diff --git a/tests/integrations/unleash/testutils.py b/tests/integrations/unleash/testutils.py index 07b065e2f0..4e91b6190b 100644 --- a/tests/integrations/unleash/testutils.py +++ b/tests/integrations/unleash/testutils.py @@ -34,7 +34,6 @@ def mock_unleash_client(): class MockUnleashClient: - def __init__(self, *a, **kw): self.features = { "hello": True, diff --git a/tests/integrations/unraisablehook/test_unraisablehook.py b/tests/integrations/unraisablehook/test_unraisablehook.py index 2f97886ce8..dbe8164cf5 100644 --- a/tests/integrations/unraisablehook/test_unraisablehook.py +++ b/tests/integrations/unraisablehook/test_unraisablehook.py @@ -42,9 +42,7 @@ def capture_envelope(self, envelope): undeletable = Undeletable() del undeletable - """.format( - transport=transport, options=options - ) + """.format(transport=transport, options=options) ) ) diff --git a/tests/integrations/wsgi/test_wsgi.py b/tests/integrations/wsgi/test_wsgi.py index 656fc1757f..a741d1c57b 100644 --- a/tests/integrations/wsgi/test_wsgi.py +++ b/tests/integrations/wsgi/test_wsgi.py @@ -136,7 +136,10 @@ def test_keyboard_interrupt_is_captured(sentry_init, capture_events): def test_transaction_with_error( - sentry_init, crashing_app, capture_events, DictionaryContaining # noqa:N803 + sentry_init, + crashing_app, + capture_events, + DictionaryContaining, # noqa:N803 ): def dogpark(environ, start_response): raise ValueError("Fetch aborted. The ball was not returned.") @@ -173,7 +176,9 @@ def dogpark(environ, start_response): def test_transaction_no_error( - sentry_init, capture_events, DictionaryContaining # noqa:N803 + sentry_init, + capture_events, + DictionaryContaining, # noqa:N803 ): def dogpark(environ, start_response): start_response("200 OK", []) diff --git a/tests/test_basics.py b/tests/test_basics.py index 45303c9a59..b0b577b796 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1218,6 +1218,6 @@ def recurse(): # On my machine, it takes about 100-200ms to capture the exception, # so this limit should be generous enough. - assert ( - capture_end_time - capture_start_time < 10**9 * 2 - ), "stacktrace capture took too long, check that frame limit is set correctly" + assert capture_end_time - capture_start_time < 10**9 * 2, ( + "stacktrace capture took too long, check that frame limit is set correctly" + ) diff --git a/tests/test_client.py b/tests/test_client.py index a02ea6e56a..ff3f61d702 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -608,9 +608,7 @@ def capture_envelope(self, envelope): for _ in range({num_messages}): capture_message("HI") - """.format( - transport=transport, options=options, num_messages=num_messages - ) + """.format(transport=transport, options=options, num_messages=num_messages) ) ) @@ -1194,9 +1192,9 @@ def test_spotlight_option( client = sentry_sdk.get_client() url = client.spotlight.url if client.spotlight else None - assert ( - url == spotlight_url_expected - ), f"With config {client_option} and env {env_var_value}" + assert url == spotlight_url_expected, ( + f"With config {client_option} and env {env_var_value}" + ) class IssuesSamplerTestConfig: diff --git a/tests/test_conftest.py b/tests/test_conftest.py index 3b8cd098f5..a36fe17894 100644 --- a/tests/test_conftest.py +++ b/tests/test_conftest.py @@ -22,7 +22,9 @@ ], ) def test_string_containing( - test_string, expected_result, StringContaining # noqa: N803 + test_string, + expected_result, + StringContaining, # noqa: N803 ): assert (test_string == StringContaining("dogs")) is expected_result @@ -46,7 +48,9 @@ def test_string_containing( ], ) def test_dictionary_containing( - test_dict, expected_result, DictionaryContaining # noqa: N803 + test_dict, + expected_result, + DictionaryContaining, # noqa: N803 ): assert ( test_dict == DictionaryContaining({"dogs": "yes", "cats": "maybe"}) diff --git a/tests/test_gevent.py b/tests/test_gevent.py index d330760adf..05fa6ed2e8 100644 --- a/tests/test_gevent.py +++ b/tests/test_gevent.py @@ -104,13 +104,11 @@ def test_transport_works_gevent( (compression_level is None) or ( # setting compression level to 0 means don't compress - compression_level - > 0 + compression_level > 0 ) ) and ( # if we couldn't resolve to a known algo, we don't compress - compression_algo - != "" + compression_algo != "" ) assert capturing_server.captured[0].compressed == should_compress diff --git a/tests/test_propagationcontext.py b/tests/test_propagationcontext.py index 078a69c72b..e014012956 100644 --- a/tests/test_propagationcontext.py +++ b/tests/test_propagationcontext.py @@ -155,8 +155,7 @@ def mock_random_class(seed): ) assert ( - ctx.dynamic_sampling_context["sample_rand"] - == f"{expected_interval[0]:.6f}" # noqa: E231 + ctx.dynamic_sampling_context["sample_rand"] == f"{expected_interval[0]:.6f}" # noqa: E231 ) assert mock_randrange.call_count == 1 assert mock_randrange.call_args[0] == ( diff --git a/tests/test_transport.py b/tests/test_transport.py index e493515e9a..68669fa24d 100644 --- a/tests/test_transport.py +++ b/tests/test_transport.py @@ -137,13 +137,11 @@ def test_transport_works( (compression_level is None) or ( # setting compression level to 0 means don't compress - compression_level - > 0 + compression_level > 0 ) ) and ( # if we couldn't resolve to a known algo, we don't compress - compression_algo - != "" + compression_algo != "" ) assert capturing_server.captured[0].compressed == should_compress diff --git a/tests/test_utils.py b/tests/test_utils.py index b268fbd57b..e1c6786e1b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -196,9 +196,9 @@ def test_datetime_from_isoformat_with_py_36_or_lower(input_str, expected_output) ], ) def test_env_to_bool(env_var_value, strict, expected): - assert ( - env_to_bool(env_var_value, strict=strict) == expected - ), f"Value: {env_var_value}, strict: {strict}" + assert env_to_bool(env_var_value, strict=strict) == expected, ( + f"Value: {env_var_value}, strict: {strict}" + ) @pytest.mark.parametrize( diff --git a/tests/tracing/test_sampling.py b/tests/tracing/test_sampling.py index 1761a3dbac..63da5a1399 100644 --- a/tests/tracing/test_sampling.py +++ b/tests/tracing/test_sampling.py @@ -212,7 +212,8 @@ def mock_set_initial_sampling_decision(_, sampling_context): def test_passes_custom_sampling_context_from_start_transaction_to_traces_sampler( - sentry_init, DictionaryContaining # noqa: N803 + sentry_init, + DictionaryContaining, # noqa: N803 ): traces_sampler = mock.Mock() sentry_init(traces_sampler=traces_sampler) @@ -251,7 +252,9 @@ def test_sample_rate_affects_errors(sentry_init, capture_events): ], ) def test_warns_and_sets_sampled_to_false_on_invalid_traces_sampler_return_value( - sentry_init, traces_sampler_return_value, StringContaining # noqa: N803 + sentry_init, + traces_sampler_return_value, + StringContaining, # noqa: N803 ): sentry_init(traces_sampler=mock.Mock(return_value=traces_sampler_return_value)) diff --git a/tox.ini b/tox.ini index b993397389..e310a6245b 100644 --- a/tox.ini +++ b/tox.ini @@ -91,7 +91,7 @@ envlist = {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.38 + {py3.9,py3.12,py3.13}-boto3-v1.40.39 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -157,7 +157,7 @@ envlist = {py3.7,py3.8}-grpc-v1.32.0 {py3.7,py3.9,py3.10}-grpc-v1.47.5 {py3.7,py3.11,py3.12}-grpc-v1.62.3 - {py3.9,py3.12,py3.13}-grpc-v1.75.0 + {py3.9,py3.12,py3.13}-grpc-v1.75.1 {py3.6,py3.8,py3.9}-httpx-v0.16.1 {py3.6,py3.9,py3.10}-httpx-v0.20.0 @@ -396,7 +396,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.38: boto3==1.40.38 + boto3-v1.40.39: boto3==1.40.39 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -482,7 +482,7 @@ deps = grpc-v1.32.0: grpcio==1.32.0 grpc-v1.47.5: grpcio==1.47.5 grpc-v1.62.3: grpcio==1.62.3 - grpc-v1.75.0: grpcio==1.75.0 + grpc-v1.75.1: grpcio==1.75.1 grpc: protobuf grpc: mypy-protobuf grpc: types-protobuf @@ -803,11 +803,9 @@ basepython = py3.12: python3.12 py3.13: python3.13 - # Python version is pinned here because flake8 actually behaves differently - # depending on which version is used. You can patch this out to point to - # some random Python 3 binary, but then you get guaranteed mismatches with - # CI. Other tools such as mypy and black have options that pin the Python - # version. + # Python version is pinned here for consistency across environments. + # Tools like ruff and mypy have options that pin the target Python + # version (configured in pyproject.toml), ensuring consistent behavior. linters: python3.12 commands = @@ -823,6 +821,6 @@ commands = [testenv:linters] commands = - flake8 tests sentry_sdk - black --check tests sentry_sdk + ruff check tests sentry_sdk + ruff format --check tests sentry_sdk mypy sentry_sdk From 7e493b481e2cf9b5ded12c3c66be22abe059168f Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 26 Sep 2025 15:44:24 +0200 Subject: [PATCH 03/20] docs: Update CONTRIBUTING.md (#4870) ### Description Removed hub mention, some obsolete/unclear instructions, added a note about defensiveness, toxgen. #### Issues #### Reminders - Please add tests to validate your changes, and lint your code using `tox -e linters`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) --- CONTRIBUTING.md | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 89330087d9..753b169214 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,7 +78,7 @@ We test against a number of Python language and library versions, which are auto The tox CLI tool is required to run the tests locally. Follow [the installation instructions](https://tox.wiki/en/latest/installation.html) for tox. Dependencies are installed for you when you run the command below, but _you_ need to bring an appropriate Python interpreter. -[Pyenv](https://github.com/pyenv/pyenv) is a cross-platform utility for managing Python versions. You can also use a conventional package manager, but not all versions may be distributed in the package manager of your choice. For macOS, Versions 3.8 and up can be installed with Homebrew. +[Pyenv](https://github.com/pyenv/pyenv) is a cross-platform utility for managing Python versions. You can also use a conventional package manager, but not all versions may be distributed in the package manager of your choice. For macOS, versions 3.8 and up can be installed with Homebrew. An environment consists of the Python major and minor version and the library name and version. The exception to the rule is that you can provide `common` instead of the library information. The environments tied to a specific library usually run the corresponding test suite, while `common` targets all tests but skips those that require uninstalled dependencies. @@ -109,29 +109,20 @@ tox -p auto -o -e -- ## Adding a New Integration 1. Write the integration. - - - Instrument all application instances by default. Prefer global signals/patches instead of configuring a specific instance. Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. - - - Everybody monkeypatches. That means: - - - Make sure to think about conflicts with other monkeypatches when monkeypatching. - - - You don't need to feel bad about it. - + - Instrument all application instances by default. Prefer global signals/patches. + - Don't make the user pass anything to your integration for anything to work. Aim for zero configuration. + - Everybody monkeypatches. That means you don't need to feel bad about it. - Make sure your changes don't break end user contracts. The SDK should never alter the expected behavior of the underlying library or framework from the user's perspective and it shouldn't have any side effects. - - - Avoid modifying the hub, registering a new client or the like. The user drives the client, and the client owns integrations. - - - Allow the user to turn off the integration by changing the client. Check `Hub.current.get_integration(MyIntegration)` from within your signal handlers to see if your integration is still active before you do anything impactful (such as sending an event). + - Be defensive. Don't assume the code you're patching will stay the same forever, especially if it's an internal function. Allow for future variability whenever it makes sense. + - Avoid registering a new client or the like. The user drives the client, and the client owns integrations. + - Allow the user to turn off the integration by changing the client. Check `sentry_sdk.get_client().get_integration(MyIntegration)` from within your signal handlers to see if your integration is still active before you do anything impactful (such as sending an event). 2. Write tests. - - - Consider the minimum versions supported, and test each version in a separate env in `tox.ini`. - + - Consider the minimum versions supported, and document in `_MIN_VERSIONS` in `integrations/__init__.py`. - Create a new folder in `tests/integrations/`, with an `__init__` file that skips the entire suite if the package is not installed. + - Add the test suite to the script generating our test matrix. See [`scripts/populate_tox/README.md`](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md#add-a-new-test-suite). 3. Update package metadata. - - We use `extras_require` in `setup.py` to communicate minimum version requirements for integrations. People can use this in combination with tools like Poetry or Pipenv to detect conflicts between our supported versions and their used versions programmatically. Do not set upper bounds on version requirements as people are often faster in adopting new versions of a web framework than we are in adding them to the test matrix or our package metadata. @@ -140,8 +131,6 @@ tox -p auto -o -e -- 5. Merge docs after new version has been released. The docs are built and deployed after each merge, so your changes should go live in a few minutes. -6. (optional, if possible) Update data in [`sdk_updates.py`](https://github.com/getsentry/sentry/blob/master/src/sentry/sdk_updates.py) to give users in-app suggestions to use your integration. This step will only apply to some integrations. - ## Releasing a New Version _(only relevant for Python SDK core team)_ From e13b7a7b4b022df24af3eaaa93884d9c9ea3b6a7 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 26 Sep 2025 15:44:45 +0200 Subject: [PATCH 04/20] feat: Add script to determine lowest supported versions (#4867) ### Description With toxgen, we now have an automated way of detecting the effective minimum version of a framework. This depends on both package metadata (e.g., the min version needs to support at least some of the Python versions we support) as well as on our test matrix and explicitly defined lower bounds (in `sentry_sdk/integrations/__init__.py`'s `_MIN_VERSIONS`). When we release a new major in which we drop support for a Python version, we can use this script to automatically generate the new `_MIN_VERSIONS`. Example output: ``` Effective minimal versions: - The format is the same as _MIN_VERSIONS in sentry_sdk/integrations/__init__.py for easy replacing. - When updating these, make sure to also update: - The docs page for the integration - The lower bounds in extras_require in setup.py "aiohttp": (3, 4, 4), "anthropic": (0, 16, 0), "ariadne": (0, 20, 1), "arq": (0, 23), "asyncpg": (0, 23, 0), "beam": (2, 14, 0), "boto3": (1, 12, 49), "bottle": (0, 12, 25), "celery": (4, 4, 7), "chalice": (1, 16, 0), "clickhouse_driver": (0, 2, 9), "cohere": (5, 4, 0), "django": (1, 11, 29), "dramatiq": (1, 9, 0), "falcon": (1, 4, 1), "fastapi": (0, 79, 1), "flask": (1, 1, 4), "gql": (3, 4, 1), "graphene": (3, 3), "grpc": (1, 32, 0), "httpx": (0, 16, 1), "huey": (2, 1, 3), "huggingface_hub": (0, 24, 7), "langchain": (0, 1, 20), "langgraph": (0, 6, 7), "launchdarkly": (9, 8, 1), "litestar": (2, 0, 1), "loguru": (0, 7, 3), "openai": (1, 0, 1), "openai_agents": (0, 0, 19), "openfeature": (0, 7, 5), "pure_eval": (0, 0, 3), "pymongo": (3, 5, 1), "pyramid": (1, 8, 6), "quart": (0, 16, 3), "ray": (2, 7, 2), "redis": (2, 10, 6), "redis_py_cluster_legacy": (1, 3, 6), "requests": (2, 12, 5), "rq": (0, 8, 2), "sanic": (0, 8, 3), "spark": (3, 0, 3), "sqlalchemy": (1, 3, 24), "starlette": (0, 16, 0), "starlite": (1, 48, 1), "statsig": (0, 55, 3), "strawberry": (0, 209, 8), "tornado": (6, 0, 4), "trytond": (4, 6, 22), "typer": (0, 15, 4), "unleash": (6, 0, 1), ``` #### Issues Ref https://github.com/getsentry/sentry-python/issues/4047 #### Reminders - Please add tests to validate your changes, and lint your code using `tox -e linters`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) --- scripts/populate_tox/populate_tox.py | 4 +- scripts/update_integration_support.py | 55 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 scripts/update_integration_support.py diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index d625c1da72..c0bf7f1a9f 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -665,7 +665,7 @@ def _normalize_release(release: dict) -> dict: return normalized -def main(fail_on_changes: bool = False) -> None: +def main(fail_on_changes: bool = False) -> dict[str, list]: """ Generate tox.ini from the tox.jinja template. @@ -825,6 +825,8 @@ def main(fail_on_changes: bool = False) -> None: "files to reflect the new test targets." ) + return packages + if __name__ == "__main__": fail_on_changes = len(sys.argv) == 2 and sys.argv[1] == "--fail-on-changes" diff --git a/scripts/update_integration_support.py b/scripts/update_integration_support.py new file mode 100644 index 0000000000..7e686b2729 --- /dev/null +++ b/scripts/update_integration_support.py @@ -0,0 +1,55 @@ +""" +Small utility to determine the actual minimum supported version of each framework/library. +""" + +import os +import sys +from textwrap import dedent + +populate_tox_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "populate_tox" +) +sys.path.append(populate_tox_dir) + +from populate_tox import main + + +def update(): + print("Running populate_tox.py...") + packages = main() + + print("Figuring out the lowest supported version of integrations...") + min_versions = [] + + for _, integrations in packages.items(): + for integration in integrations: + min_versions.append( + (integration["integration_name"], str(integration["releases"][0])) + ) + + min_versions = sorted( + set( + [ + (integration, tuple([int(v) for v in min_version.split(".")])) + for integration, min_version in min_versions + ] + ) + ) + + print() + print("Effective minimal versions:") + print( + dedent(""" + - The format is the same as _MIN_VERSIONS in sentry_sdk/integrations/__init__.py for easy replacing. + - When updating these, make sure to also update: + - The docs page for the integration + - The lower bounds in extras_require in setup.py + """) + ) + print() + for integration, min_version in min_versions: + print(f'"{integration}": {min_version},') + + +if __name__ == "__main__": + update() From 5e24a79ffe95abcf0dfaee2fa7dd7b4cc72e0b2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 06:54:47 +0000 Subject: [PATCH 05/20] build(deps): update shibuya requirement from <2025.9.22 (#4871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the requirements on [shibuya](https://github.com/lepture/shibuya) to permit the latest version.
Release notes

Sourced from shibuya's releases.

2025.9.25

   🐞 Bug Fixes

    View changes on GitHub
Changelog

Sourced from shibuya's changelog.

2025.9.25

  • Fix: Add dark theme support for sphinxcontrib-mermaid extension.
  • Fix: Fix building error when toc is empty, via :issue:91.
  • Fix: Improve shibuya.sponsors extension.

2025.9.24

  • New: add a shibuya.sponsors extension.
  • Fix: remove useless pygments css files.
  • Fix: hide username when repository name is too long.
  • Fix: update head backrop blur style.
  • Fix: update base template's block theme_scripts to themescripts.

2025.9.23

  • Fix: fix pageurl context when it is None, via :issue:90.

2025.9.22

  • Refactor: Update python code structure.
  • Fix: Support for Pygments extension, via :issue:89.
  • Fix: Build pygments styles dynamically.
  • Fix: Fix text overflow for math block and pre.
  • Fix: Fix style for buysellads.

2025.8.16

  • Update: Use tailwindcss v4.
  • Fix: Improve copybutton's position in code blocks.
  • Fix: Improve style for sphinx-contributors.
  • Fix: Fix JS errors when scrolling on landing page.

2025.7.24

  • Fix: Improve accessibility for nav links, you can now using keyboard to open sub nav links.
  • Fix: Update sphinx-design's style for iconify-icon.

2025.7.14

  • New: Add data-accent-color style for :ref:sphinx-iconify.
  • New: Add outline variant style for sphinx-design's tab-set.

... (truncated)

Commits
  • e3832e3 chore: release 2025.9.25
  • fb9446b docs: update changelog
  • b61847f chore: ruff format
  • 4cea3db docs: add a sponsors page
  • 214dde7 fix: update sponsors style
  • f623d24 fix: improve SponsorsDirective
  • cf082e1 fix: prevent building error when toc is empty
  • 8c6e8b7 fix: dark theme support for sphinxcontrib-mermaid extension
  • 0055315 chore: release 2025.9.24
  • 53d76dc fix: update block theme_scripts to themescripts
  • Additional commits viewable in compare view

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--- > [!NOTE] > Removes the version cap on `shibuya` in `requirements-docs.txt` to allow the latest release. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit cbacc632a603a969a2042355a17349200d4d27e7. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ivana Kellyer --- requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-docs.txt b/requirements-docs.txt index b98ad4834a..81e04ba3ef 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,5 @@ gevent -shibuya<2025.9.22 +shibuya sphinx<8.2 sphinx-autodoc-typehints[type_comments]>=1.8.0 typing-extensions From 9b58d3155db85947cb951649f8651d3afdf559f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 06:57:35 +0000 Subject: [PATCH 06/20] =?UTF-8?q?ci:=20=F0=9F=A4=96=20Update=20test=20matr?= =?UTF-8?q?ix=20with=20new=20releases=20(09/29)=20(#4872)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update our test matrix with new releases of integrated frameworks and libraries. ## How it works - Scan PyPI for all supported releases of all frameworks we have a dedicated test suite for. - Pick a representative sample of releases to run our test suite against. We always test the latest and oldest supported version. - Update [tox.ini](https://github.com/getsentry/sentry-python/blob/master/tox.ini) with the new releases. ## Action required - If CI passes on this PR, it's safe to approve and merge. It means our integrations can handle new versions of frameworks that got pulled in. - If CI doesn't pass on this PR, this points to an incompatibility of either our integration or our test setup with a new version of a framework. - Check what the failures look like and either fix them, or update the [test config](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/config.py) and rerun [scripts/generate-test-files.sh](https://github.com/getsentry/sentry-python/blob/master/scripts/generate-test-files.sh). See [scripts/populate_tox/README.md](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md) for what configuration options are available. _____________________ _🤖 This PR was automatically created using [a GitHub action](https://github.com/getsentry/sentry-python/blob/master/.github/workflows/update-tox.yml)._ --- > [!NOTE] > Bumps test targets and dependency pins for anthropic, boto3, fastapi, and redis to their latest versions across releases.jsonl and tox.ini. > > - **Integrations matrix updates** > - **AI**: > - `anthropic`: `0.68.0` → `0.68.1` (envlist and deps) > - **Cloud**: > - `boto3`: `1.40.39` → `1.40.40` (envlist and deps) > - **Web**: > - `fastapi`: `0.117.1` → `0.118.0` (envlist and deps) > - **DB/Cache**: > - `redis`: `7.0.0b1` → `7.0.0b2` (envlist and deps) > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6c871c3bb56dac1e8540e1edb2dcc9f9bde2b451. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ivana Kellyer --- scripts/populate_tox/releases.jsonl | 8 ++++---- tox.ini | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index fa24b089cd..3532b61c75 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -26,7 +26,7 @@ {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.7", "version": "0.16.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.7", "version": "0.33.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.50.0", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.68.0", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.68.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.12.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.13.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.14.0", "yanked": false}} @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.39", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.40", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} @@ -66,7 +66,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Cython", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "falcon", "requires_python": ">=3.5", "version": "3.1.3", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Cython", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Typing :: Typed"], "name": "falcon", "requires_python": ">=3.8", "version": "4.1.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "fastapi", "requires_python": ">=3.8", "version": "0.105.0", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "fastapi", "requires_python": ">=3.8", "version": "0.117.1", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Framework :: Pydantic :: 2", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "fastapi", "requires_python": ">=3.8", "version": "0.118.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "fastapi", "requires_python": ">=3.6.1", "version": "0.79.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: FastAPI", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "fastapi", "requires_python": ">=3.7", "version": "0.92.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries"], "name": "gql", "requires_python": "", "version": "3.4.1", "yanked": false}} @@ -153,7 +153,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "redis", "requires_python": ">=3.7", "version": "4.6.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "redis", "requires_python": ">=3.8", "version": "5.3.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "redis", "requires_python": ">=3.9", "version": "6.4.0", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "redis", "requires_python": ">=3.9", "version": "7.0.0b1", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "redis", "requires_python": ">=3.9", "version": "7.0.0b2", "yanked": false}} {"info": {"classifiers": ["Development Status :: 3 - Alpha", "Environment :: Web Environment", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4"], "name": "redis-py-cluster", "requires_python": null, "version": "0.1.0", "yanked": false}} {"info": {"classifiers": [], "name": "redis-py-cluster", "requires_python": null, "version": "1.1.0", "yanked": false}} {"info": {"classifiers": [], "name": "redis-py-cluster", "requires_python": null, "version": "1.2.0", "yanked": false}} diff --git a/tox.ini b/tox.ini index e310a6245b..2354c66c7c 100644 --- a/tox.ini +++ b/tox.ini @@ -48,7 +48,7 @@ envlist = {py3.8,py3.11,py3.12}-anthropic-v0.16.0 {py3.8,py3.11,py3.12}-anthropic-v0.33.1 {py3.8,py3.11,py3.12}-anthropic-v0.50.0 - {py3.8,py3.12,py3.13}-anthropic-v0.68.0 + {py3.8,py3.12,py3.13}-anthropic-v0.68.1 {py3.9,py3.10,py3.11}-cohere-v5.4.0 {py3.9,py3.11,py3.12}-cohere-v5.9.4 @@ -91,7 +91,7 @@ envlist = {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.39 + {py3.9,py3.12,py3.13}-boto3-v1.40.40 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -114,7 +114,7 @@ envlist = {py3.7,py3.10,py3.11}-redis-v4.6.0 {py3.8,py3.11,py3.12}-redis-v5.3.1 {py3.9,py3.12,py3.13}-redis-v6.4.0 - {py3.9,py3.12,py3.13}-redis-v7.0.0b1 + {py3.9,py3.12,py3.13}-redis-v7.0.0b2 {py3.6}-redis_py_cluster_legacy-v1.3.6 {py3.6,py3.7,py3.8}-redis_py_cluster_legacy-v2.1.3 @@ -218,7 +218,7 @@ envlist = {py3.6,py3.9,py3.10}-fastapi-v0.79.1 {py3.7,py3.10,py3.11}-fastapi-v0.92.0 {py3.8,py3.10,py3.11}-fastapi-v0.105.0 - {py3.8,py3.12,py3.13}-fastapi-v0.117.1 + {py3.8,py3.12,py3.13}-fastapi-v0.118.0 # ~~~ Web 2 ~~~ @@ -334,7 +334,7 @@ deps = anthropic-v0.16.0: anthropic==0.16.0 anthropic-v0.33.1: anthropic==0.33.1 anthropic-v0.50.0: anthropic==0.50.0 - anthropic-v0.68.0: anthropic==0.68.0 + anthropic-v0.68.1: anthropic==0.68.1 anthropic: pytest-asyncio anthropic-v0.16.0: httpx<0.28.0 anthropic-v0.33.1: httpx<0.28.0 @@ -396,7 +396,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.39: boto3==1.40.39 + boto3-v1.40.40: boto3==1.40.40 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -423,7 +423,7 @@ deps = redis-v4.6.0: redis==4.6.0 redis-v5.3.1: redis==5.3.1 redis-v6.4.0: redis==6.4.0 - redis-v7.0.0b1: redis==7.0.0b1 + redis-v7.0.0b2: redis==7.0.0b2 redis: fakeredis!=1.7.4 redis: pytest<8.0.0 redis-v4.6.0: fakeredis<2.31.0 @@ -599,7 +599,7 @@ deps = fastapi-v0.79.1: fastapi==0.79.1 fastapi-v0.92.0: fastapi==0.92.0 fastapi-v0.105.0: fastapi==0.105.0 - fastapi-v0.117.1: fastapi==0.117.1 + fastapi-v0.118.0: fastapi==0.118.0 fastapi: httpx fastapi: pytest-asyncio fastapi: python-multipart From 2872675b8b37b3a654fc9cac5590f0de64183c9f Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Wed, 1 Oct 2025 08:53:19 +0200 Subject: [PATCH 07/20] fix(openai-agents): Move _set_agent_data call to ai_client_span function (#4876) ### Description We can set the agent data early, so that we get input model and other data, even if there's an exception while running the agent #### Issues Resolves: TET-1205 --- > [!NOTE] > Move `_set_agent_data` to `ai_client_span` so agent/model metadata is recorded when the span is created. > > - **Tracing (`sentry_sdk/integrations/openai_agents/spans/ai_client.py`)**: > - Set agent metadata in `ai_client_span` via `_set_agent_data` at span creation. > - Remove `_set_agent_data` from `update_ai_client_span` to avoid duplication. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 27e94cc89cd48716d9949fd402e04dda0f932e98. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- sentry_sdk/integrations/openai_agents/spans/ai_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/ai_client.py b/sentry_sdk/integrations/openai_agents/spans/ai_client.py index d325ae86e3..e215edfd26 100644 --- a/sentry_sdk/integrations/openai_agents/spans/ai_client.py +++ b/sentry_sdk/integrations/openai_agents/spans/ai_client.py @@ -28,12 +28,13 @@ def ai_client_span(agent, get_response_kwargs): # TODO-anton: remove hardcoded stuff and replace something that also works for embedding and so on span.set_data(SPANDATA.GEN_AI_OPERATION_NAME, "chat") + _set_agent_data(span, agent) + return span def update_ai_client_span(span, agent, get_response_kwargs, result): # type: (sentry_sdk.tracing.Span, Agent, dict[str, Any], Any) -> None - _set_agent_data(span, agent) _set_usage_data(span, result.usage) _set_input_data(span, get_response_kwargs) _set_output_data(span, result) From b838765160305aacc9b1ea0f42d6ca2e090c4b2e Mon Sep 17 00:00:00 2001 From: Alex Alderman Webb Date: Wed, 1 Oct 2025 15:45:14 +0200 Subject: [PATCH 08/20] feat: Option to not trace HTTP requests based on status codes (#4869) Add `trace_ignore_status_codes` option to exclude HTTP requests from tracing if they have certain status codes. A debug-level log message is printed if a transaction is dropped because its status code is ignored. By default no status codes automatically cause transactions to be dropped. Closes https://github.com/getsentry/sentry-python/issues/4812 --------- Co-authored-by: Anton Pirker --- sentry_sdk/consts.py | 10 ++ sentry_sdk/tracing.py | 37 +++++- tests/tracing/test_ignore_status_codes.py | 139 ++++++++++++++++++++++ 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 tests/tracing/test_ignore_status_codes.py diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 9e84dc3dd2..5bcc487037 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -40,6 +40,7 @@ class CompressionAlgo(Enum): from typing import Any from typing import Sequence from typing import Tuple + from typing import AbstractSet from typing_extensions import Literal from typing_extensions import TypedDict @@ -919,6 +920,7 @@ def __init__( max_stack_frames=DEFAULT_MAX_STACK_FRAMES, # type: Optional[int] enable_logs=False, # type: bool before_send_log=None, # type: Optional[Callable[[Log, Hint], Optional[Log]]] + trace_ignore_status_codes=frozenset(), # type: AbstractSet[int] ): # type: (...) -> None """Initialize the Sentry SDK with the given parameters. All parameters described here can be used in a call to `sentry_sdk.init()`. @@ -1307,6 +1309,14 @@ def __init__( function will be retained. If the function returns None, the log will not be sent to Sentry. + :param trace_ignore_status_codes: An optional property that disables tracing for + HTTP requests with certain status codes. + + Requests are not traced if the status code is contained in the provided set. + + If `trace_ignore_status_codes` is not provided, requests with any status code + may be traced. + :param _experiments: """ pass diff --git a/sentry_sdk/tracing.py b/sentry_sdk/tracing.py index a82b99ff4d..1697df1f22 100644 --- a/sentry_sdk/tracing.py +++ b/sentry_sdk/tracing.py @@ -30,6 +30,7 @@ from typing import Tuple from typing import Union from typing import TypeVar + from typing import Set from typing_extensions import TypedDict, Unpack @@ -970,6 +971,12 @@ def _get_scope_from_finish_args( return scope_or_hub + def _get_log_representation(self): + # type: () -> str + return "{op}transaction <{name}>".format( + op=("<" + self.op + "> " if self.op else ""), name=self.name + ) + def finish( self, scope=None, # type: Optional[sentry_sdk.Scope] @@ -1039,6 +1046,32 @@ def finish( super().finish(scope, end_timestamp) + status_code = self._data.get(SPANDATA.HTTP_STATUS_CODE) + if ( + status_code is not None + and status_code in client.options["trace_ignore_status_codes"] + ): + logger.debug( + "[Tracing] Discarding {transaction_description} because the HTTP status code {status_code} is matched by trace_ignore_status_codes: {trace_ignore_status_codes}".format( + transaction_description=self._get_log_representation(), + status_code=self._data[SPANDATA.HTTP_STATUS_CODE], + trace_ignore_status_codes=client.options[ + "trace_ignore_status_codes" + ], + ) + ) + if client.transport: + client.transport.record_lost_event( + "event_processor", data_category="transaction" + ) + + num_spans = len(self._span_recorder.spans) + 1 + client.transport.record_lost_event( + "event_processor", data_category="span", quantity=num_spans + ) + + self.sampled = False + if not self.sampled: # At this point a `sampled = None` should have already been resolved # to a concrete decision. @@ -1186,9 +1219,7 @@ def _set_initial_sampling_decision(self, sampling_context): """ client = sentry_sdk.get_client() - transaction_description = "{op}transaction <{name}>".format( - op=("<" + self.op + "> " if self.op else ""), name=self.name - ) + transaction_description = self._get_log_representation() # nothing to do if tracing is disabled if not has_tracing_enabled(client.options): diff --git a/tests/tracing/test_ignore_status_codes.py b/tests/tracing/test_ignore_status_codes.py new file mode 100644 index 0000000000..b2899e0ad9 --- /dev/null +++ b/tests/tracing/test_ignore_status_codes.py @@ -0,0 +1,139 @@ +import sentry_sdk +from sentry_sdk import start_transaction, start_span + +import pytest + +from collections import Counter + + +def test_no_ignored_codes(sentry_init, capture_events): + sentry_init( + traces_sample_rate=1.0, + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", 404) + + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 404]) +def test_single_code_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes={ + 404, + }, + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if status_code == 404: + assert not events + else: + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 305, 307, 399, 404]) +def test_range_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=set( + range( + 305, + 400, + ), + ), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if 305 <= status_code <= 399: + assert not events + else: + assert len(events) == 1 + + +@pytest.mark.parametrize("status_code", [200, 301, 303, 355, 404]) +def test_variety_ignored(sentry_init, capture_events, status_code): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes={ + 301, + 302, + 303, + *range( + 305, + 400, + ), + *range( + 401, + 405, + ), + }, + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", status_code) + + if ( + 301 <= status_code <= 303 + or 305 <= status_code <= 399 + or 401 <= status_code <= 404 + ): + assert not events + else: + assert len(events) == 1 + + +def test_transaction_not_ignored_when_status_code_has_invalid_type( + sentry_init, capture_events +): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes=set( + range(401, 404), + ), + ) + events = capture_events() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", "404") + + assert len(events) == 1 + + +def test_records_lost_events(sentry_init, capture_record_lost_event_calls): + sentry_init( + traces_sample_rate=1.0, + trace_ignore_status_codes={ + 404, + }, + ) + record_lost_event_calls = capture_record_lost_event_calls() + + with start_transaction(op="http", name="GET /"): + span_or_tx = sentry_sdk.get_current_span() + span_or_tx.set_data("http.response.status_code", 404) + + with start_span(op="child-span"): + with start_span(op="child-child-span"): + pass + + assert Counter(record_lost_event_calls) == Counter( + [ + ("event_processor", "transaction", None, 1), + ("event_processor", "span", None, 3), + ] + ) From 2e4457c8da5112e095362899ef8464bc304cc54a Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Thu, 2 Oct 2025 16:35:31 +0200 Subject: [PATCH 09/20] fix(openai-agents): also emit spans for MCP tool calls done by the LLM (#4875) ### Description We cannot directly intercept MCP Tool calls, as they are done remotely by the LLM and not in the Agent itself. However, we see when such a tool call took place, so we can emit a zero-length span with the tool call specifics. It will start at the same time as the parent span. Closes https://linear.app/getsentry/issue/TET-1192/openai-agents-hosted-mcp-calls-cannot-be-wrapped-in-an-execute-tool --- > [!NOTE] > Emit execute_tool spans for MCP tool calls detected in agent results, with tool metadata, input/output (PII-gated), and error status. > > - **Tracing/Spans (openai_agents)**: > - Add `utils._create_mcp_execute_tool_spans` to emit `OP.GEN_AI_EXECUTE_TOOL` spans for MCP tool calls (`McpCall`) found in `result.output`. > - Sets `GEN_AI_TOOL_TYPE=mcp`, `GEN_AI_TOOL_NAME`, propagates input/output when PII allowed, and marks `SPANSTATUS.ERROR` on error. > - Spans start at the parent span's start time (zero-length representation of remote call). > - Wire into `spans/ai_client.update_ai_client_span` to create these tool spans after setting usage/input/output data. > - Update imports to include `SPANSTATUS` and `OP`. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 96df8c1c08768d7f1d1369e66a9c4e7f6ebfc04c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: Ivana Kellyer --- .../openai_agents/spans/ai_client.py | 2 + .../integrations/openai_agents/utils.py | 26 +- .../openai_agents/test_openai_agents.py | 302 ++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/spans/ai_client.py b/sentry_sdk/integrations/openai_agents/spans/ai_client.py index e215edfd26..88b403ba85 100644 --- a/sentry_sdk/integrations/openai_agents/spans/ai_client.py +++ b/sentry_sdk/integrations/openai_agents/spans/ai_client.py @@ -7,6 +7,7 @@ _set_input_data, _set_output_data, _set_usage_data, + _create_mcp_execute_tool_spans, ) from typing import TYPE_CHECKING @@ -38,3 +39,4 @@ def update_ai_client_span(span, agent, get_response_kwargs, result): _set_usage_data(span, result.usage) _set_input_data(span, get_response_kwargs) _set_output_data(span, result) + _create_mcp_execute_tool_spans(span, result) diff --git a/sentry_sdk/integrations/openai_agents/utils.py b/sentry_sdk/integrations/openai_agents/utils.py index 73d2858e7f..b0ad6bf903 100644 --- a/sentry_sdk/integrations/openai_agents/utils.py +++ b/sentry_sdk/integrations/openai_agents/utils.py @@ -1,6 +1,6 @@ import sentry_sdk from sentry_sdk.ai.utils import set_data_normalized -from sentry_sdk.consts import SPANDATA +from sentry_sdk.consts import SPANDATA, SPANSTATUS, OP from sentry_sdk.integrations import DidNotEnable from sentry_sdk.scope import should_send_default_pii from sentry_sdk.tracing_utils import set_span_errored @@ -156,3 +156,27 @@ def _set_output_data(span, result): set_data_normalized( span, SPANDATA.GEN_AI_RESPONSE_TEXT, output_messages["response"] ) + + +def _create_mcp_execute_tool_spans(span, result): + # type: (sentry_sdk.tracing.Span, agents.Result) -> None + for output in result.output: + if output.__class__.__name__ == "McpCall": + with sentry_sdk.start_span( + op=OP.GEN_AI_EXECUTE_TOOL, + description=f"execute_tool {output.name}", + start_timestamp=span.start_timestamp, + ) as execute_tool_span: + set_data_normalized(execute_tool_span, SPANDATA.GEN_AI_TOOL_TYPE, "mcp") + set_data_normalized( + execute_tool_span, SPANDATA.GEN_AI_TOOL_NAME, output.name + ) + if should_send_default_pii(): + execute_tool_span.set_data( + SPANDATA.GEN_AI_TOOL_INPUT, output.arguments + ) + execute_tool_span.set_data( + SPANDATA.GEN_AI_TOOL_OUTPUT, output.output + ) + if output.error: + execute_tool_span.set_status(SPANSTATUS.ERROR) diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index bd7f15faff..1768971c99 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -15,6 +15,7 @@ ModelSettings, ) from agents.items import ( + McpCall, ResponseOutputMessage, ResponseOutputText, ResponseFunctionToolCall, @@ -683,6 +684,307 @@ async def test_span_status_error(sentry_init, capture_events, test_agent): assert transaction["contexts"]["trace"]["status"] == "error" +@pytest.mark.asyncio +async def test_mcp_tool_execution_spans(sentry_init, capture_events, test_agent): + """ + Test that MCP (Model Context Protocol) tool calls create execute_tool spans. + """ + + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}): + with patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + # Create a McpCall object + mcp_call = McpCall( + id="mcp_call_123", + name="test_mcp_tool", + arguments='{"query": "search term"}', + output="MCP tool executed successfully", + error=None, + type="mcp_call", + server_label="test_server", + ) + + # Create a ModelResponse with an McpCall in the output + mcp_response = ModelResponse( + output=[mcp_call], + usage=Usage( + requests=1, + input_tokens=10, + output_tokens=5, + total_tokens=15, + ), + response_id="resp_mcp_123", + ) + + # Final response after MCP tool execution + final_response = ModelResponse( + output=[ + ResponseOutputMessage( + id="msg_final", + type="message", + status="completed", + content=[ + ResponseOutputText( + text="Task completed using MCP tool", + type="output_text", + annotations=[], + ) + ], + role="assistant", + ) + ], + usage=Usage( + requests=1, + input_tokens=15, + output_tokens=10, + total_tokens=25, + ), + response_id="resp_final_123", + ) + + mock_get_response.side_effect = [mcp_response, final_response] + + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + ) + + events = capture_events() + + await agents.Runner.run( + test_agent, + "Please use MCP tool", + run_config=test_run_config, + ) + + (transaction,) = events + spans = transaction["spans"] + + # Find the MCP execute_tool span + mcp_tool_span = None + for span in spans: + if ( + span.get("description") == "execute_tool test_mcp_tool" + and span.get("data", {}).get("gen_ai.tool.type") == "mcp" + ): + mcp_tool_span = span + break + + # Verify the MCP tool span was created + assert mcp_tool_span is not None, "MCP execute_tool span was not created" + assert mcp_tool_span["description"] == "execute_tool test_mcp_tool" + assert mcp_tool_span["data"]["gen_ai.tool.type"] == "mcp" + assert mcp_tool_span["data"]["gen_ai.tool.name"] == "test_mcp_tool" + assert mcp_tool_span["data"]["gen_ai.tool.input"] == '{"query": "search term"}' + assert ( + mcp_tool_span["data"]["gen_ai.tool.output"] == "MCP tool executed successfully" + ) + + # Verify no error status since error was None + assert mcp_tool_span.get("tags", {}).get("status") != "error" + + +@pytest.mark.asyncio +async def test_mcp_tool_execution_with_error(sentry_init, capture_events, test_agent): + """ + Test that MCP tool calls with errors are tracked with error status. + """ + + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}): + with patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + # Create a McpCall object with an error + mcp_call_with_error = McpCall( + id="mcp_call_error_123", + name="failing_mcp_tool", + arguments='{"query": "test"}', + output=None, + error="MCP tool execution failed", + type="mcp_call", + server_label="test_server", + ) + + # Create a ModelResponse with a failing McpCall + mcp_response = ModelResponse( + output=[mcp_call_with_error], + usage=Usage( + requests=1, + input_tokens=10, + output_tokens=5, + total_tokens=15, + ), + response_id="resp_mcp_error_123", + ) + + # Final response after error + final_response = ModelResponse( + output=[ + ResponseOutputMessage( + id="msg_final", + type="message", + status="completed", + content=[ + ResponseOutputText( + text="The MCP tool encountered an error", + type="output_text", + annotations=[], + ) + ], + role="assistant", + ) + ], + usage=Usage( + requests=1, + input_tokens=15, + output_tokens=10, + total_tokens=25, + ), + response_id="resp_final_error_123", + ) + + mock_get_response.side_effect = [mcp_response, final_response] + + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=True, + ) + + events = capture_events() + + await agents.Runner.run( + test_agent, + "Please use failing MCP tool", + run_config=test_run_config, + ) + + (transaction,) = events + spans = transaction["spans"] + + # Find the MCP execute_tool span with error + mcp_tool_span = None + for span in spans: + if ( + span.get("description") == "execute_tool failing_mcp_tool" + and span.get("data", {}).get("gen_ai.tool.type") == "mcp" + ): + mcp_tool_span = span + break + + # Verify the MCP tool span was created with error status + assert mcp_tool_span is not None, "MCP execute_tool span was not created" + assert mcp_tool_span["description"] == "execute_tool failing_mcp_tool" + assert mcp_tool_span["data"]["gen_ai.tool.type"] == "mcp" + assert mcp_tool_span["data"]["gen_ai.tool.name"] == "failing_mcp_tool" + assert mcp_tool_span["data"]["gen_ai.tool.input"] == '{"query": "test"}' + assert mcp_tool_span["data"]["gen_ai.tool.output"] is None + + # Verify error status was set + assert mcp_tool_span["tags"]["status"] == "error" + + +@pytest.mark.asyncio +async def test_mcp_tool_execution_without_pii(sentry_init, capture_events, test_agent): + """ + Test that MCP tool input/output are not included when send_default_pii is False. + """ + + with patch.dict(os.environ, {"OPENAI_API_KEY": "test-key"}): + with patch( + "agents.models.openai_responses.OpenAIResponsesModel.get_response" + ) as mock_get_response: + # Create a McpCall object + mcp_call = McpCall( + id="mcp_call_pii_123", + name="test_mcp_tool", + arguments='{"query": "sensitive data"}', + output="Result with sensitive info", + error=None, + type="mcp_call", + server_label="test_server", + ) + + # Create a ModelResponse with an McpCall + mcp_response = ModelResponse( + output=[mcp_call], + usage=Usage( + requests=1, + input_tokens=10, + output_tokens=5, + total_tokens=15, + ), + response_id="resp_mcp_123", + ) + + # Final response + final_response = ModelResponse( + output=[ + ResponseOutputMessage( + id="msg_final", + type="message", + status="completed", + content=[ + ResponseOutputText( + text="Task completed", + type="output_text", + annotations=[], + ) + ], + role="assistant", + ) + ], + usage=Usage( + requests=1, + input_tokens=15, + output_tokens=10, + total_tokens=25, + ), + response_id="resp_final_123", + ) + + mock_get_response.side_effect = [mcp_response, final_response] + + sentry_init( + integrations=[OpenAIAgentsIntegration()], + traces_sample_rate=1.0, + send_default_pii=False, # PII disabled + ) + + events = capture_events() + + await agents.Runner.run( + test_agent, + "Please use MCP tool", + run_config=test_run_config, + ) + + (transaction,) = events + spans = transaction["spans"] + + # Find the MCP execute_tool span + mcp_tool_span = None + for span in spans: + if ( + span.get("description") == "execute_tool test_mcp_tool" + and span.get("data", {}).get("gen_ai.tool.type") == "mcp" + ): + mcp_tool_span = span + break + + # Verify the MCP tool span was created but without input/output + assert mcp_tool_span is not None, "MCP execute_tool span was not created" + assert mcp_tool_span["description"] == "execute_tool test_mcp_tool" + assert mcp_tool_span["data"]["gen_ai.tool.type"] == "mcp" + assert mcp_tool_span["data"]["gen_ai.tool.name"] == "test_mcp_tool" + + # Verify input and output are not included when send_default_pii is False + assert "gen_ai.tool.input" not in mcp_tool_span["data"] + assert "gen_ai.tool.output" not in mcp_tool_span["data"] + + @pytest.mark.asyncio async def test_multiple_agents_asyncio( sentry_init, capture_events, test_agent, mock_model_response From ffc88f5e21688d15ea486e2af4a7b3fe5f2ea0d4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:02:22 +0000 Subject: [PATCH 10/20] =?UTF-8?q?ci:=20=F0=9F=A4=96=20Update=20test=20matr?= =?UTF-8?q?ix=20with=20new=20releases=20(10/02)=20(#4880)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update our test matrix with new releases of integrated frameworks and libraries. ## How it works - Scan PyPI for all supported releases of all frameworks we have a dedicated test suite for. - Pick a representative sample of releases to run our test suite against. We always test the latest and oldest supported version. - Update [tox.ini](https://github.com/getsentry/sentry-python/blob/master/tox.ini) with the new releases. ## Action required - If CI passes on this PR, it's safe to approve and merge. It means our integrations can handle new versions of frameworks that got pulled in. - If CI doesn't pass on this PR, this points to an incompatibility of either our integration or our test setup with a new version of a framework. - Check what the failures look like and either fix them, or update the [test config](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/config.py) and rerun [scripts/generate-test-files.sh](https://github.com/getsentry/sentry-python/blob/master/scripts/generate-test-files.sh). See [scripts/populate_tox/README.md](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md) for what configuration options are available. _____________________ _🤖 This PR was automatically created using [a GitHub action](https://github.com/getsentry/sentry-python/blob/master/.github/workflows/update-tox.yml)._ Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- scripts/populate_tox/releases.jsonl | 29 ++++--- .../openai_agents/test_openai_agents.py | 42 +++++----- tox.ini | 76 +++++++++---------- 3 files changed, 73 insertions(+), 74 deletions(-) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index 3532b61c75..afbb5aef09 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -5,8 +5,8 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.5", "version": "2.2.28", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.6", "version": "3.1.14", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.6", "version": "3.2.25", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.8", "version": "4.2.24", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.10", "version": "5.2.6", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.8", "version": "4.2.25", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.10", "version": "5.2.7", "yanked": false}} {"info": {"classifiers": ["Development Status :: 3 - Alpha", "Environment :: Web Environment", "Framework :: Django", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Django", "requires_python": ">=3.12", "version": "6.0a1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Flask", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "Flask", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "1.1.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Flask", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "Flask", "requires_python": ">=3.8", "version": "2.3.3", "yanked": false}} @@ -24,9 +24,9 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP"], "name": "aiohttp", "requires_python": ">=3.5.3", "version": "3.4.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Framework :: AsyncIO", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "aiohttp", "requires_python": ">=3.6", "version": "3.7.4", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.7", "version": "0.16.0", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.7", "version": "0.33.1", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.50.0", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.68.1", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.7", "version": "0.34.2", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.52.2", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "anthropic", "requires_python": ">=3.8", "version": "0.69.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.12.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.13.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: End Users/Desktop", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "apache-beam", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*", "version": "2.14.0", "yanked": false}} @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.40", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.43", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} @@ -93,13 +93,13 @@ {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.24.7", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.28.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.32.6", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.35.1", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.35.3", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9"], "name": "langchain", "requires_python": "<4.0,>=3.8.1", "version": "0.1.20", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9"], "name": "langchain", "requires_python": "<4.0,>=3.8.1", "version": "0.2.17", "yanked": false}} {"info": {"classifiers": [], "name": "langchain", "requires_python": "<4.0,>=3.9", "version": "0.3.27", "yanked": false}} -{"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.9", "version": "0.6.7", "yanked": false}} -{"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.10", "version": "1.0.0a3", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.9", "version": "9.12.0", "yanked": false}} +{"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.9", "version": "0.6.8", "yanked": false}} +{"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.10", "version": "1.0.0a4", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.9", "version": "9.12.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.8", "version": "9.8.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": ">=3.8,<4.0", "version": "2.0.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.12.1", "yanked": false}} @@ -108,12 +108,11 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: System :: Logging"], "name": "loguru", "requires_python": "<4.0,>=3.5", "version": "0.7.3", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.7.1", "version": "1.0.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "1.109.1", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.7.1", "version": "1.37.2", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "1.73.0", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "2.0.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.0.19", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.1.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.2.11", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.3.2", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.3.3", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3"], "name": "openfeature-sdk", "requires_python": ">=3.8", "version": "0.7.5", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3"], "name": "openfeature-sdk", "requires_python": ">=3.9", "version": "0.8.3", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8"], "name": "pure-eval", "requires_python": "", "version": "0.0.3", "yanked": false}} @@ -126,7 +125,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database"], "name": "pymongo", "requires_python": "", "version": "3.5.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database"], "name": "pymongo", "requires_python": "", "version": "3.6.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database"], "name": "pymongo", "requires_python": ">=3.6", "version": "4.0.2", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database", "Typing :: Typed"], "name": "pymongo", "requires_python": ">=3.9", "version": "4.15.1", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database", "Typing :: Typed"], "name": "pymongo", "requires_python": ">=3.9", "version": "4.15.2", "yanked": false}} {"info": {"classifiers": ["Framework :: Pylons", "Intended Audience :: Developers", "License :: Repoze Public License", "Programming Language :: Python", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI"], "name": "pyramid", "requires_python": null, "version": "1.0.2", "yanked": false}} {"info": {"classifiers": ["Development Status :: 6 - Mature", "Framework :: Pyramid", "Intended Audience :: Developers", "License :: Repoze Public License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI"], "name": "pyramid", "requires_python": ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*", "version": "1.10.8", "yanked": false}} {"info": {"classifiers": ["Development Status :: 6 - Mature", "Framework :: Pyramid", "Intended Audience :: Developers", "License :: Repoze Public License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI"], "name": "pyramid", "requires_python": "", "version": "1.6.5", "yanked": false}} @@ -187,7 +186,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Typing :: Typed"], "name": "starlite", "requires_python": ">=3.8,<4.0", "version": "1.48.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Typing :: Typed"], "name": "starlite", "requires_python": "<4.0,>=3.8", "version": "1.51.16", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries"], "name": "statsig", "requires_python": ">=3.7", "version": "0.55.3", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries"], "name": "statsig", "requires_python": ">=3.7", "version": "0.64.0", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries"], "name": "statsig", "requires_python": ">=3.7", "version": "0.65.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "strawberry-graphql", "requires_python": ">=3.8,<4.0", "version": "0.209.8", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "strawberry-graphql", "requires_python": "<4.0,>=3.9", "version": "0.282.0", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "tornado", "requires_python": ">= 3.5", "version": "6.0.4", "yanked": false}} diff --git a/tests/integrations/openai_agents/test_openai_agents.py b/tests/integrations/openai_agents/test_openai_agents.py index 1768971c99..e9a8372806 100644 --- a/tests/integrations/openai_agents/test_openai_agents.py +++ b/tests/integrations/openai_agents/test_openai_agents.py @@ -6,6 +6,7 @@ from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration from sentry_sdk.integrations.openai_agents.utils import safe_serialize +from sentry_sdk.utils import parse_version import agents from agents import ( @@ -20,6 +21,7 @@ ResponseOutputText, ResponseFunctionToolCall, ) +from agents.version import __version__ as OPENAI_AGENTS_VERSION from openai.types.responses.response_usage import ( InputTokensDetails, @@ -438,24 +440,28 @@ def simple_test_tool(message: str) -> str: ai_client_span2, ) = spans - available_tools = safe_serialize( - [ - { - "name": "simple_test_tool", - "description": "A simple tool", - "params_json_schema": { - "properties": {"message": {"title": "Message", "type": "string"}}, - "required": ["message"], - "title": "simple_test_tool_args", - "type": "object", - "additionalProperties": False, - }, - "on_invoke_tool": "._create_function_tool.._on_invoke_tool>", - "strict_json_schema": True, - "is_enabled": True, - } - ] - ) + available_tools = [ + { + "name": "simple_test_tool", + "description": "A simple tool", + "params_json_schema": { + "properties": {"message": {"title": "Message", "type": "string"}}, + "required": ["message"], + "title": "simple_test_tool_args", + "type": "object", + "additionalProperties": False, + }, + "on_invoke_tool": "._create_function_tool.._on_invoke_tool>", + "strict_json_schema": True, + "is_enabled": True, + } + ] + if parse_version(OPENAI_AGENTS_VERSION) >= (0, 3, 3): + available_tools[0].update( + {"tool_input_guardrails": None, "tool_output_guardrails": None} + ) + + available_tools = safe_serialize(available_tools) assert transaction["transaction"] == "test_agent workflow" assert transaction["contexts"]["trace"]["origin"] == "auto.ai.openai_agents" diff --git a/tox.ini b/tox.ini index 2354c66c7c..1bca280a11 100644 --- a/tox.ini +++ b/tox.ini @@ -46,9 +46,9 @@ envlist = # ~~~ AI ~~~ {py3.8,py3.11,py3.12}-anthropic-v0.16.0 - {py3.8,py3.11,py3.12}-anthropic-v0.33.1 - {py3.8,py3.11,py3.12}-anthropic-v0.50.0 - {py3.8,py3.12,py3.13}-anthropic-v0.68.1 + {py3.8,py3.11,py3.12}-anthropic-v0.34.2 + {py3.8,py3.11,py3.12}-anthropic-v0.52.2 + {py3.8,py3.12,py3.13}-anthropic-v0.69.0 {py3.9,py3.10,py3.11}-cohere-v5.4.0 {py3.9,py3.11,py3.12}-cohere-v5.9.4 @@ -64,34 +64,32 @@ envlist = {py3.9,py3.12,py3.13}-langchain-notiktoken-v0.3.27 {py3.8,py3.11,py3.12}-openai-base-v1.0.1 - {py3.8,py3.11,py3.12}-openai-base-v1.37.2 - {py3.8,py3.11,py3.12}-openai-base-v1.73.0 {py3.8,py3.12,py3.13}-openai-base-v1.109.1 + {py3.8,py3.12,py3.13}-openai-base-v2.0.1 {py3.8,py3.11,py3.12}-openai-notiktoken-v1.0.1 - {py3.8,py3.11,py3.12}-openai-notiktoken-v1.37.2 - {py3.8,py3.11,py3.12}-openai-notiktoken-v1.73.0 {py3.8,py3.12,py3.13}-openai-notiktoken-v1.109.1 + {py3.8,py3.12,py3.13}-openai-notiktoken-v2.0.1 - {py3.9,py3.12,py3.13}-langgraph-v0.6.7 - {py3.10,py3.12,py3.13}-langgraph-v1.0.0a3 + {py3.9,py3.12,py3.13}-langgraph-v0.6.8 + {py3.10,py3.12,py3.13}-langgraph-v1.0.0a4 {py3.10,py3.11,py3.12}-openai_agents-v0.0.19 {py3.10,py3.12,py3.13}-openai_agents-v0.1.0 {py3.10,py3.12,py3.13}-openai_agents-v0.2.11 - {py3.10,py3.12,py3.13}-openai_agents-v0.3.2 + {py3.10,py3.12,py3.13}-openai_agents-v0.3.3 {py3.8,py3.10,py3.11}-huggingface_hub-v0.24.7 {py3.8,py3.12,py3.13}-huggingface_hub-v0.28.1 {py3.8,py3.12,py3.13}-huggingface_hub-v0.32.6 - {py3.8,py3.12,py3.13}-huggingface_hub-v0.35.1 + {py3.8,py3.12,py3.13}-huggingface_hub-v0.35.3 # ~~~ Cloud ~~~ {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.40 + {py3.9,py3.12,py3.13}-boto3-v1.40.43 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -107,7 +105,7 @@ envlist = {py3.6}-pymongo-v3.5.1 {py3.6,py3.10,py3.11}-pymongo-v3.13.0 - {py3.9,py3.12,py3.13}-pymongo-v4.15.1 + {py3.9,py3.12,py3.13}-pymongo-v4.15.2 {py3.6}-redis-v2.10.6 {py3.6,py3.7,py3.8}-redis-v3.5.3 @@ -126,13 +124,13 @@ envlist = # ~~~ Flags ~~~ {py3.8,py3.12,py3.13}-launchdarkly-v9.8.1 - {py3.9,py3.12,py3.13}-launchdarkly-v9.12.0 + {py3.9,py3.12,py3.13}-launchdarkly-v9.12.1 {py3.8,py3.12,py3.13}-openfeature-v0.7.5 {py3.9,py3.12,py3.13}-openfeature-v0.8.3 {py3.7,py3.12,py3.13}-statsig-v0.55.3 - {py3.7,py3.12,py3.13}-statsig-v0.64.0 + {py3.7,py3.12,py3.13}-statsig-v0.65.0 {py3.8,py3.12,py3.13}-unleash-v6.0.1 {py3.8,py3.12,py3.13}-unleash-v6.3.0 @@ -202,8 +200,8 @@ envlist = {py3.6,py3.7}-django-v1.11.29 {py3.6,py3.8,py3.9}-django-v2.2.28 {py3.6,py3.9,py3.10}-django-v3.2.25 - {py3.8,py3.11,py3.12}-django-v4.2.24 - {py3.10,py3.12,py3.13}-django-v5.2.6 + {py3.8,py3.11,py3.12}-django-v4.2.25 + {py3.10,py3.12,py3.13}-django-v5.2.7 {py3.12,py3.13}-django-v6.0a1 {py3.6,py3.7,py3.8}-flask-v1.1.4 @@ -332,12 +330,12 @@ deps = # ~~~ AI ~~~ anthropic-v0.16.0: anthropic==0.16.0 - anthropic-v0.33.1: anthropic==0.33.1 - anthropic-v0.50.0: anthropic==0.50.0 - anthropic-v0.68.1: anthropic==0.68.1 + anthropic-v0.34.2: anthropic==0.34.2 + anthropic-v0.52.2: anthropic==0.52.2 + anthropic-v0.69.0: anthropic==0.69.0 anthropic: pytest-asyncio anthropic-v0.16.0: httpx<0.28.0 - anthropic-v0.33.1: httpx<0.28.0 + anthropic-v0.34.2: httpx<0.28.0 cohere-v5.4.0: cohere==5.4.0 cohere-v5.9.4: cohere==5.9.4 @@ -360,35 +358,31 @@ deps = langchain-notiktoken-v0.3.27: langchain-community openai-base-v1.0.1: openai==1.0.1 - openai-base-v1.37.2: openai==1.37.2 - openai-base-v1.73.0: openai==1.73.0 openai-base-v1.109.1: openai==1.109.1 + openai-base-v2.0.1: openai==2.0.1 openai-base: pytest-asyncio openai-base: tiktoken openai-base-v1.0.1: httpx<0.28 - openai-base-v1.37.2: httpx<0.28 openai-notiktoken-v1.0.1: openai==1.0.1 - openai-notiktoken-v1.37.2: openai==1.37.2 - openai-notiktoken-v1.73.0: openai==1.73.0 openai-notiktoken-v1.109.1: openai==1.109.1 + openai-notiktoken-v2.0.1: openai==2.0.1 openai-notiktoken: pytest-asyncio openai-notiktoken-v1.0.1: httpx<0.28 - openai-notiktoken-v1.37.2: httpx<0.28 - langgraph-v0.6.7: langgraph==0.6.7 - langgraph-v1.0.0a3: langgraph==1.0.0a3 + langgraph-v0.6.8: langgraph==0.6.8 + langgraph-v1.0.0a4: langgraph==1.0.0a4 openai_agents-v0.0.19: openai-agents==0.0.19 openai_agents-v0.1.0: openai-agents==0.1.0 openai_agents-v0.2.11: openai-agents==0.2.11 - openai_agents-v0.3.2: openai-agents==0.3.2 + openai_agents-v0.3.3: openai-agents==0.3.3 openai_agents: pytest-asyncio huggingface_hub-v0.24.7: huggingface_hub==0.24.7 huggingface_hub-v0.28.1: huggingface_hub==0.28.1 huggingface_hub-v0.32.6: huggingface_hub==0.32.6 - huggingface_hub-v0.35.1: huggingface_hub==0.35.1 + huggingface_hub-v0.35.3: huggingface_hub==0.35.3 huggingface_hub: responses @@ -396,7 +390,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.40: boto3==1.40.40 + boto3-v1.40.43: boto3==1.40.43 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -415,7 +409,7 @@ deps = pymongo-v3.5.1: pymongo==3.5.1 pymongo-v3.13.0: pymongo==3.13.0 - pymongo-v4.15.1: pymongo==4.15.1 + pymongo-v4.15.2: pymongo==4.15.2 pymongo: mockupdb redis-v2.10.6: redis==2.10.6 @@ -440,13 +434,13 @@ deps = # ~~~ Flags ~~~ launchdarkly-v9.8.1: launchdarkly-server-sdk==9.8.1 - launchdarkly-v9.12.0: launchdarkly-server-sdk==9.12.0 + launchdarkly-v9.12.1: launchdarkly-server-sdk==9.12.1 openfeature-v0.7.5: openfeature-sdk==0.7.5 openfeature-v0.8.3: openfeature-sdk==0.8.3 statsig-v0.55.3: statsig==0.55.3 - statsig-v0.64.0: statsig==0.64.0 + statsig-v0.65.0: statsig==0.65.0 statsig: typing_extensions unleash-v6.0.1: UnleashClient==6.0.1 @@ -548,8 +542,8 @@ deps = django-v1.11.29: django==1.11.29 django-v2.2.28: django==2.2.28 django-v3.2.25: django==3.2.25 - django-v4.2.24: django==4.2.24 - django-v5.2.6: django==5.2.6 + django-v4.2.25: django==4.2.25 + django-v5.2.7: django==5.2.7 django-v6.0a1: django==6.0a1 django: psycopg2-binary django: djangorestframework @@ -557,13 +551,13 @@ deps = django: Werkzeug django-v2.2.28: channels[daphne] django-v3.2.25: channels[daphne] - django-v4.2.24: channels[daphne] - django-v5.2.6: channels[daphne] + django-v4.2.25: channels[daphne] + django-v5.2.7: channels[daphne] django-v6.0a1: channels[daphne] django-v2.2.28: six django-v3.2.25: pytest-asyncio - django-v4.2.24: pytest-asyncio - django-v5.2.6: pytest-asyncio + django-v4.2.25: pytest-asyncio + django-v5.2.7: pytest-asyncio django-v6.0a1: pytest-asyncio django-v1.11.29: djangorestframework>=3.0,<4.0 django-v1.11.29: Werkzeug<2.1.0 From f3e8a5ccd0a341cf139f474856163f0e5335741c Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Fri, 3 Oct 2025 08:40:35 +0200 Subject: [PATCH 11/20] fix(tests): Don't assume release is set (#4879) ### Description Even though we try to figure out the current release automatically if it's not provided, it can still end up being `None`. If that's the case, it won't be attached to logs. The `test_logs_attributes` test assumes there always is a release, which is incorrect. I opted for conditionally checking for `sentry.release` in the test instead of removing the check altogether, even though the test itself is supposed to test custom user provided attributes. The reason is that there is no other generic logs test testing `sentry.release`. #### Issues Closes https://github.com/getsentry/sentry-python/issues/4878 #### Reminders - Please add tests to validate your changes, and lint your code using `tox -e linters`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) --- tests/test_logs.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_logs.py b/tests/test_logs.py index 596a31922e..1e252c5bfb 100644 --- a/tests/test_logs.py +++ b/tests/test_logs.py @@ -230,7 +230,8 @@ def test_logs_attributes(sentry_init, capture_envelopes): for k, v in attrs.items(): assert logs[0]["attributes"][k] == v assert logs[0]["attributes"]["sentry.environment"] == "production" - assert "sentry.release" in logs[0]["attributes"] + if sentry_sdk.get_client().options.get("release") is not None: + assert "sentry.release" in logs[0]["attributes"] assert logs[0]["attributes"]["sentry.message.parameter.my_var"] == "some value" assert logs[0]["attributes"][SPANDATA.SERVER_ADDRESS] == "test-server" assert logs[0]["attributes"]["sentry.sdk.name"].startswith("sentry.python") From bbd2a5d8cd3725dd0d4e0840c2e126038404d1a3 Mon Sep 17 00:00:00 2001 From: Vyskorko Igor Date: Fri, 3 Oct 2025 16:00:08 +0700 Subject: [PATCH 12/20] feat(integrations): Add tracing to DramatiqIntegration (#4571) Adds tracing support to DramatiqIntegration #3454 --------- Co-authored-by: igorek Co-authored-by: Anton Pirker Co-authored-by: Ivana Kellyer --- pyproject.toml | 4 + sentry_sdk/consts.py | 1 + sentry_sdk/integrations/dramatiq.py | 120 ++++++++++++++----- tests/integrations/dramatiq/test_dramatiq.py | 57 ++++++++- 4 files changed, 146 insertions(+), 36 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8e6fe345f4..5b86531014 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -179,6 +179,10 @@ ignore_missing_imports = true module = "agents.*" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "dramatiq.*" +ignore_missing_imports = true + # # Tool: Ruff (linting and formatting) # diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 5bcc487037..7b9e61a065 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -839,6 +839,7 @@ class OP: QUEUE_TASK_HUEY = "queue.task.huey" QUEUE_SUBMIT_RAY = "queue.submit.ray" QUEUE_TASK_RAY = "queue.task.ray" + QUEUE_TASK_DRAMATIQ = "queue.task.dramatiq" SUBPROCESS = "subprocess" SUBPROCESS_WAIT = "subprocess.wait" SUBPROCESS_COMMUNICATE = "subprocess.communicate" diff --git a/sentry_sdk/integrations/dramatiq.py b/sentry_sdk/integrations/dramatiq.py index a756b4c669..8b85831cf4 100644 --- a/sentry_sdk/integrations/dramatiq.py +++ b/sentry_sdk/integrations/dramatiq.py @@ -1,18 +1,31 @@ import json import sentry_sdk -from sentry_sdk.integrations import Integration +from sentry_sdk.consts import OP, SPANSTATUS +from sentry_sdk.api import continue_trace, get_baggage, get_traceparent +from sentry_sdk.integrations import Integration, DidNotEnable from sentry_sdk.integrations._wsgi_common import request_body_within_bounds +from sentry_sdk.tracing import ( + BAGGAGE_HEADER_NAME, + SENTRY_TRACE_HEADER_NAME, + TransactionSource, +) from sentry_sdk.utils import ( AnnotatedValue, capture_internal_exceptions, event_from_exception, ) +from typing import TypeVar + +R = TypeVar("R") -from dramatiq.broker import Broker # type: ignore -from dramatiq.message import Message # type: ignore -from dramatiq.middleware import Middleware, default_middleware # type: ignore -from dramatiq.errors import Retry # type: ignore +try: + from dramatiq.broker import Broker + from dramatiq.middleware import Middleware, default_middleware + from dramatiq.errors import Retry + from dramatiq.message import Message +except ImportError: + raise DidNotEnable("Dramatiq is not installed") from typing import TYPE_CHECKING @@ -34,10 +47,12 @@ class DramatiqIntegration(Integration): """ identifier = "dramatiq" + origin = f"auto.queue.{identifier}" @staticmethod def setup_once(): # type: () -> None + _patch_dramatiq_broker() @@ -85,22 +100,54 @@ class SentryMiddleware(Middleware): # type: ignore[misc] DramatiqIntegration. """ - def before_process_message(self, broker, message): - # type: (Broker, Message) -> None + SENTRY_HEADERS_NAME = "_sentry_headers" + + def before_enqueue(self, broker, message, delay): + # type: (Broker, Message[R], int) -> None integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) if integration is None: return - message._scope_manager = sentry_sdk.new_scope() - message._scope_manager.__enter__() + message.options[self.SENTRY_HEADERS_NAME] = { + BAGGAGE_HEADER_NAME: get_baggage(), + SENTRY_TRACE_HEADER_NAME: get_traceparent(), + } + + def before_process_message(self, broker, message): + # type: (Broker, Message[R]) -> None + integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) + if integration is None: + return - scope = sentry_sdk.get_current_scope() - scope.set_transaction_name(message.actor_name) + message._scope_manager = sentry_sdk.isolation_scope() + scope = message._scope_manager.__enter__() + scope.clear_breadcrumbs() scope.set_extra("dramatiq_message_id", message.message_id) scope.add_event_processor(_make_message_event_processor(message, integration)) + sentry_headers = message.options.get(self.SENTRY_HEADERS_NAME) or {} + if "retries" in message.options: + # start new trace in case of retrying + sentry_headers = {} + + transaction = continue_trace( + sentry_headers, + name=message.actor_name, + op=OP.QUEUE_TASK_DRAMATIQ, + source=TransactionSource.TASK, + origin=DramatiqIntegration.origin, + ) + transaction.set_status(SPANSTATUS.OK) + sentry_sdk.start_transaction( + transaction, + name=message.actor_name, + op=OP.QUEUE_TASK_DRAMATIQ, + source=TransactionSource.TASK, + ) + transaction.__enter__() + def after_process_message(self, broker, message, *, result=None, exception=None): - # type: (Broker, Message, Any, Optional[Any], Optional[Exception]) -> None + # type: (Broker, Message[R], Optional[Any], Optional[Exception]) -> None integration = sentry_sdk.get_client().get_integration(DramatiqIntegration) if integration is None: return @@ -108,27 +155,38 @@ def after_process_message(self, broker, message, *, result=None, exception=None) actor = broker.get_actor(message.actor_name) throws = message.options.get("throws") or actor.options.get("throws") - try: - if ( - exception is not None - and not (throws and isinstance(exception, throws)) - and not isinstance(exception, Retry) - ): - event, hint = event_from_exception( - exception, - client_options=sentry_sdk.get_client().options, - mechanism={ - "type": DramatiqIntegration.identifier, - "handled": False, - }, - ) - sentry_sdk.capture_event(event, hint=hint) - finally: - message._scope_manager.__exit__(None, None, None) + scope_manager = message._scope_manager + transaction = sentry_sdk.get_current_scope().transaction + if not transaction: + return None + + is_event_capture_required = ( + exception is not None + and not (throws and isinstance(exception, throws)) + and not isinstance(exception, Retry) + ) + if not is_event_capture_required: + # normal transaction finish + transaction.__exit__(None, None, None) + scope_manager.__exit__(None, None, None) + return + + event, hint = event_from_exception( + exception, # type: ignore[arg-type] + client_options=sentry_sdk.get_client().options, + mechanism={ + "type": DramatiqIntegration.identifier, + "handled": False, + }, + ) + sentry_sdk.capture_event(event, hint=hint) + # transaction error + transaction.__exit__(type(exception), exception, None) + scope_manager.__exit__(type(exception), exception, None) def _make_message_event_processor(message, integration): - # type: (Message, DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]] + # type: (Message[R], DramatiqIntegration) -> Callable[[Event, Hint], Optional[Event]] def inner(event, hint): # type: (Event, Hint) -> Optional[Event] @@ -142,7 +200,7 @@ def inner(event, hint): class DramatiqMessageExtractor: def __init__(self, message): - # type: (Message) -> None + # type: (Message[R]) -> None self.message_data = dict(message.asdict()) def content_length(self): diff --git a/tests/integrations/dramatiq/test_dramatiq.py b/tests/integrations/dramatiq/test_dramatiq.py index d7917cbd00..53c36b640c 100644 --- a/tests/integrations/dramatiq/test_dramatiq.py +++ b/tests/integrations/dramatiq/test_dramatiq.py @@ -5,12 +5,21 @@ from dramatiq.brokers.stub import StubBroker import sentry_sdk +from sentry_sdk.tracing import TransactionSource +from sentry_sdk import start_transaction +from sentry_sdk.consts import SPANSTATUS from sentry_sdk.integrations.dramatiq import DramatiqIntegration +from sentry_sdk.integrations.logging import ignore_logger +ignore_logger("dramatiq.worker.WorkerThread") -@pytest.fixture -def broker(sentry_init): - sentry_init(integrations=[DramatiqIntegration()]) + +@pytest.fixture(scope="function") +def broker(request, sentry_init): + sentry_init( + integrations=[DramatiqIntegration()], + traces_sample_rate=getattr(request, "param", None), + ) broker = StubBroker() broker.emit_after("process_boot") dramatiq.set_broker(broker) @@ -44,19 +53,57 @@ def dummy_actor(x, y): assert exception["type"] == "ZeroDivisionError" -def test_that_actor_name_is_set_as_transaction(broker, worker, capture_events): +@pytest.mark.parametrize( + "broker,expected_span_status", + [ + (1.0, SPANSTATUS.INTERNAL_ERROR), + (1.0, SPANSTATUS.OK), + ], + ids=["error", "success"], + indirect=["broker"], +) +def test_task_transaction(broker, worker, capture_events, expected_span_status): events = capture_events() + task_fails = expected_span_status == SPANSTATUS.INTERNAL_ERROR @dramatiq.actor(max_retries=0) def dummy_actor(x, y): return x / y - dummy_actor.send(1, 0) + dummy_actor.send(1, int(not task_fails)) broker.join(dummy_actor.queue_name) worker.join() + if task_fails: + error_event = events.pop(0) + exception = error_event["exception"]["values"][0] + assert exception["type"] == "ZeroDivisionError" + assert exception["mechanism"]["type"] == DramatiqIntegration.identifier + (event,) = events + assert event["type"] == "transaction" assert event["transaction"] == "dummy_actor" + assert event["transaction_info"] == {"source": TransactionSource.TASK} + assert event["contexts"]["trace"]["status"] == expected_span_status + + +@pytest.mark.parametrize("broker", [1.0], indirect=True) +def test_dramatiq_propagate_trace(broker, worker, capture_events): + events = capture_events() + + @dramatiq.actor(max_retries=0) + def propagated_trace_task(): + pass + + with start_transaction() as outer_transaction: + propagated_trace_task.send() + broker.join(propagated_trace_task.queue_name) + worker.join() + + assert ( + events[0]["transaction"] == "propagated_trace_task" + ) # the "inner" transaction + assert events[0]["contexts"]["trace"]["trace_id"] == outer_transaction.trace_id def test_that_dramatiq_message_id_is_set_as_extra(broker, worker, capture_events): From f979abfc25b935f00b3c18d99d18888d833d0f78 Mon Sep 17 00:00:00 2001 From: Fabian Schindler Date: Fri, 3 Oct 2025 11:47:57 +0200 Subject: [PATCH 13/20] feat(integrations): add litellm integration (#4864) Add a first implementation of the litellm integration, supporting completion and embeddings Closes https://linear.app/getsentry/issue/PY-1828/add-agent-monitoring-support-for-litellm Closes https://linear.app/getsentry/issue/TET-1218/litellm-testing --- > [!NOTE] > Introduce `LiteLLMIntegration` that instruments LiteLLM chat/embeddings calls with spans, token usage, optional prompt logging, and exception capture. > > - **Integrations**: > - Add `sentry_sdk/integrations/litellm.py` with `LiteLLMIntegration` registering LiteLLM `input/success/failure` callbacks. > - Start spans for `chat`/`embeddings`, set `gen_ai.*` metadata (provider/system, operation, model, params like `max_tokens`, `temperature`, `top_p`, `stream`). > - Record LiteLLM-specific fields: `api_base`, `api_version`, `custom_llm_provider`. > - Optionally capture request/response messages when `include_prompts` and PII are enabled. > - Track token usage from response `usage` and capture exceptions; always finish spans. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 1ecd559392f9526c884e3cec12a152c65f098965. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: Ivana Kellyer --- .github/workflows/test-integrations-ai.yml | 4 + scripts/populate_tox/config.py | 3 + scripts/populate_tox/releases.jsonl | 7 +- .../split_tox_gh_actions.py | 1 + sentry_sdk/integrations/__init__.py | 1 + sentry_sdk/integrations/litellm.py | 251 ++++++++ setup.py | 1 + tests/integrations/litellm/__init__.py | 0 tests/integrations/litellm/test_litellm.py | 547 ++++++++++++++++++ tox.ini | 21 +- 10 files changed, 825 insertions(+), 11 deletions(-) create mode 100644 sentry_sdk/integrations/litellm.py create mode 100644 tests/integrations/litellm/__init__.py create mode 100644 tests/integrations/litellm/test_litellm.py diff --git a/.github/workflows/test-integrations-ai.yml b/.github/workflows/test-integrations-ai.yml index cf21720ff1..fcbb464078 100644 --- a/.github/workflows/test-integrations-ai.yml +++ b/.github/workflows/test-integrations-ai.yml @@ -66,6 +66,10 @@ jobs: run: | set -x # print commands that are executed ./scripts/runtox.sh "py${{ matrix.python-version }}-langchain-notiktoken" + - name: Test litellm + run: | + set -x # print commands that are executed + ./scripts/runtox.sh "py${{ matrix.python-version }}-litellm" - name: Test openai-base run: | set -x # print commands that are executed diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 34ae680fad..f69e5f2f90 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -214,6 +214,9 @@ "package": "launchdarkly-server-sdk", "num_versions": 2, }, + "litellm": { + "package": "litellm", + }, "litestar": { "package": "litestar", "deps": { diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index afbb5aef09..b7cca55815 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.43", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.44", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} @@ -101,6 +101,7 @@ {"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.10", "version": "1.0.0a4", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.9", "version": "9.12.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.8", "version": "9.8.1", "yanked": false}} +{"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9"], "name": "litellm", "requires_python": "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8", "version": "1.77.5", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": ">=3.8,<4.0", "version": "2.0.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.12.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.17.0", "yanked": false}} @@ -108,7 +109,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: System :: Logging"], "name": "loguru", "requires_python": "<4.0,>=3.5", "version": "0.7.3", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.7.1", "version": "1.0.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "1.109.1", "yanked": false}} -{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "2.0.1", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.8", "version": "2.1.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.0.19", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.1.0", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai-agents", "requires_python": ">=3.9", "version": "0.2.11", "yanked": false}} @@ -200,6 +201,6 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Framework :: Tryton", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Czech", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Hungarian", "Natural Language :: Indonesian", "Natural Language :: Italian", "Natural Language :: Persian", "Natural Language :: Polish", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Russian", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Turkish", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": ">=3.6", "version": "5.8.16", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Framework :: Tryton", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Czech", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Hungarian", "Natural Language :: Indonesian", "Natural Language :: Italian", "Natural Language :: Persian", "Natural Language :: Polish", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Romanian", "Natural Language :: Russian", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Turkish", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": ">=3.6", "version": "6.2.14", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Framework :: Tryton", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Czech", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Hungarian", "Natural Language :: Indonesian", "Natural Language :: Italian", "Natural Language :: Persian", "Natural Language :: Polish", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Romanian", "Natural Language :: Russian", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Turkish", "Natural Language :: Ukrainian", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": ">=3.8", "version": "6.8.17", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Framework :: Tryton", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Czech", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Hungarian", "Natural Language :: Indonesian", "Natural Language :: Italian", "Natural Language :: Persian", "Natural Language :: Polish", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Romanian", "Natural Language :: Russian", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Turkish", "Natural Language :: Ukrainian", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": ">=3.9", "version": "7.6.7", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Console", "Environment :: No Input/Output (Daemon)", "Framework :: Tryton", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: Bulgarian", "Natural Language :: Catalan", "Natural Language :: Chinese (Simplified)", "Natural Language :: Czech", "Natural Language :: Dutch", "Natural Language :: English", "Natural Language :: Finnish", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Hungarian", "Natural Language :: Indonesian", "Natural Language :: Italian", "Natural Language :: Persian", "Natural Language :: Polish", "Natural Language :: Portuguese (Brazilian)", "Natural Language :: Romanian", "Natural Language :: Russian", "Natural Language :: Slovenian", "Natural Language :: Spanish", "Natural Language :: Turkish", "Natural Language :: Ukrainian", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": ">=3.9", "version": "7.6.8", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "typer", "requires_python": ">=3.7", "version": "0.15.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "typer", "requires_python": ">=3.8", "version": "0.19.2", "yanked": false}} diff --git a/scripts/split_tox_gh_actions/split_tox_gh_actions.py b/scripts/split_tox_gh_actions/split_tox_gh_actions.py index a7b7c394b1..81f887ad4f 100755 --- a/scripts/split_tox_gh_actions/split_tox_gh_actions.py +++ b/scripts/split_tox_gh_actions/split_tox_gh_actions.py @@ -74,6 +74,7 @@ "cohere", "langchain-base", "langchain-notiktoken", + "litellm", "openai-base", "openai-notiktoken", "langgraph", diff --git a/sentry_sdk/integrations/__init__.py b/sentry_sdk/integrations/__init__.py index e397c9986a..3f71f0f4ba 100644 --- a/sentry_sdk/integrations/__init__.py +++ b/sentry_sdk/integrations/__init__.py @@ -146,6 +146,7 @@ def iter_default_integrations(with_auto_enabling_integrations): "langchain": (0, 1, 0), "langgraph": (0, 6, 6), "launchdarkly": (9, 8, 0), + "litellm": (1, 77, 5), "loguru": (0, 7, 0), "openai": (1, 0, 0), "openai_agents": (0, 0, 19), diff --git a/sentry_sdk/integrations/litellm.py b/sentry_sdk/integrations/litellm.py new file mode 100644 index 0000000000..2582c2bc05 --- /dev/null +++ b/sentry_sdk/integrations/litellm.py @@ -0,0 +1,251 @@ +from typing import TYPE_CHECKING + +import sentry_sdk +from sentry_sdk import consts +from sentry_sdk.ai.monitoring import record_token_usage +from sentry_sdk.ai.utils import get_start_span_function, set_data_normalized +from sentry_sdk.consts import SPANDATA +from sentry_sdk.integrations import DidNotEnable, Integration +from sentry_sdk.scope import should_send_default_pii +from sentry_sdk.utils import event_from_exception + +if TYPE_CHECKING: + from typing import Any, Dict + from datetime import datetime + +try: + import litellm # type: ignore[import-not-found] +except ImportError: + raise DidNotEnable("LiteLLM not installed") + + +def _get_metadata_dict(kwargs): + # type: (Dict[str, Any]) -> Dict[str, Any] + """Get the metadata dictionary from the kwargs.""" + litellm_params = kwargs.setdefault("litellm_params", {}) + + # we need this weird little dance, as metadata might be set but may be None initially + metadata = litellm_params.get("metadata") + if metadata is None: + metadata = {} + litellm_params["metadata"] = metadata + return metadata + + +def _input_callback(kwargs): + # type: (Dict[str, Any]) -> None + """Handle the start of a request.""" + integration = sentry_sdk.get_client().get_integration(LiteLLMIntegration) + + if integration is None: + return + + # Get key parameters + full_model = kwargs.get("model", "") + try: + model, provider, _, _ = litellm.get_llm_provider(full_model) + except Exception: + model = full_model + provider = "unknown" + + messages = kwargs.get("messages", []) + operation = "chat" if messages else "embeddings" + + # Start a new span/transaction + span = get_start_span_function()( + op=( + consts.OP.GEN_AI_CHAT + if operation == "chat" + else consts.OP.GEN_AI_EMBEDDINGS + ), + name=f"{operation} {model}", + origin=LiteLLMIntegration.origin, + ) + span.__enter__() + + # Store span for later + _get_metadata_dict(kwargs)["_sentry_span"] = span + + # Set basic data + set_data_normalized(span, SPANDATA.GEN_AI_SYSTEM, provider) + set_data_normalized(span, SPANDATA.GEN_AI_OPERATION_NAME, operation) + + # Record messages if allowed + if messages and should_send_default_pii() and integration.include_prompts: + set_data_normalized( + span, SPANDATA.GEN_AI_REQUEST_MESSAGES, messages, unpack=False + ) + + # Record other parameters + params = { + "model": SPANDATA.GEN_AI_REQUEST_MODEL, + "stream": SPANDATA.GEN_AI_RESPONSE_STREAMING, + "max_tokens": SPANDATA.GEN_AI_REQUEST_MAX_TOKENS, + "presence_penalty": SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY, + "frequency_penalty": SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY, + "temperature": SPANDATA.GEN_AI_REQUEST_TEMPERATURE, + "top_p": SPANDATA.GEN_AI_REQUEST_TOP_P, + } + for key, attribute in params.items(): + value = kwargs.get(key) + if value is not None: + set_data_normalized(span, attribute, value) + + # Record LiteLLM-specific parameters + litellm_params = { + "api_base": kwargs.get("api_base"), + "api_version": kwargs.get("api_version"), + "custom_llm_provider": kwargs.get("custom_llm_provider"), + } + for key, value in litellm_params.items(): + if value is not None: + set_data_normalized(span, f"gen_ai.litellm.{key}", value) + + +def _success_callback(kwargs, completion_response, start_time, end_time): + # type: (Dict[str, Any], Any, datetime, datetime) -> None + """Handle successful completion.""" + + span = _get_metadata_dict(kwargs).get("_sentry_span") + if span is None: + return + + integration = sentry_sdk.get_client().get_integration(LiteLLMIntegration) + if integration is None: + return + + try: + # Record model information + if hasattr(completion_response, "model"): + set_data_normalized( + span, SPANDATA.GEN_AI_RESPONSE_MODEL, completion_response.model + ) + + # Record response content if allowed + if should_send_default_pii() and integration.include_prompts: + if hasattr(completion_response, "choices"): + response_messages = [] + for choice in completion_response.choices: + if hasattr(choice, "message"): + if hasattr(choice.message, "model_dump"): + response_messages.append(choice.message.model_dump()) + elif hasattr(choice.message, "dict"): + response_messages.append(choice.message.dict()) + else: + # Fallback for basic message objects + msg = {} + if hasattr(choice.message, "role"): + msg["role"] = choice.message.role + if hasattr(choice.message, "content"): + msg["content"] = choice.message.content + if hasattr(choice.message, "tool_calls"): + msg["tool_calls"] = choice.message.tool_calls + response_messages.append(msg) + + if response_messages: + set_data_normalized( + span, SPANDATA.GEN_AI_RESPONSE_TEXT, response_messages + ) + + # Record token usage + if hasattr(completion_response, "usage"): + usage = completion_response.usage + record_token_usage( + span, + input_tokens=getattr(usage, "prompt_tokens", None), + output_tokens=getattr(usage, "completion_tokens", None), + total_tokens=getattr(usage, "total_tokens", None), + ) + + finally: + # Always finish the span and clean up + span.__exit__(None, None, None) + + +def _failure_callback(kwargs, exception, start_time, end_time): + # type: (Dict[str, Any], Exception, datetime, datetime) -> None + """Handle request failure.""" + span = _get_metadata_dict(kwargs).get("_sentry_span") + if span is None: + return + + try: + # Capture the exception + event, hint = event_from_exception( + exception, + client_options=sentry_sdk.get_client().options, + mechanism={"type": "litellm", "handled": False}, + ) + sentry_sdk.capture_event(event, hint=hint) + finally: + # Always finish the span and clean up + span.__exit__(type(exception), exception, None) + + +class LiteLLMIntegration(Integration): + """ + LiteLLM integration for Sentry. + + This integration automatically captures LiteLLM API calls and sends them to Sentry + for monitoring and error tracking. It supports all 100+ LLM providers that LiteLLM + supports, including OpenAI, Anthropic, Google, Cohere, and many others. + + Features: + - Automatic exception capture for all LiteLLM calls + - Token usage tracking across all providers + - Provider detection and attribution + - Input/output message capture (configurable) + - Streaming response support + - Cost tracking integration + + Usage: + + ```python + import litellm + import sentry_sdk + + # Initialize Sentry with the LiteLLM integration + sentry_sdk.init( + dsn="your-dsn", + send_default_pii=True + integrations=[ + sentry_sdk.integrations.LiteLLMIntegration( + include_prompts=True # Set to False to exclude message content + ) + ] + ) + + # All LiteLLM calls will now be monitored + response = litellm.completion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hello!"}] + ) + ``` + + Configuration: + - include_prompts (bool): Whether to include prompts and responses in spans. + Defaults to True. Set to False to exclude potentially sensitive data. + """ + + identifier = "litellm" + origin = f"auto.ai.{identifier}" + + def __init__(self, include_prompts=True): + # type: (LiteLLMIntegration, bool) -> None + self.include_prompts = include_prompts + + @staticmethod + def setup_once(): + # type: () -> None + """Set up LiteLLM callbacks for monitoring.""" + litellm.input_callback = litellm.input_callback or [] + if _input_callback not in litellm.input_callback: + litellm.input_callback.append(_input_callback) + + litellm.success_callback = litellm.success_callback or [] + if _success_callback not in litellm.success_callback: + litellm.success_callback.append(_success_callback) + + litellm.failure_callback = litellm.failure_callback or [] + if _failure_callback not in litellm.failure_callback: + litellm.failure_callback.append(_failure_callback) diff --git a/setup.py b/setup.py index 7119e20e90..6629726798 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,7 @@ def get_file_text(file_name): "langchain": ["langchain>=0.0.210"], "langgraph": ["langgraph>=0.6.6"], "launchdarkly": ["launchdarkly-server-sdk>=9.8.0"], + "litellm": ["litellm>=1.77.5"], "litestar": ["litestar>=2.0.0"], "loguru": ["loguru>=0.5"], "openai": ["openai>=1.0.0", "tiktoken>=0.3.0"], diff --git a/tests/integrations/litellm/__init__.py b/tests/integrations/litellm/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integrations/litellm/test_litellm.py b/tests/integrations/litellm/test_litellm.py new file mode 100644 index 0000000000..b600c32905 --- /dev/null +++ b/tests/integrations/litellm/test_litellm.py @@ -0,0 +1,547 @@ +import pytest +from unittest import mock +from datetime import datetime + +try: + from unittest.mock import AsyncMock +except ImportError: + + class AsyncMock(mock.MagicMock): + async def __call__(self, *args, **kwargs): + return super(AsyncMock, self).__call__(*args, **kwargs) + + +try: + import litellm +except ImportError: + pytest.skip("litellm not installed", allow_module_level=True) + +from sentry_sdk import start_transaction +from sentry_sdk.consts import OP, SPANDATA +from sentry_sdk.integrations.litellm import ( + LiteLLMIntegration, + _input_callback, + _success_callback, + _failure_callback, +) +from sentry_sdk.utils import package_version + + +LITELLM_VERSION = package_version("litellm") + + +# Mock response objects +class MockMessage: + def __init__(self, role="assistant", content="Test response"): + self.role = role + self.content = content + self.tool_calls = None + + def model_dump(self): + return {"role": self.role, "content": self.content} + + +class MockChoice: + def __init__(self, message=None): + self.message = message or MockMessage() + self.index = 0 + self.finish_reason = "stop" + + +class MockUsage: + def __init__(self, prompt_tokens=10, completion_tokens=20, total_tokens=30): + self.prompt_tokens = prompt_tokens + self.completion_tokens = completion_tokens + self.total_tokens = total_tokens + + +class MockCompletionResponse: + def __init__( + self, + model="gpt-3.5-turbo", + choices=None, + usage=None, + ): + self.id = "chatcmpl-test" + self.model = model + self.choices = choices or [MockChoice()] + self.usage = usage or MockUsage() + self.object = "chat.completion" + self.created = 1234567890 + + +class MockEmbeddingData: + def __init__(self, embedding=None): + self.embedding = embedding or [0.1, 0.2, 0.3] + self.index = 0 + self.object = "embedding" + + +class MockEmbeddingResponse: + def __init__(self, model="text-embedding-ada-002", data=None, usage=None): + self.model = model + self.data = data or [MockEmbeddingData()] + self.usage = usage or MockUsage( + prompt_tokens=5, completion_tokens=0, total_tokens=5 + ) + self.object = "list" + + +@pytest.mark.parametrize( + "send_default_pii, include_prompts", + [ + (True, True), + (True, False), + (False, True), + (False, False), + ], +) +def test_nonstreaming_chat_completion( + sentry_init, capture_events, send_default_pii, include_prompts +): + sentry_init( + integrations=[LiteLLMIntegration(include_prompts=include_prompts)], + traces_sample_rate=1.0, + send_default_pii=send_default_pii, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + # Simulate what litellm does: call input callback, then success callback + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + assert len(events) == 1 + (event,) = events + + assert event["type"] == "transaction" + assert event["transaction"] == "litellm test" + + assert len(event["spans"]) == 1 + (span,) = event["spans"] + + assert span["op"] == OP.GEN_AI_CHAT + assert span["description"] == "chat gpt-3.5-turbo" + assert span["data"][SPANDATA.GEN_AI_REQUEST_MODEL] == "gpt-3.5-turbo" + assert span["data"][SPANDATA.GEN_AI_RESPONSE_MODEL] == "gpt-3.5-turbo" + assert span["data"][SPANDATA.GEN_AI_SYSTEM] == "openai" + assert span["data"][SPANDATA.GEN_AI_OPERATION_NAME] == "chat" + + if send_default_pii and include_prompts: + assert SPANDATA.GEN_AI_REQUEST_MESSAGES in span["data"] + assert SPANDATA.GEN_AI_RESPONSE_TEXT in span["data"] + else: + assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in span["data"] + assert SPANDATA.GEN_AI_RESPONSE_TEXT not in span["data"] + + assert span["data"][SPANDATA.GEN_AI_USAGE_INPUT_TOKENS] == 10 + assert span["data"][SPANDATA.GEN_AI_USAGE_OUTPUT_TOKENS] == 20 + assert span["data"][SPANDATA.GEN_AI_USAGE_TOTAL_TOKENS] == 30 + + +@pytest.mark.parametrize( + "send_default_pii, include_prompts", + [ + (True, True), + (True, False), + (False, True), + (False, False), + ], +) +def test_streaming_chat_completion( + sentry_init, capture_events, send_default_pii, include_prompts +): + sentry_init( + integrations=[LiteLLMIntegration(include_prompts=include_prompts)], + traces_sample_rate=1.0, + send_default_pii=send_default_pii, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + "stream": True, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + assert len(events) == 1 + (event,) = events + + assert event["type"] == "transaction" + assert len(event["spans"]) == 1 + (span,) = event["spans"] + + assert span["op"] == OP.GEN_AI_CHAT + assert span["data"][SPANDATA.GEN_AI_RESPONSE_STREAMING] is True + + +def test_embeddings_create(sentry_init, capture_events): + sentry_init( + integrations=[LiteLLMIntegration(include_prompts=True)], + traces_sample_rate=1.0, + send_default_pii=True, + ) + events = capture_events() + + mock_response = MockEmbeddingResponse() + + with start_transaction(name="litellm test"): + # For embeddings, messages would be empty + kwargs = { + "model": "text-embedding-ada-002", + "input": "Hello!", + "messages": [], # Empty for embeddings + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + assert len(events) == 1 + (event,) = events + + assert event["type"] == "transaction" + assert len(event["spans"]) == 1 + (span,) = event["spans"] + + assert span["op"] == OP.GEN_AI_EMBEDDINGS + assert span["description"] == "embeddings text-embedding-ada-002" + assert span["data"][SPANDATA.GEN_AI_OPERATION_NAME] == "embeddings" + assert span["data"][SPANDATA.GEN_AI_USAGE_INPUT_TOKENS] == 5 + + +def test_exception_handling(sentry_init, capture_events): + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + _input_callback(kwargs) + _failure_callback( + kwargs, + Exception("API rate limit reached"), + datetime.now(), + datetime.now(), + ) + + # Should have error event and transaction + assert len(events) >= 1 + # Find the error event + error_events = [e for e in events if e.get("level") == "error"] + assert len(error_events) == 1 + + +def test_span_origin(sentry_init, capture_events): + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + + assert event["contexts"]["trace"]["origin"] == "manual" + assert event["spans"][0]["origin"] == "auto.ai.litellm" + + +def test_multiple_providers(sentry_init, capture_events): + """Test that the integration correctly identifies different providers.""" + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + + # Test with different model prefixes + test_cases = [ + ("gpt-3.5-turbo", "openai"), + ("claude-3-opus-20240229", "anthropic"), + ("gemini/gemini-pro", "gemini"), + ] + + for model, _ in test_cases: + mock_response = MockCompletionResponse(model=model) + with start_transaction(name=f"test {model}"): + kwargs = { + "model": model, + "messages": messages, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + assert len(events) == len(test_cases) + + for i in range(len(test_cases)): + span = events[i]["spans"][0] + # The provider should be detected by litellm.get_llm_provider + assert SPANDATA.GEN_AI_SYSTEM in span["data"] + + +def test_additional_parameters(sentry_init, capture_events): + """Test that additional parameters are captured.""" + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + "temperature": 0.7, + "max_tokens": 100, + "top_p": 0.9, + "frequency_penalty": 0.5, + "presence_penalty": 0.5, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + (span,) = event["spans"] + + assert span["data"][SPANDATA.GEN_AI_REQUEST_TEMPERATURE] == 0.7 + assert span["data"][SPANDATA.GEN_AI_REQUEST_MAX_TOKENS] == 100 + assert span["data"][SPANDATA.GEN_AI_REQUEST_TOP_P] == 0.9 + assert span["data"][SPANDATA.GEN_AI_REQUEST_FREQUENCY_PENALTY] == 0.5 + assert span["data"][SPANDATA.GEN_AI_REQUEST_PRESENCE_PENALTY] == 0.5 + + +def test_litellm_specific_parameters(sentry_init, capture_events): + """Test that LiteLLM-specific parameters are captured.""" + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + "api_base": "https://custom-api.example.com", + "api_version": "2023-01-01", + "custom_llm_provider": "custom_provider", + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + (span,) = event["spans"] + + assert span["data"]["gen_ai.litellm.api_base"] == "https://custom-api.example.com" + assert span["data"]["gen_ai.litellm.api_version"] == "2023-01-01" + assert span["data"]["gen_ai.litellm.custom_llm_provider"] == "custom_provider" + + +def test_no_integration(sentry_init, capture_events): + """Test that when integration is not enabled, callbacks don't break.""" + sentry_init( + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + mock_response = MockCompletionResponse() + + with start_transaction(name="litellm test"): + # When the integration isn't enabled, the callbacks should exit early + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + # These should not crash, just do nothing + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + # Should still have the transaction, but no child spans since integration is off + assert event["type"] == "transaction" + assert len(event.get("spans", [])) == 0 + + +def test_response_without_usage(sentry_init, capture_events): + """Test handling of responses without usage information.""" + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + + # Create a mock response without usage + mock_response = type( + "obj", + (object,), + { + "model": "gpt-3.5-turbo", + "choices": [MockChoice()], + }, + )() + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + (span,) = event["spans"] + + # Span should still be created even without usage info + assert span["op"] == OP.GEN_AI_CHAT + assert span["description"] == "chat gpt-3.5-turbo" + + +def test_integration_setup(sentry_init): + """Test that the integration sets up the callbacks correctly.""" + sentry_init( + integrations=[LiteLLMIntegration()], + traces_sample_rate=1.0, + ) + + # Check that callbacks are registered + assert _input_callback in (litellm.input_callback or []) + assert _success_callback in (litellm.success_callback or []) + assert _failure_callback in (litellm.failure_callback or []) + + +def test_message_dict_extraction(sentry_init, capture_events): + """Test that response messages are properly extracted with dict() fallback.""" + sentry_init( + integrations=[LiteLLMIntegration(include_prompts=True)], + traces_sample_rate=1.0, + send_default_pii=True, + ) + events = capture_events() + + messages = [{"role": "user", "content": "Hello!"}] + + # Create a message that has dict() method instead of model_dump() + class DictMessage: + def __init__(self): + self.role = "assistant" + self.content = "Response" + self.tool_calls = None + + def dict(self): + return {"role": self.role, "content": self.content} + + mock_response = MockCompletionResponse(choices=[MockChoice(message=DictMessage())]) + + with start_transaction(name="litellm test"): + kwargs = { + "model": "gpt-3.5-turbo", + "messages": messages, + } + + _input_callback(kwargs) + _success_callback( + kwargs, + mock_response, + datetime.now(), + datetime.now(), + ) + + (event,) = events + (span,) = event["spans"] + + # Should have extracted the response message + assert SPANDATA.GEN_AI_RESPONSE_TEXT in span["data"] diff --git a/tox.ini b/tox.ini index 1bca280a11..c74755f206 100644 --- a/tox.ini +++ b/tox.ini @@ -63,13 +63,15 @@ envlist = {py3.9,py3.11,py3.12}-langchain-notiktoken-v0.2.17 {py3.9,py3.12,py3.13}-langchain-notiktoken-v0.3.27 + {py3.9,py3.12,py3.13}-litellm-v1.77.5 + {py3.8,py3.11,py3.12}-openai-base-v1.0.1 {py3.8,py3.12,py3.13}-openai-base-v1.109.1 - {py3.8,py3.12,py3.13}-openai-base-v2.0.1 + {py3.8,py3.12,py3.13}-openai-base-v2.1.0 {py3.8,py3.11,py3.12}-openai-notiktoken-v1.0.1 {py3.8,py3.12,py3.13}-openai-notiktoken-v1.109.1 - {py3.8,py3.12,py3.13}-openai-notiktoken-v2.0.1 + {py3.8,py3.12,py3.13}-openai-notiktoken-v2.1.0 {py3.9,py3.12,py3.13}-langgraph-v0.6.8 {py3.10,py3.12,py3.13}-langgraph-v1.0.0a4 @@ -89,7 +91,7 @@ envlist = {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.43 + {py3.9,py3.12,py3.13}-boto3-v1.40.44 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -267,7 +269,7 @@ envlist = {py3.6}-trytond-v4.8.18 {py3.6,py3.7,py3.8}-trytond-v5.8.16 {py3.8,py3.10,py3.11}-trytond-v6.8.17 - {py3.9,py3.12,py3.13}-trytond-v7.6.7 + {py3.9,py3.12,py3.13}-trytond-v7.6.8 {py3.7,py3.12,py3.13}-typer-v0.15.4 {py3.8,py3.12,py3.13}-typer-v0.19.2 @@ -357,16 +359,18 @@ deps = langchain-notiktoken: langchain-openai langchain-notiktoken-v0.3.27: langchain-community + litellm-v1.77.5: litellm==1.77.5 + openai-base-v1.0.1: openai==1.0.1 openai-base-v1.109.1: openai==1.109.1 - openai-base-v2.0.1: openai==2.0.1 + openai-base-v2.1.0: openai==2.1.0 openai-base: pytest-asyncio openai-base: tiktoken openai-base-v1.0.1: httpx<0.28 openai-notiktoken-v1.0.1: openai==1.0.1 openai-notiktoken-v1.109.1: openai==1.109.1 - openai-notiktoken-v2.0.1: openai==2.0.1 + openai-notiktoken-v2.1.0: openai==2.1.0 openai-notiktoken: pytest-asyncio openai-notiktoken-v1.0.1: httpx<0.28 @@ -390,7 +394,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.43: boto3==1.40.43 + boto3-v1.40.44: boto3==1.40.44 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -688,7 +692,7 @@ deps = trytond-v4.8.18: trytond==4.8.18 trytond-v5.8.16: trytond==5.8.16 trytond-v6.8.17: trytond==6.8.17 - trytond-v7.6.7: trytond==7.6.7 + trytond-v7.6.8: trytond==7.6.8 trytond: werkzeug trytond-v4.6.22: werkzeug<1.0 trytond-v4.8.18: werkzeug<1.0 @@ -746,6 +750,7 @@ setenv = langchain-notiktoken: TESTPATH=tests/integrations/langchain langgraph: TESTPATH=tests/integrations/langgraph launchdarkly: TESTPATH=tests/integrations/launchdarkly + litellm: TESTPATH=tests/integrations/litellm litestar: TESTPATH=tests/integrations/litestar loguru: TESTPATH=tests/integrations/loguru openai-base: TESTPATH=tests/integrations/openai From 39cb2c53b5e896c4641d67e088e9ffca56226f35 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 6 Oct 2025 10:15:38 +0200 Subject: [PATCH 14/20] feat(huggingface): Support 1.0.0rc2 (#4873) ### Description huggingface_hub has a release candidate out and our test suite doesn't work with it. Two changes necessary: - 1.0 uses `httpx`, so our `responses` mocks don't work, we also need `pytest_httpx`. - With httpx we get additional `http.client` spans in the transaction, while before we were assuming the transaction only contains exactly one `gen_ai.*` span and nothing else. #### Issues Closes https://github.com/getsentry/sentry-python/issues/4802 #### Reminders - Please add tests to validate your changes, and lint your code using `tox -e linters`. - Add GH Issue ID _&_ Linear ID (if applicable) - PR title should use [conventional commit](https://develop.sentry.dev/engineering-practices/commit-messages/#type) style (`feat:`, `fix:`, `ref:`, `meta:`) - For external contributors: [CONTRIBUTING.md](https://github.com/getsentry/sentry-python/blob/master/CONTRIBUTING.md), [Sentry SDK development docs](https://develop.sentry.dev/sdk/), [Discord community](https://discord.gg/Ww9hbqr) --- scripts/populate_tox/config.py | 3 +- scripts/populate_tox/releases.jsonl | 1 + .../huggingface_hub/test_huggingface_hub.py | 296 ++++++++++++++---- tox.ini | 3 + 4 files changed, 244 insertions(+), 59 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index f69e5f2f90..0ff0e9b434 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -183,9 +183,8 @@ "huggingface_hub": { "package": "huggingface_hub", "deps": { - "*": ["responses"], + "*": ["responses", "pytest-httpx"], }, - "include": "<1.0", }, "langchain-base": { "package": "langchain", diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index b7cca55815..bf95102c71 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -94,6 +94,7 @@ {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.28.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.32.6", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.8.0", "version": "0.35.3", "yanked": false}} +{"info": {"classifiers": ["Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Scientific/Engineering :: Artificial Intelligence"], "name": "huggingface-hub", "requires_python": ">=3.9.0", "version": "1.0.0rc2", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9"], "name": "langchain", "requires_python": "<4.0,>=3.8.1", "version": "0.1.20", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.9"], "name": "langchain", "requires_python": "<4.0,>=3.8.1", "version": "0.2.17", "yanked": false}} {"info": {"classifiers": [], "name": "langchain", "requires_python": "<4.0,>=3.9", "version": "0.3.27", "yanked": false}} diff --git a/tests/integrations/huggingface_hub/test_huggingface_hub.py b/tests/integrations/huggingface_hub/test_huggingface_hub.py index 5aa3928a67..b9ab4df5bf 100644 --- a/tests/integrations/huggingface_hub/test_huggingface_hub.py +++ b/tests/integrations/huggingface_hub/test_huggingface_hub.py @@ -1,6 +1,8 @@ from unittest import mock import pytest +import re import responses +import httpx from huggingface_hub import InferenceClient @@ -32,17 +34,49 @@ ) +def _add_mock_response( + httpx_mock, rsps, method, url, json=None, status=200, body=None, headers=None +): + # HF v1+ uses httpx for making requests to their API, while <1 uses requests. + # Since we have to test both, we need mocks for both httpx and requests. + if HF_VERSION >= (1, 0, 0): + httpx_mock.add_response( + method=method, + url=url, + json=json, + content=body, + status_code=status, + headers=headers, + is_optional=True, + is_reusable=True, + ) + else: + rsps.add( + method=method, + url=url, + json=json, + body=body, + status=status, + headers=headers, + ) + + @pytest.fixture -def mock_hf_text_generation_api(): +def mock_hf_text_generation_api(httpx_mock): # type: () -> Any """Mock HuggingFace text generation API""" + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" - # Mock model info endpoint - rsps.add( - responses.GET, - MODEL_ENDPOINT.format(model_name=model_name), + _add_mock_response( + httpx_mock, + rsps, + "GET", + re.compile( + MODEL_ENDPOINT.format(model_name=model_name) + + r"(\?expand=inferenceProviderMapping)?" + ), json={ "id": model_name, "pipeline_tag": "text-generation", @@ -57,9 +91,10 @@ def mock_hf_text_generation_api(): status=200, ) - # Mock text generation endpoint - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name), json={ "generated_text": "[mocked] Hello! How can i help you?", @@ -73,61 +108,78 @@ def mock_hf_text_generation_api(): status=200, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_api_with_errors(): +def mock_hf_api_with_errors(httpx_mock): # type: () -> Any """Mock HuggingFace API that always raises errors for any request""" + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint with error - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={"error": "Model not found"}, status=404, ) # Mock text generation endpoint with error - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name), json={"error": "Internal server error", "message": "Something went wrong"}, status=500, ) # Mock chat completion endpoint with error - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name) + "/v1/chat/completions", json={"error": "Internal server error", "message": "Something went wrong"}, status=500, ) # Catch-all pattern for any other model requests - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", "https://huggingface.co/api/models/test-model-error", json={"error": "Generic model error"}, status=500, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_text_generation_api_streaming(): +def mock_hf_text_generation_api_streaming(httpx_mock): # type: () -> Any """Mock streaming HuggingFace text generation API""" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={ "id": model_name, @@ -146,8 +198,10 @@ def mock_hf_text_generation_api_streaming(): # Mock text generation endpoint for streaming streaming_response = b'data:{"token":{"id":1, "special": false, "text": "the mocked "}}\n\ndata:{"token":{"id":2, "special": false, "text": "model response"}, "details":{"finish_reason": "length", "generated_tokens": 10, "seed": 0}}\n\n' - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name), body=streaming_response, status=200, @@ -158,19 +212,24 @@ def mock_hf_text_generation_api_streaming(): }, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_chat_completion_api(): +def mock_hf_chat_completion_api(httpx_mock): # type: () -> Any """Mock HuggingFace chat completion API""" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={ "id": model_name, @@ -187,8 +246,10 @@ def mock_hf_chat_completion_api(): ) # Mock chat completion endpoint - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name) + "/v1/chat/completions", json={ "id": "xyz-123", @@ -214,19 +275,24 @@ def mock_hf_chat_completion_api(): status=200, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_chat_completion_api_tools(): +def mock_hf_chat_completion_api_tools(httpx_mock): # type: () -> Any """Mock HuggingFace chat completion API with tool calls.""" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={ "id": model_name, @@ -243,8 +309,10 @@ def mock_hf_chat_completion_api_tools(): ) # Mock chat completion endpoint - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name) + "/v1/chat/completions", json={ "id": "xyz-123", @@ -279,19 +347,24 @@ def mock_hf_chat_completion_api_tools(): status=200, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_chat_completion_api_streaming(): +def mock_hf_chat_completion_api_streaming(httpx_mock): # type: () -> Any """Mock streaming HuggingFace chat completion API""" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={ "id": model_name, @@ -313,8 +386,10 @@ def mock_hf_chat_completion_api_streaming(): b'data:{"id":"xyz-124","created":1234567890,"model":"test-model-123","system_fingerprint":"fp_123","choices":[{"delta":{"role":"assistant","content":"model response"},"index":0,"finish_reason":"stop"}],"usage":{"prompt_tokens":183,"completion_tokens":14,"total_tokens":197}}\n\n' ) - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name) + "/v1/chat/completions", body=streaming_chat_response, status=200, @@ -325,19 +400,24 @@ def mock_hf_chat_completion_api_streaming(): }, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps @pytest.fixture -def mock_hf_chat_completion_api_streaming_tools(): +def mock_hf_chat_completion_api_streaming_tools(httpx_mock): # type: () -> Any """Mock streaming HuggingFace chat completion API with tool calls.""" with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: model_name = "test-model" # Mock model info endpoint - rsps.add( - responses.GET, + _add_mock_response( + httpx_mock, + rsps, + "GET", MODEL_ENDPOINT.format(model_name=model_name), json={ "id": model_name, @@ -359,8 +439,10 @@ def mock_hf_chat_completion_api_streaming_tools(): b'data:{"id":"xyz-124","created":1234567890,"model":"test-model-123","system_fingerprint":"fp_123","choices":[{"delta":{"role":"assistant","tool_calls": [{"id": "call_123","type": "function","function": {"name": "get_weather", "arguments": {"location": "Paris"}}}]},"index":0,"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":183,"completion_tokens":14,"total_tokens":197}}\n\n' ) - rsps.add( - responses.POST, + _add_mock_response( + httpx_mock, + rsps, + "POST", INFERENCE_ENDPOINT.format(model_name=model_name) + "/v1/chat/completions", body=streaming_chat_response, status=200, @@ -371,9 +453,13 @@ def mock_hf_chat_completion_api_streaming_tools(): }, ) - yield rsps + if HF_VERSION >= (1, 0, 0): + yield httpx_mock + else: + yield rsps +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_text_generation( @@ -401,7 +487,18 @@ def test_text_generation( ) (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.generate_text" assert span["description"] == "generate_text test-model" @@ -431,6 +528,7 @@ def test_text_generation( assert "gen_ai.response.model" not in span["data"] +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_text_generation_streaming( @@ -459,7 +557,18 @@ def test_text_generation_streaming( pass (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.generate_text" assert span["description"] == "generate_text test-model" @@ -489,6 +598,7 @@ def test_text_generation_streaming( assert "gen_ai.response.model" not in span["data"] +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_chat_completion( @@ -515,7 +625,18 @@ def test_chat_completion( ) (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.chat" assert span["description"] == "chat test-model" @@ -549,6 +670,7 @@ def test_chat_completion( assert span["data"] == expected_data +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_chat_completion_streaming( @@ -577,7 +699,18 @@ def test_chat_completion_streaming( ) (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.chat" assert span["description"] == "chat test-model" @@ -611,6 +744,7 @@ def test_chat_completion_streaming( assert span["data"] == expected_data +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_chat_completion_api_error( sentry_init, capture_events, mock_hf_api_with_errors ): @@ -634,7 +768,17 @@ def test_chat_completion_api_error( assert error["exception"]["values"][0]["mechanism"]["type"] == "huggingface_hub" assert not error["exception"]["values"][0]["mechanism"]["handled"] - (span,) = transaction["spans"] + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.chat" assert span["description"] == "chat test-model" @@ -654,6 +798,7 @@ def test_chat_completion_api_error( assert span["data"] == expected_data +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_span_status_error(sentry_init, capture_events, mock_hf_api_with_errors): # type: (Any, Any, Any) -> None sentry_init(traces_sample_rate=1.0) @@ -669,10 +814,24 @@ def test_span_status_error(sentry_init, capture_events, mock_hf_api_with_errors) (error, transaction) = events assert error["level"] == "error" - assert transaction["spans"][0]["tags"]["status"] == "error" + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None + assert span["tags"]["status"] == "error" + assert transaction["contexts"]["trace"]["status"] == "error" +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_chat_completion_with_tools( @@ -715,7 +874,18 @@ def test_chat_completion_with_tools( ) (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.chat" assert span["description"] == "chat test-model" @@ -750,6 +920,7 @@ def test_chat_completion_with_tools( assert span["data"] == expected_data +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) @pytest.mark.parametrize("send_default_pii", [True, False]) @pytest.mark.parametrize("include_prompts", [True, False]) def test_chat_completion_streaming_with_tools( @@ -795,7 +966,18 @@ def test_chat_completion_streaming_with_tools( ) (transaction,) = events - (span,) = transaction["spans"] + + span = None + for sp in transaction["spans"]: + if sp["op"].startswith("gen_ai"): + assert span is None, "there is exactly one gen_ai span" + span = sp + else: + # there should be no other spans, just the gen_ai span + # and optionally some http.client spans from talking to the hf api + assert sp["op"] == "http.client" + + assert span is not None assert span["op"] == "gen_ai.chat" assert span["description"] == "chat test-model" diff --git a/tox.ini b/tox.ini index c74755f206..41243ccc28 100644 --- a/tox.ini +++ b/tox.ini @@ -85,6 +85,7 @@ envlist = {py3.8,py3.12,py3.13}-huggingface_hub-v0.28.1 {py3.8,py3.12,py3.13}-huggingface_hub-v0.32.6 {py3.8,py3.12,py3.13}-huggingface_hub-v0.35.3 + {py3.9,py3.12,py3.13}-huggingface_hub-v1.0.0rc2 # ~~~ Cloud ~~~ @@ -387,7 +388,9 @@ deps = huggingface_hub-v0.28.1: huggingface_hub==0.28.1 huggingface_hub-v0.32.6: huggingface_hub==0.32.6 huggingface_hub-v0.35.3: huggingface_hub==0.35.3 + huggingface_hub-v1.0.0rc2: huggingface_hub==1.0.0rc2 huggingface_hub: responses + huggingface_hub: pytest-httpx # ~~~ Cloud ~~~ From 8bc196620c70e437f24128e20740dd0990579ff9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 08:32:12 +0000 Subject: [PATCH 15/20] =?UTF-8?q?ci:=20=F0=9F=A4=96=20Update=20test=20matr?= =?UTF-8?q?ix=20with=20new=20releases=20(10/06)=20(#4889)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update our test matrix with new releases of integrated frameworks and libraries. ## How it works - Scan PyPI for all supported releases of all frameworks we have a dedicated test suite for. - Pick a representative sample of releases to run our test suite against. We always test the latest and oldest supported version. - Update [tox.ini](https://github.com/getsentry/sentry-python/blob/master/tox.ini) with the new releases. ## Action required - If CI passes on this PR, it's safe to approve and merge. It means our integrations can handle new versions of frameworks that got pulled in. - If CI doesn't pass on this PR, this points to an incompatibility of either our integration or our test setup with a new version of a framework. - Check what the failures look like and either fix them, or update the [test config](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/config.py) and rerun [scripts/generate-test-files.sh](https://github.com/getsentry/sentry-python/blob/master/scripts/generate-test-files.sh). See [scripts/populate_tox/README.md](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md) for what configuration options are available. _____________________ _🤖 This PR was automatically created using [a GitHub action](https://github.com/getsentry/sentry-python/blob/master/.github/workflows/update-tox.yml)._ --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ivana Kellyer --- scripts/populate_tox/releases.jsonl | 7 ++++--- tox.ini | 14 ++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index bf95102c71..d52a769def 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -46,7 +46,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7"], "name": "boto3", "requires_python": "", "version": "1.12.49", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.6", "version": "1.20.54", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">= 3.7", "version": "1.28.85", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.44", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "boto3", "requires_python": ">=3.9", "version": "1.40.45", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": "", "version": "0.12.25", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Internet :: WWW/HTTP :: WSGI", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", "Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware", "Topic :: Internet :: WWW/HTTP :: WSGI :: Server", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "bottle", "requires_python": null, "version": "0.13.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Object Brokering", "Topic :: System :: Distributed Computing"], "name": "celery", "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", "version": "4.4.7", "yanked": false}} @@ -78,6 +78,7 @@ {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.6", "version": "1.47.5", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.7", "version": "1.62.3", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.9", "version": "1.75.1", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9"], "name": "grpcio", "requires_python": ">=3.9", "version": "1.76.0rc1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.16.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.20.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Trio", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet :: WWW/HTTP"], "name": "httpx", "requires_python": ">=3.6", "version": "0.22.0", "yanked": false}} @@ -102,10 +103,10 @@ {"info": {"classifiers": [], "name": "langgraph", "requires_python": ">=3.10", "version": "1.0.0a4", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.9", "version": "9.12.1", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development", "Topic :: Software Development :: Libraries"], "name": "launchdarkly-server-sdk", "requires_python": ">=3.8", "version": "9.8.1", "yanked": false}} -{"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9"], "name": "litellm", "requires_python": "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8", "version": "1.77.5", "yanked": false}} +{"info": {"classifiers": ["License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9"], "name": "litellm", "requires_python": "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8", "version": "1.77.7", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: AsyncIO", "Framework :: Pydantic", "Framework :: Pydantic :: 1", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": ">=3.8,<4.0", "version": "2.0.1", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.12.1", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.17.0", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.18.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "Intended Audience :: Information Technology", "Intended Audience :: System Administrators", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: HTTP Servers", "Topic :: Software Development", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Application Frameworks", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "litestar", "requires_python": "<4.0,>=3.8", "version": "2.6.4", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: System :: Logging"], "name": "loguru", "requires_python": "<4.0,>=3.5", "version": "0.7.3", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries :: Python Modules", "Typing :: Typed"], "name": "openai", "requires_python": ">=3.7.1", "version": "1.0.1", "yanked": false}} diff --git a/tox.ini b/tox.ini index 41243ccc28..d02b3c73ab 100644 --- a/tox.ini +++ b/tox.ini @@ -63,7 +63,7 @@ envlist = {py3.9,py3.11,py3.12}-langchain-notiktoken-v0.2.17 {py3.9,py3.12,py3.13}-langchain-notiktoken-v0.3.27 - {py3.9,py3.12,py3.13}-litellm-v1.77.5 + {py3.9,py3.12,py3.13}-litellm-v1.77.7 {py3.8,py3.11,py3.12}-openai-base-v1.0.1 {py3.8,py3.12,py3.13}-openai-base-v1.109.1 @@ -92,7 +92,7 @@ envlist = {py3.6,py3.7}-boto3-v1.12.49 {py3.6,py3.9,py3.10}-boto3-v1.20.54 {py3.7,py3.11,py3.12}-boto3-v1.28.85 - {py3.9,py3.12,py3.13}-boto3-v1.40.44 + {py3.9,py3.12,py3.13}-boto3-v1.40.45 {py3.6,py3.7,py3.8}-chalice-v1.16.0 {py3.9,py3.12,py3.13}-chalice-v1.32.0 @@ -159,6 +159,7 @@ envlist = {py3.7,py3.9,py3.10}-grpc-v1.47.5 {py3.7,py3.11,py3.12}-grpc-v1.62.3 {py3.9,py3.12,py3.13}-grpc-v1.75.1 + {py3.9,py3.12,py3.13}-grpc-v1.76.0rc1 {py3.6,py3.8,py3.9}-httpx-v0.16.1 {py3.6,py3.9,py3.10}-httpx-v0.20.0 @@ -239,7 +240,7 @@ envlist = {py3.8,py3.10,py3.11}-litestar-v2.0.1 {py3.8,py3.11,py3.12}-litestar-v2.6.4 {py3.8,py3.11,py3.12}-litestar-v2.12.1 - {py3.8,py3.12,py3.13}-litestar-v2.17.0 + {py3.8,py3.12,py3.13}-litestar-v2.18.0 {py3.6}-pyramid-v1.8.6 {py3.6,py3.8,py3.9}-pyramid-v1.10.8 @@ -360,7 +361,7 @@ deps = langchain-notiktoken: langchain-openai langchain-notiktoken-v0.3.27: langchain-community - litellm-v1.77.5: litellm==1.77.5 + litellm-v1.77.7: litellm==1.77.7 openai-base-v1.0.1: openai==1.0.1 openai-base-v1.109.1: openai==1.109.1 @@ -397,7 +398,7 @@ deps = boto3-v1.12.49: boto3==1.12.49 boto3-v1.20.54: boto3==1.20.54 boto3-v1.28.85: boto3==1.28.85 - boto3-v1.40.44: boto3==1.40.44 + boto3-v1.40.45: boto3==1.40.45 {py3.7,py3.8}-boto3: urllib3<2.0.0 chalice-v1.16.0: chalice==1.16.0 @@ -484,6 +485,7 @@ deps = grpc-v1.47.5: grpcio==1.47.5 grpc-v1.62.3: grpcio==1.62.3 grpc-v1.75.1: grpcio==1.75.1 + grpc-v1.76.0rc1: grpcio==1.76.0rc1 grpc: protobuf grpc: mypy-protobuf grpc: types-protobuf @@ -633,7 +635,7 @@ deps = litestar-v2.0.1: litestar==2.0.1 litestar-v2.6.4: litestar==2.6.4 litestar-v2.12.1: litestar==2.12.1 - litestar-v2.17.0: litestar==2.17.0 + litestar-v2.18.0: litestar==2.18.0 litestar: pytest-asyncio litestar: python-multipart litestar: requests From 41f709e27683ada43513be0edbaeae6ecc099ca0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:46:47 +0000 Subject: [PATCH 16/20] =?UTF-8?q?ci:=20=F0=9F=A4=96=20Update=20test=20matr?= =?UTF-8?q?ix=20with=20new=20releases=20(10/06)=20(#4890)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update our test matrix with new releases of integrated frameworks and libraries. ## How it works - Scan PyPI for all supported releases of all frameworks we have a dedicated test suite for. - Pick a representative sample of releases to run our test suite against. We always test the latest and oldest supported version. - Update [tox.ini](https://github.com/getsentry/sentry-python/blob/master/tox.ini) with the new releases. ## Action required - If CI passes on this PR, it's safe to approve and merge. It means our integrations can handle new versions of frameworks that got pulled in. - If CI doesn't pass on this PR, this points to an incompatibility of either our integration or our test setup with a new version of a framework. - Check what the failures look like and either fix them, or update the [test config](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/config.py) and rerun [scripts/generate-test-files.sh](https://github.com/getsentry/sentry-python/blob/master/scripts/generate-test-files.sh). See [scripts/populate_tox/README.md](https://github.com/getsentry/sentry-python/blob/master/scripts/populate_tox/README.md) for what configuration options are available. _____________________ _🤖 This PR was automatically created using [a GitHub action](https://github.com/getsentry/sentry-python/blob/master/.github/workflows/update-tox.yml)._ --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ivana Kellyer --- scripts/populate_tox/releases.jsonl | 2 +- tox.ini | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/populate_tox/releases.jsonl b/scripts/populate_tox/releases.jsonl index d52a769def..bd04eb7c28 100644 --- a/scripts/populate_tox/releases.jsonl +++ b/scripts/populate_tox/releases.jsonl @@ -191,7 +191,7 @@ {"info": {"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries"], "name": "statsig", "requires_python": ">=3.7", "version": "0.55.3", "yanked": false}} {"info": {"classifiers": ["Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries"], "name": "statsig", "requires_python": ">=3.7", "version": "0.65.0", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "strawberry-graphql", "requires_python": ">=3.8,<4.0", "version": "0.209.8", "yanked": false}} -{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "strawberry-graphql", "requires_python": "<4.0,>=3.9", "version": "0.282.0", "yanked": false}} +{"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules"], "name": "strawberry-graphql", "requires_python": "<4.0,>=3.9", "version": "0.283.0", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "tornado", "requires_python": ">= 3.5", "version": "6.0.4", "yanked": false}} {"info": {"classifiers": ["License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy"], "name": "tornado", "requires_python": ">=3.9", "version": "6.5.2", "yanked": false}} {"info": {"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: No Input/Output (Daemon)", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "Natural Language :: English", "Natural Language :: French", "Natural Language :: German", "Natural Language :: Spanish", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Application Frameworks"], "name": "trytond", "requires_python": null, "version": "1.2.10", "yanked": false}} diff --git a/tox.ini b/tox.ini index d02b3c73ab..8eb04550fb 100644 --- a/tox.ini +++ b/tox.ini @@ -151,7 +151,7 @@ envlist = {py3.8,py3.12,py3.13}-graphene-v3.4.3 {py3.8,py3.10,py3.11}-strawberry-v0.209.8 - {py3.9,py3.12,py3.13}-strawberry-v0.282.0 + {py3.9,py3.12,py3.13}-strawberry-v0.283.0 # ~~~ Network ~~~ @@ -475,7 +475,7 @@ deps = {py3.6}-graphene: aiocontextvars strawberry-v0.209.8: strawberry-graphql[fastapi,flask]==0.209.8 - strawberry-v0.282.0: strawberry-graphql[fastapi,flask]==0.282.0 + strawberry-v0.283.0: strawberry-graphql[fastapi,flask]==0.283.0 strawberry: httpx strawberry-v0.209.8: pydantic<2.11 From bf77a86b92edc8b281b32df99efb6c438e746b18 Mon Sep 17 00:00:00 2001 From: Alex Alderman Webb Date: Mon, 6 Oct 2025 13:27:23 +0200 Subject: [PATCH 17/20] fix(litestar): Copy request info to prevent cookies mutation (#4883) Prevent mutating cookies on incoming HTTP requests if the cookie name is in the scrubbers denylist. Cookies like `token=...` were replaced with `AnnotatedValue` because a shallow reference of the request information was held by the client. A deep copy is introduced so scrubbing does not interfere with Litestar, and in particular does not break `JWTCookieAuth`. Closes https://github.com/getsentry/sentry-python/issues/4882 --------- Co-authored-by: Ivana Kellyer --- sentry_sdk/integrations/litestar.py | 4 +++- sentry_sdk/integrations/starlite.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/litestar.py b/sentry_sdk/integrations/litestar.py index 745a00bcba..0cb9f4b972 100644 --- a/sentry_sdk/integrations/litestar.py +++ b/sentry_sdk/integrations/litestar.py @@ -1,4 +1,6 @@ from collections.abc import Set +from copy import deepcopy + import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations import ( @@ -260,7 +262,7 @@ def event_processor(event, _): event.update( { - "request": request_info, + "request": deepcopy(request_info), "transaction": tx_name, "transaction_info": tx_info, } diff --git a/sentry_sdk/integrations/starlite.py b/sentry_sdk/integrations/starlite.py index daab82d642..855b87ad60 100644 --- a/sentry_sdk/integrations/starlite.py +++ b/sentry_sdk/integrations/starlite.py @@ -1,3 +1,5 @@ +from copy import deepcopy + import sentry_sdk from sentry_sdk.consts import OP from sentry_sdk.integrations import DidNotEnable, Integration @@ -237,7 +239,7 @@ def event_processor(event, _): event.update( { - "request": request_info, + "request": deepcopy(request_info), "transaction": tx_name, "transaction_info": tx_info, } From 91cc2bcd40bd0ce68e30a24d5201357d0ca321c8 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 6 Oct 2025 11:40:44 +0000 Subject: [PATCH 18/20] release: 2.40.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ docs/conf.py | 2 +- sentry_sdk/consts.py | 2 +- setup.py | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 941aec99e6..bb147b2d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## 2.40.0 + +### Various fixes & improvements + +- fix(litestar): Copy request info to prevent cookies mutation (#4883) by @alexander-alderman-webb +- ci: 🤖 Update test matrix with new releases (10/06) (#4890) by @github-actions +- ci: 🤖 Update test matrix with new releases (10/06) (#4889) by @github-actions +- feat(huggingface): Support 1.0.0rc2 (#4873) by @sentrivana +- feat(integrations): add litellm integration (#4864) by @constantinius +- feat(integrations): Add tracing to DramatiqIntegration (#4571) by @Igreh +- fix(tests): Don't assume release is set (#4879) by @sentrivana +- ci: 🤖 Update test matrix with new releases (10/02) (#4880) by @github-actions +- fix(openai-agents): also emit spans for MCP tool calls done by the LLM (#4875) by @constantinius +- feat: Option to not trace HTTP requests based on status codes (#4869) by @alexander-alderman-webb +- fix(openai-agents): Move _set_agent_data call to ai_client_span function (#4876) by @constantinius +- ci: 🤖 Update test matrix with new releases (09/29) (#4872) by @github-actions +- build(deps): update shibuya requirement from <2025.9.22 (#4871) by @dependabot +- feat: Add script to determine lowest supported versions (#4867) by @sentrivana +- docs: Update CONTRIBUTING.md (#4870) by @sentrivana +- ci: Replace `black` and `flake8` with `ruff`. (#4866) by @antonpirker +- feat(toxgen): Generate `TESTPATH` for integrated test suites (#4863) by @sentrivana + ## 2.39.0 ### Various fixes & improvements diff --git a/docs/conf.py b/docs/conf.py index 292e0971e2..2f630c382b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -31,7 +31,7 @@ copyright = "2019-{}, Sentry Team and Contributors".format(datetime.now().year) author = "Sentry Team and Contributors" -release = "2.39.0" +release = "2.40.0" version = ".".join(release.split(".")[:2]) # The short X.Y version. diff --git a/sentry_sdk/consts.py b/sentry_sdk/consts.py index 7b9e61a065..43c7e857ac 100644 --- a/sentry_sdk/consts.py +++ b/sentry_sdk/consts.py @@ -1343,4 +1343,4 @@ def _get_default_options(): del _get_default_options -VERSION = "2.39.0" +VERSION = "2.40.0" diff --git a/setup.py b/setup.py index 6629726798..fbb8694e5e 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def get_file_text(file_name): setup( name="sentry-sdk", - version="2.39.0", + version="2.40.0", author="Sentry Team and Contributors", author_email="hello@sentry.io", url="https://github.com/getsentry/sentry-python", From 070ecd045014c31b79ef131bcfb7f9a8f1ff8771 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 6 Oct 2025 13:50:13 +0200 Subject: [PATCH 19/20] Update CHANGELOG.md --- CHANGELOG.md | 54 +++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb147b2d8b..0aef8e9681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,23 +4,43 @@ ### Various fixes & improvements -- fix(litestar): Copy request info to prevent cookies mutation (#4883) by @alexander-alderman-webb -- ci: 🤖 Update test matrix with new releases (10/06) (#4890) by @github-actions -- ci: 🤖 Update test matrix with new releases (10/06) (#4889) by @github-actions -- feat(huggingface): Support 1.0.0rc2 (#4873) by @sentrivana -- feat(integrations): add litellm integration (#4864) by @constantinius -- feat(integrations): Add tracing to DramatiqIntegration (#4571) by @Igreh -- fix(tests): Don't assume release is set (#4879) by @sentrivana -- ci: 🤖 Update test matrix with new releases (10/02) (#4880) by @github-actions -- fix(openai-agents): also emit spans for MCP tool calls done by the LLM (#4875) by @constantinius -- feat: Option to not trace HTTP requests based on status codes (#4869) by @alexander-alderman-webb -- fix(openai-agents): Move _set_agent_data call to ai_client_span function (#4876) by @constantinius -- ci: 🤖 Update test matrix with new releases (09/29) (#4872) by @github-actions -- build(deps): update shibuya requirement from <2025.9.22 (#4871) by @dependabot -- feat: Add script to determine lowest supported versions (#4867) by @sentrivana -- docs: Update CONTRIBUTING.md (#4870) by @sentrivana -- ci: Replace `black` and `flake8` with `ruff`. (#4866) by @antonpirker -- feat(toxgen): Generate `TESTPATH` for integrated test suites (#4863) by @sentrivana +- Add LiteLLM integration (#4864) by @constantinius + Once you've enabled the new LiteLLM integration, you can use the Sentry AI Agents Monitoring, a Sentry dashboard that helps you understand what's going on with your AI requests: + + ```python + import sentry_sdk + from sentry_sdk.integrations.litellm import LiteLLMIntegration + sentry_sdk.init( + dsn="", + # Set traces_sample_rate to 1.0 to capture 100% + # of transactions for tracing. + traces_sample_rate=1.0, + # Add data like inputs and responses; + # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info + send_default_pii=True, + integrations=[ + LiteLLMIntegration(), + ], + ) + ``` + +- Litestar: Copy request info to prevent cookies mutation (#4883) by @alexander-alderman-webb +- Add tracing to `DramatiqIntegration` (#4571) by @Igreh +- Also emit spans for MCP tool calls done by the LLM (#4875) by @constantinius +- Option to not trace HTTP requests based on status codes (#4869) by @alexander-alderman-webb + You can now disable transactions for incoming requests with specific HTTP status codes. The new `trace_ignore_status_codes` option accepts a `set` of status codes as integers. If a transaction wraps a request that results in one of the provided status codes, the transaction will be unsampled. + + ```python + import sentry_sdk + + sentry_sdk.init( + trace_ignore_status_codes={301, 302, 303, *range(305, 400), 404}, + ) + ``` + +- Move `_set_agent_data` call to `ai_client_span` function (#4876) by @constantinius +- Add script to determine lowest supported versions (#4867) by @sentrivana +- Update `CONTRIBUTING.md` (#4870) by @sentrivana ## 2.39.0 From 04968c4dc2923cf66635378bf3787b9c8a167625 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Mon, 6 Oct 2025 13:55:01 +0200 Subject: [PATCH 20/20] Add links to CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aef8e9681..0bcd623611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Various fixes & improvements - Add LiteLLM integration (#4864) by @constantinius - Once you've enabled the new LiteLLM integration, you can use the Sentry AI Agents Monitoring, a Sentry dashboard that helps you understand what's going on with your AI requests: + Once you've enabled the [new LiteLLM integration](https://docs.sentry.io/platforms/python/integrations/litellm/), you can use the Sentry AI Agents Monitoring, a Sentry dashboard that helps you understand what's going on with your AI requests: ```python import sentry_sdk @@ -28,7 +28,7 @@ - Add tracing to `DramatiqIntegration` (#4571) by @Igreh - Also emit spans for MCP tool calls done by the LLM (#4875) by @constantinius - Option to not trace HTTP requests based on status codes (#4869) by @alexander-alderman-webb - You can now disable transactions for incoming requests with specific HTTP status codes. The new `trace_ignore_status_codes` option accepts a `set` of status codes as integers. If a transaction wraps a request that results in one of the provided status codes, the transaction will be unsampled. + You can now disable transactions for incoming requests with specific HTTP status codes. The [new `trace_ignore_status_codes` option](https://docs.sentry.io/platforms/python/configuration/options/#trace_ignore_status_codes) accepts a `set` of status codes as integers. If a transaction wraps a request that results in one of the provided status codes, the transaction will be unsampled. ```python import sentry_sdk