From 3128327807f9df4c0bf89a18d526477ae72bb52c Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Fri, 17 Oct 2025 11:22:07 -0700 Subject: [PATCH 01/42] chore: remove cassandra integration (#14936) This change removes the `cassandra` contrib package, which is only tested on the to-be-removed Python 3.8 runtime. --- .riot/requirements/164d658.txt | 27 - .riot/requirements/f229429.txt | 27 - ddtrace/_monkey.py | 4 +- .../integration_registry/registry.yaml | 10 - .../contrib/internal/cassandra/__init__.py | 24 - ddtrace/contrib/internal/cassandra/patch.py | 9 - ddtrace/contrib/internal/cassandra/session.py | 316 ---------- ddtrace/ext/cassandra.py | 6 - ddtrace/settings/_config.py | 1 - docs/index.rst | 2 - docs/integrations.rst | 7 - .../notes/cassandra-d3c8aaf478bddc56.yaml | 5 + riotfile.py | 6 - supported_versions_output.json | 12 +- supported_versions_table.csv | 5 +- tests/contrib/cassandra/__init__.py | 0 tests/contrib/cassandra/test.py | 546 ------------------ .../contrib/cassandra/test_cassandra_patch.py | 31 - tests/contrib/suitespec.yml | 16 - 19 files changed, 11 insertions(+), 1043 deletions(-) delete mode 100644 .riot/requirements/164d658.txt delete mode 100644 .riot/requirements/f229429.txt delete mode 100644 ddtrace/contrib/internal/cassandra/__init__.py delete mode 100644 ddtrace/contrib/internal/cassandra/patch.py delete mode 100644 ddtrace/contrib/internal/cassandra/session.py delete mode 100644 ddtrace/ext/cassandra.py create mode 100644 releasenotes/notes/cassandra-d3c8aaf478bddc56.yaml delete mode 100644 tests/contrib/cassandra/__init__.py delete mode 100644 tests/contrib/cassandra/test.py delete mode 100644 tests/contrib/cassandra/test_cassandra_patch.py diff --git a/.riot/requirements/164d658.txt b/.riot/requirements/164d658.txt deleted file mode 100644 index cd0bb3c4f4c..00000000000 --- a/.riot/requirements/164d658.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/164d658.in -# -attrs==23.1.0 -cassandra-driver==3.28.0 -click==8.1.7 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -geomet==0.2.1.post1 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/f229429.txt b/.riot/requirements/f229429.txt deleted file mode 100644 index 4f0448ccdbb..00000000000 --- a/.riot/requirements/f229429.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/f229429.in -# -attrs==23.1.0 -cassandra-driver==3.24.0 -click==8.1.7 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -geomet==0.2.1.post1 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index f943f465d1b..92e3c468807 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -39,7 +39,6 @@ "boto": True, "botocore": True, "bottle": True, - "cassandra": True, "celery": True, "consul": True, "ddtrace_api": True, @@ -156,7 +155,6 @@ "psycopg2", ), "snowflake": ("snowflake.connector",), - "cassandra": ("cassandra.cluster",), "dogpile_cache": ("dogpile.cache",), "mysqldb": ("MySQLdb",), "futures": ("concurrent.futures.thread",), @@ -335,7 +333,7 @@ def patch_all(**patch_modules: bool) -> None: :param dict patch_modules: Override whether particular modules are patched or not. - >>> _patch_all(redis=False, cassandra=False) + >>> _patch_all(redis=False) """ deprecate( "patch_all is deprecated and will be removed in a future version of the tracer.", diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index 4a467241940..1a877c8e784 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -195,16 +195,6 @@ integrations: min: 0.12.25 max: 0.13.4 -- integration_name: cassandra - is_external_package: true - is_tested: true - dependency_names: - - cassandra-driver - tested_versions_by_dependency: - cassandra-driver: - min: 3.24.0 - max: 3.28.0 - - integration_name: celery is_external_package: true is_tested: true diff --git a/ddtrace/contrib/internal/cassandra/__init__.py b/ddtrace/contrib/internal/cassandra/__init__.py deleted file mode 100644 index d0de07f8f16..00000000000 --- a/ddtrace/contrib/internal/cassandra/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Instrument Cassandra to report Cassandra queries. - -``import ddtrace.auto`` will automatically patch your Cluster instance to make it work. -:: - - from ddtrace import patch - from ddtrace.trace import Pin - from cassandra.cluster import Cluster - - # If not patched yet, you can patch cassandra specifically - patch(cassandra=True) - - # This will report spans with the default instrumentation - cluster = Cluster(contact_points=["127.0.0.1"], port=9042) - session = cluster.connect("my_keyspace") - # Example of instrumented query - session.execute("select id from my_table limit 10;") - - # Use a pin to specify metadata related to this cluster - cluster = Cluster(contact_points=['10.1.1.3', '10.1.1.4', '10.1.1.5'], port=9042) - Pin.override(cluster, service='cassandra-backend') - session = cluster.connect("my_keyspace") - session.execute("select id from my_table limit 10;") -""" diff --git a/ddtrace/contrib/internal/cassandra/patch.py b/ddtrace/contrib/internal/cassandra/patch.py deleted file mode 100644 index bc82b8dbe0e..00000000000 --- a/ddtrace/contrib/internal/cassandra/patch.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Dict - -from .session import get_version # noqa: F401 -from .session import patch # noqa: F401 -from .session import unpatch # noqa: F401 - - -def _supported_versions() -> Dict[str, str]: - return {"cassandra": ">=3.24.0"} diff --git a/ddtrace/contrib/internal/cassandra/session.py b/ddtrace/contrib/internal/cassandra/session.py deleted file mode 100644 index ed1b56ea251..00000000000 --- a/ddtrace/contrib/internal/cassandra/session.py +++ /dev/null @@ -1,316 +0,0 @@ -""" -Trace queries along a session to a cassandra cluster -""" -import sys -from typing import Any -from typing import Dict -from typing import List -from typing import Optional - -from cassandra import __version__ - - -try: - import cassandra.cluster as cassandra_cluster -except AttributeError: - from cassandra import cluster as cassandra_cluster -from cassandra.query import BatchStatement -from cassandra.query import BoundStatement -from cassandra.query import PreparedStatement -from cassandra.query import SimpleStatement -import wrapt - -from ddtrace import config -from ddtrace._trace.pin import Pin -from ddtrace.constants import _SPAN_MEASURED_KEY -from ddtrace.constants import ERROR_MSG -from ddtrace.constants import ERROR_TYPE -from ddtrace.constants import SPAN_KIND -from ddtrace.ext import SpanKind -from ddtrace.ext import SpanTypes -from ddtrace.ext import cassandra as cassx -from ddtrace.ext import db -from ddtrace.ext import net -from ddtrace.internal.compat import maybe_stringify -from ddtrace.internal.constants import COMPONENT -from ddtrace.internal.logger import get_logger -from ddtrace.internal.schema import schematize_database_operation -from ddtrace.internal.schema import schematize_service_name -from ddtrace.internal.utils import get_argument_value -from ddtrace.internal.utils.formats import deep_getattr -from ddtrace.trace import Span - - -log = get_logger(__name__) - -RESOURCE_MAX_LENGTH = 5000 -SERVICE = schematize_service_name("cassandra") -CURRENT_SPAN = "_ddtrace_current_span" -PAGE_NUMBER = "_ddtrace_page_number" - - -# Original connect connect function -_connect = cassandra_cluster.Cluster.connect - - -def get_version(): - # type: () -> str - return __version__ - - -def patch(): - """patch will add tracing to the cassandra library.""" - cassandra_cluster.Cluster.connect = wrapt.FunctionWrapper(_connect, traced_connect) - Pin(service=SERVICE).onto(cassandra_cluster.Cluster) - cassandra_cluster._datadog_patch = True - - -def unpatch(): - cassandra_cluster.Cluster.connect = _connect - cassandra_cluster._datadog_patch = False - - -def traced_connect(func, instance, args, kwargs): - session = func(*args, **kwargs) - if not isinstance(session.execute, wrapt.FunctionWrapper): - # FIXME[matt] this should probably be private. - session.execute_async = wrapt.FunctionWrapper(session.execute_async, traced_execute_async) - return session - - -def _close_span_on_success(result, future): - span = getattr(future, CURRENT_SPAN, None) - if not span: - log.debug("traced_set_final_result was not able to get the current span from the ResponseFuture") - return - try: - span.set_tags(_extract_result_metas(cassandra_cluster.ResultSet(future, result))) - except Exception: - log.debug("an exception occurred while setting tags", exc_info=True) - finally: - span.finish() - delattr(future, CURRENT_SPAN) - - -def traced_set_final_result(func, instance, args, kwargs): - result = get_argument_value(args, kwargs, 0, "response") - _close_span_on_success(result, instance) - return func(*args, **kwargs) - - -def _close_span_on_error(exc, future): - span = getattr(future, CURRENT_SPAN, None) - if not span: - log.debug("traced_set_final_exception was not able to get the current span from the ResponseFuture") - return - try: - # handling the exception manually because we - # don't have an ongoing exception here - span.error = 1 - span.set_tag_str(ERROR_MSG, exc.args[0]) - span.set_tag_str(ERROR_TYPE, exc.__class__.__name__) - except Exception: - log.debug("traced_set_final_exception was not able to set the error, failed with error", exc_info=True) - finally: - span.finish() - delattr(future, CURRENT_SPAN) - - -def traced_set_final_exception(func, instance, args, kwargs): - exc = get_argument_value(args, kwargs, 0, "response") - _close_span_on_error(exc, instance) - return func(*args, **kwargs) - - -def traced_start_fetching_next_page(func, instance, args, kwargs): - has_more_pages = getattr(instance, "has_more_pages", True) - if not has_more_pages: - return func(*args, **kwargs) - session = getattr(instance, "session", None) - cluster = getattr(session, "cluster", None) - pin = Pin.get_from(cluster) - if not pin or not pin.enabled(): - return func(*args, **kwargs) - - # In case the current span is not finished we make sure to finish it - old_span = getattr(instance, CURRENT_SPAN, None) - if old_span: - log.debug("previous span was not finished before fetching next page") - old_span.finish() - - query = getattr(instance, "query", None) - - sanitized_query = _sanitize_query(query) if isinstance(query, BatchStatement) else None - statements_and_parameters = query._statements_and_parameters if isinstance(query, BatchStatement) else None - additional_tags = dict(**_extract_session_metas(session), **_extract_cluster_metas(cluster)) - span = _start_span_and_set_tags( - pin, _get_resource(query), additional_tags, sanitized_query, statements_and_parameters - ) - - page_number = getattr(instance, PAGE_NUMBER, 1) + 1 - setattr(instance, PAGE_NUMBER, page_number) - setattr(instance, CURRENT_SPAN, span) - try: - return func(*args, **kwargs) - except Exception: - with span: - span.set_exc_info(*sys.exc_info()) - raise - - -def traced_execute_async(func, instance, args, kwargs): - cluster = getattr(instance, "cluster", None) - pin = Pin.get_from(cluster) - if not pin or not pin.enabled(): - return func(*args, **kwargs) - - query = get_argument_value(args, kwargs, 0, "query") - - sanitized_query = _sanitize_query(query) if isinstance(query, BatchStatement) else None - statements_and_parameters = query._statements_and_parameters if isinstance(query, BatchStatement) else None - additional_tags = dict(**_extract_session_metas(instance), **_extract_cluster_metas(cluster)) - span = _start_span_and_set_tags( - pin, _get_resource(query), additional_tags, sanitized_query, statements_and_parameters - ) - - try: - result = func(*args, **kwargs) - setattr(result, CURRENT_SPAN, span) - setattr(result, PAGE_NUMBER, 1) - result._set_final_result = wrapt.FunctionWrapper(result._set_final_result, traced_set_final_result) - result._set_final_exception = wrapt.FunctionWrapper(result._set_final_exception, traced_set_final_exception) - result.start_fetching_next_page = wrapt.FunctionWrapper( - result.start_fetching_next_page, traced_start_fetching_next_page - ) - - # Since we cannot be sure that the previous methods were overwritten - # before the call ended, we add callbacks that will be run - # synchronously if the call already returned and we remove them right - # after. - result.add_callbacks( - _close_span_on_success, _close_span_on_error, callback_args=(result,), errback_args=(result,) - ) - result.clear_callbacks() - return result - except Exception: - with span: - span.set_exc_info(*sys.exc_info()) - raise - - -def _start_span_and_set_tags( - pin, - resource: str, - additional_tags: Dict, - query: Optional[str] = None, - statements_and_parameters: Optional[List] = None, -) -> Span: - span = pin.tracer.trace( - schematize_database_operation("cassandra.query", database_provider="cassandra"), - service=pin.service, - span_type=SpanTypes.CASSANDRA, - ) - span.set_tag_str(COMPONENT, config.cassandra.integration_name) - span.set_tag_str(db.SYSTEM, "cassandra") - span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - # PERF: avoid setting via Span.set_tag - span.set_metric(_SPAN_MEASURED_KEY, 1) - span.set_tags(additional_tags) - if query is not None: - span.set_tag_str("cassandra.query", query) - if statements_and_parameters is not None: - span.set_metric("cassandra.batch_size", len(statements_and_parameters)) - span.resource = resource[:RESOURCE_MAX_LENGTH] - return span - - -def _extract_session_metas(session): - metas = {} - - if getattr(session, "keyspace", None): - # FIXME the keyspace can be overridden explicitly in the query itself - # e.g. 'select * from trace.hash_to_resource' - metas[cassx.KEYSPACE] = session.keyspace.lower() - - return metas - - -def _extract_cluster_metas(cluster): - metas = {} - if deep_getattr(cluster, "metadata.cluster_name"): - metas[cassx.CLUSTER] = cluster.metadata.cluster_name - if getattr(cluster, "port", None): - metas[net.TARGET_PORT] = cluster.port - - return metas - - -def _extract_result_metas(result): - metas = {} - if result is None: - return metas - - future = getattr(result, "response_future", None) - - if future: - # get the host - host = maybe_stringify(getattr(future, "coordinator_host", None)) - if host: - host, _, port = host.partition(":") - metas[net.TARGET_HOST] = host - metas[net.SERVER_ADDRESS] = host - if port: - metas[net.TARGET_PORT] = int(port) - elif hasattr(future, "_current_host"): - address = deep_getattr(future, "_current_host.address") - if address: - metas[net.TARGET_HOST] = address - metas[net.SERVER_ADDRESS] = address - - query = getattr(future, "query", None) - if getattr(query, "consistency_level", None): - metas[cassx.CONSISTENCY_LEVEL] = query.consistency_level - if getattr(query, "keyspace", None): - metas[cassx.KEYSPACE] = query.keyspace.lower() - - page_number = getattr(future, PAGE_NUMBER, 1) - has_more_pages = future.has_more_pages - is_paginated = has_more_pages or page_number > 1 - metas[cassx.PAGINATED] = is_paginated - if is_paginated: - metas[cassx.PAGE_NUMBER] = page_number - - if hasattr(result, "current_rows"): - result_rows = result.current_rows or [] - metas[db.ROWCOUNT] = len(result_rows) - - return metas - - -def _get_resource(query: Any) -> str: - if isinstance(query, SimpleStatement) or isinstance(query, PreparedStatement): - return getattr(query, "query_string", query) - elif isinstance(query, BatchStatement): - return "BatchStatement" - elif isinstance(query, BoundStatement): - ps = getattr(query, "prepared_statement", None) - if ps: - return getattr(ps, "query_string", None) - elif isinstance(query, str): - return query - else: - return "unknown-query-type" - - -def _sanitize_query(query: BatchStatement) -> str: - """ - Each element in `_statements_and_parameters` is: - (is_prepared, statement, parameters) - ref:https://github.com/datastax/python-driver/blob/13d6d72be74f40fcef5ec0f2b3e98538b3b87459/cassandra/query.py#L844 - - For prepared statements, the `statement` value is just the query_id - which is not a statement and when trying to join with other strings - raises an error in python3 around joining bytes to unicode, so this - just filters out prepared statements from this tag value - """ - return "; ".join(q[1] for q in query._statements_and_parameters[:2] if not q[0]) diff --git a/ddtrace/ext/cassandra.py b/ddtrace/ext/cassandra.py deleted file mode 100644 index d510897d12d..00000000000 --- a/ddtrace/ext/cassandra.py +++ /dev/null @@ -1,6 +0,0 @@ -# tags -CLUSTER = "cassandra.cluster" -KEYSPACE = "cassandra.keyspace" -CONSISTENCY_LEVEL = "cassandra.consistency_level" -PAGINATED = "cassandra.paginated" -PAGE_NUMBER = "cassandra.page_number" diff --git a/ddtrace/settings/_config.py b/ddtrace/settings/_config.py index 96c66bb5fce..9a14793c080 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/settings/_config.py @@ -178,7 +178,6 @@ "crewai", "pydantic_ai", "logging", - "cassandra", "boto", "mariadb", "aiohttp", diff --git a/docs/index.rst b/docs/index.rst index caf65d873e2..8275e9998b2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -72,8 +72,6 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`bottle` | >= 0.12 | No | | +--------------------------------------------------+------------+----------+------+ -| :ref:`cassandra` | >= 3.24 | Yes | | -+--------------------------------------------------+------------+----------+------+ | :ref:`celery` | >= 4.4 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`cherrypy` | >= 17.0 | No | | diff --git a/docs/integrations.rst b/docs/integrations.rst index c33138a508e..ba36c26912d 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -110,13 +110,6 @@ Bottle .. automodule:: ddtrace.contrib.bottle -.. _cassandra: - -Cassandra -^^^^^^^^^ -.. automodule:: ddtrace.contrib.internal.cassandra - - .. _celery: Celery diff --git a/releasenotes/notes/cassandra-d3c8aaf478bddc56.yaml b/releasenotes/notes/cassandra-d3c8aaf478bddc56.yaml new file mode 100644 index 00000000000..07df72ad56a --- /dev/null +++ b/releasenotes/notes/cassandra-d3c8aaf478bddc56.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + cassandra: The Cassandra integration is removed because it is only compatible with Python 3.8, + which is a year past its end-of-life. diff --git a/riotfile.py b/riotfile.py index f12379c4b8c..224a59ca7b4 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2170,12 +2170,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), - Venv( - name="cassandra", - pys="3.8", # see https://github.com/r4fek/django-cassandra-engine/issues/104 - pkgs={"cassandra-driver": ["~=3.24.0", latest], "pytest-randomly": latest}, - command="pytest {cmdargs} tests/contrib/cassandra", - ), Venv( name="algoliasearch", command="pytest {cmdargs} tests/contrib/algoliasearch", diff --git a/supported_versions_output.json b/supported_versions_output.json index c1ea2d658ad..ba6dc49f9cb 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -106,7 +106,7 @@ "minimum_tracer_supported": "5.12.2", "max_tracer_supported": "5.15.0", "pinned": "true", - "auto-instrumented": false + "auto-instrumented": true }, { "dependency": "azure-functions", @@ -147,13 +147,6 @@ "max_tracer_supported": "0.13.4", "auto-instrumented": true }, - { - "dependency": "cassandra-driver", - "integration": "cassandra", - "minimum_tracer_supported": "3.24.0", - "max_tracer_supported": "3.28.0", - "auto-instrumented": true - }, { "dependency": "celery", "integration": "celery", @@ -489,7 +482,8 @@ "dependency": "openai", "integration": "openai", "minimum_tracer_supported": "1.0.0", - "max_tracer_supported": "1.91.0", + "max_tracer_supported": "1.109.1", + "pinned": "true", "auto-instrumented": true }, { diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 94af82316ad..25309eedf48 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -13,13 +13,12 @@ asyncpg,asyncpg,0.22.0,0.30.0,True avro,avro,1.12.0,1.12.0,True datadog-lambda,aws_lambda,6.105.0,6.105.0,True datadog_lambda,aws_lambda,6.105.0,6.105.0,True -azure-eventhub,azure_eventhubs *,5.12.2,5.15.0,False +azure-eventhub,azure_eventhubs *,5.12.2,5.15.0,True azure-functions,azure_functions *,1.10.1,1.23.0,True azure-servicebus,azure_servicebus *,7.14.2,7.14.2,True boto3,botocore *,1.34.49,1.38.26,True botocore,botocore *,1.34.49,1.38.26,True bottle,bottle,0.12.25,0.13.4,True -cassandra-driver,cassandra,3.24.0,3.28.0,True celery,celery,5.5.3,5.5.3,True cherrypy,cherrypy,17.0.0,18.10.0,False python-consul,consul,1.1.0,1.1.0,True @@ -67,7 +66,7 @@ molten,molten,1.0.2,1.0.2,True mongoengine,mongoengine,0.23.1,0.29.1,True mysql-connector-python,mysql,8.0.5,9.4.0,True mysqlclient,mysqldb,2.2.1,2.2.6,True -openai,openai,1.0.0,1.91.0,True +openai,openai *,1.0.0,1.109.1,True openai-agents,openai_agents,0.0.8,0.0.16,True protobuf,protobuf,5.29.3,6.32.0,False psycopg,psycopg,3.0.18,3.2.10,True diff --git a/tests/contrib/cassandra/__init__.py b/tests/contrib/cassandra/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/contrib/cassandra/test.py b/tests/contrib/cassandra/test.py deleted file mode 100644 index 708d599040a..00000000000 --- a/tests/contrib/cassandra/test.py +++ /dev/null @@ -1,546 +0,0 @@ -import contextlib -import logging -from threading import Event -import unittest - -from cassandra.cluster import Cluster -from cassandra.cluster import ResultSet -from cassandra.query import BatchStatement -from cassandra.query import SimpleStatement -import mock - -from ddtrace import config -from ddtrace._trace.pin import Pin -from ddtrace.constants import ERROR_MSG -from ddtrace.constants import ERROR_TYPE -from ddtrace.contrib.internal.cassandra.patch import patch -from ddtrace.contrib.internal.cassandra.patch import unpatch -from ddtrace.contrib.internal.cassandra.session import SERVICE -from ddtrace.ext import cassandra as cassx -from ddtrace.ext import net -from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.contrib.config import CASSANDRA_CONFIG -from tests.opentracer.utils import init_tracer -from tests.utils import DummyTracer -from tests.utils import TracerTestCase -from tests.utils import assert_is_measured - - -# Oftentimes our tests fails because Cassandra connection timeouts during keyspace drop. Slowness in keyspace drop -# is known and is due to 'auto_snapshot' configuration. In our test env we should disable it, but the official cassandra -# image that we are using only allows us to configure a few configs: -# https://github.com/docker-library/cassandra/blob/4474c6c5cc2a81ee57c5615aae00555fca7e26a6/3.11/docker-entrypoint.sh#L51 -# So for now we just increase the timeout, if this is not enough we may want to extend the official image with our own -# custom image. -CONNECTION_TIMEOUT_SECS = 20 # override the default value of 5 - -logging.getLogger("cassandra").setLevel(logging.INFO) - - -def _setup(testObject): - self = testObject or mock.Mock() - - # skip all the modules if the Cluster is not available - if not Cluster: - raise unittest.SkipTest("cassandra.cluster.Cluster is not available.") - - # create the KEYSPACE for this test module - self.cluster = Cluster(port=CASSANDRA_CONFIG["port"], connect_timeout=CONNECTION_TIMEOUT_SECS) - self.session = self.cluster.connect() - self.session.execute("DROP KEYSPACE IF EXISTS test", timeout=10) - self.session.execute( - "CREATE KEYSPACE if not exists test WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 1};" # noqa:E501 - ) - self.session.execute("CREATE TABLE if not exists test.person (name text PRIMARY KEY, age int, description text)") - self.session.execute( - "CREATE TABLE if not exists test.person_write (name text PRIMARY KEY, age int, description text)" - ) - self.session.execute( - "INSERT INTO test.person (name, age, description) VALUES ('Cassandra', 100, 'A cruel mistress')" - ) - self.session.execute( - "INSERT INTO test.person (name, age, description) VALUES ('Athena', 100, 'Whose shield is thunder')" - ) - self.session.execute( - "INSERT INTO test.person (name, age, description) VALUES ('Calypso', 100, 'Softly-braided nymph')" - ) - - -def _teardown(testObject): - self = testObject or mock.Mock() - # destroy the KEYSPACE - self.session.execute("DROP TABLE IF EXISTS test.person") - self.session.execute("DROP TABLE IF EXISTS test.person_write") - self.session.execute("DROP KEYSPACE IF EXISTS test", timeout=10) - - -def setUpModule(): - _setup(None) - - -def tearDownModule(): - _teardown(None) - - -class CassandraBase(object): - """ - Needs a running Cassandra - """ - - TEST_QUERY = "SELECT * from test.person WHERE name = 'Cassandra'" - TEST_QUERY_PAGINATED = "SELECT * from test.person" - TEST_KEYSPACE = "test" - TEST_PORT = CASSANDRA_CONFIG["port"] - TEST_SERVICE = "test-cassandra" - - def setUp(self): - _setup(self) - - def tearDown(self): - _teardown(self) - - @contextlib.contextmanager - def override_config(self, integration, values): - """ - Temporarily override an integration configuration value - >>> with self.override_config('flask', dict(service_name='test-service')): - ... # Your test - """ - options = getattr(config, integration) - - original = dict((key, options.get(key)) for key in values.keys()) - - options.update(values) - try: - yield - finally: - options.update(original) - - def _assert_result_correct(self, result): - assert len(result.current_rows) == 1 - for r in result: - assert r.name == "Cassandra" - assert r.age == 100 - assert r.description == "A cruel mistress" - - def _test_query_base(self, execute_fn): - session, tracer = self._traced_session() - - result = execute_fn(session, self.TEST_QUERY) - self._assert_result_correct(result) - - spans = tracer.pop() - assert spans, spans - - # another for the actual query - assert len(spans) == 1 - - query = spans[0] - - assert_is_measured(query) - assert query.service == self.TEST_SERVICE - assert query.resource == self.TEST_QUERY - assert query.span_type == "cassandra" - - assert query.get_tag(cassx.KEYSPACE) == self.TEST_KEYSPACE - assert query.get_metric("db.row_count") == 1 - assert query.get_metric("network.destination.port") == self.TEST_PORT - assert query.get_tag(cassx.PAGE_NUMBER) is None - assert query.get_tag(cassx.PAGINATED) == "False" - assert query.get_tag(net.TARGET_HOST) == "127.0.0.1" - assert query.get_tag(net.SERVER_ADDRESS) == "127.0.0.1" - assert query.get_tag("component") == "cassandra" - assert query.get_tag("span.kind") == "client" - assert query.get_tag("db.system") == "cassandra" - - def test_query(self): - def execute_fn(session, query): - return session.execute(query) - - self._test_query_base(execute_fn) - - def test_query_ot(self): - """Ensure that cassandra works with the opentracer.""" - - def execute_fn(session, query): - return session.execute(query) - - session, tracer = self._traced_session() - ot_tracer = init_tracer("cass_svc", tracer) - - with ot_tracer.start_active_span("cass_op"): - result = execute_fn(session, self.TEST_QUERY) - self._assert_result_correct(result) - - spans = tracer.pop() - assert spans, spans - - # another for the actual query - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "cass_op" - assert ot_span.service == "cass_svc" - - assert dd_span.service == self.TEST_SERVICE - assert dd_span.resource == self.TEST_QUERY - assert dd_span.span_type == "cassandra" - - assert dd_span.get_tag(cassx.KEYSPACE) == self.TEST_KEYSPACE - assert dd_span.get_metric("db.row_count") == 1 - assert dd_span.get_metric("network.destination.port") == self.TEST_PORT - assert dd_span.get_tag(cassx.PAGE_NUMBER) is None - assert dd_span.get_tag(cassx.PAGINATED) == "False" - assert dd_span.get_tag(net.TARGET_HOST) == "127.0.0.1" - assert dd_span.get_tag(net.SERVER_ADDRESS) == "127.0.0.1" - assert dd_span.get_tag("component") == "cassandra" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.get_tag("db.system") == "cassandra" - - def test_query_async(self): - def execute_fn(session, query): - event = Event() - result = [] - future = session.execute_async(query) - - def callback(results): - result.append(ResultSet(future, results)) - event.set() - - future.add_callback(callback) - event.wait() - return result[0] - - self._test_query_base(execute_fn) - - def test_query_async_clearing_callbacks(self): - def execute_fn(session, query): - future = session.execute_async(query) - future.clear_callbacks() - return future.result() - - self._test_query_base(execute_fn) - - def test_span_is_removed_from_future(self): - session, tracer = self._traced_session() - future = session.execute_async(self.TEST_QUERY) - future.result() - span = getattr(future, "_ddtrace_current_span", None) - assert span is None - - def test_paginated_query(self): - session, tracer = self._traced_session() - - statement = SimpleStatement(self.TEST_QUERY_PAGINATED, fetch_size=1) - result = session.execute(statement) - # iterate over all pages - results = list(result) - assert len(results) == 3 - - spans = tracer.pop() - assert spans, spans - - # There are 4 spans for 3 results since the driver makes a request with - # no result to check that it has reached the last page - assert len(spans) == 4 - - for i in range(4): - query = spans[i] - assert query.service == self.TEST_SERVICE - assert query.resource == self.TEST_QUERY_PAGINATED - assert query.span_type == "cassandra" - - assert query.get_tag(cassx.KEYSPACE) == self.TEST_KEYSPACE - assert query.get_metric("network.destination.port") == self.TEST_PORT - if i == 3: - assert query.get_metric("db.row_count") == 0 - else: - assert query.get_metric("db.row_count") == 1 - assert query.get_tag(net.TARGET_HOST) == "127.0.0.1" - assert query.get_tag(net.SERVER_ADDRESS) == "127.0.0.1" - assert query.get_tag(cassx.PAGINATED) == "True" - assert query.get_metric(cassx.PAGE_NUMBER) == i + 1 - assert query.get_tag("db.system") == "cassandra" - - def test_trace_with_service(self): - session, tracer = self._traced_session() - - session.execute(self.TEST_QUERY) - spans = tracer.pop() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.service == self.TEST_SERVICE - - def test_trace_error(self): - session, tracer = self._traced_session() - - try: - session.execute("select * from test.i_dont_exist limit 1") - except Exception: - pass - else: - assert 0 - - spans = tracer.pop() - assert spans - query = spans[0] - assert query.error == 1 - for k in (ERROR_MSG, ERROR_TYPE): - assert query.get_tag(k) - - def test_bound_statement(self): - session, tracer = self._traced_session() - - query = "INSERT INTO test.person_write (name, age, description) VALUES (?, ?, ?)" - prepared = session.prepare(query) - session.execute(prepared, ("matt", 34, "can")) - - prepared = session.prepare(query) - bound_stmt = prepared.bind(("leo", 16, "fr")) - session.execute(bound_stmt) - - spans = tracer.pop() - assert len(spans) == 2 - for s in spans: - assert s.resource == query - - def test_batch_statement(self): - session, tracer = self._traced_session() - - batch = BatchStatement() - batch.add( - SimpleStatement("INSERT INTO test.person_write (name, age, description) VALUES (%s, %s, %s)"), - ("Joe", 1, "a"), - ) - batch.add( - SimpleStatement("INSERT INTO test.person_write (name, age, description) VALUES (%s, %s, %s)"), - ("Jane", 2, "b"), - ) - session.execute(batch) - - spans = tracer.pop() - assert len(spans) == 1 - s = spans[0] - assert s.resource == "BatchStatement" - assert s.get_metric("cassandra.batch_size") == 2 - assert "test.person" in s.get_tag("cassandra.query") - - def test_batched_bound_statement(self): - session, tracer = self._traced_session() - - batch = BatchStatement() - - prepared_statement = session.prepare("INSERT INTO test.person_write (name, age, description) VALUES (?, ?, ?)") - batch.add(prepared_statement.bind(("matt", 34, "can"))) - session.execute(batch) - - spans = tracer.pop() - assert len(spans) == 1 - s = spans[0] - assert s.resource == "BatchStatement" - assert s.get_tag("cassandra.query") == "" - - -class TestCassPatchDefault(unittest.TestCase, CassandraBase): - """Test Cassandra instrumentation with patching and default configuration""" - - TEST_SERVICE = SERVICE - - def tearDown(self): - unpatch() - - def setUp(self): - CassandraBase.setUp(self) - patch() - - def _traced_session(self): - tracer = DummyTracer() - Pin.get_from(self.cluster)._clone(tracer=tracer).onto(self.cluster) - return self.cluster.connect(self.TEST_KEYSPACE), tracer - - -class TestCassPatchAll(TestCassPatchDefault): - """Test Cassandra instrumentation with patching and custom service on all clusters""" - - TEST_SERVICE = "test-cassandra-patch-all" - - def tearDown(self): - unpatch() - - def setUp(self): - CassandraBase.setUp(self) - patch() - - def _traced_session(self): - tracer = DummyTracer() - # pin the global Cluster to test if they will conflict - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = tracer - pin.onto(Cluster) - self.cluster = Cluster(port=CASSANDRA_CONFIG["port"]) - - return self.cluster.connect(self.TEST_KEYSPACE), tracer - - -class TestCassPatchOne(TestCassPatchDefault): - """Test Cassandra instrumentation with patching and custom service on one cluster""" - - TEST_SERVICE = "test-cassandra-patch-one" - - def tearDown(self): - unpatch() - - def setUp(self): - CassandraBase.setUp(self) - patch() - - def _traced_session(self): - tracer = DummyTracer() - # pin the global Cluster to test if they will conflict - Pin(service="not-%s" % self.TEST_SERVICE).onto(Cluster) - self.cluster = Cluster(port=CASSANDRA_CONFIG["port"]) - - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = tracer - pin.onto(self.cluster) - return self.cluster.connect(self.TEST_KEYSPACE), tracer - - def test_patch_unpatch(self): - # Test patch idempotence - patch() - patch() - - tracer = DummyTracer() - Pin.get_from(Cluster)._clone(tracer=tracer).onto(Cluster) - - session = Cluster(port=CASSANDRA_CONFIG["port"]).connect(self.TEST_KEYSPACE) - session.execute(self.TEST_QUERY) - - spans = tracer.pop() - assert spans, spans - assert len(spans) == 1 - - # Test unpatch - unpatch() - - session = Cluster(port=CASSANDRA_CONFIG["port"]).connect(self.TEST_KEYSPACE) - session.execute(self.TEST_QUERY) - - spans = tracer.pop() - assert not spans, spans - - # Test patch again - patch() - Pin.get_from(Cluster)._clone(tracer=tracer).onto(Cluster) - - session = Cluster(port=CASSANDRA_CONFIG["port"]).connect(self.TEST_KEYSPACE) - session.execute(self.TEST_QUERY) - - spans = tracer.pop() - assert spans, spans - - -class TestCassandraConfig(TracerTestCase): - """ - Test various configurations of the Cassandra integration. - """ - - TEST_QUERY = "SELECT * from test.person WHERE name = 'Cassandra'" - TEST_KEYSPACE = "test" - - def setUp(self): - super(TestCassandraConfig, self).setUp() - patch() - self.tracer = DummyTracer() - self.cluster = Cluster(port=CASSANDRA_CONFIG["port"]) - Pin.get_from(self.cluster)._clone(tracer=self.tracer).onto(self.cluster) - self.session = self.cluster.connect(self.TEST_KEYSPACE) - - def tearDown(self): - unpatch() - super(TestCassandraConfig, self).tearDown() - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) - def test_user_specified_service_v0(self): - """ - v0: When a user specifies a service for the app - The cassandra integration should not use it. - """ - # Ensure that the service name was configured - from ddtrace import config - - assert config.service == "mysvc" - - self.session.execute(self.TEST_QUERY) - spans = self.pop_spans() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.service != "mysvc" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) - def test_user_specified_service_v1(self): - """ - v1: When a user specifies a service for the app - The cassandra integration should use it. - """ - # Ensure that the service name was configured - from ddtrace import config - - assert config.service == "mysvc" - - self.session.execute(self.TEST_QUERY) - spans = self.pop_spans() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.service == "mysvc" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) - def test_unspecified_service_v1(self): - """ - v1: When a user does not specify a service for the app - dd-trace-py should default to internal.schema.DEFAULT_SPAN_SERVICE_NAME - """ - # Ensure that the service name was configured - from ddtrace import config - - assert config.service == DEFAULT_SPAN_SERVICE_NAME - - self.session.execute(self.TEST_QUERY) - spans = self.pop_spans() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.service == DEFAULT_SPAN_SERVICE_NAME - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) - def test_span_name_v0_schema(self): - """ - When a user specifies a service for the app - The cassandra integration should not use it. - """ - self.session.execute(self.TEST_QUERY) - spans = self.pop_spans() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.name == "cassandra.query" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) - def test_span_name_v1_schema(self): - """ - When a user specifies a service for the app - The cassandra integration should not use it. - """ - self.session.execute(self.TEST_QUERY) - spans = self.pop_spans() - assert spans - assert len(spans) == 1 - query = spans[0] - assert query.name == "cassandra.query" diff --git a/tests/contrib/cassandra/test_cassandra_patch.py b/tests/contrib/cassandra/test_cassandra_patch.py deleted file mode 100644 index 19a09daccf4..00000000000 --- a/tests/contrib/cassandra/test_cassandra_patch.py +++ /dev/null @@ -1,31 +0,0 @@ -# This test script was automatically generated by the contrib-patch-tests.py -# script. If you want to make changes to it, you should make sure that you have -# removed the ``_generated`` suffix from the file name, to prevent the content -# from being overwritten by future re-generations. - -from ddtrace.contrib.internal.cassandra.patch import patch -from ddtrace.contrib.internal.cassandra.session import get_version - - -try: - from ddtrace.contrib.internal.cassandra.patch import unpatch -except ImportError: - unpatch = None -from tests.contrib.patch import PatchTestCase - - -class TestCassandraPatch(PatchTestCase.Base): - __integration_name__ = "cassandra" - __module_name__ = "cassandra.cluster" - __patch_func__ = patch - __unpatch_func__ = unpatch - __get_version__ = get_version - - def assert_module_patched(self, cassandra_cluster): - pass - - def assert_not_module_patched(self, cassandra_cluster): - pass - - def assert_not_module_double_patched(self, cassandra_cluster): - pass diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 830cc5511b3..ec373de1f66 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -31,9 +31,6 @@ components: bottle: - ddtrace/contrib/bottle.py - ddtrace/contrib/internal/bottle/* - cassandra: - - ddtrace/contrib/internal/cassandra/* - - ddtrace/ext/cassandra.py celery: - ddtrace/contrib/celery.py - ddtrace/contrib/internal/celery/* @@ -459,19 +456,6 @@ suites: - tests/contrib/bottle/* runner: riot snapshot: true - cassandra: - paths: - - '@bootstrap' - - '@core' - - '@contrib' - - '@tracing' - - '@cassandra' - - tests/contrib/cassandra/* - runner: riot - snapshot: true - parallelism: 2 - services: - - cassandra celery: env: DD_DISABLE_ERROR_RESPONSES: true From b2cee418ce62caff5e056b53db0234389783d6f5 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Tue, 21 Oct 2025 10:12:11 -0700 Subject: [PATCH 02/42] update system tests --- .github/workflows/system-tests.yml | 6 +++--- .gitlab-ci.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 88446a9a863..911974c8958 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'd5152ebefb021a4273f8d189d798c5e302986f7b' + ref: '201596d32822ba4b9ba184e6a16244ee1a8b74e4' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'd5152ebefb021a4273f8d189d798c5e302986f7b' + ref: '201596d32822ba4b9ba184e6a16244ee1a8b74e4' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'd5152ebefb021a4273f8d189d798c5e302986f7b' + ref: '201596d32822ba4b9ba184e6a16244ee1a8b74e4' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cff4dba02bb..9ca2c371ff2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "d5152ebefb021a4273f8d189d798c5e302986f7b" + SYSTEM_TESTS_REF: "201596d32822ba4b9ba184e6a16244ee1a8b74e4" default: interruptible: true From 3d713e183c06f8350525f5bc31a6b4566368013d Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Wed, 22 Oct 2025 18:02:23 -0400 Subject: [PATCH 03/42] chore(mongoengine): remove integration (#14173) ## Motivation The mongoengine integration does not generate any spans and only supports attaching a Pin object to the underlying pymongo client. Since we're deprecating the Pin mechanism and pymongo already fully supports the needed functionality, maintaining the mongoengine integration is redundant. ## Description This PR removes the mongoengine integration from ddtrace. The pymongo integration, which is enabled by default, will continue to provide tracing for applications using mongoengine, as mongoengine internally uses pymongo. The only notable change is that users can no longer set a Pin on the mongoengine client. Instead, they should configure the Pin directly on the pymongo client if needed. Support for the Pin API will be removed in a future release. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [ ] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) --- .riot/requirements/106f38d.txt | 23 - .riot/requirements/1087ca6.txt | 26 -- .riot/requirements/10a00e7.txt | 24 - .riot/requirements/10b490c.txt | 23 - .riot/requirements/1424e42.txt | 22 - .riot/requirements/1468cf5.txt | 23 - .riot/requirements/14e85f3.txt | 22 - .riot/requirements/168e13d.txt | 23 - .riot/requirements/170ff7e.txt | 28 -- .riot/requirements/1c4e625.txt | 26 -- .riot/requirements/1ce93b3.txt | 22 - .riot/requirements/328b28c.txt | 24 - .riot/requirements/3dd53da.txt | 22 - .riot/requirements/40a41fd.txt | 23 - .riot/requirements/a0b94b1.txt | 23 - .riot/requirements/ad40916.txt | 22 - .riot/requirements/b089663.txt | 26 -- .riot/requirements/b344fed.txt | 22 - .riot/requirements/de53117.txt | 26 -- .riot/requirements/f9d0e8e.txt | 26 -- ddtrace/_monkey.py | 1 - .../integration_registry/registry.yaml | 10 - .../contrib/internal/mongoengine/__init__.py | 19 - ddtrace/contrib/internal/mongoengine/patch.py | 38 -- ddtrace/contrib/internal/mongoengine/trace.py | 38 -- ddtrace/settings/_config.py | 1 - docs/contributing-integrations.rst | 1 - docs/index.rst | 2 - docs/integrations.rst | 7 - ...emove-pymongo-engine-0584c2055377f718.yaml | 5 + riotfile.py | 20 - supported_versions_output.json | 7 - supported_versions_table.csv | 1 - .../integration_registry_manager.py | 2 +- tests/contrib/mongoengine/__init__.py | 0 tests/contrib/mongoengine/test.py | 415 ------------------ .../mongoengine/test_mongoengine_patch.py | 31 -- tests/contrib/suitespec.yml | 13 - 38 files changed, 6 insertions(+), 1081 deletions(-) delete mode 100644 .riot/requirements/106f38d.txt delete mode 100644 .riot/requirements/1087ca6.txt delete mode 100644 .riot/requirements/10a00e7.txt delete mode 100644 .riot/requirements/10b490c.txt delete mode 100644 .riot/requirements/1424e42.txt delete mode 100644 .riot/requirements/1468cf5.txt delete mode 100644 .riot/requirements/14e85f3.txt delete mode 100644 .riot/requirements/168e13d.txt delete mode 100644 .riot/requirements/170ff7e.txt delete mode 100644 .riot/requirements/1c4e625.txt delete mode 100644 .riot/requirements/1ce93b3.txt delete mode 100644 .riot/requirements/328b28c.txt delete mode 100644 .riot/requirements/3dd53da.txt delete mode 100644 .riot/requirements/40a41fd.txt delete mode 100644 .riot/requirements/a0b94b1.txt delete mode 100644 .riot/requirements/ad40916.txt delete mode 100644 .riot/requirements/b089663.txt delete mode 100644 .riot/requirements/b344fed.txt delete mode 100644 .riot/requirements/de53117.txt delete mode 100644 .riot/requirements/f9d0e8e.txt delete mode 100644 ddtrace/contrib/internal/mongoengine/__init__.py delete mode 100644 ddtrace/contrib/internal/mongoengine/patch.py delete mode 100644 ddtrace/contrib/internal/mongoengine/trace.py create mode 100644 releasenotes/notes/remove-pymongo-engine-0584c2055377f718.yaml delete mode 100644 tests/contrib/mongoengine/__init__.py delete mode 100644 tests/contrib/mongoengine/test.py delete mode 100644 tests/contrib/mongoengine/test_mongoengine_patch.py diff --git a/.riot/requirements/106f38d.txt b/.riot/requirements/106f38d.txt deleted file mode 100644 index 35ad753ef8f..00000000000 --- a/.riot/requirements/106f38d.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.14 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/106f38d.in -# -attrs==25.3.0 -coverage[toml]==7.10.5 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.2 -pymongo==4.8.0 -pytest==8.4.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1087ca6.txt b/.riot/requirements/1087ca6.txt deleted file mode 100644 index 875cc5be3a4..00000000000 --- a/.riot/requirements/1087ca6.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1087ca6.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -dnspython==2.7.0 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pymongo==4.8.0 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.14.0 diff --git a/.riot/requirements/10a00e7.txt b/.riot/requirements/10a00e7.txt deleted file mode 100644 index ed2fd846015..00000000000 --- a/.riot/requirements/10a00e7.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/10a00e7.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/10b490c.txt b/.riot/requirements/10b490c.txt deleted file mode 100644 index 4126321ff11..00000000000 --- a/.riot/requirements/10b490c.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/10b490c.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pymongo==4.8.0 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1424e42.txt b/.riot/requirements/1424e42.txt deleted file mode 100644 index f58bbb22bd6..00000000000 --- a/.riot/requirements/1424e42.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1424e42.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1468cf5.txt b/.riot/requirements/1468cf5.txt deleted file mode 100644 index 6b90ac2ac97..00000000000 --- a/.riot/requirements/1468cf5.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.14 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1468cf5.in -# -attrs==25.3.0 -coverage[toml]==7.10.5 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.2 -pymongo==4.8.0 -pytest==8.4.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/14e85f3.txt b/.riot/requirements/14e85f3.txt deleted file mode 100644 index 44ce4a54256..00000000000 --- a/.riot/requirements/14e85f3.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/14e85f3.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/168e13d.txt b/.riot/requirements/168e13d.txt deleted file mode 100644 index 5161e01c8a3..00000000000 --- a/.riot/requirements/168e13d.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.14 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/168e13d.in -# -attrs==25.3.0 -coverage[toml]==7.10.5 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.2 -pymongo==4.8.0 -pytest==8.4.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/170ff7e.txt b/.riot/requirements/170ff7e.txt deleted file mode 100644 index 64fffcfc4f2..00000000000 --- a/.riot/requirements/170ff7e.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/170ff7e.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -dnspython==2.7.0 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.7.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pymongo==4.8.0 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.14.0 -zipp==3.23.0 diff --git a/.riot/requirements/1c4e625.txt b/.riot/requirements/1c4e625.txt deleted file mode 100644 index 4ee880ebb56..00000000000 --- a/.riot/requirements/1c4e625.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1c4e625.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.23.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==3.13.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1ce93b3.txt b/.riot/requirements/1ce93b3.txt deleted file mode 100644 index a0edba9ffd0..00000000000 --- a/.riot/requirements/1ce93b3.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ce93b3.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/328b28c.txt b/.riot/requirements/328b28c.txt deleted file mode 100644 index 38eac9651b9..00000000000 --- a/.riot/requirements/328b28c.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/328b28c.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/3dd53da.txt b/.riot/requirements/3dd53da.txt deleted file mode 100644 index 088ac0ddd7e..00000000000 --- a/.riot/requirements/3dd53da.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3dd53da.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/40a41fd.txt b/.riot/requirements/40a41fd.txt deleted file mode 100644 index 9f9034b3892..00000000000 --- a/.riot/requirements/40a41fd.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/40a41fd.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pymongo==4.8.0 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/a0b94b1.txt b/.riot/requirements/a0b94b1.txt deleted file mode 100644 index 71d76a09e22..00000000000 --- a/.riot/requirements/a0b94b1.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/a0b94b1.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -dnspython==2.7.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.24.2 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pymongo==4.8.0 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/ad40916.txt b/.riot/requirements/ad40916.txt deleted file mode 100644 index 853f497ee9e..00000000000 --- a/.riot/requirements/ad40916.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/ad40916.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/b089663.txt b/.riot/requirements/b089663.txt deleted file mode 100644 index 956c6d73e92..00000000000 --- a/.riot/requirements/b089663.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/b089663.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/b344fed.txt b/.riot/requirements/b344fed.txt deleted file mode 100644 index 73e61eb69f9..00000000000 --- a/.riot/requirements/b344fed.txt +++ /dev/null @@ -1,22 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/b344fed.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/de53117.txt b/.riot/requirements/de53117.txt deleted file mode 100644 index 1dd3dcf18f2..00000000000 --- a/.riot/requirements/de53117.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/de53117.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/f9d0e8e.txt b/.riot/requirements/f9d0e8e.txt deleted file mode 100644 index 42bc8937d56..00000000000 --- a/.riot/requirements/f9d0e8e.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/f9d0e8e.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymongo==4.8.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.2 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 5cef2d0f5cf..170c1870871 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -57,7 +57,6 @@ "kafka": True, "langgraph": True, "litellm": True, - "mongoengine": True, "mysql": True, "mysqldb": True, "pymysql": True, diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index 1a877c8e784..2adf38cf686 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -599,16 +599,6 @@ integrations: min: 1.0.2 max: 1.0.2 -- integration_name: mongoengine - is_external_package: true - is_tested: true - dependency_names: - - mongoengine - tested_versions_by_dependency: - mongoengine: - min: 0.23.1 - max: 0.29.1 - - integration_name: mysql is_external_package: true is_tested: true diff --git a/ddtrace/contrib/internal/mongoengine/__init__.py b/ddtrace/contrib/internal/mongoengine/__init__.py deleted file mode 100644 index a72c861f4b7..00000000000 --- a/ddtrace/contrib/internal/mongoengine/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Instrument mongoengine to report MongoDB queries. - -``import ddtrace.auto`` will automatically patch your mongoengine connect method to make it work. -:: - - from ddtrace import patch - from ddtrace.trace import Pin - import mongoengine - - # If not patched yet, you can patch mongoengine specifically - patch(mongoengine=True) - - # At that point, mongoengine is instrumented with the default settings - mongoengine.connect('db', alias='default') - - # Use a pin to specify metadata related to this client - client = mongoengine.connect('db', alias='master') - Pin.override(client, service="mongo-master") -""" diff --git a/ddtrace/contrib/internal/mongoengine/patch.py b/ddtrace/contrib/internal/mongoengine/patch.py deleted file mode 100644 index 550d1e83199..00000000000 --- a/ddtrace/contrib/internal/mongoengine/patch.py +++ /dev/null @@ -1,38 +0,0 @@ -# TODO(mabdinur): Remove the pymongoengine integration, this integration does nothing special -# it just uses the pymongo integration and creates unnecessary pin objects -from typing import Dict - -import mongoengine - -from ..pymongo.patch import patch as patch_pymongo_module -from ..pymongo.patch import unpatch as unpatch_pymongo_module -from .trace import WrappedConnect - - -# Original connect function -_connect = mongoengine.connect - - -def get_version(): - # type: () -> str - return getattr(mongoengine, "__version__", "") - - -def _supported_versions() -> Dict[str, str]: - return {"mongoengine": ">=0.23"} - - -def patch(): - if getattr(mongoengine, "_datadog_patch", False): - return - mongoengine.connect = WrappedConnect(_connect) - mongoengine._datadog_patch = True - patch_pymongo_module() - - -def unpatch(): - if not getattr(mongoengine, "_datadog_patch", False): - return - mongoengine.connect = _connect - mongoengine._datadog_patch = False - unpatch_pymongo_module() diff --git a/ddtrace/contrib/internal/mongoengine/trace.py b/ddtrace/contrib/internal/mongoengine/trace.py deleted file mode 100644 index e3deee0e4a4..00000000000 --- a/ddtrace/contrib/internal/mongoengine/trace.py +++ /dev/null @@ -1,38 +0,0 @@ -# 3p -# project -import wrapt - -from ddtrace._trace.pin import Pin - -# keep the TracedMongoClient import to avoid breaking the public api -from ddtrace.contrib.internal.pymongo.client import TracedMongoClient # noqa: F401 -from ddtrace.ext import mongo as mongox -from ddtrace.internal.schema import schematize_service_name - - -# TODO(Benjamin): we should instrument register_connection instead, because more generic -# We should also extract the "alias" attribute and set it as a meta -_SERVICE = schematize_service_name(mongox.SERVICE) - - -# TODO(mabdinur): Remove this class when ``ddtrace.contrib.mongoengine.trace`` is removed -class WrappedConnect(wrapt.ObjectProxy): - """WrappedConnect wraps mongoengines 'connect' function to ensure - that all returned connections are wrapped for tracing. - """ - - def __init__(self, connect): - super(WrappedConnect, self).__init__(connect) - Pin(_SERVICE).onto(self) - - def __call__(self, *args, **kwargs): - client = self.__wrapped__(*args, **kwargs) - pin = Pin.get_from(self) - if pin: - tracer = pin.tracer - pp = Pin(service=pin.service) - if tracer is not None: - pp._tracer = tracer - pp.onto(client) - - return client diff --git a/ddtrace/settings/_config.py b/ddtrace/settings/_config.py index 9a14793c080..41175d7a56f 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/settings/_config.py @@ -158,7 +158,6 @@ "aiopg", "dogpile_cache", "pylibmc", - "mongoengine", "httpx", "httplib", "rq", diff --git a/docs/contributing-integrations.rst b/docs/contributing-integrations.rst index 8db0e205a2f..0ec2289013d 100644 --- a/docs/contributing-integrations.rst +++ b/docs/contributing-integrations.rst @@ -204,7 +204,6 @@ are not yet any expected spans stored for it, so we need to create some. mongo: - ddtrace/contrib/internal/pymongo/* - - ddtrace/contrib/internal/mongoengine/* - ddtrace/ext/mongo.py 15. Add a `suite` for your integration in `tests/contrib/suitespec.yml`. This defines test configuration diff --git a/docs/index.rst b/docs/index.rst index 8275e9998b2..6b3d6ffa67c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -152,8 +152,6 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`molten` | >= 1.0 | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`mongoengine` | >= 0.23 | Yes | | -+--------------------------------------------------+------------+----------+------+ | :ref:`mysql-connector` | >= 8.0.5 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`mysqldb` | \* | Yes | | diff --git a/docs/integrations.rst b/docs/integrations.rst index ba36c26912d..b01829193c3 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -368,13 +368,6 @@ Molten .. automodule:: ddtrace.contrib.internal.molten -.. _mongoengine: - -Mongoengine -^^^^^^^^^^^ -.. automodule:: ddtrace.contrib.internal.mongoengine - - .. _mysql-connector: mysql-connector diff --git a/releasenotes/notes/remove-pymongo-engine-0584c2055377f718.yaml b/releasenotes/notes/remove-pymongo-engine-0584c2055377f718.yaml new file mode 100644 index 00000000000..772aade8185 --- /dev/null +++ b/releasenotes/notes/remove-pymongo-engine-0584c2055377f718.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + mongoengine: Drops support for the ``ddtrace.Pin`` object with mongoengine. With this change, the ddtrace library no longer directly supports mongoengine. + Mongoengine will be supported through the ``pymongo`` integration. diff --git a/riotfile.py b/riotfile.py index 224a59ca7b4..6cbee9ddbc1 100644 --- a/riotfile.py +++ b/riotfile.py @@ -1632,26 +1632,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), - Venv( - name="mongoengine", - command="pytest {cmdargs} tests/contrib/mongoengine", - pkgs={ - # pymongo v4.9.0 introduced breaking changes that are not yet supported by mongoengine - "pymongo": "<4.9.0", - "pytest-randomly": latest, - }, - venvs=[ - Venv( - pys="3.8", - pkgs={"mongoengine": ["~=0.23.0", latest]}, - ), - Venv( - # mongoengine added support for Python 3.9/3.10 in 0.24 - pys=select_pys(min_version="3.9"), - pkgs={"mongoengine": ["~=0.24.0", "~=0.24", latest]}, - ), - ], - ), Venv( name="asgi", pkgs={ diff --git a/supported_versions_output.json b/supported_versions_output.json index ba6dc49f9cb..54d2847881a 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -457,13 +457,6 @@ "max_tracer_supported": "1.0.2", "auto-instrumented": true }, - { - "dependency": "mongoengine", - "integration": "mongoengine", - "minimum_tracer_supported": "0.23.1", - "max_tracer_supported": "0.29.1", - "auto-instrumented": true - }, { "dependency": "mysql-connector-python", "integration": "mysql", diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 25309eedf48..1da57e9f537 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -63,7 +63,6 @@ mako,mako,1.0.14,1.3.10,True mariadb,mariadb,1.0.11,1.1.13,True mcp,mcp,1.10.1,1.16.0,True molten,molten,1.0.2,1.0.2,True -mongoengine,mongoengine,0.23.1,0.29.1,True mysql-connector-python,mysql,8.0.5,9.4.0,True mysqlclient,mysqldb,2.2.1,2.2.6,True openai,openai *,1.0.0,1.109.1,True diff --git a/tests/contrib/integration_registry/registry_update_helpers/integration_registry_manager.py b/tests/contrib/integration_registry/registry_update_helpers/integration_registry_manager.py index f5990c6c6c2..4e6659b6321 100644 --- a/tests/contrib/integration_registry/registry_update_helpers/integration_registry_manager.py +++ b/tests/contrib/integration_registry/registry_update_helpers/integration_registry_manager.py @@ -39,7 +39,7 @@ def _is_valid_patch_call(self, tb_string): """Checks if the patch call originated from ddtrace.contrib.internal/*/patch.py.""" # reverse the lines to check the most recent patch call first since some integrations call # other integrations patches: - # e.g. mongoengine calls pymongo's patch + # e.g. django calls postgres's patch return any( "ddtrace/contrib/internal" in line and "/patch.py" in line for line in reversed(tb_string.splitlines()) ) diff --git a/tests/contrib/mongoengine/__init__.py b/tests/contrib/mongoengine/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/contrib/mongoengine/test.py b/tests/contrib/mongoengine/test.py deleted file mode 100644 index d47c5a397e6..00000000000 --- a/tests/contrib/mongoengine/test.py +++ /dev/null @@ -1,415 +0,0 @@ -import time - -import mongoengine -import pymongo - -from ddtrace._trace.pin import Pin -from ddtrace.contrib.internal.mongoengine.patch import patch -from ddtrace.contrib.internal.mongoengine.patch import unpatch -from ddtrace.ext import mongo as mongox -from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer -from tests.utils import DummyTracer -from tests.utils import TracerTestCase -from tests.utils import assert_is_measured - -from ..config import MONGO_CONFIG - - -class Artist(mongoengine.Document): - first_name = mongoengine.StringField(max_length=50) - last_name = mongoengine.StringField(max_length=50) - - -class MongoEngineCore(object): - # Define the service at the class level, so that each test suite can use a different service - # and therefore catch any sneaky badly-unpatched stuff. - TEST_SERVICE = "deadbeef" - - def get_tracer_and_connect(self): - # implement me - pass - - def test_insert_update_delete_query(self): - tracer = self.get_tracer_and_connect() - - start = time.time() - Artist.drop_collection() - end = time.time() - - # ensure we get a drop collection span - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - - assert_is_measured(span) - assert span.resource == "drop artist" - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - _assert_timing(span, start, end) - - start = end - joni = Artist() - joni.first_name = "Joni" - joni.last_name = "Mitchell" - joni.save() - end = time.time() - - # ensure we get an insert span - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - assert_is_measured(span) - assert span.resource == "insert artist" - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - _assert_timing(span, start, end) - - # ensure full scans work - start = time.time() - artists = [a for a in Artist.objects] - end = time.time() - assert len(artists) == 1 - assert artists[0].first_name == "Joni" - assert artists[0].last_name == "Mitchell" - - # query names should be used in pymongo>3.1 - name = "find" if pymongo.version_tuple >= (3, 1, 0) else "query" - - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - assert_is_measured(span) - assert span.resource == "{} artist".format(name) - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - _assert_timing(span, start, end) - - # ensure filtered queries work - start = time.time() - artists = [a for a in Artist.objects(first_name="Joni")] - end = time.time() - assert len(artists) == 1 - joni = artists[0] - assert artists[0].first_name == "Joni" - assert artists[0].last_name == "Mitchell" - - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - assert_is_measured(span) - assert span.resource == '{} artist {{"first_name": "?"}}'.format(name) - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - _assert_timing(span, start, end) - - # ensure updates work - start = time.time() - joni.last_name = "From Saskatoon" - joni.save() - end = time.time() - - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - assert_is_measured(span) - assert span.resource == 'update artist {"_id": "?"}' - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - _assert_timing(span, start, end) - - # ensure deletes - start = time.time() - joni.delete() - end = time.time() - - spans = tracer.pop() - assert len(spans) == 2 - span = spans[1] - assert span.name == "pymongo.cmd" - assert_is_measured(span) - assert span.resource == 'delete artist {"_id": "?"}' - assert span.span_type == "mongodb" - assert span.service == self.TEST_SERVICE - assert span.get_tag("component") == "pymongo" - assert span.get_tag("span.kind") == "client" - assert span.get_tag("db.system") == "mongodb" - _assert_timing(span, start, end) - - def test_opentracing(self): - """Ensure the opentracer works with mongoengine.""" - tracer = self.get_tracer_and_connect() - ot_tracer = init_tracer("my_svc", tracer) - - with ot_tracer.start_active_span("ot_span"): - start = time.time() - Artist.drop_collection() - end = time.time() - - # ensure we get a drop collection span - spans = tracer.pop() - assert len(spans) == 3 - ot_span, dd_server_span, dd_cmd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_server_span.parent_id == ot_span.span_id - - assert ot_span.name == "ot_span" - assert ot_span.service == "my_svc" - - assert_is_measured(dd_cmd_span) - assert dd_cmd_span.resource == "drop artist" - assert dd_cmd_span.span_type == "mongodb" - assert dd_cmd_span.service == self.TEST_SERVICE - _assert_timing(dd_cmd_span, start, end) - - -class TestMongoEnginePatchConnectDefault(TracerTestCase, MongoEngineCore): - """Test suite with a global Pin for the connect function with the default configuration""" - - TEST_SERVICE = mongox.SERVICE - - def setUp(self): - patch() - - def tearDown(self): - unpatch() - # Disconnect and remove the client - mongoengine.connection.disconnect() - - def get_tracer_and_connect(self): - tracer = DummyTracer() - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - Pin.get_from(client)._clone(tracer=tracer).onto(client) - return tracer - - -class TestMongoEnginePatchConnectSchematization(TestMongoEnginePatchConnectDefault): - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) - def test_user_specified_service_default(self): - """ - : When a user specifies a service for the app - The mongoengine integration should not use it. - """ - from ddtrace import config - - assert config.service == "mysvc" - - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[1].name == "pymongo.cmd" - assert spans[1].service != "mysvc" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0", DD_SERVICE="mysvc")) - def test_user_specified_service_v0(self): - """ - v0: When a user specifies a service for the app - The mongoengine integration should not use it. - """ - from ddtrace import config - - assert config.service == "mysvc" - - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[1].name == "pymongo.cmd" - assert spans[1].service != "mysvc" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1", DD_SERVICE="mysvc")) - def test_user_specified_service_v1(self): - """ - In v1 of the span attribute schema, when a user specifies a service for the app - The mongoengine integration should use it as the default. - """ - from ddtrace import config - - assert config.service == "mysvc" - - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[1].name == "mongodb.query" - assert spans[1].service == "mysvc" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) - def test_unspecified_service_v0(self): - """ - In v0 of the span attribute schema, when there is no specified DD_SERVICE - The mongoengine integration should use None as the default. - """ - from ddtrace import config - - assert config.service is DEFAULT_SPAN_SERVICE_NAME - - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[0].service == "mongodb" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) - def test_unspecified_service_v1(self): - """ - In v1 of the span attribute schema, when there is no specified DD_SERVICE - The mongoengine integration should use DEFAULT_SPAN_SERVICE_NAME as the default. - """ - from ddtrace import config - - assert config.service == DEFAULT_SPAN_SERVICE_NAME - - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[0].service == DEFAULT_SPAN_SERVICE_NAME - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) - def test_span_name_v0_schema(self): - """ - When a user specifies a service for the app - The mongoengine integration should not use it. - """ - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[0].name == "pymongo.checkout" or spans[0].name == "pymongo.get_socket" - assert spans[1].name == "pymongo.cmd" - - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) - def test_span_name_v1_schema(self): - """ - When a user specifies a service for the app - The mongoengine integration should not use it. - """ - tracer = self.get_tracer_and_connect() - Artist.drop_collection() - - spans = tracer.pop() - assert len(spans) == 2 - assert spans[0].name == "pymongo.checkout" or spans[0].name == "pymongo.get_socket" - assert spans[1].name == "mongodb.query" - - -class TestMongoEnginePatchConnect(TestMongoEnginePatchConnectDefault): - """Test suite with a global Pin for the connect function with custom service""" - - TEST_SERVICE = "test-mongo-patch-connect" - - def get_tracer_and_connect(self): - tracer = TestMongoEnginePatchConnectDefault.get_tracer_and_connect(self) - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = tracer - pin.onto(mongoengine.connect) - mongoengine.connect(port=MONGO_CONFIG["port"]) - - return tracer - - -class TestMongoEnginePatchClientDefault(TracerTestCase, MongoEngineCore): - """Test suite with a Pin local to a specific client with default configuration""" - - TEST_SERVICE = mongox.SERVICE - - def setUp(self): - patch() - - def tearDown(self): - unpatch() - # Disconnect and remove the client - mongoengine.connection.disconnect() - - def get_tracer_and_connect(self): - tracer = DummyTracer() - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - Pin.get_from(client)._clone(tracer=tracer).onto(client) - - return tracer - - -class TestMongoEnginePatchClient(TestMongoEnginePatchClientDefault): - """Test suite with a Pin local to a specific client with custom service""" - - TEST_SERVICE = "test-mongo-patch-client" - - def get_tracer_and_connect(self): - tracer = DummyTracer() - # Set a connect-level service, to check that we properly override it - Pin(service="not-%s" % self.TEST_SERVICE).onto(mongoengine.connect) - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = tracer - pin.onto(client) - - return tracer - - def test_patch_unpatch(self): - tracer = DummyTracer() - - # Test patch idempotence - patch() - patch() - - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - Pin.get_from(client)._clone(tracer=tracer).onto(client) - - Artist.drop_collection() - spans = tracer.pop() - assert spans, spans - assert len(spans) == 2 - - mongoengine.connection.disconnect() - tracer.pop() - - # Test unpatch - unpatch() - - mongoengine.connect(port=MONGO_CONFIG["port"]) - - Artist.drop_collection() - spans = tracer.pop() - assert not spans, spans - - # Disconnect so a new pymongo client can be created, - # connections are patched on instantiation - mongoengine.connection.disconnect() - # Test patch again - patch() - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - Pin.get_from(client)._clone(tracer=tracer).onto(client) - - Artist.drop_collection() - spans = tracer.pop() - assert spans, spans - assert len(spans) == 2 - - def test_multiple_connect_no_double_patching(self): - """Ensure we do not double patch client._topology - - Regression test for https://github.com/DataDog/dd-trace-py/issues/2474 - """ - client = mongoengine.connect(port=MONGO_CONFIG["port"]) - assert Pin.get_from(client) is Pin.get_from(client._topology) - client.close() - - -def _assert_timing(span, start, end): - assert start < span.start < end - assert span.duration < end - start diff --git a/tests/contrib/mongoengine/test_mongoengine_patch.py b/tests/contrib/mongoengine/test_mongoengine_patch.py deleted file mode 100644 index 6f219d1566e..00000000000 --- a/tests/contrib/mongoengine/test_mongoengine_patch.py +++ /dev/null @@ -1,31 +0,0 @@ -# This test script was automatically generated by the contrib-patch-tests.py -# script. If you want to make changes to it, you should make sure that you have -# removed the ``_generated`` suffix from the file name, to prevent the content -# from being overwritten by future re-generations. - -from ddtrace.contrib.internal.mongoengine.patch import get_version -from ddtrace.contrib.internal.mongoengine.patch import patch - - -try: - from ddtrace.contrib.internal.mongoengine.patch import unpatch -except ImportError: - unpatch = None -from tests.contrib.patch import PatchTestCase - - -class TestMongoenginePatch(PatchTestCase.Base): - __integration_name__ = "mongoengine" - __module_name__ = "mongoengine" - __patch_func__ = patch - __unpatch_func__ = unpatch - __get_version__ = get_version - - def assert_module_patched(self, mongoengine): - pass - - def assert_not_module_patched(self, mongoengine): - pass - - def assert_not_module_double_patched(self, mongoengine): - pass diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index ec373de1f66..d3ac8289e95 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -126,7 +126,6 @@ components: - ddtrace/contrib/internal/molten/* mongo: - ddtrace/contrib/internal/pymongo/* - - ddtrace/contrib/internal/mongoengine/* - ddtrace/ext/mongo.py mysql: - ddtrace/contrib/internal/mysql/* @@ -882,18 +881,6 @@ suites: - tests/contrib/molten/* runner: riot snapshot: true - mongoengine: - paths: - - '@bootstrap' - - '@core' - - '@contrib' - - '@tracing' - - '@mongo' - - tests/contrib/mongoengine/* - runner: riot - snapshot: true - services: - - mongo mysqlpython: paths: - '@bootstrap' From 1e1dda5ecdd1ea040162615eec67da793e9f93ce Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:10:14 -0700 Subject: [PATCH 04/42] chore: remove opentracer package (#14892) This change removes the deprecated `opentracer` package from ddtrace. Note the base branch, a staging area for breaking changes slated for 4.0. --------- Co-authored-by: brettlangdon --- ddtrace/opentracer/__init__.py | 18 - ddtrace/opentracer/helpers.py | 25 - ddtrace/opentracer/propagation/__init__.py | 6 - ddtrace/opentracer/propagation/binary.py | 0 ddtrace/opentracer/propagation/http.py | 74 --- ddtrace/opentracer/propagation/propagator.py | 13 - ddtrace/opentracer/propagation/text.py | 0 ddtrace/opentracer/settings.py | 41 -- ddtrace/opentracer/span.py | 197 ------ ddtrace/opentracer/span_context.py | 66 -- ddtrace/opentracer/tags.py | 23 - ddtrace/opentracer/tracer.py | 399 ------------ ddtrace/opentracer/utils.py | 43 -- .../opentracer-remove-b1883d26ea035c50.yaml | 4 + tests/commands/ddtrace_run_app_name.py | 6 - tests/commands/test_runner.py | 7 - tests/contrib/aiobotocore/test.py | 86 --- tests/contrib/aiohttp/test_middleware.py | 18 - tests/contrib/aiopg/test.py | 24 - tests/contrib/aredis/test_aredis.py | 14 - tests/contrib/asyncio/test_propagation.py | 57 -- tests/contrib/boto/test.py | 54 -- tests/contrib/botocore/test.py | 38 -- tests/contrib/bottle/test.py | 39 -- tests/contrib/celery/test_integration.py | 50 -- tests/contrib/django/test_django.py | 33 - tests/contrib/falcon/test_suite.py | 32 - tests/contrib/flask_cache/test.py | 39 -- tests/contrib/futures/test_propagation.py | 28 - tests/contrib/gevent/test_tracer.py | 30 - tests/contrib/httplib/test_httplib.py | 32 - tests/contrib/mysql/test_mysql.py | 88 --- tests/contrib/mysqldb/test_mysqldb.py | 84 --- tests/contrib/psycopg/test_psycopg.py | 42 -- tests/contrib/psycopg/test_psycopg_async.py | 42 -- tests/contrib/psycopg2/test_psycopg.py | 42 -- tests/contrib/pylibmc/test.py | 28 - tests/contrib/pymongo/test.py | 62 -- tests/contrib/pymysql/test_pymysql.py | 68 -- tests/contrib/pyramid/utils.py | 31 - tests/contrib/redis/test_redis.py | 49 -- tests/contrib/requests/test_requests.py | 30 - tests/contrib/snowflake/test_snowflake.py | 75 --- tests/contrib/sqlalchemy/mixins.py | 34 - tests/contrib/sqlite3/test_sqlite3.py | 42 -- tests/contrib/suitespec.yml | 9 - tests/contrib/tornado/test_tornado_web.py | 44 -- tests/contrib/urllib3/test_urllib3.py | 29 - tests/contrib/valkey/test_valkey.py | 44 -- tests/contrib/vertica/test_vertica.py | 32 - tests/contrib/yaaredis/test_yaaredis.py | 13 - tests/opentracer/__init__.py | 0 tests/opentracer/conftest.py | 60 -- tests/opentracer/core/__init__.py | 0 .../opentracer/core/test_dd_compatibility.py | 180 ------ tests/opentracer/core/test_span.py | 163 ----- tests/opentracer/core/test_span_context.py | 38 -- tests/opentracer/core/test_tracer.py | 585 ------------------ tests/opentracer/core/test_utils.py | 17 - tests/opentracer/test_tracer_asyncio.py | 143 ----- tests/opentracer/test_tracer_gevent.py | 193 ------ tests/opentracer/test_tracer_tornado.py | 30 - tests/opentracer/utils.py | 11 - tests/suitespec.yml | 2 - tests/telemetry/test_telemetry_metrics_e2e.py | 66 -- tests/tracer/test_correlation_log_context.py | 21 - tests/tracer/test_propagation.py | 16 - 67 files changed, 4 insertions(+), 3905 deletions(-) delete mode 100644 ddtrace/opentracer/__init__.py delete mode 100644 ddtrace/opentracer/helpers.py delete mode 100644 ddtrace/opentracer/propagation/__init__.py delete mode 100644 ddtrace/opentracer/propagation/binary.py delete mode 100644 ddtrace/opentracer/propagation/http.py delete mode 100644 ddtrace/opentracer/propagation/propagator.py delete mode 100644 ddtrace/opentracer/propagation/text.py delete mode 100644 ddtrace/opentracer/settings.py delete mode 100644 ddtrace/opentracer/span.py delete mode 100644 ddtrace/opentracer/span_context.py delete mode 100644 ddtrace/opentracer/tags.py delete mode 100644 ddtrace/opentracer/tracer.py delete mode 100644 ddtrace/opentracer/utils.py create mode 100644 releasenotes/notes/opentracer-remove-b1883d26ea035c50.yaml delete mode 100644 tests/commands/ddtrace_run_app_name.py delete mode 100644 tests/opentracer/__init__.py delete mode 100644 tests/opentracer/conftest.py delete mode 100644 tests/opentracer/core/__init__.py delete mode 100644 tests/opentracer/core/test_dd_compatibility.py delete mode 100644 tests/opentracer/core/test_span.py delete mode 100644 tests/opentracer/core/test_span_context.py delete mode 100644 tests/opentracer/core/test_tracer.py delete mode 100644 tests/opentracer/core/test_utils.py delete mode 100644 tests/opentracer/test_tracer_asyncio.py delete mode 100644 tests/opentracer/test_tracer_gevent.py delete mode 100644 tests/opentracer/test_tracer_tornado.py delete mode 100644 tests/opentracer/utils.py diff --git a/ddtrace/opentracer/__init__.py b/ddtrace/opentracer/__init__.py deleted file mode 100644 index 815cdae0022..00000000000 --- a/ddtrace/opentracer/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from ddtrace.vendor.debtcollector import deprecate - -from .helpers import set_global_tracer -from .tracer import Tracer - - -deprecate( - "The `ddtrace.opentracer` package is deprecated", - message="The ddtrace library no longer supports the OpenTracing API. " - "Use the OpenTelemetry API instead (`ddtrace.opentelemetry`).", - removal_version="4.0.0", -) - - -__all__ = [ - "Tracer", - "set_global_tracer", -] diff --git a/ddtrace/opentracer/helpers.py b/ddtrace/opentracer/helpers.py deleted file mode 100644 index e8e6c4896a4..00000000000 --- a/ddtrace/opentracer/helpers.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import TYPE_CHECKING - -import opentracing - -import ddtrace - - -if TYPE_CHECKING: # pragma: no cover - from ddtrace.opentracer import Tracer # noqa:F401 - - -""" -Helper routines for Datadog OpenTracing. -""" - - -def set_global_tracer(tracer): - # type: (Tracer) -> None - """Sets the global tracers to the given tracer.""" - - # overwrite the opentracer reference - opentracing.tracer = tracer - - # overwrite the Datadog tracer reference - ddtrace.tracer = tracer._dd_tracer diff --git a/ddtrace/opentracer/propagation/__init__.py b/ddtrace/opentracer/propagation/__init__.py deleted file mode 100644 index 04ddde7014d..00000000000 --- a/ddtrace/opentracer/propagation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from .http import HTTPPropagator - - -__all__ = [ - "HTTPPropagator", -] diff --git a/ddtrace/opentracer/propagation/binary.py b/ddtrace/opentracer/propagation/binary.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/ddtrace/opentracer/propagation/http.py b/ddtrace/opentracer/propagation/http.py deleted file mode 100644 index 539f8dc2ebd..00000000000 --- a/ddtrace/opentracer/propagation/http.py +++ /dev/null @@ -1,74 +0,0 @@ -from typing import Dict # noqa:F401 - -from opentracing import InvalidCarrierException - -from ddtrace.propagation.http import HTTPPropagator as DDHTTPPropagator - -from ...internal.logger import get_logger -from ..span_context import SpanContext -from .propagator import Propagator - - -log = get_logger(__name__) - -_HTTP_BAGGAGE_PREFIX = "ot-baggage-" -_HTTP_BAGGAGE_PREFIX_LEN = len(_HTTP_BAGGAGE_PREFIX) - - -class HTTPPropagator(Propagator): - """OpenTracing compatible HTTP_HEADER and TEXT_MAP format propagator. - - `HTTPPropagator` provides compatibility by using existing OpenTracing - compatible methods from the ddtracer along with new logic supporting the - outstanding OpenTracing-defined functionality. - """ - - @staticmethod - def inject(span_context, carrier): - # type: (SpanContext, Dict[str, str]) -> None - """Inject a span context into a carrier. - - *span_context* is injected into the carrier by first using an - :class:`ddtrace.propagation.http.HTTPPropagator` to inject the ddtracer - specific fields. - - Then the baggage is injected into *carrier*. - - :param span_context: span context to inject. - - :param carrier: carrier to inject into. - """ - if not isinstance(carrier, dict): - raise InvalidCarrierException("propagator expects carrier to be a dict") - - DDHTTPPropagator.inject(span_context._dd_context, carrier) - - # Add the baggage - if span_context.baggage is not None: - for key in span_context.baggage: - carrier[_HTTP_BAGGAGE_PREFIX + key] = span_context.baggage[key] - - @staticmethod - def extract(carrier): - # type: (Dict[str, str]) -> SpanContext - """Extract a span context from a carrier. - - :class:`ddtrace.propagation.http.HTTPPropagator` is used to extract - ddtracer supported fields into a `ddtrace.Context` context which is - combined with new logic to extract the baggage which is returned in an - OpenTracing compatible span context. - - :param carrier: carrier to extract from. - - :return: extracted span context. - """ - if not isinstance(carrier, dict): - raise InvalidCarrierException("propagator expects carrier to be a dict") - - ddspan_ctx = DDHTTPPropagator.extract(carrier) - baggage = {} - for key in carrier: - if key.startswith(_HTTP_BAGGAGE_PREFIX): - baggage[key[_HTTP_BAGGAGE_PREFIX_LEN:]] = carrier[key] - - return SpanContext(ddcontext=ddspan_ctx, baggage=baggage) diff --git a/ddtrace/opentracer/propagation/propagator.py b/ddtrace/opentracer/propagation/propagator.py deleted file mode 100644 index 77eadf3912b..00000000000 --- a/ddtrace/opentracer/propagation/propagator.py +++ /dev/null @@ -1,13 +0,0 @@ -import abc - - -class Propagator(metaclass=abc.ABCMeta): - @staticmethod - @abc.abstractmethod - def inject(span_context, carrier): - pass - - @staticmethod - @abc.abstractmethod - def extract(carrier): - pass diff --git a/ddtrace/opentracer/propagation/text.py b/ddtrace/opentracer/propagation/text.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/ddtrace/opentracer/settings.py b/ddtrace/opentracer/settings.py deleted file mode 100644 index 944df88233b..00000000000 --- a/ddtrace/opentracer/settings.py +++ /dev/null @@ -1,41 +0,0 @@ -from collections import namedtuple -from typing import Any # noqa:F401 -from typing import Dict # noqa:F401 -from typing import List # noqa:F401 - - -# Keys used for the configuration dict -ConfigKeyNames = namedtuple( - "ConfigKeyNames", - [ - "AGENT_HOSTNAME", - "AGENT_HTTPS", - "AGENT_PORT", - "DEBUG", - "ENABLED", - "GLOBAL_TAGS", - "SAMPLER", - "PRIORITY_SAMPLING", - "UDS_PATH", - "SETTINGS", - ], -) - -ConfigKeys = ConfigKeyNames( - AGENT_HOSTNAME="agent_hostname", - AGENT_HTTPS="agent_https", - AGENT_PORT="agent_port", - DEBUG="debug", - ENABLED="enabled", - GLOBAL_TAGS="global_tags", - SAMPLER="sampler", - PRIORITY_SAMPLING="priority_sampling", - UDS_PATH="uds_path", - SETTINGS="settings", -) - - -def config_invalid_keys(config): - # type: (Dict[str, Any]) -> List[str] - """Returns a list of keys that exist in *config* and not in KEYS.""" - return [key for key in config.keys() if key not in ConfigKeys] diff --git a/ddtrace/opentracer/span.py b/ddtrace/opentracer/span.py deleted file mode 100644 index 3aea2eda580..00000000000 --- a/ddtrace/opentracer/span.py +++ /dev/null @@ -1,197 +0,0 @@ -import threading -from typing import TYPE_CHECKING # noqa:F401 -from typing import Any # noqa:F401 -from typing import Dict # noqa:F401 -from typing import Optional # noqa:F401 -from typing import Text # noqa:F401 -from typing import Union # noqa:F401 - -from opentracing import Span as OpenTracingSpan -from opentracing.ext import tags as OTTags - -from ddtrace.constants import ERROR_MSG -from ddtrace.constants import ERROR_STACK -from ddtrace.constants import ERROR_TYPE -from ddtrace.internal.compat import NumericType # noqa:F401 -from ddtrace.internal.constants import SPAN_API_OPENTRACING -from ddtrace.trace import Context as DatadogContext # noqa:F401 -from ddtrace.trace import Span as DatadogSpan - -from .span_context import SpanContext -from .tags import Tags - - -if TYPE_CHECKING: # pragma: no cover - from ddtrace.trace import Tracer # noqa:F401 - - -_TagNameType = Union[Text, bytes] - - -class Span(OpenTracingSpan): - """Datadog implementation of :class:`opentracing.Span`""" - - def __init__(self, tracer, context, operation_name): - # type: (Tracer, Optional[SpanContext], str) -> None - if context is not None: - context = SpanContext(ddcontext=context._dd_context, baggage=context.baggage) - else: - context = SpanContext() - - super(Span, self).__init__(tracer, context) - - self.finished = False - self._lock = threading.Lock() - # use a datadog span - self._dd_span = DatadogSpan(operation_name, context=context._dd_context, span_api=SPAN_API_OPENTRACING) - - def finish(self, finish_time=None): - # type: (Optional[float]) -> None - """Finish the span. - - This calls finish on the ddspan. - - :param finish_time: specify a custom finish time with a unix timestamp - per time.time() - :type timestamp: float - """ - if self.finished: - return - - # finish the datadog span - self._dd_span.finish(finish_time) - self.finished = True - - def set_baggage_item(self, key, value): - # type: (str, Any) -> Span - """Sets a baggage item in the span context of this span. - - Baggage is used to propagate state between spans. - - :param key: baggage item key - :type key: str - - :param value: baggage item value - :type value: a type that can be str'd - - :rtype: Span - :return: itself for chaining calls - """ - new_ctx = self.context.with_baggage_item(key, value) - with self._lock: - self._context = new_ctx - return self - - def get_baggage_item(self, key): - # type: (str) -> Optional[str] - """Gets a baggage item from the span context of this span. - - :param key: baggage item key - :type key: str - - :rtype: str - :return: the baggage value for the given key or ``None``. - """ - return self.context.get_baggage_item(key) - - def set_operation_name(self, operation_name): - # type: (str) -> Span - """Set the operation name.""" - self._dd_span.name = operation_name - return self - - def log_kv(self, key_values, timestamp=None): - # type: (Dict[_TagNameType, Any], Optional[float]) -> Span - """Add a log record to this span. - - Passes on relevant opentracing key values onto the datadog span. - - :param key_values: a dict of string keys and values of any type - :type key_values: dict - - :param timestamp: a unix timestamp per time.time() - :type timestamp: float - - :return: the span itself, for call chaining - :rtype: Span - """ - - # match opentracing defined keys to datadog functionality - # opentracing/specification/blob/1be630515dafd4d2a468d083300900f89f28e24d/semantic_conventions.md#log-fields-table # noqa: E501 - for key, val in key_values.items(): - if key == "event" and val == "error": - # TODO: not sure if it's actually necessary to set the error manually - self._dd_span.error = 1 - self.set_tag("error", 1) - elif key == "error" or key == "error.object": - self.set_tag(ERROR_TYPE, val) - elif key == "message": - self.set_tag(ERROR_MSG, val) - elif key == "stack": - self.set_tag(ERROR_STACK, val) - else: - pass - - return self - - def set_tag(self, key, value): - # type: (_TagNameType, Any) -> Span - """Set a tag on the span. - - This sets the tag on the underlying datadog span. - """ - if key == Tags.SPAN_TYPE: - self._dd_span.span_type = value - elif key == Tags.SERVICE_NAME: - self._dd_span.service = value - elif key == Tags.RESOURCE_NAME or key == OTTags.DATABASE_STATEMENT: - self._dd_span.resource = value - elif key == OTTags.PEER_HOSTNAME: - self._dd_span._set_tag_str(Tags.TARGET_HOST, value) - elif key == OTTags.PEER_PORT: - self._dd_span.set_tag(Tags.TARGET_PORT, value) - elif key == Tags.SAMPLING_PRIORITY: - self._dd_span.context.sampling_priority = value - else: - self._dd_span.set_tag(key, value) - return self - - def _get_tag(self, key): - # type: (_TagNameType) -> Optional[Text] - """Gets a tag from the span. - - This method retrieves the tag from the underlying datadog span. - """ - return self._dd_span.get_tag(key) - - def _get_metric(self, key): - # type: (_TagNameType) -> Optional[NumericType] - """Gets a metric from the span. - - This method retrieves the metric from the underlying datadog span. - """ - return self._dd_span.get_metric(key) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type: - self._dd_span.set_exc_info(exc_type, exc_val, exc_tb) - - # note: self.finish() AND _dd_span.__exit__ will call _span.finish() but - # it is idempotent - self._dd_span.__exit__(exc_type, exc_val, exc_tb) - self.finish() - - def _associate_dd_span(self, ddspan): - # type: (DatadogSpan) -> None - """Associates a DD span with this span.""" - # get the datadog span context - self._dd_span = ddspan - self.context._dd_context = ddspan.context - - @property - def _dd_context(self): - # type: () -> DatadogContext - return self._dd_span.context diff --git a/ddtrace/opentracer/span_context.py b/ddtrace/opentracer/span_context.py deleted file mode 100644 index 171142d18a8..00000000000 --- a/ddtrace/opentracer/span_context.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Any # noqa:F401 -from typing import Dict # noqa:F401 -from typing import Optional # noqa:F401 - -from opentracing import SpanContext as OpenTracingSpanContext - -from ddtrace.internal.compat import NumericType # noqa:F401 -from ddtrace.trace import Context as DatadogContext - - -class SpanContext(OpenTracingSpanContext): - """Implementation of the OpenTracing span context.""" - - def __init__( - self, - trace_id=None, # type: Optional[int] - span_id=None, # type: Optional[int] - sampling_priority=None, # type: Optional[NumericType] - baggage=None, # type: Optional[Dict[str, Any]] - ddcontext=None, # type: Optional[DatadogContext] - ): - # type: (...) -> None - # create a new dict for the baggage if it is not provided - # NOTE: it would be preferable to use opentracing.SpanContext.EMPTY_BAGGAGE - # but it is mutable. - # see: opentracing-python/blob/8775c7bfc57fd66e1c8bcf9a54d3e434d37544f9/opentracing/span.py#L30 - baggage = baggage or {} - - if ddcontext is not None: - self._dd_context = ddcontext - else: - self._dd_context = DatadogContext( - trace_id=trace_id, - span_id=span_id, - sampling_priority=sampling_priority, - ) - - self._baggage = dict(baggage) - - @property - def baggage(self): - # type: () -> Dict[str, Any] - return self._baggage - - def set_baggage_item(self, key, value): - # type: (str, Any) -> None - """Sets a baggage item in this span context. - - Note that this operation mutates the baggage of this span context - """ - self.baggage[key] = value - - def with_baggage_item(self, key, value): - # type: (str, Any) -> SpanContext - """Returns a copy of this span with a new baggage item. - - Useful for instantiating new child span contexts. - """ - baggage = dict(self._baggage) - baggage[key] = value - return SpanContext(ddcontext=self._dd_context, baggage=baggage) - - def get_baggage_item(self, key): - # type: (str) -> Optional[Any] - """Gets a baggage item in this span context.""" - return self.baggage.get(key, None) diff --git a/ddtrace/opentracer/tags.py b/ddtrace/opentracer/tags.py deleted file mode 100644 index ebc2d86d146..00000000000 --- a/ddtrace/opentracer/tags.py +++ /dev/null @@ -1,23 +0,0 @@ -from collections import namedtuple - - -TagNames = namedtuple( - "TagNames", - [ - "RESOURCE_NAME", - "SAMPLING_PRIORITY", - "SERVICE_NAME", - "SPAN_TYPE", - "TARGET_HOST", - "TARGET_PORT", - ], -) - -Tags = TagNames( - RESOURCE_NAME="resource.name", - SAMPLING_PRIORITY="sampling.priority", - SERVICE_NAME="service.name", - TARGET_HOST="out.host", - TARGET_PORT="network.destination.port", - SPAN_TYPE="span.type", -) diff --git a/ddtrace/opentracer/tracer.py b/ddtrace/opentracer/tracer.py deleted file mode 100644 index a783d3263dc..00000000000 --- a/ddtrace/opentracer/tracer.py +++ /dev/null @@ -1,399 +0,0 @@ -from typing import Any # noqa:F401 -from typing import Dict # noqa:F401 -from typing import List # noqa:F401 -from typing import Optional # noqa:F401 -from typing import Union # noqa:F401 -from urllib.parse import urlparse - -import opentracing -from opentracing import Format -from opentracing import Scope # noqa:F401 -from opentracing import ScopeManager # noqa:F401 -from opentracing.scope_managers import ThreadLocalScopeManager - -import ddtrace -from ddtrace import config as ddconfig -from ddtrace.internal.constants import SPAN_API_OPENTRACING -from ddtrace.internal.utils.config import get_application_name -from ddtrace.internal.writer import AgentWriterInterface -from ddtrace.settings.exceptions import ConfigException -from ddtrace.trace import Context as DatadogContext # noqa:F401 -from ddtrace.trace import Span as DatadogSpan -from ddtrace.trace import Tracer as DatadogTracer - -from ..internal.logger import get_logger -from .propagation import HTTPPropagator -from .settings import ConfigKeys as keys -from .settings import config_invalid_keys -from .span import Span -from .span_context import SpanContext -from .utils import get_context_provider_for_scope_manager - - -log = get_logger(__name__) - -DEFAULT_CONFIG: Dict[str, Optional[Any]] = { - keys.AGENT_HOSTNAME: None, - keys.AGENT_HTTPS: None, - keys.AGENT_PORT: None, - keys.DEBUG: False, - keys.ENABLED: None, - keys.GLOBAL_TAGS: {}, - keys.SAMPLER: None, - # Not used, priority sampling can not be disabled in +v3.0 - keys.PRIORITY_SAMPLING: None, - keys.UDS_PATH: None, - keys.SETTINGS: { - "FILTERS": [], - }, -} - - -class Tracer(opentracing.Tracer): - """A wrapper providing an OpenTracing API for the Datadog tracer.""" - - def __init__( - self, - service_name: Optional[str] = None, - config: Optional[Dict[str, Any]] = None, - scope_manager: Optional[ScopeManager] = None, - _dd_tracer: Optional[DatadogTracer] = None, - ) -> None: - """Initialize a new Datadog opentracer. - - :param service_name: (optional) the name of the service that this - tracer will be used with. Note if not provided, a service name will - try to be determined based off of ``sys.argv``. If this fails a - :class:`ddtrace.settings.ConfigException` will be raised. - :param config: (optional) a configuration object to specify additional - options. See the documentation for further information. - :param scope_manager: (optional) the scope manager for this tracer to - use. The available managers are listed in the Python OpenTracing repo - here: https://github.com/opentracing/opentracing-python#scope-managers. - If ``None`` is provided, defaults to - :class:`opentracing.scope_managers.ThreadLocalScopeManager`. - """ - # Merge the given config with the default into a new dict - self._config = DEFAULT_CONFIG.copy() - if config is not None: - self._config.update(config) - # Pull out commonly used properties for performance - self._service_name = service_name or get_application_name() - self._debug = self._config.get(keys.DEBUG) - - if self._debug and ddconfig._raise: - # Ensure there are no typos in any of the keys - invalid_keys = config_invalid_keys(self._config) - if invalid_keys: - str_invalid_keys = ",".join(invalid_keys) - raise ConfigException("invalid key(s) given ({})".format(str_invalid_keys)) - - if not self._service_name and ddconfig._raise: - raise ConfigException( - """ Cannot detect the \'service_name\'. - Please set the \'service_name=\' - keyword argument. - """ - ) - - self._scope_manager = scope_manager or ThreadLocalScopeManager() - self._dd_tracer = _dd_tracer or ddtrace.tracer - self._dd_tracer.context_provider = get_context_provider_for_scope_manager(self._scope_manager) - - self._dd_tracer.set_tags(self._config.get(keys.GLOBAL_TAGS)) # type: ignore[arg-type] - trace_processors = None - if isinstance(self._config.get(keys.SETTINGS), dict) and self._config[keys.SETTINGS].get("FILTERS"): # type: ignore[union-attr] - trace_processors = self._config[keys.SETTINGS]["FILTERS"] # type: ignore[index] - self._dd_tracer._span_aggregator.user_processors = trace_processors - - if self._config[keys.ENABLED]: - self._dd_tracer.enabled = self._config[keys.ENABLED] - - if ( - self._config[keys.AGENT_HOSTNAME] - or self._config[keys.AGENT_HTTPS] - or self._config[keys.AGENT_PORT] - or self._config[keys.UDS_PATH] - ): - scheme = "https" if self._config[keys.AGENT_HTTPS] else "http" - hostname = self._config[keys.AGENT_HOSTNAME] - port = self._config[keys.AGENT_PORT] - if self._dd_tracer._agent_url: - curr_agent_url = urlparse(self._dd_tracer._agent_url) - scheme = "https" if self._config[keys.AGENT_HTTPS] else curr_agent_url.scheme - hostname = hostname or curr_agent_url.hostname - port = port or curr_agent_url.port - uds_path = self._config[keys.UDS_PATH] - - if uds_path: - new_url = f"unix://{uds_path}" - else: - new_url = f"{scheme}://{hostname}:{port}" - if isinstance(self._dd_tracer._span_aggregator.writer, AgentWriterInterface): - self._dd_tracer._span_aggregator.writer.intake_url = new_url - self._dd_tracer._recreate() - - if self._config[keys.SAMPLER]: - self._dd_tracer._sampler = self._config[keys.SAMPLER] - - self._propagators = { - Format.HTTP_HEADERS: HTTPPropagator, - Format.TEXT_MAP: HTTPPropagator, - } - - @property - def scope_manager(self): - # type: () -> ScopeManager - """Returns the scope manager being used by this tracer.""" - return self._scope_manager - - def start_active_span( - self, - operation_name, # type: str - child_of=None, # type: Optional[Union[Span, SpanContext]] - references=None, # type: Optional[List[Any]] - tags=None, # type: Optional[Dict[str, str]] - start_time=None, # type: Optional[int] - ignore_active_span=False, # type: bool - finish_on_close=True, # type: bool - ): - # type: (...) -> Scope - """Returns a newly started and activated `Scope`. - The returned `Scope` supports with-statement contexts. For example:: - - with tracer.start_active_span('...') as scope: - scope.span.set_tag('http.method', 'GET') - do_some_work() - # Span.finish() is called as part of Scope deactivation through - # the with statement. - - It's also possible to not finish the `Span` when the `Scope` context - expires:: - - with tracer.start_active_span('...', - finish_on_close=False) as scope: - scope.span.set_tag('http.method', 'GET') - do_some_work() - # Span.finish() is not called as part of Scope deactivation as - # `finish_on_close` is `False`. - - :param operation_name: name of the operation represented by the new - span from the perspective of the current service. - :param child_of: (optional) a Span or SpanContext instance representing - the parent in a REFERENCE_CHILD_OF Reference. If specified, the - `references` parameter must be omitted. - :param references: (optional) a list of Reference objects that identify - one or more parent SpanContexts. (See the Reference documentation - for detail). - :param tags: an optional dictionary of Span Tags. The caller gives up - ownership of that dictionary, because the Tracer may use it as-is - to avoid extra data copying. - :param start_time: an explicit Span start time as a unix timestamp per - time.time(). - :param ignore_active_span: (optional) an explicit flag that ignores - the current active `Scope` and creates a root `Span`. - :param finish_on_close: whether span should automatically be finished - when `Scope.close()` is called. - :return: a `Scope`, already registered via the `ScopeManager`. - """ - otspan = self.start_span( - operation_name=operation_name, - child_of=child_of, - references=references, - tags=tags, - start_time=start_time, - ignore_active_span=ignore_active_span, - ) - - # activate this new span - scope = self._scope_manager.activate(otspan, finish_on_close) - self._dd_tracer.context_provider.activate(otspan._dd_span) - return scope - - def start_span( - self, - operation_name: Optional[str] = None, - child_of: Optional[Union[Span, SpanContext]] = None, - references: Optional[List[Any]] = None, - tags: Optional[Dict[str, str]] = None, - start_time: Optional[int] = None, - ignore_active_span: bool = False, - ) -> Span: - """Starts and returns a new Span representing a unit of work. - - Starting a root Span (a Span with no causal references):: - - tracer.start_span('...') - - Starting a child Span (see also start_child_span()):: - - tracer.start_span( - '...', - child_of=parent_span) - - Starting a child Span in a more verbose way:: - - tracer.start_span( - '...', - references=[opentracing.child_of(parent_span)]) - - Note: the precedence when defining a relationship is the following, from highest to lowest: - 1. *child_of* - 2. *references* - 3. `scope_manager.active` (unless *ignore_active_span* is True) - 4. None - - Currently Datadog only supports `child_of` references. - - :param operation_name: name of the operation represented by the new - span from the perspective of the current service. - :param child_of: (optional) a Span or SpanContext instance representing - the parent in a REFERENCE_CHILD_OF Reference. If specified, the - `references` parameter must be omitted. - :param references: (optional) a list of Reference objects that identify - one or more parent SpanContexts. (See the Reference documentation - for detail) - :param tags: an optional dictionary of Span Tags. The caller gives up - ownership of that dictionary, because the Tracer may use it as-is - to avoid extra data copying. - :param start_time: an explicit Span start time as a unix timestamp per - time.time() - :param ignore_active_span: an explicit flag that ignores the current - active `Scope` and creates a root `Span`. - :return: an already-started Span instance. - """ - ot_parent = None # 'ot_parent' is more readable than 'child_of' - ot_parent_context = None # the parent span's context - # dd_parent: the child_of to pass to the ddtracer - dd_parent = None # type: Optional[Union[DatadogSpan, DatadogContext]] - - if child_of is not None: - ot_parent = child_of # 'ot_parent' is more readable than 'child_of' - elif references and isinstance(references, list): - # we currently only support child_of relations to one span - ot_parent = references[0].referenced_context - - # - whenever child_of is not None ddspans with parent-child - # relationships will share a ddcontext which maintains a hierarchy of - # ddspans for the execution flow - # - when child_of is a ddspan then the ddtracer uses this ddspan to - # create the child ddspan - # - when child_of is a ddcontext then the ddtracer uses the ddcontext to - # get_current_span() for the parent - if ot_parent is None and not ignore_active_span: - # attempt to get the parent span from the scope manager - scope = self._scope_manager.active - parent_span = getattr(scope, "span", None) - ot_parent_context = getattr(parent_span, "context", None) - - # Compare the active ot and dd spans. Using the one which - # was created later as the parent. - active_dd_parent = self._dd_tracer.context_provider.active() - if parent_span and isinstance(active_dd_parent, DatadogSpan): - dd_parent_span = parent_span._dd_span - if active_dd_parent.start_ns >= dd_parent_span.start_ns: - dd_parent = active_dd_parent - else: - dd_parent = dd_parent_span - else: - dd_parent = active_dd_parent - elif ot_parent is not None and isinstance(ot_parent, Span): - # a span is given to use as a parent - ot_parent_context = ot_parent.context - dd_parent = ot_parent._dd_span - elif ot_parent is not None and isinstance(ot_parent, SpanContext): - # a span context is given to use to find the parent ddspan - dd_parent = ot_parent._dd_context - elif ot_parent is None: - # user wants to create a new parent span we don't have to do - # anything - pass - elif ddconfig._raise: - raise TypeError("invalid span configuration given") - - # create a new otspan and ddspan using the ddtracer and associate it - # with the new otspan - ddspan = self._dd_tracer.start_span( - name=operation_name, # type: ignore[arg-type] - child_of=dd_parent, - service=self._service_name, - activate=False, - span_api=SPAN_API_OPENTRACING, - ) - - # set the start time if one is specified - ddspan.start = start_time or ddspan.start - - otspan = Span(self, ot_parent_context, operation_name) # type: ignore[arg-type] - # sync up the OT span with the DD span - otspan._associate_dd_span(ddspan) - - if tags is not None: - for k in tags: - # Make sure we set the tags on the otspan to ensure that the special compatibility tags - # are handled correctly (resource name, span type, sampling priority, etc). - otspan.set_tag(k, tags[k]) - - return otspan - - @property - def active_span(self): - # type: () -> Optional[Span] - """Retrieves the active span from the opentracing scope manager - - Falls back to using the datadog active span if one is not found. This - allows opentracing users to use datadog instrumentation. - """ - scope = self._scope_manager.active - if scope: - return scope.span - else: - dd_span = self._dd_tracer.current_span() - ot_span = None # type: Optional[Span] - if dd_span: - ot_span = Span(self, None, dd_span.name) - ot_span._associate_dd_span(dd_span) - return ot_span - - def inject(self, span_context, format, carrier): # noqa: A002 - # type: (SpanContext, str, Dict[str, str]) -> None - """Injects a span context into a carrier. - - :param span_context: span context to inject. - :param format: format to encode the span context with. - :param carrier: the carrier of the encoded span context. - """ - propagator = self._propagators.get(format, None) - - if propagator is None: - raise opentracing.UnsupportedFormatException - - propagator.inject(span_context, carrier) - - def extract(self, format, carrier): # noqa: A002 - # type: (str, Dict[str, str]) -> SpanContext - """Extracts a span context from a carrier. - - :param format: format that the carrier is encoded with. - :param carrier: the carrier to extract from. - """ - propagator = self._propagators.get(format, None) - - if propagator is None: - raise opentracing.UnsupportedFormatException - - # we have to manually activate the returned context from a distributed - # trace - ot_span_ctx = propagator.extract(carrier) - dd_span_ctx = ot_span_ctx._dd_context - self._dd_tracer.context_provider.activate(dd_span_ctx) - return ot_span_ctx - - def get_log_correlation_context(self): - # type: () -> Dict[str, str] - """Retrieves the data used to correlate a log with the current active trace. - Generates a dictionary for custom logging instrumentation including the trace id and - span id of the current active span, as well as the configured service, version, and environment names. - If there is no active span, a dictionary with an empty string for each value will be returned. - """ - return self._dd_tracer.get_log_correlation_context() diff --git a/ddtrace/opentracer/utils.py b/ddtrace/opentracer/utils.py deleted file mode 100644 index 886e998d8a3..00000000000 --- a/ddtrace/opentracer/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -from opentracing import ScopeManager # noqa:F401 - -from ddtrace._trace.provider import BaseContextProvider -from ddtrace._trace.provider import DefaultContextProvider - - -# DEV: If `asyncio` or `gevent` are unavailable we do not throw an error, -# `context_provider` will just not be set and we'll get an `AttributeError` instead - - -def get_context_provider_for_scope_manager(scope_manager: ScopeManager) -> BaseContextProvider: - """Returns the context_provider to use with a given scope_manager.""" - - dd_context_provider = DefaultContextProvider() - _patch_scope_manager(scope_manager, dd_context_provider) - - return dd_context_provider - - -def _patch_scope_manager(scope_manager: ScopeManager, context_provider: BaseContextProvider) -> None: - """ - Patches a scope manager so that any time a span is activated - it'll also activate the underlying ddcontext with the underlying - datadog context provider. - - This allows opentracing users to rely on ddtrace.contrib patches and - have them parent correctly. - - :param scope_manager: Something that implements `opentracing.ScopeManager` - :param context_provider: Something that implements `datadog.provider.BaseContextProvider` - """ - if getattr(scope_manager, "_datadog_patch", False): - return - scope_manager._datadog_patch = True - - old_method = scope_manager.activate - - def _patched_activate(*args, **kwargs): - otspan = kwargs.get("span", args[0]) - context_provider.activate(otspan._dd_context) - return old_method(*args, **kwargs) - - scope_manager.activate = _patched_activate diff --git a/releasenotes/notes/opentracer-remove-b1883d26ea035c50.yaml b/releasenotes/notes/opentracer-remove-b1883d26ea035c50.yaml new file mode 100644 index 00000000000..6d248930d9c --- /dev/null +++ b/releasenotes/notes/opentracer-remove-b1883d26ea035c50.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + opentracer: This change removes the deprecated ``opentracer`` package diff --git a/tests/commands/ddtrace_run_app_name.py b/tests/commands/ddtrace_run_app_name.py deleted file mode 100644 index 4cf41192e79..00000000000 --- a/tests/commands/ddtrace_run_app_name.py +++ /dev/null @@ -1,6 +0,0 @@ -from ddtrace.opentracer import Tracer - - -if __name__ == "__main__": - tracer = Tracer() - print(tracer._service_name) diff --git a/tests/commands/test_runner.py b/tests/commands/test_runner.py index 967db4a7f5e..4b29dff8ea3 100644 --- a/tests/commands/test_runner.py +++ b/tests/commands/test_runner.py @@ -197,13 +197,6 @@ def test_argv_passed(self): out = subprocess.check_output(["ddtrace-run", "python", "tests/commands/ddtrace_run_argv.py", "foo", "bar"]) assert out.startswith(b"Test success") - def test_got_app_name(self): - """ - apps run with ddtrace-run have a proper app name - """ - out = subprocess.check_output(["ddtrace-run", "python", "tests/commands/ddtrace_run_app_name.py"]) - assert out.startswith(b"ddtrace_run_app_name.py") - def test_global_trace_tags(self): """Ensure global tags are passed in from environment""" with self.override_env(dict(DD_TRACE_GLOBAL_TAGS="a:True,b:0,c:C")): diff --git a/tests/contrib/aiobotocore/test.py b/tests/contrib/aiobotocore/test.py index 5e7151797b0..0fdf25414e8 100644 --- a/tests/contrib/aiobotocore/test.py +++ b/tests/contrib/aiobotocore/test.py @@ -303,92 +303,6 @@ async def test_double_patch(tracer): assert len(traces[0]) == 1 -@pytest.mark.asyncio -async def test_opentraced_client(tracer): - from tests.opentracer.utils import init_tracer - - ot_tracer = init_tracer("my_svc", tracer) - - with ot_tracer.start_active_span("ot_outer_span"): - async with aiobotocore_client("ec2", tracer) as ec2: - await ec2.describe_instances() - - traces = tracer.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - ot_span = traces[0][0] - dd_span = traces[0][1] - - assert ot_span.resource == "ot_outer_span" - assert ot_span.service == "my_svc" - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert_is_measured(dd_span) - assert dd_span.get_tag("aws.agent") == "aiobotocore" - assert dd_span.get_tag("aws.region") == "us-west-2" - assert dd_span.get_tag("region") == "us-west-2" - assert dd_span.get_tag("aws.operation") == "DescribeInstances" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_metric("retry_attempts") == 0 - assert dd_span.service == "aws.ec2" - assert dd_span.resource == "ec2.describeinstances" - assert dd_span.name == "ec2.command" - assert dd_span.get_tag("component") == "aiobotocore" - assert dd_span.get_tag("span.kind") == "client" - - -@pytest.mark.asyncio -async def test_opentraced_s3_client(tracer): - from tests.opentracer.utils import init_tracer - - ot_tracer = init_tracer("my_svc", tracer) - - with ot_tracer.start_active_span("ot_outer_span"): - async with aiobotocore_client("s3", tracer) as s3: - await s3.list_buckets() - with ot_tracer.start_active_span("ot_inner_span1"): - await s3.list_buckets() - with ot_tracer.start_active_span("ot_inner_span2"): - pass - - traces = tracer.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 5 - ot_outer_span = traces[0][0] - dd_span = traces[0][1] - ot_inner_span = traces[0][2] - dd_span2 = traces[0][3] - ot_inner_span2 = traces[0][4] - - assert ot_outer_span.resource == "ot_outer_span" - assert ot_inner_span.resource == "ot_inner_span1" - assert ot_inner_span2.resource == "ot_inner_span2" - - # confirm the parenting - assert ot_outer_span.parent_id is None - assert dd_span.parent_id == ot_outer_span.span_id - assert ot_inner_span.parent_id == ot_outer_span.span_id - assert dd_span2.parent_id == ot_inner_span.span_id - assert ot_inner_span2.parent_id == ot_outer_span.span_id - - assert_is_measured(dd_span) - assert dd_span.get_tag("aws.operation") == "ListBuckets" - assert_span_http_status_code(dd_span, 200) - assert dd_span.service == "aws.s3" - assert dd_span.resource == "s3.listbuckets" - assert dd_span.name == "s3.command" - - assert dd_span2.get_tag("aws.operation") == "ListBuckets" - assert_span_http_status_code(dd_span2, 200) - assert dd_span2.service == "aws.s3" - assert dd_span2.resource == "s3.listbuckets" - assert dd_span2.name == "s3.command" - assert dd_span.get_tag("component") == "aiobotocore" - - @pytest.mark.asyncio async def test_user_specified_service(tracer): """ diff --git a/tests/contrib/aiohttp/test_middleware.py b/tests/contrib/aiohttp/test_middleware.py index 37e6ea2e3de..e067c197685 100644 --- a/tests/contrib/aiohttp/test_middleware.py +++ b/tests/contrib/aiohttp/test_middleware.py @@ -1,6 +1,5 @@ import os -from opentracing.scope_managers.asyncio import AsyncioScopeManager import pytest import pytest_asyncio @@ -14,7 +13,6 @@ from ddtrace.contrib.internal.aiohttp.middlewares import trace_middleware from ddtrace.ext import http from ddtrace.internal.utils.version import parse_version -from tests.opentracer.utils import init_tracer from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data from tests.utils import assert_span_http_status_code from tests.utils import override_global_config @@ -545,22 +543,6 @@ async def test_parenting_200_dd(app_tracer, aiohttp_client): _assert_200_parenting(client, traces) -async def test_parenting_200_ot(app_tracer, aiohttp_client): - """OpenTracing version of test_handler.""" - app, tracer = app_tracer - client = await aiohttp_client(app) - ot_tracer = init_tracer("aiohttp_svc", tracer, scope_manager=AsyncioScopeManager()) - - with ot_tracer.start_active_span("aiohttp_op"): - request = await client.request("GET", "/") - assert 200 == request.status - text = await request.text() - - assert "What's tracing?" == text - traces = tracer.pop_traces() - _assert_200_parenting(client, traces) - - @pytest.mark.parametrize( "test_app", [ diff --git a/tests/contrib/aiopg/test.py b/tests/contrib/aiopg/test.py index b60e5989dda..63f2b89a379 100644 --- a/tests/contrib/aiopg/test.py +++ b/tests/contrib/aiopg/test.py @@ -11,7 +11,6 @@ from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from tests.contrib.asyncio.utils import AsyncioTestCase from tests.contrib.config import POSTGRES_CONFIG -from tests.opentracer.utils import init_tracer from tests.subprocesstest import run_in_subprocess from tests.utils import assert_is_measured @@ -75,29 +74,6 @@ async def assert_conn_is_traced(self, tracer, db, service): assert span.get_tag("component") == "aiopg" assert span.get_tag("span.kind") == "client" - # Ensure OpenTracing compatibility - ot_tracer = init_tracer("aiopg_svc", tracer) - with ot_tracer.start_active_span("aiopg_op"): - cursor = await db.cursor() - await cursor.execute(q) - rows = await cursor.fetchall() - assert rows == [("foobarblah",)] - spans = self.pop_spans() - assert len(spans) == 2 - ot_span, dd_span = spans - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - assert ot_span.name == "aiopg_op" - assert ot_span.service == "aiopg_svc" - assert dd_span.name == "postgres.query" - assert dd_span.resource == q - assert dd_span.service == service - assert dd_span.error == 0 - assert dd_span.span_type == "sql" - assert dd_span.get_tag("component") == "aiopg" - assert span.get_tag("span.kind") == "client" - # run a query with an error and ensure all is well q = "select * from some_non_existant_table" cur = await db.cursor() diff --git a/tests/contrib/aredis/test_aredis.py b/tests/contrib/aredis/test_aredis.py index baa8b7f6c30..a40415e4c06 100644 --- a/tests/contrib/aredis/test_aredis.py +++ b/tests/contrib/aredis/test_aredis.py @@ -9,7 +9,6 @@ from ddtrace.contrib.internal.aredis.patch import patch from ddtrace.contrib.internal.aredis.patch import unpatch from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import override_config from ..config import REDIS_CONFIG @@ -185,19 +184,6 @@ async def test(tracer, test_spans): assert err == b"", err.decode() -@pytest.mark.asyncio -async def test_opentracing(tracer, snapshot_context): - """Ensure OpenTracing works with redis.""" - - with snapshot_context(): - r = aredis.StrictRedis(port=REDIS_CONFIG["port"]) - pin = Pin.get_from(r) - ot_tracer = init_tracer("redis_svc", pin.tracer) - - with ot_tracer.start_active_span("redis_get"): - await r.get("cheese") - - @pytest.mark.subprocess(ddtrace_run=True, env=dict(DD_REDIS_RESOURCE_ONLY_COMMAND="false")) @pytest.mark.snapshot def test_full_command_in_resource_env(): diff --git a/tests/contrib/asyncio/test_propagation.py b/tests/contrib/asyncio/test_propagation.py index fd962e544ea..fc976d59ea8 100644 --- a/tests/contrib/asyncio/test_propagation.py +++ b/tests/contrib/asyncio/test_propagation.py @@ -7,7 +7,6 @@ from ddtrace.contrib.internal.asyncio.patch import patch from ddtrace.contrib.internal.asyncio.patch import unpatch from ddtrace.trace import Context -from tests.opentracer.utils import init_tracer _orig_create_task = asyncio.BaseEventLoop.create_task @@ -115,59 +114,3 @@ async def test_propagation_with_new_context(tracer): span = traces[0][0] assert span.trace_id == 100 assert span.parent_id == 101 - - -@pytest.mark.asyncio -async def test_trace_multiple_coroutines_ot_outer(tracer): - """OpenTracing version of test_trace_multiple_coroutines.""" - - # if multiple coroutines have nested tracing, they must belong - # to the same trace - async def coro(): - # another traced coroutine - with tracer.trace("coroutine_2"): - return 42 - - ot_tracer = init_tracer("asyncio_svc", tracer) - with ot_tracer.start_active_span("coroutine_1"): - value = await coro() - - # the coroutine has been called correctly - assert 42 == value - # a single trace has been properly reported - traces = tracer.pop_traces() - assert 1 == len(traces) - assert 2 == len(traces[0]) - assert "coroutine_1" == traces[0][0].name - assert "coroutine_2" == traces[0][1].name - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id - - -@pytest.mark.asyncio -async def test_trace_multiple_coroutines_ot_inner(tracer): - """OpenTracing version of test_trace_multiple_coroutines.""" - # if multiple coroutines have nested tracing, they must belong - # to the same trace - ot_tracer = init_tracer("asyncio_svc", tracer) - - async def coro(): - # another traced coroutine - with ot_tracer.start_active_span("coroutine_2"): - return 42 - - with tracer.trace("coroutine_1"): - value = await coro() - - # the coroutine has been called correctly - assert 42 == value - # a single trace has been properly reported - traces = tracer.pop_traces() - assert 1 == len(traces) - assert 2 == len(traces[0]) - assert "coroutine_1" == traces[0][0].name - assert "coroutine_2" == traces[0][1].name - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id diff --git a/tests/contrib/boto/test.py b/tests/contrib/boto/test.py index 91c626e6cbc..76dd974692a 100644 --- a/tests/contrib/boto/test.py +++ b/tests/contrib/boto/test.py @@ -20,7 +20,6 @@ from ddtrace.contrib.internal.boto.patch import unpatch from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -759,56 +758,3 @@ def test_elasticache_client(self): self.assertEqual(span.get_tag("span.kind"), "client") self.assertEqual(span.service, "test-boto-tracing.elasticache") self.assertEqual(span.resource, "elasticache") - - @mock_ec2 - def test_ec2_client_ot(self): - """OpenTracing compatibility check of the test_ec2_client test.""" - ec2 = boto.ec2.connect_to_region("us-west-2") - ot_tracer = init_tracer("my_svc", self.tracer) - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = self.tracer - pin.onto(ec2) - - with ot_tracer.start_active_span("ot_span"): - ec2.get_all_instances() - spans = self.pop_spans() - assert spans - self.assertEqual(len(spans), 2) - ot_span, dd_span = spans - - # confirm the parenting - self.assertIsNone(ot_span.parent_id) - self.assertEqual(dd_span.parent_id, ot_span.span_id) - - self.assertEqual(ot_span.resource, "ot_span") - self.assertEqual(dd_span.get_tag("aws.operation"), "DescribeInstances") - self.assertEqual(dd_span.get_tag("component"), "boto") - self.assertEqual(dd_span.get_tag("span.kind"), "client") - assert_span_http_status_code(dd_span, 200) - self.assertEqual(dd_span.get_tag(http.METHOD), "POST") - self.assertEqual(dd_span.get_tag("aws.region"), "us-west-2") - self.assertEqual(dd_span.get_tag("region"), "us-west-2") - self.assertEqual(dd_span.get_tag("aws.partition"), "aws") - - with ot_tracer.start_active_span("ot_span"): - ec2.run_instances(21) - spans = self.pop_spans() - assert spans - self.assertEqual(len(spans), 2) - ot_span, dd_span = spans - - # confirm the parenting - self.assertIsNone(ot_span.parent_id) - self.assertEqual(dd_span.parent_id, ot_span.span_id) - - self.assertEqual(dd_span.get_tag("aws.operation"), "RunInstances") - assert_span_http_status_code(dd_span, 200) - self.assertEqual(dd_span.get_tag(http.METHOD), "POST") - self.assertEqual(dd_span.get_tag("aws.region"), "us-west-2") - self.assertEqual(dd_span.get_tag("region"), "us-west-2") - self.assertEqual(dd_span.get_tag("aws.partition"), "aws") - self.assertEqual(dd_span.get_tag("component"), "boto") - self.assertEqual(dd_span.get_tag("span.kind"), "client") - self.assertEqual(dd_span.service, "test-boto-tracing.ec2") - self.assertEqual(dd_span.resource, "ec2.runinstances") - self.assertEqual(dd_span.name, "ec2.command") diff --git a/tests/contrib/botocore/test.py b/tests/contrib/botocore/test.py index 5270f69f021..1cfdb2306fd 100644 --- a/tests/contrib/botocore/test.py +++ b/tests/contrib/botocore/test.py @@ -47,7 +47,6 @@ from ddtrace.internal.utils.version import parse_version from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -2245,43 +2244,6 @@ def test_schematized_unspecified_service_kms_client_v1(self): assert span.service == DEFAULT_SPAN_SERVICE_NAME assert span.name == "aws.kms.request" - @mock_ec2 - def test_traced_client_ot(self): - """OpenTracing version of test_traced_client.""" - ot_tracer = init_tracer("ec2_svc", self.tracer) - - with ot_tracer.start_active_span("ec2_op"): - ec2 = self.session.create_client("ec2", region_name="us-west-2") - pin = Pin(service=self.TEST_SERVICE) - pin._tracer = self.tracer - pin.onto(ec2) - ec2.describe_instances() - - spans = self.get_spans() - assert spans - assert len(spans) == 2 - - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "ec2_op" - assert ot_span.service == "ec2_svc" - - assert dd_span.get_tag("aws.agent") == "botocore" - assert dd_span.get_tag("aws.region") == "us-west-2" - assert dd_span.get_tag("region") == "us-west-2" - assert dd_span.get_tag("aws.operation") == "DescribeInstances" - assert dd_span.get_tag("component") == "botocore" - assert dd_span.get_tag("span.kind"), "client" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_metric("retry_attempts") == 0 - assert dd_span.service == "test-botocore-tracing.ec2" - assert dd_span.resource == "ec2.describeinstances" - assert dd_span.name == "ec2.command" - @unittest.skipIf(BOTOCORE_VERSION < (1, 9, 0), "Skipping for older versions of botocore without Stubber") def test_stubber_no_response_metadata(self): """When no ResponseMetadata key is provided in the response""" diff --git a/tests/contrib/bottle/test.py b/tests/contrib/bottle/test.py index 5a274802d85..d74ceb4935d 100644 --- a/tests/contrib/bottle/test.py +++ b/tests/contrib/bottle/test.py @@ -7,7 +7,6 @@ from ddtrace.contrib.internal.bottle.patch import TracePlugin from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data from tests.utils import TracerTestCase from tests.utils import assert_is_measured @@ -316,44 +315,6 @@ def home(): assert s.get_tag("span.kind") == "server" assert s.get_tag("http.route") == "/home/" - def test_200_ot(self): - ot_tracer = init_tracer("my_svc", self.tracer) - - # setup our test app - @self.app.route("/hi/") - def hi(name): - return "hi %s" % name - - self._trace_app(self.tracer) - - # make a request - with ot_tracer.start_active_span("ot_span"): - resp = self.app.get("/hi/dougie") - - assert resp.status_int == 200 - assert resp.body.decode("utf-8", errors="ignore") == "hi dougie" - # validate it's traced - spans = self.pop_spans() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.resource == "ot_span" - - assert_is_measured(dd_span) - assert dd_span.name == "bottle.request" - assert dd_span.service == "bottle-app" - assert dd_span.resource == "GET /hi/" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_tag("http.method") == "GET" - assert dd_span.get_tag(http.URL) == "http://localhost:80/hi/dougie" - assert dd_span.get_tag("component") == "bottle" - assert dd_span.get_tag("span.kind") == "server" - assert dd_span.get_tag("http.route") == "/hi/" - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) def test_user_specified_service_default_schema(self): """ diff --git a/tests/contrib/celery/test_integration.py b/tests/contrib/celery/test_integration.py index 9646c0aceda..8a831e2c709 100644 --- a/tests/contrib/celery/test_integration.py +++ b/tests/contrib/celery/test_integration.py @@ -15,7 +15,6 @@ import ddtrace.internal.forksafe as forksafe from ddtrace.propagation.http import HTTPPropagator from ddtrace.trace import Context -from tests.opentracer.utils import init_tracer from ...utils import override_global_config from .base import CeleryBaseTestCase @@ -599,55 +598,6 @@ def fn_task(): assert run_trace[1].name == "test" assert run_trace[1].parent_id == run_trace[0].span_id - def test_fn_task_apply_async_ot(self): - """OpenTracing version of test_fn_task_apply_async.""" - ot_tracer = init_tracer("celery_svc", self.tracer) - - # it should execute a traced async task that has parameters - @self.app.task - def fn_task_parameters(user, force_logout=False): - return (user, force_logout) - - with ot_tracer.start_active_span("celery_op"): - t = fn_task_parameters.apply_async(args=["user"], kwargs={"force_logout": True}) - assert tuple(t.get(timeout=self.ASYNC_GET_TIMEOUT)) == ("user", True) - - ot_span = self.find_span(name="celery_op") - assert ot_span.parent_id is None - assert ot_span.name == "celery_op" - assert ot_span.service == "celery_svc" - - if self.ASYNC_USE_CELERY_FIXTURES: - async_span = self.find_span(name="celery.apply") - self.assert_is_measured(async_span) - assert async_span.error == 0 - - # confirm the parenting - assert async_span.parent_id == ot_span.span_id - assert async_span.name == "celery.apply" - assert async_span.resource == "tests.contrib.celery.test_integration.fn_task_parameters" - assert async_span.service == "celery-producer" - assert async_span.get_tag("celery.id") == t.task_id - assert async_span.get_tag("celery.action") == "apply_async" - assert async_span.get_tag("celery.routing_key") == "celery" - assert async_span.get_tag("component") == "celery" - assert async_span.get_tag("span.kind") == "producer" - assert async_span.get_tag("out.host") == "memory://" - - run_span = self.find_span(name="celery.run") - assert run_span.name == "celery.run" - assert run_span.parent_id is None - assert run_span.resource == "tests.contrib.celery.test_integration.fn_task_parameters" - assert run_span.service == "celery-worker" - assert run_span.get_tag("celery.id") == t.task_id - assert run_span.get_tag("celery.action") == "run" - assert run_span.get_tag("component") == "celery" - assert run_span.get_tag("span.kind") == "consumer" - - traces = self.pop_traces() - assert len(traces) == 2 - assert len(traces[0]) + len(traces[1]) == 3 - @pytest.mark.no_getattr_patch # this mark is added to prevent patching of getattr necessary for integration registry update # see: https://github.com/DataDog/dd-trace-py/pull/13215 diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index 7b58471c1d7..a10478fb143 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -36,7 +36,6 @@ from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data from tests.utils import assert_dict_issuperset from tests.utils import override_config @@ -1952,38 +1951,6 @@ def test_template_name(test_spans): assert span.resource == "/my-template" -""" -OpenTracing tests -""" - - -@pytest.mark.django_db -def test_middleware_trace_request_ot(client, test_spans, tracer): - """OpenTracing version of test_middleware_trace_request.""" - ot_tracer = init_tracer("my_svc", tracer) - - # ensures that the internals are properly traced - with ot_tracer.start_active_span("ot_span"): - assert client.get("/users/").status_code == 200 - - # check for spans - spans = test_spans.get_spans() - ot_span = spans[0] - sp_request = spans[1] - - # confirm parenting - assert ot_span.parent_id is None - assert sp_request.parent_id == ot_span.span_id - - assert ot_span.resource == "ot_span" - assert ot_span.service == "my_svc" - - assert sp_request.get_tag("http.status_code") == "200" - assert sp_request.get_tag(http.URL) == "http://testserver/users/" - assert sp_request.get_tag("django.user.is_authenticated") == "False" - assert sp_request.get_tag("http.method") == "GET" - - def test_collecting_requests_handles_improperly_configured_error(client, test_spans): """ Since it's difficult to reproduce the ImproperlyConfigured error via django (server setup), will instead diff --git a/tests/contrib/falcon/test_suite.py b/tests/contrib/falcon/test_suite.py index 2cb912ee760..9771158e34d 100644 --- a/tests/contrib/falcon/test_suite.py +++ b/tests/contrib/falcon/test_suite.py @@ -3,7 +3,6 @@ from ddtrace.constants import USER_KEEP from ddtrace.contrib.internal.falcon.patch import FALCON_VERSION from ddtrace.ext import http as httpx -from tests.opentracer.utils import init_tracer from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -225,37 +224,6 @@ def test_404_exception_no_stacktracer(self): assert span.get_tag("component") == "falcon" assert span.get_tag("span.kind") == "server" - def test_200_ot(self): - """OpenTracing version of test_200.""" - writer = self.tracer._span_aggregator.writer - ot_tracer = init_tracer("my_svc", self.tracer) - ot_tracer._dd_tracer._span_aggregator.writer = writer - ot_tracer._dd_tracer._recreate() - - with ot_tracer.start_active_span("ot_span"): - out = self.make_test_call("/200", expected_status_code=200) - assert out.content.decode("utf-8") == "Success" - - traces = self.tracer.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - ot_span, dd_span = traces[0] - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "my_svc" - assert ot_span.resource == "ot_span" - - assert_is_measured(dd_span) - assert dd_span.name == "falcon.request" - assert dd_span.service == self._service - assert dd_span.resource == "GET tests.contrib.falcon.app.resources.Resource200" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_tag(httpx.URL) == "http://falconframework.org/200" - assert dd_span.error == 0 - def test_falcon_request_hook(self): @config.falcon.hooks.on("request") def on_falcon_request(span, request, response): diff --git a/tests/contrib/flask_cache/test.py b/tests/contrib/flask_cache/test.py index 25ed861dbe2..6e23414eace 100644 --- a/tests/contrib/flask_cache/test.py +++ b/tests/contrib/flask_cache/test.py @@ -5,7 +5,6 @@ from ddtrace.contrib.internal.flask_cache.patch import get_traced_cache from ddtrace.ext import net from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_dict_issuperset from tests.utils import assert_is_measured @@ -317,44 +316,6 @@ def test_default_span_tags_memcached(self): self.assertEqual(span.get_tag(net.TARGET_HOST), "127.0.0.1") self.assertEqual(span.get_metric("network.destination.port"), self.TEST_MEMCACHED_PORT) - def test_simple_cache_get_ot(self): - """OpenTracing version of test_simple_cache_get.""" - ot_tracer = init_tracer("my_svc", self.tracer) - - # create the TracedCache instance for a Flask app - Cache = get_traced_cache(self.tracer, service=self.SERVICE) - app = Flask(__name__) - cache = Cache(app, config={"CACHE_TYPE": "simple"}) - - with ot_tracer.start_active_span("ot_span"): - cache.get("á_complex_operation") - - spans = self.get_spans() - self.assertEqual(len(spans), 2) - ot_span, dd_span = spans - - # confirm the parenting - self.assertIsNone(ot_span.parent_id) - self.assertEqual(dd_span.parent_id, ot_span.span_id) - - self.assertEqual(ot_span.resource, "ot_span") - self.assertEqual(ot_span.service, "my_svc") - - assert_is_measured(dd_span) - self.assertEqual(dd_span.service, self.SERVICE) - self.assertEqual(dd_span.resource, "get") - self.assertEqual(dd_span.name, "flask_cache.cmd") - self.assertEqual(dd_span.span_type, "cache") - self.assertEqual(dd_span.error, 0) - - expected_meta = { - "flask_cache.key": "á_complex_operation", - "flask_cache.backend": "simple", - "component": "flask_cache", - } - - assert_dict_issuperset(dd_span.get_tags(), expected_meta) - class TestFlaskCacheSchematization(TracerTestCase): TEST_REDIS_PORT = REDIS_CONFIG["port"] diff --git a/tests/contrib/futures/test_propagation.py b/tests/contrib/futures/test_propagation.py index 77a9e2f25a1..763052dda0c 100644 --- a/tests/contrib/futures/test_propagation.py +++ b/tests/contrib/futures/test_propagation.py @@ -6,7 +6,6 @@ from ddtrace.contrib.internal.futures.patch import patch from ddtrace.contrib.internal.futures.patch import unpatch -from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer from tests.utils import TracerTestCase @@ -408,33 +407,6 @@ def fn(): assert spans[1].trace_id == spans[0].trace_id assert spans[1].parent_id == spans[0].span_id - def test_propagation_ot(self): - """OpenTracing version of test_propagation.""" - # it must propagate the tracing context if available - ot_tracer = init_tracer("my_svc", self.tracer) - - def fn(): - # an active context must be available - self.assertTrue(self.tracer.context_provider.active() is not None) - with self.tracer.trace("executor.thread"): - return 42 - - with self.override_global_tracer(): - with ot_tracer.start_active_span("main.thread"): - with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: - future = executor.submit(fn) - result = future.result() - # assert the right result - self.assertEqual(result, 42) - - # the trace must be completed - self.assert_span_count(2) - spans = self.get_spans() - assert spans[0].name == "main.thread" - assert spans[1].name == "executor.thread" - assert spans[1].trace_id == spans[0].trace_id - assert spans[1].parent_id == spans[0].span_id - @pytest.mark.skipif(sys.version_info > (3, 12), reason="Fails on 3.13") @pytest.mark.subprocess(ddtrace_run=True, timeout=5) diff --git a/tests/contrib/gevent/test_tracer.py b/tests/contrib/gevent/test_tracer.py index dc72ccc08ca..a7505d56f7d 100644 --- a/tests/contrib/gevent/test_tracer.py +++ b/tests/contrib/gevent/test_tracer.py @@ -2,7 +2,6 @@ import gevent import gevent.pool -from opentracing.scope_managers.gevent import GeventScopeManager import ddtrace from ddtrace.constants import ERROR_MSG @@ -11,7 +10,6 @@ from ddtrace.trace import Context from ddtrace.contrib.internal.gevent.patch import patch from ddtrace.contrib.internal.gevent.patch import unpatch -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from .utils import silence_errors @@ -356,34 +354,6 @@ def green_2(): spans = self.pop_spans() self._assert_spawn_multiple_greenlets(spans) - def test_trace_spawn_multiple_greenlets_multiple_traces_ot(self): - """OpenTracing version of the same test.""" - - ot_tracer = init_tracer("my_svc", self.tracer, scope_manager=GeventScopeManager()) - - def entrypoint(): - with ot_tracer.start_active_span("greenlet.main") as span: - span.resource = "base" - jobs = [gevent.spawn(green_1), gevent.spawn(green_2)] - gevent.joinall(jobs) - - def green_1(): - with self.tracer.trace("greenlet.worker1") as span: - span.set_tag("worker_id", "1") - gevent.sleep(0.01) - - # note that replacing the `tracer.trace` call here with the - # OpenTracing equivalent will cause the checks to fail - def green_2(): - with ot_tracer.start_active_span("greenlet.worker2") as scope: - scope.span.set_tag("worker_id", "2") - gevent.sleep(0.01) - - gevent.spawn(entrypoint).join() - - spans = self.pop_spans() - self._assert_spawn_multiple_greenlets(spans) - def test_ddtracerun(self): """ Regression test case for the following issue. diff --git a/tests/contrib/httplib/test_httplib.py b/tests/contrib/httplib/test_httplib.py index 7a2f4a07463..867afad7687 100644 --- a/tests/contrib/httplib/test_httplib.py +++ b/tests/contrib/httplib/test_httplib.py @@ -18,7 +18,6 @@ from ddtrace.ext import http from ddtrace.internal.constants import _HTTPLIB_NO_TRACE_REQUEST from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_span_http_status_code from tests.utils import override_global_tracer @@ -529,37 +528,6 @@ def test_urllib_request_opener(self): self.assertEqual(span.get_tag("span.kind"), "client") self.assertEqual(span.get_tag("out.host"), "localhost") - def test_httplib_request_get_request_ot(self): - """OpenTracing version of test with same name.""" - ot_tracer = init_tracer("my_svc", self.tracer) - - with ot_tracer.start_active_span("ot_span"): - conn = self.get_http_connection(SOCKET) - with contextlib.closing(conn): - conn.request("GET", "/status/200") - resp = conn.getresponse() - self.assertEqual(self.to_str(resp.read()), "") - self.assertEqual(resp.status, 200) - - spans = self.pop_spans() - self.assertEqual(len(spans), 2) - ot_span, dd_span = spans - - # confirm the parenting - self.assertEqual(ot_span.parent_id, None) - self.assertEqual(dd_span.parent_id, ot_span.span_id) - - self.assertEqual(ot_span.service, "my_svc") - self.assertEqual(ot_span.name, "ot_span") - - self.assert_is_not_measured(dd_span) - self.assertEqual(dd_span.span_type, "http") - self.assertEqual(dd_span.name, self.SPAN_NAME) - self.assertEqual(dd_span.error, 0) - assert dd_span.get_tag("http.method") == "GET" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_tag("http.url") == URL_200 - def test_httplib_bad_url(self): conn = self.get_http_connection("DNE", "80") with contextlib.closing(conn): diff --git a/tests/contrib/mysql/test_mysql.py b/tests/contrib/mysql/test_mysql.py index a071731d470..edb7fed2076 100644 --- a/tests/contrib/mysql/test_mysql.py +++ b/tests/contrib/mysql/test_mysql.py @@ -6,7 +6,6 @@ from ddtrace.contrib.internal.mysql.patch import unpatch from tests.contrib import shared_tests from tests.contrib.config import MYSQL_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_dict_issuperset from tests.utils import assert_is_measured @@ -253,93 +252,6 @@ def test_query_proc(self): ) assert span.get_tag("sql.query") is None - def test_simple_query_ot(self): - """OpenTracing version of test_simple_query.""" - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 2 - - ot_span, dd_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "mysql" - assert dd_span.name == "mysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == 3306 - assert_dict_issuperset( - dd_span.get_tags(), - { - "out.host": "127.0.0.1", - "db.name": "test", - "db.system": "mysql", - "db.user": "test", - "component": "mysql", - "span.kind": "client", - }, - ) - - def test_simple_query_ot_fetchall(self): - """OpenTracing version of test_simple_query.""" - with self.override_config("mysql", dict(trace_fetch_methods=True)): - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 3 - - ot_span, dd_span, fetch_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "mysql" - assert dd_span.name == "mysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == 3306 - assert_dict_issuperset( - dd_span.get_tags(), - { - "out.host": "127.0.0.1", - "db.name": "test", - "db.system": "mysql", - "db.user": "test", - "component": "mysql", - "span.kind": "client", - }, - ) - - assert fetch_span.name == "mysql.query.fetchall" - def test_commit(self): conn, tracer = self._get_conn_tracer() conn.commit() diff --git a/tests/contrib/mysqldb/test_mysqldb.py b/tests/contrib/mysqldb/test_mysqldb.py index 344e42c46ad..82c99afd968 100644 --- a/tests/contrib/mysqldb/test_mysqldb.py +++ b/tests/contrib/mysqldb/test_mysqldb.py @@ -7,7 +7,6 @@ from ddtrace.contrib.internal.mysqldb.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from tests.contrib import shared_tests -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_dict_issuperset from tests.utils import assert_is_measured @@ -323,89 +322,6 @@ def test_query_proc(self): ) assert span.get_tag("sql.query") is None - def test_simple_query_ot(self): - """OpenTracing version of test_simple_query.""" - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "mysql" - assert dd_span.name == "mysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == 3306 - assert_dict_issuperset( - dd_span.get_tags(), - { - "out.host": "127.0.0.1", - "db.name": "test", - "db.system": "mysql", - "db.user": "test", - "component": "mysqldb", - "span.kind": "client", - }, - ) - - def test_simple_query_ot_fetchall(self): - """OpenTracing version of test_simple_query.""" - with self.override_config("mysqldb", dict(trace_fetch_methods=True)): - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 3 - ot_span, dd_span, fetch_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "mysql" - assert dd_span.name == "mysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == 3306 - assert_dict_issuperset( - dd_span.get_tags(), - { - "out.host": "127.0.0.1", - "db.name": "test", - "db.system": "mysql", - "db.user": "test", - "component": "mysqldb", - "span.kind": "client", - }, - ) - - assert fetch_span.name == "mysql.query.fetchall" - def test_commit(self): conn, tracer = self._get_conn_tracer() diff --git a/tests/contrib/psycopg/test_psycopg.py b/tests/contrib/psycopg/test_psycopg.py index 987890dbbd7..b433bcb68f3 100644 --- a/tests/contrib/psycopg/test_psycopg.py +++ b/tests/contrib/psycopg/test_psycopg.py @@ -14,7 +14,6 @@ from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.internal.utils.version import parse_version from tests.contrib.config import POSTGRES_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import snapshot @@ -142,47 +141,6 @@ def test_psycopg3_connection_with_string(self): Pin.get_from(conn)._clone(service="postgres", tracer=self.tracer).onto(conn) self.assert_conn_is_traced(conn, "postgres") - def test_opentracing_propagation(self): - # ensure OpenTracing plays well with our integration - query = """SELECT 'tracing'""" - - db = self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - cursor.execute(query) - rows = cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - (dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"),), - ) - assert_is_measured(self.get_spans()[1]) - self.reset() - - with self.override_config("psycopg", dict(trace_fetch_methods=True)): - db = self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - cursor.execute(query) - rows = cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - ( - dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"), - dict(name="postgres.query.fetchall", resource=query, service="postgres", error=0, span_type="sql"), - ), - ) - assert_is_measured(self.get_spans()[1]) - def test_cursor_ctx_manager(self): # ensure cursors work with context managers # https://github.com/DataDog/dd-trace-py/issues/228 diff --git a/tests/contrib/psycopg/test_psycopg_async.py b/tests/contrib/psycopg/test_psycopg_async.py index b4778e0693a..a21dc2d794c 100644 --- a/tests/contrib/psycopg/test_psycopg_async.py +++ b/tests/contrib/psycopg/test_psycopg_async.py @@ -10,7 +10,6 @@ from ddtrace.contrib.internal.psycopg.patch import unpatch from tests.contrib.asyncio.utils import AsyncioTestCase from tests.contrib.config import POSTGRES_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import assert_is_measured @@ -127,47 +126,6 @@ async def assert_conn_is_traced_async(self, db, service): self.assertIsNone(root.get_tag("sql.query")) self.reset() - async def test_opentracing_propagation(self): - # ensure OpenTracing plays well with our integration - query = """SELECT 'tracing'""" - - db = await self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - await cursor.execute(query) - rows = await cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - (dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"),), - ) - assert_is_measured(self.get_spans()[1]) - self.reset() - - with self.override_config("psycopg", dict(trace_fetch_methods=True)): - db = await self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - await cursor.execute(query) - rows = await cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - ( - dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"), - dict(name="postgres.query.fetchall", resource=query, service="postgres", error=0, span_type="sql"), - ), - ) - assert_is_measured(self.get_spans()[1]) - async def test_cursor_ctx_manager(self): # ensure cursors work with context managers # https://github.com/DataDog/dd-trace-py/issues/228 diff --git a/tests/contrib/psycopg2/test_psycopg.py b/tests/contrib/psycopg2/test_psycopg.py index 209de02c880..10051da0cff 100644 --- a/tests/contrib/psycopg2/test_psycopg.py +++ b/tests/contrib/psycopg2/test_psycopg.py @@ -13,7 +13,6 @@ from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.internal.utils.version import parse_version from tests.contrib.config import POSTGRES_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import snapshot @@ -148,47 +147,6 @@ def test_psycopg2_connection_with_string(self): Pin.get_from(conn)._clone(service="postgres", tracer=self.tracer).onto(conn) self.assert_conn_is_traced(conn, "postgres") - def test_opentracing_propagation(self): - # ensure OpenTracing plays well with our integration - query = """SELECT 'tracing'""" - - db = self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - cursor.execute(query) - rows = cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - (dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"),), - ) - assert_is_measured(self.get_spans()[1]) - self.reset() - - with self.override_config("psycopg", dict(trace_fetch_methods=True)): - db = self._get_conn() - ot_tracer = init_tracer("psycopg-svc", self.tracer) - - with ot_tracer.start_active_span("db.access"): - cursor = db.cursor() - cursor.execute(query) - rows = cursor.fetchall() - - self.assertEqual(rows, [("tracing",)]) - - self.assert_structure( - dict(name="db.access", service="psycopg-svc"), - ( - dict(name="postgres.query", resource=query, service="postgres", error=0, span_type="sql"), - dict(name="postgres.query.fetchall", resource=query, service="postgres", error=0, span_type="sql"), - ), - ) - assert_is_measured(self.get_spans()[1]) - @skipIf(PSYCOPG2_VERSION < (2, 5), "context manager not available in psycopg2==2.4") def test_cursor_ctx_manager(self): # ensure cursors work with context managers diff --git a/tests/contrib/pylibmc/test.py b/tests/contrib/pylibmc/test.py index da5823fc6b3..91242bbe871 100644 --- a/tests/contrib/pylibmc/test.py +++ b/tests/contrib/pylibmc/test.py @@ -12,7 +12,6 @@ from ddtrace.contrib.internal.pylibmc.patch import unpatch from ddtrace.ext import memcached from tests.contrib.config import MEMCACHED_CONFIG as cfg -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured @@ -78,33 +77,6 @@ def test_incr_decr(self): resources = sorted(s.resource for s in spans) assert expected_resources == resources - def test_incr_decr_ot(self): - """OpenTracing version of test_incr_decr.""" - client, tracer = self.get_client() - ot_tracer = init_tracer("memcached", tracer) - - start = time.time() - with ot_tracer.start_active_span("mc_ops"): - client.set("a", 1) - client.incr("a", 2) - client.decr("a", 1) - v = client.get("a") - assert v == 2 - end = time.time() - - # verify spans - spans = tracer.pop() - ot_span = spans[0] - - assert ot_span.name == "mc_ops" - - for s in spans[1:]: - assert s.parent_id == ot_span.span_id - self._verify_cache_span(s, start, end) - expected_resources = sorted(["get", "set", "incr", "decr"]) - resources = sorted(s.resource for s in spans[1:]) - assert expected_resources == resources - def test_clone(self): # ensure cloned connections are traced as well. client, tracer = self.get_client() diff --git a/tests/contrib/pymongo/test.py b/tests/contrib/pymongo/test.py index 236fa582910..ccabc218ce9 100644 --- a/tests/contrib/pymongo/test.py +++ b/tests/contrib/pymongo/test.py @@ -10,7 +10,6 @@ from ddtrace.contrib.internal.pymongo.patch import patch from ddtrace.contrib.internal.pymongo.patch import unpatch from ddtrace.ext import SpanTypes -from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured @@ -298,67 +297,6 @@ def test_insert_find(self): assert spans[-1].resource == 'find teams {"name": "?"}' assert spans[-1].get_tag("mongodb.query") == '{"name": "?"}' - def test_update_ot(self): - """OpenTracing version of test_update.""" - tracer, client = self.get_tracer_and_client() - ot_tracer = init_tracer("mongo_svc", tracer) - - with ot_tracer.start_active_span("mongo_op"): - db = client["testdb"] - db.drop_collection("songs") - input_songs = [ - {"name": "Powderfinger", "artist": "Neil"}, - {"name": "Harvest", "artist": "Neil"}, - {"name": "Suzanne", "artist": "Leonard"}, - {"name": "Partisan", "artist": "Leonard"}, - ] - db.songs.insert_many(input_songs) - result = db.songs.update_many( - {"artist": "Neil"}, - {"$set": {"artist": "Shakey"}}, - ) - - assert result.matched_count == 2 - assert result.modified_count == 2 - - # ensure all is traced. - spans = tracer.pop() - assert spans, spans - assert len(spans) == 7 - - ot_span = spans[0] - assert ot_span.parent_id is None - assert ot_span.name == "mongo_op" - assert ot_span.service == "mongo_svc" - - # remove pymongo.get_socket and pymongo.checkout spans - spans = [s for s in spans if s.name == "pymongo.cmd"] - assert len(spans) == 3 - for span in spans: - # ensure all the of the common metadata is set - assert_is_measured(span) - assert span.service == "pymongo" - assert span.span_type == "mongodb" - assert span.get_tag("component") == "pymongo" - assert span.get_tag("span.kind") == "client" - assert span.get_tag("db.system") == "mongodb" - assert span.get_tag("mongodb.collection") == "songs" - assert span.get_tag("mongodb.db") == "testdb" - assert span.get_tag("out.host") - assert span.get_metric("network.destination.port") - - expected_resources = set( - [ - "drop songs", - 'update songs {"artist": "?"}', - "insert songs", - "pymongo.get_socket", - "pymongo.checkout", - ] - ) - - assert {s.resource for s in spans[1:]}.issubset(expected_resources) - def test_rowcount(self): tracer, client = self.get_tracer_and_client() db = client["testdb"] diff --git a/tests/contrib/pymysql/test_pymysql.py b/tests/contrib/pymysql/test_pymysql.py index 762f55bed08..8fb5ef78621 100644 --- a/tests/contrib/pymysql/test_pymysql.py +++ b/tests/contrib/pymysql/test_pymysql.py @@ -6,7 +6,6 @@ from ddtrace.contrib.internal.pymysql.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from tests.contrib import shared_tests -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_dict_issuperset from tests.utils import assert_is_measured @@ -249,73 +248,6 @@ def test_query_proc(self): meta.update(self.DB_INFO) assert_dict_issuperset(span.get_tags(), meta) - def test_simple_query_ot(self): - """OpenTracing version of test_simple_query.""" - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "pymysql" - assert dd_span.name == "pymysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == MYSQL_CONFIG.get("port") - meta = {} - meta.update(self.DB_INFO) - assert_dict_issuperset(dd_span.get_tags(), meta) - - def test_simple_query_ot_fetchall(self): - """OpenTracing version of test_simple_query.""" - with self.override_config("pymysql", dict(trace_fetch_methods=True)): - conn, tracer = self._get_conn_tracer() - - ot_tracer = init_tracer("mysql_svc", tracer) - with ot_tracer.start_active_span("mysql_op"): - cursor = conn.cursor() - cursor.execute("SELECT 1") - rows = cursor.fetchall() - assert len(rows) == 1 - - spans = tracer.pop() - assert len(spans) == 3 - ot_span, dd_span, fetch_span = spans - - # confirm parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.service == "mysql_svc" - assert ot_span.name == "mysql_op" - - assert_is_measured(dd_span) - assert dd_span.service == "pymysql" - assert dd_span.name == "pymysql.query" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.get_metric("network.destination.port") == MYSQL_CONFIG.get("port") - meta = {} - meta.update(self.DB_INFO) - assert_dict_issuperset(dd_span.get_tags(), meta) - - assert fetch_span.name == "pymysql.query.fetchall" - def test_commit(self): conn, tracer = self._get_conn_tracer() diff --git a/tests/contrib/pyramid/utils.py b/tests/contrib/pyramid/utils.py index 3dec370d500..2f663258ac5 100644 --- a/tests/contrib/pyramid/utils.py +++ b/tests/contrib/pyramid/utils.py @@ -11,7 +11,6 @@ from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code -from ...opentracer.utils import init_tracer from .app import create_app @@ -273,33 +272,3 @@ def test_include_conflicts(self): self.app.get("/404", status=404) spans = self.pop_spans() assert len(spans) == 1 - - def test_200_ot(self): - """OpenTracing version of test_200.""" - ot_tracer = init_tracer("pyramid_svc", self.tracer) - - with ot_tracer.start_active_span("pyramid_get"): - res = self.app.get("/", status=200) - assert b"idx" in res.body - - spans = self.pop_spans() - assert len(spans) == 2 - - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "pyramid_get" - assert ot_span.service == "pyramid_svc" - - assert_is_measured(dd_span) - assert dd_span.service == "foobar" - assert dd_span.resource == "GET index" - assert dd_span.error == 0 - assert dd_span.span_type == "web" - assert dd_span.get_tag("http.method") == "GET" - assert_span_http_status_code(dd_span, 200) - assert dd_span.get_tag(http.URL) == "http://localhost/" - assert dd_span.get_tag("pyramid.route.name") == "index" diff --git a/tests/contrib/redis/test_redis.py b/tests/contrib/redis/test_redis.py index 31fe287fdf1..f2a42b83d19 100644 --- a/tests/contrib/redis/test_redis.py +++ b/tests/contrib/redis/test_redis.py @@ -4,12 +4,10 @@ import pytest import redis -import ddtrace from ddtrace._trace.pin import Pin from ddtrace.contrib.internal.redis.patch import patch from ddtrace.contrib.internal.redis.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import snapshot @@ -238,39 +236,6 @@ def test_patch_unpatch(self): assert spans, spans assert len(spans) == 1 - def test_opentracing(self): - """Ensure OpenTracing works with redis.""" - ot_tracer = init_tracer("redis_svc", self.tracer) - - with ot_tracer.start_active_span("redis_get"): - us = self.r.get("cheese") - assert us is None - - spans = self.get_spans() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "redis_get" - assert ot_span.service == "redis_svc" - - self.assert_is_measured(dd_span) - assert dd_span.service == "redis" - assert dd_span.name == "redis.command" - assert dd_span.span_type == "redis" - assert dd_span.error == 0 - assert dd_span.get_metric("out.redis_db") == 0 - assert dd_span.get_tag("out.host") == "localhost" - assert dd_span.get_tag("redis.raw_command") == "GET cheese" - assert dd_span.get_tag("component") == "redis" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.get_tag("db.system") == "redis" - assert dd_span.get_metric("redis.args_length") == 2 - assert dd_span.resource == "GET" - def test_redis_rowcount_all_keys_valid(self): self.r.set("key1", "value1") @@ -540,20 +505,6 @@ def test_patch_unpatch(self): assert spans, spans assert len(spans) == 1 - @snapshot() - def test_opentracing(self): - """Ensure OpenTracing works with redis.""" - writer = ddtrace.tracer._span_aggregator.writer - ot_tracer = init_tracer("redis_svc", ddtrace.tracer) - # FIXME: OpenTracing always overrides the hostname/port and creates a new - # writer so we have to reconfigure with the previous one - ddtrace.tracer._span_aggregator.writer = writer - ddtrace.tracer._recreate() - - with ot_tracer.start_active_span("redis_get"): - us = self.r.get("cheese") - assert us is None - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) @snapshot() def test_user_specified_service(self): diff --git a/tests/contrib/requests/test_requests.py b/tests/contrib/requests/test_requests.py index e1a8d2672d6..f7f7c24bc07 100644 --- a/tests/contrib/requests/test_requests.py +++ b/tests/contrib/requests/test_requests.py @@ -18,7 +18,6 @@ from ddtrace.contrib.internal.requests.patch import unpatch from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -580,35 +579,6 @@ def test_global_config_service(self): spans = self.pop_spans() assert spans[0].service == "override" - def test_200_ot(self): - """OpenTracing version of test_200.""" - - ot_tracer = init_tracer("requests_svc", self.tracer) - - with ot_tracer.start_active_span("requests_get"): - out = self.session.get(URL_200) - assert out.status_code == 200 - - # validation - spans = self.pop_spans() - assert len(spans) == 2 - - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "requests_get" - assert ot_span.service == "requests_svc" - - assert_is_measured(dd_span) - assert dd_span.get_tag(http.METHOD) == "GET" - assert_span_http_status_code(dd_span, 200) - assert dd_span.error == 0 - assert dd_span.span_type == "http" - assert dd_span.resource == "GET /status/200" - def test_request_and_response_headers(self): # Disabled when not configured self.session.get(URL_200, headers={"my-header": "my_value"}) diff --git a/tests/contrib/snowflake/test_snowflake.py b/tests/contrib/snowflake/test_snowflake.py index f4995255b4c..67ce242b8e4 100644 --- a/tests/contrib/snowflake/test_snowflake.py +++ b/tests/contrib/snowflake/test_snowflake.py @@ -9,8 +9,6 @@ from ddtrace._trace.pin import Pin from ddtrace.contrib.internal.snowflake.patch import patch from ddtrace.contrib.internal.snowflake.patch import unpatch -from ddtrace.trace import tracer -from tests.opentracer.utils import init_tracer from tests.utils import override_config from tests.utils import snapshot @@ -93,13 +91,6 @@ def client(): yield ctx -@contextlib.contextmanager -def ot_trace(): - ot = init_tracer("snowflake_svc", tracer) - with ot.start_active_span("snowflake_op"): - yield - - @snapshot() @req_mock.activate def test_snowflake_fetchone(client): @@ -224,72 +215,6 @@ def test_snowflake_executemany_insert(client): assert res.rowcount == 2 -@snapshot() -@req_mock.activate -def test_snowflake_ot_fetchone(client): - add_snowflake_query_response( - rowtype=["TEXT"], - rows=[("4.30.2",)], - ) - with ot_trace(): - with client.cursor() as cur: - res = cur.execute("select current_version();") - assert res == cur - assert cur.fetchone() == ("4.30.2",) - - -@snapshot() -@req_mock.activate -def test_snowflake_ot_fetchall(client): - add_snowflake_query_response( - rowtype=["TEXT"], - rows=[("4.30.2",)], - ) - with ot_trace(): - with client.cursor() as cur: - res = cur.execute("select current_version();") - assert res == cur - assert cur.fetchall() == [("4.30.2",)] - - -@snapshot() -@req_mock.activate -def test_snowflake_ot_fetchall_multiple_rows(client): - add_snowflake_query_response( - rowtype=["TEXT", "TEXT"], - rows=[("1a", "1b"), ("2a", "2b")], - ) - with ot_trace(): - with client.cursor() as cur: - res = cur.execute("select a, b from t;") - assert res == cur - assert cur.fetchall() == [ - ("1a", "1b"), - ("2a", "2b"), - ] - - -@snapshot() -@req_mock.activate -def test_snowflake_ot_executemany_insert(client): - add_snowflake_query_response( - rowtype=[], - rows=[], - total=2, - ) - with ot_trace(): - with client.cursor() as cur: - res = cur.executemany( - "insert into t (a, b) values (%s, %s);", - [ - ("1a", "1b"), - ("2a", "2b"), - ], - ) - assert res == cur - assert res.rowcount == 2 - - @pytest.mark.snapshot() @pytest.mark.parametrize( "service_schema", diff --git a/tests/contrib/sqlalchemy/mixins.py b/tests/contrib/sqlalchemy/mixins.py index 18b180db2d3..031c9ca3aea 100644 --- a/tests/contrib/sqlalchemy/mixins.py +++ b/tests/contrib/sqlalchemy/mixins.py @@ -9,7 +9,6 @@ from sqlalchemy.orm import sessionmaker from ddtrace.contrib.internal.sqlalchemy.engine import trace_engine -from tests.opentracer.utils import init_tracer Base = declarative_base() @@ -166,36 +165,3 @@ def test_engine_connect_execute(self): assert span.span_type == "sql" assert span.error == 0 assert span.duration > 0 - - def test_opentracing(self): - """Ensure that sqlalchemy works with the opentracer.""" - ot_tracer = init_tracer("sqlalch_svc", self.tracer) - - with ot_tracer.start_active_span("sqlalch_op"): - with self.connection() as conn: - rows = conn.execute(text("SELECT * FROM players")).fetchall() - assert len(rows) == 0 - - traces = self.pop_traces() - # trace composition - assert len(traces) == 1 - assert len(traces[0]) == 2 - ot_span, dd_span = traces[0] - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "sqlalch_op" - assert ot_span.service == "sqlalch_svc" - - # span fields - assert dd_span.name == "{}.query".format(self.VENDOR) - assert dd_span.service == self.SERVICE - assert dd_span.resource == "SELECT * FROM players" - assert dd_span.get_tag("sql.db") == self.SQL_DB - assert dd_span.get_tag("component") == "sqlalchemy" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.span_type == "sql" - assert dd_span.error == 0 - assert dd_span.duration > 0 diff --git a/tests/contrib/sqlite3/test_sqlite3.py b/tests/contrib/sqlite3/test_sqlite3.py index e4b12d7b4e8..de2b18f72b4 100644 --- a/tests/contrib/sqlite3/test_sqlite3.py +++ b/tests/contrib/sqlite3/test_sqlite3.py @@ -21,7 +21,6 @@ from ddtrace.contrib.internal.sqlite3.patch import patch from ddtrace.contrib.internal.sqlite3.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured from tests.utils import assert_is_not_measured @@ -206,47 +205,6 @@ def test_sqlite_fetchmany_is_traced(self): self.assertIsNone(fetchmany_span.get_tag("sql.query")) self.assertEqual(fetchmany_span.get_tag("db.system"), "sqlite") - def test_sqlite_ot(self): - """Ensure sqlite works with the opentracer.""" - ot_tracer = init_tracer("sqlite_svc", self.tracer) - - # Ensure we can run a query and it's correctly traced - q = "select * from sqlite_master" - with ot_tracer.start_active_span("sqlite_op"): - db = sqlite3.connect(":memory:") - pin = Pin.get_from(db) - assert pin - pin._clone(tracer=self.tracer).onto(db) - cursor = db.execute(q) - rows = cursor.fetchall() - assert not rows - - self.assert_structure( - dict(name="sqlite_op", service="sqlite_svc"), - (dict(name="sqlite.query", service="sqlite", span_type="sql", resource=q, error=0),), - ) - assert_is_measured(self.get_spans()[1]) - self.reset() - - with self.override_config("sqlite", dict(trace_fetch_methods=True)): - with ot_tracer.start_active_span("sqlite_op"): - db = sqlite3.connect(":memory:") - pin = Pin.get_from(db) - assert pin - pin._clone(tracer=self.tracer).onto(db) - cursor = db.execute(q) - rows = cursor.fetchall() - assert not rows - - self.assert_structure( - dict(name="sqlite_op", service="sqlite_svc"), - ( - dict(name="sqlite.query", span_type="sql", resource=q, error=0), - dict(name="sqlite.query.fetchall", span_type="sql", resource=q, error=0), - ), - ) - assert_is_measured(self.get_spans()[1]) - def test_commit(self): connection = self._given_a_traced_connection(self.tracer) connection.commit() diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index d3ac8289e95..35e712500fa 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -908,15 +908,6 @@ suites: - tests/snapshots/tests.opentelemetry.* runner: riot snapshot: true - opentracer: - parallelism: 1 - paths: - - '@bootstrap' - - '@core' - - '@tracing' - - '@opentracer' - - tests/opentracer/* - runner: riot protobuf: parallelism: 1 paths: diff --git a/tests/contrib/tornado/test_tornado_web.py b/tests/contrib/tornado/test_tornado_web.py index 642246aa244..cbb4d2c7785 100644 --- a/tests/contrib/tornado/test_tornado_web.py +++ b/tests/contrib/tornado/test_tornado_web.py @@ -1,6 +1,3 @@ -import pytest -import tornado - from ddtrace import config from ddtrace.constants import _ORIGIN_KEY from ddtrace.constants import _SAMPLING_PRIORITY_KEY @@ -8,7 +5,6 @@ from ddtrace.constants import USER_KEEP from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data from tests.utils import assert_is_measured from tests.utils import assert_span_http_status_code @@ -385,46 +381,6 @@ def test_propagation(self): assert request_span.get_tag("component") == "tornado" assert request_span.get_tag("span.kind") == "server" - # Opentracing support depends on new AsyncioScopeManager - # See: https://github.com/opentracing/opentracing-python/pull/118 - @pytest.mark.skipif( - tornado.version_info >= (5, 0), reason="Opentracing ScopeManager not available for Tornado >= 5" - ) - def test_success_handler_ot(self): - """OpenTracing version of test_success_handler.""" - from opentracing.scope_managers.tornado import TornadoScopeManager - - ot_tracer = init_tracer("tornado_svc", self.tracer, scope_manager=TornadoScopeManager()) - - with ot_tracer.start_active_span("tornado_op"): - response = self.fetch("/success/") - assert 200 == response.code - - traces = self.pop_traces() - assert 1 == len(traces) - assert 2 == len(traces[0]) - # dd_span will start and stop before the ot_span finishes - ot_span, dd_span = traces[0] - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "tornado_op" - assert ot_span.service == "tornado_svc" - - assert_is_measured(dd_span) - assert "tornado-web" == dd_span.service - assert "tornado.request" == dd_span.name - assert "web" == dd_span.span_type - assert "tests.contrib.tornado.web.app.SuccessHandler" == dd_span.resource - assert "GET" == dd_span.get_tag("http.method") - assert_span_http_status_code(dd_span, 200) - assert self.get_url("/success/") == dd_span.get_tag(http.URL) - assert 0 == dd_span.error - assert dd_span.get_tag("component") == "tornado" - assert dd_span.get_tag("span.kind") == "server" - class TestNoPropagationTornadoWebViaSetting(TornadoTestCase): """ diff --git a/tests/contrib/urllib3/test_urllib3.py b/tests/contrib/urllib3/test_urllib3.py index 01d3b87893c..c1a6434c0bc 100644 --- a/tests/contrib/urllib3/test_urllib3.py +++ b/tests/contrib/urllib3/test_urllib3.py @@ -14,7 +14,6 @@ from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.settings.asm import config as asm_config from tests.contrib.config import HTTPBIN_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import TracerTestCase from tests.utils import snapshot @@ -399,34 +398,6 @@ def test_split_by_domain_includes_port(self): assert s.error == 1 assert s.service == "httpbin.org:8000" - def test_200_ot(self): - """OpenTracing version of test_200.""" - - ot_tracer = init_tracer("urllib3_svc", self.tracer) - - with ot_tracer.start_active_span("urllib3_get"): - out = self.http.request("GET", URL_200) - assert out.status == 200 - - spans = self.pop_spans() - assert len(spans) == 2 - - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "urllib3_get" - assert ot_span.service == "urllib3_svc" - - assert dd_span.get_tag(http.METHOD) == "GET" - assert dd_span.get_tag(http.STATUS_CODE) == "200" - assert dd_span.get_tag("component") == "urllib3" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.error == 0 - assert dd_span.span_type == "http" - def test_request_and_response_headers(self): """Tests the headers are added as tag when the headers are whitelisted""" self.http.request("GET", URL_200, headers={"my-header": "my_value"}) diff --git a/tests/contrib/valkey/test_valkey.py b/tests/contrib/valkey/test_valkey.py index 867f435939e..447ae932771 100644 --- a/tests/contrib/valkey/test_valkey.py +++ b/tests/contrib/valkey/test_valkey.py @@ -4,12 +4,10 @@ import pytest import valkey -import ddtrace from ddtrace._trace.pin import Pin from ddtrace.contrib.internal.valkey.patch import patch from ddtrace.contrib.internal.valkey.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import snapshot @@ -238,39 +236,6 @@ def test_patch_unpatch(self): assert spans, spans assert len(spans) == 1 - def test_opentracing(self): - """Ensure OpenTracing works with valkey.""" - ot_tracer = init_tracer("valkey_svc", self.tracer) - - with ot_tracer.start_active_span("valkey_get"): - us = self.r.get("cheese") - assert us is None - - spans = self.get_spans() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert ot_span.name == "valkey_get" - assert ot_span.service == "valkey_svc" - - self.assert_is_measured(dd_span) - assert dd_span.service == "valkey" - assert dd_span.name == "valkey.command" - assert dd_span.span_type == "valkey" - assert dd_span.error == 0 - assert dd_span.get_metric("out.valkey_db") == 0 - assert dd_span.get_tag("out.host") == "localhost" - assert dd_span.get_tag("valkey.raw_command") == "GET cheese" - assert dd_span.get_tag("component") == "valkey" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.get_tag("db.system") == "valkey" - assert dd_span.get_metric("valkey.args_length") == 2 - assert dd_span.resource == "GET" - def test_valkey_rowcount_all_keys_valid(self): self.r.set("key1", "value1") @@ -540,15 +505,6 @@ def test_patch_unpatch(self): assert spans, spans assert len(spans) == 1 - @snapshot() - def test_opentracing(self): - """Ensure OpenTracing works with valkey.""" - ot_tracer = init_tracer("valkey_svc", ddtrace.tracer) - - with ot_tracer.start_active_span("valkey_get"): - us = self.r.get("cheese") - assert us is None - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) @snapshot() def test_user_specified_service(self): diff --git a/tests/contrib/vertica/test_vertica.py b/tests/contrib/vertica/test_vertica.py index c65b19dd0fd..07c640965ee 100644 --- a/tests/contrib/vertica/test_vertica.py +++ b/tests/contrib/vertica/test_vertica.py @@ -12,7 +12,6 @@ from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.settings._config import _deepmerge from tests.contrib.config import VERTICA_CONFIG -from tests.opentracer.utils import init_tracer from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import assert_is_measured @@ -367,37 +366,6 @@ def test_copy(self): assert spans[1].name == "vertica.query" assert spans[1].resource == "COMMIT;" - def test_opentracing(self): - """Ensure OpenTracing works with vertica.""" - conn, cur = self.test_conn - - ot_tracer = init_tracer("vertica_svc", self.test_tracer) - - with ot_tracer.start_active_span("vertica_execute"): - cur.execute("INSERT INTO {} (a, b) VALUES (1, 'aa');".format(TEST_TABLE)) - conn.close() - - spans = self.test_tracer.pop() - assert len(spans) == 2 - ot_span, dd_span = spans - - # confirm the parenting - assert ot_span.parent_id is None - assert dd_span.parent_id == ot_span.span_id - - assert_is_measured(dd_span) - assert dd_span.service == "vertica" - assert dd_span.span_type == "sql" - assert dd_span.name == "vertica.query" - assert dd_span.get_metric("db.row_count") == -1 - query = "INSERT INTO test_table (a, b) VALUES (1, 'aa');" - assert dd_span.resource == query - assert dd_span.get_tag("out.host") == "127.0.0.1" - assert dd_span.get_tag("span.kind") == "client" - assert dd_span.get_metric("network.destination.port") == 5433 - assert dd_span.get_tag("db.system") == "vertica" - assert dd_span.get_tag("component") == "vertica" - @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc"), use_pytest=True) @pytest.mark.usefixtures("test_tracer", "test_conn") def test_user_specified_service_default(self): diff --git a/tests/contrib/yaaredis/test_yaaredis.py b/tests/contrib/yaaredis/test_yaaredis.py index fd9fb02c0f4..04ebc8d8709 100644 --- a/tests/contrib/yaaredis/test_yaaredis.py +++ b/tests/contrib/yaaredis/test_yaaredis.py @@ -9,7 +9,6 @@ from ddtrace._trace.pin import Pin from ddtrace.contrib.internal.yaaredis.patch import patch from ddtrace.contrib.internal.yaaredis.patch import unpatch -from tests.opentracer.utils import init_tracer from tests.utils import override_config from ..config import REDIS_CONFIG @@ -149,18 +148,6 @@ async def test_service_name_config(tracer, test_spans, traced_yaaredis): assert test_spans.spans[0].service == service -@pytest.mark.asyncio -async def test_opentracing(tracer, snapshot_context, traced_yaaredis): - """Ensure OpenTracing works with redis.""" - - with snapshot_context(): - pin = Pin.get_from(traced_yaaredis) - ot_tracer = init_tracer("redis_svc", pin.tracer) - - with ot_tracer.start_active_span("redis_get"): - await traced_yaaredis.get("cheese") - - @pytest.mark.parametrize( "service_schema", [ diff --git a/tests/opentracer/__init__.py b/tests/opentracer/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/opentracer/conftest.py b/tests/opentracer/conftest.py deleted file mode 100644 index 09a4dad886c..00000000000 --- a/tests/opentracer/conftest.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -pytest local plugin used to automatically make the following fixtures -available for all tests in this directory - -https://docs.pytest.org/en/latest/writing_plugins.html#testing-plugins -""" -import pytest - -from ddtrace.opentracer import Tracer as OTTracer -from ddtrace.opentracer import set_global_tracer -from tests.utils import DummyTracer -from tests.utils import TracerSpanContainer - - -@pytest.fixture() -def ot_tracer_factory(): - """Fixture which returns an opentracer ready to use for testing.""" - - def make_ot_tracer(service_name="my_svc", config=None, scope_manager=None, context_provider=None): - config = config or {} - tracer = OTTracer(service_name=service_name, config=config, scope_manager=scope_manager) - - # similar to how we test the ddtracer, use a dummy tracer - dd_tracer = DummyTracer() - if context_provider: - dd_tracer.context_provider = context_provider - - # attach the dummy tracer to the opentracer - tracer._dd_tracer = dd_tracer - return tracer - - return make_ot_tracer - - -@pytest.fixture() -def ot_tracer(ot_tracer_factory): - """Fixture for a default opentracer.""" - return ot_tracer_factory() - - -@pytest.fixture -def test_spans(ot_tracer): - container = TracerSpanContainer(ot_tracer._dd_tracer) - yield container - container.reset() - - -@pytest.fixture() -def global_tracer(ot_tracer): - """A function similar to one OpenTracing users would write to initialize - their OpenTracing tracer. - """ - set_global_tracer(ot_tracer) - - return ot_tracer - - -@pytest.fixture() -def dd_tracer(ot_tracer): - return ot_tracer._dd_tracer diff --git a/tests/opentracer/core/__init__.py b/tests/opentracer/core/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/opentracer/core/test_dd_compatibility.py b/tests/opentracer/core/test_dd_compatibility.py deleted file mode 100644 index c68b5ca6d6c..00000000000 --- a/tests/opentracer/core/test_dd_compatibility.py +++ /dev/null @@ -1,180 +0,0 @@ -import opentracing -from opentracing import Format - -import ddtrace -from ddtrace.opentracer.span_context import SpanContext - - -class TestTracerCompatibility(object): - """Ensure that our opentracer produces results in the underlying ddtracer.""" - - def test_ottracer_uses_global_ddtracer(self): - """Ensure that the opentracer will by default use the global ddtracer - as its underlying Datadog tracer. - """ - tracer = ddtrace.opentracer.Tracer() - assert tracer._dd_tracer is ddtrace.tracer - - def test_ot_dd_global_tracers(self, global_tracer): - """Ensure our test function opentracer_init() prep""" - ot_tracer = global_tracer - dd_tracer = global_tracer._dd_tracer - - # check all the global references - assert ot_tracer is opentracing.tracer - assert ot_tracer._dd_tracer is dd_tracer - assert dd_tracer is ddtrace.tracer - - def test_ot_dd_nested_trace(self, ot_tracer, dd_tracer, test_spans): - """Ensure intertwined usage of the opentracer and ddtracer.""" - - with ot_tracer.start_span("my_ot_span") as ot_span: - with dd_tracer.trace("my_dd_span") as dd_span: - pass - spans = test_spans.pop() - assert len(spans) == 2 - - # confirm the ordering - assert spans[1] is ot_span._dd_span - assert spans[0] is dd_span - - # check the parenting - assert spans[0].parent_id is None - assert spans[1].parent_id is None - - def test_dd_ot_nested_trace(self, ot_tracer, dd_tracer, test_spans): - """Ensure intertwined usage of the opentracer and ddtracer.""" - with dd_tracer.trace("my_dd_span") as dd_span: - with ot_tracer.start_span("my_ot_span") as ot_span: - pass - spans = test_spans.pop() - assert len(spans) == 2 - - # confirm the ordering - assert spans[0] is dd_span - assert spans[1] is ot_span._dd_span - - # check the parenting - assert spans[0].parent_id is None - assert spans[1].parent_id is spans[0].span_id - - def test_ot_dd_ot_dd_nested_trace(self, ot_tracer, dd_tracer, test_spans): - """Ensure intertwined usage of the opentracer and ddtracer.""" - with ot_tracer.start_active_span("ot_span") as ot_scope: - with dd_tracer.trace("dd_span") as dd_span: - with ot_tracer.start_active_span("ot_span2") as ot_scope2: - with dd_tracer.trace("dd_span2") as dd_span2: - pass - - spans = test_spans.pop() - assert len(spans) == 4 - - spans = {span.name: span for span in spans} - assert spans["ot_span"] == ot_scope.span._dd_span - assert spans["dd_span"] == dd_span - assert spans["ot_span2"] == ot_scope2.span._dd_span - assert spans["dd_span2"] == dd_span2 - - # check the parenting - assert spans["ot_span"].parent_id is None - assert spans["dd_span"].parent_id is spans["ot_span"].span_id - assert spans["ot_span2"].parent_id is spans["dd_span"].span_id - assert spans["dd_span2"].parent_id is spans["ot_span2"].span_id - - def test_ot_ot_dd_ot_dd_nested_trace_active(self, ot_tracer, dd_tracer, test_spans): - """Ensure intertwined usage of the opentracer and ddtracer.""" - with ot_tracer.start_active_span("my_ot_span") as ot_scope: - with ot_tracer.start_active_span("my_ot_span") as ot_scope2: - with dd_tracer.trace("my_dd_span") as dd_span: - with ot_tracer.start_active_span("my_ot_span") as ot_scope3: - with dd_tracer.trace("my_dd_span") as dd_span2: - pass - - spans = test_spans.pop() - assert len(spans) == 5 - - # confirm the ordering - assert spans[0] is ot_scope.span._dd_span - assert spans[1] is ot_scope2.span._dd_span - assert spans[2] is dd_span - assert spans[3] is ot_scope3.span._dd_span - assert spans[4] is dd_span2 - - # check the parenting - assert spans[0].parent_id is None - assert spans[1].parent_id == spans[0].span_id - assert spans[2].parent_id == spans[1].span_id - assert spans[3].parent_id == spans[2].span_id - assert spans[4].parent_id == spans[3].span_id - - def test_consecutive_trace(self, ot_tracer, dd_tracer, test_spans): - """Ensure consecutive usage of the opentracer and ddtracer.""" - with ot_tracer.start_active_span("my_ot_span") as ot_scope: - pass - - with dd_tracer.trace("my_dd_span") as dd_span: - pass - - with ot_tracer.start_active_span("my_ot_span") as ot_scope2: - pass - - with dd_tracer.trace("my_dd_span") as dd_span2: - pass - - spans = test_spans.pop() - assert len(spans) == 4 - - # confirm the ordering - assert spans[0] is ot_scope.span._dd_span - assert spans[1] is dd_span - assert spans[2] is ot_scope2.span._dd_span - assert spans[3] is dd_span2 - - # check the parenting - assert spans[0].parent_id is None - assert spans[1].parent_id is None - assert spans[2].parent_id is None - assert spans[3].parent_id is None - - def test_ddtrace_wrapped_fn(self, ot_tracer, dd_tracer, test_spans): - """Ensure ddtrace wrapped functions work with the opentracer""" - - @dd_tracer.wrap() - def fn(): - with ot_tracer.start_span("ot_span_inner"): - pass - - with ot_tracer.start_active_span("ot_span_outer"): - fn() - - spans = test_spans.pop() - assert len(spans) == 3 - - # confirm the ordering - assert spans[0].name == "ot_span_outer" - assert spans[1].name == "tests.opentracer.core.test_dd_compatibility.fn" - assert spans[2].name == "ot_span_inner" - - # check the parenting - assert spans[0].parent_id is None - assert spans[1].parent_id is spans[0].span_id - assert spans[2].parent_id is spans[1].span_id - - def test_distributed_trace_propagation(self, ot_tracer, dd_tracer, test_spans): - """Ensure that a propagated span context is properly activated.""" - span_ctx = SpanContext(trace_id=123, span_id=456) - carrier = {} - ot_tracer.inject(span_ctx, Format.HTTP_HEADERS, carrier) - - # extract should activate the span so that a subsequent start_span - # will inherit from the propagated span context - ot_tracer.extract(Format.HTTP_HEADERS, carrier) - - with dd_tracer.trace("test") as span: - pass - - assert span.parent_id == 456 - assert span.trace_id == 123 - - spans = test_spans.pop() - assert len(spans) == 1 diff --git a/tests/opentracer/core/test_span.py b/tests/opentracer/core/test_span.py deleted file mode 100644 index ea2fc3bbbc1..00000000000 --- a/tests/opentracer/core/test_span.py +++ /dev/null @@ -1,163 +0,0 @@ -import pytest - -from ddtrace.opentracer.span import Span -from tests.utils import DummyTracer - - -@pytest.fixture -def nop_tracer(): - from ddtrace.opentracer import Tracer - - tracer = Tracer(service_name="mysvc", config={}) - # use the same test tracer used by the primary tests - tracer._tracer = DummyTracer() - return tracer - - -@pytest.fixture -def nop_span_ctx(): - from ddtrace.constants import AUTO_KEEP - from ddtrace.opentracer.span_context import SpanContext - - return SpanContext(sampling_priority=AUTO_KEEP) - - -@pytest.fixture -def nop_span(nop_tracer, nop_span_ctx): - return Span(nop_tracer, nop_span_ctx, "my_op_name") - - -class TestSpan(object): - """Test the Datadog OpenTracing Span implementation.""" - - def test_init(self, nop_tracer, nop_span_ctx): - """Very basic test for skeleton code""" - span = Span(nop_tracer, nop_span_ctx, "my_op_name") - assert not span.finished - - def test_tags(self, nop_span): - """Set a tag and get it back.""" - r = nop_span.set_tag("test", 23) - assert nop_span._get_metric("test") == 23 - assert r is nop_span - - def test_set_baggage(self, nop_span): - """Test setting baggage.""" - r = nop_span.set_baggage_item("test", 23) - assert r is nop_span - - r = nop_span.set_baggage_item("1", 1).set_baggage_item("2", 2) - assert r is nop_span - - def test_get_baggage(self, nop_span): - """Test setting and getting baggage.""" - # test a single item - nop_span.set_baggage_item("test", 23) - assert int(nop_span.get_baggage_item("test")) == 23 - - # test multiple items - nop_span.set_baggage_item("1", "1").set_baggage_item("2", 2) - assert int(nop_span.get_baggage_item("test")) == 23 - assert nop_span.get_baggage_item("1") == "1" - assert int(nop_span.get_baggage_item("2")) == 2 - - def test_log_kv(self, nop_span): - """Ensure logging values doesn't break anything.""" - # just log a bunch of values - nop_span.log_kv({"myval": 2}) - nop_span.log_kv({"myval2": 3}) - nop_span.log_kv({"myval3": 5}) - nop_span.log_kv({"myval": 2}) - - def test_log_dd_kv(self, nop_span): - """Ensure keys that can be handled by our impl. are indeed handled.""" - import traceback - - from ddtrace.constants import ERROR_MSG - from ddtrace.constants import ERROR_STACK - from ddtrace.constants import ERROR_TYPE - - stack_trace = str(traceback.format_stack()) - nop_span.log_kv( - { - "event": "error", - "error": 3, - "message": "my error message", - "stack": stack_trace, - } - ) - - # Ensure error flag is set... - assert nop_span._dd_span.error - # ...and that error tags are set with the correct key - assert nop_span._get_tag(ERROR_STACK) == stack_trace - assert nop_span._get_tag(ERROR_MSG) == "my error message" - assert nop_span._get_metric(ERROR_TYPE) == 3 - - def test_operation_name(self, nop_span): - """Sanity check for setting the operation name.""" - # just try setting the operation name - r = nop_span.set_operation_name("new_op_name") - assert nop_span._dd_span.name == "new_op_name" - assert r is nop_span - - def test_context_manager(self, nop_span): - """Test the span context manager.""" - import time - - assert not nop_span.finished - # run the context manager but since the span has not been added - # to the span context, we will not get any traces - with nop_span: - time.sleep(0.005) - - # span should be finished when the context manager exits - assert nop_span.finished - - # there should be no traces (see above comment) - spans = nop_span.tracer._tracer.pop() - assert len(spans) == 0 - - def test_immutable_span_context(self, nop_span): - """Ensure span contexts are immutable.""" - before_ctx = nop_span._context - nop_span.set_baggage_item("key", "value") - after_ctx = nop_span._context - # should be different contexts - assert before_ctx is not after_ctx - - -class TestSpanCompatibility(object): - """Ensure our opentracer spans features correspond to datadog span features.""" - - def test_set_tag(self, nop_span): - nop_span.set_tag("test", 2) - assert nop_span._get_metric("test") == 2 - - def test_tag_resource_name(self, nop_span): - nop_span.set_tag("resource.name", "myresource") - assert nop_span._dd_span.resource == "myresource" - - def test_tag_span_type(self, nop_span): - nop_span.set_tag("span.type", "db") - assert nop_span._dd_span.span_type == "db" - - def test_tag_service_name(self, nop_span): - nop_span.set_tag("service.name", "mysvc234") - assert nop_span._dd_span.service == "mysvc234" - - def test_tag_db_statement(self, nop_span): - nop_span.set_tag("db.statement", "SELECT * FROM USERS") - assert nop_span._dd_span.resource == "SELECT * FROM USERS" - - def test_tag_peer_hostname(self, nop_span): - nop_span.set_tag("peer.hostname", "peername") - assert nop_span._dd_span.get_tag("out.host") == "peername" - - def test_tag_peer_port(self, nop_span): - nop_span.set_tag("peer.port", 55555) - assert nop_span._get_metric("network.destination.port") == 55555 - - def test_tag_sampling_priority(self, nop_span): - nop_span.set_tag("sampling.priority", "2") - assert nop_span._dd_span.context.sampling_priority == "2" diff --git a/tests/opentracer/core/test_span_context.py b/tests/opentracer/core/test_span_context.py deleted file mode 100644 index 2c7038fe327..00000000000 --- a/tests/opentracer/core/test_span_context.py +++ /dev/null @@ -1,38 +0,0 @@ -from ddtrace.opentracer.span_context import SpanContext - - -class TestSpanContext(object): - def test_init(self): - """Make sure span context creation is fine.""" - span_ctx = SpanContext() - assert span_ctx - - def test_baggage(self): - """Ensure baggage passed is the resulting baggage of the span context.""" - baggage = { - "some": "stuff", - } - - span_ctx = SpanContext(baggage=baggage) - - assert span_ctx.baggage == baggage - - def test_with_baggage_item(self): - """Should allow immutable extension of new span contexts.""" - baggage = { - "1": 1, - } - - first_ctx = SpanContext(baggage=baggage) - - second_ctx = first_ctx.with_baggage_item("2", 2) - - assert "2" not in first_ctx.baggage - assert second_ctx.baggage is not first_ctx.baggage - - def test_span_context_immutable_baggage(self): - """Ensure that two different span contexts do not share baggage.""" - ctx1 = SpanContext() - ctx1.set_baggage_item("test", 3) - ctx2 = SpanContext() - assert "test" not in ctx2._baggage diff --git a/tests/opentracer/core/test_tracer.py b/tests/opentracer/core/test_tracer.py deleted file mode 100644 index 5d9f11ab74f..00000000000 --- a/tests/opentracer/core/test_tracer.py +++ /dev/null @@ -1,585 +0,0 @@ -import time - -import mock -import opentracing -from opentracing import Format -from opentracing import InvalidCarrierException -from opentracing import UnsupportedFormatException -from opentracing import child_of -import pytest - -import ddtrace -from ddtrace.constants import AUTO_KEEP -from ddtrace.opentracer import Tracer -from ddtrace.opentracer import set_global_tracer -from ddtrace.opentracer.span_context import SpanContext -from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID -from ddtrace.settings.exceptions import ConfigException - - -class TestTracerConfig(object): - def test_config(self): - """Test the configuration of the tracer""" - config = {"enabled": True} - tracer = Tracer(service_name="myservice", config=config) - - assert tracer._service_name == "myservice" - assert tracer._dd_tracer.enabled is True - - def test_no_service_name(self): - """A service_name should be generated if one is not provided.""" - tracer = Tracer() - assert tracer._service_name in {"pytest.py", "pytest", "__main__.py"} - - def test_multiple_tracer_configs(self): - """Ensure that a tracer config is a copy of the passed config.""" - config = {"enabled": True} - - tracer1 = Tracer(service_name="serv1", config=config) - assert tracer1._service_name == "serv1" - - config["enabled"] = False - tracer2 = Tracer(service_name="serv2", config=config) - - # Ensure tracer1's config was not mutated - assert tracer1._service_name == "serv1" - assert tracer2._service_name == "serv2" - - def test_invalid_config_key(self): - """A config with an invalid key should raise a ConfigException.""" - - config = {"enabeld": False} # codespell:ignore - - # No debug flag should not raise an error - tracer = Tracer(service_name="mysvc", config=config) - - # With debug flag should raise an error - config["debug"] = True - with pytest.raises(ConfigException) as ce_info: - tracer = Tracer(config=config) - assert "enabeld" in str(ce_info) # codespell:ignore - assert tracer is not None - - # Test with multiple incorrect keys - config["setttings"] = {} - with pytest.raises(ConfigException) as ce_info: - tracer = Tracer(service_name="mysvc", config=config) - assert ["enabeld", "setttings"] in str(ce_info) # codespell:ignore - assert tracer is not None - - def test_global_tags(self): - """Global tags should be passed from the opentracer to the tracer.""" - config = { - "global_tags": { - "tag1": "value1", - "tag2": 2, - }, - } - - tracer = Tracer(service_name="mysvc", config=config) - with tracer.start_span("myop") as span: - # global tags should be attached to generated all datadog spans - assert span._dd_span.get_tag("tag1") == "value1" - assert span._dd_span.get_metric("tag2") == 2 - - with tracer.start_span("myop2") as span2: - assert span2._dd_span.get_tag("tag1") == "value1" - assert span2._dd_span.get_metric("tag2") == 2 - - -class TestTracer(object): - def test_start_span(self, ot_tracer, test_spans): - """Start and finish a span.""" - with ot_tracer.start_span("myop") as span: - pass - - # span should be finished when the context manager exits - assert span.finished - - spans = test_spans.get_spans() - assert len(spans) == 1 - - def test_start_span_references(self, ot_tracer, test_spans): - """Start a span using references.""" - - with ot_tracer.start_span("one", references=[child_of()]): - pass - - spans = test_spans.pop() - assert spans[0].parent_id is None - - root = ot_tracer.start_active_span("root") - # create a child using a parent reference that is not the context parent - with ot_tracer.start_active_span("one"): - with ot_tracer.start_active_span("two", references=[child_of(root.span)]): - pass - root.close() - - spans = test_spans.pop() - assert spans[1].parent_id == spans[0].span_id - assert spans[2].parent_id == spans[0].span_id - - def test_start_span_custom_start_time(self, ot_tracer): - """Start a span with a custom start time.""" - t = 100 - with mock.patch("ddtrace._trace.span.Time.time_ns") as time: - time.return_value = 102 * 1e9 - with ot_tracer.start_span("myop", start_time=t) as span: - pass - - assert span._dd_span.start == t - assert span._dd_span.duration == 2 - - def test_start_span_with_spancontext(self, ot_tracer, test_spans): - """Start and finish a span using a span context as the child_of - reference. - """ - with ot_tracer.start_span("myop") as span: - with ot_tracer.start_span("myop", child_of=span.context) as span2: - pass - - # span should be finished when the context manager exits - assert span.finished - assert span2.finished - - spans = test_spans.pop() - assert len(spans) == 2 - - # ensure proper parenting - assert spans[1].parent_id is spans[0].span_id - - def test_start_span_with_tags(self, ot_tracer): - """Create a span with initial tags.""" - tags = {"key": "value", "key2": "value2"} - with ot_tracer.start_span("myop", tags=tags) as span: - pass - - assert span._dd_span.get_tag("key") == "value" - assert span._dd_span.get_tag("key2") == "value2" - - def test_start_span_with_resource_name_tag(self, ot_tracer): - """Create a span with the tag to set the resource name""" - tags = {"resource.name": "value", "key2": "value2"} - with ot_tracer.start_span("myop", tags=tags) as span: - pass - - # Span resource name should be set to tag value, and should not get set as - # a tag on the underlying span. - assert span._dd_span.resource == "value" - assert span._dd_span.get_tag("resource.name") is None - - # Other tags are set as normal - assert span._dd_span.get_tag("key2") == "value2" - - def test_start_active_span_multi_child(self, ot_tracer, test_spans): - """Start and finish multiple child spans. - This should ensure that child spans can be created 2 levels deep. - """ - with ot_tracer.start_active_span("myfirstop") as scope1: - time.sleep(0.009) - with ot_tracer.start_active_span("mysecondop") as scope2: - time.sleep(0.007) - with ot_tracer.start_active_span("mythirdop") as scope3: - time.sleep(0.005) - - # spans should be finished when the context manager exits - assert scope1.span.finished - assert scope2.span.finished - assert scope3.span.finished - - spans = test_spans.pop() - - # check spans are captured in the trace - assert scope1.span._dd_span is spans[0] - assert scope2.span._dd_span is spans[1] - assert scope3.span._dd_span is spans[2] - - # ensure proper parenting - assert spans[1].parent_id is spans[0].span_id - assert spans[2].parent_id is spans[1].span_id - - # sanity check a lower bound on the durations - assert spans[0].duration >= 0.009 + 0.007 + 0.005 - assert spans[1].duration >= 0.007 + 0.005 - assert spans[2].duration >= 0.005 - - def test_start_active_span_multi_child_siblings(self, ot_tracer, test_spans): - """Start and finish multiple span at the same level. - This should test to ensure a parent can have multiple child spans at the - same level. - """ - with ot_tracer.start_active_span("myfirstop") as scope1: - time.sleep(0.009) - with ot_tracer.start_active_span("mysecondop") as scope2: - time.sleep(0.007) - with ot_tracer.start_active_span("mythirdop") as scope3: - time.sleep(0.005) - - # spans should be finished when the context manager exits - assert scope1.span.finished - assert scope2.span.finished - assert scope3.span.finished - - spans = test_spans.pop() - - # check spans are captured in the trace - assert scope1.span._dd_span is spans[0] - assert scope2.span._dd_span is spans[1] - assert scope3.span._dd_span is spans[2] - - # ensure proper parenting - assert spans[1].parent_id is spans[0].span_id - assert spans[2].parent_id is spans[0].span_id - - # sanity check a lower bound on the durations - assert spans[0].duration >= 0.009 + 0.007 + 0.005 - assert spans[1].duration >= 0.007 - assert spans[2].duration >= 0.005 - - def test_start_span_manual_child_of(self, ot_tracer, test_spans): - """Start spans without using a scope manager. - Spans should be created without parents since there will be no call - for the active span. - """ - root = ot_tracer.start_span("zero") - - with ot_tracer.start_span("one", child_of=root): - with ot_tracer.start_span("two", child_of=root): - with ot_tracer.start_span("three", child_of=root): - pass - root.finish() - - spans = test_spans.pop() - - assert spans[0].parent_id is None - # ensure each child span is a child of root - assert spans[1].parent_id is root._dd_span.span_id - assert spans[2].parent_id is root._dd_span.span_id - assert spans[3].parent_id is root._dd_span.span_id - assert spans[0].trace_id == spans[1].trace_id and spans[1].trace_id == spans[2].trace_id - - def test_start_span_no_active_span(self, ot_tracer, test_spans): - """Start spans without using a scope manager. - Spans should be created without parents since there will be no call - for the active span. - """ - with ot_tracer.start_span("one", ignore_active_span=True): - with ot_tracer.start_span("two", ignore_active_span=True): - pass - with ot_tracer.start_span("three", ignore_active_span=True): - pass - - spans = test_spans.pop() - - # ensure each span does not have a parent - assert spans[0].parent_id is None - assert spans[1].parent_id is None - assert spans[2].parent_id is None - # and that each span is a new trace - assert ( - spans[0].trace_id != spans[1].trace_id - and spans[1].trace_id != spans[2].trace_id - and spans[0].trace_id != spans[2].trace_id - ) - - def test_start_active_span_child_finish_after_parent(self, ot_tracer, test_spans): - """Start a child span and finish it after its parent.""" - span1 = ot_tracer.start_active_span("one").span - span2 = ot_tracer.start_active_span("two").span - span1.finish() - time.sleep(0.005) - span2.finish() - - spans = test_spans.pop() - assert len(spans) == 2 - assert spans[0].parent_id is None - assert spans[1].parent_id is span1._dd_span.span_id - assert spans[1].duration > spans[0].duration - - def test_start_span_multi_intertwined(self, ot_tracer, test_spans): - """Start multiple spans at the top level intertwined. - Alternate calling between two traces. - """ - import threading - - # synchronize threads with a threading event object - event = threading.Event() - - def trace_one(): - _id = 11 - with ot_tracer.start_active_span(str(_id)): - _id += 1 - with ot_tracer.start_active_span(str(_id)): - _id += 1 - with ot_tracer.start_active_span(str(_id)): - pass - event.set() - - def trace_two(): - _id = 21 - event.wait() - with ot_tracer.start_active_span(str(_id)): - _id += 1 - with ot_tracer.start_active_span(str(_id)): - _id += 1 - with ot_tracer.start_active_span(str(_id)): - pass - - # the ordering should be - # t1.span1/t2.span1, t2.span2, t1.span2, t1.span3, t2.span3 - t1 = threading.Thread(target=trace_one) - t2 = threading.Thread(target=trace_two) - - t1.start() - t2.start() - # wait for threads to finish - t1.join() - t2.join() - - spans = test_spans.pop() - - # trace_one will finish before trace_two so its spans should be written - # before the spans from trace_two, let's confirm this - assert spans[0].name == "11" - assert spans[1].name == "12" - assert spans[2].name == "13" - assert spans[3].name == "21" - assert spans[4].name == "22" - assert spans[5].name == "23" - - # next let's ensure that each span has the correct parent: - # trace_one - assert spans[0].parent_id is None - assert spans[1].parent_id is spans[0].span_id - assert spans[2].parent_id is spans[1].span_id - # trace_two - assert spans[3].parent_id is None - assert spans[4].parent_id is spans[3].span_id - assert spans[5].parent_id is spans[3].span_id - - # finally we should ensure that the trace_ids are reasonable - # trace_one - assert spans[0].trace_id == spans[1].trace_id and spans[1].trace_id == spans[2].trace_id - # traces should be independent - assert spans[2].trace_id != spans[3].trace_id - # trace_two - assert spans[3].trace_id == spans[4].trace_id and spans[4].trace_id == spans[5].trace_id - - def test_start_active_span(self, ot_tracer, test_spans): - with ot_tracer.start_active_span("one") as scope: - pass - - assert scope.span._dd_span.name == "one" - assert scope.span.finished - spans = test_spans.pop() - assert spans - - def test_start_active_span_finish_on_close(self, ot_tracer, test_spans): - with ot_tracer.start_active_span("one", finish_on_close=False) as scope: - pass - - assert scope.span._dd_span.name == "one" - assert not scope.span.finished - spans = test_spans.pop() - assert not spans - scope.span.finish() - - def test_start_active_span_nested(self, ot_tracer): - """Test the active span of multiple nested calls of start_active_span.""" - with ot_tracer.start_active_span("one") as outer_scope: - assert ot_tracer.active_span == outer_scope.span - with ot_tracer.start_active_span("two") as inner_scope: - assert ot_tracer.active_span == inner_scope.span - with ot_tracer.start_active_span("three") as innest_scope: # why isn't it innest? innermost so verbose - assert ot_tracer.active_span == innest_scope.span - with ot_tracer.start_active_span("two") as inner_scope: - assert ot_tracer.active_span == inner_scope.span - assert ot_tracer.active_span == outer_scope.span - assert ot_tracer.active_span is None - - def test_start_active_span_trace(self, ot_tracer, test_spans): - """Test the active span of multiple nested calls of start_active_span.""" - with ot_tracer.start_active_span("one") as outer_scope: - outer_scope.span.set_tag("outer", 2) - with ot_tracer.start_active_span("two") as inner_scope: - inner_scope.span.set_tag("inner", 3) - with ot_tracer.start_active_span("two") as inner_scope: - inner_scope.span.set_tag("inner", 3) - with ot_tracer.start_active_span("three") as innest_scope: - innest_scope.span.set_tag("innerest", 4) - - spans = test_spans.pop() - - assert spans[0].parent_id is None - assert spans[1].parent_id is spans[0].span_id - assert spans[2].parent_id is spans[0].span_id - assert spans[3].parent_id is spans[2].span_id - - def test_interleave(self, dd_tracer, ot_tracer, test_spans): - with ot_tracer.start_active_span("ot_root_1", ignore_active_span=True): - with dd_tracer.trace("dd_child"): - with ot_tracer.start_active_span("ot_child_1"): - pass - with ot_tracer.start_active_span("ot_child_2"): - pass - - spans = test_spans.pop() - assert len(spans) == 4 - assert spans[0].name == "ot_root_1" and spans[0].parent_id is None - assert spans[1].name == "dd_child" and spans[1].parent_id == spans[0].span_id - assert spans[2].name == "ot_child_1" and spans[2].parent_id == spans[1].span_id - assert spans[3].name == "ot_child_2" and spans[3].parent_id == spans[0].span_id - - def test_active_span(self, ot_tracer, test_spans): - with ot_tracer._dd_tracer.trace("dd") as span: - assert ot_tracer.active_span is not None - assert ot_tracer.active_span._dd_span is span - - -@pytest.fixture -def nop_span_ctx(): - return SpanContext(sampling_priority=AUTO_KEEP) - - -class TestTracerSpanContextPropagation(object): - """Test the injection and extraction of a span context from a tracer.""" - - def test_invalid_format(self, ot_tracer, nop_span_ctx): - """An invalid format should raise an UnsupportedFormatException.""" - # test inject - with pytest.raises(UnsupportedFormatException): - ot_tracer.inject(nop_span_ctx, None, {}) - - # test extract - with pytest.raises(UnsupportedFormatException): - ot_tracer.extract(None, {}) - - def test_inject_invalid_carrier(self, ot_tracer, nop_span_ctx): - """Only dicts should be supported as a carrier.""" - with pytest.raises(InvalidCarrierException): - ot_tracer.inject(nop_span_ctx, Format.HTTP_HEADERS, None) - - def test_extract_invalid_carrier(self, ot_tracer): - """Only dicts should be supported as a carrier.""" - with pytest.raises(InvalidCarrierException): - ot_tracer.extract(Format.HTTP_HEADERS, None) - - def test_http_headers_base(self, ot_tracer): - """extract should undo inject for http headers.""" - - span_ctx = SpanContext(trace_id=123, span_id=456) - carrier = {} - - ot_tracer.inject(span_ctx, Format.HTTP_HEADERS, carrier) - assert len(carrier.keys()) > 0 - - ext_span_ctx = ot_tracer.extract(Format.HTTP_HEADERS, carrier) - assert ext_span_ctx._dd_context.trace_id == 123 - assert ext_span_ctx._dd_context.span_id == 456 - - def test_http_headers_baggage(self, ot_tracer): - """extract should undo inject for http headers.""" - span_ctx = SpanContext(trace_id=123, span_id=456, baggage={"test": 4, "test2": "string"}) - carrier = {} - - ot_tracer.inject(span_ctx, Format.HTTP_HEADERS, carrier) - assert len(carrier.keys()) > 0 - - ext_span_ctx = ot_tracer.extract(Format.HTTP_HEADERS, carrier) - assert ext_span_ctx._dd_context.trace_id == 123 - assert ext_span_ctx._dd_context.span_id == 456 - assert ext_span_ctx.baggage == span_ctx.baggage - - def test_empty_propagated_context(self, ot_tracer): - """An empty propagated context should not raise a - SpanContextCorruptedException when extracted. - """ - carrier = {} - ot_tracer.extract(Format.HTTP_HEADERS, carrier) - - def test_text(self, ot_tracer): - """extract should undo inject for http headers""" - span_ctx = SpanContext(trace_id=123, span_id=456, baggage={"test": 4, "test2": "string"}) - carrier = {} - - ot_tracer.inject(span_ctx, Format.TEXT_MAP, carrier) - assert len(carrier.keys()) > 0 - - ext_span_ctx = ot_tracer.extract(Format.TEXT_MAP, carrier) - assert ext_span_ctx._dd_context.trace_id == 123 - assert ext_span_ctx._dd_context.span_id == 456 - assert ext_span_ctx.baggage == span_ctx.baggage - - def test_corrupted_propagated_context(self, ot_tracer): - """Corrupted context should raise a SpanContextCorruptedException.""" - span_ctx = SpanContext(trace_id=123, span_id=456, baggage={"test": 4, "test2": "string"}) - carrier = {} - - ot_tracer.inject(span_ctx, Format.TEXT_MAP, carrier) - assert len(carrier.keys()) > 0 - - # manually alter a key in the carrier baggage - del carrier[HTTP_HEADER_TRACE_ID] - corrupted_key = HTTP_HEADER_TRACE_ID[2:] - carrier[corrupted_key] = 123 - - ot_tracer.extract(Format.TEXT_MAP, carrier) - - def test_immutable_span_context(self, ot_tracer): - """Span contexts should be immutable.""" - with ot_tracer.start_span("root") as root: - ctx_before = root.context - root.set_baggage_item("test", 2) - assert ctx_before is not root.context - with ot_tracer.start_span("child") as level1: - with ot_tracer.start_span("child") as level2: - pass - assert root.context is not level1.context - assert level2.context is not level1.context - assert level2.context is not root.context - - def test_inherited_baggage(self, ot_tracer): - """Baggage should be inherited by child spans.""" - with ot_tracer.start_active_span("root") as root: - # this should be passed down to the child - root.span.set_baggage_item("root", 1) - root.span.set_baggage_item("root2", 1) - with ot_tracer.start_active_span("child") as level1: - level1.span.set_baggage_item("level1", 1) - with ot_tracer.start_active_span("child") as level2: - level2.span.set_baggage_item("level2", 1) - # ensure immutability - assert level1.span.context is not root.span.context - assert level2.span.context is not level1.span.context - - # level1 should have inherited the baggage of root - assert level1.span.get_baggage_item("root") - assert level1.span.get_baggage_item("root2") - - # level2 should have inherited the baggage of both level1 and level2 - assert level2.span.get_baggage_item("root") - assert level2.span.get_baggage_item("root2") - assert level2.span.get_baggage_item("level1") - assert level2.span.get_baggage_item("level2") - - -class TestTracerCompatibility(object): - """Ensure that our opentracer produces results in the underlying datadog tracer.""" - - def test_required_dd_fields(self): - """Ensure required fields needed for successful tracing are possessed - by the underlying datadog tracer. - """ - # a service name is required - tracer = Tracer("service") - with tracer.start_span("my_span") as span: - assert span._dd_span.service - - -def test_set_global_tracer(): - """Sanity check for set_global_tracer""" - my_tracer = Tracer("service") - set_global_tracer(my_tracer) - - assert opentracing.tracer is my_tracer - assert ddtrace.tracer is my_tracer._dd_tracer diff --git a/tests/opentracer/core/test_utils.py b/tests/opentracer/core/test_utils.py deleted file mode 100644 index 37c9e9dd305..00000000000 --- a/tests/opentracer/core/test_utils.py +++ /dev/null @@ -1,17 +0,0 @@ -from opentracing.scope_managers import ThreadLocalScopeManager -from opentracing.scope_managers.asyncio import AsyncioScopeManager - -import ddtrace -from ddtrace.opentracer.utils import get_context_provider_for_scope_manager - - -class TestOpentracerUtils(object): - def test_get_context_provider_for_scope_manager_thread(self): - scope_manager = ThreadLocalScopeManager() - ctx_prov = get_context_provider_for_scope_manager(scope_manager) - assert isinstance(ctx_prov, ddtrace._trace.provider.DefaultContextProvider) - - def test_get_context_provider_for_asyncio_scope_manager(self): - scope_manager = AsyncioScopeManager() - ctx_prov = get_context_provider_for_scope_manager(scope_manager) - assert isinstance(ctx_prov, ddtrace._trace.provider.DefaultContextProvider) diff --git a/tests/opentracer/test_tracer_asyncio.py b/tests/opentracer/test_tracer_asyncio.py deleted file mode 100644 index 35ece48c126..00000000000 --- a/tests/opentracer/test_tracer_asyncio.py +++ /dev/null @@ -1,143 +0,0 @@ -import asyncio - -import pytest - -from ddtrace.constants import ERROR_MSG - - -@pytest.mark.asyncio -def test_trace_coroutine(test_spans): - # it should use the task context when invoked in a coroutine - with test_spans.tracer.start_span("coroutine"): - pass - - traces = test_spans.pop_traces() - - assert len(traces) == 1 - assert len(traces[0]) == 1 - assert traces[0][0].name == "coroutine" - - -@pytest.mark.asyncio -async def test_trace_multiple_coroutines(ot_tracer, test_spans): - # if multiple coroutines have nested tracing, they must belong - # to the same trace - - async def coro(): - # another traced coroutine - with ot_tracer.start_active_span("coroutine_2"): - return 42 - - with ot_tracer.start_active_span("coroutine_1"): - value = await coro() - - # the coroutine has been called correctly - assert value == 42 - # a single trace has been properly reported - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - assert traces[0][0].name == "coroutine_1" - assert traces[0][1].name == "coroutine_2" - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id - - -@pytest.mark.asyncio -async def test_exception(ot_tracer, test_spans): - async def f1(): - with ot_tracer.start_span("f1"): - raise Exception("f1 error") - - with pytest.raises(Exception, match="f1 error"): - await f1() - - traces = test_spans.pop_traces() - assert len(traces) == 1 - spans = traces[0] - assert len(spans) == 1 - span = spans[0] - assert span.error == 1 - assert span.get_tag(ERROR_MSG) == "f1 error" - assert "Exception: f1 error" in span.get_tag("error.stack") - - -@pytest.mark.asyncio -async def test_trace_multiple_calls(ot_tracer, test_spans): - # create multiple futures so that we expect multiple - # traces instead of a single one (helper not used) - async def coro(): - # another traced coroutine - with ot_tracer.start_span("coroutine"): - await asyncio.sleep(0.01) - - futures = [asyncio.ensure_future(coro()) for x in range(10)] - for future in futures: - await future - - traces = test_spans.pop_traces() - - assert len(traces) == 10 - assert len(traces[0]) == 1 - assert traces[0][0].name == "coroutine" - - -@pytest.mark.asyncio -async def test_trace_multiple_coroutines_ot_dd(ot_tracer): - """ - Ensure we can trace from opentracer to ddtracer across asyncio - context switches. - """ - - # if multiple coroutines have nested tracing, they must belong - # to the same trace - async def coro(): - # another traced coroutine - with ot_tracer._dd_tracer.trace("coroutine_2"): - return 42 - - with ot_tracer.start_active_span("coroutine_1"): - value = await coro() - - # the coroutine has been called correctly - assert value == 42 - # a single trace has been properly reported - traces = ot_tracer._dd_tracer.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - assert traces[0][0].name == "coroutine_1" - assert traces[0][1].name == "coroutine_2" - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id - - -@pytest.mark.asyncio -async def test_trace_multiple_coroutines_dd_ot(ot_tracer): - """ - Ensure we can trace from ddtracer to opentracer across asyncio - context switches. - """ - - # if multiple coroutines have nested tracing, they must belong - # to the same trace - async def coro(): - # another traced coroutine - with ot_tracer.start_span("coroutine_2"): - return 42 - - with ot_tracer._dd_tracer.trace("coroutine_1"): - value = await coro() - - # the coroutine has been called correctly - assert value == 42 - # a single trace has been properly reported - traces = ot_tracer._dd_tracer.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - assert traces[0][0].name == "coroutine_1" - assert traces[0][1].name == "coroutine_2" - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id diff --git a/tests/opentracer/test_tracer_gevent.py b/tests/opentracer/test_tracer_gevent.py deleted file mode 100644 index 320b39ee997..00000000000 --- a/tests/opentracer/test_tracer_gevent.py +++ /dev/null @@ -1,193 +0,0 @@ -import gevent -from opentracing.scope_managers.gevent import GeventScopeManager -import pytest - -from ddtrace.contrib.internal.gevent.patch import patch -from ddtrace.contrib.internal.gevent.patch import unpatch - - -@pytest.fixture() -def ot_tracer(ot_tracer_factory): - """Fixture providing an opentracer configured for gevent usage.""" - # patch gevent - patch() - yield ot_tracer_factory("gevent_svc", {}, GeventScopeManager()) - # unpatch gevent - unpatch() - - -class TestTracerGevent(object): - """Converted Gevent tests for the regular tracer. - - Ensures that greenlets are properly traced when using - the opentracer. - """ - - def test_no_threading(self, ot_tracer): - with ot_tracer.start_span("span") as span: - span.set_tag("tag", "value") - - assert span.finished - - def test_greenlets(self, ot_tracer, test_spans): - def f(): - with ot_tracer.start_span("f") as span: - gevent.sleep(0.04) - span.set_tag("f", "yes") - - def g(): - with ot_tracer.start_span("g") as span: - gevent.sleep(0.03) - span.set_tag("g", "yes") - - with ot_tracer.start_active_span("root"): - gevent.joinall([gevent.spawn(f), gevent.spawn(g)]) - - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 3 - - def test_trace_greenlet(self, ot_tracer, test_spans): - # a greenlet can be traced using the trace API - def greenlet(): - with ot_tracer.start_span("greenlet"): - pass - - gevent.spawn(greenlet).join() - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 1 - assert traces[0][0].name == "greenlet" - - def test_trace_later_greenlet(self, ot_tracer, test_spans): - # a greenlet can be traced using the trace API - def greenlet(): - with ot_tracer.start_span("greenlet"): - pass - - gevent.spawn_later(0.01, greenlet).join() - traces = test_spans.pop_traces() - - assert len(traces) == 1 - assert len(traces[0]) == 1 - assert traces[0][0].name == "greenlet" - - def test_trace_concurrent_calls(self, ot_tracer, test_spans): - # create multiple futures so that we expect multiple - # traces instead of a single one - def greenlet(): - with ot_tracer.start_span("greenlet"): - gevent.sleep(0.01) - - jobs = [gevent.spawn(greenlet) for x in range(100)] - gevent.joinall(jobs) - - traces = test_spans.pop_traces() - - assert len(traces) == 100 - assert len(traces[0]) == 1 - assert traces[0][0].name == "greenlet" - - def test_trace_concurrent_spawn_later_calls(self, ot_tracer, test_spans): - # create multiple futures so that we expect multiple - # traces instead of a single one, even if greenlets - # are delayed - def greenlet(): - with ot_tracer.start_span("greenlet"): - gevent.sleep(0.01) - - jobs = [gevent.spawn_later(0.01, greenlet) for x in range(100)] - gevent.joinall(jobs) - - traces = test_spans.pop_traces() - assert len(traces) == 100 - assert len(traces[0]) == 1 - assert traces[0][0].name == "greenlet" - - -class TestTracerGeventCompatibility(object): - """Ensure the opentracer works in tandem with the ddtracer and gevent.""" - - def test_trace_spawn_multiple_greenlets_multiple_traces_ot_parent(self, ot_tracer, dd_tracer, test_spans): - """ - Copy of gevent test with the same name but testing with mixed usage of - the opentracer and datadog tracers. - - Uses an opentracer span as the parent span. - """ - - # multiple greenlets must be part of the same trace - def entrypoint(): - with ot_tracer.start_active_span("greenlet.main"): - jobs = [gevent.spawn(green_1), gevent.spawn(green_2)] - gevent.joinall(jobs) - - def green_1(): - with dd_tracer.trace("greenlet.worker") as span: - span.set_tag("worker_id", "1") - gevent.sleep(0.01) - - def green_2(): - with ot_tracer.start_span("greenlet.worker") as span: - span.set_tag("worker_id", "2") - gevent.sleep(0.01) - - gevent.spawn(entrypoint).join() - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 3 - parent_span = traces[0][0] - worker_1 = traces[0][1] - worker_2 = traces[0][2] - # check spans data and hierarchy - assert parent_span.name == "greenlet.main" - assert worker_1.get_tag("worker_id") == "1" - assert worker_1.name == "greenlet.worker" - assert worker_1.resource == "greenlet.worker" - assert worker_1.parent_id == parent_span.span_id - assert worker_2.get_tag("worker_id") == "2" - assert worker_2.name == "greenlet.worker" - assert worker_2.resource == "greenlet.worker" - assert worker_2.parent_id == parent_span.span_id - - def test_trace_spawn_multiple_greenlets_multiple_traces_dd_parent(self, ot_tracer, dd_tracer, test_spans): - """ - Copy of gevent test with the same name but testing with mixed usage of - the opentracer and datadog tracers. - - Uses an opentracer span as the parent span. - """ - - # multiple greenlets must be part of the same trace - def entrypoint(): - with dd_tracer.trace("greenlet.main"): - jobs = [gevent.spawn(green_1), gevent.spawn(green_2)] - gevent.joinall(jobs) - - def green_1(): - with ot_tracer.start_span("greenlet.worker") as span: - span.set_tag("worker_id", "1") - gevent.sleep(0.01) - - def green_2(): - with dd_tracer.trace("greenlet.worker") as span: - span.set_tag("worker_id", "2") - gevent.sleep(0.01) - - gevent.spawn(entrypoint).join() - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 3 - parent_span = traces[0][0] - worker_1 = traces[0][1] - worker_2 = traces[0][2] - # check spans data and hierarchy - assert parent_span.name == "greenlet.main" - assert worker_1.get_tag("worker_id") == "1" - assert worker_1.name == "greenlet.worker" - assert worker_1.resource == "greenlet.worker" - assert worker_1.parent_id == parent_span.span_id - assert worker_2.get_tag("worker_id") == "2" - assert worker_2.name == "greenlet.worker" - assert worker_2.resource == "greenlet.worker" - assert worker_2.parent_id == parent_span.span_id diff --git a/tests/opentracer/test_tracer_tornado.py b/tests/opentracer/test_tracer_tornado.py deleted file mode 100644 index d81541e0a52..00000000000 --- a/tests/opentracer/test_tracer_tornado.py +++ /dev/null @@ -1,30 +0,0 @@ -from opentracing.scope_managers.tornado import TornadoScopeManager -import pytest - - -@pytest.fixture() -def ot_tracer(ot_tracer_factory): - """Fixture providing an opentracer configured for tornado usage.""" - yield ot_tracer_factory("tornado_svc", {}, TornadoScopeManager()) - - -class TestTracerTornado(object): - """ - Since the ScopeManager is provided by OpenTracing we should simply test - whether it exists and works for a very simple use-case. - """ - - def test_sanity(self, ot_tracer, test_spans): - with ot_tracer.start_active_span("one"): - with ot_tracer.start_active_span("two"): - pass - - traces = test_spans.pop_traces() - assert len(traces) == 1 - assert len(traces[0]) == 2 - assert traces[0][0].name == "one" - assert traces[0][1].name == "two" - - # the parenting is correct - assert traces[0][0] == traces[0][1]._parent - assert traces[0][0].trace_id == traces[0][1].trace_id diff --git a/tests/opentracer/utils.py b/tests/opentracer/utils.py deleted file mode 100644 index 85b84865ad8..00000000000 --- a/tests/opentracer/utils.py +++ /dev/null @@ -1,11 +0,0 @@ -from ddtrace.opentracer import Tracer - - -def init_tracer(service_name, dd_tracer, scope_manager=None): - """A method that emulates what a user of OpenTracing would call to - initialize a Datadog opentracer. - - It accepts a Datadog tracer that should be the same one used for testing. - """ - ot_tracer = Tracer(service_name, scope_manager=scope_manager, _dd_tracer=dd_tracer) - return ot_tracer diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 977f6685e79..8e24ecaf5d6 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -90,8 +90,6 @@ components: opentelemetry: - ddtrace/opentelemetry/* - ddtrace/internal/opentelemetry/* - opentracer: - - ddtrace/opentracer/* profiling: - ddtrace/profiling/* - ddtrace/internal/datadog/profiling/* diff --git a/tests/telemetry/test_telemetry_metrics_e2e.py b/tests/telemetry/test_telemetry_metrics_e2e.py index 8eed0b55426..03bf27b9682 100644 --- a/tests/telemetry/test_telemetry_metrics_e2e.py +++ b/tests/telemetry/test_telemetry_metrics_e2e.py @@ -141,69 +141,3 @@ def test_span_creation_and_finished_metrics_otel(test_agent_session, ddtrace_run assert metrics_sf[0]["metric"] == "spans_finished" assert metrics_sf[0]["tags"] == ["integration_name:otel"] assert metrics_sf[0]["points"][0][1] == 9 - - -def test_span_creation_and_finished_metrics_opentracing(test_agent_session, ddtrace_run_python_code_in_subprocess): - code = """ -from ddtrace.opentracer import Tracer - -ot = Tracer() -for _ in range(2): - with ot.start_span('span'): - pass -""" - env = os.environ.copy() - env["DD_TRACE_OTEL_ENABLED"] = "true" - env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true" - _, stderr, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env) - assert status == 0, stderr - - metrics_sc = test_agent_session.get_metrics("spans_created") - assert len(metrics_sc) == 1 - assert metrics_sc[0]["metric"] == "spans_created" - assert metrics_sc[0]["tags"] == ["integration_name:opentracing"] - assert metrics_sc[0]["points"][0][1] == 2 - - metrics_sf = test_agent_session.get_metrics("spans_finished") - assert len(metrics_sf) == 1 - assert metrics_sf[0]["metric"] == "spans_finished" - assert metrics_sf[0]["tags"] == ["integration_name:opentracing"] - assert metrics_sf[0]["points"][0][1] == 2 - - -def test_span_creation_no_finish(test_agent_session, ddtrace_run_python_code_in_subprocess): - code = """ -import ddtrace -import opentelemetry.trace -from ddtrace import opentracer - -ddtracer = ddtrace.tracer -otel = opentelemetry.trace.get_tracer(__name__) -ot = opentracer.Tracer() - -# we must finish at least one span to enable sending telemetry to the agent -ddtracer.trace("first_span").finish() - -for _ in range(4): - ot.start_span('ot_span') - otel.start_span('otel_span') - ddtracer.trace("ddspan") -""" - env = os.environ.copy() - env["DD_TRACE_OTEL_ENABLED"] = "true" - env["_DD_INSTRUMENTATION_TELEMETRY_TESTS_FORCE_APP_STARTED"] = "true" - _, stderr, status, _ = ddtrace_run_python_code_in_subprocess(code, env=env) - assert status == 0, stderr - - metrics = test_agent_session.get_metrics("spans_created") - assert len(metrics) == 3 - - assert metrics[0]["metric"] == "spans_created" - assert metrics[0]["tags"] == ["integration_name:datadog"] - assert metrics[0]["points"][0][1] == 5 - assert metrics[1]["metric"] == "spans_created" - assert metrics[1]["tags"] == ["integration_name:opentracing"] - assert metrics[1]["points"][0][1] == 4 - assert metrics[2]["metric"] == "spans_created" - assert metrics[2]["tags"] == ["integration_name:otel"] - assert metrics[2]["points"][0][1] == 4 diff --git a/tests/tracer/test_correlation_log_context.py b/tests/tracer/test_correlation_log_context.py index abd82ad91a7..fa5f8b045f6 100644 --- a/tests/tracer/test_correlation_log_context.py +++ b/tests/tracer/test_correlation_log_context.py @@ -58,27 +58,6 @@ def test_get_log_correlation_trace_context(): }, dd_log_record -@pytest.mark.subprocess( - ddtrace_run=True, env={"DD_VERSION": "test-version", "DD_ENV": "test-env", "DD_SERVICE": "test-service"} -) -def test_get_log_correlation_context_opentracer(): - """Ensure expected DDLogRecord generated via get_correlation_log_record with an opentracing Tracer.""" - from ddtrace.internal.utils.formats import format_trace_id - from ddtrace.opentracer.tracer import Tracer as OT_Tracer - - ot_tracer = OT_Tracer(service_name="test-service") - with ot_tracer.start_active_span("operation") as scope: - dd_span = scope._span._dd_span - dd_log_record = ot_tracer.get_log_correlation_context() - assert dd_log_record == { - "dd.span_id": str(dd_span.span_id), - "dd.trace_id": format_trace_id(dd_span.trace_id), - "dd.service": "test-service", - "dd.env": "test-env", - "dd.version": "test-version", - }, dd_log_record - - @pytest.mark.subprocess() def test_get_log_correlation_context_no_active_span(): """Ensure empty DDLogRecord generated if no active span.""" diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 9232d4c2f20..eee54604355 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -3515,22 +3515,6 @@ def test_http_propagator_baggage_extract(headers): assert context._baggage == {"key1": "val1", "key2": "val2", "foo": "bar", "x": "y"} -@pytest.mark.subprocess( - env=dict(DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED="True"), - parametrize=dict(DD_TRACE_PROPAGATION_EXTRACT_FIRST=["True", "False"]), -) -def test_opentracer_propagator_baggage_extract(): - from ddtrace.propagation.http import HTTPPropagator - - headers = { - "x-datadog-trace-id": "1234", - "x-datadog-parent-id": "5678", - "http_ot_baggage_key1": "value1", - } - context = HTTPPropagator.extract(headers) - assert context._baggage == {"key1": "value1"} - - def test_baggage_span_tags_default(): headers = {"baggage": "user.id=123,correlation_id=abc,region=us-east"} context = HTTPPropagator.extract(headers) From c47f4d5e00ea866449bfcf1c0366ebf9d7446dc3 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:58:09 -0700 Subject: [PATCH 05/42] chore: remove deprecated Span function parameters (#14894) This change removes deprecated methods and method parameters from the `Span` class. Note the base branch, a staging area for breaking changes slated for 4.0. --------- Co-authored-by: brettlangdon --- ddtrace/_trace/span.py | 33 ------------- .../span-args-remove-5feecae6cf00537f.yaml | 4 ++ tests/contrib/yaaredis/test_yaaredis.py | 1 - tests/opentelemetry/test_span.py | 1 - tests/tracer/test_span.py | 47 ------------------- 5 files changed, 4 insertions(+), 82 deletions(-) create mode 100644 releasenotes/notes/span-args-remove-5feecae6cf00537f.yaml diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index 9e306f621bc..df5c36df611 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -52,10 +52,8 @@ from ddtrace.internal.constants import SPAN_API_DATADOG from ddtrace.internal.constants import SamplingMechanism from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.time import Time from ddtrace.settings._config import config -from ddtrace.vendor.debtcollector import deprecate from ddtrace.vendor.debtcollector import removals @@ -630,8 +628,6 @@ def record_exception( self, exception: BaseException, attributes: Optional[Dict[str, _AttributeValueType]] = None, - timestamp: Optional[int] = None, - escaped: bool = False, ) -> None: """ Records an exception as a span event. Multiple exceptions can be recorded on a span. @@ -643,23 +639,6 @@ def record_exception( :param timestamp: Deprecated. :param escaped: Deprecated. """ - if escaped: - deprecate( - prefix="The escaped argument is deprecated for record_exception", - message="""If an exception exits the scope of the span, it will automatically be - reported in the span tags.""", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - if timestamp is not None: - deprecate( - prefix="The timestamp argument is deprecated for record_exception", - message="""The timestamp of the span event should correspond to the time when the - error is recorded which is set automatically.""", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - tb = self._get_traceback(type(exception), exception, exception.__traceback__) attrs: Dict[str, _AttributeValueType] = { @@ -847,18 +826,6 @@ def __exit__( except Exception: log.exception("error closing trace") - def _pprint(self) -> str: - # Although Span._pprint has been internal to ddtrace since v1.0.0, it is still - # used to debug spans in the wild. Introducing a deprecation warning here to - # give users a chance to migrate to __repr__ before we remove it. - deprecate( - prefix="The _pprint method is deprecated for __repr__", - message="""Use __repr__ instead.""", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - return self.__repr__() - def __repr__(self) -> str: """Return a detailed string representation of a span.""" return ( diff --git a/releasenotes/notes/span-args-remove-5feecae6cf00537f.yaml b/releasenotes/notes/span-args-remove-5feecae6cf00537f.yaml new file mode 100644 index 00000000000..03d65342e97 --- /dev/null +++ b/releasenotes/notes/span-args-remove-5feecae6cf00537f.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + This change removes deprecated methods and method parameters from the `Span` class. diff --git a/tests/contrib/yaaredis/test_yaaredis.py b/tests/contrib/yaaredis/test_yaaredis.py index 84185652ba0..d3fa5743b70 100644 --- a/tests/contrib/yaaredis/test_yaaredis.py +++ b/tests/contrib/yaaredis/test_yaaredis.py @@ -9,7 +9,6 @@ from ddtrace.contrib.internal.yaaredis.patch import patch from ddtrace.contrib.internal.yaaredis.patch import unpatch from ddtrace.internal.compat import is_wrapted -from tests.opentracer.utils import init_tracer from tests.utils import override_config from ..config import REDIS_CONFIG diff --git a/tests/opentelemetry/test_span.py b/tests/opentelemetry/test_span.py index 3af064de795..61f9c6e0359 100644 --- a/tests/opentelemetry/test_span.py +++ b/tests/opentelemetry/test_span.py @@ -272,4 +272,3 @@ def test_otel_span_interoperability(oteltracer): otel_span_clone = Span(otel_span_og._ddspan) # Ensure all properties are consistent assert otel_span_clone.__dict__ == otel_span_og.__dict__ - assert otel_span_clone._ddspan._pprint() == otel_span_og._ddspan._pprint() diff --git a/tests/tracer/test_span.py b/tests/tracer/test_span.py index a47391ef3da..fcfd753255f 100644 --- a/tests/tracer/test_span.py +++ b/tests/tracer/test_span.py @@ -10,7 +10,6 @@ from ddtrace._trace._span_link import SpanLink from ddtrace._trace._span_pointer import _SpanPointerDirection -from ddtrace._trace.context import Context from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ENV_KEY from ddtrace.constants import ERROR_MSG @@ -866,52 +865,6 @@ def test_span_preconditions(arg): Span("test", **{arg: "foo"}) -def test_span_pprint(): - root = Span("test.span", service="s", resource="r", span_type=SpanTypes.WEB, context=Context(trace_id=1, span_id=2)) - root.set_tag("t", "v") - root.set_metric("m", 1.0) - root._add_event("message", {"importance": 10}, 16789898242) - root.set_link(trace_id=99, span_id=10, attributes={"link.name": "s1_to_s2", "link.kind": "scheduled_by"}) - root._add_span_pointer("test_kind", _SpanPointerDirection.DOWNSTREAM, "test_hash_123", {"extra": "attr"}) - - root.finish() - actual = root._pprint() - assert "name='test.span'" in actual - assert "service='s'" in actual - assert "resource='r'" in actual - assert "type='web'" in actual - assert "error=0" in actual - assert "tags={'t': 'v'}" in actual - assert "metrics={'m': 1.0}" in actual - assert "events=[SpanEvent(name='message', time=16789898242, attributes={'importance': 10})]" in actual - assert ( - "SpanLink(trace_id=99, span_id=10, attributes={'link.name': 's1_to_s2', 'link.kind': 'scheduled_by'}, " - "tracestate=None, flags=None, dropped_attributes=0)" - ) in actual - assert "SpanPointer(trace_id=0, span_id=0, kind=span-pointer" in actual - assert "direction=d, hash=test_hash_123" in actual - assert ( - f"context=Context(trace_id={root.trace_id}, span_id={root.span_id}, _meta={{}}, " - "_metrics={}, _span_links=[], _baggage={}, _is_remote=False)" - ) in actual - assert f"span_id={root.span_id}" in actual - assert f"trace_id={root.trace_id}" in actual - assert f"parent_id={root.parent_id}" in actual - assert f"start={root.start_ns}" in actual - assert f"duration={root.duration_ns}" in actual - assert f"end={root.start_ns + root.duration_ns}" in actual - - root = Span("test.span", service="s", resource="r", span_type=SpanTypes.WEB) - root.error = 1 - kv = {f"😌{i}": "😌" for i in range(100)} - root.set_tags(kv) - actual = root._pprint() - assert "duration=None" in actual - assert "end=None" in actual - assert "error=1" in actual - assert f"tags={kv}" in actual - - def test_manual_context_usage(): span1 = Span("span1") span2 = Span("span2", context=span1.context) From 22c88f9c0ad7104bc8fdf1c5593606626d58fd93 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:53:46 -0400 Subject: [PATCH 06/42] feat: remove Python 3.8 support, deprecate Python 3.9 support (#14891) This change removes support and testing for Python 3.8, which reached its end-of-life in late 2024 and was deprecated in ddtrace in https://github.com/DataDog/dd-trace-py/pull/14890. It also deprecates support for Python 3.9, which reached its end-of-life recently. This includes removal of the `cassandra` integration, which is only tested under Python 3.8. Note that the base branch is not `main`, but a branch on which we can stage the breaking changes for the 4.0 release. Depends on https://github.com/DataDog/system-tests/pull/5547 --------- Co-authored-by: Sam Brenner <106700075+sabrenner@users.noreply.github.com> Co-authored-by: Gabriele N. Tornetta --- .github/workflows/build_deploy.yml | 12 +- .github/workflows/system-tests.yml | 6 +- .github/workflows/unit_tests.yml | 2 +- .gitlab-ci.yml | 2 +- .gitlab/package.yml | 2 - .gitlab/templates/build-base-venvs.yml | 2 +- .gitlab/templates/cached-testrunner.yml | 2 +- .gitlab/templates/detect-global-locks.yml | 2 +- .gitlab/testrunner.yml | 2 +- .riot/requirements/1002685.txt | 25 -- .riot/requirements/1067a9b.txt | 45 -- .riot/requirements/1078c3b.txt | 27 -- .riot/requirements/108d1af.txt | 42 -- .riot/requirements/1097f9f.txt | 26 -- .riot/requirements/10b89f6.txt | 33 -- .riot/requirements/10bae0a.txt | 25 -- .riot/requirements/11047da.txt | 26 -- .riot/requirements/11091fd.txt | 24 -- .riot/requirements/110b5c2.txt | 26 -- .riot/requirements/111559c.txt | 74 ---- .riot/requirements/112e093.txt | 42 -- .riot/requirements/114922a.txt | 26 -- .riot/requirements/116b01f.txt | 60 --- .riot/requirements/119044a.txt | 33 -- .riot/requirements/11ac941.txt | 26 -- .riot/requirements/11d9fc2.txt | 45 -- .riot/requirements/1213604.txt | 24 -- .riot/requirements/1214426.txt | 24 -- .riot/requirements/122e427.txt | 31 -- .riot/requirements/12304dc.txt | 27 -- .riot/requirements/1258e80.txt | 30 -- .riot/requirements/1280196.txt | 30 -- .riot/requirements/128a8db.txt | 24 -- .riot/requirements/1291b76.txt | 30 -- .riot/requirements/12aa44c.txt | 27 -- .riot/requirements/12b4a54.txt | 25 -- .riot/requirements/13015fd.txt | 27 -- .riot/requirements/130dd21.txt | 32 -- .riot/requirements/132915c.txt | 27 -- .riot/requirements/13342d2.txt | 36 -- .riot/requirements/1337ee3.txt | 29 -- .riot/requirements/1344329.txt | 25 -- .riot/requirements/134a53d.txt | 25 -- .riot/requirements/134e77a.txt | 41 -- .riot/requirements/1356251.txt | 35 -- .riot/requirements/1367a0e.txt | 24 -- .riot/requirements/137cba1.txt | 28 -- .riot/requirements/138886e.txt | 35 -- .riot/requirements/13bb925.txt | 24 -- .riot/requirements/13c380c.txt | 24 -- .riot/requirements/13c42e3.txt | 54 --- .riot/requirements/13f5237.txt | 60 --- .riot/requirements/13f6818.txt | 35 -- .riot/requirements/13f7c51.txt | 34 -- .riot/requirements/140ec91.txt | 34 -- .riot/requirements/1413039.txt | 30 -- .riot/requirements/1415ef8.txt | 40 -- .riot/requirements/14395e9.txt | 45 -- .riot/requirements/144ad1a.txt | 42 -- .riot/requirements/1467f24.txt | 34 -- .riot/requirements/14767b5.txt | 23 -- .riot/requirements/14e9a3d.txt | 34 -- .riot/requirements/14effbf.txt | 24 -- .riot/requirements/15199f6.txt | 30 -- .riot/requirements/151e533.txt | 25 -- .riot/requirements/1522cb8.txt | 51 --- .riot/requirements/1560ba9.txt | 24 -- .riot/requirements/15ba505.txt | 26 -- .riot/requirements/15c5dd6.txt | 28 -- .riot/requirements/15eba42.txt | 25 -- .riot/requirements/15eea13.txt | 34 -- .riot/requirements/15eebc1.txt | 30 -- .riot/requirements/1631653.txt | 23 -- .riot/requirements/1632ff5.txt | 29 -- .riot/requirements/1634f79.txt | 38 -- .riot/requirements/163a963.txt | 30 -- .riot/requirements/164cf92.txt | 40 -- .riot/requirements/167d6de.txt | 26 -- .riot/requirements/169a623.txt | 24 -- .riot/requirements/16b7aa5.txt | 25 -- .riot/requirements/16bdd8d.txt | 30 -- .riot/requirements/16c251e.txt | 27 -- .riot/requirements/16eb426.txt | 23 -- .riot/requirements/174cced.txt | 26 -- .riot/requirements/177f4da.txt | 44 -- .riot/requirements/178cd30.txt | 29 -- .riot/requirements/17b0130.txt | 31 -- .riot/requirements/17c09be.txt | 24 -- .riot/requirements/17d317e.txt | 25 -- .riot/requirements/17ec5eb.txt | 35 -- .riot/requirements/180a9be.txt | 31 -- .riot/requirements/1810da7.txt | 24 -- .riot/requirements/181c98f.txt | 24 -- .riot/requirements/1828aa7.txt | 26 -- .riot/requirements/185fc1c.txt | 26 -- .riot/requirements/1878fa7.txt | 29 -- .riot/requirements/189128e.txt | 24 -- .riot/requirements/18abddb.txt | 77 ---- .riot/requirements/18c9043.txt | 24 -- .riot/requirements/192e4d0.txt | 46 --- .riot/requirements/1951a77.txt | 25 -- .riot/requirements/195a93b.txt | 35 -- .riot/requirements/198266a.txt | 45 -- .riot/requirements/19aab60.txt | 24 -- .riot/requirements/19aba18.txt | 27 -- .riot/requirements/19aeb31.txt | 34 -- .riot/requirements/1a1c5ae.txt | 36 -- .riot/requirements/1a2c79e.txt | 28 -- .riot/requirements/1a3a39d.txt | 42 -- .riot/requirements/1a6e6c0.txt | 32 -- .riot/requirements/1a84cc2.txt | 23 -- .riot/requirements/1ac9ec1.txt | 23 -- .riot/requirements/1aca748.txt | 30 -- .riot/requirements/1adbb5d.txt | 42 -- .riot/requirements/1ae2797.txt | 35 -- .riot/requirements/1af4fe2.txt | 35 -- .riot/requirements/1b02ea2.txt | 34 -- .riot/requirements/1b19707.txt | 25 -- .riot/requirements/1b6f5be.txt | 24 -- .riot/requirements/1ba4b57.txt | 24 -- .riot/requirements/1bceb88.txt | 56 --- .riot/requirements/1bf3da5.txt | 25 -- .riot/requirements/1c0509d.txt | 28 -- .riot/requirements/1c0ccc9.txt | 26 -- .riot/requirements/1c1da8c.txt | 25 -- .riot/requirements/1c31001.txt | 50 --- .riot/requirements/1c56cf0.txt | 74 ---- .riot/requirements/1c84e93.txt | 37 -- .riot/requirements/1c87bc4.txt | 24 -- .riot/requirements/1cc7b0e.txt | 25 -- .riot/requirements/1cda235.txt | 28 -- .riot/requirements/1ce3412.txt | 31 -- .riot/requirements/1ce4e3f.txt | 51 --- .riot/requirements/1cef696.txt | 27 -- .riot/requirements/1d23fbc.txt | 25 -- .riot/requirements/1d38b9f.txt | 26 -- .riot/requirements/1d390e8.txt | 42 -- .riot/requirements/1d788df.txt | 32 -- .riot/requirements/1d81907.txt | 36 -- .riot/requirements/1db8cf2.txt | 28 -- .riot/requirements/1dcf37e.txt | 27 -- .riot/requirements/1dd7f62.txt | 29 -- .riot/requirements/1df8347.txt | 36 -- .riot/requirements/1dfd438.txt | 24 -- .riot/requirements/1e08b64.txt | 25 -- .riot/requirements/1e0e29e.txt | 24 -- .riot/requirements/1e3534f.txt | 36 -- .riot/requirements/1e649b4.txt | 55 --- .riot/requirements/1e8124b.txt | 54 --- .riot/requirements/1ea308d.txt | 30 -- .riot/requirements/1eb29d6.txt | 24 -- .riot/requirements/1eded52.txt | 44 -- .riot/requirements/1ee2a7f.txt | 31 -- .riot/requirements/1ef7371.txt | 25 -- .riot/requirements/1efb912.txt | 47 --- .riot/requirements/1f27e33.txt | 24 -- .riot/requirements/1f2ab25.txt | 26 -- .riot/requirements/1f540f4.txt | 49 --- .riot/requirements/1f9c58a.txt | 38 -- .riot/requirements/1fb1389.txt | 25 -- .riot/requirements/1fcb05f.txt | 27 -- .riot/requirements/1fe5c31.txt | 24 -- .riot/requirements/1ffebce.txt | 35 -- .riot/requirements/20699e5.txt | 26 -- .riot/requirements/206be6b.txt | 36 -- .riot/requirements/24618e2.txt | 42 -- .riot/requirements/260ead7.txt | 48 --- .riot/requirements/2715c88.txt | 42 -- .riot/requirements/273fcaf.txt | 26 -- .riot/requirements/2be0e27.txt | 45 -- .riot/requirements/2c0f966.txt | 39 -- .riot/requirements/2d3b0ef.txt | 25 -- .riot/requirements/2f7da3e.txt | 88 ---- .riot/requirements/3007b59.txt | 25 -- .riot/requirements/30641af.txt | 29 -- .riot/requirements/30b2227.txt | 35 -- .riot/requirements/315c2cb.txt | 26 -- .riot/requirements/3348fe3.txt | 46 --- .riot/requirements/3a2a320.txt | 38 -- .riot/requirements/3aa457c.txt | 42 -- .riot/requirements/3b65323.txt | 24 -- .riot/requirements/3ba7e37.txt | 33 -- .riot/requirements/3f2ebdc.txt | 40 -- .riot/requirements/3f3ce6e.txt | 30 -- .riot/requirements/3f40530.txt | 24 -- .riot/requirements/40adc31.txt | 34 -- .riot/requirements/44339c7.txt | 56 --- .riot/requirements/4ad5317.txt | 24 -- .riot/requirements/4de03a5.txt | 79 ---- .riot/requirements/4ef6c1c.txt | 47 --- .riot/requirements/4f441db.txt | 25 -- .riot/requirements/4f4caf8.txt | 45 -- .riot/requirements/4f9be04.txt | 25 -- .riot/requirements/50b70d9.txt | 35 -- .riot/requirements/55b2430.txt | 30 -- .riot/requirements/5b0fa38.txt | 39 -- .riot/requirements/5b339ac.txt | 26 -- .riot/requirements/5b55f2d.txt | 27 -- .riot/requirements/5e79012.txt | 44 -- .riot/requirements/5ed7bed.txt | 24 -- .riot/requirements/610b7cb.txt | 27 -- .riot/requirements/65abb19.txt | 41 -- .riot/requirements/66e0a12.txt | 51 --- .riot/requirements/6724bb2.txt | 30 -- .riot/requirements/685a359.txt | 45 -- .riot/requirements/696c125.txt | 74 ---- .riot/requirements/6a87378.txt | 35 -- .riot/requirements/6bec1ec.txt | 31 -- .riot/requirements/6c7321b.txt | 30 -- .riot/requirements/6c872ab.txt | 27 -- .riot/requirements/6d67b0b.txt | 42 -- .riot/requirements/6da0824.txt | 27 -- .riot/requirements/6e26af7.txt | 51 --- .riot/requirements/70dec77.txt | 35 -- .riot/requirements/7341bd9.txt | 25 -- .riot/requirements/73d37c5.txt | 25 -- .riot/requirements/75dda93.txt | 34 -- .riot/requirements/7613d04.txt | 32 -- .riot/requirements/777f0da.txt | 38 -- .riot/requirements/77db507.txt | 38 -- .riot/requirements/79deb5b.txt | 42 -- .riot/requirements/7b02bf5.txt | 31 -- .riot/requirements/7fc5d79.txt | 49 --- .riot/requirements/7ffd29a.txt | 20 - .riot/requirements/82fb241.txt | 33 -- .riot/requirements/85e923f.txt | 36 -- .riot/requirements/8733595.txt | 38 -- .riot/requirements/89a14cf.txt | 37 -- .riot/requirements/8a17cb2.txt | 25 -- .riot/requirements/8c110bf.txt | 30 -- .riot/requirements/9029977.txt | 23 -- .riot/requirements/921b9fb.txt | 51 --- .riot/requirements/95f5020.txt | 25 -- .riot/requirements/9777f3d.txt | 24 -- .riot/requirements/97f1328.txt | 88 ---- .riot/requirements/9a319c8.txt | 37 -- .riot/requirements/9a6a8b9.txt | 30 -- .riot/requirements/9b8251b.txt | 25 -- .riot/requirements/9d50a6f.txt | 33 -- .riot/requirements/9d72125.txt | 24 -- .riot/requirements/9e76fdf.txt | 33 -- .riot/requirements/9eedbc0.txt | 42 -- .riot/requirements/a25912e.txt | 26 -- .riot/requirements/a3adb9c.txt | 25 -- .riot/requirements/a582736.txt | 28 -- .riot/requirements/a6f9342.txt | 51 --- .riot/requirements/aa2ebfa.txt | 48 --- .riot/requirements/abc0b46.txt | 26 -- .riot/requirements/ac01b32.txt | 33 -- .riot/requirements/ac28820.txt | 29 -- .riot/requirements/ad1bcb5.txt | 28 -- .riot/requirements/b39e5f7.txt | 33 -- .riot/requirements/b436a4c.txt | 46 --- .riot/requirements/b68c552.txt | 51 --- .riot/requirements/b6e9905.txt | 79 ---- .riot/requirements/b786604.txt | 25 -- .riot/requirements/b7a530f.txt | 24 -- .riot/requirements/b80e42b.txt | 29 -- .riot/requirements/baf46ab.txt | 26 -- .riot/requirements/bb588fd.txt | 25 -- .riot/requirements/bdada1a.txt | 24 -- .riot/requirements/c10c210.txt | 33 -- .riot/requirements/c2ee914.txt | 24 -- .riot/requirements/c482689.txt | 50 --- .riot/requirements/c4dace8.txt | 24 -- .riot/requirements/c74560f.txt | 32 -- .riot/requirements/c826075.txt | 25 -- .riot/requirements/ce26b2c.txt | 25 -- .riot/requirements/ce48624.txt | 49 --- .../requirements/{bfd8366.txt => cf86081.txt} | 50 +-- .riot/requirements/cfb7b47.txt | 33 -- .riot/requirements/d002f87.txt | 24 -- .riot/requirements/d15c0f8.txt | 31 -- .riot/requirements/d2cb323.txt | 24 -- .riot/requirements/d59e395.txt | 25 -- .riot/requirements/d66afaf.txt | 26 -- .riot/requirements/d776a9a.txt | 35 -- .riot/requirements/d84f5ef.txt | 51 --- .riot/requirements/d8c9ddb.txt | 40 -- .riot/requirements/dbf191e.txt | 45 -- .riot/requirements/dc3ecf5.txt | 35 -- .riot/requirements/dc9f475.txt | 42 -- .riot/requirements/e1e09c9.txt | 24 -- .riot/requirements/e222783.txt | 24 -- .riot/requirements/e2c6900.txt | 33 -- .riot/requirements/e7a63a3.txt | 29 -- .riot/requirements/e8693b9.txt | 77 ---- .riot/requirements/e871798.txt | 26 -- .riot/requirements/e87b392.txt | 25 -- .riot/requirements/eab5e7a.txt | 41 -- .riot/requirements/eb4440f.txt | 45 -- .riot/requirements/ee62ebe.txt | 24 -- .riot/requirements/ef10d26.txt | 27 -- .riot/requirements/ef66bb3.txt | 25 -- .riot/requirements/f334e66.txt | 25 -- .riot/requirements/f408d1f.txt | 38 -- .riot/requirements/f4b1bd3.txt | 26 -- .riot/requirements/f61cdff.txt | 44 -- .riot/requirements/f7e8645.txt | 21 - .riot/requirements/f8e5119.txt | 31 -- .riot/requirements/f903257.txt | 37 -- .riot/requirements/fadb064.txt | 28 -- .riot/requirements/fbab99a.txt | 28 -- .riot/requirements/fd2d2d1.txt | 25 -- .riot/requirements/ff0c51d.txt | 40 -- ddtrace/__init__.py | 8 +- ddtrace/_version.py | 34 ++ ddtrace/appsec/_iast/_ast/visitor.py | 12 - .../integration_registry/registry.yaml | 18 +- ddtrace/contrib/internal/aioredis/patch.py | 3 +- .../contrib/internal/algoliasearch/patch.py | 5 +- ddtrace/contrib/internal/asyncpg/patch.py | 5 +- ddtrace/contrib/internal/gevent/patch.py | 2 +- ddtrace/contrib/internal/psycopg/patch.py | 8 +- ddtrace/contrib/internal/pynamodb/patch.py | 5 +- ddtrace/contrib/internal/requests/patch.py | 5 +- ddtrace/contrib/internal/snowflake/patch.py | 5 +- ddtrace/internal/compat.py | 4 +- ddtrace/internal/coverage/instrumentation.py | 3 +- .../coverage/instrumentation_py3_8.py | 390 ------------------ .../coverage/instrumentation_py3_9.py | 380 +++++++++++++++++ .../datadog/profiling/ddup/CMakeLists.txt | 4 - ddtrace/profiling/_asyncio.py | 4 +- ddtrace/profiling/_threading.pyx | 9 +- ddtrace/profiling/collector/stack.pyx | 26 +- docs/index.rst | 14 +- hatch.toml | 2 +- pyproject.toml | 9 +- .../notes/py38-remove-52943a5d318b4736.yaml | 8 + riotfile.py | 386 ++++++----------- supported_versions_output.json | 18 +- supported_versions_table.csv | 18 +- tests/appsec/suitespec.yml | 16 +- tests/ci_visibility/suitespec.yml | 2 +- tests/commands/test_runner.py | 2 + tests/integration/test_integration.py | 2 +- tests/internal/test_module.py | 2 +- tests/lib_injection/conftest.py | 5 +- tests/profiling/suitespec.yml | 4 +- tests/profiling/test_profiler.py | 2 +- tests/profiling_v2/test_profiler.py | 2 +- tests/suitespec.yml | 1 + tests/tracer/test_settings.py | 8 +- 343 files changed, 694 insertions(+), 10763 deletions(-) delete mode 100644 .riot/requirements/1002685.txt delete mode 100644 .riot/requirements/1067a9b.txt delete mode 100644 .riot/requirements/1078c3b.txt delete mode 100644 .riot/requirements/108d1af.txt delete mode 100644 .riot/requirements/1097f9f.txt delete mode 100644 .riot/requirements/10b89f6.txt delete mode 100644 .riot/requirements/10bae0a.txt delete mode 100644 .riot/requirements/11047da.txt delete mode 100644 .riot/requirements/11091fd.txt delete mode 100644 .riot/requirements/110b5c2.txt delete mode 100644 .riot/requirements/111559c.txt delete mode 100644 .riot/requirements/112e093.txt delete mode 100644 .riot/requirements/114922a.txt delete mode 100644 .riot/requirements/116b01f.txt delete mode 100644 .riot/requirements/119044a.txt delete mode 100644 .riot/requirements/11ac941.txt delete mode 100644 .riot/requirements/11d9fc2.txt delete mode 100644 .riot/requirements/1213604.txt delete mode 100644 .riot/requirements/1214426.txt delete mode 100644 .riot/requirements/122e427.txt delete mode 100644 .riot/requirements/12304dc.txt delete mode 100644 .riot/requirements/1258e80.txt delete mode 100644 .riot/requirements/1280196.txt delete mode 100644 .riot/requirements/128a8db.txt delete mode 100644 .riot/requirements/1291b76.txt delete mode 100644 .riot/requirements/12aa44c.txt delete mode 100644 .riot/requirements/12b4a54.txt delete mode 100644 .riot/requirements/13015fd.txt delete mode 100644 .riot/requirements/130dd21.txt delete mode 100644 .riot/requirements/132915c.txt delete mode 100644 .riot/requirements/13342d2.txt delete mode 100644 .riot/requirements/1337ee3.txt delete mode 100644 .riot/requirements/1344329.txt delete mode 100644 .riot/requirements/134a53d.txt delete mode 100644 .riot/requirements/134e77a.txt delete mode 100644 .riot/requirements/1356251.txt delete mode 100644 .riot/requirements/1367a0e.txt delete mode 100644 .riot/requirements/137cba1.txt delete mode 100644 .riot/requirements/138886e.txt delete mode 100644 .riot/requirements/13bb925.txt delete mode 100644 .riot/requirements/13c380c.txt delete mode 100644 .riot/requirements/13c42e3.txt delete mode 100644 .riot/requirements/13f5237.txt delete mode 100644 .riot/requirements/13f6818.txt delete mode 100644 .riot/requirements/13f7c51.txt delete mode 100644 .riot/requirements/140ec91.txt delete mode 100644 .riot/requirements/1413039.txt delete mode 100644 .riot/requirements/1415ef8.txt delete mode 100644 .riot/requirements/14395e9.txt delete mode 100644 .riot/requirements/144ad1a.txt delete mode 100644 .riot/requirements/1467f24.txt delete mode 100644 .riot/requirements/14767b5.txt delete mode 100644 .riot/requirements/14e9a3d.txt delete mode 100644 .riot/requirements/14effbf.txt delete mode 100644 .riot/requirements/15199f6.txt delete mode 100644 .riot/requirements/151e533.txt delete mode 100644 .riot/requirements/1522cb8.txt delete mode 100644 .riot/requirements/1560ba9.txt delete mode 100644 .riot/requirements/15ba505.txt delete mode 100644 .riot/requirements/15c5dd6.txt delete mode 100644 .riot/requirements/15eba42.txt delete mode 100644 .riot/requirements/15eea13.txt delete mode 100644 .riot/requirements/15eebc1.txt delete mode 100644 .riot/requirements/1631653.txt delete mode 100644 .riot/requirements/1632ff5.txt delete mode 100644 .riot/requirements/1634f79.txt delete mode 100644 .riot/requirements/163a963.txt delete mode 100644 .riot/requirements/164cf92.txt delete mode 100644 .riot/requirements/167d6de.txt delete mode 100644 .riot/requirements/169a623.txt delete mode 100644 .riot/requirements/16b7aa5.txt delete mode 100644 .riot/requirements/16bdd8d.txt delete mode 100644 .riot/requirements/16c251e.txt delete mode 100644 .riot/requirements/16eb426.txt delete mode 100644 .riot/requirements/174cced.txt delete mode 100644 .riot/requirements/177f4da.txt delete mode 100644 .riot/requirements/178cd30.txt delete mode 100644 .riot/requirements/17b0130.txt delete mode 100644 .riot/requirements/17c09be.txt delete mode 100644 .riot/requirements/17d317e.txt delete mode 100644 .riot/requirements/17ec5eb.txt delete mode 100644 .riot/requirements/180a9be.txt delete mode 100644 .riot/requirements/1810da7.txt delete mode 100644 .riot/requirements/181c98f.txt delete mode 100644 .riot/requirements/1828aa7.txt delete mode 100644 .riot/requirements/185fc1c.txt delete mode 100644 .riot/requirements/1878fa7.txt delete mode 100644 .riot/requirements/189128e.txt delete mode 100644 .riot/requirements/18abddb.txt delete mode 100644 .riot/requirements/18c9043.txt delete mode 100644 .riot/requirements/192e4d0.txt delete mode 100644 .riot/requirements/1951a77.txt delete mode 100644 .riot/requirements/195a93b.txt delete mode 100644 .riot/requirements/198266a.txt delete mode 100644 .riot/requirements/19aab60.txt delete mode 100644 .riot/requirements/19aba18.txt delete mode 100644 .riot/requirements/19aeb31.txt delete mode 100644 .riot/requirements/1a1c5ae.txt delete mode 100644 .riot/requirements/1a2c79e.txt delete mode 100644 .riot/requirements/1a3a39d.txt delete mode 100644 .riot/requirements/1a6e6c0.txt delete mode 100644 .riot/requirements/1a84cc2.txt delete mode 100644 .riot/requirements/1ac9ec1.txt delete mode 100644 .riot/requirements/1aca748.txt delete mode 100644 .riot/requirements/1adbb5d.txt delete mode 100644 .riot/requirements/1ae2797.txt delete mode 100644 .riot/requirements/1af4fe2.txt delete mode 100644 .riot/requirements/1b02ea2.txt delete mode 100644 .riot/requirements/1b19707.txt delete mode 100644 .riot/requirements/1b6f5be.txt delete mode 100644 .riot/requirements/1ba4b57.txt delete mode 100644 .riot/requirements/1bceb88.txt delete mode 100644 .riot/requirements/1bf3da5.txt delete mode 100644 .riot/requirements/1c0509d.txt delete mode 100644 .riot/requirements/1c0ccc9.txt delete mode 100644 .riot/requirements/1c1da8c.txt delete mode 100644 .riot/requirements/1c31001.txt delete mode 100644 .riot/requirements/1c56cf0.txt delete mode 100644 .riot/requirements/1c84e93.txt delete mode 100644 .riot/requirements/1c87bc4.txt delete mode 100644 .riot/requirements/1cc7b0e.txt delete mode 100644 .riot/requirements/1cda235.txt delete mode 100644 .riot/requirements/1ce3412.txt delete mode 100644 .riot/requirements/1ce4e3f.txt delete mode 100644 .riot/requirements/1cef696.txt delete mode 100644 .riot/requirements/1d23fbc.txt delete mode 100644 .riot/requirements/1d38b9f.txt delete mode 100644 .riot/requirements/1d390e8.txt delete mode 100644 .riot/requirements/1d788df.txt delete mode 100644 .riot/requirements/1d81907.txt delete mode 100644 .riot/requirements/1db8cf2.txt delete mode 100644 .riot/requirements/1dcf37e.txt delete mode 100644 .riot/requirements/1dd7f62.txt delete mode 100644 .riot/requirements/1df8347.txt delete mode 100644 .riot/requirements/1dfd438.txt delete mode 100644 .riot/requirements/1e08b64.txt delete mode 100644 .riot/requirements/1e0e29e.txt delete mode 100644 .riot/requirements/1e3534f.txt delete mode 100644 .riot/requirements/1e649b4.txt delete mode 100644 .riot/requirements/1e8124b.txt delete mode 100644 .riot/requirements/1ea308d.txt delete mode 100644 .riot/requirements/1eb29d6.txt delete mode 100644 .riot/requirements/1eded52.txt delete mode 100644 .riot/requirements/1ee2a7f.txt delete mode 100644 .riot/requirements/1ef7371.txt delete mode 100644 .riot/requirements/1efb912.txt delete mode 100644 .riot/requirements/1f27e33.txt delete mode 100644 .riot/requirements/1f2ab25.txt delete mode 100644 .riot/requirements/1f540f4.txt delete mode 100644 .riot/requirements/1f9c58a.txt delete mode 100644 .riot/requirements/1fb1389.txt delete mode 100644 .riot/requirements/1fcb05f.txt delete mode 100644 .riot/requirements/1fe5c31.txt delete mode 100644 .riot/requirements/1ffebce.txt delete mode 100644 .riot/requirements/20699e5.txt delete mode 100644 .riot/requirements/206be6b.txt delete mode 100644 .riot/requirements/24618e2.txt delete mode 100644 .riot/requirements/260ead7.txt delete mode 100644 .riot/requirements/2715c88.txt delete mode 100644 .riot/requirements/273fcaf.txt delete mode 100644 .riot/requirements/2be0e27.txt delete mode 100644 .riot/requirements/2c0f966.txt delete mode 100644 .riot/requirements/2d3b0ef.txt delete mode 100644 .riot/requirements/2f7da3e.txt delete mode 100644 .riot/requirements/3007b59.txt delete mode 100644 .riot/requirements/30641af.txt delete mode 100644 .riot/requirements/30b2227.txt delete mode 100644 .riot/requirements/315c2cb.txt delete mode 100644 .riot/requirements/3348fe3.txt delete mode 100644 .riot/requirements/3a2a320.txt delete mode 100644 .riot/requirements/3aa457c.txt delete mode 100644 .riot/requirements/3b65323.txt delete mode 100644 .riot/requirements/3ba7e37.txt delete mode 100644 .riot/requirements/3f2ebdc.txt delete mode 100644 .riot/requirements/3f3ce6e.txt delete mode 100644 .riot/requirements/3f40530.txt delete mode 100644 .riot/requirements/40adc31.txt delete mode 100644 .riot/requirements/44339c7.txt delete mode 100644 .riot/requirements/4ad5317.txt delete mode 100644 .riot/requirements/4de03a5.txt delete mode 100644 .riot/requirements/4ef6c1c.txt delete mode 100644 .riot/requirements/4f441db.txt delete mode 100644 .riot/requirements/4f4caf8.txt delete mode 100644 .riot/requirements/4f9be04.txt delete mode 100644 .riot/requirements/50b70d9.txt delete mode 100644 .riot/requirements/55b2430.txt delete mode 100644 .riot/requirements/5b0fa38.txt delete mode 100644 .riot/requirements/5b339ac.txt delete mode 100644 .riot/requirements/5b55f2d.txt delete mode 100644 .riot/requirements/5e79012.txt delete mode 100644 .riot/requirements/5ed7bed.txt delete mode 100644 .riot/requirements/610b7cb.txt delete mode 100644 .riot/requirements/65abb19.txt delete mode 100644 .riot/requirements/66e0a12.txt delete mode 100644 .riot/requirements/6724bb2.txt delete mode 100644 .riot/requirements/685a359.txt delete mode 100644 .riot/requirements/696c125.txt delete mode 100644 .riot/requirements/6a87378.txt delete mode 100644 .riot/requirements/6bec1ec.txt delete mode 100644 .riot/requirements/6c7321b.txt delete mode 100644 .riot/requirements/6c872ab.txt delete mode 100644 .riot/requirements/6d67b0b.txt delete mode 100644 .riot/requirements/6da0824.txt delete mode 100644 .riot/requirements/6e26af7.txt delete mode 100644 .riot/requirements/70dec77.txt delete mode 100644 .riot/requirements/7341bd9.txt delete mode 100644 .riot/requirements/73d37c5.txt delete mode 100644 .riot/requirements/75dda93.txt delete mode 100644 .riot/requirements/7613d04.txt delete mode 100644 .riot/requirements/777f0da.txt delete mode 100644 .riot/requirements/77db507.txt delete mode 100644 .riot/requirements/79deb5b.txt delete mode 100644 .riot/requirements/7b02bf5.txt delete mode 100644 .riot/requirements/7fc5d79.txt delete mode 100644 .riot/requirements/7ffd29a.txt delete mode 100644 .riot/requirements/82fb241.txt delete mode 100644 .riot/requirements/85e923f.txt delete mode 100644 .riot/requirements/8733595.txt delete mode 100644 .riot/requirements/89a14cf.txt delete mode 100644 .riot/requirements/8a17cb2.txt delete mode 100644 .riot/requirements/8c110bf.txt delete mode 100644 .riot/requirements/9029977.txt delete mode 100644 .riot/requirements/921b9fb.txt delete mode 100644 .riot/requirements/95f5020.txt delete mode 100644 .riot/requirements/9777f3d.txt delete mode 100644 .riot/requirements/97f1328.txt delete mode 100644 .riot/requirements/9a319c8.txt delete mode 100644 .riot/requirements/9a6a8b9.txt delete mode 100644 .riot/requirements/9b8251b.txt delete mode 100644 .riot/requirements/9d50a6f.txt delete mode 100644 .riot/requirements/9d72125.txt delete mode 100644 .riot/requirements/9e76fdf.txt delete mode 100644 .riot/requirements/9eedbc0.txt delete mode 100644 .riot/requirements/a25912e.txt delete mode 100644 .riot/requirements/a3adb9c.txt delete mode 100644 .riot/requirements/a582736.txt delete mode 100644 .riot/requirements/a6f9342.txt delete mode 100644 .riot/requirements/aa2ebfa.txt delete mode 100644 .riot/requirements/abc0b46.txt delete mode 100644 .riot/requirements/ac01b32.txt delete mode 100644 .riot/requirements/ac28820.txt delete mode 100644 .riot/requirements/ad1bcb5.txt delete mode 100644 .riot/requirements/b39e5f7.txt delete mode 100644 .riot/requirements/b436a4c.txt delete mode 100644 .riot/requirements/b68c552.txt delete mode 100644 .riot/requirements/b6e9905.txt delete mode 100644 .riot/requirements/b786604.txt delete mode 100644 .riot/requirements/b7a530f.txt delete mode 100644 .riot/requirements/b80e42b.txt delete mode 100644 .riot/requirements/baf46ab.txt delete mode 100644 .riot/requirements/bb588fd.txt delete mode 100644 .riot/requirements/bdada1a.txt delete mode 100644 .riot/requirements/c10c210.txt delete mode 100644 .riot/requirements/c2ee914.txt delete mode 100644 .riot/requirements/c482689.txt delete mode 100644 .riot/requirements/c4dace8.txt delete mode 100644 .riot/requirements/c74560f.txt delete mode 100644 .riot/requirements/c826075.txt delete mode 100644 .riot/requirements/ce26b2c.txt delete mode 100644 .riot/requirements/ce48624.txt rename .riot/requirements/{bfd8366.txt => cf86081.txt} (52%) delete mode 100644 .riot/requirements/cfb7b47.txt delete mode 100644 .riot/requirements/d002f87.txt delete mode 100644 .riot/requirements/d15c0f8.txt delete mode 100644 .riot/requirements/d2cb323.txt delete mode 100644 .riot/requirements/d59e395.txt delete mode 100644 .riot/requirements/d66afaf.txt delete mode 100644 .riot/requirements/d776a9a.txt delete mode 100644 .riot/requirements/d84f5ef.txt delete mode 100644 .riot/requirements/d8c9ddb.txt delete mode 100644 .riot/requirements/dbf191e.txt delete mode 100644 .riot/requirements/dc3ecf5.txt delete mode 100644 .riot/requirements/dc9f475.txt delete mode 100644 .riot/requirements/e1e09c9.txt delete mode 100644 .riot/requirements/e222783.txt delete mode 100644 .riot/requirements/e2c6900.txt delete mode 100644 .riot/requirements/e7a63a3.txt delete mode 100644 .riot/requirements/e8693b9.txt delete mode 100644 .riot/requirements/e871798.txt delete mode 100644 .riot/requirements/e87b392.txt delete mode 100644 .riot/requirements/eab5e7a.txt delete mode 100644 .riot/requirements/eb4440f.txt delete mode 100644 .riot/requirements/ee62ebe.txt delete mode 100644 .riot/requirements/ef10d26.txt delete mode 100644 .riot/requirements/ef66bb3.txt delete mode 100644 .riot/requirements/f334e66.txt delete mode 100644 .riot/requirements/f408d1f.txt delete mode 100644 .riot/requirements/f4b1bd3.txt delete mode 100644 .riot/requirements/f61cdff.txt delete mode 100644 .riot/requirements/f7e8645.txt delete mode 100644 .riot/requirements/f8e5119.txt delete mode 100644 .riot/requirements/f903257.txt delete mode 100644 .riot/requirements/fadb064.txt delete mode 100644 .riot/requirements/fbab99a.txt delete mode 100644 .riot/requirements/fd2d2d1.txt delete mode 100644 .riot/requirements/ff0c51d.txt create mode 100644 ddtrace/_version.py delete mode 100644 ddtrace/internal/coverage/instrumentation_py3_8.py create mode 100644 ddtrace/internal/coverage/instrumentation_py3_9.py create mode 100644 releasenotes/notes/py38-remove-52943a5d318b4736.yaml diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index 03fb56277fb..cc97a3c8220 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -50,8 +50,12 @@ jobs: "$GITHUB_REF_NAME" =~ ^[0-9]+\.x$ ]]; then LIBRARY_VERSION=$(setuptools-scm --strip-dev) else - # All else, maintain the dev version - LIBRARY_VERSION=$(setuptools-scm) + # use version string explicitly set in the project metadata, if exists + LIBRARY_VERSION=$(grep '^version = ' pyproject.toml | tr -d '"' | cut -d' ' -f3) + if [[ -z $LIBRARY_VERSION ]]; then + # All else, maintain the dev version + LIBRARY_VERSION=$(setuptools-scm) + fi fi echo "${LIBRARY_VERSION}" | tee version.txt @@ -65,8 +69,8 @@ jobs: needs: [ "compute_version" ] uses: ./.github/workflows/build_python_3.yml with: - cibw_build: 'cp38* cp39* cp310* cp311* cp312* cp313* cp314*' - cibw_skip: 'cp38-win_arm64 cp39-win_arm64 cp310-win_arm64 cp314t*' + cibw_build: 'cp39* cp310* cp311* cp312* cp313* cp314*' + cibw_skip: 'cp39-win_arm64 cp310-win_arm64 cp314t*' library_version: ${{ needs.compute_version.outputs.library_version }} build_sdist: diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 2cdc1ac32ad..a8229553fa9 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '58dc99266c404d0e04868a5817c61fad360cc4cb' + ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '58dc99266c404d0e04868a5817c61fad360cc4cb' + ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '58dc99266c404d0e04868a5817c61fad360cc4cb' + ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 7451f923229..93c6cb727e2 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -16,7 +16,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] # Keep this in sync with hatch.toml - python-version: ["3.8", "3.10", "3.12", "3.14"] + python-version: ["3.10", "3.12", "3.14"] steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 24f62ef3267..c936a14a553 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "58dc99266c404d0e04868a5817c61fad360cc4cb" + SYSTEM_TESTS_REF: "f053b033c1d93c95c3b76882b2aedf9b9929aff2" default: interruptible: true diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 1206e4305f1..75bdbf9c68d 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -59,8 +59,6 @@ download_dependency_wheels: PIP_CACHE_DIR: "${CI_PROJECT_DIR}/.cache/pip" parallel: matrix: # The image tags that are mirrored are in: https://github.com/DataDog/images/blob/master/mirror.yaml - - PYTHON_IMAGE_TAG: "3.8" - PYTHON_VERSION: "3.8" - PYTHON_IMAGE_TAG: "3.9.13" PYTHON_VERSION: "3.9" - PYTHON_IMAGE_TAG: "3.10.13" diff --git a/.gitlab/templates/build-base-venvs.yml b/.gitlab/templates/build-base-venvs.yml index de8d29218ea..36557c6d510 100644 --- a/.gitlab/templates/build-base-venvs.yml +++ b/.gitlab/templates/build-base-venvs.yml @@ -4,7 +4,7 @@ build_base_venvs: needs: [] parallel: matrix: - - PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] variables: CMAKE_BUILD_PARALLEL_LEVEL: '12' PIP_VERBOSE: '0' diff --git a/.gitlab/templates/cached-testrunner.yml b/.gitlab/templates/cached-testrunner.yml index 1faef291770..5c4f91dee7b 100644 --- a/.gitlab/templates/cached-testrunner.yml +++ b/.gitlab/templates/cached-testrunner.yml @@ -5,7 +5,7 @@ EXT_CACHE_VENV: '${{CI_PROJECT_DIR}}/.cache/ext_cache_venv${{PYTHON_VERSION}}' before_script: | ulimit -c unlimited - pyenv global 3.12 3.8 3.9 3.10 3.11 3.13 3.14 + pyenv global 3.12 3.9 3.10 3.11 3.13 3.14 export _CI_DD_AGENT_URL=http://${{HOST_IP}}:8126/ set -e -o pipefail if [ ! -d $EXT_CACHE_VENV ]; then diff --git a/.gitlab/templates/detect-global-locks.yml b/.gitlab/templates/detect-global-locks.yml index 18e5a7f5281..5b16e8d1722 100644 --- a/.gitlab/templates/detect-global-locks.yml +++ b/.gitlab/templates/detect-global-locks.yml @@ -4,7 +4,7 @@ detect-global-locks: needs: [] parallel: matrix: - - PYTHON_VERSION: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] variables: DD_DYNAMIC_INSTRUMENTATION_ENABLED: '1' DD_CODE_ORIGIN_FOR_SPANS_ENABLED: '1' diff --git a/.gitlab/testrunner.yml b/.gitlab/testrunner.yml index 4420728793e..1c010690c71 100644 --- a/.gitlab/testrunner.yml +++ b/.gitlab/testrunner.yml @@ -12,7 +12,7 @@ variables: before_script: - ulimit -c unlimited - git config --global --add safe.directory ${CI_PROJECT_DIR} - - pyenv global 3.12 3.8 3.9 3.10 3.11 3.13 3.14 + - pyenv global 3.12 3.9 3.10 3.11 3.13 3.14 - export _CI_DD_AGENT_URL=http://${HOST_IP}:8126/ retry: 2 artifacts: diff --git a/.riot/requirements/1002685.txt b/.riot/requirements/1002685.txt deleted file mode 100644 index 8bea0b26ba6..00000000000 --- a/.riot/requirements/1002685.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1002685.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.2.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -protobuf==4.21.12 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1067a9b.txt b/.riot/requirements/1067a9b.txt deleted file mode 100644 index d9b1caa7c54..00000000000 --- a/.riot/requirements/1067a9b.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1067a9b.in -# -aiofiles==23.2.1 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.16.3 -httptools==0.6.1 -httpx==0.23.3 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -requests==2.31.0 -rfc3986[idna2008]==1.5.0 -sanic==22.12.0 -sanic-routing==23.6.0 -sanic-testing==22.3.1 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -ujson==5.9.0 -urllib3==2.1.0 -uvloop==0.19.0 -websockets==10.4 -zipp==3.17.0 diff --git a/.riot/requirements/1078c3b.txt b/.riot/requirements/1078c3b.txt deleted file mode 100644 index 3dfee8f68b4..00000000000 --- a/.riot/requirements/1078c3b.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1078c3b.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mysql-connector-python==9.0.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==1.3.24 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/108d1af.txt b/.riot/requirements/108d1af.txt deleted file mode 100644 index 95aa2e94b5b..00000000000 --- a/.riot/requirements/108d1af.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/108d1af.in -# -aiofiles==24.1.0 -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.116.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.44.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/1097f9f.txt b/.riot/requirements/1097f9f.txt deleted file mode 100644 index 3154cac7e78..00000000000 --- a/.riot/requirements/1097f9f.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1097f9f.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elasticsearch7==7.13.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/10b89f6.txt b/.riot/requirements/10b89f6.txt deleted file mode 100644 index 59297b1e0b1..00000000000 --- a/.riot/requirements/10b89f6.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/10b89f6.in -# -attrs==23.2.0 -blinker==1.7.0 -click==8.1.7 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -flask==3.0.2 -flask-caching==1.10.1 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -itsdangerous==2.1.2 -jinja2==3.1.3 -markupsafe==2.1.5 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-memcached==1.62 -redis==2.10.6 -sortedcontainers==2.4.0 -tomli==2.0.1 -werkzeug==3.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/10bae0a.txt b/.riot/requirements/10bae0a.txt deleted file mode 100644 index b6ac23fbc1a..00000000000 --- a/.riot/requirements/10bae0a.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/10bae0a.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -tornado==6.0.4 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/11047da.txt b/.riot/requirements/11047da.txt deleted file mode 100644 index 205ab7860ff..00000000000 --- a/.riot/requirements/11047da.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/11047da.in -# -aiomysql==0.1.1 -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pymysql==1.1.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/11091fd.txt b/.riot/requirements/11091fd.txt deleted file mode 100644 index 90586cdcc5f..00000000000 --- a/.riot/requirements/11091fd.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/11091fd.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pymemcache==4.0.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/110b5c2.txt b/.riot/requirements/110b5c2.txt deleted file mode 100644 index d2a20cc6715..00000000000 --- a/.riot/requirements/110b5c2.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/110b5c2.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mako==1.0.14 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/111559c.txt b/.riot/requirements/111559c.txt deleted file mode 100644 index 1440229c1ce..00000000000 --- a/.riot/requirements/111559c.txt +++ /dev/null @@ -1,74 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/111559c.in -# -annotated-types==0.7.0 -attrs==25.3.0 -aws-sam-translator==1.97.0 -aws-xray-sdk==2.14.0 -boto==2.49.0 -boto3==1.37.38 -botocore==1.37.38 -certifi==2025.4.26 -cffi==1.17.1 -cfn-lint==0.53.1 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -cryptography==45.0.3 -docker==7.1.0 -ecdsa==0.14.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -idna==2.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jinja2==2.10.3 -jmespath==1.0.1 -jsondiff==2.2.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -jsonschema==3.2.0 -junit-xml==1.9 -markupsafe==1.1.1 -mock==5.2.0 -more-itertools==10.5.0 -moto==1.3.16 -networkx==2.8.8 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pyasn1==0.4.8 -pycparser==2.22 -pydantic==2.10.6 -pydantic-core==2.27.2 -pynamodb==5.5.1 -pyrsistent==0.20.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -python-dateutil==2.9.0.post0 -python-jose[cryptography]==3.4.0 -pytz==2025.2 -pyyaml==6.0.2 -requests==2.32.3 -responses==0.25.7 -rsa==4.9.1 -s3transfer==0.11.5 -six==1.17.0 -sortedcontainers==2.4.0 -sshpubkeys==3.3.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -xmltodict==0.14.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/112e093.txt b/.riot/requirements/112e093.txt deleted file mode 100644 index 5fff90d1609..00000000000 --- a/.riot/requirements/112e093.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/112e093.in -# -aiofiles==24.1.0 -aiosqlite==0.20.0 -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -databases==0.8.0 -exceptiongroup==1.3.0 -greenlet==3.1.1 -h11==0.12.0 -httpcore==0.14.7 -httpx==0.22.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -sqlalchemy==1.4.54 -starlette==0.44.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/114922a.txt b/.riot/requirements/114922a.txt deleted file mode 100644 index 9e2467bba9a..00000000000 --- a/.riot/requirements/114922a.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/114922a.in -# -async-timeout==5.0.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -dramatiq==1.10.0 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pika==1.3.2 -pluggy==1.5.0 -prometheus-client==0.21.1 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -redis==6.1.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 diff --git a/.riot/requirements/116b01f.txt b/.riot/requirements/116b01f.txt deleted file mode 100644 index d3d083bf336..00000000000 --- a/.riot/requirements/116b01f.txt +++ /dev/null @@ -1,60 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/116b01f.in -# -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==8.1.8 -coverage[toml]==7.6.1 -deprecated==1.2.18 -exceptiongroup==1.3.0 -flask==2.1.3 -gevent==24.2.1 -googleapis-common-protos==1.70.0 -greenlet==3.1.1 -grpcio==1.70.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.33.1 -opentelemetry-exporter-otlp==1.33.1 -opentelemetry-exporter-otlp-proto-common==1.33.1 -opentelemetry-exporter-otlp-proto-grpc==1.33.1 -opentelemetry-exporter-otlp-proto-http==1.33.1 -opentelemetry-instrumentation==0.54b1 -opentelemetry-instrumentation-flask==0.54b1 -opentelemetry-instrumentation-wsgi==0.54b1 -opentelemetry-proto==1.33.1 -opentelemetry-sdk==1.33.1 -opentelemetry-semantic-conventions==0.54b1 -opentelemetry-util-http==0.54b1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==5.29.5 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/119044a.txt b/.riot/requirements/119044a.txt deleted file mode 100644 index cae7551e20a..00000000000 --- a/.riot/requirements/119044a.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/119044a.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-functions==1.23.0 -azure-servicebus==7.14.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 diff --git a/.riot/requirements/11ac941.txt b/.riot/requirements/11ac941.txt deleted file mode 100644 index 92df617ba6e..00000000000 --- a/.riot/requirements/11ac941.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/11ac941.in -# -async-timeout==5.0.1 -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -valkey==6.0.2 -zipp==3.20.2 diff --git a/.riot/requirements/11d9fc2.txt b/.riot/requirements/11d9fc2.txt deleted file mode 100644 index b89da5d9931..00000000000 --- a/.riot/requirements/11d9fc2.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/11d9fc2.in -# -aiofiles==23.2.1 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.16.3 -httptools==0.6.1 -httpx==0.23.3 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==6.0.4 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -requests==2.31.0 -rfc3986[idna2008]==1.5.0 -sanic==22.12.0 -sanic-routing==23.6.0 -sanic-testing==22.3.1 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -ujson==5.9.0 -urllib3==2.1.0 -uvloop==0.19.0 -websockets==10.4 -zipp==3.17.0 diff --git a/.riot/requirements/1213604.txt b/.riot/requirements/1213604.txt deleted file mode 100644 index df2535c1773..00000000000 --- a/.riot/requirements/1213604.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1213604.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1214426.txt b/.riot/requirements/1214426.txt deleted file mode 100644 index 27ac717aad0..00000000000 --- a/.riot/requirements/1214426.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1214426.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/122e427.txt b/.riot/requirements/122e427.txt deleted file mode 100644 index 58d51498b2c..00000000000 --- a/.riot/requirements/122e427.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/122e427.in -# -attrs==25.3.0 -certifi==2025.1.31 -coverage[toml]==7.6.1 -elastic-transport==8.17.1 -elasticsearch==9.0.0 -elasticsearch7==7.17.12 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -zipp==3.20.2 diff --git a/.riot/requirements/12304dc.txt b/.riot/requirements/12304dc.txt deleted file mode 100644 index a7efa420de5..00000000000 --- a/.riot/requirements/12304dc.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/12304dc.in -# -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg==3.0.18 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1258e80.txt b/.riot/requirements/1258e80.txt deleted file mode 100644 index 449021d50d8..00000000000 --- a/.riot/requirements/1258e80.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1258e80.in -# -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-consul==1.1.0 -requests==2.31.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/1280196.txt b/.riot/requirements/1280196.txt deleted file mode 100644 index 9ddea946400..00000000000 --- a/.riot/requirements/1280196.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1280196.in -# -attrs==25.3.0 -beautifulsoup4==4.14.2 -bottle==0.13.4 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -soupsieve==2.7 -tomli==2.3.0 -typing-extensions==4.13.2 -waitress==3.0.0 -webob==1.8.9 -webtest==3.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/128a8db.txt b/.riot/requirements/128a8db.txt deleted file mode 100644 index 8fbc2c95ecf..00000000000 --- a/.riot/requirements/128a8db.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/128a8db.in -# -attrs==25.3.0 -clang==20.1.5 -cmake==4.0.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pybind11==3.0.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 diff --git a/.riot/requirements/1291b76.txt b/.riot/requirements/1291b76.txt deleted file mode 100644 index 383d3c58109..00000000000 --- a/.riot/requirements/1291b76.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1291b76.in -# -asgiref==3.8.1 -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -django==3.2.25 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/12aa44c.txt b/.riot/requirements/12aa44c.txt deleted file mode 100644 index 2c11e62efab..00000000000 --- a/.riot/requirements/12aa44c.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/12aa44c.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elastic-transport==8.11.0 -elasticsearch==8.0.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/12b4a54.txt b/.riot/requirements/12b4a54.txt deleted file mode 100644 index 11a84b3a69a..00000000000 --- a/.riot/requirements/12b4a54.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/12b4a54.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -logbook==1.0.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/13015fd.txt b/.riot/requirements/13015fd.txt deleted file mode 100644 index 29ed26daa1c..00000000000 --- a/.riot/requirements/13015fd.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13015fd.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==4.10.1 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/130dd21.txt b/.riot/requirements/130dd21.txt deleted file mode 100644 index a1eb686cbfd..00000000000 --- a/.riot/requirements/130dd21.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/130dd21.in -# -attrs==25.3.0 -cheroot==10.0.1 -cherrypy==17.0.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jaraco-functools==4.1.0 -mock==5.2.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -portend==3.2.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tempora==5.7.1 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/132915c.txt b/.riot/requirements/132915c.txt deleted file mode 100644 index 7b85f7727d7..00000000000 --- a/.riot/requirements/132915c.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/132915c.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -googleapis-common-protos==1.70.0 -grpcio==1.59.5 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -protobuf==5.29.4 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/13342d2.txt b/.riot/requirements/13342d2.txt deleted file mode 100644 index bca1e8dc140..00000000000 --- a/.riot/requirements/13342d2.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13342d2.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -coverage==7.6.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -more-itertools==8.10.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py==1.11.0 -pytest==6.2.5 -pytest-cov==2.9.0 -pytest-mock==2.0.0 -pytest-randomly==3.15.0 -pytest-xdist==3.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -toml==0.10.2 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1337ee3.txt b/.riot/requirements/1337ee3.txt deleted file mode 100644 index 7a2b39ce1e6..00000000000 --- a/.riot/requirements/1337ee3.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1337ee3.in -# -attrs==25.3.0 -azure-functions==1.23.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 diff --git a/.riot/requirements/1344329.txt b/.riot/requirements/1344329.txt deleted file mode 100644 index cf2e4583b0a..00000000000 --- a/.riot/requirements/1344329.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1344329.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -elasticsearch5==5.5.6 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/134a53d.txt b/.riot/requirements/134a53d.txt deleted file mode 100644 index 1473061d7c1..00000000000 --- a/.riot/requirements/134a53d.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/134a53d.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pyyaml==6.0.2 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/134e77a.txt b/.riot/requirements/134e77a.txt deleted file mode 100644 index da96e381bb6..00000000000 --- a/.riot/requirements/134e77a.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/134e77a.in -# -amqp==5.3.1 -attrs==25.3.0 -backports-zoneinfo[tzdata]==0.2.1 -billiard==4.2.1 -celery==5.5.3 -click==8.1.8 -click-didyoumean==0.3.1 -click-plugins==1.1.1.2 -click-repl==0.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==5.5.4 -mock==5.2.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -prompt-toolkit==3.0.51 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -redis==3.5.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -tzdata==2025.2 -vine==5.1.0 -wcwidth==0.2.13 -zipp==3.20.2 diff --git a/.riot/requirements/1356251.txt b/.riot/requirements/1356251.txt deleted file mode 100644 index 0b3c927d4fb..00000000000 --- a/.riot/requirements/1356251.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1356251.in -# -aiohttp==3.9.5 -aiohttp-jinja2==1.6 -aiosignal==1.3.1 -async-timeout==4.0.3 -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markupsafe==2.1.5 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yarl==1.9.4 -zipp==3.19.2 diff --git a/.riot/requirements/1367a0e.txt b/.riot/requirements/1367a0e.txt deleted file mode 100644 index 10a489ee4f0..00000000000 --- a/.riot/requirements/1367a0e.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1367a0e.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tornado==5.1.1 -zipp==3.17.0 diff --git a/.riot/requirements/137cba1.txt b/.riot/requirements/137cba1.txt deleted file mode 100644 index 4ce4b48c527..00000000000 --- a/.riot/requirements/137cba1.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/137cba1.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -decorator==5.1.1 -dogpile-cache==1.3.0 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pbr==6.0.0 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -stevedore==5.1.0 -tomli==2.0.1 -typing-extensions==4.9.0 -zipp==3.17.0 diff --git a/.riot/requirements/138886e.txt b/.riot/requirements/138886e.txt deleted file mode 100644 index 480cd22178b..00000000000 --- a/.riot/requirements/138886e.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/138886e.in -# -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aiosignal==1.3.1 -async-timeout==5.0.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -frozenlist==1.5.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pytest==8.3.5 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/13bb925.txt b/.riot/requirements/13bb925.txt deleted file mode 100644 index f87641d20cc..00000000000 --- a/.riot/requirements/13bb925.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13bb925.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -falcon==3.0.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/13c380c.txt b/.riot/requirements/13c380c.txt deleted file mode 100644 index bea29a1b8ab..00000000000 --- a/.riot/requirements/13c380c.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/13c380c.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/13c42e3.txt b/.riot/requirements/13c42e3.txt deleted file mode 100644 index 82838d89360..00000000000 --- a/.riot/requirements/13c42e3.txt +++ /dev/null @@ -1,54 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13c42e3.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.4.26 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -numpy==1.24.4 -openai[datalib,embeddings]==1.30.1 -opentracing==2.4.0 -packaging==25.0 -pandas==2.0.3 -pandas-stubs==2.0.3.230814 -pillow==9.5.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -pytz==2025.2 -pyyaml==6.0.2 -six==1.17.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -tqdm==4.67.1 -types-pytz==2024.2.0.20241221 -typing-extensions==4.13.2 -tzdata==2025.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/13f5237.txt b/.riot/requirements/13f5237.txt deleted file mode 100644 index a9f480d16ae..00000000000 --- a/.riot/requirements/13f5237.txt +++ /dev/null @@ -1,60 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13f5237.in -# -attrs==25.3.0 -backoff==2.2.1 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==8.1.8 -coverage[toml]==7.6.1 -deprecated==1.2.18 -exceptiongroup==1.3.0 -flask==2.1.3 -gevent==24.2.1 -googleapis-common-protos==1.70.0 -greenlet==3.1.1 -grpcio==1.70.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.33.1 -opentelemetry-exporter-otlp==1.15.0 -opentelemetry-exporter-otlp-proto-grpc==1.15.0 -opentelemetry-exporter-otlp-proto-http==1.15.0 -opentelemetry-instrumentation==0.54b1 -opentelemetry-instrumentation-flask==0.54b1 -opentelemetry-instrumentation-wsgi==0.54b1 -opentelemetry-proto==1.15.0 -opentelemetry-sdk==1.33.1 -opentelemetry-semantic-conventions==0.54b1 -opentelemetry-util-http==0.54b1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==4.25.8 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/13f6818.txt b/.riot/requirements/13f6818.txt deleted file mode 100644 index 11bbbf63862..00000000000 --- a/.riot/requirements/13f6818.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13f6818.in -# -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aiosignal==1.3.1 -async-timeout==5.0.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -frozenlist==1.5.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pytest==8.3.5 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/13f7c51.txt b/.riot/requirements/13f7c51.txt deleted file mode 100644 index caf600998bb..00000000000 --- a/.riot/requirements/13f7c51.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13f7c51.in -# -attrs==25.3.0 -certifi==2025.7.9 -charset-normalizer==3.4.2 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==2.1.3 -hypothesis==6.113.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==2.3.8 -zipp==3.20.2 diff --git a/.riot/requirements/140ec91.txt b/.riot/requirements/140ec91.txt deleted file mode 100644 index 2c62a8d4b92..00000000000 --- a/.riot/requirements/140ec91.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/140ec91.in -# -attrs==23.2.0 -blinker==1.7.0 -cachelib==0.9.0 -click==8.1.7 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -flask==3.0.2 -flask-caching==2.1.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -itsdangerous==2.1.2 -jinja2==3.1.3 -markupsafe==2.1.5 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-memcached==1.62 -redis==2.10.6 -sortedcontainers==2.4.0 -tomli==2.0.1 -werkzeug==3.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1413039.txt b/.riot/requirements/1413039.txt deleted file mode 100644 index 82340d380e3..00000000000 --- a/.riot/requirements/1413039.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1413039.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/1415ef8.txt b/.riot/requirements/1415ef8.txt deleted file mode 100644 index 24cd0a250b4..00000000000 --- a/.riot/requirements/1415ef8.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1415ef8.in -# -annotated-types==0.7.0 -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==3.0.3 -flask-openapi3==4.0.3 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==3.0.6 -zipp==3.20.2 diff --git a/.riot/requirements/14395e9.txt b/.riot/requirements/14395e9.txt deleted file mode 100644 index 55ad6e69192..00000000000 --- a/.riot/requirements/14395e9.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/14395e9.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -dill==0.4.0 -django==4.2.24 -django-configurations==2.5.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pylibmc==1.6.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pyyaml==6.0.2 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/144ad1a.txt b/.riot/requirements/144ad1a.txt deleted file mode 100644 index 2a1b6cd94b3..00000000000 --- a/.riot/requirements/144ad1a.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/144ad1a.in -# -annotated-types==0.7.0 -anthropic==0.67.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.9.1 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pyyaml==6.0.2 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.3 -yarl==1.15.2 diff --git a/.riot/requirements/1467f24.txt b/.riot/requirements/1467f24.txt deleted file mode 100644 index a59bd2ed545..00000000000 --- a/.riot/requirements/1467f24.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1467f24.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -django==4.2.21 -django-configurations==2.5.1 -djangorestframework==3.15.2 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/14767b5.txt b/.riot/requirements/14767b5.txt deleted file mode 100644 index 0bb110811df..00000000000 --- a/.riot/requirements/14767b5.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/14767b5.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/14e9a3d.txt b/.riot/requirements/14e9a3d.txt deleted file mode 100644 index ffbb95edc30..00000000000 --- a/.riot/requirements/14e9a3d.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/14e9a3d.in -# -asgiref==3.8.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -django==3.2.25 -django-configurations==2.5.1 -djangorestframework==3.11.2 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/14effbf.txt b/.riot/requirements/14effbf.txt deleted file mode 100644 index 0fcf733c893..00000000000 --- a/.riot/requirements/14effbf.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/14effbf.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pylibmc==1.6.3 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/15199f6.txt b/.riot/requirements/15199f6.txt deleted file mode 100644 index 039082c9342..00000000000 --- a/.riot/requirements/15199f6.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15199f6.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-eventhub==5.12.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/151e533.txt b/.riot/requirements/151e533.txt deleted file mode 100644 index 6f9a56bd894..00000000000 --- a/.riot/requirements/151e533.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/151e533.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -logbook==1.7.0.post0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1522cb8.txt b/.riot/requirements/1522cb8.txt deleted file mode 100644 index fb583577f6d..00000000000 --- a/.riot/requirements/1522cb8.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1522cb8.in -# -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==8.1.8 -coverage[toml]==7.6.1 -deprecated==1.2.18 -exceptiongroup==1.3.0 -flask==2.1.3 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.0.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.26.0 -opentelemetry-instrumentation==0.47b0 -opentelemetry-instrumentation-flask==0.47b0 -opentelemetry-instrumentation-wsgi==0.47b0 -opentelemetry-semantic-conventions==0.47b0 -opentelemetry-util-http==0.47b0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/1560ba9.txt b/.riot/requirements/1560ba9.txt deleted file mode 100644 index e7f12e49d80..00000000000 --- a/.riot/requirements/1560ba9.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1560ba9.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/15ba505.txt b/.riot/requirements/15ba505.txt deleted file mode 100644 index 0de23cc2c0a..00000000000 --- a/.riot/requirements/15ba505.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/15ba505.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -googleapis-common-protos==1.65.0 -grpcio==1.66.1 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -protobuf==5.28.0 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/15c5dd6.txt b/.riot/requirements/15c5dd6.txt deleted file mode 100644 index a015618b336..00000000000 --- a/.riot/requirements/15c5dd6.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/15c5dd6.in -# -attrs==23.1.0 -beautifulsoup4==4.12.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -soupsieve==2.5 -tomli==2.0.1 -waitress==2.1.2 -webob==1.8.7 -webtest==3.0.0 -zipp==3.17.0 diff --git a/.riot/requirements/15eba42.txt b/.riot/requirements/15eba42.txt deleted file mode 100644 index e815da238a7..00000000000 --- a/.riot/requirements/15eba42.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/15eba42.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -graphql-core==3.2.3 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/15eea13.txt b/.riot/requirements/15eea13.txt deleted file mode 100644 index 882c470efc7..00000000000 --- a/.riot/requirements/15eea13.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15eea13.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -django==4.2.21 -django-configurations==2.5.1 -djangorestframework==3.15.2 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/15eebc1.txt b/.riot/requirements/15eebc1.txt deleted file mode 100644 index 04325d6e406..00000000000 --- a/.riot/requirements/15eebc1.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15eebc1.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -graphene==3.4.3 -graphql-core==3.2.6 -graphql-relay==3.2.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1631653.txt b/.riot/requirements/1631653.txt deleted file mode 100644 index 2f8bf49a9df..00000000000 --- a/.riot/requirements/1631653.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1631653.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1632ff5.txt b/.riot/requirements/1632ff5.txt deleted file mode 100644 index e382438fc6a..00000000000 --- a/.riot/requirements/1632ff5.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1632ff5.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -future==1.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -vertica-python==0.7.4 -zipp==3.20.2 diff --git a/.riot/requirements/1634f79.txt b/.riot/requirements/1634f79.txt deleted file mode 100644 index b9cc3be1e5f..00000000000 --- a/.riot/requirements/1634f79.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1634f79.in -# -attrs==25.1.0 -blinker==1.8.2 -certifi==2025.1.31 -charset-normalizer==3.4.1 -click==7.1.2 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -flask==1.1.4 -flask-openapi3==1.1.5 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pydantic==1.10.21 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.12.2 -urllib3==1.26.20 -werkzeug==1.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/163a963.txt b/.riot/requirements/163a963.txt deleted file mode 100644 index 68e73bd43c0..00000000000 --- a/.riot/requirements/163a963.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/163a963.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/164cf92.txt b/.riot/requirements/164cf92.txt deleted file mode 100644 index 83dfe13f9e2..00000000000 --- a/.riot/requirements/164cf92.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/164cf92.in -# -aiofiles==24.1.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.64.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.22 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.13.6 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/167d6de.txt b/.riot/requirements/167d6de.txt deleted file mode 100644 index 295e09e1410..00000000000 --- a/.riot/requirements/167d6de.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/167d6de.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==3.8.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/169a623.txt b/.riot/requirements/169a623.txt deleted file mode 100644 index 3b56c7174fb..00000000000 --- a/.riot/requirements/169a623.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/169a623.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/16b7aa5.txt b/.riot/requirements/16b7aa5.txt deleted file mode 100644 index 1957b9a5706..00000000000 --- a/.riot/requirements/16b7aa5.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/16b7aa5.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mariadb==1.0.11 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/16bdd8d.txt b/.riot/requirements/16bdd8d.txt deleted file mode 100644 index f248df3e158..00000000000 --- a/.riot/requirements/16bdd8d.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/16bdd8d.in -# -attrs==23.2.0 -certifi==2024.2.2 -charset-normalizer==3.3.2 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -requests==2.31.0 -requests-mock==1.11.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/16c251e.txt b/.riot/requirements/16c251e.txt deleted file mode 100644 index 31796fe5ae4..00000000000 --- a/.riot/requirements/16c251e.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/16c251e.in -# -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg==3.2.9 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/16eb426.txt b/.riot/requirements/16eb426.txt deleted file mode 100644 index e1072294f88..00000000000 --- a/.riot/requirements/16eb426.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/16eb426.in -# -attrs==23.2.0 -coverage[toml]==7.4.3 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.2 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/174cced.txt b/.riot/requirements/174cced.txt deleted file mode 100644 index 61ba59f5372..00000000000 --- a/.riot/requirements/174cced.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/174cced.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elasticsearch==7.13.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/177f4da.txt b/.riot/requirements/177f4da.txt deleted file mode 100644 index 09614cde509..00000000000 --- a/.riot/requirements/177f4da.txt +++ /dev/null @@ -1,44 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/177f4da.in -# -aiobotocore==1.0.7 -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aioitertools==0.12.0 -aiosignal==1.3.1 -async-generator==1.10 -async-timeout==5.0.1 -attrs==25.3.0 -botocore==1.15.32 -coverage[toml]==7.6.1 -docutils==0.15.2 -exceptiongroup==1.3.0 -frozenlist==1.5.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jmespath==0.10.0 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.25.11 -wrapt==1.17.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/178cd30.txt b/.riot/requirements/178cd30.txt deleted file mode 100644 index 635350b856e..00000000000 --- a/.riot/requirements/178cd30.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/178cd30.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -future==1.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -vertica-python==0.6.14 -zipp==3.20.2 diff --git a/.riot/requirements/17b0130.txt b/.riot/requirements/17b0130.txt deleted file mode 100644 index c893b33f3ff..00000000000 --- a/.riot/requirements/17b0130.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/17b0130.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-functions==1.10.1 -azure-servicebus==7.14.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/17c09be.txt b/.riot/requirements/17c09be.txt deleted file mode 100644 index 232f0a3a355..00000000000 --- a/.riot/requirements/17c09be.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/17c09be.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -mysqlclient==2.2.1 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/17d317e.txt b/.riot/requirements/17d317e.txt deleted file mode 100644 index 819553cb0e3..00000000000 --- a/.riot/requirements/17d317e.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/17d317e.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -elasticsearch6==6.8.2 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/17ec5eb.txt b/.riot/requirements/17ec5eb.txt deleted file mode 100644 index 40b68f0c906..00000000000 --- a/.riot/requirements/17ec5eb.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/17ec5eb.in -# -aiohttp==3.9.5 -aiohttp-jinja2==1.5.1 -aiosignal==1.3.1 -async-timeout==4.0.3 -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markupsafe==2.1.5 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yarl==1.9.4 -zipp==3.19.2 diff --git a/.riot/requirements/180a9be.txt b/.riot/requirements/180a9be.txt deleted file mode 100644 index ed0a3c11f03..00000000000 --- a/.riot/requirements/180a9be.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/180a9be.in -# -attrs==25.3.0 -certifi==2025.4.26 -chardet==3.0.4 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==2.7 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.20.1 -requests-mock==1.11.0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.24.3 -zipp==3.20.2 diff --git a/.riot/requirements/1810da7.txt b/.riot/requirements/1810da7.txt deleted file mode 100644 index 020c016edce..00000000000 --- a/.riot/requirements/1810da7.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1810da7.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pyodbc==4.0.39 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/181c98f.txt b/.riot/requirements/181c98f.txt deleted file mode 100644 index b89a5382948..00000000000 --- a/.riot/requirements/181c98f.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/181c98f.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1828aa7.txt b/.riot/requirements/1828aa7.txt deleted file mode 100644 index 8a7d96d3a0e..00000000000 --- a/.riot/requirements/1828aa7.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1828aa7.in -# -attrs==23.2.0 -certifi==2024.2.2 -charset-normalizer==3.3.2 -coverage[toml]==7.4.4 -docker==7.0.0 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -idna==3.7 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.0 -pluggy==1.4.0 -pytest==8.1.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.31.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.2.1 diff --git a/.riot/requirements/185fc1c.txt b/.riot/requirements/185fc1c.txt deleted file mode 100644 index f593ce365a6..00000000000 --- a/.riot/requirements/185fc1c.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/185fc1c.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==3.13.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1878fa7.txt b/.riot/requirements/1878fa7.txt deleted file mode 100644 index db98927c9c0..00000000000 --- a/.riot/requirements/1878fa7.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1878fa7.in -# -attrs==25.3.0 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opensearch-py[requests]==2.0.1 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==1.26.20 -zipp==3.20.2 diff --git a/.riot/requirements/189128e.txt b/.riot/requirements/189128e.txt deleted file mode 100644 index a90089d09b4..00000000000 --- a/.riot/requirements/189128e.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/189128e.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymysql==0.10.1 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/18abddb.txt b/.riot/requirements/18abddb.txt deleted file mode 100644 index cf90d7073c4..00000000000 --- a/.riot/requirements/18abddb.txt +++ /dev/null @@ -1,77 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/18abddb.in -# -arrow==1.3.0 -asgiref==3.8.1 -attrs==25.3.0 -autobahn==23.1.2 -automat==24.8.1 -bcrypt==4.2.1 -blessed==1.21.0 -certifi==2025.4.26 -cffi==1.17.1 -channels==3.0.5 -charset-normalizer==3.4.2 -constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==45.0.3 -daphne==3.0.2 -django==2.2.28 -django-configurations==2.3.2 -django-picklefield==3.0.1 -django-pylibmc==0.6.1 -django-q==1.3.6 -django-redis==4.5.0 -exceptiongroup==1.3.0 -hyperlink==21.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -incremental==24.7.2 -iniconfig==2.1.0 -isodate==0.7.2 -lxml==5.4.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -platformdirs==4.3.6 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pycparser==2.22 -pylibmc==1.6.3 -pyopenssl==25.1.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -python-memcached==1.62 -pytz==2025.2 -redis==2.10.6 -requests==2.32.3 -requests-file==2.1.0 -requests-toolbelt==1.0.0 -service-identity==24.2.0 -six==1.17.0 -sortedcontainers==2.4.0 -spyne==2.14.0 -sqlparse==0.5.3 -tomli==2.2.1 -twisted[tls]==24.11.0 -txaio==23.1.1 -types-python-dateutil==2.9.0.20241206 -typing-extensions==4.13.2 -urllib3==2.2.3 -wcwidth==0.2.13 -zeep==4.3.1 -zipp==3.20.2 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/18c9043.txt b/.riot/requirements/18c9043.txt deleted file mode 100644 index 93b2a354491..00000000000 --- a/.riot/requirements/18c9043.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/18c9043.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tornado==4.5.3 -zipp==3.17.0 diff --git a/.riot/requirements/192e4d0.txt b/.riot/requirements/192e4d0.txt deleted file mode 100644 index a2835589432..00000000000 --- a/.riot/requirements/192e4d0.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/192e4d0.in -# -attrs==23.2.0 -beautifulsoup4==4.12.3 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hupper==1.12.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pastedeploy==3.1.0 -plaster==1.1.2 -plaster-pastedeploy==1.0.1 -pluggy==1.5.0 -pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app -pyramid==2.0.2 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -soupsieve==2.5 -tomli==2.0.1 -translationstring==1.4 -urllib3==2.2.2 -venusian==3.1.0 -waitress==3.0.0 -webob==1.8.7 -webtest==3.0.0 -zipp==3.19.2 -zope-deprecation==5.0 -zope-interface==6.4.post2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/1951a77.txt b/.riot/requirements/1951a77.txt deleted file mode 100644 index 384b84d06d0..00000000000 --- a/.riot/requirements/1951a77.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1951a77.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -jinja2==3.1.2 -markupsafe==2.1.3 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/195a93b.txt b/.riot/requirements/195a93b.txt deleted file mode 100644 index 418997b2e76..00000000000 --- a/.riot/requirements/195a93b.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/195a93b.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -django==4.2.20 -django-configurations==2.5.1 -django-hosts==6.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/198266a.txt b/.riot/requirements/198266a.txt deleted file mode 100644 index a0a7c21269e..00000000000 --- a/.riot/requirements/198266a.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/198266a.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -dill==0.4.0 -django==4.0.10 -django-configurations==2.5.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pylibmc==1.6.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pyyaml==6.0.2 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/19aab60.txt b/.riot/requirements/19aab60.txt deleted file mode 100644 index 0bf2d25d3a2..00000000000 --- a/.riot/requirements/19aab60.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/19aab60.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -falcon==4.0.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/19aba18.txt b/.riot/requirements/19aba18.txt deleted file mode 100644 index 752af632f91..00000000000 --- a/.riot/requirements/19aba18.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/19aba18.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -dnspython==2.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==4.10.1 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/19aeb31.txt b/.riot/requirements/19aeb31.txt deleted file mode 100644 index 148e9a30091..00000000000 --- a/.riot/requirements/19aeb31.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/19aeb31.in -# -anyio==4.5.2 -asgiref==3.0.0 -async-timeout==3.0.1 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1a1c5ae.txt b/.riot/requirements/1a1c5ae.txt deleted file mode 100644 index d56f382026a..00000000000 --- a/.riot/requirements/1a1c5ae.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a1c5ae.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1a2c79e.txt b/.riot/requirements/1a2c79e.txt deleted file mode 100644 index 9edb41d3df2..00000000000 --- a/.riot/requirements/1a2c79e.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1a2c79e.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -decorator==5.1.1 -dogpile-cache==1.3.0 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pbr==6.0.0 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -stevedore==5.1.0 -tomli==2.0.1 -typing-extensions==4.9.0 -zipp==3.17.0 diff --git a/.riot/requirements/1a3a39d.txt b/.riot/requirements/1a3a39d.txt deleted file mode 100644 index 6ba873d2190..00000000000 --- a/.riot/requirements/1a3a39d.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a3a39d.in -# -aiobotocore==2.0.1 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 -aioitertools==0.11.0 -aiosignal==1.3.1 -async-generator==1.10 -async-timeout==4.0.3 -attrs==24.2.0 -botocore==1.22.8 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -jmespath==0.10.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.12.2 -urllib3==1.26.19 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.20.0 diff --git a/.riot/requirements/1a6e6c0.txt b/.riot/requirements/1a6e6c0.txt deleted file mode 100644 index b7d1ec2eb01..00000000000 --- a/.riot/requirements/1a6e6c0.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1a6e6c0.in -# -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==1.0.2 -httpx==0.26.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -zipp==3.17.0 diff --git a/.riot/requirements/1a84cc2.txt b/.riot/requirements/1a84cc2.txt deleted file mode 100644 index beb0cbbdbc9..00000000000 --- a/.riot/requirements/1a84cc2.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1a84cc2.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/1ac9ec1.txt b/.riot/requirements/1ac9ec1.txt deleted file mode 100644 index a491beef90c..00000000000 --- a/.riot/requirements/1ac9ec1.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ac9ec1.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1aca748.txt b/.riot/requirements/1aca748.txt deleted file mode 100644 index 3dac8924a3d..00000000000 --- a/.riot/requirements/1aca748.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1aca748.in -# -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-consul==1.1.0 -requests==2.31.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/1adbb5d.txt b/.riot/requirements/1adbb5d.txt deleted file mode 100644 index efa8a19a752..00000000000 --- a/.riot/requirements/1adbb5d.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1adbb5d.in -# -aiofiles==24.1.0 -aiosqlite==0.20.0 -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -databases==0.8.0 -exceptiongroup==1.3.0 -greenlet==3.1.1 -h11==0.12.0 -httpcore==0.14.7 -httpx==0.22.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -sqlalchemy==1.4.54 -starlette==0.14.2 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/1ae2797.txt b/.riot/requirements/1ae2797.txt deleted file mode 100644 index b1170153af9..00000000000 --- a/.riot/requirements/1ae2797.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ae2797.in -# -aiohttp==3.9.5 -aiohttp-jinja2==1.5.1 -aiosignal==1.3.1 -async-timeout==4.0.3 -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markupsafe==2.1.5 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yarl==1.9.4 -zipp==3.19.2 diff --git a/.riot/requirements/1af4fe2.txt b/.riot/requirements/1af4fe2.txt deleted file mode 100644 index 3a4761a3456..00000000000 --- a/.riot/requirements/1af4fe2.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1af4fe2.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -more-itertools==8.10.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==7.4.4 -pytest-cov==2.12.0 -pytest-mock==2.0.0 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1b02ea2.txt b/.riot/requirements/1b02ea2.txt deleted file mode 100644 index 73847ef6b54..00000000000 --- a/.riot/requirements/1b02ea2.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b02ea2.in -# -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.7.9 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.86.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.113.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.22 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.20.4 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/1b19707.txt b/.riot/requirements/1b19707.txt deleted file mode 100644 index 5a50cb0f571..00000000000 --- a/.riot/requirements/1b19707.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b19707.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mako==1.3.8 -markupsafe==2.1.5 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/1b6f5be.txt b/.riot/requirements/1b6f5be.txt deleted file mode 100644 index 30ccd368628..00000000000 --- a/.riot/requirements/1b6f5be.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1b6f5be.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -msgpack==1.0.7 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1ba4b57.txt b/.riot/requirements/1ba4b57.txt deleted file mode 100644 index 18b24da31a7..00000000000 --- a/.riot/requirements/1ba4b57.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ba4b57.in -# -attrs==23.2.0 -coverage[toml]==7.4.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.0 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.2.0 -zipp==3.17.0 diff --git a/.riot/requirements/1bceb88.txt b/.riot/requirements/1bceb88.txt deleted file mode 100644 index 2c50572f098..00000000000 --- a/.riot/requirements/1bceb88.txt +++ /dev/null @@ -1,56 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bceb88.in -# -aiobotocore==2.3.1 -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aioitertools==0.12.0 -aiosignal==1.3.1 -async-timeout==5.0.1 -attrs==24.3.0 -botocore==1.24.21 -certifi==2024.12.14 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -elastic-transport==8.15.1 -elasticsearch==8.17.0 -events==0.5 -exceptiongroup==1.2.2 -frozenlist==1.5.0 -gevent==20.12.1 -greenlet==1.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -jmespath==1.0.1 -mock==5.1.0 -multidict==6.1.0 -opensearch-py==2.8.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -propcache==0.2.0 -pynamodb==5.5.1 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.12.2 -urllib3==1.26.20 -wrapt==1.17.0 -yarl==1.15.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/1bf3da5.txt b/.riot/requirements/1bf3da5.txt deleted file mode 100644 index da379d432f9..00000000000 --- a/.riot/requirements/1bf3da5.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bf3da5.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mariadb==1.1.13 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1c0509d.txt b/.riot/requirements/1c0509d.txt deleted file mode 100644 index e08e98db570..00000000000 --- a/.riot/requirements/1c0509d.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1c0509d.in -# -async-timeout==4.0.3 -attrs==23.1.0 -click==7.1.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -redis==5.0.1 -rq==1.8.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1c0ccc9.txt b/.riot/requirements/1c0ccc9.txt deleted file mode 100644 index 8a49d5e9d54..00000000000 --- a/.riot/requirements/1c0ccc9.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1c0ccc9.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elasticsearch==7.17.9 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/1c1da8c.txt b/.riot/requirements/1c1da8c.txt deleted file mode 100644 index 090dda34995..00000000000 --- a/.riot/requirements/1c1da8c.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1c1da8c.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==3.5.3 -redis-py-cluster==2.1.3 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 diff --git a/.riot/requirements/1c31001.txt b/.riot/requirements/1c31001.txt deleted file mode 100644 index 0aa511b0f41..00000000000 --- a/.riot/requirements/1c31001.txt +++ /dev/null @@ -1,50 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c31001.in -# -annotated-types==0.7.0 -anthropic==0.28.1 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -filelock==3.16.1 -fsspec==2025.3.0 -h11==0.16.0 -hf-xet==1.1.3 -httpcore==1.0.9 -httpx==0.27.2 -huggingface-hub==0.32.4 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.9.1 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pyyaml==6.0.2 -requests==2.32.3 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tokenizers==0.21.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -yarl==1.15.2 diff --git a/.riot/requirements/1c56cf0.txt b/.riot/requirements/1c56cf0.txt deleted file mode 100644 index d292b56cb3d..00000000000 --- a/.riot/requirements/1c56cf0.txt +++ /dev/null @@ -1,74 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c56cf0.in -# -annotated-types==0.7.0 -attrs==25.3.0 -aws-sam-translator==1.98.0 -aws-xray-sdk==2.14.0 -boto==2.49.0 -boto3==1.22.0 -botocore==1.25.0 -certifi==2025.4.26 -cffi==1.17.1 -cfn-lint==0.53.1 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -cryptography==45.0.3 -docker==7.1.0 -ecdsa==0.14.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -idna==2.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jinja2==2.10.3 -jmespath==1.0.1 -jsondiff==2.2.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -jsonschema==3.2.0 -junit-xml==1.9 -markupsafe==1.1.1 -mock==5.2.0 -more-itertools==10.5.0 -moto==1.3.16 -networkx==2.8.8 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pyasn1==0.4.8 -pycparser==2.22 -pydantic==2.10.6 -pydantic-core==2.27.2 -pynamodb==5.0.3 -pyrsistent==0.20.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -python-dateutil==2.9.0.post0 -python-jose[cryptography]==3.4.0 -pytz==2025.2 -pyyaml==6.0.2 -requests==2.32.4 -responses==0.25.7 -rsa==4.9.1 -s3transfer==0.5.2 -six==1.17.0 -sortedcontainers==2.4.0 -sshpubkeys==3.3.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -xmltodict==0.14.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/1c84e93.txt b/.riot/requirements/1c84e93.txt deleted file mode 100644 index 66fc775af92..00000000000 --- a/.riot/requirements/1c84e93.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c84e93.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-eventhub==5.15.0 -azure-functions==1.23.0 -azure-storage-blob==12.26.0 -certifi==2025.8.3 -cffi==1.17.1 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -cryptography==46.0.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pycparser==2.23 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 diff --git a/.riot/requirements/1c87bc4.txt b/.riot/requirements/1c87bc4.txt deleted file mode 100644 index bc50f51cc3a..00000000000 --- a/.riot/requirements/1c87bc4.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1c87bc4.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -mysql-connector-python==8.0.5 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1cc7b0e.txt b/.riot/requirements/1cc7b0e.txt deleted file mode 100644 index adb8f71e30b..00000000000 --- a/.riot/requirements/1cc7b0e.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1cc7b0e.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yaaredis==2.0.4 -zipp==3.17.0 diff --git a/.riot/requirements/1cda235.txt b/.riot/requirements/1cda235.txt deleted file mode 100644 index 5b372bb3fec..00000000000 --- a/.riot/requirements/1cda235.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1cda235.in -# -aiopg==1.4.0 -async-timeout==4.0.3 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.41 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1ce3412.txt b/.riot/requirements/1ce3412.txt deleted file mode 100644 index 2013dc5e8b8..00000000000 --- a/.riot/requirements/1ce3412.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ce3412.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.12.3 -httpx==0.17.1 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1ce4e3f.txt b/.riot/requirements/1ce4e3f.txt deleted file mode 100644 index 744c86d71c0..00000000000 --- a/.riot/requirements/1ce4e3f.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ce4e3f.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jiter==0.9.1 -mock==5.2.0 -multidict==6.1.0 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==10.4.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.7.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/1cef696.txt b/.riot/requirements/1cef696.txt deleted file mode 100644 index 7a7725cdf1a..00000000000 --- a/.riot/requirements/1cef696.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1cef696.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -googleapis-common-protos==1.65.0 -grpcio==1.34.1 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -protobuf==5.28.0 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/1d23fbc.txt b/.riot/requirements/1d23fbc.txt deleted file mode 100644 index ba4db809a86..00000000000 --- a/.riot/requirements/1d23fbc.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1d23fbc.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -jinja2==2.10.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/1d38b9f.txt b/.riot/requirements/1d38b9f.txt deleted file mode 100644 index 2be422fecc0..00000000000 --- a/.riot/requirements/1d38b9f.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d38b9f.in -# -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/1d390e8.txt b/.riot/requirements/1d390e8.txt deleted file mode 100644 index e288067465c..00000000000 --- a/.riot/requirements/1d390e8.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d390e8.in -# -aiobotocore==2.13.3 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 -aioitertools==0.11.0 -aiosignal==1.3.1 -async-generator==1.10 -async-timeout==4.0.3 -attrs==24.2.0 -botocore==1.34.162 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -jmespath==1.0.1 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.12.2 -urllib3==1.26.19 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.20.0 diff --git a/.riot/requirements/1d788df.txt b/.riot/requirements/1d788df.txt deleted file mode 100644 index 29c6cd06a17..00000000000 --- a/.riot/requirements/1d788df.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d788df.in -# -attrs==25.3.0 -certifi==2025.7.9 -charset-normalizer==3.4.2 -click==7.1.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==1.1.4 -hypothesis==6.113.0 -idna==3.10 -iniconfig==2.1.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==1.0.1 diff --git a/.riot/requirements/1d81907.txt b/.riot/requirements/1d81907.txt deleted file mode 100644 index cc2a4fca3c9..00000000000 --- a/.riot/requirements/1d81907.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d81907.in -# -astunparse==1.6.3 -attrs==25.3.0 -certifi==2025.7.14 -cffi==1.17.1 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -cryptography==45.0.5 -exceptiongroup==1.3.0 -grpcio==1.70.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pycparser==2.22 -pycryptodome==3.23.0 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -simplejson==3.20.1 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -wheel==0.45.1 diff --git a/.riot/requirements/1db8cf2.txt b/.riot/requirements/1db8cf2.txt deleted file mode 100644 index e84a492d5e7..00000000000 --- a/.riot/requirements/1db8cf2.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1db8cf2.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -greenlet==3.0.3 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mysql-connector-python==9.0.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.40 -tomli==2.2.1 -typing-extensions==4.13.1 -zipp==3.20.2 diff --git a/.riot/requirements/1dcf37e.txt b/.riot/requirements/1dcf37e.txt deleted file mode 100644 index 458a62f2355..00000000000 --- a/.riot/requirements/1dcf37e.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1dcf37e.in -# -aiopg==0.16.0 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.41 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1dd7f62.txt b/.riot/requirements/1dd7f62.txt deleted file mode 100644 index a9e66451ce7..00000000000 --- a/.riot/requirements/1dd7f62.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dd7f62.in -# -amqp==5.3.1 -attrs==25.3.0 -backports-zoneinfo[tzdata]==0.2.1 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==5.5.4 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -tzdata==2025.2 -vine==5.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/1df8347.txt b/.riot/requirements/1df8347.txt deleted file mode 100644 index ca1c3a6ec3f..00000000000 --- a/.riot/requirements/1df8347.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1df8347.in -# -attrs==24.3.0 -certifi==2024.12.14 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==2.2.3 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/1dfd438.txt b/.riot/requirements/1dfd438.txt deleted file mode 100644 index 32ced73b7f7..00000000000 --- a/.riot/requirements/1dfd438.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dfd438.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymysql==1.1.1 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/1e08b64.txt b/.riot/requirements/1e08b64.txt deleted file mode 100644 index 1145707ee4c..00000000000 --- a/.riot/requirements/1e08b64.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e08b64.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.0.8 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1e0e29e.txt b/.riot/requirements/1e0e29e.txt deleted file mode 100644 index 2ba80bbb6ef..00000000000 --- a/.riot/requirements/1e0e29e.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1e0e29e.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -loguru==0.7.2 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1e3534f.txt b/.riot/requirements/1e3534f.txt deleted file mode 100644 index 6f5850a6d4f..00000000000 --- a/.riot/requirements/1e3534f.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e3534f.in -# -attrs==23.2.0 -cheroot==10.0.1 -cherrypy==17.4.2 -contextlib2==21.6.0 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -jaraco-functools==4.0.1 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -portend==3.2.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tempora==5.6.0 -tomli==2.0.1 -typing-extensions==4.12.2 -zc-lockfile==3.0.post1 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/1e649b4.txt b/.riot/requirements/1e649b4.txt deleted file mode 100644 index 964238d148f..00000000000 --- a/.riot/requirements/1e649b4.txt +++ /dev/null @@ -1,55 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1e649b4.in -# -amqp==5.3.1 -attrs==25.3.0 -backports-zoneinfo[tzdata]==0.2.1 -billiard==4.2.1 -celery==5.5.3 -certifi==2025.4.26 -charset-normalizer==3.4.2 -click==8.1.8 -click-didyoumean==0.3.1 -click-plugins==1.1.1 -click-repl==0.3.0 -coverage[toml]==7.6.1 -django==2.2.28 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==5.5.4 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -prompt-toolkit==3.0.51 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -pytz==2025.2 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlalchemy==1.2.19 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -tzdata==2025.2 -urllib3==2.2.3 -vine==5.1.0 -wcwidth==0.2.13 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/1e8124b.txt b/.riot/requirements/1e8124b.txt deleted file mode 100644 index e9d4b404a12..00000000000 --- a/.riot/requirements/1e8124b.txt +++ /dev/null @@ -1,54 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e8124b.in -# -annotated-types==0.7.0 -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.4.26 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -numpy==1.24.4 -openai[datalib,embeddings]==1.0.0 -opentracing==2.4.0 -packaging==25.0 -pandas==2.0.3 -pandas-stubs==2.0.3.230814 -pillow==9.5.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -pytz==2025.2 -pyyaml==6.0.2 -six==1.17.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -tqdm==4.67.1 -types-pytz==2024.2.0.20241221 -typing-extensions==4.13.2 -tzdata==2025.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/1ea308d.txt b/.riot/requirements/1ea308d.txt deleted file mode 100644 index 8c8ddfcc11f..00000000000 --- a/.riot/requirements/1ea308d.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ea308d.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -certifi==2025.6.15 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -django==4.0.10 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/1eb29d6.txt b/.riot/requirements/1eb29d6.txt deleted file mode 100644 index 2de32e68a6d..00000000000 --- a/.riot/requirements/1eb29d6.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1eb29d6.in -# -attrs==23.2.0 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.25 -zipp==3.17.0 diff --git a/.riot/requirements/1eded52.txt b/.riot/requirements/1eded52.txt deleted file mode 100644 index 145cebeb4a7..00000000000 --- a/.riot/requirements/1eded52.txt +++ /dev/null @@ -1,44 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1eded52.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn[gevent]==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/1ee2a7f.txt b/.riot/requirements/1ee2a7f.txt deleted file mode 100644 index 714af2c4877..00000000000 --- a/.riot/requirements/1ee2a7f.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/1ee2a7f.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 diff --git a/.riot/requirements/1ef7371.txt b/.riot/requirements/1ef7371.txt deleted file mode 100644 index c94f76cedcb..00000000000 --- a/.riot/requirements/1ef7371.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1ef7371.in -# -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -hypothesis==6.45.0 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==3.0.1 -redis-py-cluster==2.0.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 diff --git a/.riot/requirements/1efb912.txt b/.riot/requirements/1efb912.txt deleted file mode 100644 index 50742922dd8..00000000000 --- a/.riot/requirements/1efb912.txt +++ /dev/null @@ -1,47 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1efb912.in -# -attrs==25.3.0 -certifi==2025.4.26 -cffi==1.17.1 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -cryptography==45.0.3 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jinja2==3.1.6 -linkify-it-py==2.0.3 -markdown-it-py[linkify,plugins]==3.0.0 -markupsafe==2.1.5 -mdit-py-plugins==0.4.2 -mdurl==0.1.2 -memray==1.17.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -platformdirs==4.3.6 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pycparser==2.22 -pycryptodome==3.23.0 -pygments==2.19.1 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-memray==1.7.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.3 -rich==14.0.0 -sortedcontainers==2.4.0 -textual==3.2.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uc-micro-py==1.0.3 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/1f27e33.txt b/.riot/requirements/1f27e33.txt deleted file mode 100644 index c4a6c126e7f..00000000000 --- a/.riot/requirements/1f27e33.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1f27e33.in -# -attrs==23.1.0 -confluent-kafka==2.3.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1f2ab25.txt b/.riot/requirements/1f2ab25.txt deleted file mode 100644 index ee70e55666e..00000000000 --- a/.riot/requirements/1f2ab25.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f2ab25.in -# -async-timeout==5.0.1 -asyncpg==0.30.0 -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-asyncio==0.21.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/1f540f4.txt b/.riot/requirements/1f540f4.txt deleted file mode 100644 index 46b38265655..00000000000 --- a/.riot/requirements/1f540f4.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/1f540f4.in -# -attrs==24.2.0 -boto3==1.35.45 -botocore==1.35.45 -bytecode==0.15.1 -cattrs==23.2.3 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.5.4 -datadog==0.51.0 -datadog-lambda==6.105.0 -ddsketch==3.0.1 -ddtrace==2.20.0 -deprecated==1.2.14 -envier==0.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -jmespath==1.0.1 -mock==5.1.0 -opentelemetry-api==1.27.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -protobuf==5.28.2 -pytest==8.3.3 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -s3transfer==0.10.3 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.2 -typing-extensions==4.12.2 -ujson==5.10.0 -urllib3==1.26.20 -wrapt==1.16.0 -xmltodict==0.14.2 -zipp==3.20.2 diff --git a/.riot/requirements/1f9c58a.txt b/.riot/requirements/1f9c58a.txt deleted file mode 100644 index 141f6723214..00000000000 --- a/.riot/requirements/1f9c58a.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f9c58a.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -httpretty==1.1.4 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py-cpuinfo==9.0.0 -pyfakefs==5.10.0 -pytest==8.3.5 -pytest-asyncio==0.23.8 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-json-logger==2.0.7 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -wrapt==1.17.3 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/1fb1389.txt b/.riot/requirements/1fb1389.txt deleted file mode 100644 index 6006e992b98..00000000000 --- a/.riot/requirements/1fb1389.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1fb1389.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pymemcache==3.5.2 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1fcb05f.txt b/.riot/requirements/1fcb05f.txt deleted file mode 100644 index a9332da417c..00000000000 --- a/.riot/requirements/1fcb05f.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1fcb05f.in -# -amqp==2.6.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==4.6.11 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -vine==1.3.0 -zipp==3.20.2 diff --git a/.riot/requirements/1fe5c31.txt b/.riot/requirements/1fe5c31.txt deleted file mode 100644 index 106cb794d61..00000000000 --- a/.riot/requirements/1fe5c31.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1fe5c31.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -structlog==23.2.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/1ffebce.txt b/.riot/requirements/1ffebce.txt deleted file mode 100644 index 5b613bc5d30..00000000000 --- a/.riot/requirements/1ffebce.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ffebce.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -more-itertools==8.10.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==2.12.0 -pytest-mock==2.0.0 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/20699e5.txt b/.riot/requirements/20699e5.txt deleted file mode 100644 index 75d6b416d16..00000000000 --- a/.riot/requirements/20699e5.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/20699e5.in -# -asyncpg==0.22.0 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.2 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/206be6b.txt b/.riot/requirements/206be6b.txt deleted file mode 100644 index 2b2d3633eca..00000000000 --- a/.riot/requirements/206be6b.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/206be6b.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.7.9 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.114.2 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.113.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/24618e2.txt b/.riot/requirements/24618e2.txt deleted file mode 100644 index 2481c88634f..00000000000 --- a/.riot/requirements/24618e2.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/24618e2.in -# -aiofiles==24.1.0 -aiosqlite==0.20.0 -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -databases==0.8.0 -exceptiongroup==1.3.0 -greenlet==3.1.1 -h11==0.12.0 -httpcore==0.14.7 -httpx==0.22.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -sqlalchemy==1.4.54 -starlette==0.20.4 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/260ead7.txt b/.riot/requirements/260ead7.txt deleted file mode 100644 index f006fcd4f84..00000000000 --- a/.riot/requirements/260ead7.txt +++ /dev/null @@ -1,48 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/260ead7.in -# -aiofiles==23.2.1 -aiohttp==3.9.1 -aiosignal==1.3.1 -async-generator==1.10 -async-timeout==4.0.3 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -frozenlist==1.4.1 -h11==0.9.0 -httpcore==0.11.1 -httptools==0.6.1 -httpx==0.15.4 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==5.2.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -pytest-sanic==1.6.2 -requests==2.31.0 -rfc3986[idna2008]==1.5.0 -sanic==20.12.7 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -ujson==5.9.0 -urllib3==2.1.0 -uvloop==0.19.0 -websockets==9.1 -yarl==1.9.4 -zipp==3.17.0 diff --git a/.riot/requirements/2715c88.txt b/.riot/requirements/2715c88.txt deleted file mode 100644 index ed246768e2e..00000000000 --- a/.riot/requirements/2715c88.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/2715c88.in -# -aiofiles==24.1.0 -aiosqlite==0.20.0 -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -databases==0.8.0 -exceptiongroup==1.3.0 -greenlet==3.1.1 -h11==0.12.0 -httpcore==0.14.7 -httpx==0.22.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -sqlalchemy==1.4.54 -starlette==0.33.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/273fcaf.txt b/.riot/requirements/273fcaf.txt deleted file mode 100644 index eb4ea0f7ab0..00000000000 --- a/.riot/requirements/273fcaf.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/273fcaf.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -py-cpuinfo==9.0.0 -pytest==8.3.5 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/2be0e27.txt b/.riot/requirements/2be0e27.txt deleted file mode 100644 index da5795c27eb..00000000000 --- a/.riot/requirements/2be0e27.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/2be0e27.in -# -aiofiles==23.2.1 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.16.3 -httptools==0.6.1 -httpx==0.23.3 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==5.2.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -requests==2.31.0 -rfc3986[idna2008]==1.5.0 -sanic==21.12.2 -sanic-routing==0.7.2 -sanic-testing==0.8.3 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -ujson==5.9.0 -urllib3==2.1.0 -uvloop==0.19.0 -websockets==10.4 -zipp==3.17.0 diff --git a/.riot/requirements/2c0f966.txt b/.riot/requirements/2c0f966.txt deleted file mode 100644 index 6291808fc42..00000000000 --- a/.riot/requirements/2c0f966.txt +++ /dev/null @@ -1,39 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/2c0f966.in -# -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.86.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.24 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.20.4 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/2d3b0ef.txt b/.riot/requirements/2d3b0ef.txt deleted file mode 100644 index d99a88b036e..00000000000 --- a/.riot/requirements/2d3b0ef.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/2d3b0ef.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -graphql-core==3.2.3 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/2f7da3e.txt b/.riot/requirements/2f7da3e.txt deleted file mode 100644 index 362060f9ca9..00000000000 --- a/.riot/requirements/2f7da3e.txt +++ /dev/null @@ -1,88 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/2f7da3e.in -# -annotated-types==0.7.0 -attrs==25.3.0 -aws-sam-translator==1.100.0 -aws-xray-sdk==2.14.0 -boto3==1.34.49 -botocore==1.34.49 -certifi==2025.8.3 -cffi==1.17.1 -cfn-lint==1.26.1 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -cryptography==45.0.7 -docker==7.1.0 -ecdsa==0.19.1 -exceptiongroup==1.3.0 -graphql-core==3.2.6 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jinja2==3.1.6 -jmespath==1.0.1 -jsondiff==2.2.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -jsonschema==4.23.0 -jsonschema-path==0.3.4 -jsonschema-specifications==2023.12.1 -lazy-object-proxy==1.10.0 -markupsafe==2.1.5 -mock==5.2.0 -moto[all]==4.2.14 -mpmath==1.3.0 -multidict==6.1.0 -multipart==1.3.0 -networkx==3.1 -openapi-schema-validator==0.6.3 -openapi-spec-validator==0.7.2 -opentracing==2.4.0 -packaging==25.0 -pathable==0.4.4 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -propcache==0.2.0 -py-partiql-parser==0.5.0 -pyasn1==0.4.8 -pycparser==2.23 -pydantic==2.10.6 -pydantic-core==2.27.2 -pyparsing==3.1.4 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -python-jose[cryptography]==3.4.0 -pyyaml==6.0.2 -referencing==0.35.1 -regex==2024.11.6 -requests==2.32.4 -responses==0.25.8 -rfc3339-validator==0.1.4 -rpds-py==0.20.1 -rsa==4.9.1 -s3transfer==0.10.4 -six==1.17.0 -sortedcontainers==2.4.0 -sshpubkeys==3.3.1 -sympy==1.13.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.1 -werkzeug==3.0.6 -wrapt==1.17.3 -xmltodict==0.15.0 -yarl==1.15.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/3007b59.txt b/.riot/requirements/3007b59.txt deleted file mode 100644 index ae662d03dd9..00000000000 --- a/.riot/requirements/3007b59.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3007b59.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/30641af.txt b/.riot/requirements/30641af.txt deleted file mode 100644 index 407ecbf61ed..00000000000 --- a/.riot/requirements/30641af.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/30641af.in -# -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -django==2.2.28 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/30b2227.txt b/.riot/requirements/30b2227.txt deleted file mode 100644 index 11938ffc708..00000000000 --- a/.riot/requirements/30b2227.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/30b2227.in -# -aiohttp==3.9.5 -aiohttp-jinja2==1.6 -aiosignal==1.3.1 -async-timeout==4.0.3 -attrs==23.2.0 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -jinja2==3.1.4 -markupsafe==2.1.5 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-aiohttp==1.0.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yarl==1.9.4 -zipp==3.19.2 diff --git a/.riot/requirements/315c2cb.txt b/.riot/requirements/315c2cb.txt deleted file mode 100644 index 8a45f9b13fe..00000000000 --- a/.riot/requirements/315c2cb.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/315c2cb.in -# -async-timeout==5.0.1 -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.3 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==4.6.0 -sortedcontainers==2.4.0 -tomli==2.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/3348fe3.txt b/.riot/requirements/3348fe3.txt deleted file mode 100644 index 956b6f44882..00000000000 --- a/.riot/requirements/3348fe3.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3348fe3.in -# -attrs==23.2.0 -beautifulsoup4==4.12.3 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hupper==1.12.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pastedeploy==3.1.0 -plaster==1.1.2 -plaster-pastedeploy==1.0.1 -pluggy==1.5.0 -pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app -pyramid==2.0.2 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -soupsieve==2.5 -tomli==2.0.1 -translationstring==1.4 -urllib3==2.2.2 -venusian==3.1.0 -waitress==3.0.0 -webob==1.8.7 -webtest==3.0.0 -zipp==3.19.2 -zope-deprecation==5.0 -zope-interface==6.4.post2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/3a2a320.txt b/.riot/requirements/3a2a320.txt deleted file mode 100644 index 0b00a03ec2b..00000000000 --- a/.riot/requirements/3a2a320.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3a2a320.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 diff --git a/.riot/requirements/3aa457c.txt b/.riot/requirements/3aa457c.txt deleted file mode 100644 index 0f35c37a47a..00000000000 --- a/.riot/requirements/3aa457c.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3aa457c.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==7.1.2 -coverage[toml]==7.6.1 -django==4.2.24 -exceptiongroup==1.3.0 -flask==1.1.4 -gunicorn==23.0.0 -httpretty==1.0.5 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==1.0.1 -xmltodict==0.15.0 -zipp==3.20.2 diff --git a/.riot/requirements/3b65323.txt b/.riot/requirements/3b65323.txt deleted file mode 100644 index 6e7fb0a5c7f..00000000000 --- a/.riot/requirements/3b65323.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/3b65323.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -mysqlclient==2.2.1 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/3ba7e37.txt b/.riot/requirements/3ba7e37.txt deleted file mode 100644 index 3dbb32d1178..00000000000 --- a/.riot/requirements/3ba7e37.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3ba7e37.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -glob2==0.7 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mako==1.3.10 -markupsafe==2.1.5 -mock==5.2.0 -more-itertools==8.10.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -parse==1.20.2 -parse-type==0.6.4 -pluggy==1.5.0 -py==1.11.0 -pytest==7.4.4 -pytest-bdd==6.0.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/3f2ebdc.txt b/.riot/requirements/3f2ebdc.txt deleted file mode 100644 index a8cdfd63d33..00000000000 --- a/.riot/requirements/3f2ebdc.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3f2ebdc.in -# -annotated-types==0.7.0 -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==2.3.3 -flask-openapi3==4.0.3 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==3.0.6 -zipp==3.20.2 diff --git a/.riot/requirements/3f3ce6e.txt b/.riot/requirements/3f3ce6e.txt deleted file mode 100644 index 15223e399f6..00000000000 --- a/.riot/requirements/3f3ce6e.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/3f3ce6e.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-eventhub==5.15.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/3f40530.txt b/.riot/requirements/3f40530.txt deleted file mode 100644 index 125f04e194c..00000000000 --- a/.riot/requirements/3f40530.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/3f40530.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -loguru==0.4.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/40adc31.txt b/.riot/requirements/40adc31.txt deleted file mode 100644 index 21dbd2582a2..00000000000 --- a/.riot/requirements/40adc31.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/40adc31.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.7.9 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.94.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.113.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.22 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.26.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/44339c7.txt b/.riot/requirements/44339c7.txt deleted file mode 100644 index 2aa39fbf0f5..00000000000 --- a/.riot/requirements/44339c7.txt +++ /dev/null @@ -1,56 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/44339c7.in -# -anyio==4.2.0 -asn1crypto==1.5.1 -attrs==23.1.0 -azure-common==1.1.28 -azure-core==1.29.6 -azure-storage-blob==12.19.0 -boto3==1.34.6 -botocore==1.34.6 -certifi==2020.12.5 -cffi==1.16.0 -chardet==3.0.4 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -cryptography==3.4.8 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -idna==2.10 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -isodate==0.6.1 -jmespath==1.0.1 -mock==5.1.0 -opentracing==2.4.0 -oscrypto==1.3.0 -packaging==23.2 -pluggy==1.3.0 -pycparser==2.21 -pycryptodomex==3.19.0 -pyjwt==2.8.0 -pyopenssl==19.1.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-dateutil==2.8.2 -pytz==2020.5 -requests==2.31.0 -responses==0.16.0 -s3transfer==0.10.0 -six==1.16.0 -sniffio==1.3.0 -snowflake-connector-python==2.3.10 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -urllib3==1.26.18 -zipp==3.17.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/4ad5317.txt b/.riot/requirements/4ad5317.txt deleted file mode 100644 index 9d6cecbc8e5..00000000000 --- a/.riot/requirements/4ad5317.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/4ad5317.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -psycopg2-binary==2.8.6 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/4de03a5.txt b/.riot/requirements/4de03a5.txt deleted file mode 100644 index 8fa32aa29f3..00000000000 --- a/.riot/requirements/4de03a5.txt +++ /dev/null @@ -1,79 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/4de03a5.in -# -arrow==1.3.0 -asgiref==3.8.1 -attrs==24.3.0 -autobahn==23.1.2 -automat==24.8.1 -backports-zoneinfo==0.2.1 -bcrypt==4.2.1 -blessed==1.20.0 -certifi==2024.12.14 -cffi==1.17.1 -channels==4.2.0 -charset-normalizer==3.4.0 -constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==44.0.0 -daphne==4.1.2 -django==4.2.17 -django-configurations==2.5.1 -django-picklefield==3.2 -django-pylibmc==0.6.1 -django-q==1.3.6 -django-redis==4.5.0 -exceptiongroup==1.2.2 -hyperlink==21.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -incremental==24.7.2 -iniconfig==2.0.0 -isodate==0.7.2 -lxml==5.3.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -platformdirs==4.3.6 -pluggy==1.5.0 -psycopg==3.2.3 -psycopg2-binary==2.9.10 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pycparser==2.22 -pylibmc==1.6.3 -pyopenssl==24.3.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -python-memcached==1.62 -pytz==2024.2 -redis==2.10.6 -requests==2.32.3 -requests-file==2.1.0 -requests-toolbelt==1.0.0 -service-identity==24.2.0 -six==1.17.0 -sortedcontainers==2.4.0 -spyne==2.14.0 -sqlparse==0.5.3 -tomli==2.2.1 -twisted[tls]==24.11.0 -txaio==23.1.1 -types-python-dateutil==2.9.0.20241206 -typing-extensions==4.12.2 -urllib3==2.2.3 -wcwidth==0.2.13 -zeep==4.3.1 -zipp==3.20.2 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/4ef6c1c.txt b/.riot/requirements/4ef6c1c.txt deleted file mode 100644 index b17633d8a1a..00000000000 --- a/.riot/requirements/4ef6c1c.txt +++ /dev/null @@ -1,47 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate --resolver=backtracking .riot/requirements/4ef6c1c.in -# -attrs==25.1.0 -boto3==1.36.19 -botocore==1.36.19 -bytecode==0.16.1 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -datadog==0.51.0 -datadog-lambda==6.105.0 -ddtrace==2.20.1 -deprecated==1.2.18 -envier==0.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -jmespath==1.0.1 -mock==5.1.0 -opentelemetry-api==1.30.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -protobuf==5.29.3 -pytest==8.3.4 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -s3transfer==0.11.2 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.12.2 -ujson==5.10.0 -urllib3==1.26.20 -wrapt==1.17.2 -xmltodict==0.14.2 -zipp==3.20.2 diff --git a/.riot/requirements/4f441db.txt b/.riot/requirements/4f441db.txt deleted file mode 100644 index 8bcbc844c30..00000000000 --- a/.riot/requirements/4f441db.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/4f441db.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -dogpile-cache==0.6.8 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/4f4caf8.txt b/.riot/requirements/4f4caf8.txt deleted file mode 100644 index 7441d0631a9..00000000000 --- a/.riot/requirements/4f4caf8.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/4f4caf8.in -# -attrs==25.3.0 -babel==2.17.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -markupsafe==2.1.5 -mock==5.2.0 -mysql-connector-python==9.0.0 -mysqlclient==2.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pymysql==1.1.2 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-xdist==3.6.1 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlalchemy==2.0.43 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/4f9be04.txt b/.riot/requirements/4f9be04.txt deleted file mode 100644 index 1bc07ce87aa..00000000000 --- a/.riot/requirements/4f9be04.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/4f9be04.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -elasticsearch2==2.5.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/50b70d9.txt b/.riot/requirements/50b70d9.txt deleted file mode 100644 index 8bedee18d6e..00000000000 --- a/.riot/requirements/50b70d9.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/50b70d9.in -# -asgiref==3.8.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -django==3.2.25 -django-configurations==2.5.1 -django-hosts==4.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/55b2430.txt b/.riot/requirements/55b2430.txt deleted file mode 100644 index 9e5c9096838..00000000000 --- a/.riot/requirements/55b2430.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/55b2430.in -# -attrs==25.3.0 -cattrs==22.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -molten==1.0.2 -mypy-extensions==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==3.10.0.2 -typing-inspect==0.6.0 -zipp==3.20.2 diff --git a/.riot/requirements/5b0fa38.txt b/.riot/requirements/5b0fa38.txt deleted file mode 100644 index 82a78c0afdc..00000000000 --- a/.riot/requirements/5b0fa38.txt +++ /dev/null @@ -1,39 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/5b0fa38.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.94.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.24 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.26.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/5b339ac.txt b/.riot/requirements/5b339ac.txt deleted file mode 100644 index 9c52400986b..00000000000 --- a/.riot/requirements/5b339ac.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/5b339ac.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elasticsearch7==7.17.9 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/5b55f2d.txt b/.riot/requirements/5b55f2d.txt deleted file mode 100644 index 4d502d83648..00000000000 --- a/.riot/requirements/5b55f2d.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/5b55f2d.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elastic-transport==8.11.0 -elasticsearch8==8.11.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/5e79012.txt b/.riot/requirements/5e79012.txt deleted file mode 100644 index 22de4e112ba..00000000000 --- a/.riot/requirements/5e79012.txt +++ /dev/null @@ -1,44 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/5e79012.in -# -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aiosignal==1.3.1 -async-timeout==5.0.1 -attrs==25.3.0 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -elastic-transport==8.17.1 -elasticsearch[async]==9.0.0 -elasticsearch7[async]==7.17.12 -events==0.5 -exceptiongroup==1.2.2 -frozenlist==1.5.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -opensearch-py[async]==2.8.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -propcache==0.2.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/5ed7bed.txt b/.riot/requirements/5ed7bed.txt deleted file mode 100644 index 8d62589d83b..00000000000 --- a/.riot/requirements/5ed7bed.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/5ed7bed.in -# -attrs==23.1.0 -confluent-kafka==1.9.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/610b7cb.txt b/.riot/requirements/610b7cb.txt deleted file mode 100644 index 59a69bc25a6..00000000000 --- a/.riot/requirements/610b7cb.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/610b7cb.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elastic-transport==8.11.0 -elasticsearch==8.11.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/65abb19.txt b/.riot/requirements/65abb19.txt deleted file mode 100644 index 3144d75498d..00000000000 --- a/.riot/requirements/65abb19.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/65abb19.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.114.2 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/66e0a12.txt b/.riot/requirements/66e0a12.txt deleted file mode 100644 index bda03628b26..00000000000 --- a/.riot/requirements/66e0a12.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/66e0a12.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.10.5 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jiter==0.9.1 -mock==5.2.0 -multidict==6.1.0 -openai==1.109.1 -opentracing==2.4.0 -packaging==25.0 -pillow==10.4.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pyyaml==6.0.3 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.7.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.3 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/6724bb2.txt b/.riot/requirements/6724bb2.txt deleted file mode 100644 index 8962a3db440..00000000000 --- a/.riot/requirements/6724bb2.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/6724bb2.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -certifi==2025.6.15 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -django==4.2.23 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/685a359.txt b/.riot/requirements/685a359.txt deleted file mode 100644 index 72c9d9c6554..00000000000 --- a/.riot/requirements/685a359.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/685a359.in -# -aiofiles==23.2.1 -anyio==4.2.0 -attrs==23.1.0 -certifi==2023.11.17 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -h11==0.14.0 -httpcore==0.16.3 -httptools==0.6.1 -httpx==0.23.3 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -multidict==5.2.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -requests==2.31.0 -rfc3986[idna2008]==1.5.0 -sanic==21.12.2 -sanic-routing==0.7.2 -sanic-testing==0.8.3 -sniffio==1.3.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.9.0 -ujson==5.9.0 -urllib3==2.1.0 -uvloop==0.19.0 -websockets==10.4 -zipp==3.17.0 diff --git a/.riot/requirements/696c125.txt b/.riot/requirements/696c125.txt deleted file mode 100644 index 6dfb1e7605d..00000000000 --- a/.riot/requirements/696c125.txt +++ /dev/null @@ -1,74 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/696c125.in -# -annotated-types==0.7.0 -attrs==25.3.0 -aws-sam-translator==1.97.0 -aws-xray-sdk==2.14.0 -boto==2.49.0 -boto3==1.37.38 -botocore==1.37.38 -certifi==2025.4.26 -cffi==1.17.1 -cfn-lint==0.53.1 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -cryptography==45.0.3 -docker==7.1.0 -ecdsa==0.14.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -idna==2.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jinja2==2.10.3 -jmespath==1.0.1 -jsondiff==2.2.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -jsonschema==3.2.0 -junit-xml==1.9 -markupsafe==1.1.1 -mock==5.2.0 -more-itertools==10.5.0 -moto==1.3.16 -networkx==2.8.8 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pyasn1==0.4.8 -pycparser==2.22 -pydantic==2.10.6 -pydantic-core==2.27.2 -pynamodb==5.5.1 -pyrsistent==0.20.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -python-dateutil==2.9.0.post0 -python-jose[cryptography]==3.4.0 -pytz==2025.2 -pyyaml==6.0.2 -requests==2.32.3 -responses==0.25.7 -rsa==4.9.1 -s3transfer==0.11.5 -six==1.17.0 -sortedcontainers==2.4.0 -sshpubkeys==3.3.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -xmltodict==0.14.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/6a87378.txt b/.riot/requirements/6a87378.txt deleted file mode 100644 index 8e0eabae4ad..00000000000 --- a/.riot/requirements/6a87378.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6a87378.in -# -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.7.9 -charset-normalizer==3.4.2 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==3.0.3 -hypothesis==6.113.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 -zipp==3.20.2 diff --git a/.riot/requirements/6bec1ec.txt b/.riot/requirements/6bec1ec.txt deleted file mode 100644 index 3e128a77c79..00000000000 --- a/.riot/requirements/6bec1ec.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6bec1ec.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/6c7321b.txt b/.riot/requirements/6c7321b.txt deleted file mode 100644 index 95dbc79d252..00000000000 --- a/.riot/requirements/6c7321b.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6c7321b.in -# -attrs==25.3.0 -cattrs==22.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -molten==1.0.2 -mypy-extensions==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==3.10.0.2 -typing-inspect==0.6.0 -zipp==3.20.2 diff --git a/.riot/requirements/6c872ab.txt b/.riot/requirements/6c872ab.txt deleted file mode 100644 index e5434a6da08..00000000000 --- a/.riot/requirements/6c872ab.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/6c872ab.in -# -attrs==23.1.0 -certifi==2023.11.17 -coverage[toml]==7.3.4 -elastic-transport==8.11.0 -elasticsearch8==8.0.1 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==2.1.0 -zipp==3.17.0 diff --git a/.riot/requirements/6d67b0b.txt b/.riot/requirements/6d67b0b.txt deleted file mode 100644 index d701321ec5c..00000000000 --- a/.riot/requirements/6d67b0b.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/6d67b0b.in -# -asn1crypto==1.5.1 -attrs==23.1.0 -certifi==2023.11.17 -cffi==1.16.0 -charset-normalizer==3.3.2 -coverage[toml]==7.3.4 -cryptography==38.0.4 -exceptiongroup==1.2.0 -filelock==3.13.1 -hypothesis==6.45.0 -idna==3.6 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -platformdirs==3.11.0 -pluggy==1.3.0 -pycparser==2.21 -pyjwt==2.8.0 -pyopenssl==23.2.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -pytz==2023.3.post1 -requests==2.31.0 -responses==0.16.0 -six==1.16.0 -snowflake-connector-python==3.6.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -tomlkit==0.12.3 -typing-extensions==4.9.0 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/6da0824.txt b/.riot/requirements/6da0824.txt deleted file mode 100644 index 72d3c32244c..00000000000 --- a/.riot/requirements/6da0824.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6da0824.in -# -attrs==25.3.0 -azure-functions==1.10.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/6e26af7.txt b/.riot/requirements/6e26af7.txt deleted file mode 100644 index 96aa8bbb8ad..00000000000 --- a/.riot/requirements/6e26af7.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6e26af7.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -boto3==1.37.38 -botocore==1.37.38 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.116.1 -freezegun==1.5.5 -h11==0.16.0 -httpcore==1.0.9 -httpretty==1.1.4 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jmespath==1.0.1 -mock==5.2.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -s3transfer==0.11.5 -six==1.17.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.44.0 -structlog==25.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -wheel==0.45.1 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/70dec77.txt b/.riot/requirements/70dec77.txt deleted file mode 100644 index 16751370567..00000000000 --- a/.riot/requirements/70dec77.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/70dec77.in -# -asgiref==3.8.1 -attrs==25.3.0 -backports-zoneinfo==0.2.1 -coverage[toml]==7.6.1 -django==4.2.20 -django-configurations==2.5.1 -django-hosts==5.2 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/7341bd9.txt b/.riot/requirements/7341bd9.txt deleted file mode 100644 index 95fd932c141..00000000000 --- a/.riot/requirements/7341bd9.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/7341bd9.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pymemcache==3.4.4 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/73d37c5.txt b/.riot/requirements/73d37c5.txt deleted file mode 100644 index af1be13fd5b..00000000000 --- a/.riot/requirements/73d37c5.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/73d37c5.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -tornado==6.4.2 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/75dda93.txt b/.riot/requirements/75dda93.txt deleted file mode 100644 index 8d2df273f73..00000000000 --- a/.riot/requirements/75dda93.txt +++ /dev/null @@ -1,34 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/75dda93.in -# -attrs==23.2.0 -blinker==1.7.0 -cachelib==0.9.0 -click==7.1.2 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -flask==1.1.4 -flask-caching==2.1.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-memcached==1.62 -redis==2.10.6 -sortedcontainers==2.4.0 -tomli==2.0.1 -werkzeug==1.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/7613d04.txt b/.riot/requirements/7613d04.txt deleted file mode 100644 index af4b5537dd7..00000000000 --- a/.riot/requirements/7613d04.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/7613d04.in -# -attrs==25.3.0 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -events==0.5 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opensearch-py[requests]==2.8.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==1.26.20 -zipp==3.20.2 diff --git a/.riot/requirements/777f0da.txt b/.riot/requirements/777f0da.txt deleted file mode 100644 index ad98b1b9997..00000000000 --- a/.riot/requirements/777f0da.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/777f0da.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -protobuf==3.19.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 diff --git a/.riot/requirements/77db507.txt b/.riot/requirements/77db507.txt deleted file mode 100644 index bc4ac6664eb..00000000000 --- a/.riot/requirements/77db507.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/77db507.in -# -astunparse==1.6.3 -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.4.26 -charset-normalizer==3.4.2 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -flask==3.0.3 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==2.2.3 -virtualenv-clone==0.5.7 -werkzeug==3.0.6 -wheel==0.45.1 -zipp==3.20.2 diff --git a/.riot/requirements/79deb5b.txt b/.riot/requirements/79deb5b.txt deleted file mode 100644 index 22e9b499ea7..00000000000 --- a/.riot/requirements/79deb5b.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/79deb5b.in -# -aiobotocore==1.4.2 -aiohappyeyeballs==2.4.0 -aiohttp==3.10.5 -aioitertools==0.11.0 -aiosignal==1.3.1 -async-generator==1.10 -async-timeout==4.0.3 -attrs==24.2.0 -botocore==1.20.106 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -frozenlist==1.4.1 -hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -jmespath==0.10.0 -mock==5.1.0 -multidict==6.0.5 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.12.2 -urllib3==1.26.19 -wrapt==1.16.0 -yarl==1.9.4 -zipp==3.20.0 diff --git a/.riot/requirements/7b02bf5.txt b/.riot/requirements/7b02bf5.txt deleted file mode 100644 index 399b31b7be8..00000000000 --- a/.riot/requirements/7b02bf5.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/7b02bf5.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-servicebus==7.14.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/7fc5d79.txt b/.riot/requirements/7fc5d79.txt deleted file mode 100644 index 3b3c5a35f65..00000000000 --- a/.riot/requirements/7fc5d79.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/7fc5d79.in -# -attrs==25.3.0 -babel==2.17.0 -blinker==1.8.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==3.0.3 -flask-babel==4.0.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlalchemy==2.0.43 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/7ffd29a.txt b/.riot/requirements/7ffd29a.txt deleted file mode 100644 index 447a518853d..00000000000 --- a/.riot/requirements/7ffd29a.txt +++ /dev/null @@ -1,20 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/7ffd29a.in -# -attrs==25.1.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -sortedcontainers==2.4.0 -tomli==2.2.1 diff --git a/.riot/requirements/82fb241.txt b/.riot/requirements/82fb241.txt deleted file mode 100644 index 269b0fb3b8b..00000000000 --- a/.riot/requirements/82fb241.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/82fb241.in -# -aiohttp==3.7.4.post0 -async-timeout==3.0.1 -attrs==25.3.0 -chardet==4.0.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -multidict==6.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -propcache==0.2.0 -pytest==8.3.5 -pytest-aiohttp==0.3.0 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/85e923f.txt b/.riot/requirements/85e923f.txt deleted file mode 100644 index dc94da04908..00000000000 --- a/.riot/requirements/85e923f.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/85e923f.in -# -attrs==24.3.0 -certifi==2024.12.14 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==20.0.4 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==2.2.3 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/8733595.txt b/.riot/requirements/8733595.txt deleted file mode 100644 index e921c950132..00000000000 --- a/.riot/requirements/8733595.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/8733595.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -httpretty==1.1.4 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py-cpuinfo==9.0.0 -pyfakefs==5.10.0 -pytest==8.3.5 -pytest-asyncio==0.23.8 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-json-logger==2.0.7 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -wrapt==2.0.0 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/89a14cf.txt b/.riot/requirements/89a14cf.txt deleted file mode 100644 index 70772bfb1ad..00000000000 --- a/.riot/requirements/89a14cf.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/89a14cf.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.29 -zipp==3.20.2 diff --git a/.riot/requirements/8a17cb2.txt b/.riot/requirements/8a17cb2.txt deleted file mode 100644 index c692572e88b..00000000000 --- a/.riot/requirements/8a17cb2.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/8a17cb2.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mariadb==1.1.13 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/8c110bf.txt b/.riot/requirements/8c110bf.txt deleted file mode 100644 index 7ad7d4b82f0..00000000000 --- a/.riot/requirements/8c110bf.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/8c110bf.in -# -attrs==25.3.0 -beautifulsoup4==4.14.2 -bottle==0.12.25 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -soupsieve==2.7 -tomli==2.3.0 -typing-extensions==4.13.2 -waitress==3.0.0 -webob==1.8.9 -webtest==3.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/9029977.txt b/.riot/requirements/9029977.txt deleted file mode 100644 index e320a67d9a0..00000000000 --- a/.riot/requirements/9029977.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/9029977.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/921b9fb.txt b/.riot/requirements/921b9fb.txt deleted file mode 100644 index 8ec138a215a..00000000000 --- a/.riot/requirements/921b9fb.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/921b9fb.in -# -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==8.1.8 -coverage[toml]==7.6.1 -deprecated==1.2.18 -exceptiongroup==1.3.0 -flask==2.1.3 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.15.0 -opentelemetry-instrumentation==0.45b0 -opentelemetry-instrumentation-flask==0.45b0 -opentelemetry-instrumentation-wsgi==0.45b0 -opentelemetry-semantic-conventions==0.45b0 -opentelemetry-util-http==0.45b0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/95f5020.txt b/.riot/requirements/95f5020.txt deleted file mode 100644 index ba732f47c55..00000000000 --- a/.riot/requirements/95f5020.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/95f5020.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/9777f3d.txt b/.riot/requirements/9777f3d.txt deleted file mode 100644 index 4dddf9cd5d5..00000000000 --- a/.riot/requirements/9777f3d.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9777f3d.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pymysql==1.1.1 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/97f1328.txt b/.riot/requirements/97f1328.txt deleted file mode 100644 index 68875c403e1..00000000000 --- a/.riot/requirements/97f1328.txt +++ /dev/null @@ -1,88 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/97f1328.in -# -aiohappyeyeballs==2.4.4 -aiohttp==3.10.11 -aiosignal==1.3.1 -annotated-types==0.7.0 -anyio==4.5.2 -appdirs==1.4.4 -async-timeout==4.0.3 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -dataclasses-json==0.6.7 -datasets==3.1.0 -dill==0.3.8 -distro==1.9.0 -exceptiongroup==1.3.0 -filelock==3.16.1 -frozenlist==1.5.0 -fsspec[http]==2024.9.0 -greenlet==3.1.1 -h11==0.16.0 -hf-xet==1.1.5 -httpcore==1.0.9 -httpx==0.28.1 -huggingface-hub==0.33.4 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.9.1 -jsonpatch==1.33 -jsonpointer==3.0.0 -langchain==0.2.17 -langchain-community==0.2.19 -langchain-core==0.2.43 -langchain-openai==0.1.25 -langchain-text-splitters==0.2.4 -langsmith==0.1.147 -marshmallow==3.22.0 -mock==5.2.0 -multidict==6.1.0 -multiprocess==0.70.16 -mypy-extensions==1.1.0 -nest-asyncio==1.6.0 -numpy==1.24.4 -openai==1.97.1 -opentracing==2.4.0 -orjson==3.10.15 -packaging==24.2 -pandas==2.0.3 -pluggy==1.5.0 -propcache==0.2.0 -pyarrow==17.0.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pysbd==0.3.4 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-dateutil==2.9.0.post0 -pytz==2025.2 -pyyaml==6.0.2 -ragas==0.1.21 -regex==2024.11.6 -requests==2.32.4 -requests-toolbelt==1.0.0 -six==1.17.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -sqlalchemy==2.0.41 -tenacity==8.5.0 -tiktoken==0.7.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.13.2 -typing-inspect==0.9.0 -tzdata==2025.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -xxhash==3.5.0 -yarl==1.15.2 diff --git a/.riot/requirements/9a319c8.txt b/.riot/requirements/9a319c8.txt deleted file mode 100644 index b36e8f8dda6..00000000000 --- a/.riot/requirements/9a319c8.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/9a319c8.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn[gevent]==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/9a6a8b9.txt b/.riot/requirements/9a6a8b9.txt deleted file mode 100644 index fc4a1144f87..00000000000 --- a/.riot/requirements/9a6a8b9.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/9a6a8b9.in -# -algoliasearch==2.5.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -zipp==3.20.2 diff --git a/.riot/requirements/9b8251b.txt b/.riot/requirements/9b8251b.txt deleted file mode 100644 index c6c4004b105..00000000000 --- a/.riot/requirements/9b8251b.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/9b8251b.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -yaaredis==3.0.0 -zipp==3.17.0 diff --git a/.riot/requirements/9d50a6f.txt b/.riot/requirements/9d50a6f.txt deleted file mode 100644 index e09d60c42d8..00000000000 --- a/.riot/requirements/9d50a6f.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9d50a6f.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -glob2==0.7 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mako==1.3.10 -markupsafe==2.1.5 -mock==5.2.0 -more-itertools==8.10.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -parse==1.20.2 -parse-type==0.6.4 -pluggy==1.5.0 -py==1.11.0 -pytest==7.4.4 -pytest-bdd==4.1.0 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/9d72125.txt b/.riot/requirements/9d72125.txt deleted file mode 100644 index 7b0be1b80c9..00000000000 --- a/.riot/requirements/9d72125.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/9d72125.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -mysqlclient==2.2.1 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/9e76fdf.txt b/.riot/requirements/9e76fdf.txt deleted file mode 100644 index c4d483c9361..00000000000 --- a/.riot/requirements/9e76fdf.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9e76fdf.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -django==2.2.28 -django-configurations==2.3.2 -djangorestframework==3.12.4 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/9eedbc0.txt b/.riot/requirements/9eedbc0.txt deleted file mode 100644 index 7d4ef3d6baf..00000000000 --- a/.riot/requirements/9eedbc0.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9eedbc0.in -# -attrs==23.2.0 -autocommand==2.2.2 -backports-tarfile==1.2.0 -cheroot==10.0.1 -cherrypy==18.10.0 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.2.0 -importlib-resources==6.4.0 -inflect==7.3.1 -iniconfig==2.0.0 -jaraco-collections==5.0.1 -jaraco-context==5.3.0 -jaraco-functools==4.0.1 -jaraco-text==3.14.0 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -portend==3.2.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tempora==5.6.0 -tomli==2.0.1 -typeguard==4.3.0 -typing-extensions==4.12.2 -zc-lockfile==3.0.post1 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/a25912e.txt b/.riot/requirements/a25912e.txt deleted file mode 100644 index e08d10b7a83..00000000000 --- a/.riot/requirements/a25912e.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/a25912e.in -# -attrs==25.3.0 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -ddtrace-api==0.0.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==2.2.3 diff --git a/.riot/requirements/a3adb9c.txt b/.riot/requirements/a3adb9c.txt deleted file mode 100644 index 39528f00e9d..00000000000 --- a/.riot/requirements/a3adb9c.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/a3adb9c.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/a582736.txt b/.riot/requirements/a582736.txt deleted file mode 100644 index 4f6a4e7e1a2..00000000000 --- a/.riot/requirements/a582736.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/a582736.in -# -aiopg==1.4.0 -async-timeout==4.0.3 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -sqlalchemy==2.0.41 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/a6f9342.txt b/.riot/requirements/a6f9342.txt deleted file mode 100644 index bd9fa7ad268..00000000000 --- a/.riot/requirements/a6f9342.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/a6f9342.in -# -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==8.1.8 -coverage[toml]==7.6.1 -deprecated==1.2.18 -exceptiongroup==1.3.0 -flask==2.1.3 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.33.1 -opentelemetry-instrumentation==0.54b1 -opentelemetry-instrumentation-flask==0.54b1 -opentelemetry-instrumentation-wsgi==0.54b1 -opentelemetry-semantic-conventions==0.54b1 -opentelemetry-util-http==0.54b1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==2.1.2 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/aa2ebfa.txt b/.riot/requirements/aa2ebfa.txt deleted file mode 100644 index 8cefc17f634..00000000000 --- a/.riot/requirements/aa2ebfa.txt +++ /dev/null @@ -1,48 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/aa2ebfa.in -# -attrs==25.3.0 -babel==2.17.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==1.1.2 -flask-babel==2.0.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.0.1 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlalchemy==2.0.43 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==2.0.3 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/abc0b46.txt b/.riot/requirements/abc0b46.txt deleted file mode 100644 index 64f004d6b5b..00000000000 --- a/.riot/requirements/abc0b46.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/abc0b46.in -# -aiomysql==0.2.0 -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pymysql==1.1.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/ac01b32.txt b/.riot/requirements/ac01b32.txt deleted file mode 100644 index 8668be156c8..00000000000 --- a/.riot/requirements/ac01b32.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ac01b32.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -django==2.2.28 -django-configurations==2.3.2 -djangorestframework==3.13.1 -exceptiongroup==1.3.0 -execnet==2.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytest-xdist==3.6.1 -pytz==2025.2 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/ac28820.txt b/.riot/requirements/ac28820.txt deleted file mode 100644 index 22f151f0679..00000000000 --- a/.riot/requirements/ac28820.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ac28820.in -# -aniso8601==9.0.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -graphene==3.0 -graphql-core==3.1.7 -graphql-relay==3.1.5 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/ad1bcb5.txt b/.riot/requirements/ad1bcb5.txt deleted file mode 100644 index 701deb13fa4..00000000000 --- a/.riot/requirements/ad1bcb5.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/ad1bcb5.in -# -async-timeout==4.0.3 -attrs==23.1.0 -click==7.1.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -redis==5.0.1 -rq==1.15.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/b39e5f7.txt b/.riot/requirements/b39e5f7.txt deleted file mode 100644 index e0845dfc719..00000000000 --- a/.riot/requirements/b39e5f7.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/b39e5f7.in -# -attrs==23.2.0 -blinker==1.7.0 -click==7.1.2 -coverage[toml]==7.4.2 -exceptiongroup==1.2.0 -flask==1.1.4 -flask-caching==1.10.1 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.4.0 -pytest==8.0.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -python-memcached==1.62 -redis==2.10.6 -sortedcontainers==2.4.0 -tomli==2.0.1 -werkzeug==1.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/b436a4c.txt b/.riot/requirements/b436a4c.txt deleted file mode 100644 index dddc661ed72..00000000000 --- a/.riot/requirements/b436a4c.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/b436a4c.in -# -attrs==23.2.0 -beautifulsoup4==4.12.3 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hupper==1.12.1 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pastedeploy==3.1.0 -plaster==1.1.2 -plaster-pastedeploy==1.0.1 -pluggy==1.5.0 -pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app -pyramid==1.10.8 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -soupsieve==2.5 -tomli==2.0.1 -translationstring==1.4 -urllib3==2.2.2 -venusian==3.1.0 -waitress==3.0.0 -webob==1.8.7 -webtest==3.0.0 -zipp==3.19.2 -zope-deprecation==5.0 -zope-interface==6.4.post2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/b68c552.txt b/.riot/requirements/b68c552.txt deleted file mode 100644 index 723fa90b9bc..00000000000 --- a/.riot/requirements/b68c552.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/b68c552.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.6.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jiter==0.9.1 -mock==5.2.0 -multidict==6.1.0 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==10.4.0 -pluggy==1.5.0 -propcache==0.2.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.7.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -vcrpy==6.0.2 -wrapt==1.17.2 -yarl==1.15.2 -zipp==3.20.2 diff --git a/.riot/requirements/b6e9905.txt b/.riot/requirements/b6e9905.txt deleted file mode 100644 index c17865f1eae..00000000000 --- a/.riot/requirements/b6e9905.txt +++ /dev/null @@ -1,79 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/b6e9905.in -# -arrow==1.3.0 -asgiref==3.8.1 -attrs==24.3.0 -autobahn==23.1.2 -automat==24.8.1 -backports-zoneinfo==0.2.1 -bcrypt==4.2.1 -blessed==1.20.0 -certifi==2024.12.14 -cffi==1.17.1 -channels==4.2.0 -charset-normalizer==3.4.0 -constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==44.0.0 -daphne==4.1.2 -django==4.2.17 -django-configurations==2.5.1 -django-picklefield==3.2 -django-pylibmc==0.6.1 -django-q==1.3.6 -django-redis==4.5.0 -exceptiongroup==1.2.2 -hyperlink==21.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -incremental==24.7.2 -iniconfig==2.0.0 -isodate==0.7.2 -lxml==5.3.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -platformdirs==4.3.6 -pluggy==1.5.0 -psycopg==3.2.3 -psycopg2-binary==2.9.10 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pycparser==2.22 -pylibmc==1.6.3 -pyopenssl==24.3.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -python-memcached==1.62 -pytz==2024.2 -redis==2.10.6 -requests==2.32.3 -requests-file==2.1.0 -requests-toolbelt==1.0.0 -service-identity==24.2.0 -six==1.17.0 -sortedcontainers==2.4.0 -spyne==2.14.0 -sqlparse==0.5.3 -tomli==2.2.1 -twisted[tls]==24.11.0 -txaio==23.1.1 -types-python-dateutil==2.9.0.20241206 -typing-extensions==4.12.2 -urllib3==2.2.3 -wcwidth==0.2.13 -zeep==4.3.1 -zipp==3.20.2 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/b786604.txt b/.riot/requirements/b786604.txt deleted file mode 100644 index cb26a822c6d..00000000000 --- a/.riot/requirements/b786604.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/b786604.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -elasticsearch1==1.10.0 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.18 -zipp==3.17.0 diff --git a/.riot/requirements/b7a530f.txt b/.riot/requirements/b7a530f.txt deleted file mode 100644 index 802d6e0593e..00000000000 --- a/.riot/requirements/b7a530f.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/b7a530f.in -# -attrs==25.1.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/b80e42b.txt b/.riot/requirements/b80e42b.txt deleted file mode 100644 index 6885e5531e6..00000000000 --- a/.riot/requirements/b80e42b.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/b80e42b.in -# -algoliasearch==2.6.3 -attrs==24.2.0 -certifi==2024.7.4 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.8 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.0.1 -urllib3==1.26.19 -zipp==3.20.0 diff --git a/.riot/requirements/baf46ab.txt b/.riot/requirements/baf46ab.txt deleted file mode 100644 index 5a983e008c5..00000000000 --- a/.riot/requirements/baf46ab.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/baf46ab.in -# -async-timeout==5.0.1 -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.3 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==4.6.0 -sortedcontainers==2.4.0 -tomli==2.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/bb588fd.txt b/.riot/requirements/bb588fd.txt deleted file mode 100644 index 900d23b901a..00000000000 --- a/.riot/requirements/bb588fd.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/bb588fd.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/bdada1a.txt b/.riot/requirements/bdada1a.txt deleted file mode 100644 index 2a394359c49..00000000000 --- a/.riot/requirements/bdada1a.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/bdada1a.in -# -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -falcon==3.1.3 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/c10c210.txt b/.riot/requirements/c10c210.txt deleted file mode 100644 index 309fa2b596d..00000000000 --- a/.riot/requirements/c10c210.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c10c210.in -# -anyio==4.5.2 -asgiref==3.8.1 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/c2ee914.txt b/.riot/requirements/c2ee914.txt deleted file mode 100644 index 66ce3c49b64..00000000000 --- a/.riot/requirements/c2ee914.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/c2ee914.in -# -attrs==23.1.0 -coverage[toml]==7.3.0 -exceptiongroup==1.1.3 -httpretty==1.1.4 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -msgpack==1.0.5 -opentracing==2.4.0 -packaging==23.1 -pluggy==1.2.0 -pytest==7.4.0 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.11.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.7.1 diff --git a/.riot/requirements/c482689.txt b/.riot/requirements/c482689.txt deleted file mode 100644 index 4d61b425aa3..00000000000 --- a/.riot/requirements/c482689.txt +++ /dev/null @@ -1,50 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c482689.in -# -asgiref==3.8.1 -attrs==25.3.0 -certifi==2025.6.15 -charset-normalizer==2.1.1 -click==7.1.2 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==1.1.4 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==2.0.1 -mock==5.2.0 -opentelemetry-api==1.0.0 -opentelemetry-instrumentation==0.19b0 -opentelemetry-instrumentation-flask==0.19b0 -opentelemetry-instrumentation-wsgi==0.19b0 -opentelemetry-util-http==0.19b0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.28.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==1.0.1 -wrapt==1.17.2 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/c4dace8.txt b/.riot/requirements/c4dace8.txt deleted file mode 100644 index b828932c4c2..00000000000 --- a/.riot/requirements/c4dace8.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c4dace8.in -# -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pyodbc==5.2.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/c74560f.txt b/.riot/requirements/c74560f.txt deleted file mode 100644 index 06136e66715..00000000000 --- a/.riot/requirements/c74560f.txt +++ /dev/null @@ -1,32 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c74560f.in -# -async-timeout==5.0.1 -attrs==24.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -gevent==24.2.1 -greenlet==3.1.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==5.2.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.0 diff --git a/.riot/requirements/c826075.txt b/.riot/requirements/c826075.txt deleted file mode 100644 index 8b37fe1c728..00000000000 --- a/.riot/requirements/c826075.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c826075.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flaky==3.8.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/ce26b2c.txt b/.riot/requirements/ce26b2c.txt deleted file mode 100644 index 85a8151acd0..00000000000 --- a/.riot/requirements/ce26b2c.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/ce26b2c.in -# -aredis==1.1.8 -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/ce48624.txt b/.riot/requirements/ce48624.txt deleted file mode 100644 index 7f4fe653b48..00000000000 --- a/.riot/requirements/ce48624.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ce48624.in -# -attrs==25.3.0 -babel==2.17.0 -blinker==1.8.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==2.3.3 -flask-babel==4.0.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -pytz==2025.2 -requests==2.32.4 -sortedcontainers==2.4.0 -sqlalchemy==2.0.43 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/bfd8366.txt b/.riot/requirements/cf86081.txt similarity index 52% rename from .riot/requirements/bfd8366.txt rename to .riot/requirements/cf86081.txt index 3c91ad7fcca..7d61954e17b 100644 --- a/.riot/requirements/bfd8366.txt +++ b/.riot/requirements/cf86081.txt @@ -1,52 +1,54 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/bfd8366.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/cf86081.in # annotated-types==0.7.0 -anyio==4.5.2 +anyio==4.11.0 attrs==22.1.0 -boto3==1.37.38 -botocore==1.37.38 +boto3==1.40.52 +botocore==1.40.52 cattrs==23.1.2 -certifi==2025.8.3 -coverage[toml]==7.6.1 +certifi==2025.10.5 +coverage[toml]==7.10.7 exceptiongroup==1.3.0 -fastapi==0.116.1 +fastapi==0.119.0 freezegun==1.5.5 h11==0.16.0 httpcore==1.0.9 httpretty==1.1.4 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 +idna==3.11 +importlib-metadata==8.7.0 iniconfig==2.1.0 jmespath==1.0.1 mock==5.2.0 -msgpack==1.1.1 +msgpack==1.1.2 opentracing==2.4.0 packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 +pluggy==1.6.0 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 python-dateutil==2.9.0.post0 -s3transfer==0.11.5 +s3transfer==0.14.0 six==1.17.0 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.44.0 +starlette==0.48.0 structlog==25.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 +tomli==2.3.0 +typing-extensions==4.15.0 +typing-inspection==0.4.2 urllib3==1.26.20 wheel==0.45.1 -zipp==3.20.2 +zipp==3.23.0 # The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 +setuptools==80.9.0 diff --git a/.riot/requirements/cfb7b47.txt b/.riot/requirements/cfb7b47.txt deleted file mode 100644 index c6b7817267a..00000000000 --- a/.riot/requirements/cfb7b47.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/cfb7b47.in -# -anyio==4.5.2 -asgiref==3.8.1 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/d002f87.txt b/.riot/requirements/d002f87.txt deleted file mode 100644 index 54053f21afb..00000000000 --- a/.riot/requirements/d002f87.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/d002f87.in -# -attrs==24.2.0 -avro==1.12.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.4.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.20.1 diff --git a/.riot/requirements/d15c0f8.txt b/.riot/requirements/d15c0f8.txt deleted file mode 100644 index a75affdf75b..00000000000 --- a/.riot/requirements/d15c0f8.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/d15c0f8.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -lz4==4.3.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -uwsgi==2.0.30 -zipp==3.20.2 diff --git a/.riot/requirements/d2cb323.txt b/.riot/requirements/d2cb323.txt deleted file mode 100644 index cec5fdb7891..00000000000 --- a/.riot/requirements/d2cb323.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/d2cb323.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -structlog==20.2.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/d59e395.txt b/.riot/requirements/d59e395.txt deleted file mode 100644 index b865c214967..00000000000 --- a/.riot/requirements/d59e395.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/d59e395.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -decorator==5.1.1 -dogpile-cache==0.9.2 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/d66afaf.txt b/.riot/requirements/d66afaf.txt deleted file mode 100644 index 0b95a2b04d0..00000000000 --- a/.riot/requirements/d66afaf.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d66afaf.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -mongoengine==0.29.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pymongo==3.9.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/d776a9a.txt b/.riot/requirements/d776a9a.txt deleted file mode 100644 index 07d09e22b12..00000000000 --- a/.riot/requirements/d776a9a.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d776a9a.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-eventhub==5.15.0 -azure-functions==1.10.1 -azure-storage-blob==12.26.0 -certifi==2025.8.3 -cffi==1.17.1 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -cryptography==46.0.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pycparser==2.23 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/d84f5ef.txt b/.riot/requirements/d84f5ef.txt deleted file mode 100644 index 57914495970..00000000000 --- a/.riot/requirements/d84f5ef.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d84f5ef.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -boto3==1.37.38 -botocore==1.37.38 -certifi==2025.8.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.116.1 -freezegun==1.5.5 -h11==0.16.0 -httpcore==1.0.9 -httpretty==1.1.4 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -jmespath==1.0.1 -mock==5.2.0 -msgpack==1.1.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -s3transfer==0.11.5 -six==1.17.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.44.0 -structlog==25.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -wheel==0.45.1 -zipp==3.20.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/d8c9ddb.txt b/.riot/requirements/d8c9ddb.txt deleted file mode 100644 index a8703fdfcfe..00000000000 --- a/.riot/requirements/d8c9ddb.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d8c9ddb.in -# -aiofiles==24.1.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.90.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.22 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.23.1 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zipp==3.20.2 diff --git a/.riot/requirements/dbf191e.txt b/.riot/requirements/dbf191e.txt deleted file mode 100644 index 3e34c492c5e..00000000000 --- a/.riot/requirements/dbf191e.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/dbf191e.in -# -asn1crypto==1.5.1 -attrs==23.2.0 -certifi==2024.7.4 -cffi==1.16.0 -charset-normalizer==2.1.1 -coverage[toml]==7.6.0 -cryptography==38.0.4 -exceptiongroup==1.2.2 -filelock==3.15.4 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -oscrypto==1.3.0 -packaging==24.1 -pluggy==1.5.0 -pycparser==2.22 -pycryptodomex==3.20.0 -pyjwt==2.8.0 -pyopenssl==22.1.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -pytz==2024.1 -requests==2.32.3 -responses==0.16.0 -six==1.16.0 -snowflake-connector-python==2.9.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.12.2 -urllib3==1.26.19 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/dc3ecf5.txt b/.riot/requirements/dc3ecf5.txt deleted file mode 100644 index 3a9c449bce0..00000000000 --- a/.riot/requirements/dc3ecf5.txt +++ /dev/null @@ -1,35 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/dc3ecf5.in -# -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.7.9 -charset-normalizer==3.4.2 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==2.3.3 -hypothesis==6.113.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -werkzeug==3.0.6 -zipp==3.20.2 diff --git a/.riot/requirements/dc9f475.txt b/.riot/requirements/dc9f475.txt deleted file mode 100644 index 163edfe0799..00000000000 --- a/.riot/requirements/dc9f475.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/dc9f475.in -# -attrs==23.2.0 -autocommand==2.2.2 -backports-tarfile==1.2.0 -cheroot==10.0.1 -cherrypy==18.10.0 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.2.0 -importlib-resources==6.4.0 -inflect==7.3.1 -iniconfig==2.0.0 -jaraco-collections==5.0.1 -jaraco-context==5.3.0 -jaraco-functools==4.0.1 -jaraco-text==3.14.0 -mock==5.1.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -portend==3.2.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tempora==5.6.0 -tomli==2.0.1 -typeguard==4.3.0 -typing-extensions==4.12.2 -zc-lockfile==3.0.post1 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/e1e09c9.txt b/.riot/requirements/e1e09c9.txt deleted file mode 100644 index 9f07d4c2561..00000000000 --- a/.riot/requirements/e1e09c9.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/e1e09c9.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pylibmc==1.6.3 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/e222783.txt b/.riot/requirements/e222783.txt deleted file mode 100644 index 9d6fa6e77f5..00000000000 --- a/.riot/requirements/e222783.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e222783.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -protobuf==5.29.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/e2c6900.txt b/.riot/requirements/e2c6900.txt deleted file mode 100644 index f3cb21179d5..00000000000 --- a/.riot/requirements/e2c6900.txt +++ /dev/null @@ -1,33 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/e2c6900.in -# -anyio==4.4.0 -attrs==23.2.0 -certifi==2024.6.2 -coverage[toml]==7.5.4 -exceptiongroup==1.2.1 -h11==0.14.0 -httpcore==0.16.3 -httpx==0.23.3 -hypothesis==6.45.0 -idna==3.7 -importlib-metadata==8.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.2.2 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -rfc3986[idna2008]==1.5.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -typing-extensions==4.12.2 -zipp==3.19.2 diff --git a/.riot/requirements/e7a63a3.txt b/.riot/requirements/e7a63a3.txt deleted file mode 100644 index 6c1feed2bd3..00000000000 --- a/.riot/requirements/e7a63a3.txt +++ /dev/null @@ -1,29 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/e7a63a3.in -# -attrs==25.3.0 -certifi==2025.1.31 -charset-normalizer==3.4.1 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opensearch-py[requests]==1.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -urllib3==1.26.20 -zipp==3.20.2 diff --git a/.riot/requirements/e8693b9.txt b/.riot/requirements/e8693b9.txt deleted file mode 100644 index 4db2ef78998..00000000000 --- a/.riot/requirements/e8693b9.txt +++ /dev/null @@ -1,77 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/e8693b9.in -# -arrow==1.3.0 -asgiref==3.8.1 -attrs==25.3.0 -autobahn==23.1.2 -automat==24.8.1 -bcrypt==4.2.1 -blessed==1.21.0 -certifi==2025.4.26 -cffi==1.17.1 -channels==3.0.5 -charset-normalizer==3.4.2 -constantly==23.10.4 -coverage[toml]==7.6.1 -cryptography==45.0.3 -daphne==3.0.2 -django==3.0.14 -django-configurations==2.3.2 -django-picklefield==3.0.1 -django-pylibmc==0.6.1 -django-q==1.3.6 -django-redis==4.5.0 -exceptiongroup==1.3.0 -hyperlink==21.0.0 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -incremental==24.7.2 -iniconfig==2.1.0 -isodate==0.7.2 -lxml==5.4.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -platformdirs==4.3.6 -pluggy==1.5.0 -psycopg2-binary==2.9.10 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pycparser==2.22 -pylibmc==1.6.3 -pyopenssl==25.1.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -python-memcached==1.62 -pytz==2025.2 -redis==2.10.6 -requests==2.32.3 -requests-file==2.1.0 -requests-toolbelt==1.0.0 -service-identity==24.2.0 -six==1.17.0 -sortedcontainers==2.4.0 -spyne==2.14.0 -sqlparse==0.5.3 -tomli==2.2.1 -twisted[tls]==24.11.0 -txaio==23.1.1 -types-python-dateutil==2.9.0.20241206 -typing-extensions==4.13.2 -urllib3==2.2.3 -wcwidth==0.2.13 -zeep==4.3.1 -zipp==3.20.2 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/e871798.txt b/.riot/requirements/e871798.txt deleted file mode 100644 index 3b8c98da668..00000000000 --- a/.riot/requirements/e871798.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e871798.in -# -attrs==23.2.0 -coverage[toml]==7.6.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.2.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.19.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==71.1.0 diff --git a/.riot/requirements/e87b392.txt b/.riot/requirements/e87b392.txt deleted file mode 100644 index 56eccef1dbf..00000000000 --- a/.riot/requirements/e87b392.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/e87b392.in -# -attrs==23.1.0 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -jinja2==3.0.3 -markupsafe==2.1.3 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/eab5e7a.txt b/.riot/requirements/eab5e7a.txt deleted file mode 100644 index 272838ed70e..00000000000 --- a/.riot/requirements/eab5e7a.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/eab5e7a.in -# -amqp==5.3.1 -attrs==25.3.0 -backports-zoneinfo[tzdata]==0.2.1 -billiard==4.2.1 -celery==5.5.3 -click==8.1.8 -click-didyoumean==0.3.1 -click-plugins==1.1.1.2 -click-repl==0.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==5.5.4 -mock==5.2.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -prompt-toolkit==3.0.51 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -python-dateutil==2.9.0.post0 -redis==3.5.3 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -tzdata==2025.2 -vine==5.1.0 -wcwidth==0.2.13 -zipp==3.20.2 diff --git a/.riot/requirements/eb4440f.txt b/.riot/requirements/eb4440f.txt deleted file mode 100644 index 9420d403230..00000000000 --- a/.riot/requirements/eb4440f.txt +++ /dev/null @@ -1,45 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/eb4440f.in -# -asgiref==3.8.1 -attrs==25.3.0 -bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -dill==0.4.0 -django==3.2.25 -django-configurations==2.5.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pylibmc==1.6.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytz==2025.2 -pyyaml==6.0.2 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/ee62ebe.txt b/.riot/requirements/ee62ebe.txt deleted file mode 100644 index b0e384be4e5..00000000000 --- a/.riot/requirements/ee62ebe.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ee62ebe.in -# -async-timeout==4.0.3 -attrs==24.2.0 -coverage[toml]==7.6.1 -dramatiq==1.17.0 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -prometheus-client==0.20.0 -pytest==8.3.2 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -redis==5.0.8 -sortedcontainers==2.4.0 -tomli==2.0.1 diff --git a/.riot/requirements/ef10d26.txt b/.riot/requirements/ef10d26.txt deleted file mode 100644 index 02b4ccf8a17..00000000000 --- a/.riot/requirements/ef10d26.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ef10d26.in -# -amqp==5.3.1 -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -kombu==5.0.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -vine==5.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/ef66bb3.txt b/.riot/requirements/ef66bb3.txt deleted file mode 100644 index 7e584779306..00000000000 --- a/.riot/requirements/ef66bb3.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/ef66bb3.in -# -asynctest==0.13.0 -attrs==23.1.0 -coverage[toml]==7.3.4 -hypothesis==6.45.0 -importlib-metadata==7.0.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -py==1.11.0 -pytest==6.2.5 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -toml==0.10.2 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/f334e66.txt b/.riot/requirements/f334e66.txt deleted file mode 100644 index ba4030e4718..00000000000 --- a/.riot/requirements/f334e66.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f334e66.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/f408d1f.txt b/.riot/requirements/f408d1f.txt deleted file mode 100644 index 9a59658b081..00000000000 --- a/.riot/requirements/f408d1f.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f408d1f.in -# -attrs==25.1.0 -blinker==1.8.2 -certifi==2025.1.31 -charset-normalizer==3.4.1 -click==7.1.2 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -flask==1.1.4 -flask-openapi3==1.1.5 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -itsdangerous==1.1.0 -jinja2==2.11.3 -markupsafe==1.1.1 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pydantic==1.10.21 -pytest==8.3.4 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.12.2 -urllib3==1.26.20 -werkzeug==1.0.1 -zipp==3.20.2 diff --git a/.riot/requirements/f4b1bd3.txt b/.riot/requirements/f4b1bd3.txt deleted file mode 100644 index da3d86a840f..00000000000 --- a/.riot/requirements/f4b1bd3.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f4b1bd3.in -# -async-timeout==5.0.1 -attrs==24.2.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -pytest==8.3.3 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -redis==5.0.1 -sortedcontainers==2.4.0 -tomli==2.1.0 -zipp==3.20.2 diff --git a/.riot/requirements/f61cdff.txt b/.riot/requirements/f61cdff.txt deleted file mode 100644 index 853373c6a43..00000000000 --- a/.riot/requirements/f61cdff.txt +++ /dev/null @@ -1,44 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f61cdff.in -# -attrs==25.3.0 -bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -dill==0.4.0 -django==2.2.28 -django-configurations==2.3.2 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pylibmc==1.6.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytz==2025.2 -pyyaml==6.0.2 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/f7e8645.txt b/.riot/requirements/f7e8645.txt deleted file mode 100644 index 3bc220b653d..00000000000 --- a/.riot/requirements/f7e8645.txt +++ /dev/null @@ -1,21 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/f7e8645.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 diff --git a/.riot/requirements/f8e5119.txt b/.riot/requirements/f8e5119.txt deleted file mode 100644 index a06b17c8085..00000000000 --- a/.riot/requirements/f8e5119.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f8e5119.in -# -attrs==25.3.0 -azure-core==1.33.0 -azure-servicebus==7.14.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -isodate==0.7.2 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==2.2.3 diff --git a/.riot/requirements/f903257.txt b/.riot/requirements/f903257.txt deleted file mode 100644 index 1822758bfe4..00000000000 --- a/.riot/requirements/f903257.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/f903257.in -# -attrs==25.3.0 -blinker==1.8.2 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==0.12.5 -flask-cache==0.13.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==1.1.0 -jinja2==2.10.3 -markupsafe==1.1.1 -mock==5.2.0 -more-itertools==8.10.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py==1.11.0 -pytest==6.2.5 -pytest-cov==3.0.0 -pytest-mock==2.0.0 -pytest-randomly==3.15.0 -python-memcached==1.62 -redis==2.10.6 -sortedcontainers==2.4.0 -toml==0.10.2 -tomli==2.2.1 -typing-extensions==4.13.2 -werkzeug==0.16.1 -zipp==3.20.2 diff --git a/.riot/requirements/fadb064.txt b/.riot/requirements/fadb064.txt deleted file mode 100644 index ad51389c99f..00000000000 --- a/.riot/requirements/fadb064.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/fadb064.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.2.2 -googleapis-common-protos==1.70.0 -grpcio==1.34.1 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==24.2 -pluggy==1.5.0 -protobuf==5.29.4 -pytest==8.3.5 -pytest-asyncio==0.23.7 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -zipp==3.20.2 diff --git a/.riot/requirements/fbab99a.txt b/.riot/requirements/fbab99a.txt deleted file mode 100644 index 6351c78934a..00000000000 --- a/.riot/requirements/fbab99a.txt +++ /dev/null @@ -1,28 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/fbab99a.in -# -async-timeout==4.0.3 -attrs==23.1.0 -click==7.1.2 -coverage[toml]==7.3.4 -exceptiongroup==1.2.0 -hypothesis==6.45.0 -importlib-metadata==7.0.1 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==23.2 -pluggy==1.3.0 -pytest==7.4.3 -pytest-asyncio==0.21.1 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-randomly==3.15.0 -redis==5.0.1 -rq==1.10.1 -sortedcontainers==2.4.0 -tomli==2.0.1 -zipp==3.17.0 diff --git a/.riot/requirements/fd2d2d1.txt b/.riot/requirements/fd2d2d1.txt deleted file mode 100644 index 3cdc7c85224..00000000000 --- a/.riot/requirements/fd2d2d1.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/fd2d2d1.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -msgpack==1.1.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/ff0c51d.txt b/.riot/requirements/ff0c51d.txt deleted file mode 100644 index 56853212b68..00000000000 --- a/.riot/requirements/ff0c51d.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ff0c51d.in -# -annotated-types==0.7.0 -attrs==25.3.0 -blinker==1.8.2 -certifi==2025.10.5 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -flask==3.0.3 -flask-openapi3==4.0.3 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -requests==2.32.4 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.13.2 -urllib3==1.26.20 -werkzeug==3.0.6 -zipp==3.20.2 diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 3491da5a392..a86597bdb08 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -39,12 +39,12 @@ def check_supported_python_version(): - if PYTHON_VERSION_INFO < (3, 9): + if PYTHON_VERSION_INFO < (3, 10): deprecation_message = ( - "Support for ddtrace with Python version %d.%d is deprecated and will be removed in 4.0.0." + "Support for ddtrace with Python version %d.%d is deprecated and will be removed in 5.0.0." ) - if PYTHON_VERSION_INFO < (3, 8): - deprecation_message = "Support for ddtrace with Python version %d.%d was removed in 3.0.0." + if PYTHON_VERSION_INFO < (3, 9): + deprecation_message = "Support for ddtrace with Python version %d.%d was removed in 4.0.0." debtcollector.deprecate( (deprecation_message % (PYTHON_VERSION_INFO[0], PYTHON_VERSION_INFO[1])), category=DDTraceDeprecationWarning, diff --git a/ddtrace/_version.py b/ddtrace/_version.py new file mode 100644 index 00000000000..9fc278ef97a --- /dev/null +++ b/ddtrace/_version.py @@ -0,0 +1,34 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] +else: + VERSION_TUPLE = object + COMMIT_ID = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID + +__version__ = version = "4.0.0.dev0" +__version_tuple__ = version_tuple = (4, 0, 0, "dev0", "") + +# __commit_id__ = commit_id = 'g5db831a3e' diff --git a/ddtrace/appsec/_iast/_ast/visitor.py b/ddtrace/appsec/_iast/_ast/visitor.py index a7e1474f5f9..0df51cba7fe 100644 --- a/ddtrace/appsec/_iast/_ast/visitor.py +++ b/ddtrace/appsec/_iast/_ast/visitor.py @@ -392,7 +392,6 @@ def find_insert_position(module_node: ast.Module) -> int: @staticmethod def _none_constant(from_node: Any) -> Any: # noqa: B008 - # 3.8+ return ast.Constant( lineno=from_node.lineno, col_offset=from_node.col_offset, @@ -863,17 +862,6 @@ def visit_Subscript(self, subscr_node: ast.Subscript) -> Any: call_node.func.attr = aspect_split[1] call_node.func.value.id = aspect_split[0] call_node.args.extend([subscr_node.value, subscr_node.slice]) - # TODO: python 3.8 isn't working correctly with index_aspect, tests raise: - # corrupted size vs. prev_size in fastbins - # Test failed with exit code -6 - # https://app.circleci.com/pipelines/github/DataDog/dd-trace-py/46665/workflows/3cf1257c-feaf-4653-bb9c-fb840baa1776/jobs/3031799 - # elif isinstance(subscr_node.slice, ast.Index): - # if self._is_string_node(subscr_node.slice.value): # type: ignore[attr-defined] - # return subscr_node - # aspect_split = self._aspect_index.split(".") - # call_node.func.attr = aspect_split[1] - # call_node.func.value.id = aspect_split[0] - # call_node.args.extend([subscr_node.value, subscr_node.slice.value]) # type: ignore[attr-defined] else: return subscr_node diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index 2adf38cf686..4e2a9171171 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -66,7 +66,7 @@ integrations: - algoliasearch tested_versions_by_dependency: algoliasearch: - min: 2.5.0 + min: 2.6.3 max: 2.6.3 - integration_name: anthropic @@ -104,7 +104,7 @@ integrations: - asyncpg tested_versions_by_dependency: asyncpg: - min: 0.22.0 + min: 0.23.0 max: 0.30.0 - integration_name: avro @@ -390,7 +390,7 @@ integrations: - gevent tested_versions_by_dependency: gevent: - min: 20.12.1 + min: 21.1.2 max: 25.5.1 - integration_name: google_adk @@ -646,7 +646,7 @@ integrations: - protobuf tested_versions_by_dependency: protobuf: - min: 5.29.3 + min: 6.30.1 max: 6.32.0 - integration_name: psycopg @@ -660,7 +660,7 @@ integrations: min: 3.0.18 max: 3.2.10 psycopg2-binary: - min: 2.8.6 + min: 2.9.10 max: 2.9.10 - integration_name: pydantic_ai @@ -720,7 +720,7 @@ integrations: - pynamodb tested_versions_by_dependency: pynamodb: - min: 5.0.3 + min: 5.5.1 max: 5.5.1 - integration_name: pyodbc @@ -806,7 +806,7 @@ integrations: - requests tested_versions_by_dependency: requests: - min: 2.20.1 + min: 2.25.1 max: 2.32.5 - integration_name: rq @@ -842,7 +842,7 @@ integrations: - snowflake-connector-python tested_versions_by_dependency: snowflake-connector-python: - min: 2.3.10 + min: 2.4.6 max: 3.17.2 - integration_name: sqlalchemy @@ -908,7 +908,7 @@ integrations: - urllib3 tested_versions_by_dependency: urllib3: - min: 1.25.0 + min: 1.25.8 max: 2.5.0 - integration_name: valkey diff --git a/ddtrace/contrib/internal/aioredis/patch.py b/ddtrace/contrib/internal/aioredis/patch.py index 3ce4629620f..96945d1d33a 100644 --- a/ddtrace/contrib/internal/aioredis/patch.py +++ b/ddtrace/contrib/internal/aioredis/patch.py @@ -177,8 +177,7 @@ def _finish_span(future): future.result() if redis_command in ROW_RETURNING_COMMANDS: span.set_metric(db.ROWCOUNT, determine_row_count(redis_command=redis_command, result=future.result())) - # CancelledError exceptions extend from BaseException as of Python 3.8, instead of usual Exception - except (Exception, aioredis.CancelledError): + except aioredis.CancelledError: span.set_exc_info(*sys.exc_info()) if redis_command in ROW_RETURNING_COMMANDS: span.set_metric(db.ROWCOUNT, 0) diff --git a/ddtrace/contrib/internal/algoliasearch/patch.py b/ddtrace/contrib/internal/algoliasearch/patch.py index 5b8571457be..93b0c3caa6d 100644 --- a/ddtrace/contrib/internal/algoliasearch/patch.py +++ b/ddtrace/contrib/internal/algoliasearch/patch.py @@ -37,13 +37,12 @@ algoliasearch_version = VERSION = V0 -def get_version(): - # type: () -> str +def get_version() -> str: return VERSION def _supported_versions() -> Dict[str, str]: - return {"algoliasearch": ">=2.5.0"} + return {"algoliasearch": ">=2.6.3"} def patch(): diff --git a/ddtrace/contrib/internal/asyncpg/patch.py b/ddtrace/contrib/internal/asyncpg/patch.py index 586b751a1a0..67b55c40d70 100644 --- a/ddtrace/contrib/internal/asyncpg/patch.py +++ b/ddtrace/contrib/internal/asyncpg/patch.py @@ -47,13 +47,12 @@ log = get_logger(__name__) -def get_version(): - # type: () -> str +def get_version() -> str: return getattr(asyncpg, "__version__", "") def _supported_versions() -> Dict[str, str]: - return {"asyncpg": ">=0.22.0"} + return {"asyncpg": ">=0.23.0"} def _get_connection_tags(conn): diff --git a/ddtrace/contrib/internal/gevent/patch.py b/ddtrace/contrib/internal/gevent/patch.py index 1c1cad5ebea..6452370c338 100644 --- a/ddtrace/contrib/internal/gevent/patch.py +++ b/ddtrace/contrib/internal/gevent/patch.py @@ -19,7 +19,7 @@ def get_version(): def _supported_versions() -> Dict[str, str]: - return {"gevent": ">=20.12"} + return {"gevent": ">=21.1.2"} def patch(): diff --git a/ddtrace/contrib/internal/psycopg/patch.py b/ddtrace/contrib/internal/psycopg/patch.py index f9ae4669a98..1891e803e8a 100644 --- a/ddtrace/contrib/internal/psycopg/patch.py +++ b/ddtrace/contrib/internal/psycopg/patch.py @@ -76,8 +76,7 @@ def _psycopg_sql_injector(dbm_comment, sql_statement): ) -def get_version(): - # type: () -> str +def get_version() -> str: return "" @@ -85,11 +84,10 @@ def get_version(): def _supported_versions() -> Dict[str, str]: - return {"psycopg": ">=3.0.0", "psycopg2": ">=2.8.0"} + return {"psycopg": ">=3.0.0", "psycopg2": ">=2.9.10"} -def get_versions(): - # type: () -> List[str] +def get_versions() -> List[str]: return PATCHED_VERSIONS diff --git a/ddtrace/contrib/internal/pynamodb/patch.py b/ddtrace/contrib/internal/pynamodb/patch.py index 1d2eb8176a3..71ff07367d6 100644 --- a/ddtrace/contrib/internal/pynamodb/patch.py +++ b/ddtrace/contrib/internal/pynamodb/patch.py @@ -35,13 +35,12 @@ ) -def get_version(): - # type: () -> str +def get_version() -> str: return getattr(pynamodb, "__version__", "") def _supported_versions() -> Dict[str, str]: - return {"pynamodb": ">=5.0"} + return {"pynamodb": ">=5.5.1"} def patch(): diff --git a/ddtrace/contrib/internal/requests/patch.py b/ddtrace/contrib/internal/requests/patch.py index 8db2b85189d..57e4a4d6aae 100644 --- a/ddtrace/contrib/internal/requests/patch.py +++ b/ddtrace/contrib/internal/requests/patch.py @@ -31,13 +31,12 @@ Pin(_config=config.requests).onto(TracedSession) -def get_version(): - # type: () -> str +def get_version() -> str: return getattr(requests, "__version__", "") def _supported_versions() -> Dict[str, str]: - return {"requests": ">=2.20.0"} + return {"requests": ">=2.25.1"} def patch(): diff --git a/ddtrace/contrib/internal/snowflake/patch.py b/ddtrace/contrib/internal/snowflake/patch.py index bab4c6f06bc..4fc3f5b2973 100644 --- a/ddtrace/contrib/internal/snowflake/patch.py +++ b/ddtrace/contrib/internal/snowflake/patch.py @@ -29,8 +29,7 @@ ) -def get_version(): - # type: () -> str +def get_version() -> str: try: import snowflake.connector as c except AttributeError: @@ -41,7 +40,7 @@ def get_version(): def _supported_versions() -> Dict[str, str]: - return {"snowflake": ">=2.3.0"} + return {"snowflake": ">=2.4.6"} class _SFTracedCursor(TracedCursor): diff --git a/ddtrace/internal/compat.py b/ddtrace/internal/compat.py index aaa4b8fd358..a9067b36170 100644 --- a/ddtrace/internal/compat.py +++ b/ddtrace/internal/compat.py @@ -74,11 +74,11 @@ def ip_is_global(ip: str) -> bool: return parsed_ip.is_global +# This fix was implemented in 3.9.8 +# https://github.com/python/cpython/issues/83860 if PYTHON_VERSION_INFO >= (3, 9, 8): from functools import singledispatchmethod else: - # This fix was not backported to 3.8 - # https://github.com/python/cpython/issues/83860 from functools import singledispatchmethod def _register(self, cls, method=None): diff --git a/ddtrace/internal/coverage/instrumentation.py b/ddtrace/internal/coverage/instrumentation.py index 503f902ed9d..be58152e961 100644 --- a/ddtrace/internal/coverage/instrumentation.py +++ b/ddtrace/internal/coverage/instrumentation.py @@ -11,5 +11,4 @@ elif sys.version_info >= (3, 10): from ddtrace.internal.coverage.instrumentation_py3_10 import instrument_all_lines # noqa else: - # Python 3.8 and 3.9 use the same instrumentation - from ddtrace.internal.coverage.instrumentation_py3_8 import instrument_all_lines # noqa + from ddtrace.internal.coverage.instrumentation_py3_9 import instrument_all_lines # noqa diff --git a/ddtrace/internal/coverage/instrumentation_py3_8.py b/ddtrace/internal/coverage/instrumentation_py3_8.py deleted file mode 100644 index 59cc2841137..00000000000 --- a/ddtrace/internal/coverage/instrumentation_py3_8.py +++ /dev/null @@ -1,390 +0,0 @@ -from abc import ABC -import dis -from enum import Enum -import sys -from types import CodeType -import typing as t - -from ddtrace.internal.bytecode_injection import HookType -from ddtrace.internal.test_visibility.coverage_lines import CoverageLines - - -# This is primarily to make mypy happy without having to nest the rest of this module behind a version check -# NOTE: the "prettier" one-liner version (eg: assert (3,11) <= sys.version_info < (3,12)) does not work for mypy -# NOTE: Python 3.8 and 3.9 use the same instrumentation -assert sys.version_info < (3, 10) # nosec - - -class JumpDirection(int, Enum): - FORWARD = 1 - BACKWARD = -1 - - @classmethod - def from_opcode(cls, opcode: int) -> "JumpDirection": - return cls.BACKWARD if "BACKWARD" in dis.opname[opcode] else cls.FORWARD - - -class Jump(ABC): - # NOTE: in Python 3.9, jump arguments are offsets, vs instruction numbers (ie offsets/2) in Python 3.10 - def __init__(self, start: int, arg: int) -> None: - self.start = start - self.end: int - self.arg = arg - - -class AJump(Jump): - __opcodes__ = set(dis.hasjabs) - - def __init__(self, start: int, arg: int) -> None: - super().__init__(start, arg) - self.end = self.arg - - -class RJump(Jump): - __opcodes__ = set(dis.hasjrel) - - def __init__(self, start: int, arg: int, direction: JumpDirection) -> None: - super().__init__(start, arg) - self.direction = direction - self.end = start + (self.arg) * self.direction + 2 - - -class Instruction: - __slots__ = ("offset", "opcode", "arg", "targets") - - def __init__(self, offset: int, opcode: int, arg: int) -> None: - self.offset = offset - self.opcode = opcode - self.arg = arg - self.targets: t.List["Branch"] = [] - - -class Branch(ABC): - def __init__(self, start: Instruction, end: Instruction) -> None: - self.start = start - self.end = end - - @property - def arg(self) -> int: - raise NotImplementedError - - -class RBranch(Branch): - @property - def arg(self) -> int: - return abs(self.end.offset - self.start.offset - 2) >> 1 - - -class ABranch(Branch): - @property - def arg(self) -> int: - return self.end.offset >> 1 - - -EXTENDED_ARG = dis.EXTENDED_ARG -NO_OFFSET = -1 - - -def instr_with_arg(opcode: int, arg: int) -> t.List[Instruction]: - instructions = [Instruction(-1, opcode, arg & 0xFF)] - arg >>= 8 - while arg: - instructions.insert(0, Instruction(NO_OFFSET, EXTENDED_ARG, arg & 0xFF)) - arg >>= 8 - return instructions - - -def update_location_data( - code: CodeType, trap_map: t.Dict[int, int], ext_arg_offsets: t.List[t.Tuple[int, int]] -) -> bytes: - # Some code objects do not have co_lnotab data (eg: certain lambdas) - if code.co_lnotab == b"": - return code.co_lnotab - - # DEV: We expect the original offsets in the trap_map - new_data = bytearray() - data = code.co_lnotab - - ext_arg_offset_iter = iter(sorted(ext_arg_offsets)) - ext_arg_offset, ext_arg_size = next(ext_arg_offset_iter, (None, None)) - - current_orig_offset = 0 # Cumulative offset used to compare against trap offsets - - # All instructions have to have line numbers, so the first instructions of the trap call must mark the beginning of - # the line. The subsequent offsets need to be incremented by the size of the trap call instructions plus any - # extended args. - - # Set the first trap size: - current_new_offset = accumulated_new_offset = trap_map[0] << 1 - - for i in range(0, len(data), 2): - orig_offset_delta = data[i] - line_delta = data[i + 1] - - # For each original offset, we compute how many offsets have been added in the new code, this includes: - # - the size of the trap at the previous offset - # - the amount of extended args added since the previous offset - - current_new_offset += orig_offset_delta - current_orig_offset += orig_offset_delta - accumulated_new_offset += orig_offset_delta - - # If the current offset is 255, just increment: - if orig_offset_delta == 255: - continue - - # If the current offset is 0, it means we are only incrementing the amount of lines jumped by the previous - # non-zero offset - if orig_offset_delta == 0: - new_data.append(0) - new_data.append(line_delta) - continue - - while ext_arg_offset is not None and ext_arg_size is not None and current_new_offset > ext_arg_offset: - accumulated_new_offset += ext_arg_size << 1 - current_new_offset += ext_arg_size << 1 - ext_arg_offset, ext_arg_size = next(ext_arg_offset_iter, (None, None)) - - # If the current line delta changes, flush accumulated data: - if line_delta != 0: - while accumulated_new_offset > 255: - new_data.append(255) - new_data.append(0) - accumulated_new_offset -= 255 - - new_data.append(accumulated_new_offset) - new_data.append(line_delta) - - # Also add the current trap size to the accumulated offset - accumulated_new_offset = trap_map[current_orig_offset] << 1 - current_new_offset += accumulated_new_offset - - return bytes(new_data) - - -LOAD_CONST = dis.opmap["LOAD_CONST"] -CALL = dis.opmap["CALL_FUNCTION"] -POP_TOP = dis.opmap["POP_TOP"] -IMPORT_NAME = dis.opmap["IMPORT_NAME"] -IMPORT_FROM = dis.opmap["IMPORT_FROM"] - - -def trap_call(trap_index: int, arg_index: int) -> t.Tuple[Instruction, ...]: - return ( - *instr_with_arg(LOAD_CONST, trap_index), - *instr_with_arg(LOAD_CONST, arg_index), - Instruction(NO_OFFSET, CALL, 1), - Instruction(NO_OFFSET, POP_TOP, 0), - ) - - -def instrument_all_lines(code: CodeType, hook: HookType, path: str, package: str) -> t.Tuple[CodeType, CoverageLines]: - # TODO[perf]: Check if we really need to << and >> everywhere - trap_func, trap_arg = hook, path - - instructions: t.List[Instruction] = [] - - new_consts = list(code.co_consts) - trap_index = len(new_consts) - new_consts.append(trap_func) - - seen_lines = CoverageLines() - - offset_map = {} - - # Collect all the original jumps - jumps: t.Dict[int, Jump] = {} - traps: t.Dict[int, int] = {} # DEV: This uses the original offsets - line_map = {} - line_starts = dict(dis.findlinestarts(code)) - - # The previous two arguments are kept in order to track the depth of the IMPORT_NAME - # For example, from ...package import module - current_arg: int = 0 - previous_arg: int = 0 - previous_previous_arg: int = 0 - current_import_name: t.Optional[str] = None - current_import_package: t.Optional[str] = None - - try: - code_iter = iter(enumerate(code.co_code)) - ext: list[int] = [] - while True: - original_offset, opcode = next(code_iter) - - if original_offset in line_starts: - # Inject trap call at the beginning of the line. Keep track - # of location and size of the trap call instructions. We - # need this to adjust the location table. - line = line_starts[original_offset] - trap_instructions = trap_call(trap_index, len(new_consts)) - traps[original_offset] = len(trap_instructions) - instructions.extend(trap_instructions) - - # Make sure that the current module is marked as depending on its own package by instrumenting the - # first executable line - package_dep = None - if code.co_name == "" and len(new_consts) == len(code.co_consts) + 1: - package_dep = (package, ("",)) - - new_consts.append((line, trap_arg, package_dep)) - - line_map[original_offset] = trap_instructions[0] - - seen_lines.add(line) - - _, arg = next(code_iter) - - offset = len(instructions) << 1 - - # Propagate code - instructions.append(Instruction(original_offset, opcode, arg)) - - if opcode is EXTENDED_ARG: - ext.append(arg) - continue - else: - previous_previous_arg = previous_arg - previous_arg = current_arg - current_arg = int.from_bytes([*ext, arg], "big", signed=False) - ext.clear() - - # Track imports names - if opcode == IMPORT_NAME: - import_depth = code.co_consts[previous_previous_arg] - current_import_name = code.co_names[current_arg] - # Adjust package name if the import is relative and a parent (ie: if depth is more than 1) - current_import_package = ( - ".".join(package.split(".")[: -import_depth + 1]) if import_depth > 1 else package - ) - new_consts[-1] = ( - new_consts[-1][0], - new_consts[-1][1], - (current_import_package, (current_import_name,)), - ) - - # Also track import from statements since it's possible that the "from" target is a module, eg: - # from my_package import my_module - # Since the package has not changed, we simply extend the previous import names with the new value - if opcode == IMPORT_FROM: - import_from_name = f"{current_import_name}.{code.co_names[current_arg]}" - new_consts[-1] = ( - new_consts[-1][0], - new_consts[-1][1], - (new_consts[-1][2][0], tuple(list(new_consts[-1][2][1]) + [import_from_name])), - ) - - # Collect branching instructions for processing - if opcode in AJump.__opcodes__: - jumps[offset] = AJump(original_offset, current_arg) - elif opcode in RJump.__opcodes__: - jumps[offset] = RJump(original_offset, current_arg, JumpDirection.from_opcode(opcode)) - - if opcode is EXTENDED_ARG: - ext.append(arg) - else: - ext.clear() - except StopIteration: - pass - - # Collect all the old jump start and end offsets - jump_targets = {_ for j in jumps.values() for _ in (j.start, j.end)} - - # Adjust all the offsets and map the old offsets to the new ones for the - # jumps - for index, instr in enumerate(instructions): - new_offset = index << 1 - if instr.offset in jump_targets: - offset_map[instr.offset] = new_offset - instr.offset = new_offset - - # Adjust all the jumps, neglecting any EXTENDED_ARGs for now - branches: t.List[Branch] = [] - for jump in jumps.values(): - new_start = offset_map[jump.start] - new_end = offset_map[jump.end] - - # If we are jumping at the beginning of a line, jump to the - # beginning of the trap call instead - target_instr = line_map.get(jump.end, instructions[new_end >> 1]) - branch: Branch = ( - RBranch(instructions[new_start >> 1], target_instr) - if isinstance(jump, RJump) - else ABranch(instructions[new_start >> 1], target_instr) - ) - target_instr.targets.append(branch) - - branches.append(branch) - - # Process all the branching instructions to adjust the arguments. We - # need to add EXTENDED_ARGs if the argument is too large. - process_branches = True - exts: t.List[t.Tuple[Instruction, int]] = [] - while process_branches: - process_branches = False - for branch in branches: - jump_instr = branch.start - new_arg = branch.arg << 1 # 3.9 uses offsets, not instruction numbers - jump_instr.arg = new_arg & 0xFF - new_arg >>= 8 - c = 0 - index = jump_instr.offset >> 1 - - # Update the argument of the branching instruction, adding - # EXTENDED_ARGs if needed - while new_arg: - if index and instructions[index - 1].opcode is EXTENDED_ARG: - index -= 1 - instructions[index].arg = new_arg & 0xFF - else: - ext_instr = Instruction(index << 1, EXTENDED_ARG, new_arg & 0xFF) - instructions.insert(index, ext_instr) - c += 1 - # If the jump instruction was a target of another jump, - # make the latest EXTENDED_ARG instruction the target - # of that jump. - if jump_instr.targets: - for target in jump_instr.targets: - if target.end is not jump_instr: - raise ValueError("Invalid target") - target.end = ext_instr - ext_instr.targets.extend(jump_instr.targets) - jump_instr.targets.clear() - new_arg >>= 8 - - # Check if we added any EXTENDED_ARGs because we would have to - # reprocess the branches. - # TODO[perf]: only reprocess the branches that are affected. - # However, this branch is not expected to be taken often. - if c: - exts.append((ext_instr, c)) - # Update the instruction offset from the point of insertion - # of the EXTENDED_ARGs - for instr_index, instr in enumerate(instructions[index + 1 :], index + 1): - instr.offset = instr_index << 1 - - process_branches = True - - # Create the new code object - new_code = bytearray() - for instr in instructions: - new_code.append(instr.opcode) - new_code.append(instr.arg) - - # Instrument nested code objects recursively - for original_offset, nested_code in enumerate(code.co_consts): - if isinstance(nested_code, CodeType): - new_consts[original_offset], nested_lines = instrument_all_lines(nested_code, trap_func, trap_arg, package) - seen_lines.update(nested_lines) - - ext_arg_offsets = [(instr.offset, s) for instr, s in exts] - - return ( - code.replace( - co_code=bytes(new_code), - co_consts=tuple(new_consts), - co_stacksize=code.co_stacksize + 4, # TODO: Compute the value! - co_lnotab=update_location_data(code, traps, ext_arg_offsets), - ), - seen_lines, - ) diff --git a/ddtrace/internal/coverage/instrumentation_py3_9.py b/ddtrace/internal/coverage/instrumentation_py3_9.py new file mode 100644 index 00000000000..05544187618 --- /dev/null +++ b/ddtrace/internal/coverage/instrumentation_py3_9.py @@ -0,0 +1,380 @@ +from abc import ABC +import dis +from enum import Enum +import sys + +# This is primarily to make mypy happy without having to nest the rest of this module behind a version check +# NOTE: the "prettier" one-liner version (eg: assert (3,11) <= sys.version_info < (3,12)) does not work for mypy +from types import CodeType +import typing as t + +from ddtrace.internal.bytecode_injection import HookType +from ddtrace.internal.test_visibility.coverage_lines import CoverageLines + + +if sys.version_info < (3, 10): + + class JumpDirection(int, Enum): + FORWARD = 1 + BACKWARD = -1 + + @classmethod + def from_opcode(cls, opcode: int) -> "JumpDirection": + return cls.BACKWARD if "BACKWARD" in dis.opname[opcode] else cls.FORWARD + + class Jump(ABC): + # NOTE: in Python 3.9, jump arguments are offsets, vs instruction numbers (ie offsets/2) in Python 3.10 + def __init__(self, start: int, arg: int) -> None: + self.start = start + self.end: int + self.arg = arg + + class AJump(Jump): + __opcodes__ = set(dis.hasjabs) + + def __init__(self, start: int, arg: int) -> None: + super().__init__(start, arg) + self.end = self.arg + + class RJump(Jump): + __opcodes__ = set(dis.hasjrel) + + def __init__(self, start: int, arg: int, direction: JumpDirection) -> None: + super().__init__(start, arg) + self.direction = direction + self.end = start + (self.arg) * self.direction + 2 + + class Instruction: + __slots__ = ("offset", "opcode", "arg", "targets") + + def __init__(self, offset: int, opcode: int, arg: int) -> None: + self.offset = offset + self.opcode = opcode + self.arg = arg + self.targets: t.List["Branch"] = [] + + class Branch(ABC): + def __init__(self, start: Instruction, end: Instruction) -> None: + self.start = start + self.end = end + + @property + def arg(self) -> int: + raise NotImplementedError + + class RBranch(Branch): + @property + def arg(self) -> int: + return abs(self.end.offset - self.start.offset - 2) >> 1 + + class ABranch(Branch): + @property + def arg(self) -> int: + return self.end.offset >> 1 + + EXTENDED_ARG = dis.EXTENDED_ARG + NO_OFFSET = -1 + + def instr_with_arg(opcode: int, arg: int) -> t.List[Instruction]: + instructions = [Instruction(-1, opcode, arg & 0xFF)] + arg >>= 8 + while arg: + instructions.insert(0, Instruction(NO_OFFSET, EXTENDED_ARG, arg & 0xFF)) + arg >>= 8 + return instructions + + def update_location_data( + code: CodeType, trap_map: t.Dict[int, int], ext_arg_offsets: t.List[t.Tuple[int, int]] + ) -> bytes: + # Some code objects do not have co_lnotab data (eg: certain lambdas) + if code.co_lnotab == b"": + return code.co_lnotab + + # DEV: We expect the original offsets in the trap_map + new_data = bytearray() + data = code.co_lnotab + + ext_arg_offset_iter = iter(sorted(ext_arg_offsets)) + ext_arg_offset, ext_arg_size = next(ext_arg_offset_iter, (None, None)) + + current_orig_offset = 0 # Cumulative offset used to compare against trap offsets + + # All instructions have to have line numbers, so the first instructions of the trap call must mark the + # beginning of the line. The subsequent offsets need to be incremented by the size of the trap call + # instructions plus any extended args. + + # Set the first trap size: + current_new_offset = accumulated_new_offset = trap_map[0] << 1 + + for i in range(0, len(data), 2): + orig_offset_delta = data[i] + line_delta = data[i + 1] + + # For each original offset, we compute how many offsets have been added in the new code, this includes: + # - the size of the trap at the previous offset + # - the amount of extended args added since the previous offset + + current_new_offset += orig_offset_delta + current_orig_offset += orig_offset_delta + accumulated_new_offset += orig_offset_delta + + # If the current offset is 255, just increment: + if orig_offset_delta == 255: + continue + + # If the current offset is 0, it means we are only incrementing the amount of lines jumped by the previous + # non-zero offset + if orig_offset_delta == 0: + new_data.append(0) + new_data.append(line_delta) + continue + + while ext_arg_offset is not None and ext_arg_size is not None and current_new_offset > ext_arg_offset: + accumulated_new_offset += ext_arg_size << 1 + current_new_offset += ext_arg_size << 1 + ext_arg_offset, ext_arg_size = next(ext_arg_offset_iter, (None, None)) + + # If the current line delta changes, flush accumulated data: + if line_delta != 0: + while accumulated_new_offset > 255: + new_data.append(255) + new_data.append(0) + accumulated_new_offset -= 255 + + new_data.append(accumulated_new_offset) + new_data.append(line_delta) + + # Also add the current trap size to the accumulated offset + accumulated_new_offset = trap_map[current_orig_offset] << 1 + current_new_offset += accumulated_new_offset + + return bytes(new_data) + + LOAD_CONST = dis.opmap["LOAD_CONST"] + CALL = dis.opmap["CALL_FUNCTION"] + POP_TOP = dis.opmap["POP_TOP"] + IMPORT_NAME = dis.opmap["IMPORT_NAME"] + IMPORT_FROM = dis.opmap["IMPORT_FROM"] + + def trap_call(trap_index: int, arg_index: int) -> t.Tuple[Instruction, ...]: + return ( + *instr_with_arg(LOAD_CONST, trap_index), + *instr_with_arg(LOAD_CONST, arg_index), + Instruction(NO_OFFSET, CALL, 1), + Instruction(NO_OFFSET, POP_TOP, 0), + ) + + def instrument_all_lines( + code: CodeType, hook: HookType, path: str, package: str + ) -> t.Tuple[CodeType, CoverageLines]: + # TODO[perf]: Check if we really need to << and >> everywhere + trap_func, trap_arg = hook, path + + instructions: t.List[Instruction] = [] + + new_consts = list(code.co_consts) + trap_index = len(new_consts) + new_consts.append(trap_func) + + seen_lines = CoverageLines() + + offset_map = {} + + # Collect all the original jumps + jumps: t.Dict[int, Jump] = {} + traps: t.Dict[int, int] = {} # DEV: This uses the original offsets + line_map = {} + line_starts = dict(dis.findlinestarts(code)) + + # The previous two arguments are kept in order to track the depth of the IMPORT_NAME + # For example, from ...package import module + current_arg: int = 0 + previous_arg: int = 0 + previous_previous_arg: int = 0 + current_import_name: t.Optional[str] = None + current_import_package: t.Optional[str] = None + + try: + code_iter = iter(enumerate(code.co_code)) + ext: list[int] = [] + while True: + original_offset, opcode = next(code_iter) + + if original_offset in line_starts: + # Inject trap call at the beginning of the line. Keep track + # of location and size of the trap call instructions. We + # need this to adjust the location table. + line = line_starts[original_offset] + trap_instructions = trap_call(trap_index, len(new_consts)) + traps[original_offset] = len(trap_instructions) + instructions.extend(trap_instructions) + + # Make sure that the current module is marked as depending on its own package by instrumenting the + # first executable line + package_dep = None + if code.co_name == "" and len(new_consts) == len(code.co_consts) + 1: + package_dep = (package, ("",)) + + new_consts.append((line, trap_arg, package_dep)) + + line_map[original_offset] = trap_instructions[0] + + seen_lines.add(line) + + _, arg = next(code_iter) + + offset = len(instructions) << 1 + + # Propagate code + instructions.append(Instruction(original_offset, opcode, arg)) + + if opcode is EXTENDED_ARG: + ext.append(arg) + continue + else: + previous_previous_arg = previous_arg + previous_arg = current_arg + current_arg = int.from_bytes([*ext, arg], "big", signed=False) + ext.clear() + + # Track imports names + if opcode == IMPORT_NAME: + import_depth = code.co_consts[previous_previous_arg] + current_import_name = code.co_names[current_arg] + # Adjust package name if the import is relative and a parent (ie: if depth is more than 1) + current_import_package = ( + ".".join(package.split(".")[: -import_depth + 1]) if import_depth > 1 else package + ) + new_consts[-1] = ( + new_consts[-1][0], + new_consts[-1][1], + (current_import_package, (current_import_name,)), + ) + + # Also track import from statements since it's possible that the "from" target is a module, eg: + # from my_package import my_module + # Since the package has not changed, we simply extend the previous import names with the new value + if opcode == IMPORT_FROM: + import_from_name = f"{current_import_name}.{code.co_names[current_arg]}" + new_consts[-1] = ( + new_consts[-1][0], + new_consts[-1][1], + (new_consts[-1][2][0], tuple(list(new_consts[-1][2][1]) + [import_from_name])), + ) + + # Collect branching instructions for processing + if opcode in AJump.__opcodes__: + jumps[offset] = AJump(original_offset, current_arg) + elif opcode in RJump.__opcodes__: + jumps[offset] = RJump(original_offset, current_arg, JumpDirection.from_opcode(opcode)) + + if opcode is EXTENDED_ARG: + ext.append(arg) + else: + ext.clear() + except StopIteration: + pass + + # Collect all the old jump start and end offsets + jump_targets = {_ for j in jumps.values() for _ in (j.start, j.end)} + + # Adjust all the offsets and map the old offsets to the new ones for the + # jumps + for index, instr in enumerate(instructions): + new_offset = index << 1 + if instr.offset in jump_targets: + offset_map[instr.offset] = new_offset + instr.offset = new_offset + + # Adjust all the jumps, neglecting any EXTENDED_ARGs for now + branches: t.List[Branch] = [] + for jump in jumps.values(): + new_start = offset_map[jump.start] + new_end = offset_map[jump.end] + + # If we are jumping at the beginning of a line, jump to the + # beginning of the trap call instead + target_instr = line_map.get(jump.end, instructions[new_end >> 1]) + branch: Branch = ( + RBranch(instructions[new_start >> 1], target_instr) + if isinstance(jump, RJump) + else ABranch(instructions[new_start >> 1], target_instr) + ) + target_instr.targets.append(branch) + + branches.append(branch) + + # Process all the branching instructions to adjust the arguments. We + # need to add EXTENDED_ARGs if the argument is too large. + process_branches = True + exts: t.List[t.Tuple[Instruction, int]] = [] + while process_branches: + process_branches = False + for branch in branches: + jump_instr = branch.start + new_arg = branch.arg << 1 # 3.9 uses offsets, not instruction numbers + jump_instr.arg = new_arg & 0xFF + new_arg >>= 8 + c = 0 + index = jump_instr.offset >> 1 + + # Update the argument of the branching instruction, adding + # EXTENDED_ARGs if needed + while new_arg: + if index and instructions[index - 1].opcode is EXTENDED_ARG: + index -= 1 + instructions[index].arg = new_arg & 0xFF + else: + ext_instr = Instruction(index << 1, EXTENDED_ARG, new_arg & 0xFF) + instructions.insert(index, ext_instr) + c += 1 + # If the jump instruction was a target of another jump, + # make the latest EXTENDED_ARG instruction the target + # of that jump. + if jump_instr.targets: + for target in jump_instr.targets: + if target.end is not jump_instr: + raise ValueError("Invalid target") + target.end = ext_instr + ext_instr.targets.extend(jump_instr.targets) + jump_instr.targets.clear() + new_arg >>= 8 + + # Check if we added any EXTENDED_ARGs because we would have to + # reprocess the branches. + # TODO[perf]: only reprocess the branches that are affected. + # However, this branch is not expected to be taken often. + if c: + exts.append((ext_instr, c)) + # Update the instruction offset from the point of insertion + # of the EXTENDED_ARGs + for instr_index, instr in enumerate(instructions[index + 1 :], index + 1): + instr.offset = instr_index << 1 + + process_branches = True + + # Create the new code object + new_code = bytearray() + for instr in instructions: + new_code.append(instr.opcode) + new_code.append(instr.arg) + + # Instrument nested code objects recursively + for original_offset, nested_code in enumerate(code.co_consts): + if isinstance(nested_code, CodeType): + new_consts[original_offset], nested_lines = instrument_all_lines( + nested_code, trap_func, trap_arg, package + ) + seen_lines.update(nested_lines) + + ext_arg_offsets = [(instr.offset, s) for instr, s in exts] + + return ( + code.replace( + co_code=bytes(new_code), + co_consts=tuple(new_consts), + co_stacksize=code.co_stacksize + 4, # TODO: Compute the value! + co_lnotab=update_location_data(code, traps, ext_arg_offsets), + ), + seen_lines, + ) diff --git a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt index dbe4395cc16..1b111633968 100644 --- a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt @@ -57,10 +57,6 @@ add_library(${EXTENSION_NAME} SHARED ${DDUP_CPP_SRC}) add_ddup_config(${EXTENSION_NAME}) # Cython generates code that produces errors for the following, so relax compile options target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address) -# tp_print is marked deprecated in Python 3.8, but cython still generates code using it -if("${Python3_VERSION_MINOR}" STREQUAL "8") - target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-deprecated-declarations) -endif() # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. diff --git a/ddtrace/profiling/_asyncio.py b/ddtrace/profiling/_asyncio.py index 66d44f7fc45..32f85b7be1f 100644 --- a/ddtrace/profiling/_asyncio.py +++ b/ddtrace/profiling/_asyncio.py @@ -50,9 +50,7 @@ def _(asyncio): elif hasattr(asyncio.Task, "all_tasks"): globals()["all_tasks"] = asyncio.Task.all_tasks - if hasattr(asyncio.Task, "get_name"): - # `get_name` is only available in Python ≥ 3.8 - globals()["_task_get_name"] = lambda task: task.get_name() + globals()["_task_get_name"] = lambda task: task.get_name() if THREAD_LINK is None: THREAD_LINK = _threading._ThreadLink() diff --git a/ddtrace/profiling/_threading.pyx b/ddtrace/profiling/_threading.pyx index 2a20b29b678..70896332424 100644 --- a/ddtrace/profiling/_threading.pyx +++ b/ddtrace/profiling/_threading.pyx @@ -55,14 +55,7 @@ cpdef get_thread_native_id(thread_id): if thread is None: return thread_id - try: - # We prioritize using native ids since we expect them to be surely unique for a program. This is less true - # for hashes since they are relative to the memory address which can easily be the same across different - # objects. - return thread.native_id - except AttributeError: - # Python < 3.8 - return hash(thread) + return thread.native_id # cython does not play well with mypy diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index 78fb0efd26a..05b62469911 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -180,24 +180,22 @@ ELIF UNAME_SYSNAME != "Windows": PyObject* PyException_GetTraceback(PyObject* exc) PyObject* Py_TYPE(PyObject* ob) - IF PY_VERSION_HEX >= 0x03080000: - # Python 3.8 - cdef extern from "": + cdef extern from "": - cdef struct pyinterpreters: - PyThread_type_lock mutex + cdef struct pyinterpreters: + PyThread_type_lock mutex - ctypedef struct _PyRuntimeState: - pyinterpreters interpreters + ctypedef struct _PyRuntimeState: + pyinterpreters interpreters - cdef extern _PyRuntimeState _PyRuntime + cdef extern _PyRuntimeState _PyRuntime - IF PY_VERSION_HEX >= 0x03090000: - # Needed for accessing _PyGC_FINALIZED when we build with -DPy_BUILD_CORE - cdef extern from "": - pass - cdef extern from "": - PyObject* PyThreadState_GetFrame(PyThreadState* tstate) + IF PY_VERSION_HEX >= 0x03090000: + # Needed for accessing _PyGC_FINALIZED when we build with -DPy_BUILD_CORE + cdef extern from "": + pass + cdef extern from "": + PyObject* PyThreadState_GetFrame(PyThreadState* tstate) ELSE: FEATURES['stack-exceptions'] = False diff --git a/docs/index.rst b/docs/index.rst index 6b3d6ffa67c..47c315eff5d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -52,7 +52,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`aiopg` | >= 0.16.0 | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`algoliasearch` | >= 2.5.0 | Yes | | +| :ref:`algoliasearch` | >= 2.6.3 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`anthropic` | >= 0.28.0 | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -62,7 +62,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`asyncio` | \* | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`asyncpg` | >= 0.22.0 | Yes | | +| :ref:`asyncpg` | >= 0.23.0 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`avro` | \* | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -108,7 +108,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`futures` | \* | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`gevent` (greenlet>=1.0) | >= 20.12 | Yes | | +| :ref:`gevent` (greenlet>=1.0) | >= 21.1.2 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`google_adk` | >= 1.0.0 | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -164,7 +164,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`protobuf` | \* | Yes [6]_ | | +--------------------------------------------------+------------+----------+------+ -| :ref:`psycopg` | >= 2.8 | Yes | | +| :ref:`psycopg` | >= 2.9.10 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`pylibmc` | >= 1.6.2 | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -174,7 +174,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`pymysql` | >= 0.10 | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`pynamodb` | >= 5.0 | Yes | | +| :ref:`pynamodb` | >= 5.5.1 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`pyodbc` | >= 4.0.31 | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -190,7 +190,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`rediscluster` | >= 2.0 | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`requests` | >= 2.20 | Yes | | +| :ref:`requests` | >= 2.25.1 | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`rq` | >= 1.8 | Yes | | +--------------------------------------------------+------------+----------+------+ @@ -198,7 +198,7 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`selenium` | \* | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`snowflake` | >= 2.3.0 | No | | +| :ref:`snowflake` | >= 2.4.6 | No | | +--------------------------------------------------+------------+----------+------+ | :ref:`sqlalchemy` | >= 1.3 | No | | +--------------------------------------------------+------------+----------+------+ diff --git a/hatch.toml b/hatch.toml index f826ba78a09..f3fb254691e 100644 --- a/hatch.toml +++ b/hatch.toml @@ -234,7 +234,7 @@ test = [ ] [[envs.multiple_os_tests.matrix]] -python = ["3.14", "3.12", "3.10", "3.8"] +python = ["3.14", "3.12", "3.10"] [envs.snapshot_viewer] dev-mode = false diff --git a/pyproject.toml b/pyproject.toml index bf2080b78d8..4407053be9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,14 @@ build-backend = "setuptools.build_meta" [project] name = "ddtrace" -dynamic = ["version"] +# DEV: to directly override the version specifier, comment this... +#dynamic = ["version"] +# ...and uncomment this +version = "4.0.0.dev0" description = "Datadog APM client library" readme = "README.md" license = { text = "LICENSE.BSD3" } -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [ { name = "Datadog, Inc.", email = "dev@datadoghq.com" }, ] @@ -106,7 +109,7 @@ exclude = ''' [tool.black] line-length = 120 -target_version = ['py37', 'py38', 'py39', 'py310', 'py311', 'py312'] +target_version = ['py39', 'py310', 'py311', 'py312'] include = '''\.py[ix]?$''' exclude = ''' ( diff --git a/releasenotes/notes/py38-remove-52943a5d318b4736.yaml b/releasenotes/notes/py38-remove-52943a5d318b4736.yaml new file mode 100644 index 00000000000..f71a666e383 --- /dev/null +++ b/releasenotes/notes/py38-remove-52943a5d318b4736.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + Support for ddtrace with Python 3.8 is removed after being deprecated in the 3.0 release line. Use ddtrace 4.x with + Python 3.9 or newer. +deprecations: + - | + Support for ddtrace with Python 3.9 is deprecated after Python 3.9 reached its end-of-life. diff --git a/riotfile.py b/riotfile.py index a5981542df4..95a747ed460 100644 --- a/riotfile.py +++ b/riotfile.py @@ -10,21 +10,18 @@ latest = "" SUPPORTED_PYTHON_VERSIONS: List[Tuple[int, int]] = [ - (3, 8), (3, 9), (3, 10), (3, 11), (3, 12), (3, 13), (3, 14), -] # type: List[Tuple[int, int]] +] def version_to_str(version: Tuple[int, int]) -> str: """Convert a Python version tuple to a string - >>> version_to_str((3, 8)) - '3.8' >>> version_to_str((3, 9)) '3.9' >>> version_to_str((3, 10)) @@ -46,8 +43,6 @@ def version_to_str(version: Tuple[int, int]) -> str: def str_to_version(version: str) -> Tuple[int, int]: """Convert a Python version string to a tuple - >>> str_to_version("3.8") - (3, 8) >>> str_to_version("3.9") (3, 9) >>> str_to_version("3.10") @@ -74,13 +69,13 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT """Helper to select python versions from the list of versions we support >>> select_pys() - ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] >>> select_pys(min_version='3') - ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] + ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] >>> select_pys(max_version='3') [] - >>> select_pys(min_version='3.8', max_version='3.9') - ['3.8', '3.9'] + >>> select_pys(min_version='3.9', max_version='3.10') + ['3.9', '3.10'] """ min_version = str_to_version(min_version) max_version = str_to_version(max_version) @@ -188,7 +183,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="appsec_iast_packages", # FIXME: GrpcIO is hanging with 3.13 on CI + hatch for some reason - pys=["3.8", "3.9", "3.10", "3.11", "3.12"], + pys=["3.9", "3.10", "3.11", "3.12"], command="pytest {cmdargs} tests/appsec/iast_packages/", pkgs={ "requests": latest, @@ -244,19 +239,19 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=["3.8", "3.9"], + pys=["3.9"], pkgs={"django": "~=2.2"}, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], + pys=["3.9", "3.10", "3.11", "3.12", "3.13"], pkgs={"django": "~=3.2"}, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], + pys=["3.9", "3.10", "3.11", "3.12", "3.13"], pkgs={"django": "==4.0.10"}, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], + pys=["3.9", "3.10", "3.11", "3.12", "3.13"], pkgs={"django": "~=4.2"}, ), Venv( @@ -284,18 +279,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "DD_IAST_DEDUPLICATION_ENABLED": "false", }, venvs=[ - Venv( - pys=["3.8"], - pkgs={"fastapi": "==0.86.0", "anyio": "==3.7.1"}, - ), - Venv( - pys=["3.8"], - pkgs={"fastapi": "==0.94.1"}, - ), - Venv( - pys=["3.8"], - pkgs={"fastapi": "~=0.114.2"}, - ), Venv( pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={"fastapi": "==0.86.0", "anyio": "==3.7.1"}, @@ -516,7 +499,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "pytest-asyncio": "~=0.23.7", }, @@ -563,14 +546,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "opensearch-py": latest, }, venvs=[ - Venv( - pys="3.8", - pkgs={ - "gevent": "~=20.12.0", - # greenlet v1.0.0 adds support for contextvars - "greenlet": "~=1.0.0", - }, - ), Venv( pys="3.9", pkgs={ @@ -678,7 +653,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # FIXME: tests fail on vertica 1.x # Venv( # # vertica-python added support for Python 3.9/3.10 in 1.0 - # pys=select_pys(min_version="3.8", max_version="3.10"), + # pys=select_pys(min_version="3.9", max_version="3.10"), # pkgs={"vertica-python": ["~=1.0", latest]}, # ), # Venv( @@ -737,7 +712,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "falcon": [ "~=3.0.0", @@ -793,23 +768,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - # Celery 4.3 wants Kombu >= 4.4 and Redis >= 3.2 - # Split into <3.8 and >=3.8 to pin importlib_metadata dependency for kombu - # # celery added support for Python 3.9 in 4.x - # pys=select_pys(min_version="3.8", max_version="3.9"), - # pkgs={ - # "pytest": "~=4.0", - # "celery": [ - # "latest", # most recent 4.x - # ], - # "redis": "~=3.5", - # "kombu": "~=4.4", - # }, - # ), - # Celery 5.x wants Python 3.6+ - # Split into <3.8 and >=3.8 to pin importlib_metadata dependency for kombu - Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + Venv( + pys=["3.9"], env={ # https://docs.celeryproject.org/en/v5.0.5/userguide/testing.html#enabling "PYTEST_PLUGINS": "celery.contrib.pytest", @@ -856,7 +816,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # cherrypy added support for Python 3.11 in 18.7 - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "cherrypy": [">=18.0,<19", latest], "more_itertools": "<8.11.0", @@ -876,7 +836,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # pymmongo<3.9, 3.9<=pymongo<3.12, 3.12<=pymongo<4.5, pymongo>=4.5 # To get full test coverage we must test all these version ranges Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={"pymongo": ["~=3.8.0", "~=3.9.0", "~=3.11", "~=4.0", latest]}, ), Venv( @@ -891,14 +851,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT name="ddtrace_api", command="pytest {cmdargs} tests/contrib/ddtrace_api", pkgs={"ddtrace-api": "==0.0.1", "requests": latest}, - pys=select_pys(min_version="3.8"), + pys=select_pys(), ), # Django Python version support - # 2.2 3.5, 3.6, 3.7, 3.8 3.9 - # 3.2 3.6, 3.7, 3.8, 3.9, 3.10 - # 4.0 3.8, 3.9, 3.10 - # 4.1 3.8, 3.9, 3.10, 3.11 - # 4.2 3.8, 3.9, 3.10, 3.11, 3.12 + # 2.2 3.9 + # 3.2 3.9, 3.10 + # 4.0 3.9, 3.10 + # 4.1 3.9, 3.10, 3.11 + # 4.2 3.9, 3.10, 3.11, 3.12 # 5.0 3.10, 3.11, 3.12 # 5.1 3.10, 3.11, 3.12, 3.13 # 5.2 3.10, 3.11, 3.12, 3.13 @@ -930,10 +890,10 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - # django dropped support for Python 3.8/3.9 in 5.0 + # django dropped support for Python 3.9 in 5.0 # limit tests to only the main django test files to avoid import errors due to some tests # targeting newer django versions - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], command="pytest {cmdargs} --ignore=tests/contrib/django/test_django_snapshots.py \ --ignore=tests/contrib/django/test_django_wsgi.py tests/contrib/django", pkgs={ @@ -943,7 +903,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( # django started supporting psycopg3 in 4.2 for versions >3.1.8 - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "django": ["~=4.2"], "psycopg": latest, @@ -964,14 +924,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "django_hosts": "~=4.0", "django": "~=3.2", }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "django_hosts": ["~=5.0", latest], "django": "~=4.0", @@ -990,21 +950,21 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=[ Venv( # djangorestframework dropped support for Django 2.x in 3.14 - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={ "django": ">=2.2,<2.3", "djangorestframework": ["==3.12.4", "==3.13.1"], }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "django": "~=3.2", "djangorestframework": ">=3.11,<3.12", }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "django": ["~=4.0"], "djangorestframework": ["~=3.13", latest], @@ -1028,7 +988,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={ "sqlalchemy": "~=1.2.18", "django": "~=2.2.0", @@ -1048,7 +1008,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/dramatiq", venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={"dramatiq": "~=1.10.0", "pytest": latest, "redis": latest, "pika": latest}, ), Venv( @@ -1171,7 +1131,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "flask": [ "~=2.0", @@ -1183,7 +1143,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="python tests/ddtrace_run.py pytest {cmdargs} tests/contrib/flask_autopatch", env={ "DD_SERVICE": "test.flask.service", @@ -1229,7 +1189,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "markupsafe": "<2.0", }, venvs=[ - Venv(pys=select_pys(min_version="3.8", max_version="3.9"), pkgs={"exceptiongroup": latest}), + Venv(pys=["3.9"], pkgs={"exceptiongroup": latest}), ], ), Venv( @@ -1245,7 +1205,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), ), Venv( pys=select_pys(min_version="3.12", max_version="3.13"), @@ -1262,7 +1222,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), ), Venv(pys=select_pys(min_version="3.12", max_version="3.13"), pkgs={"redis": latest}), ], @@ -1286,7 +1246,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={"mysql-connector-python": ["==8.0.5", latest]}, ), Venv( @@ -1313,13 +1273,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys="3.8", - pkgs={"psycopg2-binary": "~=2.8.0"}, - ), - Venv( - pys=select_pys(min_version="3.8"), - # psycopg2-binary added support for Python 3.9/3.10 in 2.9.1 - # psycopg2-binary added support for Python 3.11 in 2.9.2 + pys=select_pys(), pkgs={"psycopg2-binary": ["~=2.9.2", latest]}, ), ], @@ -1334,14 +1288,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys=["3.9"], pkgs={ "psycopg": "~=3.0.0", "pytest-asyncio": "==0.21.1", }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "psycopg": latest, "pytest-asyncio": "==0.21.1", @@ -1428,19 +1382,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # TODO: Py312 requires changes to test code venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.8"), - pkgs={ - "pynamodb": ["~=5.0.0"], - "botocore": ["<=1.25.0"], - "moto": ">=1.0,<2.0", - "cfn-lint": "~=0.53.1", - "Jinja2": "~=2.10.0", - "pytest-randomly": latest, - "pytest-xdist": latest, - }, - ), - Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "pynamodb": ["~=5.3", "<6.0"], "moto": ">=1.0,<2.0", @@ -1471,7 +1413,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # starlette added new root_path/path definitions after v0.33 Venv( # starlette added support for Python 3.9 in 0.14 - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={"starlette": ["~=0.14.0", "~=0.20.0", "~=0.33.0"], "httpx": "~=0.22.0"}, ), Venv( @@ -1489,7 +1431,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"starlette": latest, "httpx": "~=0.27.0"}, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={"starlette": [latest], "httpx": "~=0.22.0"}, ), ], @@ -1514,7 +1456,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "greenlet": "==3.0.3", "sqlalchemy": ["~=1.3.0", latest], @@ -1543,16 +1485,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "requests-mock": ">=1.4", }, venvs=[ - Venv( - # requests added support for Python 3.8 in 2.23 - pys="3.8", - pkgs={ - "requests": [ - "~=2.20.0", - latest, - ], - }, - ), Venv( # requests added support for Python 3.9 in 2.25 pys="3.9", @@ -1619,7 +1551,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"botocore": "==1.34.49", "boto3": "==1.34.49"}, venvs=[ Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), ), ], ), @@ -1627,7 +1559,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"vcrpy": "==7.0.0", "botocore": "==1.38.26", "boto3": "==1.38.26"}, venvs=[ Venv( - pys=select_pys(min_version="3.9"), + pys=select_pys(), ), ], ), @@ -1641,7 +1573,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "asgiref": ["~=3.0.0", "~=3.0", latest], "pytest-randomly": latest, }, - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} tests/contrib/asgi", ), Venv( @@ -1652,7 +1584,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "mariadb": [ "~=1.0.0", @@ -1672,12 +1604,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - # pymysql added support for Python 3.8/3.9 in 0.10 - pys=select_pys(min_version="3.8", max_version="3.9"), + # pymysql added support for Python 3.9 in 0.10 + pys="3.9", pkgs={"pymysql": "~=0.10"}, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pymysql": [ "~=1.0", @@ -1706,7 +1638,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "pyramid": [ "~=1.10", @@ -1735,7 +1667,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "aiobotocore": ["~=1.0.0", "~=1.4.2", "~=2.0.0", latest], }, @@ -1759,7 +1691,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={"fastapi": ["~=0.64.0", "~=0.90.0", latest]}, ), Venv( @@ -1778,7 +1710,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/aiomysql", venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pytest-randomly": latest, "pytest-asyncio": "==0.21.1", @@ -1807,7 +1739,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "pytest": [ ">=6.0,<7.0", @@ -1873,7 +1805,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "pytest": [ ">=6.0,<7.0", @@ -1899,7 +1831,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "pytest-bdd": [ ">=4.0,<5.0", @@ -1921,7 +1853,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="pytest_benchmark", - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} --no-ddtrace --no-cov tests/contrib/pytest_benchmark/", pkgs={ "msgpack": latest, @@ -1939,7 +1871,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="pytest:flaky", - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} --no-ddtrace --no-cov -p no:flaky tests/contrib/pytest_flaky/", pkgs={ "flaky": latest, @@ -1957,7 +1889,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # Versions between 1.14 and 1.20 have known threading issues # See https://github.com/grpc/grpc/issues/18994 Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={"grpcio": ["~=1.34.0", latest]}, ), Venv( @@ -2004,7 +1936,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={"_DD_TRACE_GRPC_AIO_ENABLED": "true"}, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "grpcio": ["~=1.34.0", "~=1.59.0"], "pytest-asyncio": "==0.23.7", @@ -2038,7 +1970,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "graphene": ["~=3.0.0", latest], "pytest-asyncio": "==0.21.1", @@ -2056,7 +1988,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="graphql", command="pytest {cmdargs} tests/contrib/graphql", - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "pytest-asyncio": "==0.21.1", "graphql-core": ["~=3.2.0", latest], @@ -2071,18 +2003,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - Venv( - pys="3.8", - pkgs={ - "rq": [ - "~=1.8.0", - "~=1.10.0", - latest, - ], - # https://github.com/rq/rq/issues/1469 rq [1.0,1.8] is incompatible with click 8.0+ - "click": "==7.1.2", - }, - ), Venv( # rq added support for Python 3.9 in 1.8.1 pys="3.9", @@ -2105,7 +2025,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="httpx", - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} tests/contrib/httpx", pkgs={ "pytest-asyncio": "==0.21.1", @@ -2124,11 +2044,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - Venv( - # Support added for Python 3.8 in 1.25.0 - pys="3.8", - pkgs={"urllib3": ["==1.25.0", latest]}, - ), Venv( # Support added for Python 3.9 in 1.25.8 pys="3.9", @@ -2156,13 +2071,9 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/algoliasearch", pkgs={"urllib3": "~=1.26.15", "pytest-randomly": latest}, venvs=[ - Venv( - pys="3.8", - pkgs={"algoliasearch": ["~=2.5.0", "~=2.6"]}, - ), Venv( # algoliasearch added support for Python 3.9, 3.10, 3.11 in 3.0 - pys=select_pys(min_version="3.9"), + pys=select_pys(), pkgs={"algoliasearch": "~=2.6"}, ), ], @@ -2176,13 +2087,13 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "aiopg": ["~=0.16.0"], }, ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "aiopg": ["~=1.0", "~=1.4.0"], }, @@ -2205,7 +2116,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # only test a subset of files for older aiohttp versions command="pytest {cmdargs} tests/contrib/aiohttp/test_aiohttp_client.py \ tests/contrib/aiohttp/test_aiohttp_patch.py", - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "pytest-aiohttp": ["<=1.0.5"], "aiohttp": ["~=3.7.0"], @@ -2213,7 +2124,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pytest-asyncio": ["==0.23.7"], "pytest-aiohttp": ["==1.0.5"], @@ -2246,7 +2157,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={ "pytest-asyncio": ["==0.23.7"], }, @@ -2266,7 +2177,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(max_version="3.9"), + pys="3.9", pkgs={ "jinja2": "~=2.10.0", # https://github.com/pallets/markupsafe/issues/282 @@ -2275,7 +2186,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "jinja2": ["~=3.0.0", latest], }, @@ -2308,7 +2219,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "pytest-asyncio": "==0.23.7", }, @@ -2344,7 +2255,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="aredis", - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", command="pytest {cmdargs} tests/contrib/aredis", pkgs={ "pytest-asyncio": "==0.21.1", @@ -2354,7 +2265,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="avro", - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} tests/contrib/avro", pkgs={ "avro": latest, @@ -2364,7 +2275,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="protobuf", command="pytest {cmdargs} tests/contrib/protobuf", - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={ "protobuf": latest, "pytest-randomly": latest, @@ -2379,7 +2290,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={"yaaredis": ["~=2.0.0", latest]}, ), Venv( @@ -2401,14 +2312,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=[ Venv( # sanic added support for Python 3.9 in 20.12 - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "sanic": "~=20.12", "pytest-sanic": "~=1.6.2", }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "sanic": [ "~=21.3", @@ -2426,7 +2337,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "sanic": ["~=22.3", "~=22.12"], "sanic-testing": "~=22.3.0", @@ -2458,10 +2369,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - Venv( - pys="3.8", - pkgs={"snowflake-connector-python": ["~=2.3.0", "~=2.9.0", latest]}, - ), Venv( # snowflake-connector-python added support for Python 3.9 in 2.4.0 pys="3.9", @@ -2495,11 +2402,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - # our test_asyncpg.py uses `yield` in an async function and is not compatible with Python 3.5 - Venv( - pys="3.8", - pkgs={"asyncpg": ["~=0.22.0", latest]}, - ), Venv( # asyncpg added support for Python 3.9 in 0.22 pys="3.9", @@ -2541,7 +2443,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT # To test a range of versions without updating Python, we use Linux only pysqlite3-binary package # Remove pysqlite3-binary on Python 3.9+ locally on non-linux machines Venv(pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={"pysqlite3-binary": [latest]}), - Venv(pys=select_pys(max_version="3.8"), pkgs={"importlib-metadata": latest}), ], ), Venv( @@ -2568,7 +2469,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - Venv(pys=select_pys(min_version="3.8", max_version="3.10")), + Venv(pys=select_pys(min_version="3.9", max_version="3.10")), Venv(pys=select_pys(min_version="3.11"), pkgs={"attrs": latest}), ], ), @@ -2580,7 +2481,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "dogpile.cache": [ "~=0.6.0", @@ -2629,29 +2530,16 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "markupsafe": "==2.0.1", "mock": latest, "flask": latest, - "gevent": latest, # gevent>22.12 is not compatible with py3.8 + "gevent": latest, "requests": "==2.28.1", # specific version expected by tests }, venvs=[ - Venv( - pys="3.8", - # Ensure we test against versions of opentelemetry-api that broke compatibility with ddtrace - # gevent>24.2.1 is not compatible with py3.8 so we pin it to the last compatible version - pkgs={"gevent": "<=24.2.1", "opentelemetry-api": ["~=1.0.0", "~=1.15.0", "~=1.26.0", latest]}, - ), Venv( # opentelemetry-api doesn't yet work with Python 3.14 pys=select_pys(min_version="3.9", max_version="3.13"), # Ensure we test against versions of opentelemetry-api that broke compatibility with ddtrace pkgs={"opentelemetry-api": ["~=1.0.0", "~=1.15.0", "~=1.26.0", latest]}, ), - Venv( - pys="3.8", - # Ensure we test against versions of opentelemetry-api that broke compatibility with ddtrace - # gevent>24.2.1 is not compatible with py3.8 so we pin it to the last compatible version - pkgs={"gevent": "<=24.2.1", "opentelemetry-exporter-otlp": ["~=1.15.0", latest]}, - env={"SDK_EXPORTER_INSTALLED": "1"}, - ), Venv( # opentelemetry-exporter-otlp doesn't yet work with Python 3.14 pys=select_pys(min_version="3.9", max_version="3.13"), @@ -2693,7 +2581,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "openai[embeddings,datalib]": ["==1.0.0", "==1.30.1"], "pillow": "==9.5.0", @@ -2701,7 +2589,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "openai": ["<2.0.0", "~=1.76.2", "==1.66.0"], "tiktoken": latest, @@ -2720,12 +2608,12 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/opentracer/core", ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), command="pytest {cmdargs} tests/opentracer/test_tracer_asyncio.py", pkgs={"pytest-asyncio": "==0.21.1"}, ), Venv( - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), command="pytest {cmdargs} tests/opentracer/test_tracer_tornado.py", # TODO: update opentracing tests to be compatible with Tornado v6. # https://github.com/opentracing/opentracing-python/issues/136 @@ -2736,13 +2624,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( command="pytest {cmdargs} tests/opentracer/test_tracer_gevent.py", venvs=[ - Venv( - pys="3.8", - pkgs={ - "gevent": latest, - "greenlet": latest, - }, - ), Venv( pys="3.9", pkgs={"gevent": latest, "greenlet": latest}, @@ -2772,10 +2653,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/pyodbc", pkgs={"pytest-randomly": latest}, venvs=[ - Venv( - pys=select_pys(max_version="3.8"), - pkgs={"pyodbc": ["~=4.0.31", latest]}, - ), Venv( # pyodbc added support for Python 3.9/3.10 in 4.0.34 pys=select_pys(min_version="3.9", max_version="3.10"), @@ -2794,8 +2671,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"pytest-randomly": latest}, venvs=[ Venv( - # pylibmc added support for Python 3.8/3.9/3.10 in 1.6.2 - pys=select_pys(min_version="3.8", max_version="3.10"), + # pylibmc added support for Python 3.9/3.10 in 1.6.2 + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={ "pylibmc": ["~=1.6.2", latest], }, @@ -2814,7 +2691,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"pytest-randomly": latest}, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={ "kombu": [">=4.6,<4.7", ">=5.0,<5.1", latest], }, @@ -2835,8 +2712,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"pytest-randomly": latest}, venvs=[ Venv( - # tornado added support for Python 3.8/3.9 in 6.1 - pys=select_pys(min_version="3.8", max_version="3.9"), + # tornado added support for Python 3.9 in 6.1 + pys="3.9", # tornado 6.0.x and pytest 8.x have a compatibility bug pkgs={"tornado": ["~=6.0.0", "~=6.2"], "pytest": "<=8"}, ), @@ -2858,7 +2735,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pkgs={"pytest-randomly": latest}, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.9"), + pys="3.9", pkgs={"mysqlclient": ["~=2.0", "~=2.1", latest]}, ), Venv( @@ -2984,11 +2861,11 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.12"), + pys=select_pys(min_version="3.9", max_version="3.12"), pkgs={"anthropic": "~=0.28.0", "httpx": "~=0.27.0"}, ), Venv( - pys=select_pys(min_version="3.8"), + pys=select_pys(), pkgs={"anthropic": latest, "httpx": "<0.28.0"}, ), ], @@ -3030,7 +2907,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.9"), + pys=select_pys(), ), ], ), @@ -3043,7 +2920,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.9"), + pys=select_pys(), ), ], ), @@ -3068,7 +2945,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=select_pys(min_version="3.9"), + pys=select_pys(), pkgs={ "pydantic-ai": ["==0.3.0", "==0.4.4"], "pydantic": "==2.12.0a1", @@ -3138,7 +3015,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} -vv tests/contrib/kafka", venvs=[ Venv( - pys=select_pys(min_version="3.8", max_version="3.10"), + pys=select_pys(min_version="3.9", max_version="3.10"), pkgs={"confluent-kafka": ["~=1.9.2", latest]}, ), # confluent-kafka added support for Python 3.11 in 2.0.2 @@ -3150,7 +3027,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="aws_lambda", command="pytest --no-ddtrace {cmdargs} tests/contrib/aws_lambda", - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "boto3": latest, "datadog-lambda": [">=6.105.0", latest], @@ -3161,7 +3038,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="azure_eventhubs", command="pytest {cmdargs} tests/contrib/azure_eventhubs", - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ "azure.eventhub": ["~=5.12.0", latest], "pytest-asyncio": "==0.23.7", @@ -3170,7 +3047,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="azure_functions", command="pytest {cmdargs} tests/contrib/azure_functions", - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "azure.functions": ["~=1.10.1", latest], "requests": latest, @@ -3179,7 +3056,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="azure_functions:eventhubs", command="pytest {cmdargs} tests/contrib/azure_functions_eventhubs", - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "azure.functions": ["~=1.10.1", latest], "azure.eventhub": latest, @@ -3189,7 +3066,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( name="azure_functions:servicebus", command="pytest {cmdargs} tests/contrib/azure_functions_servicebus", - pys=select_pys(min_version="3.8", max_version="3.11"), + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "azure.functions": ["~=1.10.1", latest], "azure.servicebus": latest, @@ -3237,12 +3114,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "DD_AGENT_PORT": "9126", }, venvs=[ - Venv( - pys=["3.8"], - pkgs={"greenlet": "==3.1.0"}, - # Prevent segfaults from zope.interface c optimizations - env={"PURE_PYTHON": "1"}, - ), Venv( pys=select_pys(min_version="3.9", max_version="3.13"), ), @@ -3279,7 +3150,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "langchain": latest, "pandas": latest, }, - pys=select_pys(min_version="3.8", max_version="3.13"), + pys=select_pys(min_version="3.9", max_version="3.13"), ), Venv( name="valkey", @@ -3289,7 +3160,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, "pytest-asyncio": "==0.23.7", }, - pys=select_pys(min_version="3.8"), + pys=select_pys(), ), Venv( name="profile", @@ -3312,9 +3183,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "pytest-randomly": latest, }, venvs=[ - # Python 3.8 + 3.9 Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={"uwsgi": latest}, venvs=[ Venv( @@ -3415,9 +3285,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT pys=select_pys(max_version="3.13"), pkgs={"uwsgi": "<2.0.30"}, ), - # Python 3.8 + 3.9 Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={"uwsgi": latest}, venvs=[ Venv( @@ -3566,7 +3435,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={ "flask": "~=1.1", "MarkupSafe": "~=1.1", @@ -3575,26 +3444,25 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11"], + pys=select_pys(min_version="3.9", max_version="3.11"), pkgs={ "flask": "~=2.2", }, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], + pys=select_pys(max_version="3.13"), pkgs={ "flask": "~=2.2", }, ), Venv( - pys=["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"], + pys=select_pys(max_version="3.13"), pkgs={ "flask": "~=3.0", }, ), Venv( - # werkzeug 3.1 drops support for py3.8 - pys=["3.11", "3.12", "3.13"], + pys=select_pys(min_version="3.11", max_version="3.13"), pkgs={ "flask": "~=3.1", "Werkzeug": "~=3.1", @@ -3657,28 +3525,28 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={ "django": "~=2.2", }, venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.9", "3.10"], + pys=["3.9", "3.10"], pkgs={ "django": "~=3.2", }, venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.10"], + pys="3.10", pkgs={ "django": "==4.0.10", }, venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.11", "3.13"], + pys=["3.11", "3.13"], pkgs={ "django": "~=4.2", }, @@ -3711,7 +3579,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={ "flask": "~=1.1", "MarkupSafe": "~=1.1", @@ -3719,7 +3587,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.9"], + pys="3.9", pkgs={ "flask": "==2.1.3", "Werkzeug": "<3.0", @@ -3727,14 +3595,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.10", "3.13"], + pys=["3.10", "3.13"], pkgs={ "flask": "~=2.3", }, venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.11", "3.13"], + pys=["3.11", "3.13"], pkgs={ "flask": "~=3.0", }, @@ -3762,7 +3630,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT }, venvs=[ Venv( - pys=["3.8", "3.10", "3.13"], + pys=["3.10", "3.13"], pkgs={ "fastapi": "==0.86.0", "anyio": "==3.7.1", @@ -3770,14 +3638,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.10", "3.13"], + pys=["3.10", "3.13"], pkgs={ "fastapi": "==0.94.1", }, venvs=_appsec_threats_iast_variants, ), Venv( - pys=["3.8", "3.10", "3.13"], + pys=["3.10", "3.13"], pkgs={ "fastapi": "~=0.114.2", }, diff --git a/supported_versions_output.json b/supported_versions_output.json index 54d2847881a..40edcc3c271 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -45,7 +45,7 @@ { "dependency": "algoliasearch", "integration": "algoliasearch", - "minimum_tracer_supported": "2.5.0", + "minimum_tracer_supported": "2.6.3", "max_tracer_supported": "2.6.3", "pinned": "true", "auto-instrumented": true @@ -75,7 +75,7 @@ { "dependency": "asyncpg", "integration": "asyncpg", - "minimum_tracer_supported": "0.22.0", + "minimum_tracer_supported": "0.23.0", "max_tracer_supported": "0.30.0", "auto-instrumented": true }, @@ -312,7 +312,7 @@ { "dependency": "gevent", "integration": "gevent", - "minimum_tracer_supported": "20.12.1", + "minimum_tracer_supported": "21.1.2", "max_tracer_supported": "25.5.1", "auto-instrumented": true }, @@ -489,7 +489,7 @@ { "dependency": "protobuf", "integration": "protobuf", - "minimum_tracer_supported": "5.29.3", + "minimum_tracer_supported": "6.30.1", "max_tracer_supported": "6.32.0", "auto-instrumented": false }, @@ -503,7 +503,7 @@ { "dependency": "psycopg2-binary", "integration": "psycopg", - "minimum_tracer_supported": "2.8.6", + "minimum_tracer_supported": "2.9.10", "max_tracer_supported": "2.9.10", "auto-instrumented": true }, @@ -546,7 +546,7 @@ { "dependency": "pynamodb", "integration": "pynamodb", - "minimum_tracer_supported": "5.0.3", + "minimum_tracer_supported": "5.5.1", "max_tracer_supported": "5.5.1", "pinned": "true", "auto-instrumented": true @@ -605,7 +605,7 @@ { "dependency": "requests", "integration": "requests", - "minimum_tracer_supported": "2.20.1", + "minimum_tracer_supported": "2.25.1", "max_tracer_supported": "2.32.5", "auto-instrumented": true }, @@ -626,7 +626,7 @@ { "dependency": "snowflake-connector-python", "integration": "snowflake", - "minimum_tracer_supported": "2.3.10", + "minimum_tracer_supported": "2.4.6", "max_tracer_supported": "3.17.2", "auto-instrumented": false }, @@ -669,7 +669,7 @@ { "dependency": "urllib3", "integration": "urllib3", - "minimum_tracer_supported": "1.25", + "minimum_tracer_supported": "1.25.8", "max_tracer_supported": "2.5.0", "auto-instrumented": false }, diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 1da57e9f537..6257000e967 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -5,11 +5,11 @@ aiohttp-jinja2,aiohttp_jinja2,1.5.1,1.6,True aiohttp_jinja2,aiohttp_jinja2,1.5.1,1.6,True aiomysql,aiomysql,0.1.1,0.2.0,True aiopg,aiopg *,0.16.0,1.4.0,True -algoliasearch,algoliasearch *,2.5.0,2.6.3,True +algoliasearch,algoliasearch *,2.6.3,2.6.3,True anthropic,anthropic,0.28.1,0.69.0,True aredis,aredis,1.1.8,1.1.8,True pytest-asyncio,asyncio *,0.21.1,1.2.0,True -asyncpg,asyncpg,0.22.0,0.30.0,True +asyncpg,asyncpg,0.23.0,0.30.0,True avro,avro,1.12.0,1.12.0,True datadog-lambda,aws_lambda,6.105.0,6.105.0,True datadog_lambda,aws_lambda,6.105.0,6.105.0,True @@ -42,7 +42,7 @@ flask,flask,1.1.4,3.1.2,True flask-cache,flask_cache,0.13.1,0.13.1,False flask-caching,flask_cache,1.10.1,2.3.0,False freezegun,freezegun *,1.3.1,1.5.2,False -gevent,gevent,20.12.1,25.5.1,True +gevent,gevent,21.1.2,25.5.1,True google-adk,google_adk,1.0.0,1.15.1,True google-genai,google_genai,1.21.1,1.41.0,True google-generativeai,google_generativeai,0.7.2,0.8.5,True @@ -67,15 +67,15 @@ mysql-connector-python,mysql,8.0.5,9.4.0,True mysqlclient,mysqldb,2.2.1,2.2.6,True openai,openai *,1.0.0,1.109.1,True openai-agents,openai_agents,0.0.8,0.0.16,True -protobuf,protobuf,5.29.3,6.32.0,False +protobuf,protobuf,6.30.1,6.32.0,False psycopg,psycopg,3.0.18,3.2.10,True -psycopg2-binary,psycopg,2.8.6,2.9.10,True +psycopg2-binary,psycopg,2.9.10,2.9.10,True pydantic-ai-slim,pydantic_ai *,0.3.0,0.4.4,True pylibmc,pylibmc,1.6.3,1.6.3,True pymemcache,pymemcache,3.4.4,4.0.0,True pymongo,pymongo,3.8.0,4.15.0,True pymysql,pymysql,0.10.1,1.1.2,True -pynamodb,pynamodb *,5.0.3,5.5.1,True +pynamodb,pynamodb *,5.5.1,5.5.1,True pyodbc,pyodbc,4.0.39,5.2.0,True pyramid,pyramid,1.10.8,2.0.2,True pytest,pytest,6.2.5,8.4.2,False @@ -83,16 +83,16 @@ pytest-bdd,pytest_bdd *,4.1.0,6.0.1,False ray,ray *,2.46.0,2.49.2,False redis,redis,4.6.0,6.4.0,True redis-py-cluster,rediscluster,2.0.0,2.1.3,True -requests,requests,2.20.1,2.32.5,True +requests,requests,2.25.1,2.32.5,True rq,rq,1.8.1,1.16.2,True sanic,sanic,20.12.7,24.6.0,True -snowflake-connector-python,snowflake,2.3.10,3.17.2,False +snowflake-connector-python,snowflake,2.4.6,3.17.2,False sqlalchemy,sqlalchemy,1.3.24,2.0.43,False pysqlite3-binary,sqlite3,0.5.2.post3,0.5.2.post3,True starlette,starlette,0.14.2,0.48.0,True structlog,structlog,20.2.0,25.4.0,True tornado,tornado *,6.0.4,6.5.1,False -urllib3,urllib3,1.25,2.5.0,False +urllib3,urllib3,1.25.8,2.5.0,False valkey,valkey,6.0.2,6.1.1,True google-cloud-aiplatform,vertexai,1.71.1,1.71.1,True vertexai,vertexai,1.71.1,1.71.1,True diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml index 12df4a54ed0..b215b5edb54 100644 --- a/tests/appsec/suitespec.yml +++ b/tests/appsec/suitespec.yml @@ -27,7 +27,7 @@ suites: runner: riot snapshot: true appsec_iast_default: - parallelism: 6 + parallelism: 4 paths: - '@bootstrap' - '@core' @@ -139,7 +139,7 @@ suites: retry: 2 runner: riot appsec_integrations_flask: - parallelism: 17 + parallelism: 13 paths: - '@bootstrap' - '@core' @@ -154,7 +154,7 @@ suites: - testagent timeout: 40m appsec_integrations_django: - parallelism: 22 + parallelism: 16 paths: - '@bootstrap' - '@core' @@ -169,7 +169,7 @@ suites: - testagent timeout: 30m appsec_integrations_fastapi: - parallelism: 23 + parallelism: 17 paths: - '@bootstrap' - '@core' @@ -183,7 +183,7 @@ suites: services: - testagent appsec_threats_django: - parallelism: 12 + parallelism: 8 paths: - '@bootstrap' - '@core' @@ -199,7 +199,7 @@ suites: retry: 2 runner: riot appsec_threats_fastapi: - parallelism: 9 + parallelism: 6 paths: - '@bootstrap' - '@core' @@ -216,7 +216,7 @@ suites: retry: 2 runner: riot appsec_threats_flask: - parallelism: 10 + parallelism: 4 paths: - '@bootstrap' - '@core' @@ -270,4 +270,4 @@ suites: retry: 2 runner: riot services: - - testagent \ No newline at end of file + - testagent diff --git a/tests/ci_visibility/suitespec.yml b/tests/ci_visibility/suitespec.yml index 99b565bcd10..8a85129e5dd 100644 --- a/tests/ci_visibility/suitespec.yml +++ b/tests/ci_visibility/suitespec.yml @@ -35,7 +35,7 @@ suites: runner: riot snapshot: true dd_coverage: - parallelism: 5 + parallelism: 3 paths: - '@bootstrap' - '@core' diff --git a/tests/commands/test_runner.py b/tests/commands/test_runner.py index a9f0ab2d429..b795223328e 100644 --- a/tests/commands/test_runner.py +++ b/tests/commands/test_runner.py @@ -6,6 +6,7 @@ import pytest import ddtrace +from ddtrace.internal.compat import PYTHON_VERSION_INFO from ..utils import BaseTestCase from ..utils import override_env @@ -515,6 +516,7 @@ def test_ddtrace_run_and_auto_sitecustomize(): assert final_modules - starting_modules == set(["ddtrace.auto"]) +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess(env=dict(DD_TRACE_GLOBAL_TAGS="a:True"), err=None) def test_global_trace_tags_deprecation_warning(): """Ensure DD_TRACE_GLOBAL_TAGS deprecation warning shows""" diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 94a8b95a77b..fa1b90ab9df 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -817,7 +817,7 @@ def test_logging_during_tracer_init_succeeds_when_debug_logging_and_logs_injecti ), "stderr should not contain any exception logs" -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Python 3.8 throws a deprecation warning") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") def test_no_warnings_when_Wall(): env = os.environ.copy() # Have to disable sqlite3 as coverage uses it on process shutdown diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index 8ae177387bc..27de6444ef0 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -429,7 +429,7 @@ def ns_hook(module): ModuleWatchdog.uninstall() -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Python 3.8 throws a deprecation warning") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess( ddtrace_run=True, env=dict( diff --git a/tests/lib_injection/conftest.py b/tests/lib_injection/conftest.py index 4a19953e736..0042f84aaf8 100644 --- a/tests/lib_injection/conftest.py +++ b/tests/lib_injection/conftest.py @@ -13,9 +13,10 @@ import pytest -from ddtrace._version import __version__ as host_ddtrace_version +from ddtrace.version import get_version +HOST_DDTRACE_VERSION = get_version() LIBS_INJECTION_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../lib-injection")) LIBS_INJECTION_SRC_DIR = os.path.join(LIBS_INJECTION_DIR, "sources") PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) @@ -100,7 +101,7 @@ def ddtrace_injection_artifact(): # 5. Write the ddtrace version file version_file_path = os.path.join(sources_dir_in_session_tmp, "version") with open(version_file_path, "w") as f: - f.write(host_ddtrace_version) + f.write(HOST_DDTRACE_VERSION) yield sources_dir_in_session_tmp diff --git a/tests/profiling/suitespec.yml b/tests/profiling/suitespec.yml index 8c49696c48a..2cd2f4d051f 100644 --- a/tests/profiling/suitespec.yml +++ b/tests/profiling/suitespec.yml @@ -84,7 +84,7 @@ suites: env: DD_TRACE_AGENT_URL: '' # `riot list --hash-only profile$ | wc -1` = 19 - parallelism: 19 + parallelism: 16 paths: - '@bootstrap' - '@core' @@ -97,7 +97,7 @@ suites: env: DD_TRACE_AGENT_URL: '' # `riot list --hash-only profile-v2$ | wc -1` = 19 - parallelism: 19 + parallelism: 16 paths: - '@bootstrap' - '@core' diff --git a/tests/profiling/test_profiler.py b/tests/profiling/test_profiler.py index 7497d90cb82..81016d53a13 100644 --- a/tests/profiling/test_profiler.py +++ b/tests/profiling/test_profiler.py @@ -148,7 +148,7 @@ def test_profiler_serverless(monkeypatch): assert p.tags["functionname"] == "foobar" -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Python 3.8 throws a deprecation warning") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_profiler_ddtrace_deprecation(): """ diff --git a/tests/profiling_v2/test_profiler.py b/tests/profiling_v2/test_profiler.py index 85dd02c83c7..29743be6ef0 100644 --- a/tests/profiling_v2/test_profiler.py +++ b/tests/profiling_v2/test_profiler.py @@ -149,7 +149,7 @@ def test_profiler_serverless(monkeypatch): assert p.tags["functionname"] == "foobar" -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Python 3.8 throws a deprecation warning") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_profiler_ddtrace_deprecation(): """ diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 55870c56f04..b1bfff908f3 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -77,6 +77,7 @@ components: - ddtrace/__init__.py - ddtrace/py.typed - ddtrace/version.py + - ddtrace/_version.py - ddtrace/settings/_config.py - src/native/* datastreams: diff --git a/tests/tracer/test_settings.py b/tests/tracer/test_settings.py index 1a241f46fd7..3f8d6eff2fc 100644 --- a/tests/tracer/test_settings.py +++ b/tests/tracer/test_settings.py @@ -246,7 +246,7 @@ def test_x_datadog_tags(env, expected): assert expected == (_._x_datadog_tags_max_length, _._x_datadog_tags_enabled) -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Additional deprecation warning under Python 3.8") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_config_exception_deprecation(): import warnings @@ -264,7 +264,7 @@ def test_config_exception_deprecation(): assert "4.0.0" in str(warn.message) # TODO: update the version -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Additional deprecation warning under Python 3.8") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_http_config_deprecation(): import warnings @@ -281,7 +281,7 @@ def test_http_config_deprecation(): assert "4.0.0" in str(warn.message) # TODO: update the version -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Additional deprecation warning under Python 3.8") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_hooks_deprecation(): import warnings @@ -298,7 +298,7 @@ def test_hooks_deprecation(): assert "4.0.0" in str(warn.message) # TODO: update the version -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 9), reason="Additional deprecation warning under Python 3.8") +@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") @pytest.mark.subprocess() def test_integration_config_deprecation(): import warnings From 26b1956aa767e70e21010b34d0592f942cbc73e1 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:46:19 -0400 Subject: [PATCH 07/42] chore: remove freezegun integration (#14893) This change removes the deprecated `freezegun` integration from ddtrace. Note the base branch, a staging area for breaking changes slated for 4.0. --------- Co-authored-by: Vlad Scherbich Co-authored-by: Taegyun Kim --- .riot/requirements/14676df.txt | 26 ----- .riot/requirements/15de642.txt | 23 ----- .riot/requirements/1d1dbc1.txt | 26 ----- .riot/requirements/2bcce4e.txt | 23 ----- ddtrace/_monkey.py | 1 - .../integration_registry/registry.yaml | 10 -- .../contrib/internal/freezegun/__init__.py | 14 --- ddtrace/contrib/internal/freezegun/patch.py | 37 ------- ddtrace/settings/_config.py | 1 - docs/index.rst | 2 - docs/integrations.rst | 7 -- .../freezegun-remove-44312810d30f9e0b.yaml | 4 + riotfile.py | 15 --- supported_versions_output.json | 8 -- supported_versions_table.csv | 1 - tests/ci_visibility/suitespec.yml | 7 -- tests/contrib/freezegun/test_freezegun.py | 99 ------------------- tests/contrib/suitespec.yml | 12 --- 18 files changed, 4 insertions(+), 312 deletions(-) delete mode 100644 .riot/requirements/14676df.txt delete mode 100644 .riot/requirements/15de642.txt delete mode 100644 .riot/requirements/1d1dbc1.txt delete mode 100644 .riot/requirements/2bcce4e.txt delete mode 100644 ddtrace/contrib/internal/freezegun/__init__.py delete mode 100644 ddtrace/contrib/internal/freezegun/patch.py create mode 100644 releasenotes/notes/freezegun-remove-44312810d30f9e0b.yaml delete mode 100644 tests/contrib/freezegun/test_freezegun.py diff --git a/.riot/requirements/14676df.txt b/.riot/requirements/14676df.txt deleted file mode 100644 index 055678228c9..00000000000 --- a/.riot/requirements/14676df.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/14676df.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -exceptiongroup==1.3.0 -freezegun==1.5.2 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.14.0 diff --git a/.riot/requirements/15de642.txt b/.riot/requirements/15de642.txt deleted file mode 100644 index 9e138c07de8..00000000000 --- a/.riot/requirements/15de642.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/15de642.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -freezegun==1.5.2 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/1d1dbc1.txt b/.riot/requirements/1d1dbc1.txt deleted file mode 100644 index 179f45bf156..00000000000 --- a/.riot/requirements/1d1dbc1.txt +++ /dev/null @@ -1,26 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/1d1dbc1.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -exceptiongroup==1.3.0 -freezegun==1.3.1 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.14.0 diff --git a/.riot/requirements/2bcce4e.txt b/.riot/requirements/2bcce4e.txt deleted file mode 100644 index c444938efa6..00000000000 --- a/.riot/requirements/2bcce4e.txt +++ /dev/null @@ -1,23 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/2bcce4e.in -# -attrs==25.3.0 -coverage[toml]==7.8.2 -freezegun==1.3.1 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -python-dateutil==2.9.0.post0 -six==1.17.0 -sortedcontainers==2.4.0 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 170c1870871..9e7dca4a29e 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -46,7 +46,6 @@ "elasticsearch": True, "algoliasearch": True, "futures": True, - "freezegun": False, # deprecated, to be removed in ddtrace 4.x "google_adk": True, "google_generativeai": True, "google_genai": True, diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index 4e2a9171171..0240ac13050 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -369,16 +369,6 @@ integrations: min: 1.10.1 max: 2.3.0 -- integration_name: freezegun - is_external_package: true - is_tested: true - dependency_names: - - freezegun - tested_versions_by_dependency: - freezegun: - min: 1.3.1 - max: 1.5.2 - - integration_name: futures is_external_package: false is_tested: true diff --git a/ddtrace/contrib/internal/freezegun/__init__.py b/ddtrace/contrib/internal/freezegun/__init__.py deleted file mode 100644 index 89086940c89..00000000000 --- a/ddtrace/contrib/internal/freezegun/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -The freezegun integration updates freezegun's default ignore list to ignore ddtrace. - -Enabling -~~~~~~~~ -The freezegun integration is enabled by default. Use :func:`patch()` to enable the integration:: - from ddtrace import patch - patch(freezegun=True) - - -Configuration -~~~~~~~~~~~~~ -The freezegun integration is not configurable, but may be disabled using DD_PATCH_MODULES=freezegun:false . -""" diff --git a/ddtrace/contrib/internal/freezegun/patch.py b/ddtrace/contrib/internal/freezegun/patch.py deleted file mode 100644 index 676952eda45..00000000000 --- a/ddtrace/contrib/internal/freezegun/patch.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Dict - -from ddtrace import DDTraceDeprecationWarning -from ddtrace.internal.logger import get_logger -from ddtrace.vendor.debtcollector import deprecate - - -log = get_logger(__name__) - -DDTRACE_MODULE_NAME = "ddtrace" - - -def get_version() -> str: - import freezegun - - try: - return freezegun.__version__ - except AttributeError: - log.debug("Could not get freezegun version") - return "" - - -def _supported_versions() -> Dict[str, str]: - return {"freezegun": "*"} - - -def patch() -> None: - deprecate( - "the freezegun integration is deprecated", - message="this integration is not needed anymore for the correct reporting of span durations.", - removal_version="4.0.0", - category=DDTraceDeprecationWarning, - ) - - -def unpatch() -> None: - pass diff --git a/ddtrace/settings/_config.py b/ddtrace/settings/_config.py index 41175d7a56f..aabd1a1a4cb 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/settings/_config.py @@ -126,7 +126,6 @@ "protobuf", "aiohttp_jinja2", "pymongo", - "freezegun", "vertica", "rq_worker", "elasticsearch", diff --git a/docs/index.rst b/docs/index.rst index 47c315eff5d..93aa02503ad 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -104,8 +104,6 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`flask_cache` | >= 0.13 | No | | +--------------------------------------------------+------------+----------+------+ -| :ref:`freezegun` | \* | Yes | | -+--------------------------------------------------+------------+----------+------+ | :ref:`futures` | \* | Yes | | +--------------------------------------------------+------------+----------+------+ | :ref:`gevent` (greenlet>=1.0) | >= 21.1.2 | Yes | | diff --git a/docs/integrations.rst b/docs/integrations.rst index b01829193c3..18223f31009 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -209,13 +209,6 @@ Flask Cache .. automodule:: ddtrace.contrib.flask_cache -.. _freezegun: - -FreezeGun -^^^^^^^^^ -.. automodule:: ddtrace.contrib.internal.freezegun - - .. _futures: futures diff --git a/releasenotes/notes/freezegun-remove-44312810d30f9e0b.yaml b/releasenotes/notes/freezegun-remove-44312810d30f9e0b.yaml new file mode 100644 index 00000000000..75620f165d7 --- /dev/null +++ b/releasenotes/notes/freezegun-remove-44312810d30f9e0b.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + freezegun: The deprecated `freezegun` integration is now removed. diff --git a/riotfile.py b/riotfile.py index 95a747ed460..f2d6f33a329 100644 --- a/riotfile.py +++ b/riotfile.py @@ -3399,21 +3399,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), - Venv( - name="freezegun", - command="pytest tests/contrib/freezegun {cmdargs}", - pkgs={ - "pytest-randomly": latest, - }, - venvs=[ - Venv( - pys=["3.10", "3.12"], - pkgs={ - "freezegun": ["~=1.3.0", "~=1.5.0"], - }, - ), - ], - ), Venv( name="appsec_integrations_flask", command="pytest -vvv {cmdargs} tests/appsec/integrations/flask_tests/", diff --git a/supported_versions_output.json b/supported_versions_output.json index 40edcc3c271..f9e361f828c 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -301,14 +301,6 @@ "max_tracer_supported": "2.3.0", "auto-instrumented": false }, - { - "dependency": "freezegun", - "integration": "freezegun", - "minimum_tracer_supported": "1.3.1", - "max_tracer_supported": "1.5.2", - "pinned": "true", - "auto-instrumented": false - }, { "dependency": "gevent", "integration": "gevent", diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 6257000e967..446583b9b0c 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -41,7 +41,6 @@ fastapi,fastapi,0.64.0,0.118.0,True flask,flask,1.1.4,3.1.2,True flask-cache,flask_cache,0.13.1,0.13.1,False flask-caching,flask_cache,1.10.1,2.3.0,False -freezegun,freezegun *,1.3.1,1.5.2,False gevent,gevent,21.1.2,25.5.1,True google-adk,google_adk,1.0.0,1.15.1,True google-genai,google_genai,1.21.1,1.41.0,True diff --git a/tests/ci_visibility/suitespec.yml b/tests/ci_visibility/suitespec.yml index 8a85129e5dd..6046b518c31 100644 --- a/tests/ci_visibility/suitespec.yml +++ b/tests/ci_visibility/suitespec.yml @@ -14,8 +14,6 @@ components: - ddtrace/contrib/internal/selenium/* unittest: - ddtrace/contrib/internal/unittest/* - freezegun: - - ddtrace/contrib/internal/freezegun/* suites: ci_visibility: parallelism: 4 @@ -28,8 +26,6 @@ suites: - '@pytest' - '@codeowners' - '@unittest' - - '@freezegun' - - '@tracing' - tests/ci_visibility/* - tests/snapshots/test_api_fake_runners.* runner: riot @@ -55,7 +51,6 @@ suites: - '@ci_visibility' - '@coverage' - '@codeowners' - - '@freezegun' - tests/contrib/pytest/* - tests/contrib/pytest_benchmark/* - tests/contrib/pytest_bdd/* @@ -85,8 +80,6 @@ suites: - '@unittest' - '@ci_visibility' - '@coverage' - - '@freezegun' - - '@tracing' - tests/contrib/unittest/* - tests/snapshots/tests.contrib.unittest.* runner: riot diff --git a/tests/contrib/freezegun/test_freezegun.py b/tests/contrib/freezegun/test_freezegun.py deleted file mode 100644 index aeb08c6edfb..00000000000 --- a/tests/contrib/freezegun/test_freezegun.py +++ /dev/null @@ -1,99 +0,0 @@ -import datetime -import os -import time - -import pytest - -from ddtrace.internal.utils.time import StopWatch -from ddtrace.trace import tracer as dd_tracer -from tests.contrib.pytest.test_pytest import PytestTestCaseBase - - -class TestFreezegunTestCase: - @pytest.fixture(autouse=True) - def _patch_freezegun(self): - from ddtrace.contrib.internal.freezegun.patch import patch - from ddtrace.contrib.internal.freezegun.patch import unpatch - - patch() - yield - unpatch() - - def test_freezegun_does_not_freeze_tracing(self): - import freezegun - - with freezegun.freeze_time("2020-01-01"): - with dd_tracer.trace("freezegun.test") as span: - time.sleep(1) - - assert span.duration >= 1 - - def test_freezegun_fast_forward_does_not_affect_tracing(self): - import freezegun - - with freezegun.freeze_time("2020-01-01") as frozen_time: - with dd_tracer.trace("freezegun.test") as span: - time.sleep(1) - frozen_time.tick(delta=datetime.timedelta(days=10)) - assert 1 <= span.duration <= 5 - - def test_freezegun_does_not_freeze_stopwatch(self): - import freezegun - - with freezegun.freeze_time("2020-01-01"): - with StopWatch() as sw: - time.sleep(1) - assert sw.elapsed() >= 1 - - def test_freezegun_configure_default_ignore_list_continues_to_ignore_ddtrace(self): - import freezegun - from freezegun.config import DEFAULT_IGNORE_LIST - - try: - freezegun.configure(default_ignore_list=[]) - - with freezegun.freeze_time("2020-01-01"): - with dd_tracer.trace("freezegun.test") as span: - time.sleep(1) - - assert span.duration >= 1 - finally: - # Reset the ignore list to its default value after the test - freezegun.configure(default_ignore_list=DEFAULT_IGNORE_LIST) - - -class PytestFreezegunTestCase(PytestTestCaseBase): - def test_freezegun_pytest_plugin(self): - """Tests that pytest's patching of freezegun in the v1 plugin version works""" - import sys - - from ddtrace.contrib.internal.freezegun.patch import unpatch - - unpatch() - if "freezegun" in sys.modules: - del sys.modules["freezegun"] - - py_file = self.testdir.makepyfile( - """ - import datetime - import time - - import freezegun - - from ddtrace.trace import tracer as dd_tracer - - def test_pytest_patched_freezegun(): - with freezegun.freeze_time("2020-01-01"): - with dd_tracer.trace("freezegun.test") as span: - time.sleep(1) - assert span.duration >= 1 - - """ - ) - file_name = os.path.basename(py_file.strpath) - self.inline_run("--ddtrace", "-s", file_name) - spans = self.pop_spans() - - assert len(spans) == 4 - for span in spans: - assert span.get_tag("test.status") == "pass" diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 35e712500fa..649bc840e8e 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -86,8 +86,6 @@ components: - ddtrace/contrib/internal/flask/* - ddtrace/contrib/flask_cache.py - ddtrace/contrib/internal/flask_cache/* - freezegun: - - ddtrace/contrib/internal/freezegun/* futures: - ddtrace/contrib/internal/futures/* gevent: @@ -677,16 +675,6 @@ suites: - memcached - redis snapshot: true - freezegun: - paths: - - '@bootstrap' - - '@core' - - '@contrib' - - '@tracing' - - '@freezegun' - - tests/contrib/freezegun/* - runner: riot - snapshot: true gevent: paths: - '@bootstrap' From 4c999e0c7f4b3dd21a388e41ed81ad6d8c61b133 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:47:24 -0400 Subject: [PATCH 08/42] chore: remove deprecated telemetry interval environment variable (#14897) This change removes a deprecated environment variable related to instrumentation telemetry. Note the base branch, a staging area for breaking changes slated for 4.0. --- ddtrace/internal/runtime/constants.py | 1 + ddtrace/internal/runtime/runtime_metrics.py | 40 +++++-------------- ddtrace/runtime/__init__.py | 18 ++++----- ...move-interval-envvar-88c126a791a448a0.yaml | 4 ++ tests/tracer/runtime/test_runtime_metrics.py | 10 ++--- 5 files changed, 27 insertions(+), 46 deletions(-) create mode 100644 releasenotes/notes/remove-interval-envvar-88c126a791a448a0.yaml diff --git a/ddtrace/internal/runtime/constants.py b/ddtrace/internal/runtime/constants.py index 78b9c5e032f..41b7edb8cd2 100644 --- a/ddtrace/internal/runtime/constants.py +++ b/ddtrace/internal/runtime/constants.py @@ -18,6 +18,7 @@ ) DEFAULT_RUNTIME_METRICS = GC_RUNTIME_METRICS | PSUTIL_RUNTIME_METRICS +DEFAULT_RUNTIME_METRICS_INTERVAL = 10 SERVICE = "service" ENV = "env" diff --git a/ddtrace/internal/runtime/runtime_metrics.py b/ddtrace/internal/runtime/runtime_metrics.py index 124b97ae262..c541b333748 100644 --- a/ddtrace/internal/runtime/runtime_metrics.py +++ b/ddtrace/internal/runtime/runtime_metrics.py @@ -1,5 +1,4 @@ import itertools -import os from typing import ClassVar # noqa:F401 from typing import List # noqa:F401 from typing import Optional # noqa:F401 @@ -8,13 +7,13 @@ from ddtrace.internal import atexit from ddtrace.internal import forksafe from ddtrace.internal.constants import EXPERIMENTAL_FEATURES -from ddtrace.vendor.debtcollector import deprecate from ddtrace.vendor.dogstatsd import DogStatsd from .. import periodic from ..dogstatsd import get_dogstatsd_client from ..logger import get_logger from .constants import DEFAULT_RUNTIME_METRICS +from .constants import DEFAULT_RUNTIME_METRICS_INTERVAL from .metric_collectors import GCRuntimeMetricCollector from .metric_collectors import PSUtilRuntimeMetricCollector from .tag_collectors import PlatformTagCollector @@ -68,25 +67,14 @@ class RuntimeMetrics(RuntimeCollectorsIterable): ] -def _get_interval_or_default(): - if "DD_RUNTIME_METRICS_INTERVAL" in os.environ: - deprecate( - "`DD_RUNTIME_METRICS_INTERVAL` is deprecated and will be removed in a future version.", - removal_version="4.0.0", - ) - return float(os.getenv("DD_RUNTIME_METRICS_INTERVAL", default=10)) - - class RuntimeWorker(periodic.PeriodicService): - """Worker thread for collecting and writing runtime metrics to a DogStatsd - client. - """ + """Worker thread for collecting and writing runtime metrics to a DogStatsd client.""" enabled = False _instance = None # type: ClassVar[Optional[RuntimeWorker]] _lock = forksafe.Lock() - def __init__(self, interval=_get_interval_or_default(), tracer=None, dogstatsd_url=None) -> None: + def __init__(self, interval=DEFAULT_RUNTIME_METRICS_INTERVAL, tracer=None, dogstatsd_url=None) -> None: super().__init__(interval=interval) self.dogstatsd_url: Optional[str] = dogstatsd_url self._dogstatsd_client: DogStatsd = get_dogstatsd_client( @@ -107,8 +95,7 @@ def __init__(self, interval=_get_interval_or_default(), tracer=None, dogstatsd_u self._platform_tags = self._format_tags(PlatformTags()) @classmethod - def disable(cls): - # type: () -> None + def disable(cls) -> None: with cls._lock: if cls._instance is None: return @@ -134,14 +121,15 @@ def _restart(cls): cls.enable() @classmethod - def enable(cls, flush_interval=None, tracer=None, dogstatsd_url=None): - # type: (Optional[float], Optional[ddtrace.trace.Tracer], Optional[str]) -> None + def enable( + cls, + tracer: Optional[ddtrace.trace.Tracer] = None, + dogstatsd_url: Optional[str] = None, + ) -> None: with cls._lock: if cls._instance is not None: return - if flush_interval is None: - flush_interval = _get_interval_or_default() - runtime_worker = cls(flush_interval, tracer, dogstatsd_url) + runtime_worker = cls(DEFAULT_RUNTIME_METRICS_INTERVAL, tracer, dogstatsd_url) runtime_worker.start() forksafe.register(cls._restart) @@ -150,8 +138,7 @@ def enable(cls, flush_interval=None, tracer=None, dogstatsd_url=None): cls._instance = runtime_worker cls.enabled = True - def flush(self): - # type: () -> None + def flush(self) -> None: # Ensure runtime metrics have up-to-date tags (ex: service, env, version) rumtime_tags = self._format_tags(TracerTags()) + self._platform_tags log.debug("Sending runtime metrics with the following tags: %s", rumtime_tags) @@ -162,11 +149,6 @@ def flush(self): log.debug("Sending ddtrace runtime metric %s:%s", key, value) self.send_metric(key, value) - def _stop_service(self): - # type: (...) -> None - # De-register span hook - super(RuntimeWorker, self)._stop_service() - def _format_tags(self, tags: RuntimeCollectorsIterable) -> List[str]: # DEV: ddstatsd expects tags in the form ['key1:value1', 'key2:value2', ...] return ["{}:{}".format(k, v) for k, v in tags] diff --git a/ddtrace/runtime/__init__.py b/ddtrace/runtime/__init__.py index 79745217f11..2963023fc29 100644 --- a/ddtrace/runtime/__init__.py +++ b/ddtrace/runtime/__init__.py @@ -1,5 +1,6 @@ from typing import Optional # noqa:F401 +import ddtrace import ddtrace.internal.runtime.runtime_metrics from ddtrace.internal.telemetry import telemetry_writer @@ -29,27 +30,22 @@ class RuntimeMetrics(metaclass=_RuntimeMetricsStatus): """ @staticmethod - def enable(tracer=None, dogstatsd_url=None, flush_interval=None): - # type: (Optional[ddtrace.trace.Tracer], Optional[str], Optional[float]) -> None + def enable( + tracer: Optional[ddtrace.trace.Tracer] = None, + dogstatsd_url: Optional[str] = None, + ) -> None: """ - Enable the runtime metrics collection service. - If the service has already been activated before, this method does nothing. Use ``disable`` to turn off the runtime metric collection service. :param tracer: The tracer instance to correlate with. - :param dogstatsd_url: The DogStatsD URL. - :param flush_interval: The flush interval. """ telemetry_writer.add_configuration(TELEMETRY_RUNTIMEMETRICS_ENABLED, True, origin="code") - ddtrace.internal.runtime.runtime_metrics.RuntimeWorker.enable( - tracer=tracer, dogstatsd_url=dogstatsd_url, flush_interval=flush_interval - ) + ddtrace.internal.runtime.runtime_metrics.RuntimeWorker.enable(tracer=tracer, dogstatsd_url=dogstatsd_url) @staticmethod - def disable(): - # type: () -> None + def disable() -> None: """ Disable the runtime metrics collection service. diff --git a/releasenotes/notes/remove-interval-envvar-88c126a791a448a0.yaml b/releasenotes/notes/remove-interval-envvar-88c126a791a448a0.yaml new file mode 100644 index 00000000000..2dcd05f9c50 --- /dev/null +++ b/releasenotes/notes/remove-interval-envvar-88c126a791a448a0.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + This change removes the deprecated environment variable `DEFAULT_RUNTIME_METRICS_INTERVAL`. diff --git a/tests/tracer/runtime/test_runtime_metrics.py b/tests/tracer/runtime/test_runtime_metrics.py index 984142380e1..118e85df126 100644 --- a/tests/tracer/runtime/test_runtime_metrics.py +++ b/tests/tracer/runtime/test_runtime_metrics.py @@ -20,8 +20,8 @@ @contextlib.contextmanager -def runtime_metrics_service(tracer=None, flush_interval=None): - RuntimeWorker.enable(tracer=tracer, flush_interval=flush_interval) +def runtime_metrics_service(tracer=None): + RuntimeWorker.enable(tracer=tracer) assert RuntimeWorker._instance is not None assert RuntimeWorker._instance.status == ServiceStatus.RUNNING @@ -151,9 +151,7 @@ def test_tracer_metrics(self): # Mock socket.socket to hijack the dogstatsd socket with mock.patch("socket.socket") as sock: sock.return_value.getsockopt.return_value = 0 - # configure tracer for runtime metrics - interval = 1.0 / 4 - with runtime_metrics_service(tracer=self.tracer, flush_interval=interval): + with runtime_metrics_service(tracer=self.tracer): self.tracer.set_tags({"env": "tests.dog"}) with self.override_global_tracer(self.tracer): @@ -167,7 +165,7 @@ def test_tracer_metrics(self): with self.start_span( "query", service="db", span_type=SpanTypes.SQL, child_of=child.context ): - time.sleep(interval * 4) + time.sleep(4) # Get the mocked socket for inspection later statsd_socket = RuntimeWorker._instance._dogstatsd_client.socket received = [s.args[0].decode("utf-8") for s in statsd_socket.send.mock_calls] From fe3b228d9e2bc78c5ee05e3d49811dbb5c18009b Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 28 Oct 2025 14:48:54 -0400 Subject: [PATCH 09/42] chore(tracing): remove deprecated app analytics functionality (#14899) This change removes the deprecated functionality that controls ingestion via analytics. Note the base branch, a staging area for breaking changes slated for 4.0. --- ddtrace/settings/integration.py | 34 ++------------- ...remove-app-analytics-52ac993f27e2607f.yaml | 4 ++ tests/tracer/test_settings.py | 43 ------------------- 3 files changed, 8 insertions(+), 73 deletions(-) create mode 100644 releasenotes/notes/remove-app-analytics-52ac993f27e2607f.yaml diff --git a/ddtrace/settings/integration.py b/ddtrace/settings/integration.py index e06241bfc47..6bb9b1a16cc 100644 --- a/ddtrace/settings/integration.py +++ b/ddtrace/settings/integration.py @@ -1,9 +1,6 @@ import os from typing import Optional # noqa:F401 -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - from .._hooks import Hooks from ..internal.utils.attrdict import AttrDict from .http import HttpConfig @@ -81,8 +78,7 @@ def trace_query_string(self): return self.global_config._http.trace_query_string @property - def is_header_tracing_configured(self): - # type: (...) -> bool + def is_header_tracing_configured(self) -> bool: """Returns whether header tracing is enabled for this integration. Will return true if traced headers are configured for this integration @@ -90,45 +86,23 @@ def is_header_tracing_configured(self): """ return self.http.is_header_tracing_configured or self.global_config._http.is_header_tracing_configured - def header_is_traced(self, header_name): - # type: (str) -> bool - """ - Returns whether or not the current header should be traced. - :param header_name: the header name - :type header_name: str - :rtype: bool - """ + def header_is_traced(self, header_name: str) -> bool: + """Returns whether or not the current header should be traced.""" return self._header_tag_name(header_name) is not None - def _header_tag_name(self, header_name): - # type: (str) -> Optional[str] + def _header_tag_name(self, header_name: str) -> Optional[str]: tag_name = self.http._header_tag_name(header_name) if tag_name is None: return self.global_config._header_tag_name(header_name) return tag_name def __getattr__(self, key): - if key in self.APP_ANALYTICS_CONFIG_NAMES: - self.app_analytics_deprecated_warning(key) return super().__getattr__(key) def __setattr__(self, key, value): - if key in self.APP_ANALYTICS_CONFIG_NAMES: - self.app_analytics_deprecated_warning(key) return super().__setattr__(key, value) - def app_analytics_deprecated_warning(self, key): - deprecate( - f"{key} is deprecated", - message="Controlling ingestion via analytics is no longer supported. " - "See https://docs.datadoghq.com/tracing/legacy_app_analytics/" - "?code-lang=python#migrate-to-the-new-configuration-options", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - def get_analytics_sample_rate(self, use_global_config=False): - self.app_analytics_deprecated_warning("get_analytics_sample_rate") return 1 def __repr__(self): diff --git a/releasenotes/notes/remove-app-analytics-52ac993f27e2607f.yaml b/releasenotes/notes/remove-app-analytics-52ac993f27e2607f.yaml new file mode 100644 index 00000000000..4cc2631d603 --- /dev/null +++ b/releasenotes/notes/remove-app-analytics-52ac993f27e2607f.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + tracing: This change removes the deprecated functionality that controls ingestion via analytics. diff --git a/tests/tracer/test_settings.py b/tests/tracer/test_settings.py index 3f8d6eff2fc..4bf8167764a 100644 --- a/tests/tracer/test_settings.py +++ b/tests/tracer/test_settings.py @@ -1,5 +1,3 @@ -import warnings - import pytest from ddtrace.internal.compat import PYTHON_VERSION_INFO @@ -178,47 +176,6 @@ def test_app_analytics_property(self): assert self.integration_config.get_analytics_sample_rate() == 1 - def test_app_analytics_deprecation(self): - warnings.simplefilter("always") - with warnings.catch_warnings(record=True) as warns: - IntegrationConfig(self.config, "test") - assert len(warns) == 0 - - with warnings.catch_warnings(record=True) as warns: - self.integration_config.analytics_enabled - assert ( - "analytics_enabled is deprecated and will be removed in version '4.0.0': Controlling ingestion via analytics is no longer supported. See https://docs.datadoghq.com/tracing/legacy_app_analytics/?code-lang=python#migrate-to-the-new-configuration-options" # noqa:E501 - in str(warns[0].message) - ) - - with warnings.catch_warnings(record=True) as warns: - self.integration_config.analytics_enabled = True - assert ( - "analytics_enabled is deprecated and will be removed in version '4.0.0': Controlling ingestion via analytics is no longer supported. See https://docs.datadoghq.com/tracing/legacy_app_analytics/?code-lang=python#migrate-to-the-new-configuration-options" # noqa:E501 - in str(warns[0].message) - ) - - with warnings.catch_warnings(record=True) as warns: - self.integration_config.analytics_sample_rate - assert ( - "analytics_sample_rate is deprecated and will be removed in version '4.0.0': Controlling ingestion via analytics is no longer supported. See https://docs.datadoghq.com/tracing/legacy_app_analytics/?code-lang=python#migrate-to-the-new-configuration-options" # noqa:E501 - in str(warns[0].message) - ) - - with warnings.catch_warnings(record=True) as warns: - self.integration_config.analytics_sample_rate = 0.5 - assert ( - "analytics_sample_rate is deprecated and will be removed in version '4.0.0': Controlling ingestion via analytics is no longer supported. See https://docs.datadoghq.com/tracing/legacy_app_analytics/?code-lang=python#migrate-to-the-new-configuration-options" # noqa:E501 - in str(warns[0].message) - ) - - with warnings.catch_warnings(record=True) as warns: - self.integration_config.get_analytics_sample_rate() - assert ( - "get_analytics_sample_rate is deprecated and will be removed in version '4.0.0': Controlling ingestion via analytics is no longer supported. See https://docs.datadoghq.com/tracing/legacy_app_analytics/?code-lang=python#migrate-to-the-new-configuration-options" # noqa:E501 - in str(warns[0].message) - ) - def test_environment_header_tags(): with override_env(dict(DD_TRACE_HEADER_TAGS="Host:http.host,User-agent:http.user_agent")): From 91b50f9c48b753aed5a7096ba1ce660861db7540 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Thu, 30 Oct 2025 09:16:11 -0400 Subject: [PATCH 10/42] lockfiles --- .riot/requirements/1f77a44.txt | 25 ------------------------- .riot/requirements/1fd3342.txt | 25 ------------------------- .riot/requirements/21bc53e.txt | 25 ------------------------- .riot/requirements/59a4721.txt | 25 ------------------------- 4 files changed, 100 deletions(-) delete mode 100644 .riot/requirements/1f77a44.txt delete mode 100644 .riot/requirements/1fd3342.txt delete mode 100644 .riot/requirements/21bc53e.txt delete mode 100644 .riot/requirements/59a4721.txt diff --git a/.riot/requirements/1f77a44.txt b/.riot/requirements/1f77a44.txt deleted file mode 100644 index 6068e633bbc..00000000000 --- a/.riot/requirements/1f77a44.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f77a44.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -openfeature-sdk==0.7.5 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/1fd3342.txt b/.riot/requirements/1fd3342.txt deleted file mode 100644 index c703d4437cf..00000000000 --- a/.riot/requirements/1fd3342.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1fd3342.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -openfeature-sdk==0.5.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/21bc53e.txt b/.riot/requirements/21bc53e.txt deleted file mode 100644 index d7a646e282d..00000000000 --- a/.riot/requirements/21bc53e.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/21bc53e.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -openfeature-sdk==0.6.1 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -zipp==3.20.2 diff --git a/.riot/requirements/59a4721.txt b/.riot/requirements/59a4721.txt deleted file mode 100644 index f41c79474d7..00000000000 --- a/.riot/requirements/59a4721.txt +++ /dev/null @@ -1,25 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/59a4721.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -openfeature-sdk==0.7.5 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -zipp==3.20.2 From e28c527879e1dceb75c6492c1039106d56915492 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:03:04 -0400 Subject: [PATCH 11/42] chore: internalize ddtrace.settings (#14895) This change replaces `ddtrace.settings` with `ddtrace.internal.settings` because we've decided to disallow settings adjustment via code, preferring environment variable configuration. Note the base branch, a staging area for breaking changes slated for 4.0. --- ddtrace/__init__.py | 2 +- ddtrace/_logger.py | 30 +-- ddtrace/_monkey.py | 2 +- ddtrace/_trace/pin.py | 2 +- ddtrace/_trace/processor/__init__.py | 4 +- ddtrace/_trace/processor/resource_renaming.py | 2 +- ddtrace/_trace/product.py | 20 +- ddtrace/_trace/sampler.py | 2 +- ddtrace/_trace/span.py | 2 +- ddtrace/_trace/tracer.py | 6 +- ddtrace/appsec/_ai_guard/__init__.py | 2 +- ddtrace/appsec/_api_security/api_manager.py | 2 +- ddtrace/appsec/_asm_request_context.py | 2 +- ddtrace/appsec/_capabilities.py | 4 +- ddtrace/appsec/_common_module_patches.py | 2 +- ddtrace/appsec/_ddwaf/__init__.py | 2 +- ddtrace/appsec/_ddwaf/ddwaf_types.py | 2 +- ddtrace/appsec/_deduplications.py | 2 +- .../_exploit_prevention/stack_traces.py | 2 +- ddtrace/appsec/_handlers.py | 2 +- ddtrace/appsec/_iast/__init__.py | 2 +- ddtrace/appsec/_iast/_ast/ast_patching.py | 2 +- .../_evidence_redaction/_sensitive_handler.py | 2 +- ddtrace/appsec/_iast/_handlers.py | 2 +- ddtrace/appsec/_iast/_iast_request_context.py | 2 +- .../_iast/_iast_request_context_base.py | 2 +- ddtrace/appsec/_iast/_langchain.py | 2 +- ddtrace/appsec/_iast/_loader.py | 2 +- ddtrace/appsec/_iast/_logs.py | 2 +- ddtrace/appsec/_iast/_metrics.py | 2 +- .../appsec/_iast/_overhead_control_engine.py | 2 +- ddtrace/appsec/_iast/_patch_modules.py | 2 +- .../appsec/_iast/_patches/json_tainting.py | 2 +- ddtrace/appsec/_iast/_pytest_plugin.py | 2 +- .../appsec/_iast/_taint_tracking/__init__.py | 6 +- ddtrace/appsec/_iast/_taint_utils.py | 2 +- ddtrace/appsec/_iast/_utils.py | 2 +- ddtrace/appsec/_iast/main.py | 2 +- .../_iast/sampling/vulnerability_detection.py | 2 +- .../_iast/secure_marks/configuration.py | 2 +- ddtrace/appsec/_iast/taint_sinks/_base.py | 2 +- .../_iast/taint_sinks/code_injection.py | 2 +- .../_iast/taint_sinks/header_injection.py | 2 +- .../_iast/taint_sinks/insecure_cookie.py | 2 +- .../appsec/_iast/taint_sinks/sql_injection.py | 2 +- .../taint_sinks/untrusted_serialization.py | 2 +- .../_iast/taint_sinks/unvalidated_redirect.py | 2 +- .../appsec/_iast/taint_sinks/weak_cipher.py | 2 +- ddtrace/appsec/_iast/taint_sinks/weak_hash.py | 2 +- ddtrace/appsec/_iast/taint_sinks/xss.py | 2 +- ddtrace/appsec/_listeners.py | 4 +- ddtrace/appsec/_processor.py | 2 +- ddtrace/appsec/_remoteconfiguration.py | 2 +- ddtrace/appsec/_trace_utils.py | 2 +- ddtrace/appsec/_utils.py | 2 +- ddtrace/appsec/ai_guard/_api_client.py | 2 +- ddtrace/bootstrap/preload.py | 4 +- ddtrace/contrib/internal/asgi/middleware.py | 2 +- ddtrace/contrib/internal/aws_lambda/patch.py | 2 +- .../contrib/internal/azure_eventhubs/patch.py | 2 +- .../contrib/internal/azure_functions/patch.py | 2 +- .../internal/azure_servicebus/patch.py | 2 +- ddtrace/contrib/internal/botocore/patch.py | 2 +- ddtrace/contrib/internal/coverage/utils.py | 2 +- ddtrace/contrib/internal/django/cache.py | 2 +- ddtrace/contrib/internal/django/database.py | 2 +- ddtrace/contrib/internal/django/middleware.py | 4 +- ddtrace/contrib/internal/django/patch.py | 4 +- ddtrace/contrib/internal/django/response.py | 2 +- ddtrace/contrib/internal/django/templates.py | 2 +- ddtrace/contrib/internal/django/user.py | 2 +- ddtrace/contrib/internal/dramatiq/patch.py | 2 +- ddtrace/contrib/internal/fastapi/patch.py | 2 +- ddtrace/contrib/internal/httplib/patch.py | 2 +- ddtrace/contrib/internal/mysql/patch.py | 2 +- ddtrace/contrib/internal/mysqldb/patch.py | 2 +- ddtrace/contrib/internal/pyramid/patch.py | 2 +- ddtrace/contrib/internal/pytest/_plugin_v2.py | 2 +- ddtrace/contrib/internal/pytest/_utils.py | 2 +- ddtrace/contrib/internal/pytest/plugin.py | 4 +- .../contrib/internal/requests/connection.py | 2 +- ddtrace/contrib/internal/requests/patch.py | 2 +- ddtrace/contrib/internal/rq/patch.py | 9 +- ddtrace/contrib/internal/sqlalchemy/patch.py | 2 +- ddtrace/contrib/internal/sqlite3/patch.py | 2 +- ddtrace/contrib/internal/starlette/patch.py | 2 +- ddtrace/contrib/internal/subprocess/patch.py | 4 +- ddtrace/contrib/internal/trace_utils.py | 6 +- ddtrace/contrib/internal/trace_utils_base.py | 6 +- ddtrace/contrib/internal/urllib/patch.py | 2 +- ddtrace/contrib/internal/urllib3/patch.py | 2 +- ddtrace/contrib/internal/webbrowser/patch.py | 2 +- ddtrace/contrib/internal/wsgi/wsgi.py | 2 +- ddtrace/debugging/_config.py | 4 +- ddtrace/debugging/_exception/replay.py | 4 +- ddtrace/debugging/_origin/span.py | 2 +- .../debugging/_products/code_origin/span.py | 4 +- .../_products/dynamic_instrumentation.py | 2 +- ddtrace/debugging/_products/live_debugger.py | 2 +- ddtrace/debugging/_redaction.py | 4 +- .../_handled_exceptions/bytecode_reporting.py | 2 +- .../_handled_exceptions/collector.py | 2 +- .../monitoring_reporting.py | 2 +- ddtrace/errortracking/product.py | 2 +- ddtrace/internal/_encoding.pyx | 2 +- ddtrace/internal/agent.py | 2 +- ddtrace/internal/appsec/product.py | 4 +- ddtrace/internal/ci_visibility/git_client.py | 4 +- ddtrace/internal/ci_visibility/recorder.py | 9 +- ddtrace/internal/ci_visibility/writer.py | 2 +- ddtrace/internal/core/crashtracking.py | 8 +- ddtrace/internal/core/event_hub.py | 2 +- .../internal/datadog/profiling/ddup/_ddup.pyx | 2 +- ddtrace/internal/datastreams/processor.py | 4 +- ddtrace/internal/debug.py | 6 +- ddtrace/internal/encoding.py | 3 +- ddtrace/internal/gitmetadata.py | 2 +- ddtrace/internal/iast/product.py | 2 +- ddtrace/internal/logger.py | 23 ++ ddtrace/internal/metrics.py | 2 +- ddtrace/internal/opentelemetry/logs.py | 2 +- ddtrace/internal/opentelemetry/metrics.py | 2 +- ddtrace/internal/packages.py | 2 +- ddtrace/internal/processor/stats.py | 2 +- ddtrace/internal/products.py | 2 +- ddtrace/internal/remoteconfig/client.py | 4 +- .../internal/remoteconfig/products/client.py | 2 +- ddtrace/internal/runtime/runtime_metrics.py | 2 +- ddtrace/internal/sampling.py | 2 +- ddtrace/internal/schema/processor.py | 2 +- .../internal/schema/span_attribute_schema.py | 2 +- ddtrace/{ => internal}/settings/__init__.py | 5 +- ddtrace/{ => internal}/settings/_agent.py | 2 +- ddtrace/{ => internal}/settings/_config.py | 38 ++-- ddtrace/{ => internal}/settings/_core.py | 0 .../settings/_database_monitoring.py | 2 +- .../settings/_inferred_base_service.py | 2 +- .../{ => internal}/settings/_opentelemetry.py | 4 +- .../{ => internal}/settings/_otel_remapper.py | 6 +- ddtrace/{ => internal}/settings/_telemetry.py | 4 +- ddtrace/{ => internal}/settings/asm.py | 8 +- .../{ => internal}/settings/code_origin.py | 2 +- .../{ => internal}/settings/crashtracker.py | 2 +- .../settings/dynamic_instrumentation.py | 4 +- .../settings/endpoint_config.py | 0 .../{ => internal}/settings/errortracking.py | 2 +- .../settings/exception_replay.py | 2 +- ddtrace/{ => internal}/settings/http.py | 6 +- .../{ => internal}/settings/integration.py | 5 +- .../{ => internal}/settings/live_debugging.py | 2 +- .../{ => internal}/settings/peer_service.py | 0 ddtrace/{ => internal}/settings/profiling.py | 2 +- ddtrace/{ => internal}/settings/symbol_db.py | 2 +- .../{ => internal}/settings/third_party.py | 2 +- ddtrace/internal/symbol_db/__init__.py | 2 +- ddtrace/internal/symbol_db/product.py | 2 +- ddtrace/internal/symbol_db/symbols.py | 4 +- ddtrace/internal/telemetry/__init__.py | 16 +- ddtrace/internal/telemetry/writer.py | 6 +- ddtrace/internal/writer/writer.py | 6 +- ddtrace/llmobs/_integrations/base.py | 2 +- ddtrace/llmobs/_writer.py | 2 +- ddtrace/profiling/_asyncio.py | 2 +- ddtrace/profiling/collector/__init__.py | 2 +- ddtrace/profiling/collector/_lock.py | 2 +- ddtrace/profiling/collector/_task.pyx | 2 +- ddtrace/profiling/collector/memalloc.py | 2 +- ddtrace/profiling/collector/pytorch.py | 2 +- ddtrace/profiling/collector/stack.pyx | 2 +- ddtrace/profiling/collector/threading.py | 2 +- ddtrace/profiling/profiler.py | 4 +- ddtrace/profiling/scheduler.py | 2 +- ddtrace/propagation/_database_monitoring.py | 4 +- ddtrace/propagation/http.py | 4 +- ddtrace/settings/exceptions.py | 6 - docs/configuration.rst | 208 +++++++++--------- .../internal-settings-3b45c1e8a96edc99.yaml | 5 + tests/appsec/ai_guard/api/test_api_client.py | 2 +- tests/appsec/ai_guard/langchain/conftest.py | 2 +- .../appsec/appsec/test_remoteconfiguration.py | 2 +- tests/appsec/architectures/mini.py | 2 +- .../test_appsec_loading_modules.py | 2 +- tests/appsec/contrib_appsec/conftest.py | 2 +- tests/appsec/contrib_appsec/utils.py | 4 +- .../fixtures/integration/main_configure.py | 2 +- .../taint_sinks/test_sql_injection_dbapi.py | 6 +- .../test_multiprocessing_tracer_iast_env.py | 2 +- tests/appsec/iast/test_loader.py | 2 +- .../iast/test_overhead_control_engine.py | 2 +- .../django_tests/test_appsec_django.py | 2 +- .../django_tests/test_iast_django.py | 2 +- .../flask_tests/test_iast_flask.py | 2 +- tests/appsec/suitespec.yml | 2 +- .../test_ci_visibility_api_client.py | 6 +- tests/ci_visibility/test_ci_visibility.py | 10 +- tests/ci_visibility/util.py | 2 +- tests/commands/ddtrace_run_global_tags.py | 8 - tests/commands/test_runner.py | 25 --- tests/contrib/dbapi/test_dbapi.py | 4 +- tests/contrib/dbapi_async/test_dbapi_async.py | 4 +- tests/contrib/httplib/test_httplib.py | 2 +- tests/contrib/httpx/test_httpx.py | 2 +- tests/contrib/httpx/test_httpx_pre_0_11.py | 2 +- tests/contrib/pymongo/test.py | 8 +- tests/contrib/pytest/test_pytest.py | 2 +- .../requests/test_requests_distributed.py | 2 +- .../subprocess/test_subprocess_patch.py | 2 +- tests/contrib/suitespec.yml | 2 +- tests/contrib/urllib3/test_urllib3.py | 2 +- tests/contrib/vertica/test_vertica.py | 2 +- tests/debugging/exception/test_replay.py | 2 +- tests/debugging/exploration/_config.py | 2 +- tests/debugging/mocking.py | 2 +- tests/debugging/suitespec.yml | 4 +- tests/debugging/test_config.py | 22 +- tests/errortracking/suitespec.yml | 2 +- .../test_integration_civisibility.py | 2 +- .../framework_injection/_config.py | 2 +- .../crashtracker/test_crashtracker.py | 2 +- tests/internal/crashtracker/utils.py | 2 +- tests/internal/peer_service/test_processor.py | 6 +- .../test_inferred_base_service.py | 6 +- tests/internal/symbol_db/test_config.py | 2 +- tests/internal/test_database_monitoring.py | 2 +- tests/internal/test_settings.py | 4 +- .../test_llmobs_eval_metric_agent_writer.py | 2 +- tests/llmobs/test_llmobs_span_agent_writer.py | 2 +- tests/profiling/collector/test_memalloc.py | 4 +- tests/profiling/suitespec.yml | 4 +- .../profiling_v2/collector/test_threading.py | 2 +- tests/profiling_v2/exporter/test_ddup.py | 2 +- tests/profiling_v2/test_profiler.py | 4 +- tests/suitespec.yml | 19 +- tests/telemetry/test_writer.py | 10 +- tests/tracer/test_agent.py | 56 ++--- tests/tracer/test_endpoint_config.py | 4 +- tests/tracer/test_env_vars.py | 2 +- tests/tracer/test_global_config.py | 6 +- tests/tracer/test_instance_config.py | 2 +- tests/tracer/test_settings.py | 76 +------ tests/tracer/test_trace_utils.py | 12 +- tests/tracer/test_tracer.py | 2 +- tests/utils.py | 8 +- 243 files changed, 538 insertions(+), 657 deletions(-) rename ddtrace/{ => internal}/settings/__init__.py (88%) rename ddtrace/{ => internal}/settings/_agent.py (98%) rename ddtrace/{ => internal}/settings/_config.py (96%) rename ddtrace/{ => internal}/settings/_core.py (100%) rename ddtrace/{ => internal}/settings/_database_monitoring.py (88%) rename ddtrace/{ => internal}/settings/_inferred_base_service.py (99%) rename ddtrace/{ => internal}/settings/_opentelemetry.py (97%) rename ddtrace/{ => internal}/settings/_otel_remapper.py (97%) rename ddtrace/{ => internal}/settings/_telemetry.py (91%) rename ddtrace/{ => internal}/settings/asm.py (98%) rename ddtrace/{ => internal}/settings/code_origin.py (92%) rename ddtrace/{ => internal}/settings/crashtracker.py (98%) rename ddtrace/{ => internal}/settings/dynamic_instrumentation.py (97%) rename ddtrace/{ => internal}/settings/endpoint_config.py (100%) rename ddtrace/{ => internal}/settings/errortracking.py (97%) rename ddtrace/{ => internal}/settings/exception_replay.py (91%) rename ddtrace/{ => internal}/settings/http.py (94%) rename ddtrace/{ => internal}/settings/integration.py (97%) rename ddtrace/{ => internal}/settings/live_debugging.py (83%) rename ddtrace/{ => internal}/settings/peer_service.py (100%) rename ddtrace/{ => internal}/settings/profiling.py (99%) rename ddtrace/{ => internal}/settings/symbol_db.py (94%) rename ddtrace/{ => internal}/settings/third_party.py (90%) delete mode 100644 ddtrace/settings/exceptions.py create mode 100644 releasenotes/notes/internal-settings-3b45c1e8a96edc99.yaml delete mode 100644 tests/commands/ddtrace_run_global_tags.py diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index a86597bdb08..dac0448fb68 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -19,8 +19,8 @@ from ._monkey import patch # noqa: E402 from ._monkey import patch_all # noqa: E402 from .internal.compat import PYTHON_VERSION_INFO # noqa: E402 +from .internal.settings._config import config from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402 -from .settings._config import config from .version import get_version # noqa: E402 diff --git a/ddtrace/_logger.py b/ddtrace/_logger.py index 631e9d97e4a..97f5691d740 100644 --- a/ddtrace/_logger.py +++ b/ddtrace/_logger.py @@ -17,18 +17,7 @@ DEFAULT_FILE_SIZE_BYTES = 15 << 20 # 15 MB -class LogInjectionState(object): - # Log injection is disabled - DISABLED = "false" - # Log injection is enabled, but not yet configured - ENABLED = "true" - # Log injection is enabled and configured for structured logging - # This value is deprecated, but kept for backwards compatibility - STRUCTURED = "structured" - - -def configure_ddtrace_logger(): - # type: () -> None +def configure_ddtrace_logger() -> None: """Configures ddtrace log levels and file paths. Customization is possible with the environment variables: @@ -110,25 +99,10 @@ def _add_file_handler( return ddtrace_file_handler -def get_log_injection_state(raw_config: Optional[str]) -> bool: - """Returns the current log injection state.""" - if raw_config: - normalized = raw_config.lower().strip() - if normalized == LogInjectionState.STRUCTURED or normalized in ("true", "1"): - return True - elif normalized not in ("false", "0"): - logging.warning( - "Invalid log injection state '%s'. Expected 'true', 'false', or 'structured'. Defaulting to 'false'.", - normalized, - ) - return False - - def _configure_ddtrace_native_logger(): try: from ddtrace.internal.native._native import logger - - from .settings._config import config + from ddtrace.internal.settings._config import config if config._trace_writer_native: backend = get_config("_DD_NATIVE_LOGGING_BACKEND", "file", report_telemetry=True) diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 9e7dca4a29e..6301ffffac7 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -8,8 +8,8 @@ from wrapt.importer import when_imported from ddtrace.internal.compat import Path +from ddtrace.internal.settings._config import config from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.settings._config import config from ddtrace.vendor.debtcollector import deprecate from ddtrace.vendor.packaging.specifiers import SpecifierSet from ddtrace.vendor.packaging.version import Version diff --git a/ddtrace/_trace/pin.py b/ddtrace/_trace/pin.py index 2850adb4896..4edacd90e05 100644 --- a/ddtrace/_trace/pin.py +++ b/ddtrace/_trace/pin.py @@ -4,7 +4,7 @@ import ddtrace from ddtrace.internal.compat import is_wrapted -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ..internal.logger import get_logger diff --git a/ddtrace/_trace/processor/__init__.py b/ddtrace/_trace/processor/__init__.py index 7b1fcec816e..6e513af6d4f 100644 --- a/ddtrace/_trace/processor/__init__.py +++ b/ddtrace/_trace/processor/__init__.py @@ -26,11 +26,11 @@ from ddtrace.internal.sampling import SpanSamplingRule from ddtrace.internal.sampling import get_span_sampling_rules from ddtrace.internal.service import ServiceStatusError +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE from ddtrace.internal.writer import AgentResponse from ddtrace.internal.writer import create_trace_writer -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/_trace/processor/resource_renaming.py b/ddtrace/_trace/processor/resource_renaming.py index 25a9f646795..2db07a4bc7c 100644 --- a/ddtrace/_trace/processor/resource_renaming.py +++ b/ddtrace/_trace/processor/resource_renaming.py @@ -7,7 +7,7 @@ from ddtrace.ext import SpanTypes from ddtrace.ext import http from ddtrace.internal.logger import get_logger -from ddtrace.settings._config import config +from ddtrace.internal.settings._config import config log = get_logger(__name__) diff --git a/ddtrace/_trace/product.py b/ddtrace/_trace/product.py index 1e709c0ac00..ec78dbc2c89 100644 --- a/ddtrace/_trace/product.py +++ b/ddtrace/_trace/product.py @@ -6,11 +6,9 @@ from envier import En from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.internal.settings.http import HttpConfig from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.settings.http import HttpConfig -from ddtrace.vendor.debtcollector import deprecate log = get_logger(__name__) @@ -40,27 +38,13 @@ def post_preload(): def start(): if _config.enabled: - from ddtrace.settings._config import config + from ddtrace.internal.settings._config import config if config._trace_methods: from ddtrace.internal.tracemethods import _install_trace_methods _install_trace_methods(config._trace_methods) - if _config.global_tags: - from ddtrace.trace import tracer - - # ddtrace library supports setting tracer tags using both DD_TRACE_GLOBAL_TAGS and DD_TAGS - # moving forward we should only support DD_TRACE_GLOBAL_TAGS. - # TODO(munir): Set dd_tags here - deprecate( - "DD_TRACE_GLOBAL_TAGS is deprecated", - message="Please migrate to using DD_TAGS instead", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - tracer.set_tags(_config.global_tags) - def restart(join=False): from ddtrace.trace import tracer diff --git a/ddtrace/_trace/sampler.py b/ddtrace/_trace/sampler.py index b932d7e71a2..8bd6e0a089c 100644 --- a/ddtrace/_trace/sampler.py +++ b/ddtrace/_trace/sampler.py @@ -10,7 +10,7 @@ from ddtrace._trace.span import Span from ddtrace.constants import _SAMPLING_LIMIT_DECISION -from ddtrace.settings._config import config +from ddtrace.internal.settings._config import config from ..constants import ENV_KEY from ..internal.constants import MAX_UINT_64BITS diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index fbc23395bf3..fb5699a7d42 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -52,8 +52,8 @@ from ddtrace.internal.constants import SPAN_API_DATADOG from ddtrace.internal.constants import SamplingMechanism from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._config import config from ddtrace.internal.utils.time import Time -from ddtrace.settings._config import config from ddtrace.vendor.debtcollector import removals diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 2c348fd29ac..a2e7175b8c0 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -52,14 +52,14 @@ from ddtrace.internal.processor.endpoint_call_counter import EndpointCallCounterProcessor from ddtrace.internal.runtime import get_runtime_id from ddtrace.internal.schema.processor import BaseServiceProcessor +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.settings.peer_service import _ps_config from ddtrace.internal.utils import _get_metas_to_propagate from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.formats import format_trace_id from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import HTTPWriter -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config -from ddtrace.settings.peer_service import _ps_config from ddtrace.vendor.debtcollector.removals import remove from ddtrace.version import get_version diff --git a/ddtrace/appsec/_ai_guard/__init__.py b/ddtrace/appsec/_ai_guard/__init__.py index 0cbda1713ef..925b7277378 100644 --- a/ddtrace/appsec/_ai_guard/__init__.py +++ b/ddtrace/appsec/_ai_guard/__init__.py @@ -1,5 +1,5 @@ import ddtrace.internal.logger as ddlogger -from ddtrace.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import ai_guard_config logger = ddlogger.get_logger(__name__) diff --git a/ddtrace/appsec/_api_security/api_manager.py b/ddtrace/appsec/_api_security/api_manager.py index d05f07a1002..6138e433a93 100644 --- a/ddtrace/appsec/_api_security/api_manager.py +++ b/ddtrace/appsec/_api_security/api_manager.py @@ -12,7 +12,7 @@ import ddtrace.constants as constants from ddtrace.internal import logger as ddlogger from ddtrace.internal.service import Service -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = ddlogger.get_logger(__name__) diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 634057b13c4..522ea5d6ad9 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -25,7 +25,7 @@ from ddtrace.internal._exceptions import BlockingException from ddtrace.internal.constants import REQUEST_PATH_PARAMS import ddtrace.internal.logger as ddlogger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config if TYPE_CHECKING: diff --git a/ddtrace/appsec/_capabilities.py b/ddtrace/appsec/_capabilities.py index 116fbfe7345..f642d987965 100644 --- a/ddtrace/appsec/_capabilities.py +++ b/ddtrace/appsec/_capabilities.py @@ -1,8 +1,8 @@ import base64 import enum -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config class Flags(enum.IntFlag): diff --git a/ddtrace/appsec/_common_module_patches.py b/ddtrace/appsec/_common_module_patches.py index 056505d5fdf..5b7dccf139a 100644 --- a/ddtrace/appsec/_common_module_patches.py +++ b/ddtrace/appsec/_common_module_patches.py @@ -23,7 +23,7 @@ from ddtrace.internal._unpatched import _gc as gc from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_ddwaf/__init__.py b/ddtrace/appsec/_ddwaf/__init__.py index 5ec5148a3ab..39a014f9e42 100644 --- a/ddtrace/appsec/_ddwaf/__init__.py +++ b/ddtrace/appsec/_ddwaf/__init__.py @@ -5,7 +5,7 @@ from ddtrace.appsec._utils import DDWaf_info from ddtrace.appsec._utils import DDWaf_result from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config __all__ = ["DDWaf", "DDWaf_info", "DDWaf_result", "version", "DDWafRulesType"] diff --git a/ddtrace/appsec/_ddwaf/ddwaf_types.py b/ddtrace/appsec/_ddwaf/ddwaf_types.py index 30af95b1b1c..ba5bd95ce6c 100644 --- a/ddtrace/appsec/_ddwaf/ddwaf_types.py +++ b/ddtrace/appsec/_ddwaf/ddwaf_types.py @@ -17,7 +17,7 @@ from ddtrace.appsec._utils import _observator from ddtrace.appsec._utils import unpatching_popen from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config DDWafRulesType = Union[None, int, str, List[Any], Dict[str, Any]] diff --git a/ddtrace/appsec/_deduplications.py b/ddtrace/appsec/_deduplications.py index 59a76b0670d..f61fedacdc2 100644 --- a/ddtrace/appsec/_deduplications.py +++ b/ddtrace/appsec/_deduplications.py @@ -1,7 +1,7 @@ from collections import OrderedDict from time import monotonic -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config M_INF = float("-inf") diff --git a/ddtrace/appsec/_exploit_prevention/stack_traces.py b/ddtrace/appsec/_exploit_prevention/stack_traces.py index e4711a16385..a2262ddc835 100644 --- a/ddtrace/appsec/_exploit_prevention/stack_traces.py +++ b/ddtrace/appsec/_exploit_prevention/stack_traces.py @@ -9,7 +9,7 @@ from ddtrace.appsec import _asm_request_context from ddtrace.appsec._constants import STACK_TRACE from ddtrace.internal import core -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config def report_stack( diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index 78b8828ebdf..435ce46b39f 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -23,9 +23,9 @@ from ddtrace.internal import telemetry from ddtrace.internal.constants import RESPONSE_HEADERS from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils import http as http_utils from ddtrace.internal.utils.http import parse_form_multipart -from ddtrace.settings.asm import config as asm_config import ddtrace.vendor.xmltodict as xmltodict diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index 07664c43e79..32f8fd23488 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -34,7 +34,7 @@ def wrapped_function(wrapped, instance, args, kwargs): from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ._listener import iast_listen from ._overhead_control_engine import oce diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 958b37eb4cb..24fa2b224dc 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -14,8 +14,8 @@ from ddtrace.appsec._iast._logs import iast_instrumentation_ast_patching_debug_log from ddtrace.internal.logger import get_logger from ddtrace.internal.module import origin +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config from .visitor import AstVisitor diff --git a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py index 3fa804c68a5..d1d52fea0d0 100644 --- a/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py +++ b/ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py @@ -2,7 +2,7 @@ import string from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from .._utils import _get_source_index from ..constants import VULN_CMDI diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py index 3ccc3c34c86..33631c388a3 100644 --- a/ddtrace/appsec/_iast/_handlers.py +++ b/ddtrace/appsec/_iast/_handlers.py @@ -22,7 +22,7 @@ from ddtrace.appsec._iast.secure_marks.sanitizers import cmdi_sanitizer from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config MessageMapContainer = None diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index 65d88b2fbe9..4d17aec48c1 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -17,7 +17,7 @@ from ddtrace.constants import _ORIGIN_KEY from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_iast_request_context_base.py b/ddtrace/appsec/_iast/_iast_request_context_base.py index 7110518945a..f36f947db0b 100644 --- a/ddtrace/appsec/_iast/_iast_request_context_base.py +++ b/ddtrace/appsec/_iast/_iast_request_context_base.py @@ -12,7 +12,7 @@ from ddtrace.appsec._iast.sampling.vulnerability_detection import update_global_vulnerability_limit from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_langchain.py b/ddtrace/appsec/_iast/_langchain.py index 49456bf370c..d169c6e49ed 100644 --- a/ddtrace/appsec/_iast/_langchain.py +++ b/ddtrace/appsec/_iast/_langchain.py @@ -4,8 +4,8 @@ from ddtrace.appsec._iast._taint_tracking._taint_objects_base import get_tainted_ranges from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.contrib.internal.trace_utils import wrap +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils import get_argument_value -from ddtrace.settings.asm import config as asm_config def langchain_listen(core): diff --git a/ddtrace/appsec/_iast/_loader.py b/ddtrace/appsec/_iast/_loader.py index cef1a02d499..a1f77ee16d3 100644 --- a/ddtrace/appsec/_iast/_loader.py +++ b/ddtrace/appsec/_iast/_loader.py @@ -1,6 +1,6 @@ from ddtrace.appsec._iast._logs import iast_compiling_debug_log from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ._ast.ast_patching import astpatch_module diff --git a/ddtrace/appsec/_iast/_logs.py b/ddtrace/appsec/_iast/_logs.py index 5c07099d940..daf506bfd9e 100644 --- a/ddtrace/appsec/_iast/_logs.py +++ b/ddtrace/appsec/_iast/_logs.py @@ -2,7 +2,7 @@ from ddtrace.appsec._iast._metrics import _set_iast_error_metric from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_metrics.py b/ddtrace/appsec/_iast/_metrics.py index a27a0355c95..a7a59e62432 100644 --- a/ddtrace/appsec/_iast/_metrics.py +++ b/ddtrace/appsec/_iast/_metrics.py @@ -12,8 +12,8 @@ from ddtrace.appsec._iast._utils import _is_iast_debug_enabled from ddtrace.internal import telemetry from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_overhead_control_engine.py b/ddtrace/appsec/_iast/_overhead_control_engine.py index e9e1c00927d..bd2a1ddca9b 100644 --- a/ddtrace/appsec/_iast/_overhead_control_engine.py +++ b/ddtrace/appsec/_iast/_overhead_control_engine.py @@ -8,7 +8,7 @@ from ddtrace.appsec._iast._utils import _is_iast_debug_enabled from ddtrace.internal._unpatched import _threading as threading from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_patch_modules.py b/ddtrace/appsec/_iast/_patch_modules.py index 6a903a45a14..4b66bc3a27c 100644 --- a/ddtrace/appsec/_iast/_patch_modules.py +++ b/ddtrace/appsec/_iast/_patch_modules.py @@ -29,7 +29,7 @@ from ddtrace.appsec._iast.secure_marks.sanitizers import create_sanitizer from ddtrace.appsec._iast.secure_marks.validators import create_validator from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_patches/json_tainting.py b/ddtrace/appsec/_iast/_patches/json_tainting.py index 2c3ca903645..6a53ed8b735 100644 --- a/ddtrace/appsec/_iast/_patches/json_tainting.py +++ b/ddtrace/appsec/_iast/_patches/json_tainting.py @@ -2,7 +2,7 @@ from ddtrace.appsec._iast._iast_request_context_base import is_iast_request_enabled from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ..._constants import IAST from .._patch_modules import WrapFunctonsForIAST diff --git a/ddtrace/appsec/_iast/_pytest_plugin.py b/ddtrace/appsec/_iast/_pytest_plugin.py index 1ca1ad2dbc1..1de6aec1017 100644 --- a/ddtrace/appsec/_iast/_pytest_plugin.py +++ b/ddtrace/appsec/_iast/_pytest_plugin.py @@ -6,7 +6,7 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast.reporter import Vulnerability from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/_taint_tracking/__init__.py b/ddtrace/appsec/_iast/_taint_tracking/__init__.py index 527516dd4de..0c2d7902c61 100644 --- a/ddtrace/appsec/_iast/_taint_tracking/__init__.py +++ b/ddtrace/appsec/_iast/_taint_tracking/__init__.py @@ -1,8 +1,8 @@ from ddtrace.appsec._iast._taint_tracking._native import ops # noqa: F401 from ddtrace.appsec._iast._taint_tracking._native.aspect_format import _format_aspect # noqa: F401 -from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import ( - _convert_escaped_text_to_tainted_text, -) # noqa: F401 +from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import _convert_escaped_text_to_tainted_text + +# noqa: F401 from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import are_all_text_all_ranges # noqa: F401 from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import as_formatted_evidence # noqa: F401 from ddtrace.appsec._iast._taint_tracking._native.aspect_helpers import common_replace # noqa: F401 diff --git a/ddtrace/appsec/_iast/_taint_utils.py b/ddtrace/appsec/_iast/_taint_utils.py index f5e3622c60c..9077d209297 100644 --- a/ddtrace/appsec/_iast/_taint_utils.py +++ b/ddtrace/appsec/_iast/_taint_utils.py @@ -9,7 +9,7 @@ from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from ddtrace.appsec._iast._taint_tracking._taint_objects_base import is_pyobject_tainted from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config DBAPI_PREFIXES = ("django-",) diff --git a/ddtrace/appsec/_iast/_utils.py b/ddtrace/appsec/_iast/_utils.py index e2c2dbec836..54c98110667 100644 --- a/ddtrace/appsec/_iast/_utils.py +++ b/ddtrace/appsec/_iast/_utils.py @@ -1,6 +1,6 @@ from typing import List -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config def _get_source_index(sources: List, source) -> int: diff --git a/ddtrace/appsec/_iast/main.py b/ddtrace/appsec/_iast/main.py index fd1c9140746..057e9dd7e4f 100644 --- a/ddtrace/appsec/_iast/main.py +++ b/ddtrace/appsec/_iast/main.py @@ -42,7 +42,7 @@ from ddtrace.appsec._iast.taint_sinks.weak_hash import patch as weak_hash_patch from ddtrace.appsec._iast.taint_sinks.xss import patch as xss_patch from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/sampling/vulnerability_detection.py b/ddtrace/appsec/_iast/sampling/vulnerability_detection.py index 174a972f14a..035239ec79b 100644 --- a/ddtrace/appsec/_iast/sampling/vulnerability_detection.py +++ b/ddtrace/appsec/_iast/sampling/vulnerability_detection.py @@ -3,7 +3,7 @@ from ddtrace.appsec._iast._iast_env import _get_iast_env from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/secure_marks/configuration.py b/ddtrace/appsec/_iast/secure_marks/configuration.py index 1b62b5ff6cc..1cd54137723 100644 --- a/ddtrace/appsec/_iast/secure_marks/configuration.py +++ b/ddtrace/appsec/_iast/secure_marks/configuration.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast._taint_tracking import VulnerabilityType from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 9fcf1235482..43eff627e03 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -13,7 +13,7 @@ from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ..._constants import IAST from ..._constants import IAST_SPAN_TAGS diff --git a/ddtrace/appsec/_iast/taint_sinks/code_injection.py b/ddtrace/appsec/_iast/taint_sinks/code_injection.py index d6d6a2acc54..55cbc86eb90 100644 --- a/ddtrace/appsec/_iast/taint_sinks/code_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/code_injection.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast.constants import VULN_CODE_INJECTION from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/taint_sinks/header_injection.py b/ddtrace/appsec/_iast/taint_sinks/header_injection.py index 7d4ab9acc26..30987773555 100644 --- a/ddtrace/appsec/_iast/taint_sinks/header_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/header_injection.py @@ -71,7 +71,7 @@ from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.appsec._iast.taint_sinks.unvalidated_redirect import _iast_report_unvalidated_redirect from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py index 9dd0e5f5022..45a43b9dbba 100644 --- a/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py +++ b/ddtrace/appsec/_iast/taint_sinks/insecure_cookie.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.sampling.vulnerability_detection import should_process_vulnerability from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config class InsecureCookie(VulnerabilityBase): diff --git a/ddtrace/appsec/_iast/taint_sinks/sql_injection.py b/ddtrace/appsec/_iast/taint_sinks/sql_injection.py index 762d580bb59..bba3847f764 100644 --- a/ddtrace/appsec/_iast/taint_sinks/sql_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/sql_injection.py @@ -9,7 +9,7 @@ from ddtrace.appsec._iast.constants import DBAPI_INTEGRATIONS from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config class SqlInjection(VulnerabilityBase): diff --git a/ddtrace/appsec/_iast/taint_sinks/untrusted_serialization.py b/ddtrace/appsec/_iast/taint_sinks/untrusted_serialization.py index b878663ecdf..6acee4b5647 100644 --- a/ddtrace/appsec/_iast/taint_sinks/untrusted_serialization.py +++ b/ddtrace/appsec/_iast/taint_sinks/untrusted_serialization.py @@ -12,7 +12,7 @@ from ddtrace.appsec._iast.constants import VULN_UNTRUSTED_SERIALIZATION from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/taint_sinks/unvalidated_redirect.py b/ddtrace/appsec/_iast/taint_sinks/unvalidated_redirect.py index 551e3123ab0..300545899cb 100644 --- a/ddtrace/appsec/_iast/taint_sinks/unvalidated_redirect.py +++ b/ddtrace/appsec/_iast/taint_sinks/unvalidated_redirect.py @@ -14,8 +14,8 @@ from ddtrace.appsec._iast.secure_marks.base import add_secure_mark from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils import get_argument_value -from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py index 5bab4769876..ee4a040c59c 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_cipher.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast.constants import RC4_DEF from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from .._logs import iast_error from .._metrics import _set_metric_iast_executed_sink diff --git a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py index 8f44a6a5f5f..dc9609cfbda 100644 --- a/ddtrace/appsec/_iast/taint_sinks/weak_hash.py +++ b/ddtrace/appsec/_iast/taint_sinks/weak_hash.py @@ -4,7 +4,7 @@ from typing import Set from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ..._common_module_patches import try_unwrap from ..._constants import IAST_SPAN_TAGS diff --git a/ddtrace/appsec/_iast/taint_sinks/xss.py b/ddtrace/appsec/_iast/taint_sinks/xss.py index 29e486ccd0c..db1077fe08b 100644 --- a/ddtrace/appsec/_iast/taint_sinks/xss.py +++ b/ddtrace/appsec/_iast/taint_sinks/xss.py @@ -13,7 +13,7 @@ from ddtrace.appsec._iast.taint_sinks._base import VulnerabilityBase from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_listeners.py b/ddtrace/appsec/_listeners.py index c3d6f443f06..42ca8ba883a 100644 --- a/ddtrace/appsec/_listeners.py +++ b/ddtrace/appsec/_listeners.py @@ -1,7 +1,7 @@ import sys from ddtrace.internal import core -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config _APPSEC_TO_BE_LOADED = True @@ -43,7 +43,7 @@ def load_appsec() -> None: def load_common_appsec_modules(): """Lazily load the common module patches.""" - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config if asm_config._load_modules: from ddtrace.appsec._common_module_patches import patch_common_modules diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index 27f33e41d10..aa58b9ae5be 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -42,7 +42,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.internal.remoteconfig import PayloadType -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_remoteconfiguration.py b/ddtrace/appsec/_remoteconfiguration.py index b169a8ec86e..a49c574a25e 100644 --- a/ddtrace/appsec/_remoteconfiguration.py +++ b/ddtrace/appsec/_remoteconfiguration.py @@ -19,9 +19,9 @@ from ddtrace.internal.remoteconfig._pubsub import PubSub from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT -from ddtrace.settings.asm import config as asm_config from ddtrace.trace import Tracer from ddtrace.trace import tracer diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py index 83d47a0159d..9bda7c89cff 100644 --- a/ddtrace/appsec/_trace_utils.py +++ b/ddtrace/appsec/_trace_utils.py @@ -18,7 +18,7 @@ from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index 11fcd252915..1223dfa8675 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -16,7 +16,7 @@ from ddtrace.contrib.internal.trace_utils_base import _get_header_value_case_insensitive from ddtrace.internal._unpatched import unpatched_json_loads from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/appsec/ai_guard/_api_client.py b/ddtrace/appsec/ai_guard/_api_client.py index 345e7cdbb07..86fa4a2b495 100644 --- a/ddtrace/appsec/ai_guard/_api_client.py +++ b/ddtrace/appsec/ai_guard/_api_client.py @@ -14,11 +14,11 @@ from ddtrace.appsec._constants import AI_GUARD from ddtrace.internal import telemetry import ddtrace.internal.logger as ddlogger +from ddtrace.internal.settings.asm import ai_guard_config from ddtrace.internal.telemetry import TELEMETRY_NAMESPACE from ddtrace.internal.telemetry.metrics_namespaces import MetricTagType from ddtrace.internal.utils.http import Response from ddtrace.internal.utils.http import get_connection -from ddtrace.settings.asm import ai_guard_config logger = ddlogger.get_logger(__name__) diff --git a/ddtrace/bootstrap/preload.py b/ddtrace/bootstrap/preload.py index 50239be9cd8..b0570b577bc 100644 --- a/ddtrace/bootstrap/preload.py +++ b/ddtrace/bootstrap/preload.py @@ -10,8 +10,8 @@ from ddtrace.internal.module import ModuleWatchdog # noqa:F401 from ddtrace.internal.products import manager # noqa:F401 from ddtrace.internal.runtime.runtime_metrics import RuntimeWorker # noqa:F401 -from ddtrace.settings.crashtracker import config as crashtracker_config -from ddtrace.settings.profiling import config as profiling_config # noqa:F401 +from ddtrace.internal.settings.crashtracker import config as crashtracker_config +from ddtrace.internal.settings.profiling import config as profiling_config # noqa:F401 from ddtrace.trace import tracer diff --git a/ddtrace/contrib/internal/asgi/middleware.py b/ddtrace/contrib/internal/asgi/middleware.py index 0c2166f0526..2bb6179fd8c 100644 --- a/ddtrace/contrib/internal/asgi/middleware.py +++ b/ddtrace/contrib/internal/asgi/middleware.py @@ -24,10 +24,10 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils import get_blocked from ddtrace.internal.utils import set_blocked from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._config import _get_config from ddtrace.trace import Span diff --git a/ddtrace/contrib/internal/aws_lambda/patch.py b/ddtrace/contrib/internal/aws_lambda/patch.py index a7aa0c0515b..ebddf1c699b 100644 --- a/ddtrace/contrib/internal/aws_lambda/patch.py +++ b/ddtrace/contrib/internal/aws_lambda/patch.py @@ -8,10 +8,10 @@ from ddtrace.contrib.internal.aws_lambda._cold_start import set_cold_start from ddtrace.internal.logger import get_logger from ddtrace.internal.serverless import in_aws_lambda +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.wrapping import unwrap from ddtrace.internal.wrapping import wrap -from ddtrace.settings._config import _get_config from ddtrace.trace import tracer diff --git a/ddtrace/contrib/internal/azure_eventhubs/patch.py b/ddtrace/contrib/internal/azure_eventhubs/patch.py index 1273457983c..7daa99ed480 100644 --- a/ddtrace/contrib/internal/azure_eventhubs/patch.py +++ b/ddtrace/contrib/internal/azure_eventhubs/patch.py @@ -9,9 +9,9 @@ from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import azure_eventhubs as azure_eventhubsx from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._config import _get_config from .utils import create_context from .utils import dispatch_message_modifier diff --git a/ddtrace/contrib/internal/azure_functions/patch.py b/ddtrace/contrib/internal/azure_functions/patch.py index b3a2c420e1f..21f1b6e9e9f 100644 --- a/ddtrace/contrib/internal/azure_functions/patch.py +++ b/ddtrace/contrib/internal/azure_functions/patch.py @@ -10,9 +10,9 @@ from ddtrace.ext import azure_eventhubs as azure_eventhubsx from ddtrace.ext import azure_servicebus as azure_servicebusx from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils.formats import asbool from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings._config import _get_config from .utils import create_context from .utils import wrap_function_with_tracing diff --git a/ddtrace/contrib/internal/azure_servicebus/patch.py b/ddtrace/contrib/internal/azure_servicebus/patch.py index 82aefa6ea0e..27f88943b44 100644 --- a/ddtrace/contrib/internal/azure_servicebus/patch.py +++ b/ddtrace/contrib/internal/azure_servicebus/patch.py @@ -9,8 +9,8 @@ from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import azure_servicebus as azure_servicebusx from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._config import _get_config from .utils import create_context from .utils import dispatch_message_modifier diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py index 323676595fe..a3528d726ea 100644 --- a/ddtrace/contrib/internal/botocore/patch.py +++ b/ddtrace/contrib/internal/botocore/patch.py @@ -30,11 +30,11 @@ from ddtrace.internal.schema import schematize_cloud_faas_operation from ddtrace.internal.schema import schematize_cloud_messaging_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings._config import Config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import deep_getattr from ddtrace.llmobs._integrations import BedrockIntegration -from ddtrace.settings._config import Config from .services.bedrock import patched_bedrock_api_call from .services.bedrock_agents import patched_bedrock_agents_api_call diff --git a/ddtrace/contrib/internal/coverage/utils.py b/ddtrace/contrib/internal/coverage/utils.py index 26c6d0fc308..6f543c033fe 100644 --- a/ddtrace/contrib/internal/coverage/utils.py +++ b/ddtrace/contrib/internal/coverage/utils.py @@ -2,8 +2,8 @@ from typing import List from ddtrace.contrib.internal.coverage.data import _original_sys_argv_command +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._config import _get_config def is_coverage_loaded() -> bool: diff --git a/ddtrace/contrib/internal/django/cache.py b/ddtrace/contrib/internal/django/cache.py index 10779ef907d..f8a313144dc 100644 --- a/ddtrace/contrib/internal/django/cache.py +++ b/ddtrace/contrib/internal/django/cache.py @@ -14,10 +14,10 @@ from ddtrace.internal.constants import COMPONENT from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils.cache import cached from ddtrace.internal.wrapping import is_wrapped_with from ddtrace.internal.wrapping import wrap -from ddtrace.settings.integration import IntegrationConfig from . import utils diff --git a/ddtrace/contrib/internal/django/database.py b/ddtrace/contrib/internal/django/database.py index 1f9eec5a0fa..a56dc5e670d 100644 --- a/ddtrace/contrib/internal/django/database.py +++ b/ddtrace/contrib/internal/django/database.py @@ -19,11 +19,11 @@ from ddtrace.internal.compat import is_wrapted from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils.cache import cached from ddtrace.internal.wrapping import is_wrapped_with from ddtrace.internal.wrapping import wrap from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.settings.integration import IntegrationConfig log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/django/middleware.py b/ddtrace/contrib/internal/django/middleware.py index 1f893e1740c..d21ce410803 100644 --- a/ddtrace/contrib/internal/django/middleware.py +++ b/ddtrace/contrib/internal/django/middleware.py @@ -11,13 +11,13 @@ from ddtrace.internal import core from ddtrace.internal.constants import COMPONENT from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.importlib import func_name from ddtrace.internal.wrapping import is_wrapped from ddtrace.internal.wrapping import is_wrapped_with from ddtrace.internal.wrapping import wrap -from ddtrace.settings.asm import config as asm_config -from ddtrace.settings.integration import IntegrationConfig log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index 17f70643079..8e0b56ac040 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -29,12 +29,12 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.telemetry import get_config as _get_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.importlib import func_name -from ddtrace.settings.asm import config as asm_config -from ddtrace.settings.integration import IntegrationConfig from ddtrace.vendor.packaging.version import parse as parse_version diff --git a/ddtrace/contrib/internal/django/response.py b/ddtrace/contrib/internal/django/response.py index cd3b5ddb40d..e5f8d9966bb 100644 --- a/ddtrace/contrib/internal/django/response.py +++ b/ddtrace/contrib/internal/django/response.py @@ -29,6 +29,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils import get_blocked from ddtrace.internal.utils import http as http_utils @@ -36,7 +37,6 @@ from ddtrace.internal.wrapping import is_wrapped_with from ddtrace.internal.wrapping import unwrap from ddtrace.internal.wrapping import wrap -from ddtrace.settings.integration import IntegrationConfig from . import utils diff --git a/ddtrace/contrib/internal/django/templates.py b/ddtrace/contrib/internal/django/templates.py index 744550a1a34..7e977a5ad0d 100644 --- a/ddtrace/contrib/internal/django/templates.py +++ b/ddtrace/contrib/internal/django/templates.py @@ -13,11 +13,11 @@ from ddtrace.internal.compat import maybe_stringify from ddtrace.internal.constants import COMPONENT from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils.importlib import func_name from ddtrace.internal.wrapping import is_wrapped_with from ddtrace.internal.wrapping import unwrap from ddtrace.internal.wrapping import wrap -from ddtrace.settings.integration import IntegrationConfig T = TypeVar("T") diff --git a/ddtrace/contrib/internal/django/user.py b/ddtrace/contrib/internal/django/user.py index f8ebeb0b5fc..b1b3631cd18 100644 --- a/ddtrace/contrib/internal/django/user.py +++ b/ddtrace/contrib/internal/django/user.py @@ -1,6 +1,6 @@ from ddtrace.appsec._utils import _UserInfoRetriever from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/dramatiq/patch.py b/ddtrace/contrib/internal/dramatiq/patch.py index dbcb2c1384d..ffd8ce8c0f8 100644 --- a/ddtrace/contrib/internal/dramatiq/patch.py +++ b/ddtrace/contrib/internal/dramatiq/patch.py @@ -10,7 +10,7 @@ from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes -from ddtrace.settings._config import Config +from ddtrace.internal.settings._config import Config from ddtrace.trace import tracer diff --git a/ddtrace/contrib/internal/fastapi/patch.py b/ddtrace/contrib/internal/fastapi/patch.py index d10678f53a9..ad624febab5 100644 --- a/ddtrace/contrib/internal/fastapi/patch.py +++ b/ddtrace/contrib/internal/fastapi/patch.py @@ -14,10 +14,10 @@ from ddtrace.internal.compat import is_wrapted from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry import get_config as _get_config from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap as _u -from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/httplib/patch.py b/ddtrace/contrib/internal/httplib/patch.py index cc82045a76f..8fe29d593f6 100644 --- a/ddtrace/contrib/internal/httplib/patch.py +++ b/ddtrace/contrib/internal/httplib/patch.py @@ -19,9 +19,9 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings.asm import config as asm_config span_name = "http.client.request" diff --git a/ddtrace/contrib/internal/mysql/patch.py b/ddtrace/contrib/internal/mysql/patch.py index 557f3fb93c3..91178c0b7f3 100644 --- a/ddtrace/contrib/internal/mysql/patch.py +++ b/ddtrace/contrib/internal/mysql/patch.py @@ -13,9 +13,9 @@ from ddtrace.internal.compat import is_wrapted from ddtrace.internal.schema import schematize_database_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.settings.asm import config as asm_config config._add( diff --git a/ddtrace/contrib/internal/mysqldb/patch.py b/ddtrace/contrib/internal/mysqldb/patch.py index fefccec7776..2e727ac415e 100644 --- a/ddtrace/contrib/internal/mysqldb/patch.py +++ b/ddtrace/contrib/internal/mysqldb/patch.py @@ -18,10 +18,10 @@ from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_database_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.settings.asm import config as asm_config config._add( diff --git a/ddtrace/contrib/internal/pyramid/patch.py b/ddtrace/contrib/internal/pyramid/patch.py index ded17f54666..6f5ee9c6b5f 100644 --- a/ddtrace/contrib/internal/pyramid/patch.py +++ b/ddtrace/contrib/internal/pyramid/patch.py @@ -5,8 +5,8 @@ import wrapt from ddtrace import config +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._config import _get_config from .constants import SETTINGS_DISTRIBUTED_TRACING from .constants import SETTINGS_SERVICE diff --git a/ddtrace/contrib/internal/pytest/_plugin_v2.py b/ddtrace/contrib/internal/pytest/_plugin_v2.py index 97d2a4fae73..24fc6e96513 100644 --- a/ddtrace/contrib/internal/pytest/_plugin_v2.py +++ b/ddtrace/contrib/internal/pytest/_plugin_v2.py @@ -64,6 +64,7 @@ from ddtrace.internal.coverage.code import ModuleCodeCollector from ddtrace.internal.coverage.installer import install as install_coverage from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.test_visibility._library_capabilities import LibraryCapabilities from ddtrace.internal.test_visibility.api import InternalTest from ddtrace.internal.test_visibility.api import InternalTestModule @@ -71,7 +72,6 @@ from ddtrace.internal.test_visibility.api import InternalTestSuite from ddtrace.internal.test_visibility.coverage_lines import CoverageLines from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.debtcollector import deprecate diff --git a/ddtrace/contrib/internal/pytest/_utils.py b/ddtrace/contrib/internal/pytest/_utils.py index b944671aa35..ca4dd5c8b75 100644 --- a/ddtrace/contrib/internal/pytest/_utils.py +++ b/ddtrace/contrib/internal/pytest/_utils.py @@ -20,11 +20,11 @@ from ddtrace.internal.ci_visibility.constants import ITR_UNSKIPPABLE_REASON from ddtrace.internal.ci_visibility.utils import get_source_lines_for_test_method from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.test_visibility.api import InternalTest from ddtrace.internal.utils.cache import cached from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.inspection import undecorated -from ddtrace.settings._config import _get_config log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/pytest/plugin.py b/ddtrace/contrib/internal/pytest/plugin.py index 94de436e430..fc64e9c3f45 100644 --- a/ddtrace/contrib/internal/pytest/plugin.py +++ b/ddtrace/contrib/internal/pytest/plugin.py @@ -31,8 +31,8 @@ from ddtrace.contrib.internal.pytest._plugin_v2 import pytest_sessionstart # noqa: F401 from ddtrace.contrib.internal.pytest._plugin_v2 import pytest_terminal_summary # noqa: F401 from ddtrace.contrib.internal.pytest._utils import _extract_span -from ddtrace.settings._telemetry import config as telemetry_config -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings._telemetry import config as telemetry_config +from ddtrace.internal.settings.asm import config as asm_config if asm_config._iast_enabled: diff --git a/ddtrace/contrib/internal/requests/connection.py b/ddtrace/contrib/internal/requests/connection.py index f2a6926ff53..674bb782b17 100644 --- a/ddtrace/contrib/internal/requests/connection.py +++ b/ddtrace/contrib/internal/requests/connection.py @@ -20,9 +20,9 @@ from ddtrace.internal.opentelemetry.constants import OTLP_EXPORTER_HEADER_IDENTIFIER from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils import get_argument_value from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/requests/patch.py b/ddtrace/contrib/internal/requests/patch.py index 57e4a4d6aae..1d8ba74e9a7 100644 --- a/ddtrace/contrib/internal/requests/patch.py +++ b/ddtrace/contrib/internal/requests/patch.py @@ -8,8 +8,8 @@ from ddtrace._trace.pin import Pin from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config from .connection import _wrap_send from .session import TracedSession diff --git a/ddtrace/contrib/internal/rq/patch.py b/ddtrace/contrib/internal/rq/patch.py index bcfa7dbdc36..d66876630d7 100644 --- a/ddtrace/contrib/internal/rq/patch.py +++ b/ddtrace/contrib/internal/rq/patch.py @@ -3,19 +3,18 @@ from ddtrace import config from ddtrace._trace.pin import Pin from ddtrace.constants import SPAN_KIND +from ddtrace.contrib import trace_utils +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_messaging_operation from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings._config import _get_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool -from ....ext import SpanKind -from ....ext import SpanTypes -from ....settings._config import _get_config -from ... import trace_utils - config._add( "rq", diff --git a/ddtrace/contrib/internal/sqlalchemy/patch.py b/ddtrace/contrib/internal/sqlalchemy/patch.py index a87eb855791..2180fa096e9 100644 --- a/ddtrace/contrib/internal/sqlalchemy/patch.py +++ b/ddtrace/contrib/internal/sqlalchemy/patch.py @@ -4,7 +4,7 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.contrib.internal.trace_utils import unwrap -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from .engine import _wrap_create_engine diff --git a/ddtrace/contrib/internal/sqlite3/patch.py b/ddtrace/contrib/internal/sqlite3/patch.py index c9d5a370897..ec7188a1add 100644 --- a/ddtrace/contrib/internal/sqlite3/patch.py +++ b/ddtrace/contrib/internal/sqlite3/patch.py @@ -13,8 +13,8 @@ from ddtrace.ext import db from ddtrace.internal.schema import schematize_database_operation from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config # Original connect method diff --git a/ddtrace/contrib/internal/starlette/patch.py b/ddtrace/contrib/internal/starlette/patch.py index 5734a837f8a..abb53e4998f 100644 --- a/ddtrace/contrib/internal/starlette/patch.py +++ b/ddtrace/contrib/internal/starlette/patch.py @@ -23,13 +23,13 @@ from ddtrace.internal.endpoints import endpoint_collection from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry import get_config as _get_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils import get_blocked from ddtrace.internal.utils import set_argument_value from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap as _u -from ddtrace.settings.asm import config as asm_config from ddtrace.trace import Span # noqa:F401 from ddtrace.vendor.packaging.version import parse as parse_version diff --git a/ddtrace/contrib/internal/subprocess/patch.py b/ddtrace/contrib/internal/subprocess/patch.py index 66e0a8dac7b..9649a05329a 100644 --- a/ddtrace/contrib/internal/subprocess/patch.py +++ b/ddtrace/contrib/internal/subprocess/patch.py @@ -21,8 +21,8 @@ from ddtrace.internal import core from ddtrace.internal.forksafe import RLock from ddtrace.internal.logger import get_logger -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/trace_utils.py b/ddtrace/contrib/internal/trace_utils.py index 9f3050cc2f9..70c8fd52b1e 100644 --- a/ddtrace/contrib/internal/trace_utils.py +++ b/ddtrace/contrib/internal/trace_utils.py @@ -38,14 +38,14 @@ from ddtrace.internal.constants import SAMPLING_DECISION_TRACE_TAG_KEY from ddtrace.internal.core.event_hub import dispatch from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config import ddtrace.internal.utils.wrappers from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config if TYPE_CHECKING: # pragma: no cover - from ddtrace.settings.integration import IntegrationConfig # noqa:F401 + from ddtrace.internal.settings.integration import IntegrationConfig # noqa:F401 from ddtrace.trace import Span # noqa:F401 from ddtrace.trace import Tracer # noqa:F401 diff --git a/ddtrace/contrib/internal/trace_utils_base.py b/ddtrace/contrib/internal/trace_utils_base.py index 3d784b37325..af222425693 100644 --- a/ddtrace/contrib/internal/trace_utils_base.py +++ b/ddtrace/contrib/internal/trace_utils_base.py @@ -8,13 +8,13 @@ from ddtrace.ext import user from ddtrace.internal import core from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.utils.cache import cached from ddtrace.internal.utils.http import normalize_header_name from ddtrace.internal.utils.http import redact_url from ddtrace.internal.utils.http import strip_query_string -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config -from ddtrace.settings.integration import IntegrationConfig log = get_logger(__name__) diff --git a/ddtrace/contrib/internal/urllib/patch.py b/ddtrace/contrib/internal/urllib/patch.py index a3a7a0d31f2..6a98e8dc3ad 100644 --- a/ddtrace/contrib/internal/urllib/patch.py +++ b/ddtrace/contrib/internal/urllib/patch.py @@ -4,7 +4,7 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.contrib.internal.trace_utils import unwrap as _u -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config def get_version(): diff --git a/ddtrace/contrib/internal/urllib3/patch.py b/ddtrace/contrib/internal/urllib3/patch.py index fa2de26e876..7e467edc87e 100644 --- a/ddtrace/contrib/internal/urllib3/patch.py +++ b/ddtrace/contrib/internal/urllib3/patch.py @@ -16,12 +16,12 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.wrappers import unwrap as _u from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings.asm import config as asm_config # Ports which, if set, will not be used in hostnames/service names diff --git a/ddtrace/contrib/internal/webbrowser/patch.py b/ddtrace/contrib/internal/webbrowser/patch.py index 973e8934127..4c30af69b6d 100644 --- a/ddtrace/contrib/internal/webbrowser/patch.py +++ b/ddtrace/contrib/internal/webbrowser/patch.py @@ -4,7 +4,7 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.contrib.internal.trace_utils import unwrap as _u -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config def get_version(): diff --git a/ddtrace/contrib/internal/wsgi/wsgi.py b/ddtrace/contrib/internal/wsgi/wsgi.py index 1db5d014594..e977789108a 100644 --- a/ddtrace/contrib/internal/wsgi/wsgi.py +++ b/ddtrace/contrib/internal/wsgi/wsgi.py @@ -12,7 +12,7 @@ from typing import Optional # noqa:F401 from ddtrace._trace.pin import Pin # noqa:F401 - from ddtrace.settings._config import Config # noqa:F401 + from ddtrace.internal.settings._config import Config # noqa:F401 from ddtrace.trace import Span # noqa:F401 from ddtrace.trace import Tracer # noqa:F401 diff --git a/ddtrace/debugging/_config.py b/ddtrace/debugging/_config.py index 02fce853b66..b58ff7d68ea 100644 --- a/ddtrace/debugging/_config.py +++ b/ddtrace/debugging/_config.py @@ -1,6 +1,6 @@ from ddtrace.internal.logger import get_logger -from ddtrace.settings.dynamic_instrumentation import config as di_config # noqa: F401 -from ddtrace.settings.exception_replay import config as er_config # noqa: F401 +from ddtrace.internal.settings.dynamic_instrumentation import config as di_config # noqa: F401 +from ddtrace.internal.settings.exception_replay import config as er_config # noqa: F401 log = get_logger(__name__) diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 585fca6d3d8..414430bee59 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -19,9 +19,9 @@ from ddtrace.internal.packages import is_user_code from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded +from ddtrace.internal.settings._config import config as global_config +from ddtrace.internal.settings.exception_replay import config from ddtrace.internal.utils.time import HourGlass -from ddtrace.settings._config import config as global_config -from ddtrace.settings.exception_replay import config from ddtrace.trace import Span diff --git a/ddtrace/debugging/_origin/span.py b/ddtrace/debugging/_origin/span.py index e11d33a7a9e..ddbbe66842b 100644 --- a/ddtrace/debugging/_origin/span.py +++ b/ddtrace/debugging/_origin/span.py @@ -25,8 +25,8 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.packages import is_user_code from ddtrace.internal.safety import _isinstance +from ddtrace.internal.settings.code_origin import config as co_config from ddtrace.internal.wrapping.context import WrappingContext -from ddtrace.settings.code_origin import config as co_config from ddtrace.trace import Span diff --git a/ddtrace/debugging/_products/code_origin/span.py b/ddtrace/debugging/_products/code_origin/span.py index b05a6ac2e52..e4dad6ac720 100644 --- a/ddtrace/debugging/_products/code_origin/span.py +++ b/ddtrace/debugging/_products/code_origin/span.py @@ -5,8 +5,8 @@ import ddtrace.internal.core as core from ddtrace.internal.logger import get_logger from ddtrace.internal.products import manager as product_manager -from ddtrace.settings._core import ValueSource -from ddtrace.settings.code_origin import config +from ddtrace.internal.settings._core import ValueSource +from ddtrace.internal.settings.code_origin import config log = get_logger(__name__) diff --git a/ddtrace/debugging/_products/dynamic_instrumentation.py b/ddtrace/debugging/_products/dynamic_instrumentation.py index dceb04daa0a..6616eeb6f14 100644 --- a/ddtrace/debugging/_products/dynamic_instrumentation.py +++ b/ddtrace/debugging/_products/dynamic_instrumentation.py @@ -1,6 +1,6 @@ import enum -from ddtrace.settings.dynamic_instrumentation import config +from ddtrace.internal.settings.dynamic_instrumentation import config requires = ["remote-configuration"] diff --git a/ddtrace/debugging/_products/live_debugger.py b/ddtrace/debugging/_products/live_debugger.py index 1417d22d320..f7ab1621daf 100644 --- a/ddtrace/debugging/_products/live_debugger.py +++ b/ddtrace/debugging/_products/live_debugger.py @@ -1,4 +1,4 @@ -from ddtrace.settings.live_debugging import config +from ddtrace.internal.settings.live_debugging import config # TODO[gab]: Uncomment when the product is ready diff --git a/ddtrace/debugging/_redaction.py b/ddtrace/debugging/_redaction.py index d2eeecc160a..b5e6d9876ed 100644 --- a/ddtrace/debugging/_redaction.py +++ b/ddtrace/debugging/_redaction.py @@ -3,9 +3,9 @@ from ddtrace.debugging._expressions import DDCompiler from ddtrace.debugging._expressions import DDExpression from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.dynamic_instrumentation import config +from ddtrace.internal.settings.dynamic_instrumentation import normalize_ident from ddtrace.internal.utils.cache import cached -from ddtrace.settings.dynamic_instrumentation import config -from ddtrace.settings.dynamic_instrumentation import normalize_ident log = get_logger(__name__) diff --git a/ddtrace/errortracking/_handled_exceptions/bytecode_reporting.py b/ddtrace/errortracking/_handled_exceptions/bytecode_reporting.py index 58c9a3e6c20..fe8d0644de1 100644 --- a/ddtrace/errortracking/_handled_exceptions/bytecode_reporting.py +++ b/ddtrace/errortracking/_handled_exceptions/bytecode_reporting.py @@ -11,7 +11,7 @@ from ddtrace.internal.packages import is_stdlib from ddtrace.internal.packages import is_third_party from ddtrace.internal.packages import is_user_code -from ddtrace.settings.errortracking import config +from ddtrace.internal.settings.errortracking import config INSTRUMENTABLE_TYPES = (types.FunctionType, types.MethodType, staticmethod, type) diff --git a/ddtrace/errortracking/_handled_exceptions/collector.py b/ddtrace/errortracking/_handled_exceptions/collector.py index 20e0545c76e..baec2549626 100644 --- a/ddtrace/errortracking/_handled_exceptions/collector.py +++ b/ddtrace/errortracking/_handled_exceptions/collector.py @@ -8,7 +8,7 @@ from ddtrace.internal.constants import SPAN_EVENTS_HAS_EXCEPTION from ddtrace.internal.logger import get_logger from ddtrace.internal.service import Service -from ddtrace.settings.errortracking import config +from ddtrace.internal.settings.errortracking import config log = get_logger(__name__) diff --git a/ddtrace/errortracking/_handled_exceptions/monitoring_reporting.py b/ddtrace/errortracking/_handled_exceptions/monitoring_reporting.py index 5ef94fdb0bb..f12a0c3aaee 100644 --- a/ddtrace/errortracking/_handled_exceptions/monitoring_reporting.py +++ b/ddtrace/errortracking/_handled_exceptions/monitoring_reporting.py @@ -12,7 +12,7 @@ from ddtrace.internal.packages import is_stdlib # noqa: F401 from ddtrace.internal.packages import is_third_party # noqa: F401 from ddtrace.internal.packages import is_user_code # noqa: F401 -from ddtrace.settings.errortracking import config +from ddtrace.internal.settings.errortracking import config INSTRUMENTED_FILE_PATHS = [] diff --git a/ddtrace/errortracking/product.py b/ddtrace/errortracking/product.py index 98b91063145..71f8372b5c5 100644 --- a/ddtrace/errortracking/product.py +++ b/ddtrace/errortracking/product.py @@ -1,7 +1,7 @@ """ This is the entry point for the Error Tracking automatic reporting of handled exception. """ -from ddtrace.settings.errortracking import config +from ddtrace.internal.settings.errortracking import config requires = ["tracer"] diff --git a/ddtrace/internal/_encoding.pyx b/ddtrace/internal/_encoding.pyx index 6b11aaf94d1..63cf4348150 100644 --- a/ddtrace/internal/_encoding.pyx +++ b/ddtrace/internal/_encoding.pyx @@ -24,7 +24,7 @@ from .constants import SPAN_EVENTS_KEY from .constants import MAX_UINT_64BITS from .._trace._limits import MAX_SPAN_META_VALUE_LEN from .._trace._limits import TRUNCATED_SPAN_ATTRIBUTE_LEN -from ..settings._agent import config as agent_config +from ..internal.settings._agent import config as agent_config DEF MSGPACK_ARRAY_LENGTH_PREFIX_SIZE = 5 diff --git a/ddtrace/internal/agent.py b/ddtrace/internal/agent.py index c420fedb611..d2b43078f9f 100644 --- a/ddtrace/internal/agent.py +++ b/ddtrace/internal/agent.py @@ -4,7 +4,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import ForksafeAwakeablePeriodicService -from ddtrace.settings._agent import config +from ddtrace.internal.settings._agent import config from .utils.http import get_connection diff --git a/ddtrace/internal/appsec/product.py b/ddtrace/internal/appsec/product.py index bffa5ccb121..d3ea0035345 100644 --- a/ddtrace/internal/appsec/product.py +++ b/ddtrace/internal/appsec/product.py @@ -1,5 +1,5 @@ -from ddtrace.settings.asm import ai_guard_config -from ddtrace.settings.asm import config +from ddtrace.internal.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import config requires = ["remote-configuration"] diff --git a/ddtrace/internal/ci_visibility/git_client.py b/ddtrace/internal/ci_visibility/git_client.py index 86fb3ae115e..4f38d2183dd 100644 --- a/ddtrace/internal/ci_visibility/git_client.py +++ b/ddtrace/internal/ci_visibility/git_client.py @@ -25,9 +25,9 @@ from ddtrace.ext.git import extract_remote_url from ddtrace.ext.git import extract_workspace_path from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._telemetry import config as telemetry_config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._telemetry import config as telemetry_config from ddtrace.trace import Tracer # noqa: F401 from .. import telemetry diff --git a/ddtrace/internal/ci_visibility/recorder.py b/ddtrace/internal/ci_visibility/recorder.py index a68740fe69a..cf427ef51aa 100644 --- a/ddtrace/internal/ci_visibility/recorder.py +++ b/ddtrace/internal/ci_visibility/recorder.py @@ -67,12 +67,12 @@ from ddtrace.internal.codeowners import Codeowners from ddtrace.internal.logger import get_logger from ddtrace.internal.service import Service +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.internal.test_visibility._atr_mixins import AutoTestRetriesSettings from ddtrace.internal.test_visibility._library_capabilities import LibraryCapabilities from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import Span from ddtrace.trace import TraceFilter from ddtrace.trace import Tracer @@ -156,7 +156,10 @@ class CIVisibility(Service): enabled = False def __init__( - self, tracer: Optional[Tracer] = None, config: Optional[IntegrationConfig] = None, service: Optional[str] = None + self, + tracer: Optional[Tracer] = None, + config: Optional[IntegrationConfig] = None, + service: Optional[str] = None, ) -> None: super().__init__() diff --git a/ddtrace/internal/ci_visibility/writer.py b/ddtrace/internal/ci_visibility/writer.py index 026617c9c04..1ff2ebb2bc9 100644 --- a/ddtrace/internal/ci_visibility/writer.py +++ b/ddtrace/internal/ci_visibility/writer.py @@ -12,8 +12,8 @@ from ddtrace.internal.ci_visibility.constants import MODULE_TYPE from ddtrace.internal.ci_visibility.constants import SESSION_TYPE from ddtrace.internal.ci_visibility.constants import SUITE_TYPE +from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.utils.time import StopWatch -from ddtrace.settings._agent import config as agent_config from ddtrace.vendor.dogstatsd import DogStatsd # noqa:F401 from .. import service diff --git a/ddtrace/internal/core/crashtracking.py b/ddtrace/internal/core/crashtracking.py index 7804d9fc739..180b97274f5 100644 --- a/ddtrace/internal/core/crashtracking.py +++ b/ddtrace/internal/core/crashtracking.py @@ -10,10 +10,10 @@ from ddtrace.internal import forksafe from ddtrace.internal.compat import ensure_text from ddtrace.internal.runtime import get_runtime_id -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.crashtracker import config as crashtracker_config -from ddtrace.settings.profiling import config as profiling_config -from ddtrace.settings.profiling import config_str +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.crashtracker import config as crashtracker_config +from ddtrace.internal.settings.profiling import config as profiling_config +from ddtrace.internal.settings.profiling import config_str is_available = True diff --git a/ddtrace/internal/core/event_hub.py b/ddtrace/internal/core/event_hub.py index 8860f2d6793..5d0176113ea 100644 --- a/ddtrace/internal/core/event_hub.py +++ b/ddtrace/internal/core/event_hub.py @@ -6,7 +6,7 @@ from typing import Optional from typing import Tuple -from ddtrace.settings._config import config +from ddtrace.internal.settings._config import config _listeners: Dict[str, Dict[Any, Callable[..., Any]]] = {} diff --git a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx index efd3a4ab8ce..39944040fb1 100644 --- a/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx +++ b/ddtrace/internal/datadog/profiling/ddup/_ddup.pyx @@ -18,7 +18,7 @@ from ddtrace.internal.datadog.profiling._types import StringType from ddtrace.internal.datadog.profiling.code_provenance import json_str_to_export from ddtrace.internal.datadog.profiling.util import sanitize_string from ddtrace.internal.runtime import get_runtime_id -from ddtrace.settings._agent import config as agent_config +from ddtrace.internal.settings._agent import config as agent_config ctypedef void (*func_ptr_t)(string_view) diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index 3863d962b16..db577fbe9b0 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -19,9 +19,9 @@ from ddtrace.internal.atexit import register_on_exit_signal from ddtrace.internal.constants import DEFAULT_SERVICE_NAME from ddtrace.internal.native import DDSketch +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._config import config from ddtrace.version import get_version from .._encoding import packb diff --git a/ddtrace/internal/debug.py b/ddtrace/internal/debug.py index 1b331cfe7bd..4d174d278a1 100644 --- a/ddtrace/internal/debug.py +++ b/ddtrace/internal/debug.py @@ -10,11 +10,11 @@ import ddtrace from ddtrace.internal.packages import get_distributions +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.cache import callonce from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import LogWriter -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.asm import config as asm_config from .logger import get_logger @@ -54,7 +54,7 @@ def collect(tracer): # Inline expensive imports to avoid unnecessary overhead on startup. from ddtrace.internal import gitmetadata from ddtrace.internal.runtime.runtime_metrics import RuntimeWorker - from ddtrace.settings.crashtracker import config as crashtracker_config + from ddtrace.internal.settings.crashtracker import config as crashtracker_config if isinstance(tracer._span_aggregator.writer, LogWriter): agent_url = "AGENTLESS" diff --git a/ddtrace/internal/encoding.py b/ddtrace/internal/encoding.py index 24578cf5d1b..a5c7f3a100e 100644 --- a/ddtrace/internal/encoding.py +++ b/ddtrace/internal/encoding.py @@ -6,7 +6,8 @@ from typing import Optional # noqa:F401 from typing import Tuple # noqa:F401 -from ..settings._agent import config as agent_config # noqa:F401 +from ddtrace.internal.settings._agent import config as agent_config # noqa:F401 + from ._encoding import ListStringTable from ._encoding import MsgpackEncoderV04 from ._encoding import MsgpackEncoderV05 diff --git a/ddtrace/internal/gitmetadata.py b/ddtrace/internal/gitmetadata.py index 58d5fc1d0f9..2bfa21e2ac2 100644 --- a/ddtrace/internal/gitmetadata.py +++ b/ddtrace/internal/gitmetadata.py @@ -5,8 +5,8 @@ from ddtrace.ext.git import MAIN_PACKAGE from ddtrace.ext.git import REPOSITORY_URL from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils import formats -from ddtrace.settings._core import DDConfig _GITMETADATA_TAGS = None # type: typing.Optional[typing.Tuple[str, str, str]] diff --git a/ddtrace/internal/iast/product.py b/ddtrace/internal/iast/product.py index c52892f1804..eb2d4b4cd8f 100644 --- a/ddtrace/internal/iast/product.py +++ b/ddtrace/internal/iast/product.py @@ -27,7 +27,7 @@ import sys from ddtrace.internal.logger import get_logger -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config log = get_logger(__name__) diff --git a/ddtrace/internal/logger.py b/ddtrace/internal/logger.py index b5a5057a0b2..a37a84b5744 100644 --- a/ddtrace/internal/logger.py +++ b/ddtrace/internal/logger.py @@ -221,3 +221,26 @@ def format_stack(stack_info, limit) -> str: return stack_info stack_str = "\n".join(stack[-2 * limit :]) return f"{stack[0]}\n{stack_str}" + + +class LogInjectionState(object): + # Log injection is disabled + DISABLED = "false" + # Log injection is enabled, but not yet configured + ENABLED = "true" + # Log injection is enabled and configured for structured logging + # This value is deprecated, but kept for backwards compatibility + STRUCTURED = "structured" + + +def get_log_injection_state(raw_config: Optional[str]) -> bool: + if raw_config: + normalized = raw_config.lower().strip() + if normalized == LogInjectionState.STRUCTURED or normalized in ("true", "1"): + return True + elif normalized not in ("false", "0"): + logging.warning( + "Invalid log injection state '%s'. Expected 'true', 'false', or 'structured'. Defaulting to 'false'.", + normalized, + ) + return False diff --git a/ddtrace/internal/metrics.py b/ddtrace/internal/metrics.py index abae8ddabd9..34b9381c486 100644 --- a/ddtrace/internal/metrics.py +++ b/ddtrace/internal/metrics.py @@ -2,7 +2,7 @@ from typing import Optional # noqa:F401 from ddtrace.internal.dogstatsd import get_dogstatsd_client -from ddtrace.settings._agent import config as agent_config +from ddtrace.internal.settings._agent import config as agent_config class Metrics(object): diff --git a/ddtrace/internal/opentelemetry/logs.py b/ddtrace/internal/opentelemetry/logs.py index d25df952f2e..a3175abeaaa 100644 --- a/ddtrace/internal/opentelemetry/logs.py +++ b/ddtrace/internal/opentelemetry/logs.py @@ -8,9 +8,9 @@ from ddtrace import config from ddtrace.internal.hostname import get_hostname from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._opentelemetry import otel_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.settings._opentelemetry import otel_config log = get_logger(__name__) diff --git a/ddtrace/internal/opentelemetry/metrics.py b/ddtrace/internal/opentelemetry/metrics.py index 39708db4e50..db59d13ca91 100644 --- a/ddtrace/internal/opentelemetry/metrics.py +++ b/ddtrace/internal/opentelemetry/metrics.py @@ -8,9 +8,9 @@ from ddtrace import config from ddtrace.internal.hostname import get_hostname from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._opentelemetry import otel_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.settings._opentelemetry import otel_config log = get_logger(__name__) diff --git a/ddtrace/internal/packages.py b/ddtrace/internal/packages.py index d6fe6f16fae..781d2e8216c 100644 --- a/ddtrace/internal/packages.py +++ b/ddtrace/internal/packages.py @@ -10,8 +10,8 @@ from ddtrace.internal.compat import Path from ddtrace.internal.module import origin +from ddtrace.internal.settings.third_party import config as tp_config from ddtrace.internal.utils.cache import callonce -from ddtrace.settings.third_party import config as tp_config LOG = logging.getLogger(__name__) diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index efd8492769b..ea2227aee1b 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -12,8 +12,8 @@ from ddtrace._trace.span import Span from ddtrace.internal import compat from ddtrace.internal.native import DDSketch +from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.settings._config import config from ddtrace.version import get_version from ...constants import _SPAN_MEASURED_KEY diff --git a/ddtrace/internal/products.py b/ddtrace/internal/products.py index 629e746c46d..e9992410cba 100644 --- a/ddtrace/internal/products.py +++ b/ddtrace/internal/products.py @@ -9,13 +9,13 @@ from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.telemetry import report_configuration from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.uwsgi import check_uwsgi from ddtrace.internal.uwsgi import uWSGIConfigDeprecationWarning from ddtrace.internal.uwsgi import uWSGIConfigError from ddtrace.internal.uwsgi import uWSGIMasterProcess -from ddtrace.settings._core import DDConfig log = get_logger(__name__) diff --git a/ddtrace/internal/remoteconfig/client.py b/ddtrace/internal/remoteconfig/client.py index 608261f5cf0..efa73f65d9f 100644 --- a/ddtrace/internal/remoteconfig/client.py +++ b/ddtrace/internal/remoteconfig/client.py @@ -30,10 +30,10 @@ from ddtrace.internal.remoteconfig._pubsub import PubSub from ddtrace.internal.remoteconfig.constants import REMOTE_CONFIG_AGENT_ENDPOINT from ddtrace.internal.service import ServiceStatus +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.internal.utils.version import _pep440_to_semver -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._core import DDConfig log = get_logger(__name__) diff --git a/ddtrace/internal/remoteconfig/products/client.py b/ddtrace/internal/remoteconfig/products/client.py index ad2bffc0e6e..6309e12814f 100644 --- a/ddtrace/internal/remoteconfig/products/client.py +++ b/ddtrace/internal/remoteconfig/products/client.py @@ -1,6 +1,6 @@ from ddtrace import config from ddtrace.internal.remoteconfig.client import config as rc_config -from ddtrace.settings._agent import config as agent_config +from ddtrace.internal.settings._agent import config as agent_config # TODO: Modularize better into their own respective components diff --git a/ddtrace/internal/runtime/runtime_metrics.py b/ddtrace/internal/runtime/runtime_metrics.py index c541b333748..1059937363f 100644 --- a/ddtrace/internal/runtime/runtime_metrics.py +++ b/ddtrace/internal/runtime/runtime_metrics.py @@ -78,7 +78,7 @@ def __init__(self, interval=DEFAULT_RUNTIME_METRICS_INTERVAL, tracer=None, dogst super().__init__(interval=interval) self.dogstatsd_url: Optional[str] = dogstatsd_url self._dogstatsd_client: DogStatsd = get_dogstatsd_client( - self.dogstatsd_url or ddtrace.settings._agent.config.dogstatsd_url + self.dogstatsd_url or ddtrace.internal.settings._agent.config.dogstatsd_url ) self.tracer: ddtrace.trace.Tracer = tracer or ddtrace.tracer self._runtime_metrics: RuntimeMetrics = RuntimeMetrics() diff --git a/ddtrace/internal/sampling.py b/ddtrace/internal/sampling.py index eb1129ae26e..1c5624a7385 100644 --- a/ddtrace/internal/sampling.py +++ b/ddtrace/internal/sampling.py @@ -26,7 +26,7 @@ from ddtrace.internal.constants import SamplingMechanism from ddtrace.internal.glob_matching import GlobMatcher from ddtrace.internal.logger import get_logger -from ddtrace.settings._config import config +from ddtrace.internal.settings._config import config from .rate_limiter import RateLimiter diff --git a/ddtrace/internal/schema/processor.py b/ddtrace/internal/schema/processor.py index 9c6ea16b7df..2061f10d271 100644 --- a/ddtrace/internal/schema/processor.py +++ b/ddtrace/internal/schema/processor.py @@ -1,7 +1,7 @@ from ddtrace._trace.processor import TraceProcessor from ddtrace.constants import _BASE_SERVICE_KEY from ddtrace.internal.serverless import in_aws_lambda -from ddtrace.settings._config import config +from ddtrace.internal.settings._config import config from . import schematize_service_name diff --git a/ddtrace/internal/schema/span_attribute_schema.py b/ddtrace/internal/schema/span_attribute_schema.py index 1ebd95c0527..33b8e4eb19d 100644 --- a/ddtrace/internal/schema/span_attribute_schema.py +++ b/ddtrace/internal/schema/span_attribute_schema.py @@ -4,7 +4,7 @@ from typing import Optional from ddtrace.internal.constants import DEFAULT_SERVICE_NAME -from ddtrace.settings._inferred_base_service import detect_service +from ddtrace.internal.settings._inferred_base_service import detect_service class SpanDirection(Enum): diff --git a/ddtrace/settings/__init__.py b/ddtrace/internal/settings/__init__.py similarity index 88% rename from ddtrace/settings/__init__.py rename to ddtrace/internal/settings/__init__.py index 01e10b33296..f6c850d6a3b 100644 --- a/ddtrace/settings/__init__.py +++ b/ddtrace/internal/settings/__init__.py @@ -1,6 +1,5 @@ from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning - -from ..vendor.debtcollector import deprecate +from ddtrace.vendor.debtcollector import deprecate def __getattr__(name): @@ -18,7 +17,7 @@ def __getattr__(name): category=DDTraceDeprecationWarning, ) if name == "ConfigException": - from ddtrace.settings.exceptions import ConfigException + from ddtrace.internal.settings.exceptions import ConfigException return ConfigException elif name == "HttpConfig": diff --git a/ddtrace/settings/_agent.py b/ddtrace/internal/settings/_agent.py similarity index 98% rename from ddtrace/settings/_agent.py rename to ddtrace/internal/settings/_agent.py index f2c44b5d678..6ea154aeef5 100644 --- a/ddtrace/settings/_agent.py +++ b/ddtrace/internal/settings/_agent.py @@ -6,7 +6,7 @@ from urllib.parse import urlparse from ddtrace.internal.constants import DEFAULT_TIMEOUT -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig DEFAULT_HOSTNAME = "localhost" diff --git a/ddtrace/settings/_config.py b/ddtrace/internal/settings/_config.py similarity index 96% rename from ddtrace/settings/_config.py rename to ddtrace/internal/settings/_config.py index aabd1a1a4cb..13d6ce6aee1 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/internal/settings/_config.py @@ -11,32 +11,32 @@ from typing import Tuple # noqa:F401 from typing import Union # noqa:F401 +from ddtrace.internal import gitmetadata +from ddtrace.internal.constants import _PROPAGATION_BEHAVIOR_DEFAULT +from ddtrace.internal.constants import _PROPAGATION_BEHAVIOR_IGNORE +from ddtrace.internal.constants import _PROPAGATION_STYLE_DEFAULT +from ddtrace.internal.constants import _PROPAGATION_STYLE_NONE +from ddtrace.internal.constants import DEFAULT_BUFFER_SIZE +from ddtrace.internal.constants import DEFAULT_MAX_PAYLOAD_SIZE +from ddtrace.internal.constants import DEFAULT_PROCESSING_INTERVAL +from ddtrace.internal.constants import DEFAULT_REUSE_CONNECTIONS +from ddtrace.internal.constants import DEFAULT_SAMPLING_RATE_LIMIT +from ddtrace.internal.constants import DEFAULT_TIMEOUT +from ddtrace.internal.constants import PROPAGATION_STYLE_ALL +from ddtrace.internal.logger import get_log_injection_state +from ddtrace.internal.logger import get_logger +from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME +from ddtrace.internal.serverless import in_aws_lambda from ddtrace.internal.serverless import in_azure_function from ddtrace.internal.serverless import in_gcp_function +from ddtrace.internal.telemetry import get_config as _get_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry import validate_and_report_otel_metrics_exporter_enabled from ddtrace.internal.telemetry import validate_otel_envs from ddtrace.internal.utils.cache import cachedmethod +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.formats import parse_tags_str -from .._logger import get_log_injection_state -from ..internal import gitmetadata -from ..internal.constants import _PROPAGATION_BEHAVIOR_DEFAULT -from ..internal.constants import _PROPAGATION_BEHAVIOR_IGNORE -from ..internal.constants import _PROPAGATION_STYLE_DEFAULT -from ..internal.constants import _PROPAGATION_STYLE_NONE -from ..internal.constants import DEFAULT_BUFFER_SIZE -from ..internal.constants import DEFAULT_MAX_PAYLOAD_SIZE -from ..internal.constants import DEFAULT_PROCESSING_INTERVAL -from ..internal.constants import DEFAULT_REUSE_CONNECTIONS -from ..internal.constants import DEFAULT_SAMPLING_RATE_LIMIT -from ..internal.constants import DEFAULT_TIMEOUT -from ..internal.constants import PROPAGATION_STYLE_ALL -from ..internal.logger import get_logger -from ..internal.schema import DEFAULT_SPAN_SERVICE_NAME -from ..internal.serverless import in_aws_lambda -from ..internal.telemetry import get_config as _get_config -from ..internal.utils.formats import asbool -from ..internal.utils.formats import parse_tags_str from ._inferred_base_service import detect_service from .endpoint_config import fetch_config_from_endpoint from .http import HttpConfig diff --git a/ddtrace/settings/_core.py b/ddtrace/internal/settings/_core.py similarity index 100% rename from ddtrace/settings/_core.py rename to ddtrace/internal/settings/_core.py diff --git a/ddtrace/settings/_database_monitoring.py b/ddtrace/internal/settings/_database_monitoring.py similarity index 88% rename from ddtrace/settings/_database_monitoring.py rename to ddtrace/internal/settings/_database_monitoring.py index 424d4f21028..67e47eda76c 100644 --- a/ddtrace/settings/_database_monitoring.py +++ b/ddtrace/internal/settings/_database_monitoring.py @@ -1,6 +1,6 @@ from envier import validators -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class DatabaseMonitoringConfig(DDConfig): diff --git a/ddtrace/settings/_inferred_base_service.py b/ddtrace/internal/settings/_inferred_base_service.py similarity index 99% rename from ddtrace/settings/_inferred_base_service.py rename to ddtrace/internal/settings/_inferred_base_service.py index e0a89e8a3bb..cf592521779 100644 --- a/ddtrace/settings/_inferred_base_service.py +++ b/ddtrace/internal/settings/_inferred_base_service.py @@ -9,7 +9,7 @@ from typing import Optional from typing import Tuple -from ..internal.logger import get_logger +from ddtrace.internal.logger import get_logger log = get_logger(__name__) diff --git a/ddtrace/settings/_opentelemetry.py b/ddtrace/internal/settings/_opentelemetry.py similarity index 97% rename from ddtrace/settings/_opentelemetry.py rename to ddtrace/internal/settings/_opentelemetry.py index 2c2f0be453e..ebe75553669 100644 --- a/ddtrace/settings/_opentelemetry.py +++ b/ddtrace/internal/settings/_opentelemetry.py @@ -1,9 +1,9 @@ import typing as t +from ddtrace.internal.settings._agent import get_agent_hostname +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.telemetry import get_config from ddtrace.internal.telemetry import report_configuration -from ddtrace.settings._agent import get_agent_hostname -from ddtrace.settings._core import DDConfig def _derive_endpoint(config: "ExporterConfig"): diff --git a/ddtrace/settings/_otel_remapper.py b/ddtrace/internal/settings/_otel_remapper.py similarity index 97% rename from ddtrace/settings/_otel_remapper.py rename to ddtrace/internal/settings/_otel_remapper.py index b0841f31d2f..d8558f38220 100644 --- a/ddtrace/settings/_otel_remapper.py +++ b/ddtrace/internal/settings/_otel_remapper.py @@ -5,9 +5,9 @@ from typing import Optional from typing import Tuple -from ..constants import ENV_KEY -from ..constants import VERSION_KEY -from ..internal.logger import get_logger +from ddtrace.constants import ENV_KEY +from ddtrace.constants import VERSION_KEY +from ddtrace.internal.logger import get_logger log = get_logger(__name__) diff --git a/ddtrace/settings/_telemetry.py b/ddtrace/internal/settings/_telemetry.py similarity index 91% rename from ddtrace/settings/_telemetry.py rename to ddtrace/internal/settings/_telemetry.py index 59314854288..42f8411305d 100644 --- a/ddtrace/settings/_telemetry.py +++ b/ddtrace/internal/settings/_telemetry.py @@ -1,8 +1,8 @@ import sys import typing as t -from ddtrace.settings._core import DDConfig -from ddtrace.settings._inferred_base_service import detect_service +from ddtrace.internal.settings._core import DDConfig +from ddtrace.internal.settings._inferred_base_service import detect_service class TelemetryConfig(DDConfig): diff --git a/ddtrace/settings/asm.py b/ddtrace/internal/settings/asm.py similarity index 98% rename from ddtrace/settings/asm.py rename to ddtrace/internal/settings/asm.py index 896ce8c6f67..eb419f2526d 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/internal/settings/asm.py @@ -21,8 +21,8 @@ from ddtrace.internal.constants import AI_GUARD_MAX_HISTORY_LENGTH from ddtrace.internal.constants import DD_APPLICATION_KEY from ddtrace.internal.serverless import in_aws_lambda -from ddtrace.settings._config import config as tracer_config -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._config import config as tracer_config +from ddtrace.internal.settings._core import DDConfig def _validate_non_negative_int(r: int) -> None: @@ -60,7 +60,9 @@ def build_libddwaf_filename() -> str: ARCHI = "x86" TRANSLATE_ARCH = {"amd64": "x64", "i686": "x86_64", "x86": "win32"} ARCHITECTURE = TRANSLATE_ARCH.get(ARCHI, ARCHI) - return os.path.join(_DIRNAME, "appsec", "_ddwaf", "libddwaf", ARCHITECTURE, "lib", "libddwaf." + FILE_EXTENSION) + return os.path.join( + _DIRNAME, "..", "appsec", "_ddwaf", "libddwaf", ARCHITECTURE, "lib", "libddwaf." + FILE_EXTENSION + ) class ASMConfig(DDConfig): diff --git a/ddtrace/settings/code_origin.py b/ddtrace/internal/settings/code_origin.py similarity index 92% rename from ddtrace/settings/code_origin.py rename to ddtrace/internal/settings/code_origin.py index 8ab313945a3..ada1cd7ff93 100644 --- a/ddtrace/settings/code_origin.py +++ b/ddtrace/internal/settings/code_origin.py @@ -1,4 +1,4 @@ -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class CodeOriginConfig(DDConfig): diff --git a/ddtrace/settings/crashtracker.py b/ddtrace/internal/settings/crashtracker.py similarity index 98% rename from ddtrace/settings/crashtracker.py rename to ddtrace/internal/settings/crashtracker.py index d102fd7a54c..f58c230daa4 100644 --- a/ddtrace/settings/crashtracker.py +++ b/ddtrace/internal/settings/crashtracker.py @@ -1,8 +1,8 @@ import typing as t +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.telemetry import report_configuration from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.settings._core import DDConfig resolver_default = "full" diff --git a/ddtrace/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py similarity index 97% rename from ddtrace/settings/dynamic_instrumentation.py rename to ddtrace/internal/settings/dynamic_instrumentation.py index d08781e1ef5..d5eaff1bf1b 100644 --- a/ddtrace/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -5,9 +5,9 @@ from ddtrace.internal import gitmetadata from ddtrace.internal.compat import Path from ddtrace.internal.constants import DEFAULT_SERVICE_NAME +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils.config import get_application_name -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._core import DDConfig from ddtrace.version import get_version diff --git a/ddtrace/settings/endpoint_config.py b/ddtrace/internal/settings/endpoint_config.py similarity index 100% rename from ddtrace/settings/endpoint_config.py rename to ddtrace/internal/settings/endpoint_config.py diff --git a/ddtrace/settings/errortracking.py b/ddtrace/internal/settings/errortracking.py similarity index 97% rename from ddtrace/settings/errortracking.py rename to ddtrace/internal/settings/errortracking.py index c3b758bdb27..f590770c8e6 100644 --- a/ddtrace/settings/errortracking.py +++ b/ddtrace/internal/settings/errortracking.py @@ -1,7 +1,7 @@ import sys import typing as t -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig def parse_modules(value: t.Union[str, None]) -> t.List[str]: diff --git a/ddtrace/settings/exception_replay.py b/ddtrace/internal/settings/exception_replay.py similarity index 91% rename from ddtrace/settings/exception_replay.py rename to ddtrace/internal/settings/exception_replay.py index 84089124959..9dc72b4d51d 100644 --- a/ddtrace/settings/exception_replay.py +++ b/ddtrace/internal/settings/exception_replay.py @@ -1,4 +1,4 @@ -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class ExceptionReplayConfig(DDConfig): diff --git a/ddtrace/settings/http.py b/ddtrace/internal/settings/http.py similarity index 94% rename from ddtrace/settings/http.py rename to ddtrace/internal/settings/http.py index 4e408faddae..dec9ceb3671 100644 --- a/ddtrace/settings/http.py +++ b/ddtrace/internal/settings/http.py @@ -3,9 +3,9 @@ from typing import Optional # noqa:F401 from typing import Union # noqa:F401 -from ..internal.logger import get_logger -from ..internal.utils.cache import cachedmethod -from ..internal.utils.http import normalize_header_name +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.cache import cachedmethod +from ddtrace.internal.utils.http import normalize_header_name log = get_logger(__name__) diff --git a/ddtrace/settings/integration.py b/ddtrace/internal/settings/integration.py similarity index 97% rename from ddtrace/settings/integration.py rename to ddtrace/internal/settings/integration.py index 6bb9b1a16cc..6cea1c33c75 100644 --- a/ddtrace/settings/integration.py +++ b/ddtrace/internal/settings/integration.py @@ -1,8 +1,9 @@ import os from typing import Optional # noqa:F401 -from .._hooks import Hooks -from ..internal.utils.attrdict import AttrDict +from ddtrace._hooks import Hooks +from ddtrace.internal.utils.attrdict import AttrDict + from .http import HttpConfig diff --git a/ddtrace/settings/live_debugging.py b/ddtrace/internal/settings/live_debugging.py similarity index 83% rename from ddtrace/settings/live_debugging.py rename to ddtrace/internal/settings/live_debugging.py index 41a638ace8b..e316519d5f9 100644 --- a/ddtrace/settings/live_debugging.py +++ b/ddtrace/internal/settings/live_debugging.py @@ -1,4 +1,4 @@ -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class LiveDebuggerConfig(DDConfig): diff --git a/ddtrace/settings/peer_service.py b/ddtrace/internal/settings/peer_service.py similarity index 100% rename from ddtrace/settings/peer_service.py rename to ddtrace/internal/settings/peer_service.py diff --git a/ddtrace/settings/profiling.py b/ddtrace/internal/settings/profiling.py similarity index 99% rename from ddtrace/settings/profiling.py rename to ddtrace/internal/settings/profiling.py index fd3ccc9b41e..fab25cd7a8e 100644 --- a/ddtrace/settings/profiling.py +++ b/ddtrace/internal/settings/profiling.py @@ -10,11 +10,11 @@ from ddtrace.internal import compat from ddtrace.internal import gitmetadata from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.telemetry import report_configuration from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.settings._core import DDConfig logger = get_logger(__name__) diff --git a/ddtrace/settings/symbol_db.py b/ddtrace/internal/settings/symbol_db.py similarity index 94% rename from ddtrace/settings/symbol_db.py rename to ddtrace/internal/settings/symbol_db.py index a5e21b77262..0f1019f4421 100644 --- a/ddtrace/settings/symbol_db.py +++ b/ddtrace/internal/settings/symbol_db.py @@ -1,6 +1,6 @@ import re -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class SymbolDatabaseConfig(DDConfig): diff --git a/ddtrace/settings/third_party.py b/ddtrace/internal/settings/third_party.py similarity index 90% rename from ddtrace/settings/third_party.py rename to ddtrace/internal/settings/third_party.py index fca1a4621e4..8e55da69d19 100644 --- a/ddtrace/settings/third_party.py +++ b/ddtrace/internal/settings/third_party.py @@ -1,4 +1,4 @@ -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class ThirdPartyDetectionConfig(DDConfig): diff --git a/ddtrace/internal/symbol_db/__init__.py b/ddtrace/internal/symbol_db/__init__.py index 80013860369..1ef8d175770 100644 --- a/ddtrace/internal/symbol_db/__init__.py +++ b/ddtrace/internal/symbol_db/__init__.py @@ -2,8 +2,8 @@ from ddtrace.internal import core from ddtrace.internal.remoteconfig.worker import remoteconfig_poller +from ddtrace.internal.settings.symbol_db import config as symdb_config from ddtrace.internal.symbol_db.remoteconfig import SymbolDatabaseAdapter -from ddtrace.settings.symbol_db import config as symdb_config def bootstrap(): diff --git a/ddtrace/internal/symbol_db/product.py b/ddtrace/internal/symbol_db/product.py index c6c165e9577..bbc493d63e6 100644 --- a/ddtrace/internal/symbol_db/product.py +++ b/ddtrace/internal/symbol_db/product.py @@ -1,4 +1,4 @@ -from ddtrace.settings.symbol_db import config +from ddtrace.internal.settings.symbol_db import config requires = ["remote-configuration"] diff --git a/ddtrace/internal/symbol_db/symbols.py b/ddtrace/internal/symbol_db/symbols.py index 9842d57eac9..f3d146b3871 100644 --- a/ddtrace/internal/symbol_db/symbols.py +++ b/ddtrace/internal/symbol_db/symbols.py @@ -33,6 +33,8 @@ from ddtrace.internal.module import origin from ddtrace.internal.runtime import get_runtime_id from ddtrace.internal.safety import _isinstance +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.symbol_db import config as symdb_config from ddtrace.internal.utils.cache import cached from ddtrace.internal.utils.http import FormData from ddtrace.internal.utils.http import connector @@ -40,8 +42,6 @@ from ddtrace.internal.utils.inspection import linenos from ddtrace.internal.utils.inspection import resolved_code_origin from ddtrace.internal.utils.inspection import undecorated -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.symbol_db import config as symdb_config log = get_logger(__name__) diff --git a/ddtrace/internal/telemetry/__init__.py b/ddtrace/internal/telemetry/__init__.py index 8af349fe0fd..0a523c8752d 100644 --- a/ddtrace/internal/telemetry/__init__.py +++ b/ddtrace/internal/telemetry/__init__.py @@ -8,16 +8,16 @@ import typing as t from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._core import FLEET_CONFIG +from ddtrace.internal.settings._core import FLEET_CONFIG_IDS +from ddtrace.internal.settings._core import LOCAL_CONFIG +from ddtrace.internal.settings._core import DDConfig +from ddtrace.internal.settings._otel_remapper import ENV_VAR_MAPPINGS +from ddtrace.internal.settings._otel_remapper import SUPPORTED_OTEL_ENV_VARS +from ddtrace.internal.settings._otel_remapper import parse_otel_env from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE from ddtrace.internal.utils.formats import asbool -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._core import FLEET_CONFIG -from ddtrace.settings._core import FLEET_CONFIG_IDS -from ddtrace.settings._core import LOCAL_CONFIG -from ddtrace.settings._core import DDConfig -from ddtrace.settings._otel_remapper import ENV_VAR_MAPPINGS -from ddtrace.settings._otel_remapper import SUPPORTED_OTEL_ENV_VARS -from ddtrace.settings._otel_remapper import parse_otel_env log = get_logger(__name__) diff --git a/ddtrace/internal/telemetry/writer.py b/ddtrace/internal/telemetry/writer.py index 441c09f096e..eb98f880038 100644 --- a/ddtrace/internal/telemetry/writer.py +++ b/ddtrace/internal/telemetry/writer.py @@ -17,9 +17,9 @@ from ddtrace.internal.endpoints import endpoint_collection from ddtrace.internal.logger import get_logger from ddtrace.internal.packages import is_user_code +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._telemetry import config from ddtrace.internal.utils.http import get_connection -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._telemetry import config from ...internal import atexit from ...internal import forksafe @@ -338,7 +338,7 @@ def _report_dependencies(self) -> Optional[List[Dict[str, Any]]]: def _report_endpoints(self) -> Optional[Dict[str, Any]]: """Adds a Telemetry event which sends the list of HTTP endpoints found at startup to the agent""" - import ddtrace.settings.asm as asm_config_module + import ddtrace.internal.settings.asm as asm_config_module if not asm_config_module.config._api_security_endpoint_collection or not self._enabled: return None diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index 698d056bde5..15e60e7ba62 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -19,11 +19,11 @@ from ddtrace.internal.hostname import get_hostname import ddtrace.internal.native as native from ddtrace.internal.runtime import get_runtime_id +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import config as asm_config import ddtrace.internal.utils.http from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.asm import ai_guard_config -from ddtrace.settings.asm import config as asm_config from ...constants import _KEEP_SPANS_RATE_KEY from .. import compat diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py index 0b0fc312afc..6e129cd38a3 100644 --- a/ddtrace/llmobs/_integrations/base.py +++ b/ddtrace/llmobs/_integrations/base.py @@ -11,10 +11,10 @@ from ddtrace.contrib.internal.trace_utils import int_service from ddtrace.ext import SpanTypes from ddtrace.internal.logger import get_logger +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.llmobs._constants import INTEGRATION from ddtrace.llmobs._constants import PROXY_REQUEST from ddtrace.llmobs._llmobs import LLMObs -from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import Span diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 1f70c0c3bae..a812360c2c6 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -21,6 +21,7 @@ from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import PeriodicService +from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.utils.http import Response from ddtrace.internal.utils.http import get_connection from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter @@ -48,7 +49,6 @@ from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.types import _Meta from ddtrace.llmobs.types import _SpanLink -from ddtrace.settings._agent import config as agent_config logger = get_logger(__name__) diff --git a/ddtrace/profiling/_asyncio.py b/ddtrace/profiling/_asyncio.py index 32f85b7be1f..b7e3950e214 100644 --- a/ddtrace/profiling/_asyncio.py +++ b/ddtrace/profiling/_asyncio.py @@ -11,9 +11,9 @@ from ddtrace.internal._unpatched import _threading as ddtrace_threading from ddtrace.internal.datadog.profiling import stack_v2 from ddtrace.internal.module import ModuleWatchdog +from ddtrace.internal.settings.profiling import config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.wrapping import wrap -from ddtrace.settings.profiling import config from . import _threading diff --git a/ddtrace/profiling/collector/__init__.py b/ddtrace/profiling/collector/__init__.py index 4b066483460..d3d6a6b8fa1 100644 --- a/ddtrace/profiling/collector/__init__.py +++ b/ddtrace/profiling/collector/__init__.py @@ -3,7 +3,7 @@ from ddtrace.internal import periodic from ddtrace.internal import service -from ddtrace.settings.profiling import config +from ddtrace.internal.settings.profiling import config class CollectorError(Exception): diff --git a/ddtrace/profiling/collector/_lock.py b/ddtrace/profiling/collector/_lock.py index 6e3e2ddfd7e..2da619f6bc5 100644 --- a/ddtrace/profiling/collector/_lock.py +++ b/ddtrace/profiling/collector/_lock.py @@ -20,12 +20,12 @@ import wrapt from ddtrace.internal.datadog.profiling import ddup +from ddtrace.internal.settings.profiling import config from ddtrace.profiling import _threading from ddtrace.profiling import collector from ddtrace.profiling.collector import _task from ddtrace.profiling.collector import _traceback from ddtrace.profiling.event import DDFrame -from ddtrace.settings.profiling import config from ddtrace.trace import Tracer diff --git a/ddtrace/profiling/collector/_task.pyx b/ddtrace/profiling/collector/_task.pyx index b7939d908d8..b688d9c6bd6 100644 --- a/ddtrace/profiling/collector/_task.pyx +++ b/ddtrace/profiling/collector/_task.pyx @@ -6,7 +6,7 @@ from wrapt.importer import when_imported from .. import _asyncio from .. import _threading -from ddtrace.settings.profiling import config +from ddtrace.internal.settings.profiling import config if (is_stack_v2 := config.stack.v2_enabled): diff --git a/ddtrace/profiling/collector/memalloc.py b/ddtrace/profiling/collector/memalloc.py index 7cef93806d7..04b53fc7cf2 100644 --- a/ddtrace/profiling/collector/memalloc.py +++ b/ddtrace/profiling/collector/memalloc.py @@ -21,9 +21,9 @@ _memalloc = None # type: ignore[assignment] from ddtrace.internal.datadog.profiling import ddup +from ddtrace.internal.settings.profiling import config from ddtrace.profiling import _threading from ddtrace.profiling import collector -from ddtrace.settings.profiling import config LOG = logging.getLogger(__name__) diff --git a/ddtrace/profiling/collector/pytorch.py b/ddtrace/profiling/collector/pytorch.py index 731c92ebb24..34d8736882e 100644 --- a/ddtrace/profiling/collector/pytorch.py +++ b/ddtrace/profiling/collector/pytorch.py @@ -8,9 +8,9 @@ import wrapt from ddtrace.internal.datadog.profiling import ddup +from ddtrace.internal.settings.profiling import config from ddtrace.profiling import _threading from ddtrace.profiling import collector -from ddtrace.settings.profiling import config from ddtrace.trace import Tracer diff --git a/ddtrace/profiling/collector/stack.pyx b/ddtrace/profiling/collector/stack.pyx index 05b62469911..b123d4055a5 100644 --- a/ddtrace/profiling/collector/stack.pyx +++ b/ddtrace/profiling/collector/stack.pyx @@ -15,12 +15,12 @@ from ddtrace.internal import core from ddtrace.internal._threads import periodic_threads from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.datadog.profiling import stack_v2 +from ddtrace.internal.settings.profiling import config from ddtrace.profiling import _threading from ddtrace.profiling import collector from ddtrace.profiling.collector import _task from ddtrace.profiling.collector import _traceback from ddtrace.profiling.collector import threading -from ddtrace.settings.profiling import config LOG = logging.getLogger(__name__) diff --git a/ddtrace/profiling/collector/threading.py b/ddtrace/profiling/collector/threading.py index 2f136870d23..5fd530f9a49 100644 --- a/ddtrace/profiling/collector/threading.py +++ b/ddtrace/profiling/collector/threading.py @@ -5,7 +5,7 @@ from ddtrace.internal._unpatched import _threading as ddtrace_threading from ddtrace.internal.datadog.profiling import stack_v2 -from ddtrace.settings.profiling import config +from ddtrace.internal.settings.profiling import config from . import _lock diff --git a/ddtrace/profiling/profiler.py b/ddtrace/profiling/profiler.py index b4ac6c3e79c..68f4fb372f7 100644 --- a/ddtrace/profiling/profiler.py +++ b/ddtrace/profiling/profiler.py @@ -16,6 +16,8 @@ from ddtrace.internal import uwsgi from ddtrace.internal.datadog.profiling import ddup from ddtrace.internal.module import ModuleWatchdog +from ddtrace.internal.settings.profiling import config as profiling_config +from ddtrace.internal.settings.profiling import config_str from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.profiling import collector @@ -25,8 +27,6 @@ from ddtrace.profiling.collector import pytorch from ddtrace.profiling.collector import stack from ddtrace.profiling.collector import threading -from ddtrace.settings.profiling import config as profiling_config -from ddtrace.settings.profiling import config_str # TODO(vlad): add type annotations diff --git a/ddtrace/profiling/scheduler.py b/ddtrace/profiling/scheduler.py index 228b6cc7675..35af121e487 100644 --- a/ddtrace/profiling/scheduler.py +++ b/ddtrace/profiling/scheduler.py @@ -8,7 +8,7 @@ import ddtrace from ddtrace.internal import periodic from ddtrace.internal.datadog.profiling import ddup -from ddtrace.settings.profiling import config +from ddtrace.internal.settings.profiling import config from ddtrace.trace import Tracer diff --git a/ddtrace/propagation/_database_monitoring.py b/ddtrace/propagation/_database_monitoring.py index 9d3c6cf594c..12cc5a3335d 100644 --- a/ddtrace/propagation/_database_monitoring.py +++ b/ddtrace/propagation/_database_monitoring.py @@ -6,13 +6,13 @@ from ddtrace import config as dd_config from ddtrace.internal import core from ddtrace.internal.logger import get_logger -from ddtrace.settings.peer_service import PeerServiceConfig +from ddtrace.internal.settings.peer_service import PeerServiceConfig from ddtrace.vendor.sqlcommenter import generate_sql_comment as _generate_sql_comment from ..internal import compat +from ..internal.settings._database_monitoring import dbm_config from ..internal.utils import get_argument_value from ..internal.utils import set_argument_value -from ..settings._database_monitoring import dbm_config if TYPE_CHECKING: diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 5a0101d351e..7780068aa6f 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -20,11 +20,11 @@ from ddtrace._trace.types import _MetaDictType from ddtrace.appsec._constants import APPSEC from ddtrace.internal import core +from ddtrace.internal.settings._config import config +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.settings._config import config -from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.debtcollector import deprecate from ..constants import AUTO_KEEP diff --git a/ddtrace/settings/exceptions.py b/ddtrace/settings/exceptions.py deleted file mode 100644 index c11b83be316..00000000000 --- a/ddtrace/settings/exceptions.py +++ /dev/null @@ -1,6 +0,0 @@ -class ConfigException(Exception): - """Configuration exception when an integration that is not available - is called in the `Config` object. - """ - - pass diff --git a/docs/configuration.rst b/docs/configuration.rst index a729c222c54..0179cc8c0a0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -27,7 +27,7 @@ Unified Service Tagging DD_SERVICE: default: (autodetected) - + description: | Set the service name to be used for this application. A default is provided for these integrations: :ref:`bottle`, :ref:`flask`, :ref:`grpc`, @@ -40,7 +40,7 @@ Unified Service Tagging ``6c44da20``, ``2020.02.13``. Generally set along with ``DD_SERVICE``. See `Unified Service Tagging`_ for more information. - + version_added: v0.36.0: @@ -50,7 +50,7 @@ Traces .. ddtrace-configuration-options:: DD__DISTRIBUTED_TRACING: default: True - + description: | Enables distributed tracing for the specified . @@ -60,18 +60,18 @@ Traces DD__SERVICE: type: String default: - + description: | Set the service name, allowing default service name overrides for traces for the specific . - + version_added: v2.11.0: DD_ASGI_TRACE_WEBSOCKET: default: False - + description: | - Enables tracing ASGI websockets. Please note that the websocket span duration will last until the + Enables tracing ASGI websockets. Please note that the websocket span duration will last until the connection is closed, which can result in long running spans. version_added: @@ -80,21 +80,21 @@ Traces DD_BOTOCORE_EMPTY_POLL_ENABLED: type: Boolean default: True - + description: | Enables creation of consumer span when AWS SQS and AWS Kinesis ``poll()`` operations return no records. When disabled, no consumer span is created if no records are returned. - + version_added: v2.6.0: DD_BOTOCORE_PROPAGATION_ENABLED: type: Boolean default: False - + description: | Enables trace context propagation connecting producer and consumer spans within a single trace for AWS SQS, SNS, and Kinesis messaging services. - + version_added: v2.6.0: @@ -130,33 +130,33 @@ Traces DD_TRACE__ENABLED: type: Boolean default: True - + description: | Enables to be patched. For example, ``DD_TRACE_DJANGO_ENABLED=false`` will disable the Django integration from being installed. - + version_added: v0.41.0: DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED: type: Boolean default: True - + description: | This configuration enables the generation of 128 bit trace ids. - + version_added: v1.12.0: DD_TRACE_API_VERSION: default: | ``v0.5`` - + description: | The trace API version to use when sending traces to the Datadog agent. Currently, the supported versions are: ``v0.4`` and ``v0.5``. - + version_added: v0.56.0: v1.7.0: default changed to ``v0.5``. @@ -219,7 +219,7 @@ Traces DD_TRACE_HTTP_SERVER_ERROR_STATUSES: type: String default: "500-599" - + description: | Comma-separated list of HTTP status codes that should be considered errors when returned by an HTTP request. Multiple comma separated error ranges can be set (ex: ``200,400-404,500-599``). @@ -228,21 +228,21 @@ Traces DD_TRACE_METHODS: type: String default: "" - + description: | Specify methods to trace. For example: ``mod.submod:method1,method2;mod.submod:Class.method1``. Note that this setting is only compatible with ``ddtrace-run``, and that it doesn't work for methods implemented by libraries for which there's an integration in ``ddtrace/contrib``. - + version_added: v2.1.0: DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP: default: | ``'(?ix)(?:(?:"|%22)?)(?:(?:old[-_]?|new[-_]?)?p(?:ass)?w(?:or)?d(?:1|2)?|pass(?:[-_]?phrase)?|secret|(?:api[-_]?|private[-_]?|public[-_]?|access[-_]?|secret[-_]?)key(?:[-_]?id)?|token|consumer[-_]?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:(?:\\s|%20)*(?:=|%3D)[^&]+|(?:"|%22)(?:\\s|%20)*(?::|%3A)(?:\\s|%20)*(?:"|%22)(?:%2[^2]|%[^2]|[^"%])+(?:"|%22))|(?: bearer(?:\\s|%20)+[a-z0-9._\\-]+|token(?::|%3A)[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L](?:[\\w=-]|%3D)+\\.ey[I-L](?:[\\w=-]|%3D)+(?:\\.(?:[\\w.+/=-]|%3D|%2F|%2B)+)?|-{5}BEGIN(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY-{5}[^\\-]+-{5}END(?:[a-z\\s]|%20)+PRIVATE(?:\\s|%20)KEY(?:-{5})?(?:\\n|%0A)?|(?:ssh-(?:rsa|dss)|ecdsa-[a-z0-9]+-[a-z0-9]+)(?:\\s|%20|%09)+(?:[a-z0-9/.+]|%2F|%5C|%2B){100,}(?:=|%3D)*(?:(?:\\s|%20|%09)+[a-z0-9._-]+)?)'`` - + description: A regexp to redact sensitive query strings. Obfuscation disabled if set to empty string - + version_added: v1.19.0: | ``DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP`` replaces ``DD_TRACE_OBFUSCATION_QUERY_STRING_PATTERN`` which is deprecated @@ -251,11 +251,11 @@ Traces DD_TRACE_OTEL_ENABLED: type: Boolean default: False - + description: | When used with ``ddtrace-run`` this configuration enables OpenTelemetry support. To enable OpenTelemetry without `ddtrace-run` refer to the following :mod:`docs `. - + version_added: v1.12.0: @@ -273,24 +273,24 @@ Traces type: Boolean default: False description: Whether the propagator stops after extracting the first header. - + version_added: v2.3.0: DD_TRACE_PROPAGATION_HTTP_BAGGAGE_ENABLED: type: Boolean default: False - + description: | Enables propagation of baggage items through http headers with prefix ``ot-baggage-``. - + version_added: v2.4.0: DD_TRACE_PROPAGATION_STYLE: default: | ``datadog,tracecontext,baggage`` - + description: | Comma separated list of propagation styles used for extracting trace context from inbound request headers and injecting trace context into outbound request headers. @@ -318,10 +318,10 @@ Traces DD_TRACE_SPAN_TRACEBACK_MAX_SIZE: type: Integer default: 30 - + description: | The maximum length of a traceback included in a span. - + version_added: v2.3.0: @@ -338,7 +338,7 @@ Traces DD_TRACE_WRITER_MAX_PAYLOAD_SIZE_BYTES: type: Int default: 8388608 - + description: | The max size in bytes of each payload item sent to the trace agent. If the max payload size is greater than buffer size, then max size of each payload item will be the buffer size. @@ -346,7 +346,7 @@ Traces DD_TRACE_X_DATADOG_TAGS_MAX_LENGTH: type: Integer default: 512 - + description: | The maximum length of ``x-datadog-tags`` header allowed in the Datadog propagation style. Must be a value between 0 to 512. If 0, propagation of ``x-datadog-tags`` is disabled. @@ -354,29 +354,29 @@ Traces DD_UNLOAD_MODULES_FROM_SITECUSTOMIZE: type: String default: "auto" - + description: | Controls whether module cloning logic is executed by ``ddtrace-run``. Module cloning involves saving copies of dependency modules for internal use by ``ddtrace`` that will be unaffected by future imports of and changes to those modules by application code. Valid values for this variable are ``1``, ``0``, and ``auto``. ``1`` tells ``ddtrace`` to run its module cloning logic unconditionally, ``0`` tells it not to run that logic, and ``auto`` tells it to run module cloning logic only if ``gevent`` is accessible from the application's runtime. - + version_added: v1.9.0: DD_TRACE_SAFE_INSTRUMENTATION_ENABLED: type: Boolean default: False - + description: | Whether to enable safe instrumentation. When enabled, ``ddtrace`` will check if the version of an installed package is compatible with the respective ``ddtrace`` integration patching the package. If the version is not compatible, ``ddtrace`` will not patch the respective package. - This is useful to avoid application crashes from patching packages that are incompatible with the ``ddtrace`` supported integration + This is useful to avoid application crashes from patching packages that are incompatible with the ``ddtrace`` supported integration version ranges. - + version_added: v3.11.0: @@ -388,7 +388,7 @@ Trace Context propagation DD_TRACE_PROPAGATION_STYLE_EXTRACT: default: | ``datadog,tracecontext`` - + description: | Comma separated list of propagation styles used for extracting trace context from inbound request headers. @@ -408,7 +408,7 @@ Trace Context propagation DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT: default: | ``continue`` - + description: | String for how to handle incoming request headers that are extracted for propagation of trace info. @@ -417,7 +417,7 @@ Trace Context propagation After extracting the headers for propagation, this configuration determines what is done with them. The default value is ``continue`` which always propagates valid headers. - ``ignore`` ignores all incoming headers and ``restart`` turns the first extracted valid propagation header + ``ignore`` ignores all incoming headers and ``restart`` turns the first extracted valid propagation header into a span link and propagates baggage if present. Example: ``DD_TRACE_PROPAGATION_STYLE_EXTRACT="ignore"`` to ignore all incoming headers and to start a root span without a parent. @@ -428,7 +428,7 @@ Trace Context propagation DD_TRACE_PROPAGATION_STYLE_INJECT: default: | ``tracecontext,datadog`` - + description: | Comma separated list of propagation styles used for injecting trace context into outbound request headers. @@ -453,10 +453,10 @@ Metrics DD_RUNTIME_METRICS_ENABLED: type: Boolean default: False - + description: | When used with ``ddtrace-run`` this configuration enables sending runtime metrics to Datadog. - These metrics track the memory management and concurrency of the python runtime. + These metrics track the memory management and concurrency of the python runtime. Refer to the following `docs ` _ for more information. DD_RUNTIME_METRICS_RUNTIME_ID_ENABLED: @@ -473,11 +473,11 @@ Metrics DD_METRICS_OTEL_ENABLED: type: Boolean default: False - + description: | When used with ``ddtrace-run`` this configuration enables support for exporting OTLP metrics generated by the OpenTelemetry Metrics API. The application must also include its own OTLP metrics exporter. - + version_added: v3.11.0: @@ -489,13 +489,13 @@ Application & API Security DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING: type: String default: "safe" - + description: | Sets the mode for the automated user login events tracking feature which sets some traces on each user login event. The supported modes are ``safe`` which will only store the user id or primary key, ``extended`` which will also store the username, email and full name and ``disabled``. Note that this feature requires ``DD_APPSEC_ENABLED`` to be set to ``true`` to work. - + version_added: v1.17.0: Added support to the Django integration. No other integrations support this configuration. @@ -507,13 +507,13 @@ Application & API Security DD_APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP: default: | ``(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?)key)|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)|bearer|authorization`` - + description: Sensitive parameter key regexp for obfuscation. DD_APPSEC_OBFUSCATION_PARAMETER_VALUE_REGEXP: default: | ``(?i)(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)(?:\s*=[^;]|"\s*:\s*"[^"]+")|bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(?:\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}`` - + description: Sensitive parameter value regexp for obfuscation. DD_APPSEC_RULES: @@ -589,37 +589,37 @@ Code Security DD_IAST_REDACTION_ENABLED: type: Boolean default: True - + description: | Replace potentially sensitive information in the vulnerability report, like passwords with ``*`` for non tainted strings and ``abcde...`` for tainted ones. This will use the regular expressions of the two next settings to decide what to scrub. - + version_added: v1.17.0: DD_IAST_REDACTION_NAME_PATTERN: type: String - + default: | ``(?i)^.*(?:p(?:ass)?w(?:or)?d|pass(?:_?phrase)?|secret|(?:api_?|private_?|public_?|access_?|secret_?)key(?:_?id)?|token|consumer_?(?:id|key|secret)|sign(?:ed|ature)?|auth(?:entication|orization)?)`` - + description: | Regular expression containing key or name style strings matched against vulnerability origin and evidence texts. If it matches, the scrubbing of the report will be enabled. - + version_added: v1.17.0: DD_IAST_REDACTION_VALUE_PATTERN: type: String - + default: | ``(?i)bearer\s+[a-z0-9\._\-]+|token:[a-z0-9]{13}|gh[opsu]_[0-9a-zA-Z]{36}|ey[I-L][\w=-]+\.ey[I-L][\w=-]+(\.[\w.+\/=-]+)?|[\-]{5}BEGIN[a-z\s]+PRIVATE\sKEY[\-]{5}[^\-]+[\-]{5}END[a-z\s]+PRIVATE\sKEY|ssh-rsa\s*[a-z0-9\/\.+]{100,}`` - + description: | Regular expression containing value style strings matched against vulnerability origin and evidence texts. If it matches, the scrubbing of the report will be enabled. - + version_added: v1.17.0: @@ -660,63 +660,63 @@ Test Visibility DD_CIVISIBILITY_AGENTLESS_ENABLED: type: Boolean default: False - + description: | Configures the ``CIVisibility`` service to use a test-reporting ``CIVisibilityWriter``. This writer sends payloads for traces on which it's used to the intake endpoint for Datadog CI Visibility. If there is a reachable Datadog agent that supports proxying these requests, the writer will send its payloads to that agent instead. - + version_added: v1.12.0: DD_CIVISIBILITY_AGENTLESS_URL: type: String default: "" - + description: | Configures the ``CIVisibility`` service to send event payloads to the specified host. If unspecified, the host "https://citestcycle-intake." is used, where ```` is replaced by that environment variable's value, or "datadoghq.com" if unspecified. - + version_added: v1.13.0: DD_CIVISIBILITY_ITR_ENABLED: type: Boolean default: True - + description: | Configures the ``CIVisibility`` service to query the Datadog API to decide whether to enable the Datadog `Test Impact Analysis `_ (formerly Intelligent Test Runner). Setting the variable to ``false`` will skip querying the API and disable code coverage collection and test skipping. - + version_added: v1.13.0: DD_CIVISIBILITY_LOG_LEVEL: type: String default: "info" - + description: | Configures the ``CIVisibility`` service to replace the default Datadog logger's stream handler with one that only displays messages related to the ``CIVisibility`` service, at a level of or higher than the given log level. The Datadog logger's file handler is unaffected. Valid, case-insensitive, values are ``critical``, ``error``, ``warning``, ``info``, or ``debug``. A value of ``none`` silently disables the logger. Note: enabling debug logging with the ``DD_TRACE_DEBUG`` environment variable overrides this behavior. - + version_added: v2.5.0: DD_TEST_SESSION_NAME: type: String default: (autodetected) - + description: | Configures the ``CIVisibility`` service to use the given string as the value of the ``test_session.name`` tag in test events. If not specified, this string will be constructed from the CI job id (if available) and the test command used to start the test session. - + version_added: v2.16.0: @@ -761,10 +761,10 @@ Agent DD_AGENT_HOST: type: String - + default: | ``localhost`` - + description: | The host name to use to connect the Datadog agent for traces. The host name can be IPv4, IPv6, or a domain name. If ``DD_TRACE_AGENT_URL`` is specified, the @@ -775,18 +775,18 @@ Agent Example for IPv6: ``DD_AGENT_HOST=2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF`` Example for domain name: ``DD_AGENT_HOST=host`` - + version_added: v0.17.0: v1.7.0: DD_DOGSTATSD_URL: type: URL - + default: | ``unix:///var/run/datadog/dsd.socket`` if available otherwise ``udp://localhost:8125`` - + description: | The URL to use to connect the Datadog agent for Dogstatsd metrics. The url can start with ``udp://`` to connect using UDP or with ``unix://`` to use a Unix @@ -801,14 +801,14 @@ Agent Override the modules patched for this execution of the program. Must be a list in the ``module1:boolean,module2:boolean`` format. For example, ``boto:true,redis:false``. - + version_added: v0.55.0: | Formerly named ``DATADOG_PATCH_MODULES`` DD_SITE: default: datadoghq.com - + description: | Specify which site to use for uploading profiles and logs. Set to ``datadoghq.eu`` to use EU site. @@ -818,7 +818,7 @@ Agent Set global tags to be attached to every span. Value must be either comma and/or space separated. e.g. ``key1:value1,key2:value2,key3``, ``key1:value key2:value2 key3`` or ``key1:value1, key2:value2, key3``. If a tag value is not supplied the value will be an empty string. - + version_added: v0.38.0: Comma separated support added v0.48.0: Space separated support added @@ -830,11 +830,11 @@ Agent DD_TRACE_AGENT_URL: type: URL - + default: | ``unix:///var/run/datadog/apm.socket`` if available otherwise ``http://localhost:8126`` - + description: | The URL to use to connect the Datadog agent for traces. The url can start with ``http://`` to connect using HTTP or with ``unix://`` to use a Unix @@ -867,19 +867,19 @@ Logs description: | When used with ``ddtrace-run`` this configuration enables support for exporting OTLP logs generated by the OpenTelemetry Logging API. The application must also include its own OTLP logs exporter. - + version_added: v3.12.0: Adds support for submitting logs via an OTLP Exporter. DD_TRACE_DEBUG: type: Boolean default: False - + description: | Enables debug logging in the tracer. Can be used with `DD_TRACE_LOG_FILE` to route logs to a file. - + version_added: v0.41.0: | Formerly named ``DATADOG_TRACE_DEBUG`` @@ -891,7 +891,7 @@ Logs DD_TRACE_LOG_FILE_LEVEL: default: DEBUG - + description: | Configures the ``RotatingFileHandler`` used by the `ddtrace` logger to write logs to a file based on the level specified. Defaults to `DEBUG`, but will accept the values found in the standard **logging** library, such as WARNING, ERROR, and INFO, @@ -900,7 +900,7 @@ Logs DD_TRACE_LOG_FILE_SIZE_BYTES: type: Int default: 15728640 - + description: | Max size for a file when used with `DD_TRACE_LOG_FILE`. When a log has exceeded this size, there will be one backup log file created. In total, the files will store ``2 * DD_TRACE_LOG_FILE_SIZE_BYTES`` worth of logs. @@ -917,7 +917,7 @@ Sampling DD_SPAN_SAMPLING_RULES: type: string - + description: | A JSON array of objects. Each object must have a "name" and/or "service" field, while the "max_per_second" and "sample_rate" fields are optional. The "sample_rate" value must be between 0.0 and 1.0 (inclusive), and will default to 1.0 (100% sampled). @@ -933,7 +933,7 @@ Sampling DD_SPAN_SAMPLING_RULES_FILE: type: string - + description: | A path to a JSON file containing span sampling rules organized as JSON array of objects. For the rules each object must have a "name" and/or "service" field, and the "sample_rate" field is optional. @@ -952,11 +952,11 @@ Sampling DD_TRACE_RATE_LIMIT: type: int default: 100 - + description: | Maximum number of traces per second to sample. Set a rate limit to avoid the ingestion volume overages in the case of traffic spikes. This configuration is only applied when client based sampling is configured, otherwise agent based rate limits are used. - + version_added: v0.33.0: v2.15.0: Only applied when DD_TRACE_SAMPLE_RATE, DD_TRACE_SAMPLING_RULES, or DD_SPAN_SAMPLING_RULE are set. @@ -964,14 +964,14 @@ Sampling DD_TRACE_SAMPLING_RULES: type: JSON array - + description: | A JSON array of objects. Each object must have a “sample_rate”, and the “name”, “service”, "resource", and "tags" fields are optional. The “sample_rate” value must be between 0.0 and 1.0 (inclusive). **Example:** ``DD_TRACE_SAMPLING_RULES='[{"sample_rate":0.5,"service":"my-service","resource":"my-url","tags":{"my-tag":"example"}}]'`` **Note** that the JSON object must be included in single quotes (') to avoid problems with escaping of the double quote (") character.' - + version_added: v1.19.0: added support for "resource" v1.20.0: added support for "tags" @@ -985,14 +985,14 @@ Other DD_INSTRUMENTATION_TELEMETRY_ENABLED: type: Boolean default: True - + description: | Enables sending :ref:`telemetry ` events to the agent. DD_TRACE_EXPERIMENTAL_FEATURES_ENABLED: type: string version_added: - v3.2.0: Adds initial support and support for enabling experimental runtime metrics. + v3.2.0: Adds initial support and support for enabling experimental runtime metrics. default: "" description: | @@ -1000,7 +1000,7 @@ Other DD_SUBPROCESS_SENSITIVE_WILDCARDS: type: String - + description: | Add more possible matches to the internal list of subprocess execution argument scrubbing. Must be a comma-separated list and each item can take `fnmatch` style wildcards, for example: ``*ssn*,*personalid*,*idcard*,*creditcard*``. @@ -1008,32 +1008,32 @@ Other DD_USER_MODEL_EMAIL_FIELD: type: String default: "" - + description: | Field to be used to read the user email when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. - + version_added: v1.15.0: DD_USER_MODEL_LOGIN_FIELD: type: String default: "" - + description: | Field to be used to read the user login when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. Please note that, if set, this field will be used to retrieve the user login even if ``DD_APPSEC_AUTOMATED_USER_EVENTS_TRACKING`` is set to ``safe`` and, in some cases, the selected field could hold potentially private information. - + version_added: v1.15.0: DD_USER_MODEL_NAME_FIELD: type: String default: "" - + description: | Field to be used to read the user name when using a custom ``User`` model for the automatic login events. This field will take precedence over automatic inference. - + version_added: v1.15.0: @@ -1043,10 +1043,10 @@ Other description: | A comma-separated list of baggage keys, sent via HTTP headers, to automatically tag as baggage. on the local root span. - Only baggage extracted from incoming headers is supported. Baggage set via ``Context.set_baggage_item(..., ...)`` is not included. Keys must have non-empty values. + Only baggage extracted from incoming headers is supported. Baggage set via ``Context.set_baggage_item(..., ...)`` is not included. Keys must have non-empty values. Set to * to tag all baggage keys (use with caution to avoid exposing sensitive data). Set to an empty string to disable the feature. - version_added: + version_added: v3.6.0: .. _Unified Service Tagging: https://docs.datadoghq.com/getting_started/tagging/unified_service_tagging/ @@ -1057,33 +1057,33 @@ Other Profiling --------- -.. ddtrace-envier-configuration:: ddtrace.settings.profiling:ProfilingConfig +.. ddtrace-envier-configuration:: ddtrace.internal.settings.profiling:ProfilingConfig :recursive: true Dynamic Instrumentation ----------------------- -.. ddtrace-envier-configuration:: ddtrace.settings.dynamic_instrumentation:DynamicInstrumentationConfig +.. ddtrace-envier-configuration:: ddtrace.internal.settings.dynamic_instrumentation:DynamicInstrumentationConfig Exception Replay ---------------- -.. ddtrace-envier-configuration:: ddtrace.settings.exception_replay:ExceptionReplayConfig +.. ddtrace-envier-configuration:: ddtrace.internal.settings.exception_replay:ExceptionReplayConfig Code Origin ----------- -.. ddtrace-envier-configuration:: ddtrace.settings.code_origin:CodeOriginConfig +.. ddtrace-envier-configuration:: ddtrace.internal.settings.code_origin:CodeOriginConfig :recursive: true Live Debugging -------------- -.. ddtrace-envier-configuration:: ddtrace.settings.live_debugging:LiveDebuggerConfig +.. ddtrace-envier-configuration:: ddtrace.internal.settings.live_debugging:LiveDebuggerConfig Error Tracking -------------- diff --git a/releasenotes/notes/internal-settings-3b45c1e8a96edc99.yaml b/releasenotes/notes/internal-settings-3b45c1e8a96edc99.yaml new file mode 100644 index 00000000000..6dafe750d36 --- /dev/null +++ b/releasenotes/notes/internal-settings-3b45c1e8a96edc99.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + This change removes the `ddtrace.settings` package and replaces it with `ddtrace.internal.settings`. + Environment variables can be used to adjust settings. diff --git a/tests/appsec/ai_guard/api/test_api_client.py b/tests/appsec/ai_guard/api/test_api_client.py index a80d06f2f33..a965539e52c 100644 --- a/tests/appsec/ai_guard/api/test_api_client.py +++ b/tests/appsec/ai_guard/api/test_api_client.py @@ -8,7 +8,7 @@ from ddtrace.appsec.ai_guard import AIGuardClientError from ddtrace.appsec.ai_guard import Prompt from ddtrace.appsec.ai_guard import ToolCall -from ddtrace.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import ai_guard_config from tests.appsec.ai_guard.utils import assert_ai_guard_span from tests.appsec.ai_guard.utils import assert_mock_execute_request_call from tests.appsec.ai_guard.utils import find_ai_guard_span diff --git a/tests/appsec/ai_guard/langchain/conftest.py b/tests/appsec/ai_guard/langchain/conftest.py index 28e0408d3c5..270d1c6894b 100644 --- a/tests/appsec/ai_guard/langchain/conftest.py +++ b/tests/appsec/ai_guard/langchain/conftest.py @@ -7,7 +7,7 @@ from ddtrace.appsec._ai_guard import init_ai_guard from ddtrace.contrib.internal.langchain.patch import patch from ddtrace.contrib.internal.langchain.patch import unpatch -from ddtrace.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import ai_guard_config from tests.utils import override_env diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index 00729bb261f..e134b7d0118 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -22,8 +22,8 @@ from ddtrace.internal.remoteconfig.client import TargetFile from ddtrace.internal.remoteconfig.worker import remoteconfig_poller from ddtrace.internal.service import ServiceStatus +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool -from ddtrace.settings.asm import config as asm_config import tests.appsec.rules as rules from tests.appsec.utils import asm_context from tests.appsec.utils import build_payload diff --git a/tests/appsec/architectures/mini.py b/tests/appsec/architectures/mini.py index bb10e976ed4..179c85bfb80 100644 --- a/tests/appsec/architectures/mini.py +++ b/tests/appsec/architectures/mini.py @@ -11,8 +11,8 @@ from flask import request # noqa: E402 import requests # noqa: E402 F401 +from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 import ddtrace.internal.telemetry.writer # noqa: E402 -from ddtrace.settings.asm import config as asm_config # noqa: E402 from ddtrace.version import get_version # noqa: E402 diff --git a/tests/appsec/architectures/test_appsec_loading_modules.py b/tests/appsec/architectures/test_appsec_loading_modules.py index a363e7c3e81..1d34d76d8c9 100644 --- a/tests/appsec/architectures/test_appsec_loading_modules.py +++ b/tests/appsec/architectures/test_appsec_loading_modules.py @@ -9,7 +9,7 @@ import pytest -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config MODULES_ALWAYS_LOADED = ["ddtrace.appsec", "ddtrace.appsec._constants"] diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index dc5e5454a58..951465bedcf 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -8,7 +8,7 @@ import pytest # noqa: E402 -from ddtrace.settings.asm import config as asm_config # noqa: E402 +from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 from tests.utils import TracerSpanContainer # noqa: E402 from tests.utils import _build_tree # noqa: E402 diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 6e3a7586153..9298e0ea047 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -16,7 +16,7 @@ from ddtrace.appsec import _constants as asm_constants from ddtrace.appsec._utils import get_triggers from ddtrace.internal import constants -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config import tests.appsec.rules as rules from tests.utils import DummyTracer from tests.utils import override_env @@ -144,7 +144,7 @@ def test_healthcheck(self, interface: Interface, get_entry_span_tag, asm_enabled response = interface.client.get("/") assert self.status(response) == 200, "healthcheck failed" assert self.body(response) == "ok ASM" - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config assert asm_config._asm_enabled is asm_enabled assert get_entry_span_tag("http.status_code") == "200" diff --git a/tests/appsec/iast/fixtures/integration/main_configure.py b/tests/appsec/iast/fixtures/integration/main_configure.py index 1ab365da869..b6c25d75afc 100644 --- a/tests/appsec/iast/fixtures/integration/main_configure.py +++ b/tests/appsec/iast/fixtures/integration/main_configure.py @@ -5,7 +5,7 @@ import ddtrace.auto # noqa: F401 from ddtrace.ext import SpanTypes -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.trace import tracer diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_dbapi.py b/tests/appsec/iast/taint_sinks/test_sql_injection_dbapi.py index d16cb29cbcc..7721beecaf6 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_dbapi.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_dbapi.py @@ -6,9 +6,9 @@ from ddtrace.appsec._iast import load_iast from ddtrace.appsec._iast._overhead_control_engine import oce from ddtrace.contrib.dbapi import TracedCursor -from ddtrace.settings._config import Config -from ddtrace.settings.asm import config as asm_config -from ddtrace.settings.integration import IntegrationConfig +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.settings.integration import IntegrationConfig from tests.appsec.iast.iast_utils import _end_iast_context_and_oce from tests.appsec.iast.iast_utils import _start_iast_context_and_oce from tests.utils import TracerTestCase diff --git a/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py b/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py index 7a0c97d9353..b06c13abb19 100644 --- a/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py +++ b/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py @@ -15,7 +15,7 @@ def _child_check(q: Queue): """Subprocess entrypoint: report tracer and IAST env status back to parent via Queue.""" try: - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config from ddtrace.trace import tracer text = "text_to_taint" diff --git a/tests/appsec/iast/test_loader.py b/tests/appsec/iast/test_loader.py index 8c91e725667..6942b4b6968 100644 --- a/tests/appsec/iast/test_loader.py +++ b/tests/appsec/iast/test_loader.py @@ -6,7 +6,7 @@ import ddtrace.appsec._iast._loader from ddtrace.internal.iast.product import post_preload -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config ASPECTS_MODULE = "ddtrace.appsec._iast._taint_tracking.aspects" diff --git a/tests/appsec/iast/test_overhead_control_engine.py b/tests/appsec/iast/test_overhead_control_engine.py index e40231951fe..3c28e472bed 100644 --- a/tests/appsec/iast/test_overhead_control_engine.py +++ b/tests/appsec/iast/test_overhead_control_engine.py @@ -7,7 +7,7 @@ from ddtrace.appsec._iast._taint_tracking._context import finish_request_context from ddtrace.appsec._iast._taint_tracking._context import start_request_context from ddtrace.appsec._iast.sampling.vulnerability_detection import reset_request_vulnerabilities -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config def function_with_vulnerabilities_3(tracer): diff --git a/tests/appsec/integrations/django_tests/test_appsec_django.py b/tests/appsec/integrations/django_tests/test_appsec_django.py index 319325b61cb..bfbf10ca11c 100644 --- a/tests/appsec/integrations/django_tests/test_appsec_django.py +++ b/tests/appsec/integrations/django_tests/test_appsec_django.py @@ -12,7 +12,7 @@ from ddtrace.ext import http from ddtrace.ext import user from ddtrace.internal import constants -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from tests.appsec.integrations.django_tests.utils import _aux_appsec_get_root_span import tests.appsec.rules as rules from tests.utils import override_global_config diff --git a/tests/appsec/integrations/django_tests/test_iast_django.py b/tests/appsec/integrations/django_tests/test_iast_django.py index 39580863142..bbd32f4f162 100644 --- a/tests/appsec/integrations/django_tests/test_iast_django.py +++ b/tests/appsec/integrations/django_tests/test_iast_django.py @@ -16,7 +16,7 @@ from ddtrace.appsec._iast.constants import VULN_SSRF from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK from ddtrace.appsec._iast.constants import VULN_UNVALIDATED_REDIRECT -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from tests.appsec.iast.iast_utils import get_line_and_hash from tests.appsec.iast.iast_utils import load_iast_report from tests.appsec.integrations.django_tests.utils import _aux_appsec_get_root_span diff --git a/tests/appsec/integrations/flask_tests/test_iast_flask.py b/tests/appsec/integrations/flask_tests/test_iast_flask.py index 27c88cd976a..f1664294412 100644 --- a/tests/appsec/integrations/flask_tests/test_iast_flask.py +++ b/tests/appsec/integrations/flask_tests/test_iast_flask.py @@ -22,7 +22,7 @@ from ddtrace.appsec._iast.taint_sinks.unvalidated_redirect import patch as patch_unvalidated_redirect from ddtrace.appsec._iast.taint_sinks.xss import patch as patch_xss_injection from ddtrace.contrib.internal.sqlite3.patch import patch as patch_sqlite_sqli -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from tests.appsec.iast.iast_utils import get_line_and_hash from tests.appsec.iast.iast_utils import load_iast_report from tests.appsec.integrations.flask_tests.utils import flask_version diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml index b215b5edb54..39782d2ef8b 100644 --- a/tests/appsec/suitespec.yml +++ b/tests/appsec/suitespec.yml @@ -2,7 +2,7 @@ components: appsec: - ddtrace/appsec/* - - ddtrace/settings/asm.py + - ddtrace/internal/settings/asm.py appsec_iast: - ddtrace/appsec/iast/* urllib: diff --git a/tests/ci_visibility/api_client/test_ci_visibility_api_client.py b/tests/ci_visibility/api_client/test_ci_visibility_api_client.py index bfb9bbb6782..ba5878a0bc9 100644 --- a/tests/ci_visibility/api_client/test_ci_visibility_api_client.py +++ b/tests/ci_visibility/api_client/test_ci_visibility_api_client.py @@ -17,7 +17,7 @@ from ddtrace.internal.ci_visibility.constants import EVP_PROXY_AGENT_BASE_PATH_V4 from ddtrace.internal.ci_visibility.constants import REQUESTS_MODE from ddtrace.internal.ci_visibility.git_data import GitData -from ddtrace.settings._config import Config +from ddtrace.internal.settings._config import Config from tests.ci_visibility.api_client._util import _AGENTLESS from tests.ci_visibility.api_client._util import _EVP_PROXY from tests.ci_visibility.api_client._util import TestTestVisibilityAPIClientBase @@ -491,7 +491,7 @@ def test_civisibility_api_client_evp_proxy_config_success(self, env_vars, expect "ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_base_url", return_value=EVP_PROXY_AGENT_BASE_PATH, ), mock.patch( - "ddtrace.settings._agent.config.trace_agent_url", return_value="http://shouldntbeused:6218" + "ddtrace.internal.settings._agent.config.trace_agent_url", return_value="http://shouldntbeused:6218" ), mock.patch( "ddtrace.internal.ci_visibility.recorder.ddtrace.tracer._span_aggregator.writer.intake_url", "http://patchedagenturl:6218", @@ -600,7 +600,7 @@ def test_civisibility_api_client_evp_respects_agent_default_config(self): ), mock.patch( "ddtrace.internal.agent.info", return_value=agent_info_response ), mock.patch( - "ddtrace.settings._agent.config.trace_agent_url", + "ddtrace.internal.settings._agent.config.trace_agent_url", new_callable=mock.PropertyMock, return_value="http://shouldntbeused:6218", ), mock.patch( diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index fca13919b82..def2ecabb33 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -32,9 +32,9 @@ from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer from ddtrace.internal.ci_visibility.recorder import _extract_repository_name_from_url from ddtrace.internal.ci_visibility.recorder import _is_item_itr_skippable +from ddtrace.internal.settings._config import Config from ddtrace.internal.test_visibility._library_capabilities import LibraryCapabilities from ddtrace.internal.utils.http import Response -from ddtrace.settings._config import Config from ddtrace.trace import Span from tests.ci_visibility.api_client._util import _make_fqdn_suite_ids from tests.ci_visibility.api_client._util import _make_fqdn_test_ids @@ -728,7 +728,7 @@ def test_civisibilitywriter_coverage_evp_proxy_url(self): DD_API_KEY="foobar.baz", ) ), mock.patch( - "ddtrace.settings._agent.config.trace_agent_url", + "ddtrace.internal.settings._agent.config.trace_agent_url", new_callable=mock.PropertyMock, return_value="http://arandomhost:9126", ) as agent_url_mock, mock.patch( @@ -773,10 +773,10 @@ def test_civisibilitywriter_evp_proxy_url(self): DD_API_KEY="foobar.baz", ) ), mock.patch( - "ddtrace.settings._agent.config.trace_agent_url", + "ddtrace.internal.settings._agent.config.trace_agent_url", new_callable=mock.PropertyMock, return_value="http://evpproxy.bar:1234", - ), mock.patch("ddtrace.settings._config.Config", _get_default_civisibility_ddconfig()), mock.patch( + ), mock.patch("ddtrace.internal.settings._config.Config", _get_default_civisibility_ddconfig()), mock.patch( "ddtrace.tracer", CIVisibilityTracer() ), mock.patch( "ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_base_url", @@ -797,7 +797,7 @@ def test_civisibilitywriter_only_traces(self): DD_API_KEY="foobar.baz", ) ), mock.patch( - "ddtrace.settings._agent.config.trace_agent_url", + "ddtrace.internal.settings._agent.config.trace_agent_url", new_callable=mock.PropertyMock, return_value="http://onlytraces:1234", ), mock.patch("ddtrace.tracer", CIVisibilityTracer()), mock.patch( diff --git a/tests/ci_visibility/util.py b/tests/ci_visibility/util.py index df9fe8c2f20..3992597c544 100644 --- a/tests/ci_visibility/util.py +++ b/tests/ci_visibility/util.py @@ -14,7 +14,7 @@ from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient from ddtrace.internal.ci_visibility.recorder import CIVisibility from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer -from ddtrace.settings._config import Config +from ddtrace.internal.settings._config import Config from tests.utils import DummyCIVisibilityWriter from tests.utils import override_env diff --git a/tests/commands/ddtrace_run_global_tags.py b/tests/commands/ddtrace_run_global_tags.py deleted file mode 100644 index 2441d80f93a..00000000000 --- a/tests/commands/ddtrace_run_global_tags.py +++ /dev/null @@ -1,8 +0,0 @@ -from ddtrace.trace import tracer - - -if __name__ == "__main__": - assert tracer._tags.get("a") == "True" - assert tracer._tags.get("b") == "0" - assert tracer._tags.get("c") == "C" - print("Test success") diff --git a/tests/commands/test_runner.py b/tests/commands/test_runner.py index b795223328e..71c11722905 100644 --- a/tests/commands/test_runner.py +++ b/tests/commands/test_runner.py @@ -6,7 +6,6 @@ import pytest import ddtrace -from ddtrace.internal.compat import PYTHON_VERSION_INFO from ..utils import BaseTestCase from ..utils import override_env @@ -198,12 +197,6 @@ def test_argv_passed(self): out = subprocess.check_output(["ddtrace-run", "python", "tests/commands/ddtrace_run_argv.py", "foo", "bar"]) assert out.startswith(b"Test success") - def test_global_trace_tags(self): - """Ensure global tags are passed in from environment""" - with self.override_env(dict(DD_TRACE_GLOBAL_TAGS="a:True,b:0,c:C")): - out = subprocess.check_output(["ddtrace-run", "python", "tests/commands/ddtrace_run_global_tags.py"]) - assert out.startswith(b"Test success") - def test_logs_injection(self): """Ensure logs injection works""" with self.override_env(dict(DD_TAGS="service:my-service,env:my-env,version:my-version")): @@ -516,24 +509,6 @@ def test_ddtrace_run_and_auto_sitecustomize(): assert final_modules - starting_modules == set(["ddtrace.auto"]) -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") -@pytest.mark.subprocess(env=dict(DD_TRACE_GLOBAL_TAGS="a:True"), err=None) -def test_global_trace_tags_deprecation_warning(): - """Ensure DD_TRACE_GLOBAL_TAGS deprecation warning shows""" - import warnings - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - import ddtrace.auto # noqa: F401 - - assert len(warns) >= 1 - warning_messages = [str(warn.message) for warn in warns] - assert ( - "DD_TRACE_GLOBAL_TAGS is deprecated and will be removed in version '4.0.0': Please migrate to using " - "DD_TAGS instead" in warning_messages - ), warning_messages - - @pytest.mark.subprocess(ddtrace_run=False, err="") def test_ddtrace_auto_atexit(): """When ddtrace-run is used, ensure atexit hooks are registered exactly once""" diff --git a/tests/contrib/dbapi/test_dbapi.py b/tests/contrib/dbapi/test_dbapi.py index 71ddaff78e6..a1299c575bc 100644 --- a/tests/contrib/dbapi/test_dbapi.py +++ b/tests/contrib/dbapi/test_dbapi.py @@ -5,9 +5,9 @@ from ddtrace.contrib.dbapi import FetchTracedCursor from ddtrace.contrib.dbapi import TracedConnection from ddtrace.contrib.dbapi import TracedCursor +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.settings._config import Config -from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import Span # noqa:F401 from tests.utils import TracerTestCase from tests.utils import assert_is_measured diff --git a/tests/contrib/dbapi_async/test_dbapi_async.py b/tests/contrib/dbapi_async/test_dbapi_async.py index 794af1ebae4..4b615043268 100644 --- a/tests/contrib/dbapi_async/test_dbapi_async.py +++ b/tests/contrib/dbapi_async/test_dbapi_async.py @@ -5,9 +5,9 @@ from ddtrace.contrib.dbapi_async import FetchTracedAsyncCursor from ddtrace.contrib.dbapi_async import TracedAsyncConnection from ddtrace.contrib.dbapi_async import TracedAsyncCursor +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.settings._config import Config -from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import Span # noqa:F401 from tests.contrib.asyncio.utils import AsyncioTestCase from tests.contrib.asyncio.utils import mark_asyncio diff --git a/tests/contrib/httplib/test_httplib.py b/tests/contrib/httplib/test_httplib.py index 867afad7687..4a8c01c7eea 100644 --- a/tests/contrib/httplib/test_httplib.py +++ b/tests/contrib/httplib/test_httplib.py @@ -395,7 +395,7 @@ def test_httplib_request_and_response_headers(self): # Enabled when configured with self.override_config("httplib", {}): - from ddtrace.settings.integration import IntegrationConfig # noqa:F401 + from ddtrace.internal.settings.integration import IntegrationConfig # noqa:F401 integration_config = config.httplib # type: IntegrationConfig integration_config.http.trace_headers(["my-header", "access-control-allow-origin"]) diff --git a/tests/contrib/httpx/test_httpx.py b/tests/contrib/httpx/test_httpx.py index 6fbbbee9427..a343add72e1 100644 --- a/tests/contrib/httpx/test_httpx.py +++ b/tests/contrib/httpx/test_httpx.py @@ -7,7 +7,7 @@ from ddtrace.contrib.internal.httpx.patch import patch from ddtrace.contrib.internal.httpx.patch import unpatch from ddtrace.internal.compat import is_wrapted -from ddtrace.settings.http import HttpConfig +from ddtrace.internal.settings.http import HttpConfig from tests.utils import override_config from tests.utils import override_http_config diff --git a/tests/contrib/httpx/test_httpx_pre_0_11.py b/tests/contrib/httpx/test_httpx_pre_0_11.py index dff425f1635..3b1c5132637 100644 --- a/tests/contrib/httpx/test_httpx_pre_0_11.py +++ b/tests/contrib/httpx/test_httpx_pre_0_11.py @@ -7,7 +7,7 @@ from ddtrace.contrib.internal.httpx.patch import patch from ddtrace.contrib.internal.httpx.patch import unpatch from ddtrace.internal.compat import is_wrapted -from ddtrace.settings.http import HttpConfig +from ddtrace.internal.settings.http import HttpConfig from tests.utils import override_config from tests.utils import override_http_config diff --git a/tests/contrib/pymongo/test.py b/tests/contrib/pymongo/test.py index ccabc218ce9..9eb066cdae0 100644 --- a/tests/contrib/pymongo/test.py +++ b/tests/contrib/pymongo/test.py @@ -877,7 +877,7 @@ def test_dbm_propagation_full_mode(self): if pymongo.version_tuple < (3, 9): self.skipTest("DBM propagation requires PyMongo 3.9+") - from ddtrace.settings._database_monitoring import dbm_config + from ddtrace.internal.settings._database_monitoring import dbm_config assert dbm_config.propagation_mode == "full" @@ -930,7 +930,7 @@ def test_dbm_propagation_full_mode(self): @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_DBM_PROPAGATION_MODE="disabled")) def test_dbm_propagation_disabled(self): """Test that DBM comment is not injected when propagation mode is 'disabled'""" - from ddtrace.settings._database_monitoring import dbm_config + from ddtrace.internal.settings._database_monitoring import dbm_config assert dbm_config.propagation_mode == "disabled" @@ -972,7 +972,7 @@ def test_dbm_propagation_service_mode(self): if pymongo.version_tuple < (3, 9): self.skipTest("DBM propagation requires PyMongo 3.9+") - from ddtrace.settings._database_monitoring import dbm_config + from ddtrace.internal.settings._database_monitoring import dbm_config assert dbm_config.propagation_mode == "service" @@ -1045,7 +1045,7 @@ def test_dbm_propagation_disabled_on_old_pymongo(self): if pymongo.version_tuple >= (3, 9): self.skipTest("Only test on PyMongo versions < 3.9") - from ddtrace.settings._database_monitoring import dbm_config + from ddtrace.internal.settings._database_monitoring import dbm_config assert dbm_config.propagation_mode == "service" diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index 6fa1f262b3b..d9a540bfd87 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -4557,7 +4557,7 @@ def test_pytest_disables_telemetry_dependency_collection(self): def test_dependency_collection_disabled(): # Check that the config is set to disable telemetry dependency collection # The pytest plugin should have done this earlier in the process - from ddtrace.settings._telemetry import config as telemetry_config + from ddtrace.internal.settings._telemetry import config as telemetry_config assert telemetry_config.DEPENDENCY_COLLECTION is False, "Dependency collection should be disabled" """ ) diff --git a/tests/contrib/requests/test_requests_distributed.py b/tests/contrib/requests/test_requests_distributed.py index 9cbeb3ab2ba..03d4caf00da 100644 --- a/tests/contrib/requests/test_requests_distributed.py +++ b/tests/contrib/requests/test_requests_distributed.py @@ -1,7 +1,7 @@ from requests_mock import Adapter from ddtrace._trace.pin import Pin -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from tests.utils import TracerTestCase from tests.utils import get_128_bit_trace_id_from_headers diff --git a/tests/contrib/subprocess/test_subprocess_patch.py b/tests/contrib/subprocess/test_subprocess_patch.py index 33e77698ceb..b2f65324c75 100644 --- a/tests/contrib/subprocess/test_subprocess_patch.py +++ b/tests/contrib/subprocess/test_subprocess_patch.py @@ -1,6 +1,6 @@ from ddtrace.contrib.internal.subprocess.patch import get_version from ddtrace.contrib.internal.subprocess.patch import patch -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config try: diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 649bc840e8e..48246288682 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -54,7 +54,7 @@ components: - ddtrace/ext/test.py - ddtrace/ext/user.py - ddtrace/propagation/* - - ddtrace/settings/_database_monitoring.py + - ddtrace/internal/settings/_database_monitoring.py - tests/contrib/patch.py - tests/contrib/config.py - tests/contrib/__init__.py diff --git a/tests/contrib/urllib3/test_urllib3.py b/tests/contrib/urllib3/test_urllib3.py index c1a6434c0bc..3598ffb2608 100644 --- a/tests/contrib/urllib3/test_urllib3.py +++ b/tests/contrib/urllib3/test_urllib3.py @@ -12,7 +12,7 @@ from ddtrace.contrib.internal.urllib3.patch import unpatch from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from ddtrace.settings.asm import config as asm_config +from ddtrace.internal.settings.asm import config as asm_config from tests.contrib.config import HTTPBIN_CONFIG from tests.utils import TracerTestCase from tests.utils import snapshot diff --git a/tests/contrib/vertica/test_vertica.py b/tests/contrib/vertica/test_vertica.py index 223dce2982b..e9404efeda1 100644 --- a/tests/contrib/vertica/test_vertica.py +++ b/tests/contrib/vertica/test_vertica.py @@ -10,7 +10,7 @@ from ddtrace.contrib.internal.vertica.patch import unpatch from ddtrace.internal.compat import is_wrapted from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME -from ddtrace.settings._config import _deepmerge +from ddtrace.internal.settings._config import _deepmerge from tests.contrib.config import VERTICA_CONFIG from tests.utils import DummyTracer from tests.utils import TracerTestCase diff --git a/tests/debugging/exception/test_replay.py b/tests/debugging/exception/test_replay.py index e7f5f1e1a66..cb5da2a288e 100644 --- a/tests/debugging/exception/test_replay.py +++ b/tests/debugging/exception/test_replay.py @@ -8,7 +8,7 @@ from ddtrace.debugging._exception import replay from ddtrace.internal.packages import _third_party_packages from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter -from ddtrace.settings.exception_replay import ExceptionReplayConfig +from ddtrace.internal.settings.exception_replay import ExceptionReplayConfig from tests.debugging.mocking import exception_replay from tests.utils import TracerTestCase from tests.utils import override_third_party_packages diff --git a/tests/debugging/exploration/_config.py b/tests/debugging/exploration/_config.py index 5307125e2f8..1d6b7f8dfcd 100644 --- a/tests/debugging/exploration/_config.py +++ b/tests/debugging/exploration/_config.py @@ -5,7 +5,7 @@ from warnings import warn from ddtrace.debugging._probe.model import CaptureLimits -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig def parse_venv(value: str) -> t.Optional[Path]: diff --git a/tests/debugging/mocking.py b/tests/debugging/mocking.py index 23381e06c71..746f9cd2691 100644 --- a/tests/debugging/mocking.py +++ b/tests/debugging/mocking.py @@ -20,7 +20,7 @@ from ddtrace.debugging._signal.collector import SignalCollector from ddtrace.debugging._signal.snapshot import Snapshot from ddtrace.debugging._uploader import SignalUploader -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig from tests.debugging.probe.test_status import DummyProbeStatusLogger diff --git a/tests/debugging/suitespec.yml b/tests/debugging/suitespec.yml index 54ea27c76e2..2ed214de3ba 100644 --- a/tests/debugging/suitespec.yml +++ b/tests/debugging/suitespec.yml @@ -2,8 +2,8 @@ components: debugging: - ddtrace/debugging/* - - ddtrace/settings/dynamic_instrumentation.py - - ddtrace/settings/exception_replay.py + - ddtrace/internal/settings/dynamic_instrumentation.py + - ddtrace/internal/settings/exception_replay.py suites: debugger: parallelism: 1 diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index 490d64fda0f..07c8ef7d739 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -2,9 +2,9 @@ import pytest +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.version import get_version from tests.utils import override_env @@ -12,22 +12,22 @@ @contextmanager def debugger_config(**kwargs): with override_env(kwargs, replace_os_env=True): - from ddtrace.settings._config import Config - import ddtrace.settings.dynamic_instrumentation + from ddtrace.internal.settings._config import Config + import ddtrace.internal.settings.dynamic_instrumentation - old_config = ddtrace.settings.dynamic_instrumentation.ddconfig - old_di_config = ddtrace.settings.dynamic_instrumentation.config.__dict__ + old_config = ddtrace.internal.settings.dynamic_instrumentation.ddconfig + old_di_config = ddtrace.internal.settings.dynamic_instrumentation.config.__dict__ try: - ddtrace.settings.dynamic_instrumentation.ddconfig = Config() + ddtrace.internal.settings.dynamic_instrumentation.ddconfig = Config() new_config = DynamicInstrumentationConfig() - ddtrace.settings.dynamic_instrumentation.config.__dict__ = new_config.__dict__ + ddtrace.internal.settings.dynamic_instrumentation.config.__dict__ = new_config.__dict__ - yield ddtrace.settings.dynamic_instrumentation.config + yield ddtrace.internal.settings.dynamic_instrumentation.config finally: - ddtrace.settings.dynamic_instrumentation.config.__dict__ = old_di_config - ddtrace.settings.dynamic_instrumentation.ddconfig = old_config + ddtrace.internal.settings.dynamic_instrumentation.config.__dict__ = old_di_config + ddtrace.internal.settings.dynamic_instrumentation.ddconfig = old_config def test_tags(): diff --git a/tests/errortracking/suitespec.yml b/tests/errortracking/suitespec.yml index 40e391dd63a..bfb10577e72 100644 --- a/tests/errortracking/suitespec.yml +++ b/tests/errortracking/suitespec.yml @@ -2,7 +2,7 @@ components: errortracking: - ddtrace/errortracking/* - - ddtrace/settings/errortracking.py + - ddtrace/internal/settings/errortracking.py suites: errortracker: parallelism: 1 diff --git a/tests/integration/test_integration_civisibility.py b/tests/integration/test_integration_civisibility.py index 89dedada3ed..d955a9c85e6 100644 --- a/tests/integration/test_integration_civisibility.py +++ b/tests/integration/test_integration_civisibility.py @@ -10,7 +10,7 @@ from ddtrace.internal.ci_visibility.constants import EVP_SUBDOMAIN_HEADER_EVENT_VALUE from ddtrace.internal.ci_visibility.constants import EVP_SUBDOMAIN_HEADER_NAME from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer -from ddtrace.settings._agent import config as agent_config +from ddtrace.internal.settings._agent import config as agent_config from tests.ci_visibility.util import _get_default_civisibility_ddconfig from tests.utils import override_env diff --git a/tests/internal/bytecode_injection/framework_injection/_config.py b/tests/internal/bytecode_injection/framework_injection/_config.py index 5af91592ece..370861ddc5e 100644 --- a/tests/internal/bytecode_injection/framework_injection/_config.py +++ b/tests/internal/bytecode_injection/framework_injection/_config.py @@ -4,7 +4,7 @@ import typing as t from warnings import warn -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig def parse_venv(value: str) -> t.Optional[Path]: diff --git a/tests/internal/crashtracker/test_crashtracker.py b/tests/internal/crashtracker/test_crashtracker.py index 412d29d1fbb..002ac16e564 100644 --- a/tests/internal/crashtracker/test_crashtracker.py +++ b/tests/internal/crashtracker/test_crashtracker.py @@ -36,7 +36,7 @@ def test_crashtracker_config_bytes(): import pytest from ddtrace.internal.core import crashtracking - from ddtrace.settings.crashtracker import config as crashtracker_config + from ddtrace.internal.settings.crashtracker import config as crashtracker_config from tests.internal.crashtracker.utils import read_files # Delete the stdout and stderr files if they exist diff --git a/tests/internal/crashtracker/utils.py b/tests/internal/crashtracker/utils.py index e26d05788dc..7ef880e1410 100644 --- a/tests/internal/crashtracker/utils.py +++ b/tests/internal/crashtracker/utils.py @@ -17,7 +17,7 @@ def start_crashtracker(port: int, stdout: Optional[str] = None, stderr: Optional ret = False try: from ddtrace.internal.core import crashtracking - from ddtrace.settings.crashtracker import config as crashtracker_config + from ddtrace.internal.settings.crashtracker import config as crashtracker_config crashtracker_config.debug_url = "http://localhost:%d" % port crashtracker_config.stdout_filename = stdout diff --git a/tests/internal/peer_service/test_processor.py b/tests/internal/peer_service/test_processor.py index d45b97e204e..2d5aeebea9e 100644 --- a/tests/internal/peer_service/test_processor.py +++ b/tests/internal/peer_service/test_processor.py @@ -6,7 +6,7 @@ from ddtrace.constants import SPAN_KIND from ddtrace.ext import SpanKind from ddtrace.internal.peer_service.processor import PeerServiceProcessor -from ddtrace.settings.peer_service import PeerServiceConfig +from ddtrace.internal.settings.peer_service import PeerServiceConfig from ddtrace.trace import Span @@ -96,7 +96,7 @@ def test_peer_service_enablement(schema_peer_enabled): schema_version, env_enabled, expected = schema_peer_enabled with mock.patch.dict(os.environ, {"DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED": env_enabled}): - with mock.patch("ddtrace.settings.peer_service.SCHEMA_VERSION", schema_version): + with mock.patch("ddtrace.internal.settings.peer_service.SCHEMA_VERSION", schema_version): assert PeerServiceConfig().set_defaults_enabled == expected @@ -104,7 +104,7 @@ def test_peer_service_enablement(schema_peer_enabled): def test_tracer_hooks(): from ddtrace.constants import SPAN_KIND from ddtrace.ext import SpanKind - from ddtrace.settings.peer_service import PeerServiceConfig + from ddtrace.internal.settings.peer_service import PeerServiceConfig from tests.utils import DummyTracer peer_service_config = PeerServiceConfig() diff --git a/tests/internal/service_name/test_inferred_base_service.py b/tests/internal/service_name/test_inferred_base_service.py index 0883be2aa22..ac323223564 100644 --- a/tests/internal/service_name/test_inferred_base_service.py +++ b/tests/internal/service_name/test_inferred_base_service.py @@ -8,9 +8,9 @@ import pytest -from ddtrace.settings._inferred_base_service import PythonDetector -from ddtrace.settings._inferred_base_service import _module_exists -from ddtrace.settings._inferred_base_service import detect_service +from ddtrace.internal.settings._inferred_base_service import PythonDetector +from ddtrace.internal.settings._inferred_base_service import _module_exists +from ddtrace.internal.settings._inferred_base_service import detect_service @pytest.fixture diff --git a/tests/internal/symbol_db/test_config.py b/tests/internal/symbol_db/test_config.py index ebaa713e0c6..a369cdab369 100644 --- a/tests/internal/symbol_db/test_config.py +++ b/tests/internal/symbol_db/test_config.py @@ -1,4 +1,4 @@ -from ddtrace.settings.symbol_db import SymbolDatabaseConfig +from ddtrace.internal.settings.symbol_db import SymbolDatabaseConfig def test_symbol_db_includes_pattern(monkeypatch): diff --git a/tests/internal/test_database_monitoring.py b/tests/internal/test_database_monitoring.py index 171118c7791..6d7e41a2dc8 100644 --- a/tests/internal/test_database_monitoring.py +++ b/tests/internal/test_database_monitoring.py @@ -2,8 +2,8 @@ import pytest +from ddtrace.internal.settings import _database_monitoring from ddtrace.propagation._database_monitoring import default_sql_injector -from ddtrace.settings import _database_monitoring from tests.utils import override_env diff --git a/tests/internal/test_settings.py b/tests/internal/test_settings.py index bea7c6c1989..2e8369e1ea5 100644 --- a/tests/internal/test_settings.py +++ b/tests/internal/test_settings.py @@ -6,7 +6,7 @@ from ddtrace._trace.product import apm_tracing_rc from ddtrace.internal.remoteconfig import Payload -from ddtrace.settings._config import Config +from ddtrace.internal.settings._config import Config from tests.utils import remote_config_build_payload as build_payload @@ -606,7 +606,7 @@ def test_remoteconfig_header_tags(ddtrace_run_python_code_in_subprocess): def test_config_public_properties_and_methods(): # Regression test to prevent unexpected changes to public attributes in Config # By default most attributes should be private and set via Environment Variables - from ddtrace.settings._config import Config + from ddtrace.internal.settings._config import Config public_attrs = set() c = Config() diff --git a/tests/llmobs/test_llmobs_eval_metric_agent_writer.py b/tests/llmobs/test_llmobs_eval_metric_agent_writer.py index 7a80bda0cf0..d8a0a20b0f1 100644 --- a/tests/llmobs/test_llmobs_eval_metric_agent_writer.py +++ b/tests/llmobs/test_llmobs_eval_metric_agent_writer.py @@ -2,10 +2,10 @@ import mock +from ddtrace.internal.settings._agent import config as agent_config from ddtrace.llmobs._constants import EVAL_ENDPOINT from ddtrace.llmobs._constants import EVP_PROXY_AGENT_BASE_PATH from ddtrace.llmobs._writer import LLMObsEvalMetricWriter -from ddtrace.settings._agent import config as agent_config from tests.llmobs.test_llmobs_eval_metric_agentless_writer import _categorical_metric_event from tests.llmobs.test_llmobs_eval_metric_agentless_writer import _score_metric_event diff --git a/tests/llmobs/test_llmobs_span_agent_writer.py b/tests/llmobs/test_llmobs_span_agent_writer.py index 564db83e0b3..469108bf650 100644 --- a/tests/llmobs/test_llmobs_span_agent_writer.py +++ b/tests/llmobs/test_llmobs_span_agent_writer.py @@ -3,9 +3,9 @@ import mock from ddtrace.internal.ci_visibility.constants import EVP_PROXY_AGENT_BASE_PATH +from ddtrace.internal.settings._agent import config as agent_config from ddtrace.llmobs._constants import SPAN_ENDPOINT from ddtrace.llmobs._writer import LLMObsSpanWriter -from ddtrace.settings._agent import config as agent_config from tests.llmobs._utils import _chat_completion_event from tests.llmobs._utils import _completion_event from tests.llmobs._utils import _large_event diff --git a/tests/profiling/collector/test_memalloc.py b/tests/profiling/collector/test_memalloc.py index 54f1997a46b..28043f02d92 100644 --- a/tests/profiling/collector/test_memalloc.py +++ b/tests/profiling/collector/test_memalloc.py @@ -6,10 +6,10 @@ import pytest +from ddtrace.internal.settings.profiling import ProfilingConfig +from ddtrace.internal.settings.profiling import _derive_default_heap_sample_size from ddtrace.profiling.collector import memalloc from ddtrace.profiling.event import DDFrame -from ddtrace.settings.profiling import ProfilingConfig -from ddtrace.settings.profiling import _derive_default_heap_sample_size try: diff --git a/tests/profiling/suitespec.yml b/tests/profiling/suitespec.yml index 2cd2f4d051f..732fd31baca 100644 --- a/tests/profiling/suitespec.yml +++ b/tests/profiling/suitespec.yml @@ -30,7 +30,7 @@ components: - ddtrace/profiling/* - ddtrace/internal/datadog/profiling/* - ddtrace/internal/processor/endpoint_call_counter.py - - ddtrace/settings/profiling.py + - ddtrace/internal/settings/profiling.py core: - ddtrace/internal/__init__.py - ddtrace/internal/_exceptions.py @@ -73,7 +73,7 @@ components: - ddtrace/__init__.py - ddtrace/py.typed - ddtrace/version.py - - ddtrace/settings/_config.py + - ddtrace/internal/settings/_config.py - src/native/* bootstrap: - ddtrace/bootstrap/* diff --git a/tests/profiling_v2/collector/test_threading.py b/tests/profiling_v2/collector/test_threading.py index 6a9de6fa3d9..1cfbb42582a 100644 --- a/tests/profiling_v2/collector/test_threading.py +++ b/tests/profiling_v2/collector/test_threading.py @@ -730,7 +730,7 @@ def test_lock_enter_exit_events(self) -> None: [True, False], ) def test_class_member_lock(self, inspect_dir_enabled: bool) -> None: - with mock.patch("ddtrace.settings.profiling.config.lock.name_inspect_dir", inspect_dir_enabled): + with mock.patch("ddtrace.internal.settings.profiling.config.lock.name_inspect_dir", inspect_dir_enabled): expected_lock_name: Optional[str] = "foo_lock" if inspect_dir_enabled else None with self.collector_class(capture_pct=100): diff --git a/tests/profiling_v2/exporter/test_ddup.py b/tests/profiling_v2/exporter/test_ddup.py index 6ec350abfe3..f799bfe0e28 100644 --- a/tests/profiling_v2/exporter/test_ddup.py +++ b/tests/profiling_v2/exporter/test_ddup.py @@ -46,7 +46,7 @@ def test_tags_propagated(): from ddtrace.profiling.profiler import Profiler # noqa: I001 from ddtrace.internal.datadog.profiling import ddup - from ddtrace.settings.profiling import config + from ddtrace.internal.settings.profiling import config # DD_PROFILING_TAGS should override DD_TAGS assert config.tags["hello"] == "python" diff --git a/tests/profiling_v2/test_profiler.py b/tests/profiling_v2/test_profiler.py index 29743be6ef0..fd2cd861d9a 100644 --- a/tests/profiling_v2/test_profiler.py +++ b/tests/profiling_v2/test_profiler.py @@ -189,8 +189,8 @@ def test_libdd_failure_telemetry_logging(): failure_msg="mock failure message", is_available=False, ), mock.patch("ddtrace.internal.telemetry.telemetry_writer.add_log") as mock_add_log: + from ddtrace.internal.settings.profiling import config # noqa:F401 from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL - from ddtrace.settings.profiling import config # noqa:F401 mock_add_log.assert_called_once() call_args = mock_add_log.call_args @@ -240,8 +240,8 @@ def test_stack_v2_failure_telemetry_logging(): failure_msg="mock failure message", is_available=False, ), mock.patch("ddtrace.internal.telemetry.telemetry_writer.add_log") as mock_add_log: + from ddtrace.internal.settings.profiling import config # noqa: F401 from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL - from ddtrace.settings.profiling import config # noqa: F401 mock_add_log.assert_called_once() call_args = mock_add_log.call_args diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 2dd30bc6740..a9a8276f58e 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -78,7 +78,7 @@ components: - ddtrace/py.typed - ddtrace/version.py - ddtrace/_version.py - - ddtrace/settings/_config.py + - ddtrace/internal/settings/_config.py - src/native/* datastreams: - ddtrace/internal/datastreams/* @@ -99,7 +99,7 @@ components: - ddtrace/profiling/* - ddtrace/internal/datadog/profiling/* - ddtrace/internal/processor/endpoint_call_counter.py - - ddtrace/settings/profiling.py + - ddtrace/internal/settings/profiling.py remoteconfig: - ddtrace/internal/remoteconfig/* runtime: @@ -107,12 +107,12 @@ components: serverless: - ddtrace/internal/serverless/* settings: - - ddtrace/settings/* + - ddtrace/internal/settings/* sourcecode: - ddtrace/sourcecode/* symbol_db: - ddtrace/internal/symbol_db/* - - ddtrace/settings/symbol_db.py + - ddtrace/internal/settings/symbol_db.py telemetry: - ddtrace/internal/telemetry/* tracing: @@ -122,11 +122,10 @@ components: - ddtrace/_trace/* - ddtrace/trace/* - ddtrace/constants.py - - ddtrace/settings/__init__.py - - ddtrace/settings/_config.py - - ddtrace/settings/http.py - - ddtrace/settings/exceptions.py - - ddtrace/settings/integration.py + - ddtrace/internal/settings/__init__.py + - ddtrace/internal/settings/_config.py + - ddtrace/internal/settings/http.py + - ddtrace/internal/settings/integration.py - ddtrace/internal/_encoding.py* - ddtrace/internal/_tagset.py* - ddtrace/internal/_utils.* @@ -136,7 +135,7 @@ components: - ddtrace/internal/pack.h - ddtrace/internal/pack_template.h - ddtrace/internal/peer_service/* - - ddtrace/settings/peer_service.py + - ddtrace/internal/settings/peer_service.py - ddtrace/internal/processor/__init__.py - ddtrace/internal/processor/stats.py - ddtrace/internal/runtime/* diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index a02848dbac1..0d3ee1a9557 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -12,6 +12,8 @@ from ddtrace import config from ddtrace.internal.compat import PYTHON_VERSION_INFO +from ddtrace.internal.settings._agent import get_agent_hostname +from ddtrace.internal.settings._telemetry import config as telemetry_config import ddtrace.internal.telemetry from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL @@ -20,8 +22,6 @@ from ddtrace.internal.telemetry.writer import TelemetryWriter from ddtrace.internal.telemetry.writer import get_runtime_id from ddtrace.internal.utils.version import _pep440_to_semver -from ddtrace.settings._agent import get_agent_hostname -from ddtrace.settings._telemetry import config as telemetry_config from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.utils import call_program from tests.utils import override_global_config @@ -80,9 +80,9 @@ def test_app_started_event_configuration_override(test_agent_session, run_python # most configurations are reported when ddtrace.auto is imported import ddtrace.auto # report configurations not used by ddtrace.auto -import ddtrace.settings.symbol_db -import ddtrace.settings.dynamic_instrumentation -import ddtrace.settings.exception_replay +import ddtrace.internal.settings.symbol_db +import ddtrace.internal.settings.dynamic_instrumentation +import ddtrace.internal.settings.exception_replay """ env = os.environ.copy() diff --git a/tests/tracer/test_agent.py b/tests/tracer/test_agent.py index 451ec47c08f..6f9bf95b07f 100644 --- a/tests/tracer/test_agent.py +++ b/tests/tracer/test_agent.py @@ -3,8 +3,8 @@ from ddtrace.internal import agent from ddtrace.internal.agent import info +from ddtrace.internal.settings._agent import is_ipv6_hostname from ddtrace.internal.utils.http import verify_url -from ddtrace.settings._agent import is_ipv6_hostname @pytest.mark.parametrize( @@ -32,7 +32,7 @@ def test_hostname(): import os from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).hostname == os.environ.get("DD_AGENT_HOST") assert urlparse(config.dogstatsd_url).hostname == os.environ.get("DD_AGENT_HOST"), urlparse(config.dogstatsd_url) @@ -44,7 +44,7 @@ def test_hostname(): def test_trace_hostname(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).hostname == "monkey" @@ -53,7 +53,7 @@ def test_trace_hostname(): def test_hostname_not_set(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).hostname == "localhost" @@ -62,7 +62,7 @@ def test_hostname_not_set(): def test_trace_port(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).port == 9999 @@ -71,7 +71,7 @@ def test_trace_port(): def test_agent_port(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).port == 1235 @@ -80,7 +80,7 @@ def test_agent_port(): def test_trace_port_not_set(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.trace_agent_url).port == 8126 @@ -89,7 +89,7 @@ def test_trace_port_not_set(): def test_stats_port(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.dogstatsd_url).port == 1235 @@ -98,7 +98,7 @@ def test_stats_port(): def test_stats_port_not_set(): from urllib.parse import urlparse - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert urlparse(config.dogstatsd_url).port == 8125 @@ -117,7 +117,7 @@ def test_trace_url_uds(): import mock with mock.patch("os.path.exists", return_value=True): - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config assert config.trace_agent_url == "unix:///var/run/datadog/apm.socket" @@ -135,7 +135,7 @@ def test_trace_url_default(): # with nothing set by user, and the default UDS unavailable, we choose default http address import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.trace_agent_url == "http://localhost:8126" @@ -148,7 +148,7 @@ def test_trace_url_with_port(): # with port set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): url = config.trace_agent_url @@ -168,7 +168,7 @@ def test_trace_url_with_host(): # with host set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.trace_agent_url == "http://mars:8126", config.trace_agent_url @@ -186,7 +186,7 @@ def test_trace_url_with_host_and_port(): # with host and port set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.trace_agent_url == "http://mars:1235" @@ -199,7 +199,7 @@ def test_trace_url_with_uds_and_port(): # with port set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.trace_agent_url == "http://localhost:1235" @@ -218,7 +218,7 @@ def test_trace_url_with_uds_and_host(): # with host set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.trace_agent_url == "http://mars:8126" @@ -236,7 +236,7 @@ def test_trace_url_with_uds_host_and_port(): # with host and port set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.trace_agent_url == "http://mars:1235" @@ -249,7 +249,7 @@ def test_trace_url_with_uds_url_host_and_port(): # with port, host, and url set by user, and default UDS available, we choose url import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.trace_agent_url == "http://saturn:1111" @@ -262,7 +262,7 @@ def test_trace_url_with_url_host_and_port(): # with port, host, and url set by user, and default UDS unavailable, we choose url import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.trace_agent_url == "http://saturn:1111" @@ -281,7 +281,7 @@ def test_stats_url_default(): # with nothing set by user, and the default UDS unavailable, we choose default http address import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.dogstatsd_url == "udp://localhost:8125" @@ -300,7 +300,7 @@ def test_stats_url_with_port(): # with port set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.dogstatsd_url == "udp://localhost:1235" @@ -319,7 +319,7 @@ def test_stats_url_with_host(): # with host set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.dogstatsd_url == "udp://mars:8125" @@ -332,7 +332,7 @@ def test_stats_url_with_host_and_port(): # with host and port set by user, and default UDS unavailable, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.dogstatsd_url == "udp://mars:1235" @@ -351,7 +351,7 @@ def test_stats_url_with_uds_and_port(): # with port set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.dogstatsd_url == "udp://localhost:1235" @@ -370,7 +370,7 @@ def test_stats_url_with_uds_and_host(): # with host set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.dogstatsd_url == "udp://mars:8125" @@ -383,7 +383,7 @@ def test_stats_url_with_uds_host_and_port(): # with host and port set by user, and default UDS available, we choose user settings import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.dogstatsd_url == "udp://mars:1235" @@ -396,7 +396,7 @@ def test_stats_url_with_uds_url_host_and_port(): # with port, host, and url set by user, and default UDS available, we choose url import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=True): assert config.dogstatsd_url == "udp://saturn:1111" @@ -409,7 +409,7 @@ def test_stats_url_with_url_host_and_port(): # with port, host, and url set by user, and default UDS unavailable, we choose url import mock - from ddtrace.settings._agent import config + from ddtrace.internal.settings._agent import config with mock.patch("os.path.exists", return_value=False): assert config.dogstatsd_url == "udp://saturn:1111" diff --git a/tests/tracer/test_endpoint_config.py b/tests/tracer/test_endpoint_config.py index df35d43e243..a2eb6061bd4 100644 --- a/tests/tracer/test_endpoint_config.py +++ b/tests/tracer/test_endpoint_config.py @@ -6,7 +6,7 @@ from unittest import mock from ddtrace.internal.http import HTTPConnection -from ddtrace.settings.endpoint_config import fetch_config_from_endpoint +from ddtrace.internal.settings.endpoint_config import fetch_config_from_endpoint from tests.utils import override_env @@ -179,6 +179,6 @@ def test_set_config_endpoint_retries(caplog): ), mock.patch.object( HTTPConnection, "getresponse", new=mock_getresponse_enabled_after_4_retries ), mock.patch( - "ddtrace.settings.endpoint_config._get_retries", return_value=5 + "ddtrace.internal.settings.endpoint_config._get_retries", return_value=5 ): assert fetch_config_from_endpoint() == {"dd_iast_enabled": True} diff --git a/tests/tracer/test_env_vars.py b/tests/tracer/test_env_vars.py index 16b92cc49f2..eb9445571e1 100644 --- a/tests/tracer/test_env_vars.py +++ b/tests/tracer/test_env_vars.py @@ -50,7 +50,7 @@ def test_obfuscation_querystring_pattern_env_var( "-c", ( """import re;from ddtrace import config; -from ddtrace.settings._config import DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT; +from ddtrace.internal.settings._config import DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT; assert config._obfuscation_query_string_pattern == %s; assert config._global_query_string_obfuscation_disabled == %s; assert config._http_tag_query_string == %s diff --git a/tests/tracer/test_global_config.py b/tests/tracer/test_global_config.py index 761115c49d4..0d60f2319e7 100644 --- a/tests/tracer/test_global_config.py +++ b/tests/tracer/test_global_config.py @@ -4,8 +4,8 @@ import pytest from ddtrace import config as global_config -from ddtrace.settings._config import Config -from ddtrace.settings.integration import IntegrationConfig +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.integration import IntegrationConfig from ..utils import DummyTracer from ..utils import override_env @@ -59,7 +59,7 @@ def test_missing_integration(self): assert isinstance(e.value, AttributeError) assert e.value.args[0] == ( - " object has no attribute " + " object has no attribute " "integration_that_does_not_exist, integration_that_does_not_exist is not a valid configuration" ) diff --git a/tests/tracer/test_instance_config.py b/tests/tracer/test_instance_config.py index 615e439789c..d2539be8d34 100644 --- a/tests/tracer/test_instance_config.py +++ b/tests/tracer/test_instance_config.py @@ -5,7 +5,7 @@ from ddtrace import config from ddtrace._trace.pin import Pin -from ddtrace.settings.integration import IntegrationConfig +from ddtrace.internal.settings.integration import IntegrationConfig class InstanceConfigTestCase(TestCase): diff --git a/tests/tracer/test_settings.py b/tests/tracer/test_settings.py index 4bf8167764a..a63fee52574 100644 --- a/tests/tracer/test_settings.py +++ b/tests/tracer/test_settings.py @@ -1,9 +1,8 @@ import pytest -from ddtrace.internal.compat import PYTHON_VERSION_INFO -from ddtrace.settings._config import Config -from ddtrace.settings.http import HttpConfig -from ddtrace.settings.integration import IntegrationConfig +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.http import HttpConfig +from ddtrace.internal.settings.integration import IntegrationConfig from tests.utils import BaseTestCase from tests.utils import override_env @@ -201,72 +200,3 @@ def test_x_datadog_tags(env, expected): with override_env(env): _ = Config() assert expected == (_._x_datadog_tags_max_length, _._x_datadog_tags_enabled) - - -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") -@pytest.mark.subprocess() -def test_config_exception_deprecation(): - import warnings - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("default") - - from ddtrace.settings import ConfigException # noqa: F401 - - assert len(warns) == 1 - warn = warns[0] - - assert issubclass(warn.category, DeprecationWarning) - assert "ddtrace.settings.ConfigException is deprecated" in str(warn.message) - assert "4.0.0" in str(warn.message) # TODO: update the version - - -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") -@pytest.mark.subprocess() -def test_http_config_deprecation(): - import warnings - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("default") - - from ddtrace.settings import HttpConfig # noqa: F401 - - assert len(warns) == 1 - warn = warns[0] - assert issubclass(warn.category, DeprecationWarning) - assert "ddtrace.settings.HttpConfig is deprecated" in str(warn.message) - assert "4.0.0" in str(warn.message) # TODO: update the version - - -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") -@pytest.mark.subprocess() -def test_hooks_deprecation(): - import warnings - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("default") - - from ddtrace.settings import Hooks # noqa: F401 - - assert len(warns) == 1 - warn = warns[0] - assert issubclass(warn.category, DeprecationWarning) - assert "ddtrace.settings.Hooks is deprecated" in str(warn.message) - assert "4.0.0" in str(warn.message) # TODO: update the version - - -@pytest.mark.skipif(PYTHON_VERSION_INFO < (3, 10), reason="ddtrace under Python 3.9 is deprecated") -@pytest.mark.subprocess() -def test_integration_config_deprecation(): - import warnings - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("default") - - from ddtrace.settings import IntegrationConfig # noqa: F401 - - assert len(warns) == 1 - warn = warns[0] - assert issubclass(warn.category, DeprecationWarning) - assert "ddtrace.settings.IntegrationConfig is deprecated" in str(warn.message) - assert "4.0.0" in str(warn.message) # TODO: update the version diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index 65b79767706..b56dc4e3eca 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -21,11 +21,11 @@ from ddtrace.ext import http from ddtrace.ext import net from ddtrace.internal.compat import ensure_text +from ddtrace.internal.settings._config import Config +from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings._config import Config -from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import Context from ddtrace.trace import Span from tests.appsec.utils import asm_context @@ -511,7 +511,7 @@ def test_set_http_meta( assert span.get_tag(tag) == value -@mock.patch("ddtrace.settings._config.log") +@mock.patch("ddtrace.internal.settings._config.log") @pytest.mark.parametrize( "error_codes,status_code,error,log_call", [ @@ -540,7 +540,7 @@ def test_set_http_meta_custom_errors(mock_log, span, int_config, error_codes, st def test_set_http_meta_custom_errors_via_env(): from ddtrace import config from ddtrace.contrib.internal.trace_utils import set_http_meta - from ddtrace.settings.integration import IntegrationConfig + from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.trace import tracer config.myint = IntegrationConfig(config, "myint") @@ -1118,7 +1118,7 @@ def test_url_in_http_with_empty_obfuscation_regex(): from ddtrace import config from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import http - from ddtrace.settings.integration import IntegrationConfig + from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.trace import tracer assert config._obfuscation_query_string_pattern.pattern == b"", config._obfuscation_query_string_pattern @@ -1144,7 +1144,7 @@ def test_url_in_http_with_obfuscation_enabled_and_empty_regex(): from ddtrace import config from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import http - from ddtrace.settings.integration import IntegrationConfig + from ddtrace.internal.settings.integration import IntegrationConfig from ddtrace.trace import tracer # assert obfuscation is disabled when the regex is an empty string diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index bd5b7909f87..1baf91c5a44 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -34,10 +34,10 @@ from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.internal.serverless import has_aws_lambda_agent_extension from ddtrace.internal.serverless import in_aws_lambda +from ddtrace.internal.settings._config import Config from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import LogWriter -from ddtrace.settings._config import Config from ddtrace.trace import Context from ddtrace.trace import tracer as global_tracer from tests.subprocesstest import run_in_subprocess diff --git a/tests/utils.py b/tests/utils.py index c4958739be0..3910af11c96 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -40,6 +40,9 @@ from ddtrace.internal.packages import is_third_party from ddtrace.internal.remoteconfig import Payload from ddtrace.internal.schema import SCHEMA_VERSION +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings._database_monitoring import dbm_config +from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.internal.writer import AgentWriter @@ -48,9 +51,6 @@ from ddtrace.propagation._database_monitoring import listen as dbm_config_listen from ddtrace.propagation._database_monitoring import unlisten as dbm_config_unlisten from ddtrace.propagation.http import _DatadogMultiHeader -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings._database_monitoring import dbm_config -from ddtrace.settings.asm import config as asm_config from ddtrace.trace import Span from ddtrace.trace import Tracer from tests.subprocesstest import SubprocessTestCase @@ -189,7 +189,7 @@ def override_global_config(values): for key, value in values.items(): if key in asm_config_keys: setattr(asm_config, key, value) - # If ddtrace.settings.asm.config has changed, check _asm_can_be_enabled again + # If ddtrace.internal.settings.asm.config has changed, check _asm_can_be_enabled again asm_config._eval_asm_can_be_enabled() from ddtrace.appsec._processor import AppSecSpanProcessor From db6eb9842fc5e4707459faadeb0b25abc5d5a57d Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:36:19 -0400 Subject: [PATCH 12/42] chore: remove deprecated ddtrace.trace.Pin and Tracer method (#14896) This change removes deprecated attributes from the `ddtrace.trace` and `ddtrace._trace` modules. Note the base branch, a staging area for breaking changes slated for 4.0. --------- Co-authored-by: Brett Langdon --- ddtrace/_trace/tracer.py | 33 -------------- ddtrace/contrib/dbapi.py | 2 +- ddtrace/contrib/dbapi_async.py | 2 +- ddtrace/contrib/internal/aiomysql/__init__.py | 2 +- ddtrace/contrib/internal/aiopg/__init__.py | 2 +- ddtrace/contrib/internal/aioredis/__init__.py | 2 +- .../contrib/internal/anthropic/__init__.py | 2 +- ddtrace/contrib/internal/aredis/__init__.py | 2 +- ddtrace/contrib/internal/asyncpg/__init__.py | 2 +- ddtrace/contrib/internal/consul/__init__.py | 2 +- ddtrace/contrib/internal/crewai/__init__.py | 2 +- .../contrib/internal/google_genai/__init__.py | 2 +- .../internal/google_generativeai/__init__.py | 2 +- ddtrace/contrib/internal/graphql/__init__.py | 2 +- ddtrace/contrib/internal/grpc/__init__.py | 4 +- ddtrace/contrib/internal/httpx/__init__.py | 2 +- ddtrace/contrib/internal/jinja2/__init__.py | 2 +- ddtrace/contrib/internal/kafka/__init__.py | 2 +- .../contrib/internal/langgraph/__init__.py | 2 +- ddtrace/contrib/internal/mariadb/__init__.py | 2 +- ddtrace/contrib/internal/mcp/__init__.py | 2 +- ddtrace/contrib/internal/mysql/__init__.py | 2 +- ddtrace/contrib/internal/mysqldb/__init__.py | 2 +- ddtrace/contrib/internal/openai/__init__.py | 2 +- .../internal/openai_agents/__init__.py | 2 +- ddtrace/contrib/internal/psycopg/__init__.py | 2 +- .../contrib/internal/pymemcache/__init__.py | 2 +- ddtrace/contrib/internal/pymongo/__init__.py | 2 +- ddtrace/contrib/internal/pymysql/__init__.py | 2 +- ddtrace/contrib/internal/pyodbc/__init__.py | 2 +- ddtrace/contrib/internal/redis/__init__.py | 2 +- .../contrib/internal/rediscluster/__init__.py | 2 +- ddtrace/contrib/internal/rq/__init__.py | 2 +- .../contrib/internal/snowflake/__init__.py | 2 +- ddtrace/contrib/internal/sqlite3/__init__.py | 2 +- ddtrace/contrib/internal/valkey/patch.py | 2 +- ddtrace/contrib/internal/vertexai/__init__.py | 2 +- ddtrace/contrib/internal/vertica/__init__.py | 2 +- ddtrace/contrib/internal/yaaredis/__init__.py | 2 +- ddtrace/contrib/pylibmc.py | 2 +- ddtrace/contrib/requests.py | 2 +- ddtrace/contrib/sqlalchemy.py | 2 +- ddtrace/contrib/valkey.py | 2 +- ddtrace/trace/__init__.py | 18 -------- docs/api.rst | 3 -- docs/contributing-integrations.rst | 4 +- .../notes/pin-remove-46288db02ed90799.yaml | 8 ++++ tests/contrib/aiohttp/test_aiohttp_client.py | 4 +- tests/contrib/aredis/test_aredis.py | 2 +- tests/contrib/openai/test_openai_v1.py | 6 +-- tests/contrib/pytest/test_pytest.py | 2 +- tests/tracer/test_tracer.py | 45 ------------------- 52 files changed, 60 insertions(+), 151 deletions(-) create mode 100644 releasenotes/notes/pin-remove-46288db02ed90799.yaml diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index a2e7175b8c0..091286187ed 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -56,11 +56,9 @@ from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.settings.peer_service import _ps_config from ddtrace.internal.utils import _get_metas_to_propagate -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.formats import format_trace_id from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import HTTPWriter -from ddtrace.vendor.debtcollector.removals import remove from ddtrace.version import get_version @@ -201,37 +199,6 @@ def _atexit(self) -> None: ) self.shutdown(timeout=self.SHUTDOWN_TIMEOUT) - @remove( - message="on_start_span is being removed with no replacement", - removal_version="4.0.0", - category=DDTraceDeprecationWarning, - ) - def on_start_span(self, func: Callable[[Span], None]) -> Callable[[Span], None]: - """Register a function to execute when a span start. - - Can be used as a decorator. - - :param func: The function to call when starting a span. - The started span will be passed as argument. - """ - core.on("trace.span_start", callback=func) - return func - - @remove( - message="deregister_on_start_span is being removed with no replacement", - removal_version="4.0.0", - category=DDTraceDeprecationWarning, - ) - def deregister_on_start_span(self, func: Callable[[Span], None]) -> Callable[[Span], None]: - """Unregister a function registered to execute when a span starts. - - Can be used as a decorator. - - :param func: The function to stop calling when starting a span. - """ - core.reset_listeners("trace.span_start", callback=func) - return func - def sample(self, span): self._sampler.sample(span) diff --git a/ddtrace/contrib/dbapi.py b/ddtrace/contrib/dbapi.py index e364c542a22..595a2c1f7fb 100644 --- a/ddtrace/contrib/dbapi.py +++ b/ddtrace/contrib/dbapi.py @@ -10,13 +10,13 @@ from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value +from .._trace.pin import Pin from ..constants import _SPAN_MEASURED_KEY from ..constants import SPAN_KIND from ..ext import SpanKind from ..ext import SpanTypes from ..ext import db from ..ext import sql -from ..trace import Pin from .internal.trace_utils import ext_service from .internal.trace_utils import iswrapped diff --git a/ddtrace/contrib/dbapi_async.py b/ddtrace/contrib/dbapi_async.py index 05c4ea9282e..d7bd3e520f0 100644 --- a/ddtrace/contrib/dbapi_async.py +++ b/ddtrace/contrib/dbapi_async.py @@ -5,11 +5,11 @@ from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value +from .._trace.pin import Pin from ..constants import _SPAN_MEASURED_KEY from ..constants import SPAN_KIND from ..ext import SpanKind from ..ext import SpanTypes -from ..trace import Pin from .dbapi import TracedConnection from .dbapi import TracedCursor from .internal.trace_utils import ext_service diff --git a/ddtrace/contrib/internal/aiomysql/__init__.py b/ddtrace/contrib/internal/aiomysql/__init__.py index 5b060571309..4aca898853e 100644 --- a/ddtrace/contrib/internal/aiomysql/__init__.py +++ b/ddtrace/contrib/internal/aiomysql/__init__.py @@ -19,7 +19,7 @@ To configure the integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import asyncio import aiomysql diff --git a/ddtrace/contrib/internal/aiopg/__init__.py b/ddtrace/contrib/internal/aiopg/__init__.py index a419df5dbbf..7c6bedf6d6d 100644 --- a/ddtrace/contrib/internal/aiopg/__init__.py +++ b/ddtrace/contrib/internal/aiopg/__init__.py @@ -2,7 +2,7 @@ Instrument aiopg to report a span for each executed Postgres queries:: from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import aiopg # If not patched yet, you can patch aiopg specifically diff --git a/ddtrace/contrib/internal/aioredis/__init__.py b/ddtrace/contrib/internal/aioredis/__init__.py index 7abbd826a3c..3fcc7750952 100644 --- a/ddtrace/contrib/internal/aioredis/__init__.py +++ b/ddtrace/contrib/internal/aioredis/__init__.py @@ -55,7 +55,7 @@ ``Pin`` API:: import aioredis - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin myaioredis = aioredis.Aioredis() Pin.override(myaioredis, service="myaioredis") diff --git a/ddtrace/contrib/internal/anthropic/__init__.py b/ddtrace/contrib/internal/anthropic/__init__.py index 81e62a6083b..f066246b656 100644 --- a/ddtrace/contrib/internal/anthropic/__init__.py +++ b/ddtrace/contrib/internal/anthropic/__init__.py @@ -77,7 +77,7 @@ import anthropic from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(anthropic, service="my-anthropic-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/aredis/__init__.py b/ddtrace/contrib/internal/aredis/__init__.py index 1ffac72fa36..03841d14c17 100644 --- a/ddtrace/contrib/internal/aredis/__init__.py +++ b/ddtrace/contrib/internal/aredis/__init__.py @@ -53,7 +53,7 @@ To configure particular aredis instances use the :class:`Pin ` API:: import aredis - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = aredis.StrictRedis(host="localhost", port=6379) diff --git a/ddtrace/contrib/internal/asyncpg/__init__.py b/ddtrace/contrib/internal/asyncpg/__init__.py index 233cde9f51c..90932dbe440 100644 --- a/ddtrace/contrib/internal/asyncpg/__init__.py +++ b/ddtrace/contrib/internal/asyncpg/__init__.py @@ -38,7 +38,7 @@ basis use the ``Pin`` API:: import asyncpg - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin conn = asyncpg.connect("postgres://localhost:5432") Pin.override(conn, service="custom-service") diff --git a/ddtrace/contrib/internal/consul/__init__.py b/ddtrace/contrib/internal/consul/__init__.py index fa159309411..f2b9bd536ee 100644 --- a/ddtrace/contrib/internal/consul/__init__.py +++ b/ddtrace/contrib/internal/consul/__init__.py @@ -6,7 +6,7 @@ :: from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import consul # If not patched yet, you can patch consul specifically diff --git a/ddtrace/contrib/internal/crewai/__init__.py b/ddtrace/contrib/internal/crewai/__init__.py index 4753fc4722d..8f6eb43829a 100644 --- a/ddtrace/contrib/internal/crewai/__init__.py +++ b/ddtrace/contrib/internal/crewai/__init__.py @@ -37,7 +37,7 @@ ``Pin`` API:: import crewai - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(crewai, service="my-crewai-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/google_genai/__init__.py b/ddtrace/contrib/internal/google_genai/__init__.py index 237a4e43ca0..61bce41aa49 100644 --- a/ddtrace/contrib/internal/google_genai/__init__.py +++ b/ddtrace/contrib/internal/google_genai/__init__.py @@ -41,7 +41,7 @@ ``Pin`` API:: from google import genai - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(genai, service="my-google-genai-service") """ diff --git a/ddtrace/contrib/internal/google_generativeai/__init__.py b/ddtrace/contrib/internal/google_generativeai/__init__.py index 963b80e7494..3247354ee17 100644 --- a/ddtrace/contrib/internal/google_generativeai/__init__.py +++ b/ddtrace/contrib/internal/google_generativeai/__init__.py @@ -74,7 +74,7 @@ import google.generativeai as genai from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(genai, service="my-gemini-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/graphql/__init__.py b/ddtrace/contrib/internal/graphql/__init__.py index e22aef69407..42ac5eeafec 100644 --- a/ddtrace/contrib/internal/graphql/__init__.py +++ b/ddtrace/contrib/internal/graphql/__init__.py @@ -45,7 +45,7 @@ To configure the graphql integration using the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import graphql Pin.override(graphql, service="mygraphql") diff --git a/ddtrace/contrib/internal/grpc/__init__.py b/ddtrace/contrib/internal/grpc/__init__.py index 5713b6779ad..f29cab70eaf 100644 --- a/ddtrace/contrib/internal/grpc/__init__.py +++ b/ddtrace/contrib/internal/grpc/__init__.py @@ -46,7 +46,7 @@ import grpc from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin patch(grpc=True) @@ -63,7 +63,7 @@ from grpc.framework.foundation import logging_pool from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin patch(grpc=True) diff --git a/ddtrace/contrib/internal/httpx/__init__.py b/ddtrace/contrib/internal/httpx/__init__.py index 3d8087fbba1..aedd8912c5d 100644 --- a/ddtrace/contrib/internal/httpx/__init__.py +++ b/ddtrace/contrib/internal/httpx/__init__.py @@ -60,7 +60,7 @@ To configure particular ``httpx`` client instances use the :class:`Pin ` API:: import httpx - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = httpx.Client() # Override service name for this instance diff --git a/ddtrace/contrib/internal/jinja2/__init__.py b/ddtrace/contrib/internal/jinja2/__init__.py index 94683ebe5c3..3cf7ee6767b 100644 --- a/ddtrace/contrib/internal/jinja2/__init__.py +++ b/ddtrace/contrib/internal/jinja2/__init__.py @@ -16,7 +16,7 @@ The library can be configured globally and per instance, using the Configuration API:: from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin # Change service name globally config.jinja2['service_name'] = 'jinja-templates' diff --git a/ddtrace/contrib/internal/kafka/__init__.py b/ddtrace/contrib/internal/kafka/__init__.py index 366dad9bb7d..1188e9f1999 100644 --- a/ddtrace/contrib/internal/kafka/__init__.py +++ b/ddtrace/contrib/internal/kafka/__init__.py @@ -40,7 +40,7 @@ To configure the kafka integration using the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin from ddtrace import patch # Make sure to patch before importing confluent_kafka diff --git a/ddtrace/contrib/internal/langgraph/__init__.py b/ddtrace/contrib/internal/langgraph/__init__.py index ea9655193fc..eff34f7e2ae 100644 --- a/ddtrace/contrib/internal/langgraph/__init__.py +++ b/ddtrace/contrib/internal/langgraph/__init__.py @@ -31,6 +31,6 @@ ``Pin`` API:: import langgraph - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(langgraph, service="my-langgraph-service") """ diff --git a/ddtrace/contrib/internal/mariadb/__init__.py b/ddtrace/contrib/internal/mariadb/__init__.py index 1ef08422a00..59d8e306236 100644 --- a/ddtrace/contrib/internal/mariadb/__init__.py +++ b/ddtrace/contrib/internal/mariadb/__init__.py @@ -34,7 +34,7 @@ To configure the mariadb integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin from ddtrace import patch # Make sure to patch before importing mariadb diff --git a/ddtrace/contrib/internal/mcp/__init__.py b/ddtrace/contrib/internal/mcp/__init__.py index 825d65b7931..b737f1cfbdf 100644 --- a/ddtrace/contrib/internal/mcp/__init__.py +++ b/ddtrace/contrib/internal/mcp/__init__.py @@ -38,7 +38,7 @@ ``Pin`` API:: import mcp - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(mcp, service="my-mcp-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/mysql/__init__.py b/ddtrace/contrib/internal/mysql/__init__.py index 3336839bcf5..ba9086abb12 100644 --- a/ddtrace/contrib/internal/mysql/__init__.py +++ b/ddtrace/contrib/internal/mysql/__init__.py @@ -41,7 +41,7 @@ To configure the mysql integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin # Make sure to import mysql.connector and not the 'connect' function, # otherwise you won't have access to the patched version import mysql.connector diff --git a/ddtrace/contrib/internal/mysqldb/__init__.py b/ddtrace/contrib/internal/mysqldb/__init__.py index 46a5e27de7b..0cfe8158071 100644 --- a/ddtrace/contrib/internal/mysqldb/__init__.py +++ b/ddtrace/contrib/internal/mysqldb/__init__.py @@ -55,7 +55,7 @@ # Make sure to import MySQLdb and not the 'connect' function, # otherwise you won't have access to the patched version - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import MySQLdb # This will report a span with the default settings diff --git a/ddtrace/contrib/internal/openai/__init__.py b/ddtrace/contrib/internal/openai/__init__.py index 0642bbb0881..faae901a39e 100644 --- a/ddtrace/contrib/internal/openai/__init__.py +++ b/ddtrace/contrib/internal/openai/__init__.py @@ -114,7 +114,7 @@ import openai from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(openai, service="my-openai-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/openai_agents/__init__.py b/ddtrace/contrib/internal/openai_agents/__init__.py index ff3cdd340fc..53f331dabee 100644 --- a/ddtrace/contrib/internal/openai_agents/__init__.py +++ b/ddtrace/contrib/internal/openai_agents/__init__.py @@ -37,7 +37,7 @@ ``Pin`` API:: import agents - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(agents, service="my-agents-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/psycopg/__init__.py b/ddtrace/contrib/internal/psycopg/__init__.py index 0c1e134bb15..3f6668961e2 100644 --- a/ddtrace/contrib/internal/psycopg/__init__.py +++ b/ddtrace/contrib/internal/psycopg/__init__.py @@ -50,7 +50,7 @@ To configure the psycopg integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import psycopg db = psycopg.connect(connection_factory=factory) diff --git a/ddtrace/contrib/internal/pymemcache/__init__.py b/ddtrace/contrib/internal/pymemcache/__init__.py index 066bb5653e6..cb874460919 100644 --- a/ddtrace/contrib/internal/pymemcache/__init__.py +++ b/ddtrace/contrib/internal/pymemcache/__init__.py @@ -3,7 +3,7 @@ ``import ddtrace.auto`` will automatically patch the pymemcache ``Client``:: from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin # If not patched yet, patch pymemcache specifically patch(pymemcache=True) diff --git a/ddtrace/contrib/internal/pymongo/__init__.py b/ddtrace/contrib/internal/pymongo/__init__.py index f1210f0047d..d1b2f7d19ab 100644 --- a/ddtrace/contrib/internal/pymongo/__init__.py +++ b/ddtrace/contrib/internal/pymongo/__init__.py @@ -9,7 +9,7 @@ # Be sure to import pymongo and not pymongo.MongoClient directly, # otherwise you won't have access to the patched version from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import pymongo # If not patched yet, you can patch pymongo specifically diff --git a/ddtrace/contrib/internal/pymysql/__init__.py b/ddtrace/contrib/internal/pymysql/__init__.py index d219e46eccd..631e9594d66 100644 --- a/ddtrace/contrib/internal/pymysql/__init__.py +++ b/ddtrace/contrib/internal/pymysql/__init__.py @@ -41,7 +41,7 @@ To configure the integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin from pymysql import connect # This will report a span with the default settings diff --git a/ddtrace/contrib/internal/pyodbc/__init__.py b/ddtrace/contrib/internal/pyodbc/__init__.py index 0a2d46d5e70..d074aaa2387 100644 --- a/ddtrace/contrib/internal/pyodbc/__init__.py +++ b/ddtrace/contrib/internal/pyodbc/__init__.py @@ -41,7 +41,7 @@ To configure the integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import pyodbc # This will report a span with the default settings diff --git a/ddtrace/contrib/internal/redis/__init__.py b/ddtrace/contrib/internal/redis/__init__.py index 3204fade8df..49a7fd52027 100644 --- a/ddtrace/contrib/internal/redis/__init__.py +++ b/ddtrace/contrib/internal/redis/__init__.py @@ -55,7 +55,7 @@ To configure particular redis instances use the :class:`Pin ` API:: import redis - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = redis.StrictRedis(host="localhost", port=6379) diff --git a/ddtrace/contrib/internal/rediscluster/__init__.py b/ddtrace/contrib/internal/rediscluster/__init__.py index 05975277291..1fc846fa8aa 100644 --- a/ddtrace/contrib/internal/rediscluster/__init__.py +++ b/ddtrace/contrib/internal/rediscluster/__init__.py @@ -4,7 +4,7 @@ :: from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import rediscluster # If not patched yet, you can patch redis specifically diff --git a/ddtrace/contrib/internal/rq/__init__.py b/ddtrace/contrib/internal/rq/__init__.py index 596c0c420f6..28606b7a9ef 100644 --- a/ddtrace/contrib/internal/rq/__init__.py +++ b/ddtrace/contrib/internal/rq/__init__.py @@ -28,7 +28,7 @@ To override the service name for a queue:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin connection = redis.Redis() queue = rq.Queue(connection=connection) diff --git a/ddtrace/contrib/internal/snowflake/__init__.py b/ddtrace/contrib/internal/snowflake/__init__.py index 20ca3021cf3..6207b854aa3 100644 --- a/ddtrace/contrib/internal/snowflake/__init__.py +++ b/ddtrace/contrib/internal/snowflake/__init__.py @@ -40,7 +40,7 @@ To configure the integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin from snowflake.connector import connect # This will report a span with the default settings diff --git a/ddtrace/contrib/internal/sqlite3/__init__.py b/ddtrace/contrib/internal/sqlite3/__init__.py index 351d639b182..c085e69e96d 100644 --- a/ddtrace/contrib/internal/sqlite3/__init__.py +++ b/ddtrace/contrib/internal/sqlite3/__init__.py @@ -41,7 +41,7 @@ To configure the integration on an per-connection basis use the ``Pin`` API:: - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import sqlite3 # This will report a span with the default settings diff --git a/ddtrace/contrib/internal/valkey/patch.py b/ddtrace/contrib/internal/valkey/patch.py index 68edd4deb20..ba8b794962b 100644 --- a/ddtrace/contrib/internal/valkey/patch.py +++ b/ddtrace/contrib/internal/valkey/patch.py @@ -55,7 +55,7 @@ To configure particular valkey instances use the :class:`Pin ` API:: import valkey - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = valkey.StrictValkey(host="localhost", port=6379) diff --git a/ddtrace/contrib/internal/vertexai/__init__.py b/ddtrace/contrib/internal/vertexai/__init__.py index 25e5fdc081b..e3fbdb24a69 100644 --- a/ddtrace/contrib/internal/vertexai/__init__.py +++ b/ddtrace/contrib/internal/vertexai/__init__.py @@ -78,7 +78,7 @@ import vertexai from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin Pin.override(vertexai, service="my-vertexai-service") """ # noqa: E501 diff --git a/ddtrace/contrib/internal/vertica/__init__.py b/ddtrace/contrib/internal/vertica/__init__.py index df997f5946b..1007efe1b68 100644 --- a/ddtrace/contrib/internal/vertica/__init__.py +++ b/ddtrace/contrib/internal/vertica/__init__.py @@ -28,7 +28,7 @@ ``Pin`` API:: from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin patch(vertica=True) import vertica_python diff --git a/ddtrace/contrib/internal/yaaredis/__init__.py b/ddtrace/contrib/internal/yaaredis/__init__.py index 65917b03c29..4fb0687ec6c 100644 --- a/ddtrace/contrib/internal/yaaredis/__init__.py +++ b/ddtrace/contrib/internal/yaaredis/__init__.py @@ -53,7 +53,7 @@ To configure particular yaaredis instances use the :class:`Pin ` API:: import yaaredis - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = yaaredis.StrictRedis(host="localhost", port=6379) diff --git a/ddtrace/contrib/pylibmc.py b/ddtrace/contrib/pylibmc.py index c894b1fa5e2..8a9dc193b5f 100644 --- a/ddtrace/contrib/pylibmc.py +++ b/ddtrace/contrib/pylibmc.py @@ -6,7 +6,7 @@ # Be sure to import pylibmc and not pylibmc.Client directly, # otherwise you won't have access to the patched version from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin import pylibmc # If not patched yet, you can patch pylibmc specifically diff --git a/ddtrace/contrib/requests.py b/ddtrace/contrib/requests.py index 2f289a467e8..747facfe98e 100644 --- a/ddtrace/contrib/requests.py +++ b/ddtrace/contrib/requests.py @@ -65,7 +65,7 @@ use the config API:: from ddtrace import config - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin from requests import Session session = Session() diff --git a/ddtrace/contrib/sqlalchemy.py b/ddtrace/contrib/sqlalchemy.py index 04bffa87b85..e69e6b9c0a8 100644 --- a/ddtrace/contrib/sqlalchemy.py +++ b/ddtrace/contrib/sqlalchemy.py @@ -8,7 +8,7 @@ # patch before importing `create_engine` from ddtrace import patch - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin patch(sqlalchemy=True) # use SQLAlchemy as usual diff --git a/ddtrace/contrib/valkey.py b/ddtrace/contrib/valkey.py index c898aff012d..cf4d942c3ee 100644 --- a/ddtrace/contrib/valkey.py +++ b/ddtrace/contrib/valkey.py @@ -55,7 +55,7 @@ To configure particular valkey instances use the :class:`Pin ` API:: import valkey - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin client = valkey.StrictValkey(host="localhost", port=6379) diff --git a/ddtrace/trace/__init__.py b/ddtrace/trace/__init__.py index d89d8e09944..6eacedba8eb 100644 --- a/ddtrace/trace/__init__.py +++ b/ddtrace/trace/__init__.py @@ -1,14 +1,9 @@ -from typing import Any - from ddtrace._trace.context import Context from ddtrace._trace.filters import TraceFilter -from ddtrace._trace.pin import Pin as _Pin from ddtrace._trace.provider import BaseContextProvider from ddtrace._trace.span import Span from ddtrace._trace.tracer import Tracer from ddtrace.internal import core -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate # a global tracer instance with integration settings @@ -16,22 +11,9 @@ core.tracer = tracer # type: ignore -def __getattr__(name: str) -> Any: - if name == "Pin": - deprecate( - prefix="ddtrace.trace.Pin is deprecated", - message="Please use environment variables for configuration instead", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - return _Pin - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") - - __all__ = [ "BaseContextProvider", "Context", - "Pin", "TraceFilter", "Tracer", "Span", diff --git a/docs/api.rst b/docs/api.rst index 1483d449a95..f2db84ca1fa 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -17,9 +17,6 @@ Tracing .. autoclass:: ddtrace.trace.Span :members: -.. autoclass:: ddtrace.trace.Pin - :members: - .. autoclass:: ddtrace.trace.Context :members: :undoc-members: diff --git a/docs/contributing-integrations.rst b/docs/contributing-integrations.rst index 0ec2289013d..9aa848b51df 100644 --- a/docs/contributing-integrations.rst +++ b/docs/contributing-integrations.rst @@ -22,7 +22,7 @@ include Tracing Spans and the AppSec WAF. Integrations should avoid exposing a public API unless it is absolutely necessary. Users should be able to configure the integration by setting environment variables or using the Pin API. For cases where a public API is necessary, integrations -should expose the API in ``ddtrace.contrib..py``. +should expose the API in ``ddtrace.contrib..py``. Integrations should define a ``ddtrace.contrib.internal..__init__.py`` module that contains a doc string describing the integration and it's supported configurations. This module should be referenced in the ``docs/integrations.rst`` file. @@ -37,7 +37,7 @@ into the runtime execution of third-party libraries. The essential task of writi the functions in the third-party library that would serve as useful entrypoints and wrapping them with ``wrap_function_wrapper``. There are exceptions, but this is generally a useful starting point. -The Pin API in ``ddtrace.trace.Pin`` is used to configure the instrumentation at runtime. It provides a ``Pin`` class +The Pin API in ``ddtrace._trace.pin.Pin`` is used to configure the instrumentation at runtime. It provides a ``Pin`` class that can store configuration data in memory in a manner that is accessible from within functions wrapped by Wrapt. ``Pin`` objects are most often used for storing configuration data scoped to a given integration, such as enable/disable flags and service name overrides. diff --git a/releasenotes/notes/pin-remove-46288db02ed90799.yaml b/releasenotes/notes/pin-remove-46288db02ed90799.yaml new file mode 100644 index 00000000000..ebb670c5633 --- /dev/null +++ b/releasenotes/notes/pin-remove-46288db02ed90799.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + tracing: The deprecated ``Tracer.on_start_span`` method has been removed. + - | + tracing: The deprecated ``Tracer.deregister_on_start_span` method has been removed. + - | + tracing: The deprecated ``ddtrace.trace.Pin`` has been removed. diff --git a/tests/contrib/aiohttp/test_aiohttp_client.py b/tests/contrib/aiohttp/test_aiohttp_client.py index 76595f6c408..c8d18c485f8 100644 --- a/tests/contrib/aiohttp/test_aiohttp_client.py +++ b/tests/contrib/aiohttp/test_aiohttp_client.py @@ -101,7 +101,7 @@ async def test_distributed_tracing_disabled(ddtrace_run_python_code_in_subproces import asyncio import sys import aiohttp -from ddtrace.trace import Pin +from ddtrace._trace.pin import Pin from tests.contrib.aiohttp.test_aiohttp_client import URL async def test(): @@ -184,7 +184,7 @@ def test_configure_service_name_pin(ddtrace_run_python_code_in_subprocess): import asyncio import sys import aiohttp -from ddtrace.trace import Pin +from ddtrace._trace.pin import Pin from tests.contrib.aiohttp.test_aiohttp_client import URL_200 async def test(): diff --git a/tests/contrib/aredis/test_aredis.py b/tests/contrib/aredis/test_aredis.py index a05e70fcdb4..f374c67668a 100644 --- a/tests/contrib/aredis/test_aredis.py +++ b/tests/contrib/aredis/test_aredis.py @@ -151,7 +151,7 @@ def test_schematization_of_service_and_operation(ddtrace_run_python_code_in_subp import pytest import sys from tests.conftest import * -from ddtrace.trace import Pin +from ddtrace._trace.pin import Pin import aredis from tests.contrib.config import REDIS_CONFIG from tests.contrib.aredis.test_aredis import traced_aredis diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index f19e4dc567e..2290ca04191 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -886,7 +886,7 @@ def test_integration_sync(openai_api_key, ddtrace_run_python_code_in_subprocess) import ddtrace from tests.contrib.openai.conftest import FilterOrg from tests.contrib.openai.test_openai_v1 import get_openai_vcr -pin = ddtrace.trace.Pin.get_from(openai) +pin = ddtrace._trace.pin.Pin.get_from(openai) pin.tracer.configure(trace_processors=[FilterOrg()]) with get_openai_vcr(subdirectory_name="v1").use_cassette("completion.yaml"): client = openai.OpenAI() @@ -927,7 +927,7 @@ def test_integration_async(openai_api_key, ddtrace_run_python_code_in_subprocess import ddtrace from tests.contrib.openai.conftest import FilterOrg from tests.contrib.openai.test_openai_v1 import get_openai_vcr -pin = ddtrace.trace.Pin.get_from(openai) +pin = ddtrace._trace.pin.Pin.get_from(openai) pin.tracer.configure(trace_processors=[FilterOrg()]) async def task(): with get_openai_vcr(subdirectory_name="v1").use_cassette("completion.yaml"): @@ -1130,7 +1130,7 @@ def test_integration_service_name(openai_api_key, ddtrace_run_python_code_in_sub import ddtrace from tests.contrib.openai.conftest import FilterOrg from tests.contrib.openai.test_openai_v1 import get_openai_vcr -pin = ddtrace.trace.Pin.get_from(openai) +pin = ddtrace._trace.pin.Pin.get_from(openai) pin.tracer.configure(trace_processors=[FilterOrg()]) with get_openai_vcr(subdirectory_name="v1").use_cassette("completion.yaml"): client = openai.OpenAI() diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index d9a540bfd87..3413ae55e2d 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -769,7 +769,7 @@ def test_dd_origin_tag_propagated_to_every_span(self): """ import pytest import ddtrace - from ddtrace.trace import Pin + from ddtrace._trace.pin import Pin def test_service(ddtracer): with ddtracer.trace("SPAN2") as span2: diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 1baf91c5a44..f251a72ea4c 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -35,7 +35,6 @@ from ddtrace.internal.serverless import has_aws_lambda_agent_extension from ddtrace.internal.serverless import in_aws_lambda from ddtrace.internal.settings._config import Config -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import LogWriter from ddtrace.trace import Context @@ -1032,50 +1031,6 @@ def test_tracer_runtime_tags_cross_execution(tracer): assert span.get_metric(PID) is not None -def test_start_span_hooks(): - t = DummyTracer() - - result = {} - - with pytest.warns(DDTraceDeprecationWarning): - - @t.on_start_span - def store_span(span): - result["span"] = span - - try: - span = t.start_span("hello") - - assert span == result["span"] - span.finish() - finally: - # Cleanup after the test is done - # DEV: Since we use the core API for these hooks, - # they are not isolated to a single tracer instance - with pytest.warns(DDTraceDeprecationWarning): - t.deregister_on_start_span(store_span) - - -def test_deregister_start_span_hooks(): - t = DummyTracer() - - result = {} - - with pytest.warns(DDTraceDeprecationWarning): - - @t.on_start_span - def store_span(span): - result["span"] = span - - with pytest.warns(DDTraceDeprecationWarning): - t.deregister_on_start_span(store_span) - - with t.start_span("hello"): - pass - - assert result == {} - - @pytest.mark.subprocess(parametrize={"DD_TRACE_ENABLED": ["true", "false"]}) def test_enable(): import os From 2821027eaab65e3ca7bb6932acb873430b18781c Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 31 Oct 2025 13:05:55 -0400 Subject: [PATCH 13/42] update system-tests --- .github/workflows/system-tests.yml | 6 +++--- .gitlab-ci.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index a8229553fa9..58dc625191c 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' + ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' + ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f053b033c1d93c95c3b76882b2aedf9b9929aff2' + ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c936a14a553..289b35495a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "f053b033c1d93c95c3b76882b2aedf9b9929aff2" + SYSTEM_TESTS_REF: "173e1e83468e28274bc4631f1ee2afc4c2620c4e" default: interruptible: true From 108c8fc4cb6ab0d63099b7ec36dd448c8683d337 Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Fri, 31 Oct 2025 19:56:53 -0400 Subject: [PATCH 14/42] chore(llmobs): remove tiktoken dependency (#14805) ## Description [MLOB-4128] Drops the optional tiktoken dependency from ddtrace. Also removes tiktoken estimation of tokens from the openai integration. This was only used by customers that explicitly rejected streamed token metrics from streamed chat/responses, which is an extreme minority of users. We made the call that this optional dependency wasn't worth the maintenance burden (difficulty in updating python versions, other third party libraries, etc) in our ddtrace 4.0 major release. We'll just default to estimating the token count with our super simple heuristic if token counts are not provided by openai. This PR also updates testing to test openai==2.2.0. ## Testing ## Risks ## Additional Notes [MLOB-4128]: https://datadoghq.atlassian.net/browse/MLOB-4128?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .riot/requirements/132e4bd.txt | 45 ++++++++ .riot/requirements/134082f.txt | 45 ++++++++ .../requirements/{d3fea5a.txt => 14aa6df.txt} | 16 +-- .riot/requirements/1547cc9.txt | 49 ++++++++ .riot/requirements/162cf2e.txt | 45 ++++++++ .riot/requirements/164ce6e.txt | 49 -------- .riot/requirements/16a63d7.txt | 45 ++++++++ .../requirements/{75757f2.txt => 1882fe7.txt} | 10 +- .riot/requirements/1911f94.txt | 51 --------- .../requirements/{5f016ad.txt => 19be394.txt} | 16 +-- .riot/requirements/1a0657d.txt | 49 -------- .riot/requirements/1a18a5a.txt | 51 --------- .riot/requirements/1b544ab.txt | 47 ++++++++ .riot/requirements/1d14cdc.txt | 49 ++++++++ .riot/requirements/1dd6795.txt | 49 -------- .riot/requirements/51ae308.txt | 45 ++++++++ .riot/requirements/663ca38.txt | 45 ++++++++ .riot/requirements/6d1e866.txt | 45 ++++++++ .riot/requirements/85ff44d.txt | 49 -------- .../requirements/{1d30a5c.txt => 95d28c3.txt} | 16 +-- .riot/requirements/a2b9112.txt | 45 ++++++++ .riot/requirements/a827c2f.txt | 49 ++++++++ .../requirements/{9a08ceb.txt => a9f0bf3.txt} | 18 ++- .riot/requirements/bbcdb10.txt | 47 ++++++++ .riot/requirements/bd89eb3.txt | 47 ++++++++ .riot/requirements/c050b53.txt | 49 -------- .riot/requirements/c5399d2.txt | 49 -------- .riot/requirements/e3b63a1.txt | 53 --------- .riot/requirements/e9c67e1.txt | 53 --------- .riot/requirements/ec404a0.txt | 45 ++++++++ .../integration_registry/registry.yaml | 2 +- ddtrace/contrib/internal/openai/__init__.py | 2 +- ddtrace/llmobs/_integrations/openai.py | 7 +- ddtrace/llmobs/_integrations/utils.py | 60 ++-------- lib-injection/sources/requirements.csv | 1 - pyproject.toml | 3 - ...ade-tiktoken-removed-a10db578f5354615.yaml | 6 + requirements.csv | 1 - riotfile.py | 4 +- supported_versions_output.json | 2 +- supported_versions_table.csv | 2 +- tests/contrib/openai/test_openai_llmobs.py | 52 ++++----- tests/contrib/openai/test_openai_v1.py | 108 +++++++----------- ..._v1.test_completion_stream_est_tokens.json | 37 ------ 44 files changed, 808 insertions(+), 750 deletions(-) create mode 100644 .riot/requirements/132e4bd.txt create mode 100644 .riot/requirements/134082f.txt rename .riot/requirements/{d3fea5a.txt => 14aa6df.txt} (80%) create mode 100644 .riot/requirements/1547cc9.txt create mode 100644 .riot/requirements/162cf2e.txt delete mode 100644 .riot/requirements/164ce6e.txt create mode 100644 .riot/requirements/16a63d7.txt rename .riot/requirements/{75757f2.txt => 1882fe7.txt} (86%) delete mode 100644 .riot/requirements/1911f94.txt rename .riot/requirements/{5f016ad.txt => 19be394.txt} (80%) delete mode 100644 .riot/requirements/1a0657d.txt delete mode 100644 .riot/requirements/1a18a5a.txt create mode 100644 .riot/requirements/1b544ab.txt create mode 100644 .riot/requirements/1d14cdc.txt delete mode 100644 .riot/requirements/1dd6795.txt create mode 100644 .riot/requirements/51ae308.txt create mode 100644 .riot/requirements/663ca38.txt create mode 100644 .riot/requirements/6d1e866.txt delete mode 100644 .riot/requirements/85ff44d.txt rename .riot/requirements/{1d30a5c.txt => 95d28c3.txt} (82%) create mode 100644 .riot/requirements/a2b9112.txt create mode 100644 .riot/requirements/a827c2f.txt rename .riot/requirements/{9a08ceb.txt => a9f0bf3.txt} (79%) create mode 100644 .riot/requirements/bbcdb10.txt create mode 100644 .riot/requirements/bd89eb3.txt delete mode 100644 .riot/requirements/c050b53.txt delete mode 100644 .riot/requirements/c5399d2.txt delete mode 100644 .riot/requirements/e3b63a1.txt delete mode 100644 .riot/requirements/e9c67e1.txt create mode 100644 .riot/requirements/ec404a0.txt create mode 100644 releasenotes/notes/upgrade-tiktoken-removed-a10db578f5354615.yaml delete mode 100644 tests/snapshots/tests.contrib.openai.test_openai_v1.test_completion_stream_est_tokens.json diff --git a/.riot/requirements/132e4bd.txt b/.riot/requirements/132e4bd.txt new file mode 100644 index 00000000000..e6431a30c69 --- /dev/null +++ b/.riot/requirements/132e4bd.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/132e4bd.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==2.3.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/134082f.txt b/.riot/requirements/134082f.txt new file mode 100644 index 00000000000..b09c30dddcd --- /dev/null +++ b/.riot/requirements/134082f.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/134082f.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.66.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/d3fea5a.txt b/.riot/requirements/14aa6df.txt similarity index 80% rename from .riot/requirements/d3fea5a.txt rename to .riot/requirements/14aa6df.txt index f3a136aee2c..0cfce3ae783 100644 --- a/.riot/requirements/d3fea5a.txt +++ b/.riot/requirements/14aa6df.txt @@ -2,20 +2,19 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d3fea5a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/14aa6df.in # annotated-types==0.7.0 anyio==4.11.0 attrs==25.4.0 certifi==2025.10.5 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jiter==0.11.0 mock==5.2.0 @@ -25,9 +24,9 @@ opentracing==2.4.0 packaging==25.0 pillow==11.3.0 pluggy==1.6.0 -propcache==0.4.0 -pydantic==2.12.0 -pydantic-core==2.41.1 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 pygments==2.19.2 pytest==8.4.2 pytest-asyncio==0.21.1 @@ -35,11 +34,8 @@ pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-randomly==4.0.1 pyyaml==6.0.3 -regex==2025.9.18 -requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 -tiktoken==0.12.0 tqdm==4.67.1 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/1547cc9.txt b/.riot/requirements/1547cc9.txt new file mode 100644 index 00000000000..11b454aec0f --- /dev/null +++ b/.riot/requirements/1547cc9.txt @@ -0,0 +1,49 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1547cc9.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.10.7 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +importlib-metadata==8.7.0 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.76.2 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 +zipp==3.23.0 diff --git a/.riot/requirements/162cf2e.txt b/.riot/requirements/162cf2e.txt new file mode 100644 index 00000000000..93fb314587d --- /dev/null +++ b/.riot/requirements/162cf2e.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/162cf2e.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.109.1 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/164ce6e.txt b/.riot/requirements/164ce6e.txt deleted file mode 100644 index fcdd2429dca..00000000000 --- a/.riot/requirements/164ce6e.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/164ce6e.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.9.1 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.4.4 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.6 -pydantic-core==2.33.2 -pygments==2.19.1 -pytest==8.4.0 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/16a63d7.txt b/.riot/requirements/16a63d7.txt new file mode 100644 index 00000000000..83475b685d0 --- /dev/null +++ b/.riot/requirements/16a63d7.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/16a63d7.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.76.2 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/75757f2.txt b/.riot/requirements/1882fe7.txt similarity index 86% rename from .riot/requirements/75757f2.txt rename to .riot/requirements/1882fe7.txt index fa13ed3c871..76bd98c0340 100644 --- a/.riot/requirements/75757f2.txt +++ b/.riot/requirements/1882fe7.txt @@ -2,13 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/75757f2.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1882fe7.in # annotated-types==0.7.0 anyio==4.11.0 attrs==25.4.0 certifi==2025.10.5 -charset-normalizer==3.4.3 coverage[toml]==7.10.7 distro==1.9.0 h11==0.16.0 @@ -20,12 +19,12 @@ iniconfig==2.1.0 jiter==0.11.0 mock==5.2.0 multidict==6.7.0 -openai==1.109.1 +openai==2.2.0 opentracing==2.4.0 packaging==25.0 pillow==11.3.0 pluggy==1.6.0 -propcache==0.4.0 +propcache==0.3.2 pydantic==2.12.0 pydantic-core==2.41.1 pygments==2.19.2 @@ -35,11 +34,8 @@ pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-randomly==4.0.1 pyyaml==6.0.3 -regex==2025.9.18 -requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 -tiktoken==0.12.0 tqdm==4.67.1 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/1911f94.txt b/.riot/requirements/1911f94.txt deleted file mode 100644 index e84f071768a..00000000000 --- a/.riot/requirements/1911f94.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1911f94.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.9.2 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.6.3 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.7 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.1 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.1 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/5f016ad.txt b/.riot/requirements/19be394.txt similarity index 80% rename from .riot/requirements/5f016ad.txt rename to .riot/requirements/19be394.txt index 151cb4678b7..5810e1d498c 100644 --- a/.riot/requirements/5f016ad.txt +++ b/.riot/requirements/19be394.txt @@ -2,20 +2,19 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/5f016ad.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/19be394.in # annotated-types==0.7.0 anyio==4.11.0 attrs==25.4.0 certifi==2025.10.5 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 distro==1.9.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jiter==0.11.0 mock==5.2.0 @@ -25,9 +24,9 @@ opentracing==2.4.0 packaging==25.0 pillow==11.3.0 pluggy==1.6.0 -propcache==0.4.0 -pydantic==2.12.0 -pydantic-core==2.41.1 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 pygments==2.19.2 pytest==8.4.2 pytest-asyncio==0.21.1 @@ -35,11 +34,8 @@ pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-randomly==4.0.1 pyyaml==6.0.3 -regex==2025.9.18 -requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 -tiktoken==0.12.0 tqdm==4.67.1 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/1a0657d.txt b/.riot/requirements/1a0657d.txt deleted file mode 100644 index 71749fcce22..00000000000 --- a/.riot/requirements/1a0657d.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a0657d.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.9.2 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.6.3 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.7 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.1 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.1 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/1a18a5a.txt b/.riot/requirements/1a18a5a.txt deleted file mode 100644 index bf781525c20..00000000000 --- a/.riot/requirements/1a18a5a.txt +++ /dev/null @@ -1,51 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a18a5a.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.9.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.4.4 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.6 -pydantic-core==2.33.2 -pygments==2.19.1 -pytest==8.4.0 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/1b544ab.txt b/.riot/requirements/1b544ab.txt new file mode 100644 index 00000000000..6a49a602c0e --- /dev/null +++ b/.riot/requirements/1b544ab.txt @@ -0,0 +1,47 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b544ab.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==2.3.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/1d14cdc.txt b/.riot/requirements/1d14cdc.txt new file mode 100644 index 00000000000..581b3f36070 --- /dev/null +++ b/.riot/requirements/1d14cdc.txt @@ -0,0 +1,49 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d14cdc.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.10.7 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +importlib-metadata==8.7.0 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==2.3.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 +zipp==3.23.0 diff --git a/.riot/requirements/1dd6795.txt b/.riot/requirements/1dd6795.txt deleted file mode 100644 index 971d7fd8945..00000000000 --- a/.riot/requirements/1dd6795.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dd6795.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.9.1 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.4.4 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.6 -pydantic-core==2.33.2 -pygments==2.19.1 -pytest==8.4.0 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/51ae308.txt b/.riot/requirements/51ae308.txt new file mode 100644 index 00000000000..07460fb5d20 --- /dev/null +++ b/.riot/requirements/51ae308.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/51ae308.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.10.7 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.76.2 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.3.2 +pydantic==2.12.0 +pydantic-core==2.41.1 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/663ca38.txt b/.riot/requirements/663ca38.txt new file mode 100644 index 00000000000..a83eaa73b96 --- /dev/null +++ b/.riot/requirements/663ca38.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/663ca38.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.66.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/6d1e866.txt b/.riot/requirements/6d1e866.txt new file mode 100644 index 00000000000..700559b2cf0 --- /dev/null +++ b/.riot/requirements/6d1e866.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6d1e866.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.76.2 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/85ff44d.txt b/.riot/requirements/85ff44d.txt deleted file mode 100644 index 244597ff29f..00000000000 --- a/.riot/requirements/85ff44d.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/85ff44d.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.9.2 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.6.3 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.7 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.1 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.1 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/1d30a5c.txt b/.riot/requirements/95d28c3.txt similarity index 82% rename from .riot/requirements/1d30a5c.txt rename to .riot/requirements/95d28c3.txt index 282b240b028..fc20713c516 100644 --- a/.riot/requirements/1d30a5c.txt +++ b/.riot/requirements/95d28c3.txt @@ -2,13 +2,12 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d30a5c.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/95d28c3.in # annotated-types==0.7.0 anyio==4.11.0 attrs==25.4.0 certifi==2025.10.5 -charset-normalizer==3.4.3 coverage[toml]==7.10.7 distro==1.9.0 exceptiongroup==1.3.0 @@ -16,7 +15,7 @@ h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 hypothesis==6.45.0 -idna==3.10 +idna==3.11 importlib-metadata==8.7.0 iniconfig==2.1.0 jiter==0.11.0 @@ -27,9 +26,9 @@ opentracing==2.4.0 packaging==25.0 pillow==11.3.0 pluggy==1.6.0 -propcache==0.4.0 -pydantic==2.12.0 -pydantic-core==2.41.1 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 pygments==2.19.2 pytest==8.4.2 pytest-asyncio==0.21.1 @@ -37,12 +36,9 @@ pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-randomly==4.0.1 pyyaml==6.0.3 -regex==2025.9.18 -requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 -tiktoken==0.12.0 -tomli==2.2.1 +tomli==2.3.0 tqdm==4.67.1 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/a2b9112.txt b/.riot/requirements/a2b9112.txt new file mode 100644 index 00000000000..ce51f17e0cc --- /dev/null +++ b/.riot/requirements/a2b9112.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a2b9112.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.10.7 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.10 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.66.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.3.2 +pydantic==2.12.0 +pydantic-core==2.41.1 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/a827c2f.txt b/.riot/requirements/a827c2f.txt new file mode 100644 index 00000000000..c857bb9b4af --- /dev/null +++ b/.riot/requirements/a827c2f.txt @@ -0,0 +1,49 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a827c2f.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.10.7 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +importlib-metadata==8.7.0 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.66.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 +zipp==3.23.0 diff --git a/.riot/requirements/9a08ceb.txt b/.riot/requirements/a9f0bf3.txt similarity index 79% rename from .riot/requirements/9a08ceb.txt rename to .riot/requirements/a9f0bf3.txt index 6cf83904b7d..f9442ab847d 100644 --- a/.riot/requirements/9a08ceb.txt +++ b/.riot/requirements/a9f0bf3.txt @@ -2,21 +2,20 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9a08ceb.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/a9f0bf3.in # annotated-types==0.7.0 anyio==4.11.0 attrs==25.4.0 certifi==2025.10.5 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 distro==1.9.0 exceptiongroup==1.3.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jiter==0.11.0 mock==5.2.0 @@ -26,9 +25,9 @@ opentracing==2.4.0 packaging==25.0 pillow==11.3.0 pluggy==1.6.0 -propcache==0.4.0 -pydantic==2.12.0 -pydantic-core==2.41.1 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 pygments==2.19.2 pytest==8.4.2 pytest-asyncio==0.21.1 @@ -36,12 +35,9 @@ pytest-cov==7.0.0 pytest-mock==3.15.1 pytest-randomly==4.0.1 pyyaml==6.0.3 -regex==2025.9.18 -requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 -tiktoken==0.12.0 -tomli==2.2.1 +tomli==2.3.0 tqdm==4.67.1 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/bbcdb10.txt b/.riot/requirements/bbcdb10.txt new file mode 100644 index 00000000000..073ed6f88ce --- /dev/null +++ b/.riot/requirements/bbcdb10.txt @@ -0,0 +1,47 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bbcdb10.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.66.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/bd89eb3.txt b/.riot/requirements/bd89eb3.txt new file mode 100644 index 00000000000..3e6eeac2f0f --- /dev/null +++ b/.riot/requirements/bd89eb3.txt @@ -0,0 +1,47 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/bd89eb3.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +exceptiongroup==1.3.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==1.76.2 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tomli==2.3.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/.riot/requirements/c050b53.txt b/.riot/requirements/c050b53.txt deleted file mode 100644 index 538e20f9b2b..00000000000 --- a/.riot/requirements/c050b53.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c050b53.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.9.1 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.4.4 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.6 -pydantic-core==2.33.2 -pygments==2.19.1 -pytest==8.4.0 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/c5399d2.txt b/.riot/requirements/c5399d2.txt deleted file mode 100644 index 186f21ddd46..00000000000 --- a/.riot/requirements/c5399d2.txt +++ /dev/null @@ -1,49 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c5399d2.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.9.2 -distro==1.9.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.6.3 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.7 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.1 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tqdm==4.67.1 -typing-extensions==4.14.1 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 diff --git a/.riot/requirements/e3b63a1.txt b/.riot/requirements/e3b63a1.txt deleted file mode 100644 index 86bab7e34aa..00000000000 --- a/.riot/requirements/e3b63a1.txt +++ /dev/null @@ -1,53 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e3b63a1.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.9.1 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.7.0 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.4.4 -openai==1.76.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.6 -pydantic-core==2.33.2 -pygments==2.19.1 -pytest==8.4.0 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 -zipp==3.23.0 diff --git a/.riot/requirements/e9c67e1.txt b/.riot/requirements/e9c67e1.txt deleted file mode 100644 index daa1f61fdcb..00000000000 --- a/.riot/requirements/e9c67e1.txt +++ /dev/null @@ -1,53 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e9c67e1.in -# -annotated-types==0.7.0 -anyio==4.9.0 -attrs==25.3.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -coverage[toml]==7.9.2 -distro==1.9.0 -exceptiongroup==1.3.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.28.1 -hypothesis==6.45.0 -idna==3.10 -importlib-metadata==8.7.0 -iniconfig==2.1.0 -jiter==0.10.0 -mock==5.2.0 -multidict==6.6.3 -openai==1.66.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -propcache==0.3.2 -pydantic==2.11.7 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.1 -pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 -pyyaml==6.0.2 -regex==2024.11.6 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -tiktoken==0.9.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.1 -typing-inspection==0.4.1 -urllib3==1.26.20 -vcrpy==7.0.0 -wrapt==1.17.2 -yarl==1.20.1 -zipp==3.23.0 diff --git a/.riot/requirements/ec404a0.txt b/.riot/requirements/ec404a0.txt new file mode 100644 index 00000000000..38308a54642 --- /dev/null +++ b/.riot/requirements/ec404a0.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/ec404a0.in +# +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +distro==1.9.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jiter==0.11.0 +mock==5.2.0 +multidict==6.7.0 +openai==2.3.0 +opentracing==2.4.0 +packaging==25.0 +pillow==11.3.0 +pluggy==1.6.0 +propcache==0.4.1 +pydantic==2.12.2 +pydantic-core==2.41.4 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +pyyaml==6.0.3 +sniffio==1.3.1 +sortedcontainers==2.4.0 +tqdm==4.67.1 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==1.26.20 +vcrpy==7.0.0 +wrapt==1.17.3 +yarl==1.22.0 diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index 0240ac13050..e6481a21362 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -617,7 +617,7 @@ integrations: tested_versions_by_dependency: openai: min: 1.0.0 - max: 1.109.1 + max: 2.3.0 - integration_name: openai_agents is_external_package: true diff --git a/ddtrace/contrib/internal/openai/__init__.py b/ddtrace/contrib/internal/openai/__init__.py index faae901a39e..f4bd7fa79cb 100644 --- a/ddtrace/contrib/internal/openai/__init__.py +++ b/ddtrace/contrib/internal/openai/__init__.py @@ -29,7 +29,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The OpenAI integration **estimates** prompt and completion token counts if streaming is turned on. -This is because the ``usage`` field is no longer returned in streamed completions, which is what +This is because the ``usage`` field is not returned in streamed completions, which is what the integration relies on for reporting metrics. Streaming responses should produce a ``openai.stream`` span. This span is tagged with estimated diff --git a/ddtrace/llmobs/_integrations/openai.py b/ddtrace/llmobs/_integrations/openai.py index f9743907656..ee72f5c13f4 100644 --- a/ddtrace/llmobs/_integrations/openai.py +++ b/ddtrace/llmobs/_integrations/openai.py @@ -191,11 +191,8 @@ def _extract_llmobs_metrics_tags( metrics[CACHE_READ_INPUT_TOKENS_METRIC_KEY] = cached_tokens return metrics elif kwargs.get("stream") and resp is not None: - model_name = span.get_tag("openai.response.model") or kwargs.get("model", "") - _, prompt_tokens = _compute_prompt_tokens( - model_name, kwargs.get("prompt", None), kwargs.get("messages", None) - ) - _, completion_tokens = _compute_completion_tokens(resp, model_name) + prompt_tokens = _compute_prompt_tokens(kwargs.get("prompt", None), kwargs.get("messages", None)) + completion_tokens = _compute_completion_tokens(resp) total_tokens = prompt_tokens + completion_tokens return { diff --git a/ddtrace/llmobs/_integrations/utils.py b/ddtrace/llmobs/_integrations/utils.py index 18d017549cf..f431f9342c6 100644 --- a/ddtrace/llmobs/_integrations/utils.py +++ b/ddtrace/llmobs/_integrations/utils.py @@ -35,13 +35,6 @@ from ddtrace.llmobs.types import ToolResult -try: - from tiktoken import encoding_for_model - - tiktoken_available = True -except ModuleNotFoundError: - tiktoken_available = False - logger = get_logger(__name__) @@ -1257,76 +1250,41 @@ def get_final_message_converse_stream_message( _punc_regex = re.compile(r"[\w']+|[.,!?;~@#$%^&*()+/-]") -def _compute_prompt_tokens(model_name, prompts=None, messages=None): +def _compute_prompt_tokens(prompts=None, messages=None): """Compute token span metrics on streamed chat/completion requests. Only required if token usage is not provided in the streamed response. """ num_prompt_tokens = 0 - estimated = False if messages: for m in messages: - estimated, prompt_tokens = _compute_token_count(m.get("content", ""), model_name) + prompt_tokens = _est_tokens(m.get("content", "")) num_prompt_tokens += prompt_tokens elif prompts: if isinstance(prompts, str) or isinstance(prompts, list) and isinstance(prompts[0], int): prompts = [prompts] for prompt in prompts: - estimated, prompt_tokens = _compute_token_count(prompt, model_name) + prompt_tokens = _est_tokens(prompt) num_prompt_tokens += prompt_tokens - return estimated, num_prompt_tokens + return num_prompt_tokens -def _compute_completion_tokens(completions_or_messages, model_name): +def _compute_completion_tokens(completions_or_messages): """Compute/Estimate the completion token count from the streamed response.""" - if not completions_or_messages: - return False, 0 - estimated = False num_completion_tokens = 0 for choice in completions_or_messages: content = choice.get("content", "") or choice.get("text", "") - estimated, completion_tokens = _compute_token_count(content, model_name) + completion_tokens = _est_tokens(content) num_completion_tokens += completion_tokens - return estimated, num_completion_tokens - - -def _compute_token_count(content, model): - # type: (Union[str, List[int]], Optional[str]) -> Tuple[bool, int] - """ - Takes in prompt/response(s) and model pair, and returns a tuple of whether or not the number of prompt - tokens was estimated, and the estimated/calculated prompt token count. - """ - num_prompt_tokens = 0 - estimated = False - if model is not None and tiktoken_available is True: - try: - enc = encoding_for_model(model) - if isinstance(content, str): - num_prompt_tokens += len(enc.encode(content)) - elif content and isinstance(content, list) and isinstance(content[0], int): - num_prompt_tokens += len(content) - return estimated, num_prompt_tokens - except KeyError: - # tiktoken.encoding_for_model() will raise a KeyError if it doesn't have a tokenizer for the model - estimated = True - else: - estimated = True - - # If model is unavailable or tiktoken is not imported, then provide a very rough estimate of the number of tokens - return estimated, _est_tokens(content) + return num_completion_tokens -def _est_tokens(prompt): - # type: (Union[str, List[int]]) -> int +def _est_tokens(prompt: Union[str, List[int]]) -> int: """ Provide a very rough estimate of the number of tokens in a string prompt. Note that if the prompt is passed in as a token array (list of ints), the token count is just the length of the token array. + Assumes 1) English text, 2) 1 token ~= 4 chars, and 3) 1 token ~= ¾ words """ - # If model is unavailable or tiktoken is not imported, then provide a very rough estimate of the number of tokens - # Approximate using the following assumptions: - # * English text - # * 1 token ~= 4 chars - # * 1 token ~= ¾ words if not prompt: return 0 est_tokens = 0 diff --git a/lib-injection/sources/requirements.csv b/lib-injection/sources/requirements.csv index 46c1d111320..530adac941c 100644 --- a/lib-injection/sources/requirements.csv +++ b/lib-injection/sources/requirements.csv @@ -11,4 +11,3 @@ protobuf,>=3, wrapt,>=1, opentracing,>=2.0.0, opentelemetry-exporter-otlp,>=1.0.0, -tiktoken,, diff --git a/pyproject.toml b/pyproject.toml index 835bf1633ad..7e33fecad7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,9 +54,6 @@ opentracing = [ opentelemetry = [ "opentelemetry-exporter-otlp>=1.0.0", ] -openai = [ - "tiktoken", -] [project.scripts] ddtrace-run = "ddtrace.commands.ddtrace_run:main" diff --git a/releasenotes/notes/upgrade-tiktoken-removed-a10db578f5354615.yaml b/releasenotes/notes/upgrade-tiktoken-removed-a10db578f5354615.yaml new file mode 100644 index 00000000000..25a348d0123 --- /dev/null +++ b/releasenotes/notes/upgrade-tiktoken-removed-a10db578f5354615.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + openai: Streamed chat/completions will no longer have token counts computed using the ``tiktoken`` library, and instead + will default to having their token counts estimated if not explicitly provided in the OpenAI response object. + To guarantee accurate streamed token metrics, set ``stream_options={"include_usage": True}`` in the OpenAI request. diff --git a/requirements.csv b/requirements.csv index 46c1d111320..530adac941c 100644 --- a/requirements.csv +++ b/requirements.csv @@ -11,4 +11,3 @@ protobuf,>=3, wrapt,>=1, opentracing,>=2.0.0, opentelemetry-exporter-otlp,>=1.0.0, -tiktoken,, diff --git a/riotfile.py b/riotfile.py index b809a13017f..6523c12e78c 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2606,11 +2606,9 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( pys=select_pys(min_version="3.9", max_version="3.13"), pkgs={ - "openai": ["<2.0.0", "~=1.76.2", "==1.66.0"], - "tiktoken": latest, + "openai": [latest, "<2.0.0", "~=1.76.2", "==1.66.0"], "pillow": latest, }, - env={"TIKTOKEN_AVAILABLE": "True"}, ), ], ), diff --git a/supported_versions_output.json b/supported_versions_output.json index f9e361f828c..04e1f3c865a 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -467,7 +467,7 @@ "dependency": "openai", "integration": "openai", "minimum_tracer_supported": "1.0.0", - "max_tracer_supported": "1.109.1", + "max_tracer_supported": "2.2.0", "pinned": "true", "auto-instrumented": true }, diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 446583b9b0c..ae1dc58328b 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -64,7 +64,7 @@ mcp,mcp,1.10.1,1.16.0,True molten,molten,1.0.2,1.0.2,True mysql-connector-python,mysql,8.0.5,9.4.0,True mysqlclient,mysqldb,2.2.1,2.2.6,True -openai,openai *,1.0.0,1.109.1,True +openai,openai,1.0.0,2.2.0,True openai-agents,openai_agents,0.0.8,0.0.16,True protobuf,protobuf,6.30.1,6.32.0,False psycopg,psycopg,3.0.18,3.2.10,True diff --git a/tests/contrib/openai/test_openai_llmobs.py b/tests/contrib/openai/test_openai_llmobs.py index cebcbda9c8a..01d4ff46850 100644 --- a/tests/contrib/openai/test_openai_llmobs.py +++ b/tests/contrib/openai/test_openai_llmobs.py @@ -269,16 +269,14 @@ async def test_completion_azure_async( def test_completion_stream(self, openai, ddtrace_global_config, mock_llmobs_writer, mock_tracer): with get_openai_vcr(subdirectory_name="v1").use_cassette("completion_streamed.yaml"): - with mock.patch("ddtrace.llmobs._integrations.utils.encoding_for_model", create=True) as mock_encoding: - with mock.patch("ddtrace.llmobs._integrations.utils._est_tokens") as mock_est: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] - mock_est.return_value = 2 - model = "ada" - expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' - client = openai.OpenAI() - resp = client.completions.create(model=model, prompt="Hello world", stream=True) - for _ in resp: - pass + with mock.patch("ddtrace.llmobs._integrations.utils._est_tokens") as mock_est: + mock_est.return_value = 2 + model = "ada" + expected_completion = '! ... A page layouts page drawer? ... Interesting. The "Tools" is' + client = openai.OpenAI() + resp = client.completions.create(model=model, prompt="Hello world", stream=True) + for _ in resp: + pass span = mock_tracer.pop_traces()[0][0] assert mock_llmobs_writer.enqueue.call_count == 1 mock_llmobs_writer.enqueue.assert_called_with( @@ -565,24 +563,22 @@ def test_chat_completion_stream_explicit_no_tokens( """ with get_openai_vcr(subdirectory_name="v1").use_cassette("chat_completion_streamed.yaml"): - with mock.patch("ddtrace.llmobs._integrations.utils.encoding_for_model", create=True) as mock_encoding: - with mock.patch("ddtrace.llmobs._integrations.utils._est_tokens") as mock_est: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2, 3, 4, 5, 6, 7, 8] - mock_est.return_value = 8 - model = "gpt-3.5-turbo" - resp_model = model - input_messages = [{"role": "user", "content": "Who won the world series in 2020?"}] - expected_completion = "The Los Angeles Dodgers won the World Series in 2020." - client = openai.OpenAI() - resp = client.chat.completions.create( - model=model, - messages=input_messages, - stream=True, - user="ddtrace-test", - stream_options={"include_usage": False}, - ) - for chunk in resp: - resp_model = chunk.model + with mock.patch("ddtrace.llmobs._integrations.utils._est_tokens") as mock_est: + mock_est.return_value = 8 + model = "gpt-3.5-turbo" + resp_model = model + input_messages = [{"role": "user", "content": "Who won the world series in 2020?"}] + expected_completion = "The Los Angeles Dodgers won the World Series in 2020." + client = openai.OpenAI() + resp = client.chat.completions.create( + model=model, + messages=input_messages, + stream=True, + user="ddtrace-test", + stream_options={"include_usage": False}, + ) + for chunk in resp: + resp_model = chunk.model span = mock_tracer.pop_traces()[0][0] assert mock_llmobs_writer.enqueue.call_count == 1 mock_llmobs_writer.enqueue.assert_called_with( diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index 2290ca04191..5021b8b0a28 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -13,7 +13,6 @@ from tests.utils import snapshot_context -TIKTOKEN_AVAILABLE = os.getenv("TIKTOKEN_AVAILABLE", False) pytestmark = pytest.mark.skipif( parse_version(openai_module.version.VERSION) < (1, 0), reason="This module only tests openai >= 1.0" ) @@ -753,51 +752,32 @@ def test_span_finish_on_stream_error(openai, openai_vcr, snapshot_tracer): ) -@pytest.mark.snapshot -@pytest.mark.skipif(TIKTOKEN_AVAILABLE, reason="This test estimates token counts") -def test_completion_stream_est_tokens(openai, openai_vcr, snapshot_tracer): - with openai_vcr.use_cassette("completion_streamed.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] - client = openai.OpenAI() - resp = client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) - _ = [c for c in resp] - - -@pytest.mark.skipif(not TIKTOKEN_AVAILABLE, reason="This test computes token counts using tiktoken") @pytest.mark.snapshot(token="tests.contrib.openai.test_openai.test_completion_stream") def test_completion_stream(openai, openai_vcr, snapshot_tracer): with openai_vcr.use_cassette("completion_streamed.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] - client = openai.OpenAI() - resp = client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) - _ = [c for c in resp] + client = openai.OpenAI() + resp = client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) + _ = [c for c in resp] -@pytest.mark.skipif(not TIKTOKEN_AVAILABLE, reason="This test computes token counts using tiktoken") @pytest.mark.snapshot(token="tests.contrib.openai.test_openai.test_completion_stream") async def test_completion_async_stream(openai, openai_vcr, snapshot_tracer): with openai_vcr.use_cassette("completion_streamed.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] - client = openai.AsyncOpenAI() - resp = await client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) - _ = [c async for c in resp] + client = openai.AsyncOpenAI() + resp = await client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) + _ = [c async for c in resp] @pytest.mark.skipif( - parse_version(openai_module.version.VERSION) < (1, 6, 0) or not TIKTOKEN_AVAILABLE, + parse_version(openai_module.version.VERSION) < (1, 6, 0), reason="Streamed response context managers are only available v1.6.0+", ) @pytest.mark.snapshot(token="tests.contrib.openai.test_openai.test_completion_stream") def test_completion_stream_context_manager(openai, openai_vcr, snapshot_tracer): with openai_vcr.use_cassette("completion_streamed.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2] - client = openai.OpenAI() - with client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) as resp: - _ = [c for c in resp] + client = openai.OpenAI() + with client.completions.create(model="ada", prompt="Hello world", stream=True, n=None) as resp: + _ = [c for c in resp] @pytest.mark.skipif( @@ -807,17 +787,15 @@ def test_completion_stream_context_manager(openai, openai_vcr, snapshot_tracer): def test_chat_completion_stream(openai, openai_vcr, snapshot_tracer): """Assert that streamed token chunk extraction logic works automatically.""" with openai_vcr.use_cassette("chat_completion_streamed_tokens.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2, 3, 4, 5, 6, 7, 8] - client = openai.OpenAI() - resp = client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Who won the world series in 2020?"}], - stream=True, - user="ddtrace-test", - n=None, - ) - _ = [c for c in resp] + client = openai.OpenAI() + resp = client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Who won the world series in 2020?"}], + stream=True, + user="ddtrace-test", + n=None, + ) + _ = [c for c in resp] @pytest.mark.skipif( @@ -826,19 +804,17 @@ def test_chat_completion_stream(openai, openai_vcr, snapshot_tracer): @pytest.mark.snapshot(token="tests.contrib.openai.test_openai.test_chat_completion_stream") async def test_chat_completion_async_stream(openai, openai_vcr, snapshot_tracer): with openai_vcr.use_cassette("chat_completion_streamed_tokens.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2, 3, 4, 5, 6, 7, 8] - client = openai.AsyncOpenAI() - resp = await client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[ - {"role": "user", "content": "Who won the world series in 2020?"}, - ], - stream=True, - n=None, - user="ddtrace-test", - ) - _ = [c async for c in resp] + client = openai.AsyncOpenAI() + resp = await client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Who won the world series in 2020?"}, + ], + stream=True, + n=None, + user="ddtrace-test", + ) + _ = [c async for c in resp] @pytest.mark.skipif( @@ -848,19 +824,17 @@ async def test_chat_completion_async_stream(openai, openai_vcr, snapshot_tracer) @pytest.mark.snapshot(token="tests.contrib.openai.test_openai.test_chat_completion_stream") async def test_chat_completion_async_stream_context_manager(openai, openai_vcr, snapshot_tracer): with openai_vcr.use_cassette("chat_completion_streamed_tokens.yaml"): - with mock.patch("ddtrace.contrib.internal.openai.utils.encoding_for_model", create=True) as mock_encoding: - mock_encoding.return_value.encode.side_effect = lambda x: [1, 2, 3, 4, 5, 6, 7, 8] - client = openai.AsyncOpenAI() - async with await client.chat.completions.create( - model="gpt-3.5-turbo", - messages=[ - {"role": "user", "content": "Who won the world series in 2020?"}, - ], - stream=True, - user="ddtrace-test", - n=None, - ) as resp: - _ = [c async for c in resp] + client = openai.AsyncOpenAI() + async with await client.chat.completions.create( + model="gpt-3.5-turbo", + messages=[ + {"role": "user", "content": "Who won the world series in 2020?"}, + ], + stream=True, + user="ddtrace-test", + n=None, + ) as resp: + _ = [c async for c in resp] @pytest.mark.snapshot( diff --git a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_completion_stream_est_tokens.json b/tests/snapshots/tests.contrib.openai.test_openai_v1.test_completion_stream_est_tokens.json deleted file mode 100644 index 0718ecf0125..00000000000 --- a/tests/snapshots/tests.contrib.openai.test_openai_v1.test_completion_stream_est_tokens.json +++ /dev/null @@ -1,37 +0,0 @@ -[[ - { - "name": "openai.request", - "service": "tests.contrib.openai", - "resource": "createCompletion", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68757a3500000000", - "component": "openai", - "language": "python", - "openai.organization.name": "datadog-4", - "openai.request.endpoint": "/v1/completions", - "openai.request.method": "POST", - "openai.request.model": "ada", - "openai.request.provider": "OpenAI", - "openai.response.model": "ada", - "runtime-id": "cbc863d986aa4ec2b8a3437b7398c196" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "openai.organization.ratelimit.requests.limit": 3000, - "openai.organization.ratelimit.requests.remaining": 2999, - "openai.organization.ratelimit.tokens.limit": 250000, - "openai.organization.ratelimit.tokens.remaining": 249979, - "process_id": 88640 - }, - "duration": 13084000, - "start": 1752529461943569000 - }]] From f222780790bdf61b3ba574bef24d689683a21fa5 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Nov 2025 09:40:47 -0800 Subject: [PATCH 15/42] update system-tests --- .github/workflows/system-tests.yml | 6 +++--- .gitlab-ci.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 58dc625191c..3d35406a7e8 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' + ref: '2794772dab3ed1fe4261d44b560102f9d0338240' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' + ref: '2794772dab3ed1fe4261d44b560102f9d0338240' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '173e1e83468e28274bc4631f1ee2afc4c2620c4e' + ref: '2794772dab3ed1fe4261d44b560102f9d0338240' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 289b35495a7..05022d0d175 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "173e1e83468e28274bc4631f1ee2afc4c2620c4e" + SYSTEM_TESTS_REF: "2794772dab3ed1fe4261d44b560102f9d0338240" default: interruptible: true From 6377bc15076453cc5e21843426c371f21902a256 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Nov 2025 09:49:17 -0800 Subject: [PATCH 16/42] update import --- ddtrace/internal/settings/openfeature.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddtrace/internal/settings/openfeature.py b/ddtrace/internal/settings/openfeature.py index 8a05efe10a6..5149bcee322 100644 --- a/ddtrace/internal/settings/openfeature.py +++ b/ddtrace/internal/settings/openfeature.py @@ -2,7 +2,7 @@ OpenFeature configuration settings. """ -from ddtrace.settings._core import DDConfig +from ddtrace.internal.settings._core import DDConfig class OpenFeatureConfig(DDConfig): From c72317148c27626e4f147227df9cf22c8b66933d Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Nov 2025 10:00:37 -0800 Subject: [PATCH 17/42] update import --- ddtrace/internal/openfeature/_provider.py | 2 +- ddtrace/internal/openfeature/writer.py | 4 ++-- tests/appsec/ai_guard/utils.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ddtrace/internal/openfeature/_provider.py b/ddtrace/internal/openfeature/_provider.py index d1aceb07f6f..4eeb4b9c9c9 100644 --- a/ddtrace/internal/openfeature/_provider.py +++ b/ddtrace/internal/openfeature/_provider.py @@ -29,7 +29,7 @@ from ddtrace.internal.openfeature.writer import start_exposure_writer from ddtrace.internal.openfeature.writer import stop_exposure_writer from ddtrace.internal.service import ServiceStatusError -from ddtrace.settings.openfeature import config as ffe_config +from ddtrace.internal.settings.openfeature import config as ffe_config # Handle different import paths between openfeature-sdk versions diff --git a/ddtrace/internal/openfeature/writer.py b/ddtrace/internal/openfeature/writer.py index f44109e47ab..aca4cef4c8f 100644 --- a/ddtrace/internal/openfeature/writer.py +++ b/ddtrace/internal/openfeature/writer.py @@ -14,11 +14,11 @@ from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger from ddtrace.internal.periodic import PeriodicService +from ddtrace.internal.settings._agent import config as agent_config +from ddtrace.internal.settings.openfeature import config as ffe_config from ddtrace.internal.utils.http import Response from ddtrace.internal.utils.http import get_connection from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.settings._agent import config as agent_config -from ddtrace.settings.openfeature import config as ffe_config logger = get_logger(__name__) diff --git a/tests/appsec/ai_guard/utils.py b/tests/appsec/ai_guard/utils.py index cca4a2e3ef5..970b097f2d3 100644 --- a/tests/appsec/ai_guard/utils.py +++ b/tests/appsec/ai_guard/utils.py @@ -13,7 +13,7 @@ from ddtrace.appsec._constants import AI_GUARD from ddtrace.appsec.ai_guard import AIGuardClient from ddtrace.appsec.ai_guard._api_client import Message -from ddtrace.settings.asm import ai_guard_config +from ddtrace.internal.settings.asm import ai_guard_config from tests.utils import DummyTracer From 9fe33ac602b78a5a832f97d69a9aac0e3fbf84b7 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Mon, 3 Nov 2025 12:06:42 -0800 Subject: [PATCH 18/42] update system-tests --- .github/workflows/system-tests.yml | 6 +++--- .gitlab-ci.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 3d35406a7e8..d3a9a81a490 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '2794772dab3ed1fe4261d44b560102f9d0338240' + ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '2794772dab3ed1fe4261d44b560102f9d0338240' + ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '2794772dab3ed1fe4261d44b560102f9d0338240' + ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05022d0d175..feb657085b8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "2794772dab3ed1fe4261d44b560102f9d0338240" + SYSTEM_TESTS_REF: "4396f2a3e338544fb808e542ebcb83a8ed40af63" default: interruptible: true From 094bc75ef715c61cab051b111e821ae0ffef49cb Mon Sep 17 00:00:00 2001 From: ncybul <124532568+ncybul@users.noreply.github.com> Date: Mon, 3 Nov 2025 17:28:34 -0500 Subject: [PATCH 19/42] chore(llmobs): remove submit_evaluation_for method (#15045) ## Description [MLOB-4217] Removes `submit_evaluation_for` method in favor of `submit_evaluation` which should be released in ddtrace 4.0. ## Testing ## Risks ## Additional Notes [MLOB-4217]: https://datadoghq.atlassian.net/browse/MLOB-4217?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ddtrace/llmobs/_llmobs.py | 48 ------------------- ...ubmit-evaluation-for-ef0c5a217eb18a46.yaml | 7 +++ tests/llmobs/test_llmobs_service.py | 6 +-- 3 files changed, 10 insertions(+), 51 deletions(-) create mode 100644 releasenotes/notes/remove-submit-evaluation-for-ef0c5a217eb18a46.yaml diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index f9451b0ce3b..bfbc5fb57f8 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -1561,50 +1561,12 @@ def _set_dict_attribute(span: Span, key, value: Dict[str, Any]) -> None: existing_value.update(value) span._set_ctx_item(key, existing_value) - @classmethod - def submit_evaluation_for( - cls, - label: str, - metric_type: str, - value: Union[str, int, float, bool], - span: Optional[dict] = None, - span_with_tag_value: Optional[Dict[str, str]] = None, - tags: Optional[Dict[str, str]] = None, - ml_app: Optional[str] = None, - timestamp_ms: Optional[int] = None, - metadata: Optional[Dict[str, object]] = None, - assessment: Optional[str] = None, - reasoning: Optional[str] = None, - ) -> None: - """ - Submits a custom evaluation metric for a given span. This method is deprecated and will be - removed in the next major version of ddtrace (4.0). Please use `LLMObs.submit_evaluation()` instead. - """ - log.warning( - "LLMObs.submit_evaluation_for() is deprecated and will be removed in the next major " - "version of ddtrace (4.0). Please use LLMObs.submit_evaluation() instead." - ) - return cls.submit_evaluation( - label=label, - metric_type=metric_type, - value=value, - span=span, - span_with_tag_value=span_with_tag_value, - tags=tags, - ml_app=ml_app, - timestamp_ms=timestamp_ms, - metadata=metadata, - assessment=assessment, - reasoning=reasoning, - ) - @classmethod def submit_evaluation( cls, label: str, metric_type: str, value: Union[str, int, float, bool], - span_context: Optional[Dict[str, str]] = None, span: Optional[dict] = None, span_with_tag_value: Optional[Dict[str, str]] = None, tags: Optional[Dict[str, str]] = None, @@ -1621,9 +1583,6 @@ def submit_evaluation( :param str metric_type: The type of the evaluation metric. One of "categorical", "score", "boolean". :param value: The value of the evaluation metric. Must be a string (categorical), integer (score), float (score), or boolean (boolean). - :param dict span_context: A dictionary containing the span_id and trace_id of interest. This is a - deprecated parameter and will be removed in the next major version of - ddtrace (4.0). Please use `span` or `span_with_tag_value` instead. :param dict span: A dictionary of shape {'span_id': str, 'trace_id': str} uniquely identifying the span associated with this evaluation. :param dict span_with_tag_value: A dictionary with the format {'tag_key': str, 'tag_value': str} @@ -1637,13 +1596,6 @@ def submit_evaluation( :param str assessment: An assessment of this evaluation. Must be either "pass" or "fail". :param str reasoning: An explanation of the evaluation result. """ - if span_context is not None: - log.warning( - "The `span_context` parameter is deprecated and will be removed in the next major version of " - "ddtrace (4.0). Please use `span` or `span_with_tag_value` instead." - ) - span = span or span_context - if cls.enabled is False: log.debug( "LLMObs.submit_evaluation() called when LLMObs is not enabled. ", diff --git a/releasenotes/notes/remove-submit-evaluation-for-ef0c5a217eb18a46.yaml b/releasenotes/notes/remove-submit-evaluation-for-ef0c5a217eb18a46.yaml new file mode 100644 index 00000000000..dc8ef083b82 --- /dev/null +++ b/releasenotes/notes/remove-submit-evaluation-for-ef0c5a217eb18a46.yaml @@ -0,0 +1,7 @@ +upgrade: + - | + LLM Observability: ``LLMObs.submit_evaluation_for()`` has been removed. Please use ``LLMObs.submit_evaluation()`` instead for submitting evaluations. + To migrate: + - ``LLMObs.submit_evaluation_for(...)`` users: rename to ``LLMObs.submit_evaluation(...)`` + - ``LLMObs.submit_evaluation_for(...)`` users: rename the ``span_context`` argument to ``span``, i.e. + ``LLMObs.submit_evaluation(span_context={"span_id": ..., "trace_id": ...}, ...)`` to ``LLMObs.submit_evaluation(span={"span_id": ..., "trace_id": ...}, ...)`` diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index 884a79a44d8..38af3aa8e28 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -1930,8 +1930,8 @@ def test_submit_evaluation_invalid_reasoning_raises_warning(llmobs, mock_llmobs_ mock_llmobs_logs.warning.assert_called_once_with("Failed to parse reasoning. reasoning must be a string.") -def test_submit_evaluation_for_enqueues_writer_with_reasoning(llmobs, mock_llmobs_eval_metric_writer): - llmobs.submit_evaluation_for( +def test_submit_evaluation_enqueues_writer_with_reasoning(llmobs, mock_llmobs_eval_metric_writer): + llmobs.submit_evaluation( span={"span_id": "123", "trace_id": "456"}, label="toxicity", metric_type="categorical", @@ -1955,7 +1955,7 @@ def test_submit_evaluation_for_enqueues_writer_with_reasoning(llmobs, mock_llmob ) ) mock_llmobs_eval_metric_writer.reset() - llmobs.submit_evaluation_for( + llmobs.submit_evaluation( span={"span_id": "123", "trace_id": "456"}, label="toxicity", metric_type="categorical", From 67dabfd49b7c117d93ad6d105d4b64c10372e70e Mon Sep 17 00:00:00 2001 From: Yun Kim <35776586+Yun-Kim@users.noreply.github.com> Date: Tue, 4 Nov 2025 11:30:29 -0500 Subject: [PATCH 20/42] chore(llmobs): remove google_generativeai integration (#14820) [MLOB-4040] ## Description Removes the google_generativeai integration. This is end of life and replaced by the google_genai library and integration, as recommended by google's official genai documentation. This is part of our ddtrace 4.0 release. [MLOB-4040]: https://datadoghq.atlassian.net/browse/MLOB-4040?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .riot/requirements/165cb23.txt | 64 -- .riot/requirements/166f21a.txt | 64 -- .riot/requirements/18caf61.txt | 66 -- .riot/requirements/1bee666.txt | 64 -- .riot/requirements/55b8536.txt | 62 -- .riot/requirements/5ac9b4e.txt | 64 -- .riot/requirements/6820ef2.txt | 62 -- .riot/requirements/ab2f587.txt | 64 -- .riot/requirements/ac77620.txt | 66 -- .riot/requirements/c16273a.txt | 64 -- ddtrace/_monkey.py | 2 - .../integration_registry/registry.yaml | 10 - .../internal/google_generativeai/__init__.py | 80 --- .../internal/google_generativeai/_utils.py | 24 - .../internal/google_generativeai/patch.py | 130 ---- ddtrace/internal/settings/_config.py | 1 - ddtrace/llmobs/_integrations/__init__.py | 2 - ddtrace/llmobs/_integrations/gemini.py | 131 ---- ddtrace/llmobs/_integrations/google_utils.py | 11 +- ddtrace/llmobs/_integrations/langchain.py | 4 - ddtrace/llmobs/_integrations/vertexai.py | 16 +- ddtrace/llmobs/_llmobs.py | 1 - docs/index.rst | 2 - docs/integrations.rst | 6 - ...generativeai-removed-23cedc4c9dc95408.yaml | 5 + riotfile.py | 16 - supported_versions_output.json | 7 - supported_versions_table.csv | 1 - tests/contrib/google_generativeai/__init__.py | 0 tests/contrib/google_generativeai/conftest.py | 87 --- .../google_generativeai/test_data/apple.jpg | Bin 42568 -> 0 bytes .../test_google_generativeai.py | 384 ----------- .../test_google_generativeai_llmobs.py | 610 ------------------ .../test_google_generativeai_patch.py | 24 - tests/contrib/google_generativeai/utils.py | 192 ------ tests/llmobs/suitespec.yml | 15 - ...e_generativeai.test_gemini_completion.json | 28 - ...rativeai.test_gemini_completion_error.json | 31 - ...rativeai.test_gemini_completion_image.json | 28 - ...t_gemini_completion_multiple_messages.json | 28 - ...ativeai.test_gemini_completion_stream.json | 28 - ....test_gemini_completion_system_prompt.json | 28 - ...ai.test_gemini_completion_tool_stream.json | 28 - ...veai.test_gemini_tool_chat_completion.json | 56 -- ...erativeai.test_gemini_tool_completion.json | 28 - 45 files changed, 18 insertions(+), 2666 deletions(-) delete mode 100644 .riot/requirements/165cb23.txt delete mode 100644 .riot/requirements/166f21a.txt delete mode 100644 .riot/requirements/18caf61.txt delete mode 100644 .riot/requirements/1bee666.txt delete mode 100644 .riot/requirements/55b8536.txt delete mode 100644 .riot/requirements/5ac9b4e.txt delete mode 100644 .riot/requirements/6820ef2.txt delete mode 100644 .riot/requirements/ab2f587.txt delete mode 100644 .riot/requirements/ac77620.txt delete mode 100644 .riot/requirements/c16273a.txt delete mode 100644 ddtrace/contrib/internal/google_generativeai/__init__.py delete mode 100644 ddtrace/contrib/internal/google_generativeai/_utils.py delete mode 100644 ddtrace/contrib/internal/google_generativeai/patch.py delete mode 100644 ddtrace/llmobs/_integrations/gemini.py create mode 100644 releasenotes/notes/upgrade-google-generativeai-removed-23cedc4c9dc95408.yaml delete mode 100644 tests/contrib/google_generativeai/__init__.py delete mode 100644 tests/contrib/google_generativeai/conftest.py delete mode 100644 tests/contrib/google_generativeai/test_data/apple.jpg delete mode 100644 tests/contrib/google_generativeai/test_google_generativeai.py delete mode 100644 tests/contrib/google_generativeai/test_google_generativeai_llmobs.py delete mode 100644 tests/contrib/google_generativeai/test_google_generativeai_patch.py delete mode 100644 tests/contrib/google_generativeai/utils.py delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json delete mode 100644 tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json diff --git a/.riot/requirements/165cb23.txt b/.riot/requirements/165cb23.txt deleted file mode 100644 index c33d090c510..00000000000 --- a/.riot/requirements/165cb23.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/165cb23.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 -docstring-parser==0.17.0 -google-ai-generativelanguage==0.6.6 -google-api-core[grpc]==2.25.1 -google-api-python-client==2.183.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.38.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.7.2 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.75.1 -grpcio-status==1.62.3 -httplib2==0.31.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.3.3 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==4.25.8 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pyparsing==3.2.5 -pytest==8.4.2 -pytest-asyncio==1.2.0 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-dateutil==2.9.0.post0 -requests==2.32.5 -rsa==4.9.1 -shapely==2.1.2 -six==1.17.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.15.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.5.0 -vertexai==1.71.1 diff --git a/.riot/requirements/166f21a.txt b/.riot/requirements/166f21a.txt deleted file mode 100644 index 7b53f8a5926..00000000000 --- a/.riot/requirements/166f21a.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/166f21a.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.8.2 -docstring-parser==0.16 -google-ai-generativelanguage==0.6.6 -google-api-core[grpc]==2.25.0 -google-api-python-client==2.171.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.34.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.7.2 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.73.0 -grpcio-status==1.62.3 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.3.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==4.25.8 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.5 -pydantic-core==2.33.2 -pygments==2.19.1 -pyparsing==3.2.3 -pytest==8.4.0 -pytest-asyncio==1.0.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -python-dateutil==2.9.0.post0 -requests==2.32.4 -rsa==4.9.1 -shapely==2.1.1 -six==1.17.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.4.0 -vertexai==1.71.1 diff --git a/.riot/requirements/18caf61.txt b/.riot/requirements/18caf61.txt deleted file mode 100644 index 21f16fd526e..00000000000 --- a/.riot/requirements/18caf61.txt +++ /dev/null @@ -1,66 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate --resolver=backtracking .riot/requirements/18caf61.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.8.2 -docstring-parser==0.16 -exceptiongroup==1.3.0 -google-ai-generativelanguage==0.6.6 -google-api-core[grpc]==2.25.0 -google-api-python-client==2.171.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.34.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.7.2 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.73.0 -grpcio-status==1.62.3 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.0.2 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==4.25.8 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.5 -pydantic-core==2.33.2 -pygments==2.19.1 -pyparsing==3.2.3 -pytest==8.4.0 -pytest-asyncio==1.0.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -python-dateutil==2.9.0.post0 -requests==2.32.4 -rsa==4.9.1 -shapely==2.0.7 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.4.0 -vertexai==1.71.1 diff --git a/.riot/requirements/1bee666.txt b/.riot/requirements/1bee666.txt deleted file mode 100644 index 70c923d2825..00000000000 --- a/.riot/requirements/1bee666.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1bee666.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.8 -docstring-parser==0.16 -exceptiongroup==1.2.2 -google-ai-generativelanguage==0.6.10 -google-api-core[grpc]==2.23.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.27.0 -google-cloud-core==2.4.1 -google-cloud-resource-manager==1.13.1 -google-cloud-storage==2.18.2 -google-crc32c==1.6.0 -google-generativeai==0.8.3 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.66.0 -grpc-google-iam-v1==0.13.1 -grpcio==1.68.0 -grpcio-status==1.68.0 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -numpy==2.0.2 -opentracing==2.4.0 -packaging==24.2 -pillow==11.0.0 -pluggy==1.5.0 -proto-plus==1.25.0 -protobuf==5.28.3 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pydantic==2.10.2 -pydantic-core==2.27.1 -pyparsing==3.2.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -rsa==4.9 -shapely==2.0.6 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.1.0 -tqdm==4.67.1 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.3 -vertexai==1.71.1 diff --git a/.riot/requirements/55b8536.txt b/.riot/requirements/55b8536.txt deleted file mode 100644 index ed6036adcd1..00000000000 --- a/.riot/requirements/55b8536.txt +++ /dev/null @@ -1,62 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/55b8536.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.8 -docstring-parser==0.16 -google-ai-generativelanguage==0.6.10 -google-api-core[grpc]==2.23.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.27.0 -google-cloud-core==2.4.1 -google-cloud-resource-manager==1.13.1 -google-cloud-storage==2.18.2 -google-crc32c==1.6.0 -google-generativeai==0.8.3 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.66.0 -grpc-google-iam-v1==0.13.1 -grpcio==1.68.0 -grpcio-status==1.68.0 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -numpy==2.1.3 -opentracing==2.4.0 -packaging==24.2 -pillow==11.0.0 -pluggy==1.5.0 -proto-plus==1.25.0 -protobuf==5.28.3 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pydantic==2.10.2 -pydantic-core==2.27.1 -pyparsing==3.2.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -rsa==4.9 -shapely==2.0.6 -six==1.16.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.3 -vertexai==1.71.1 diff --git a/.riot/requirements/5ac9b4e.txt b/.riot/requirements/5ac9b4e.txt deleted file mode 100644 index 046c13e20b2..00000000000 --- a/.riot/requirements/5ac9b4e.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/5ac9b4e.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 -docstring-parser==0.17.0 -google-ai-generativelanguage==0.6.15 -google-api-core[grpc]==2.25.1 -google-api-python-client==2.183.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.38.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.8.5 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.75.1 -grpcio-status==1.71.2 -httplib2==0.31.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.3.3 -opentracing==2.4.0 -packaging==25.0 -pillow==11.3.0 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==5.29.5 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pyparsing==3.2.5 -pytest==8.4.2 -pytest-asyncio==1.2.0 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-dateutil==2.9.0.post0 -requests==2.32.5 -rsa==4.9.1 -shapely==2.1.2 -six==1.17.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.15.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.5.0 -vertexai==1.71.1 diff --git a/.riot/requirements/6820ef2.txt b/.riot/requirements/6820ef2.txt deleted file mode 100644 index 2db99b509e5..00000000000 --- a/.riot/requirements/6820ef2.txt +++ /dev/null @@ -1,62 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6820ef2.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.8 -docstring-parser==0.16 -google-ai-generativelanguage==0.6.10 -google-api-core[grpc]==2.23.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.27.0 -google-cloud-core==2.4.1 -google-cloud-resource-manager==1.13.1 -google-cloud-storage==2.18.2 -google-crc32c==1.6.0 -google-generativeai==0.8.3 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.66.0 -grpc-google-iam-v1==0.13.1 -grpcio==1.68.0 -grpcio-status==1.68.0 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -numpy==2.1.3 -opentracing==2.4.0 -packaging==24.2 -pillow==11.0.0 -pluggy==1.5.0 -proto-plus==1.25.0 -protobuf==5.28.3 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pydantic==2.10.2 -pydantic-core==2.27.1 -pyparsing==3.2.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -rsa==4.9 -shapely==2.0.6 -six==1.16.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.3 -vertexai==1.71.1 diff --git a/.riot/requirements/ab2f587.txt b/.riot/requirements/ab2f587.txt deleted file mode 100644 index 29fd2375edd..00000000000 --- a/.riot/requirements/ab2f587.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ab2f587.in -# -annotated-types==0.7.0 -attrs==24.2.0 -cachetools==5.5.0 -certifi==2024.8.30 -charset-normalizer==3.4.0 -coverage[toml]==7.6.8 -docstring-parser==0.16 -exceptiongroup==1.2.2 -google-ai-generativelanguage==0.6.10 -google-api-core[grpc]==2.23.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.27.0 -google-cloud-core==2.4.1 -google-cloud-resource-manager==1.13.1 -google-cloud-storage==2.18.2 -google-crc32c==1.6.0 -google-generativeai==0.8.3 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.66.0 -grpc-google-iam-v1==0.13.1 -grpcio==1.68.0 -grpcio-status==1.68.0 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -numpy==2.1.3 -opentracing==2.4.0 -packaging==24.2 -pillow==11.0.0 -pluggy==1.5.0 -proto-plus==1.25.0 -protobuf==5.28.3 -pyasn1==0.6.1 -pyasn1-modules==0.4.1 -pydantic==2.10.2 -pydantic-core==2.27.1 -pyparsing==3.2.0 -pytest==8.3.3 -pytest-asyncio==0.24.0 -pytest-cov==6.0.0 -pytest-mock==3.14.0 -python-dateutil==2.9.0.post0 -requests==2.32.3 -rsa==4.9 -shapely==2.0.6 -six==1.16.0 -sortedcontainers==2.4.0 -tomli==2.1.0 -tqdm==4.67.1 -typing-extensions==4.12.2 -uritemplate==4.1.1 -urllib3==2.2.3 -vertexai==1.71.1 diff --git a/.riot/requirements/ac77620.txt b/.riot/requirements/ac77620.txt deleted file mode 100644 index adb4c0a9955..00000000000 --- a/.riot/requirements/ac77620.txt +++ /dev/null @@ -1,66 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/ac77620.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.8.2 -docstring-parser==0.16 -exceptiongroup==1.3.0 -google-ai-generativelanguage==0.6.6 -google-api-core[grpc]==2.25.0 -google-api-python-client==2.171.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.34.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.7.2 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.73.0 -grpcio-status==1.62.3 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.2.6 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==4.25.8 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.5 -pydantic-core==2.33.2 -pygments==2.19.1 -pyparsing==3.2.3 -pytest==8.4.0 -pytest-asyncio==1.0.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -python-dateutil==2.9.0.post0 -requests==2.32.4 -rsa==4.9.1 -shapely==2.1.1 -six==1.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.4.0 -vertexai==1.71.1 diff --git a/.riot/requirements/c16273a.txt b/.riot/requirements/c16273a.txt deleted file mode 100644 index 6f97a5d643a..00000000000 --- a/.riot/requirements/c16273a.txt +++ /dev/null @@ -1,64 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c16273a.in -# -annotated-types==0.7.0 -attrs==25.3.0 -cachetools==5.5.2 -certifi==2025.4.26 -charset-normalizer==3.4.2 -coverage[toml]==7.8.2 -docstring-parser==0.16 -google-ai-generativelanguage==0.6.6 -google-api-core[grpc]==2.25.0 -google-api-python-client==2.171.0 -google-auth==2.40.3 -google-auth-httplib2==0.2.0 -google-cloud-aiplatform[all]==1.71.1 -google-cloud-bigquery==3.34.0 -google-cloud-core==2.4.3 -google-cloud-resource-manager==1.14.2 -google-cloud-storage==2.19.0 -google-crc32c==1.7.1 -google-generativeai==0.7.2 -google-resumable-media==2.7.2 -googleapis-common-protos[grpc]==1.70.0 -grpc-google-iam-v1==0.14.2 -grpcio==1.73.0 -grpcio-status==1.62.3 -httplib2==0.22.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -numpy==2.3.0 -opentracing==2.4.0 -packaging==25.0 -pillow==11.2.1 -pluggy==1.6.0 -proto-plus==1.26.1 -protobuf==4.25.8 -pyasn1==0.6.1 -pyasn1-modules==0.4.2 -pydantic==2.11.5 -pydantic-core==2.33.2 -pygments==2.19.1 -pyparsing==3.2.3 -pytest==8.4.0 -pytest-asyncio==1.0.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -python-dateutil==2.9.0.post0 -requests==2.32.4 -rsa==4.9.1 -shapely==2.1.1 -six==1.17.0 -sortedcontainers==2.4.0 -tqdm==4.67.1 -typing-extensions==4.14.0 -typing-inspection==0.4.1 -uritemplate==4.2.0 -urllib3==2.4.0 -vertexai==1.71.1 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 6301ffffac7..f935b8b9f13 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -47,7 +47,6 @@ "algoliasearch": True, "futures": True, "google_adk": True, - "google_generativeai": True, "google_genai": True, "gevent": True, "graphql": True, @@ -163,7 +162,6 @@ "httplib": ("http.client",), "kafka": ("confluent_kafka",), "google_adk": ("google.adk",), - "google_generativeai": ("google.generativeai",), "google_genai": ("google.genai",), "langchain": ("langchain_core",), "langgraph": ( diff --git a/ddtrace/contrib/integration_registry/registry.yaml b/ddtrace/contrib/integration_registry/registry.yaml index e6481a21362..56ea8a82f51 100644 --- a/ddtrace/contrib/integration_registry/registry.yaml +++ b/ddtrace/contrib/integration_registry/registry.yaml @@ -403,16 +403,6 @@ integrations: min: 1.21.1 max: 1.41.0 -- integration_name: google_generativeai - is_external_package: true - is_tested: true - dependency_names: - - google-generativeai - tested_versions_by_dependency: - google-generativeai: - min: 0.7.2 - max: 0.8.5 - - integration_name: graphql is_external_package: true is_tested: true diff --git a/ddtrace/contrib/internal/google_generativeai/__init__.py b/ddtrace/contrib/internal/google_generativeai/__init__.py deleted file mode 100644 index 3247354ee17..00000000000 --- a/ddtrace/contrib/internal/google_generativeai/__init__.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -The Gemini integration instruments the Google Gemini Python API to traces for requests made to Google models. - -All traces submitted from the Gemini integration are tagged by: - -- ``service``, ``env``, ``version``: see the `Unified Service Tagging docs `_. -- ``google_generativeai.request.model``: Google model used in the request. -- ``google_generativeai.request.api_key``: Google Gemini API key used to make the request (obfuscated to match the Google AI Studio UI representation ``...XXXX`` where ``XXXX`` is the last 4 digits of the key). - - -(beta) Prompt and Completion Sampling -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Prompt texts and completion content for the ``generateContent`` endpoint are collected in span tags with a default sampling rate of ``1.0``. -These tags will have truncation applied if the text exceeds the configured character limit. - - -Enabling -~~~~~~~~ - -The Gemini integration is enabled automatically when you use -:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. - -Alternatively, use :func:`patch() ` to manually enable the Gemini integration:: - - from ddtrace import config, patch - - patch(google_generativeai=True) - - -Global Configuration -~~~~~~~~~~~~~~~~~~~~ - -.. py:data:: ddtrace.config.google_generativeai["service"] - - The service name reported by default for Gemini requests. - - Alternatively, you can set this option with the ``DD_SERVICE`` or ``DD_GOOGLE_GENERATIVEAI_SERVICE`` environment - variables. - - Default: ``DD_SERVICE`` - - -.. py:data:: (beta) ddtrace.config.google_generativeai["span_char_limit"] - - Configure the maximum number of characters for the following data within span tags: - - - Text inputs and completions - - Text exceeding the maximum number of characters is truncated to the character limit - and has ``...`` appended to the end. - - Alternatively, you can set this option with the ``DD_GOOGLE_GENERATIVEAI_SPAN_CHAR_LIMIT`` environment - variable. - - Default: ``128`` - - -.. py:data:: (beta) ddtrace.config.google_generativeai["span_prompt_completion_sample_rate"] - - Configure the sample rate for the collection of prompts and completions as span tags. - - Alternatively, you can set this option with the ``DD_GOOGLE_GENERATIVEAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE`` environment - variable. - - Default: ``1.0`` - - -Instance Configuration -~~~~~~~~~~~~~~~~~~~~~~ - -To configure the Gemini integration on a per-instance basis use the -``Pin`` API:: - - import google.generativeai as genai - from ddtrace import config - from ddtrace._trace.pin import Pin - - Pin.override(genai, service="my-gemini-service") -""" # noqa: E501 diff --git a/ddtrace/contrib/internal/google_generativeai/_utils.py b/ddtrace/contrib/internal/google_generativeai/_utils.py deleted file mode 100644 index 73c210118f8..00000000000 --- a/ddtrace/contrib/internal/google_generativeai/_utils.py +++ /dev/null @@ -1,24 +0,0 @@ -from ddtrace.llmobs._integrations.base_stream_handler import AsyncStreamHandler -from ddtrace.llmobs._integrations.base_stream_handler import StreamHandler - - -class BaseGoogleGenerativeAIStramHandler: - def finalize_stream(self, exception=None): - self.request_kwargs["instance"] = self.options.get("model_instance", None) - self.integration.llmobs_set_tags( - self.primary_span, - args=self.request_args, - kwargs=self.request_kwargs, - response=self.options.get("wrapped_stream", None), - ) - self.primary_span.finish() - - -class GoogleGenerativeAIStramHandler(BaseGoogleGenerativeAIStramHandler, StreamHandler): - def process_chunk(self, chunk, iterator=None): - pass - - -class GoogleGenerativeAIAsyncStreamHandler(BaseGoogleGenerativeAIStramHandler, AsyncStreamHandler): - async def process_chunk(self, chunk, iterator=None): - pass diff --git a/ddtrace/contrib/internal/google_generativeai/patch.py b/ddtrace/contrib/internal/google_generativeai/patch.py deleted file mode 100644 index 8aaf422f509..00000000000 --- a/ddtrace/contrib/internal/google_generativeai/patch.py +++ /dev/null @@ -1,130 +0,0 @@ -import os -import sys -from typing import Dict - -import google.generativeai as genai - -from ddtrace import config -from ddtrace._trace.pin import Pin -from ddtrace.contrib.internal.google_generativeai._utils import GoogleGenerativeAIAsyncStreamHandler -from ddtrace.contrib.internal.google_generativeai._utils import GoogleGenerativeAIStramHandler -from ddtrace.contrib.internal.trace_utils import unwrap -from ddtrace.contrib.internal.trace_utils import with_traced_module -from ddtrace.contrib.internal.trace_utils import wrap -from ddtrace.llmobs._integrations import GeminiIntegration -from ddtrace.llmobs._integrations.base_stream_handler import make_traced_stream -from ddtrace.llmobs._integrations.google_utils import extract_provider_and_model_name - - -config._add( - "genai", - { - "span_prompt_completion_sample_rate": float( - os.getenv("DD_GOOGLE_GENERATIVEAI_SPAN_PROMPT_COMPLETION_SAMPLE_RATE", 1.0) - ), - "span_char_limit": int(os.getenv("DD_GOOGLE_GENERATIVEAI_SPAN_CHAR_LIMIT", 128)), - }, -) - - -def get_version(): - # type: () -> str - return getattr(genai, "__version__", "") - - -def _supported_versions() -> Dict[str, str]: - return {"google.generativeai": ">=0.7.0"} - - -@with_traced_module -def traced_generate(genai, pin, func, instance, args, kwargs): - integration = genai._datadog_integration - stream = kwargs.get("stream", False) - generations = None - provider_name, model_name = extract_provider_and_model_name(instance=instance, model_name_attr="model_name") - span = integration.trace( - pin, - "%s.%s" % (instance.__class__.__name__, func.__name__), - provider=provider_name, - model=model_name, - submit_to_llmobs=True, - ) - try: - generations = func(*args, **kwargs) - if stream: - return make_traced_stream( - generations, - GoogleGenerativeAIStramHandler( - integration, span, args, kwargs, model_instance=instance, wrapped_stream=generations - ), - ) - except Exception: - span.set_exc_info(*sys.exc_info()) - raise - finally: - # streamed spans will be finished separately once the stream generator is exhausted - if span.error or not stream: - kwargs["instance"] = instance - integration.llmobs_set_tags(span, args=args, kwargs=kwargs, response=generations) - span.finish() - return generations - - -@with_traced_module -async def traced_agenerate(genai, pin, func, instance, args, kwargs): - integration = genai._datadog_integration - stream = kwargs.get("stream", False) - generations = None - provider_name, model_name = extract_provider_and_model_name(instance=instance, model_name_attr="model_name") - span = integration.trace( - pin, - "%s.%s" % (instance.__class__.__name__, func.__name__), - provider=provider_name, - model=model_name, - submit_to_llmobs=True, - ) - try: - generations = await func(*args, **kwargs) - if stream: - return make_traced_stream( - generations, - GoogleGenerativeAIAsyncStreamHandler( - integration, span, args, kwargs, model_instance=instance, wrapped_stream=generations - ), - ) - except Exception: - span.set_exc_info(*sys.exc_info()) - raise - finally: - # streamed spans will be finished separately once the stream generator is exhausted - if span.error or not stream: - kwargs["instance"] = instance - integration.llmobs_set_tags(span, args=args, kwargs=kwargs, response=generations) - span.finish() - return generations - - -def patch(): - if getattr(genai, "_datadog_patch", False): - return - - genai._datadog_patch = True - - Pin().onto(genai) - integration = GeminiIntegration(integration_config=config.genai) - genai._datadog_integration = integration - - wrap("google.generativeai", "GenerativeModel.generate_content", traced_generate(genai)) - wrap("google.generativeai", "GenerativeModel.generate_content_async", traced_agenerate(genai)) - - -def unpatch(): - if not getattr(genai, "_datadog_patch", False): - return - - genai._datadog_patch = False - - unwrap(genai.GenerativeModel, "generate_content") - unwrap(genai.GenerativeModel, "generate_content_async") - - delattr(genai, "_datadog_integration") diff --git a/ddtrace/internal/settings/_config.py b/ddtrace/internal/settings/_config.py index 9f1f2c8ec9c..86c7aa289c0 100644 --- a/ddtrace/internal/settings/_config.py +++ b/ddtrace/internal/settings/_config.py @@ -99,7 +99,6 @@ "pyodbc", "dramatiq", "flask", - "google_generativeai", "google_genai", "google_adk", "urllib3", diff --git a/ddtrace/llmobs/_integrations/__init__.py b/ddtrace/llmobs/_integrations/__init__.py index c79c6033ddb..5827a62ffcb 100644 --- a/ddtrace/llmobs/_integrations/__init__.py +++ b/ddtrace/llmobs/_integrations/__init__.py @@ -1,7 +1,6 @@ from .anthropic import AnthropicIntegration from .base import BaseLLMIntegration from .bedrock import BedrockIntegration -from .gemini import GeminiIntegration from .google_adk import GoogleAdkIntegration from .google_genai import GoogleGenAIIntegration from .langchain import LangChainIntegration @@ -15,7 +14,6 @@ "AnthropicIntegration", "BaseLLMIntegration", "BedrockIntegration", - "GeminiIntegration", "GoogleAdkIntegration", "GoogleGenAIIntegration", "LangChainIntegration", diff --git a/ddtrace/llmobs/_integrations/gemini.py b/ddtrace/llmobs/_integrations/gemini.py deleted file mode 100644 index eb87f95df46..00000000000 --- a/ddtrace/llmobs/_integrations/gemini.py +++ /dev/null @@ -1,131 +0,0 @@ -from typing import Any -from typing import Dict -from typing import Iterable -from typing import List -from typing import Optional - -from ddtrace.internal.utils import get_argument_value -from ddtrace.llmobs._constants import INPUT_MESSAGES -from ddtrace.llmobs._constants import INPUT_TOKENS_METRIC_KEY -from ddtrace.llmobs._constants import METADATA -from ddtrace.llmobs._constants import METRICS -from ddtrace.llmobs._constants import MODEL_NAME -from ddtrace.llmobs._constants import MODEL_PROVIDER -from ddtrace.llmobs._constants import OUTPUT_MESSAGES -from ddtrace.llmobs._constants import OUTPUT_TOKENS_METRIC_KEY -from ddtrace.llmobs._constants import SPAN_KIND -from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY -from ddtrace.llmobs._integrations.base import BaseLLMIntegration -from ddtrace.llmobs._integrations.google_utils import extract_message_from_part_gemini_vertexai -from ddtrace.llmobs._integrations.google_utils import get_system_instructions_gemini_vertexai -from ddtrace.llmobs._integrations.google_utils import llmobs_get_metadata_gemini_vertexai -from ddtrace.llmobs._utils import _get_attr -from ddtrace.llmobs.types import Message -from ddtrace.trace import Span - - -class GeminiIntegration(BaseLLMIntegration): - _integration_name = "gemini" - - def _set_base_span_tags( - self, span: Span, provider: Optional[str] = None, model: Optional[str] = None, **kwargs: Dict[str, Any] - ) -> None: - if provider is not None: - span._set_tag_str("google_generativeai.request.provider", str(provider)) - if model is not None: - span._set_tag_str("google_generativeai.request.model", str(model)) - - def _llmobs_set_tags( - self, - span: Span, - args: List[Any], - kwargs: Dict[str, Any], - response: Optional[Any] = None, - operation: str = "", - ) -> None: - instance = kwargs.get("instance", None) - metadata = llmobs_get_metadata_gemini_vertexai(kwargs, instance) - - system_instruction = get_system_instructions_gemini_vertexai(instance) - input_contents = get_argument_value(args, kwargs, 0, "contents") - input_messages: List[Message] = self._extract_input_message(input_contents, system_instruction) - - output_messages: List[Message] = [Message(content="")] - if response is not None: - output_messages = self._extract_output_message(response) - - span._set_ctx_items( - { - SPAN_KIND: "llm", - MODEL_NAME: span.get_tag("google_generativeai.request.model") or "", - MODEL_PROVIDER: span.get_tag("google_generativeai.request.provider") or "", - METADATA: metadata, - INPUT_MESSAGES: input_messages, - OUTPUT_MESSAGES: output_messages, - METRICS: self._extract_metrics(response), - } - ) - - def _extract_input_message(self, contents, system_instruction=None): - messages: List[Message] = [] - if system_instruction: - for instruction in system_instruction: - messages.append(Message(content=instruction or "", role="system")) - if isinstance(contents, str): - messages.append(Message(content=contents)) - return messages - if isinstance(contents, dict): - message = Message(content=contents.get("text", "")) - if contents.get("role", None): - message["role"] = contents["role"] - messages.append(message) - return messages - if not isinstance(contents, list): - messages.append(Message(content="[Non-text content object: {}]".format(repr(contents)))) - return messages - for content in contents: - if isinstance(content, str): - messages.append(Message(content=content)) - continue - role = _get_attr(content, "role", None) - parts = _get_attr(content, "parts", []) - if not parts or not isinstance(parts, Iterable): - message = Message(content="[Non-text content object: {}]".format(repr(content))) - if role: - message["role"] = role - messages.append(message) - continue - for part in parts: - message = extract_message_from_part_gemini_vertexai(part, role) - messages.append(message) - return messages - - def _extract_output_message(self, generations): - output_messages = [] - generations_dict = generations.to_dict() - for candidate in generations_dict.get("candidates", []): - content = candidate.get("content", {}) - role = content.get("role", "model") - parts = content.get("parts", []) - for part in parts: - message = extract_message_from_part_gemini_vertexai(part, role) - output_messages.append(message) - return output_messages - - def _extract_metrics(self, generations): - if not generations: - return {} - generations_dict = generations.to_dict() - - token_counts = generations_dict.get("usage_metadata", None) - if not token_counts: - return - input_tokens = token_counts.get("prompt_token_count", 0) - output_tokens = token_counts.get("candidates_token_count", 0) - total_tokens = input_tokens + output_tokens - - usage = {} - usage[INPUT_TOKENS_METRIC_KEY] = input_tokens - usage[OUTPUT_TOKENS_METRIC_KEY] = output_tokens - usage[TOTAL_TOKENS_METRIC_KEY] = total_tokens - return usage diff --git a/ddtrace/llmobs/_integrations/google_utils.py b/ddtrace/llmobs/_integrations/google_utils.py index 76c12daeac8..29cb06a5857 100644 --- a/ddtrace/llmobs/_integrations/google_utils.py +++ b/ddtrace/llmobs/_integrations/google_utils.py @@ -53,9 +53,8 @@ def extract_provider_and_model_name( Function to extract provider and model name from either kwargs or instance attributes. Args: kwargs: Dictionary containing model information (used for google_genai) - instance: Model instance with attributes (used for vertexai and google_generativeai) - model_name_attr: Attribute name to extract from instance (e.g., "_model_name", "model_name", used for vertexai - and google_generativeai) + instance: Model instance with attributes (used for vertexai) + model_name_attr: Attribute name to extract from instance (e.g., "_model_name", "model_name", used for vertexai) Returns: Tuple of (provider_name, model_name) @@ -237,7 +236,7 @@ def extract_message_from_part_google_genai(part, role: str) -> Message: return Message(content="Unsupported file type: {}".format(type(part)), role=role) -def llmobs_get_metadata_gemini_vertexai(kwargs, instance): +def llmobs_get_metadata_vertexai(kwargs, instance): metadata = {} model_config = getattr(instance, "_generation_config", {}) or {} model_config = model_config.to_dict() if hasattr(model_config, "to_dict") else model_config @@ -253,7 +252,7 @@ def llmobs_get_metadata_gemini_vertexai(kwargs, instance): return metadata -def extract_message_from_part_gemini_vertexai(part, role=None) -> Message: +def extract_message_from_part_vertexai(part, role=None) -> Message: text = _get_attr(part, "text", "") function_call = _get_attr(part, "function_call", None) function_response = _get_attr(part, "function_response", None) @@ -289,7 +288,7 @@ def extract_message_from_part_gemini_vertexai(part, role=None) -> Message: return message -def get_system_instructions_gemini_vertexai(model_instance): +def get_system_instructions_vertexai(model_instance): """ Extract system instructions from model and convert to []str for tagging. """ diff --git a/ddtrace/llmobs/_integrations/langchain.py b/ddtrace/llmobs/_integrations/langchain.py index 7c3c34813d6..7cd214a4bef 100644 --- a/ddtrace/llmobs/_integrations/langchain.py +++ b/ddtrace/llmobs/_integrations/langchain.py @@ -64,7 +64,6 @@ OPENAI_PROVIDER_NAME = "openai" AZURE_OAI_PROVIDER_NAME = "azure" VERTEXAI_PROVIDER_NAME = "vertexai" -GEMINI_PROVIDER_NAME = "google_palm" ROLE_MAPPING = { "human": "user", @@ -187,9 +186,6 @@ def _llmobs_set_tags( # only the llm interface for Vertex AI will get instrumented elif model_provider.startswith(VERTEXAI_PROVIDER_NAME) and operation == "llm": llmobs_integration = "vertexai" - # only the llm interface for Gemini will get instrumented - elif model_provider.startswith(GEMINI_PROVIDER_NAME) and operation == "llm": - llmobs_integration = "google_generativeai" elif any(provider in model_provider for provider in (OPENAI_PROVIDER_NAME, AZURE_OAI_PROVIDER_NAME)): llmobs_integration = "openai" elif operation == "chat" and model_provider.startswith(ANTHROPIC_PROVIDER_NAME): diff --git a/ddtrace/llmobs/_integrations/vertexai.py b/ddtrace/llmobs/_integrations/vertexai.py index 05c30e46c8e..330130c96e2 100644 --- a/ddtrace/llmobs/_integrations/vertexai.py +++ b/ddtrace/llmobs/_integrations/vertexai.py @@ -18,9 +18,9 @@ from ddtrace.llmobs._constants import TOOL_DEFINITIONS from ddtrace.llmobs._constants import TOTAL_TOKENS_METRIC_KEY from ddtrace.llmobs._integrations.base import BaseLLMIntegration -from ddtrace.llmobs._integrations.google_utils import extract_message_from_part_gemini_vertexai -from ddtrace.llmobs._integrations.google_utils import get_system_instructions_gemini_vertexai -from ddtrace.llmobs._integrations.google_utils import llmobs_get_metadata_gemini_vertexai +from ddtrace.llmobs._integrations.google_utils import extract_message_from_part_vertexai +from ddtrace.llmobs._integrations.google_utils import get_system_instructions_vertexai +from ddtrace.llmobs._integrations.google_utils import llmobs_get_metadata_vertexai from ddtrace.llmobs._utils import _get_attr from ddtrace.llmobs.types import Message from ddtrace.trace import Span @@ -48,9 +48,9 @@ def _llmobs_set_tags( instance = kwargs.get("instance", None) history = kwargs.get("history", []) metrics = kwargs.get("metrics", {}) - metadata = llmobs_get_metadata_gemini_vertexai(kwargs, instance) + metadata = llmobs_get_metadata_vertexai(kwargs, instance) - system_instruction = get_system_instructions_gemini_vertexai(instance) + system_instruction = get_system_instructions_vertexai(instance) input_contents = None try: input_contents = get_argument_value(args, kwargs, 0, "content") @@ -123,7 +123,7 @@ def _extract_input_message(self, contents, history, system_instruction=None) -> messages.append(Message(content=contents)) return messages if isinstance(contents, Part): - message = extract_message_from_part_gemini_vertexai(contents) + message = extract_message_from_part_vertexai(contents) messages.append(message) return messages if not isinstance(contents, list): @@ -134,7 +134,7 @@ def _extract_input_message(self, contents, history, system_instruction=None) -> messages.append(Message(content=content)) continue if isinstance(content, Part): - message = extract_message_from_part_gemini_vertexai(content) + message = extract_message_from_part_vertexai(content) messages.append(message) continue messages.extend(self._extract_messages_from_content(content)) @@ -176,7 +176,7 @@ def _extract_messages_from_content(content) -> List[Message]: messages.append(message) return messages for part in parts: - message = extract_message_from_part_gemini_vertexai(part, role) + message = extract_message_from_part_vertexai(part, role) messages.append(message) return messages diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index bfbc5fb57f8..8c957cc0ea5 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -131,7 +131,6 @@ "openai": "openai", "langchain": "langchain", "google_adk": "google_adk", - "google_generativeai": "google_generativeai", "google_genai": "google_genai", "vertexai": "vertexai", "langgraph": "langgraph", diff --git a/docs/index.rst b/docs/index.rst index 93aa02503ad..65e88bb1b95 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -112,8 +112,6 @@ contacting support. +--------------------------------------------------+------------+----------+------+ | :ref:`google_genai` | >= 1.21.1 | Yes | | +--------------------------------------------------+------------+----------+------+ -| :ref:`google_generativeai` | >= 0.7.0 | Yes | | -+--------------------------------------------------+------------+----------+------+ | :ref:`grpc` | >= 1.34 | Yes [4]_ | | +--------------------------------------------------+------------+----------+------+ | :ref:`graphene ` | >= 3.0.0 | Yes | | diff --git a/docs/integrations.rst b/docs/integrations.rst index 18223f31009..87907d141ab 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -236,12 +236,6 @@ google-genai ^^^^^^^^^^^^ .. automodule:: ddtrace.contrib.internal.google_genai -.. _google_generativeai: - -google-generativeai -^^^^^^^^^^^^^^^^^^^ -.. automodule:: ddtrace.contrib.internal.google_generativeai - .. _graphql: diff --git a/releasenotes/notes/upgrade-google-generativeai-removed-23cedc4c9dc95408.yaml b/releasenotes/notes/upgrade-google-generativeai-removed-23cedc4c9dc95408.yaml new file mode 100644 index 00000000000..d6e7507978e --- /dev/null +++ b/releasenotes/notes/upgrade-google-generativeai-removed-23cedc4c9dc95408.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + google_generativeai: The ``google_generativeai`` integration has been removed as the ``google_generativeai`` library has reached end-of-life. + As an alternative, you can use the recommended ``google_genai`` library and corresponding integration instead. diff --git a/riotfile.py b/riotfile.py index e7716b570ec..e3363147fab 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2883,22 +2883,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), ], ), - Venv( - name="google_generativeai", - command="pytest {cmdargs} tests/contrib/google_generativeai", - venvs=[ - Venv( - pys=select_pys(min_version="3.9", max_version="3.13"), - pkgs={ - "pytest-asyncio": latest, - "google-generativeai": ["~=0.7.0", latest], - "pillow": latest, - "google-ai-generativelanguage": [latest], - "vertexai": [latest], - }, - ) - ], - ), Venv( name="vertexai", command="pytest {cmdargs} tests/contrib/vertexai", diff --git a/supported_versions_output.json b/supported_versions_output.json index 04e1f3c865a..eb8b6780e3f 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -322,13 +322,6 @@ "max_tracer_supported": "1.41.0", "auto-instrumented": true }, - { - "dependency": "google-generativeai", - "integration": "google_generativeai", - "minimum_tracer_supported": "0.7.2", - "max_tracer_supported": "0.8.5", - "auto-instrumented": true - }, { "dependency": "graphql-core", "integration": "graphql", diff --git a/supported_versions_table.csv b/supported_versions_table.csv index ae1dc58328b..2249d582811 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -44,7 +44,6 @@ flask-caching,flask_cache,1.10.1,2.3.0,False gevent,gevent,21.1.2,25.5.1,True google-adk,google_adk,1.0.0,1.15.1,True google-genai,google_genai,1.21.1,1.41.0,True -google-generativeai,google_generativeai,0.7.2,0.8.5,True graphql-core,graphql,3.1.7,3.2.6,True grpcio,grpc,1.34.1,1.75.1,True httpx,httpx,0.17.1,0.28.1,True diff --git a/tests/contrib/google_generativeai/__init__.py b/tests/contrib/google_generativeai/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/contrib/google_generativeai/conftest.py b/tests/contrib/google_generativeai/conftest.py deleted file mode 100644 index b30aa1c0fc8..00000000000 --- a/tests/contrib/google_generativeai/conftest.py +++ /dev/null @@ -1,87 +0,0 @@ -import os - -import mock -import pytest - -from ddtrace._trace.pin import Pin -from ddtrace.contrib.internal.google_generativeai.patch import patch -from ddtrace.contrib.internal.google_generativeai.patch import unpatch -from ddtrace.llmobs import LLMObs -from tests.contrib.google_generativeai.utils import MockGenerativeModelAsyncClient -from tests.contrib.google_generativeai.utils import MockGenerativeModelClient -from tests.utils import DummyTracer -from tests.utils import DummyWriter -from tests.utils import override_config -from tests.utils import override_env -from tests.utils import override_global_config - - -def default_global_config(): - return {"_dd_api_key": ""} - - -@pytest.fixture -def ddtrace_global_config(): - return {} - - -@pytest.fixture -def ddtrace_config_google_generativeai(): - return {} - - -@pytest.fixture -def mock_tracer(ddtrace_global_config, genai): - try: - pin = Pin.get_from(genai) - mock_tracer = DummyTracer(writer=DummyWriter(trace_flush_enabled=False)) - pin._override(genai, tracer=mock_tracer) - if ddtrace_global_config.get("_llmobs_enabled", False): - # Have to disable and re-enable LLMObs to use to mock tracer. - LLMObs.disable() - LLMObs.enable(_tracer=mock_tracer, integrations_enabled=False) - yield mock_tracer - except Exception: - yield - - -@pytest.fixture -def mock_llmobs_writer(): - patcher = mock.patch("ddtrace.llmobs._llmobs.LLMObsSpanWriter") - try: - LLMObsSpanWriterMock = patcher.start() - m = mock.MagicMock() - LLMObsSpanWriterMock.return_value = m - yield m - finally: - patcher.stop() - - -@pytest.fixture -def mock_client(): - yield MockGenerativeModelClient() - - -@pytest.fixture -def mock_client_async(): - yield MockGenerativeModelAsyncClient() - - -@pytest.fixture -def genai(ddtrace_global_config, ddtrace_config_google_generativeai, mock_client, mock_client_async): - global_config = default_global_config() - global_config.update(ddtrace_global_config) - with override_global_config(global_config): - with override_config("google_generativeai", ddtrace_config_google_generativeai): - with override_env( - dict(GOOGLE_GENERATIVEAI_API_KEY=os.getenv("GOOGLE_GENERATIVEAI_API_KEY", "")) - ): - patch() - import google.generativeai as genai - from google.generativeai import client as client_lib - - client_lib._client_manager.clients["generative"] = mock_client - client_lib._client_manager.clients["generative_async"] = mock_client_async - - yield genai - unpatch() diff --git a/tests/contrib/google_generativeai/test_data/apple.jpg b/tests/contrib/google_generativeai/test_data/apple.jpg deleted file mode 100644 index f921762ae0716fd8a21b0e4f00751a7d73ac1f52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42568 zcmcG!2Ut^0*Df3ol-{L_^d36WQF;j=gc>@8P(rAo7Yj`~(z{6SEl4j)mo8nZ2vS9= zR1r?p=Y8Ji{l4>E=l`#Bow)*;%&c|aYpq#(X7AZ^^YLZ{K%%a!rVPM97hVir0N~~` z0IuZYU<&}Ksc{2v006)(01*ZzfDv62LjTcXU;{AF-(d6~-CuQMbp2OxhuJV%xw;}? zjILH5_KXfl7lf4uy3NIz(c9j^1ICM1{H6P^@0-uqqV^shu9E!x&PYBh8y9OBAJoN( z-^a?8Uyx6LA0YkE$JGkz2=idHhS@nd%P{Y^zGY@~u#sUl6x9^abX9=aJE;1(!*u<$ z^q_u@Pzf96hq8FmK9W98u1+uyD@GqDgfmjoM~3+~xFov%tC^o!77th2-NsfDtf>5# z4EmQ0^IyhzdwcVF3-h_S+wluZNJ#Jt2=NOE0nrjbq_4Awl@HJv$?^;04~8NP33YdH z^>9GD`3uv^+QrjDhMD;Blmy(*R2UG;@go2AZ>~B3Z z2lTg5NDmwSe@gsT{p~70Gb7_)yecpc4_`GWD?1ocMNL`nZxl2tpWUxMhu?j_18(PF z>+v7_HqgK2T|M0qzgcad{4fN}3Fhp9M9T`Iss2RQ72%2eTN>?~CldCn>9-ET#+DyV z_!stH3cvhN@jMsU!o)_heX0W)D&fy1%)L9CB%gVBqVEh`Qg~`~txY!|Jf44%>-Bu2E&N9EkgdPiKMs$YTKqaB>Fe?ui zciG>0tP6AdQ$aY$TEoz|PA<+K()_>D5#92q@>_=gpPJ~p&A*fP4}cuP<6lU>hD#D* z;trc*;;ubJeXzwPZ#-rVf~Hwi}jyOc+$TzL{j5-bpAI!{JxG{t=y3?JzrOt z3O9oMkPau zw&OA}vT5y7km)wEp?TZ=zp7U>*%gT|v47=_$7@fuJ$qocRvcLLq`H_ZatKmkJ27Z5 zFKQ5?&X0V+T@EI!maRP4o|X7d3_MWp*jrgCMg{$p7-o=gIhCUCrH@pOT>92$E{JP8 z=W^;Rq(@6sNerJ$#r(#ZKn(^y?gNjnan9$e{{+IKH~n#N{9SSC9UoG=rrFMBLeunn z`plZd6PLEF)GhHLjfzdrAVlXZ(4#&THQ=A^8J>d6Myj8o56gUY{mQ)Z@Z~??C{hR4 zzV_c!7imtbkEc}Mn3PnDm7H)4bZZ+aIFvHK_jTJx^LP|x*RE~SUk&o=u``oglLW($ z%(6X4&xG`?;3$3jcB$h_8R?mtf5YCU`4qEx*0nsKq;GBVkvEYwkp=I_kW&nrX?(G- z{4U~Lu|AC9^a?NFQ4Ncxa)S*e)|+xoS(f=EPcr@ zGOM^*G1^G$NN!+T3EqY@Vpmj5!#dl~`WN<(>zn_Er`~+RpYtiocVq^_Yd>NPsehL+ zp&8FR06}IhNc#zHWPK7dldf6{{KP?fQ5TzLGBhwtgvu+~IXlKO^9 zgZUA?eD%av9tG{~(jEIxv;F_YfT0%f_~F}NhP0FqCPmGgjzTk=V9LwkcwhoZqvy?N zK9ysCerh6vjI7<#r`ffb>!*Byj{JtsMAt3V25ukv&DO@-oSF$u1k?@SsrrrVTQ$?F z<$A@^+e#;w=Q6R&H2>lu=7_8ATieenYfKpT#o7UOn`DaEA|^IwH0;DkMmmtv8~egz zV;k++mxgaQ-A2DAj{gc4VhyqR;S*t_$k$RcTOf1UjIOVwohPjxskS02*_3X1ZSmW2 zAkiz=zgXz!wvS~k%GGKEMzh>2XPn*?P6HwN!Ztf*nGlbN(uiT}F^PgNW5oB1HO}5X z9_uHTZI(J23l!~>M8M6@2hQH_?!Mk z!+7yYY+|Lqj73AM&24mFW+!H|Jkv{)kA0KJcBrUPO1KVJN3Gme%VyEd*41H_^`dUb zo6bdWzP-KJ(A-OX$kY*c_SNa2%RT1|LZh;o1$K2xJF%$3bd*2c!H@RqfM&=RZ|WpUzrI<*Cl!^7v)ykil)6uv6E^m zI`Zh9#Bb5#O@^o^+JF+}g?;w(cDH>s$7UaQeET{v=ZiI z&}cOJvUwq&jL&*wcPql%c0heD8+?d(W9QAA85yO--jJ5u!Wxzrq1Ixe-dn>)sRLtD zODq=IP2tlxHNOwpx{zA@V|~CTasI~IM9B1)7|t0{6?Hx_`?BR#wnBABH=996-;5oP z+Hp4rNF86|15|^2I7Fk^OgDFQ#=y90)XfUQ=APVQY>{z3*wUq6YU}-Hp*eEw1bQD+sF|wts>EMl;ZSm zsYkgyxJXmoe$=azolqU-8M2KZZ17n5;>p{$12bQK6CC*bXbbRz*Tcuz4Q5I#m-zB@ z^oJGt-`HAjzu(ri<#ciTz9ef`IXV0b z88hMP1#xkdC$||KT=qs7PCGC`$r@%;ph*LIuJg9i(9E3EjH9+IK0A{)K%<`qRL{rGyBV(`t(R zxMkkPcBcQP^!zl0l(SAWqds$SYh(}nvA5aGCK10}O@BA5)YD`7K?G=V#<>f7&&o2P z9BVF7Ul>l(7MrFOPg^@)Wqx}7w0*Yk7x=A28Ci?Cw>;=@LxRfnYZa(eGg~FV8%K6x zNpp9tXY4a-m4H;0X(==AjpQb^teL`P>f3A+;BBF4M?}V=zU8QcK4*QZWZu^oIaT9) z!!A{_eODF=v%lkgD+&#s`k_MKQCmq)KZq?X4_?m;em&~K)y|fgmdlfWx1gw%LyC>+ z)!sPNK**&qhA1MtF6*QOqNX#k3331$lj?zWDv@zgW(8>*9&Cv%7e9d)PvWXWe#eE< zFQA|QiLQDO9t77QR}nWCglkaeD1n++udEms)Ir)hr)ro?3QHm6E9YVg=0p;DfZ?|* zdevtjwKNO*rO`8*ougaobK`<@K7Rg3KZz|!e!&t+f4_(|-!(1^Z=fl~*QUh5=td&~feruVLiU>U3gZCOMU)Eqj$5D{tt$ZMpG?<$$~C4CQvk@Eq5q zufLh}ld7wg-@`U?bn2T}X%I33D_|=ud?~Mn&x)&LyzUu<8hixNX%%Tu6Jkx~Fo4l* zS??|nh(rOyu{v$5Daz7;8Uq>&NlDW{w`pJhC26zE-@XHU+J4IN`LEPBuvN;>bHVab za3G8%inGE+P8zD*v}74&_#uyHDYl}1sT z{brlrWu#^NHh!51aD`Tnt<4TNJ7OD9$g2TD6EKZUzv8x;kz&pH+SeIKZbDGh2j#45+QU*rCt@jLbCOi+E{@nh;_=nxj4z@F< zZH@IXL24jN3|>7N(Xj_cs;Nr)dLA0n6*~q4X&JS%>B+MSbb>rE-jvAcm*fvl6KUV1 zYB`A}N+z>Mf7dwKe}TsQvkJ-`w*@XkjjBBLc>!dUhw5#1PO3tg<$6q(wfIa3C*kC2 zwhYC}5w_v^%_?Mat?g^p9zD$R3M)h*Btt$dn%pZe@FK9|mG*D1i8*qL-;L$JDyavA z)+z>JaC6riYwDCGL6{IP8}YgHl?F|O79&a?H0@ZA12s^I25FV`X2)5cWpC7t)&l7&sxrZ61x3{18j^vPFvqwpXwiLc1~AK7@D~a-N^|TA_UIGyr=Z!*ua>{zuR7~KnG)wRSe)a)EGu{zOa2W)|%1dffScI5{?+(IWy}DYy z0X(LE_8aZi$mXZb@S%|XcE0-gR5VBm}UG81`tl&@bz!v$*Qa)nKoccAfu>Bs5gGe z%@$d1+^=l+pdO6NW(EF8TR<6A!4^YS)B(XZ|^bZ(7 z`qh)Toa_T(qjC_$4K8OL!Wo34;~ryB#2Qw}q*ej*fJE>SvbIdc8`x+s39-Q*L>rq9 z*eBJG#6V6jeSQXBy!!)mi|YwAr*W)eCP*h!A3Se_g5@O`gjA}*$rVJC5-M@Io!E>; z3zSk4!J#R*%^^YWCb+;Tc|z9ugbWVN_3ujov>*Sn=+;obOZHfPwu7FYK3qZRQD|th zJOu@Wdz{H8NEJK}fvB;wCbjHy$QD3w?NPKdLJg3FaqAiQ=&=7i>5hg!7d{#b8@lPX zb7a!%AZq*^NTD%>9hy}34t?DW*o>5u;Mi-ZFNPr;fs|l4NkZf-x8BQwTKgqRM883E zAk|~al)sDt+=`RBsJIlLA1G7j(cuNI=MCti#w}4gAoc{9{nQ9JDHJtGrkNMTs*|m; zOU_ymq^bHKy?ykIVfpG+?D?~Q!4Q+*Yg1cDcQew{FM+G3sFzx)t3nu=EXTR0^?5S} zKzZtFJc2M3cmo5fY(EnL2GI@AkEDHX+&q)?wfPqafFdrvzkJk@#=U_pG%VMaxm?t` zQa(X12*3mb;XAN~QrQ%~K_v0mkY(J(N-*QgVBhupn%X)!F#eB6;x8E&2)|~H`GOk# z(v*5~?ZP0+N1!BpE`v9bW`wLGtp#j71`Cv+foxR}8EP09C7kCxX14kD`_ZN3pDFOS zHMa)GEHe9_IKpeW;5G6+Q@or(>s;IeDcfMmo*{$Kgx#|Lg59%m1(+0I=g9gifxHrgJ7oz?Eu6O;oj6)36f^a8tD92SJ96Ld*l8 zL}0?kfR|tO!`ENecK-diiRLCDSidm!lTM!%m`XNcvdR?%!E9A4nQ5N}aZ$UnN}6#sBXxdU5=BUjJ1dF_uE?ZJLHwH;WS`zbqN3SL)?q&fmqw z7fQ<8VhsYXq$Pm#6V*htTE`Z**LRwl9~hDSOBVpZA~E1@PUj;#2KDpPnW7&T^+Ecf zp~)ahRrz6q1zZ?*7e2_iMJ<=heq!|N(Cnv7|7`riD7KP+Z)RX&7D7b_+){CU4P(tc z2ZcnK3!CvJg=sT-ooLU5l))WsODpYDML?7w zy`V>b(*vIA1tokKK?9uu z{@ZSJ;*-c>TSv)9fkR=r;6j5!_2h6y-h`C$6h1Rv(|R*rGhU-w^l)J?{+kQ{c!t9m zUaB8da2H=&4htueOpP_cC@BF%24OFFsfzXNpP~RP01g%j&T}kI5&$b1nH)1(0`T%b zB!2yPhL-zPSOFM}=&%1?o}rumnREYL;{S8YE%d%6Hwgd}6M%gS8@+Xj-lzOkFfd7P zk&rP7Vlm2*GYbek(w1itwtR-($izX*U|?h309JfYj9di{p;7m*3@xHpW>Oh)?pC;5 z8R~=_GG-O5&Q#DicHaQH;7V1?Z!wc>_H(nn=gL@mBfc_J#2`ggr&&zLz;=}^v+`LM z$0n*i?|l+sZ4HAX?lKUDl1)cC{E6P}S`kesyI)u{ZGi;UTuGfyd53V8>KaSV7j<>k z!#VHdj2BG8Mg?5B>m^@VstFMuA5*c0=;(&Jp4zHu%} zJ2EoJbvHpIL&yW34-U-M6It~@!8H)D#AG7nO0d#yNF--o)VH<_X$fkdQ9g3rxSgJi z5If*w?d5(#e3`!D4zq|B}9wiQ(|5OGLza8zNR zI_SGvNrkDVX6t~>%WSgbb(Ks}N@r)L9_Jwz?xDNz5R*3CIi{F#b`>|$mIibGYB!G9jdbynt&r#G#-l~jNC=WTPV#Q zFQDi7bXhPNbE6tc+%{N!w6C8<`>6dGmLJ{zzPl%%$Zze!FUtb#%bBb`qI{Ba0*s2y z_He9~+4pdFHId+*8btJUB~54vg+#btDC$thFQ6h25;@Qz8d{rtdA=}xIx^UYf)J0U zg=?*9s>yC?CDIM85>(A@Y)IRiSm+5_d(HPgZX7OF%!E(X^ zGvE6ZGa3ImnB)}KobC5%7!BEjkyGc{)FHjA?Pcl=+vPkdj(f!U+0~s~4$;|zeLriK zI$XT^gfw+eTj!P^LwBsU8wO9=Ih?toLbT^7V{FUVfQxb9u(|5_rI-5D&R5F|488-E zseJt0A`~-^W7QkQYkwN_OvtB{d{0Y$*g5l9i#R&(s}xH?=HPodb)y^?Vw)z^jzJGI zGjDt5h{!sZ-iGdE0oZ{FZ?{Z*us`*qKkV~kyXMa8$HGttvKTg}1p$kzCKrSIW3xk_ z=6H8)qLlN;a!8>+{7no(N`D_}VFPD_>O*R zP@|1OJ}p@k+g`A8Mv}y~+2??t8ud^w;UVut&ci+j=LYUb?y)fk8y_x@1I$PO)NCXuB z<%|~cEzy7d$;7E@bgSt0kRR(lpDH(xPaUEvs=l5bnE){(j-`(!#(8Ss3W_R(K3RWr z`S|TfOalSxfJ22NI_=2F^R}UluC;_?xXzcOo<@iStb69WO;s{;WXa4GRgzF;^N?TJ zP$~@0M%^nSh==7Z=~R5|`S82Ul2=>)X=bwl7)FT8EHKM4^l_cm1{k&6cdFI%&E1UX zeO9j&6{}L}?f0>n*=D;!(tCT)DWY{x%M_69wZ`bAAe+G_RaO6Wcco* z&L06whChWi*@n)y?CoCAI0Q@+8W<+iR+j3peN;IaRQGhEWFDpQ_Z=MLw-#}J z(Sd+{SiU0j?~?tPCVjQ^7_D9(*U>OEz-1~F@PLVec}dKv&U!cHYY^X*IVd3IiL{fN zx!;>F?fsWu0;V&mlMISV;;0|GWZu7yIG(?z)E-|yZpy~#^`@SFw}eOO8d{j^UYUiT zOv%xpmIrhgks90zIJbc*q7dH3GHp#Q4knMCbM}v|dAd`14-ZQ9{8`P%SRq40Uip%y zU-G`|meY_mPVXujp^9=|4SEB6`$;)YZ5k3%uN=N{ZnLm6b7-{R0A!gbITn+vgSZEX z`8l|7)_CT`n2w!4v*w+dGjE?)BtMwRN@kEiD7_LNJa+LDBaIl=&w44Tzjtw2XjBmp z<8N+qrD@yCJ2|BAA&#G|shLBaD`Gbu!dop_(J*w?maTK`JEpZrZKvLFaryqomlq4b zIqr^AkVwT^U02vL-s!H-Vq3msLQ_IYMYho>i1XFjCqB*o7L%8fo;LuhvFW~p zPsAIQ5ne2y$pnL`iCFW(&#`fA#$8RVvn*jg3qTWM9#W6Ic!)A#zkXcuAZeIYS@*>u zWr;O#8QuyrTYiq3NXc$8uQeBr{@EMBs%X;Y^%udlEYa5Rl7abe$$95@PL^*EU3@1K5@ zAszMVKFr@;{hF4?CXT(I#_3}d^H$bLVM^{gN;*C3>CuI!t&t6_er^nv`D*6eT62G#AJ(?RALzvQskvJT2vh=4)2$tbO93 zyn>A(GVDTM?Xy<6RgKZvWK^8|@aG;CQr#A~0f?+siGL{i!QZ?-R` zwaKRBZcFz#GHF)Oa;h2?$|rhY)vx$aOg}_Rb87XqxlC7G%DPOvGw(i|-x3E1FTWov8X!Iap~E&LfFul)nHG z%yYmH2Wf zPbCJ{qvRL6gI(@mTUD>AXY90S-Y5K);A@l)la%)AK&r&sy|}{>98xoTSb=aCJRoPy z*b7WWjlJs099(veDb-Q(V)M-hO!Fg=7mL`-)HeMFl@97X_a6K zQ_>DixR#>*tDx_OzqMU?RSmLW3g$8~;a`Yoe^?cdWr-+oqt|3lRD1owK_tnrv$-&e zK$`-aR8fbFjEuATi;fZzdwkCadl6c~J4JqW|M^iV@RRSNB&q2Qv!@_LV-(~?UTae5 z6ihgXWN6+FmV5o-FAcQdug@>-Ur2w6dQ#{p(f;BtnE2KDTj7@`djk4q`E9upy8GWB z!y5qC4FLVP!-GD~fr)Vo3lkIb7W#4LlujTqg=rU2hIvO{{vMk22Sra2Bna}6#SX4CV((MLexcFMo=|ZJ;1viVB6paB@<- zkFVYf(Ia?Wn5upFK}cUeQ=0_s$YT*!2Od;&Kk>j+Xku*YR}ZT~SZyn3WfsmxU&Ul< zMlY@Q8o8!b6ya)E1B6uZ5TzBe3p4MDV~OgwDBk(bR0cuV=-UcbbG$z%HDQ2XC8H{I zSRp0tDW?T0&#lB@QJzL6tp~nTTNytS*4;g!g_qgX_7TS~B1s}^xkWy)5xty;DsV!3 zts^<1GzB_cN)+PBp>!1XvE~!_``_%2##5}wkAx-4`i8^Y>4MT0(wJWCU{qFMDJ!9D9*R0786|P{tr`oT0 zP&2(Dpm&8&ZXYZ``;29YTqw<4E#6;zx#()&4g9_$BlnF~UVj%)WXlEbak}-X@7#)3Z@S>Qy3SNt9S^EinbJ(ZMIt@C&}R_oKzJ=)2?#?`j_4u8&b|loKr8q(Wu%=fiI> z3Y`OZo>rfywD+aOd$U%$`G)2BF51SN*dJXVYhK;}wX=n8T6gdAipY+! zjF{iQ(`n6%die3YTQd9nDU%Wi+f14D5QD640HAAWycj#pS{$3FZUM{@c-3x(nuR?ZM#o7mzUg@TLkscyiDF9(e?XPNT@WXD@dlT;0jpF~7tmR-+; zH~rav@@3XT(OrBIwbyoHQ9AMTzJfnl)sR_{=Y82ckDmRVR4?ZjJKqZroBhgbpJYl- zoS!5XaX2oSef_4E;PvxKR)=gTqT`rO5_NtGu*F{&k`KD48tz zC#{FF;rAn(!l&v@EK-*sHf}ba%kZ&}s2`f+To%*H@=xWsbhs>LN~!h+J;a_`SjYSG zcJ!t$xaaBqkRtyCOR;e87kt|=1qp2BZBLPi`8k~tqnxi(G6ffxGh;S!HQ}wLp_NQFPU3!#zmcIl8UQfE?MMuY(Z%wx851{9s7t7 zGnvowk%JlPX&ORt1?tHn8F=bRTw=H0V1I}zep3oAR6lslJl%UA&wh-;{Zh{TwR~9O zjNVy>=!as_(GZOp=alN5O3q>mGyFYW)6ld!ilkXaJ76DGi+aYG{a4OT?a1?`$7w1_ z>)$&B(HkxzA(&CRs`*+b(WTqmPVyl-xd+czcLF+$?a4XSGoIzDze;o}X4^vnlXQ+E zUE)=61eLXuIT?epNHW|OEt%o!8NHJt6fpD&hN~bP?dms}j(S!>&$H~(OGPl0&P@%m zui(s8HWVLnNrJWSiX+6U1-RYu4OuN;2O9!jgWWQ0ti=aPUsE46y%x+bf1D+jb`S3s zLf}=vxAzrAmaoFLryH@aTNpy|vUyE#EFXT-X>!Imi=p+p#xiv}-q~I4gSxa%=PrIp z;wP66#Xj?!j5^#bE$P@j67D_zYGbcqOf^;!-X$I^B*enjawPOZdF8(U__J?vJGTMv zygYp0zgh!az7ev!vlsu-iVg}$dnZoVw)@aap>blZ&@6+dpsGkpYEVa69NPk8p&=lMNDkrbM zeB0;)pUYYVAde^0o^pYEqBcterdK zwBumTnMk#lS_iop`Y7&g6PxqtsNfWSK}rtH8t%@pcG~@&2XuOIEAy z^!Lx~dcz9$tg9@#)}MP__v?s##xH*f%ayIVm9Ni~CfA}aa3lkrytwU*8>#SO9meJ! zrbQDh4qH*1_}(IO`|}Mzp}jl44KuK3%Ory#Shli+OM&T4u)&)##`lHv!&Hv$ zjWIUx63J0Ji`=o_woMw<-?davOv1@62`I1PbocPqc+Y~Rbbjw`?)=(2p$EA9^Tc-KYB61+J=Jb0)ver!?@M06=z#&8v_zf_aK zIDLt1THTEh9Q0M-y=|!bf%Hhf%g@c_`N4z2Zk_vOG)tD643row5lNn`K)M5>i+fu4 zVco&(@1L!-_^6V^(mw>Z6VTg+$@IKXSrJuwEmY+2ecs*RqYs2M6i!^$KvvY0{qibo ziCT8*!KH8`&WO)xIYx>mq#L(%_d)Wm?^~{5$J9bNRq^PV{3d{)hN%pNlc|o!#1VxoX9U>ClBuZK}jf$Jd|4i zRqgOSO!bsDtOhL5cb^i*5FJg>P8Jed*^5P|el>~tpcz~F`)X5>f|by+y;$8+k)=oP z1$QoMS^T@fmE@8r!s0z{+Z#adGO(Q-bXWox91P-FV`cTJ7%vHFLK)V)R2s3BaY9czX_6hevQg>G1$W1J6M*xE72e_aqdHisU4 zy8Gw}mzM;6`u|tKxb^!b8~XhkfRss4PKU(G?RhTaqj>GwPJ!{y2Z#TD#rBZt25=zs z9MNmDl^*6L_3*q#A{eVL=94|X266xWE3?t!pFL{XWr{(VL$PY}x9?w*b)&bRXI>Q_ zUuqcsL=ygtzMCTY(_=RR(RoyR1IPq!MLzBF*ZmYyfBaM%m_pB*k$CBx#Pl7ird{

!H|&0H2TYInJVlID zgBG!n;qba#KAKasHZZ6|$WG3%>3R$86;=)j0T;XX@U)oJxwj z!p}lCmX_3T7Fn7U09)Af*MqRg>NqP03H&PuGlrQ1 zRfcZ>LFQ2ppL)B$0ZAN6G3s^s(g$(4GJLaE<4!DNNFpjuTI3_qFw)#EI zIH&0~ZcSKZn#+}jMCAGuu!p|#kp^^!Lx;hr{c%=pm&!$)kpdyZW^?94-@K{)Anx{l z>Wog2v)d?*ZyoF4<^ym5tIxq5Ui$rBnY0KvL2uwhFW%1Gj{A4(=WPxoVHOjT_xfWk zzX{LmSS6XBo|J&tnP#lE!TF}@%?U^u8hG_S>B!KzEn?4wKCxA***#89=4H;Fl4N-J z4ZzIjOv|$EaIvm*yo9%}u!>z$)@VVTSX-WMeb)7!JRiMioc0x;z&6l8Jqn+k_B2&S zy5}x4`|!~oXKGJ}P3>)IQxA0}#kx1_{DLcjJU(fwe4^=^dUuSn^0V`M(@t((b4V`M z0==Dj%$pe_Y~Q+yO&(d-za?iJE>ySVQA=!omTsh^w6-d-%-d^)FumN>KX7M z%U99azI#{8XlS9?x50q&nlW?up2kv`?vyTLlQ}_N&u|o>?l^T`Y~VZkpChW$JXt$d zraelgKI1*`{0RG};6>%ayLUJ8J^R=VjFO&7#4U;iZQ}h1y(dN)Q?U%yOcy&QEQ5;S zEc^bfkM+wJ=369g&Z;YY4s^I%wh9#bu(Lt@Md^<1=T4p7m|E=QshK-i<%;2kNi+;W zJ&-&a&M^Et63NM)>9Mqjw31CZJitXZ&Zps8%*scr+Doessym=Qimj`bM=yBX=waVe zJXn>QN?{|JF^!m8Ev}ExFj)%=?k4Ca#yqU=&QER-BdEb{i7ZcQf%6H)I}y@rnH}xW z)2P*kP|`0ff+50tZq9&F(W!>q5@6&J>GZW_W;XpEtfy~SfIutN)x69^Q2zro?)|`|K3Wn@b|v!am{5HEc>?^tDFMn zNa05}04dAafJf_n*`V=u4Sw+_W4hJU=gQBsa#wi}prqV%_zC5nG5)*+FAGoSl;|5R zZb-=Kv!*em$qk@h0GlB{z3q_H*_A2}GplAmxp2dM1!$wE&Wv;D(Bmg3-5F*}@UG5w zBMD~`$!yiNNI)>QGN^(Lx=k(~lM|1%V8k}{B}JG=(Cl&3Q#QB}{i_wnPFDS$5rs+) zqgU;XySv>5hk68TnasWWZbF$i01|F(M^ZnY_&9E9xTB@NGzc77Wif1CWIoBOZJGp= z)3GgJy`2Mby#Zk5nfg3FOwge%Sg#4U3ow@aE&p!4nG!CRJQ-&C~|GE2lkau7>QF2-cUFC2DP8 z;q9%-Kb#{kbPKN_INGt|Tk0HM^VT^G6j*49)pMpM_%`LV+!;6YOBzX63&t}T8)a#i^Q!^I>+f3??&GmwHWFZny zKKA{9>D*JYkPqppb3e$Cp*^r6M7!+jWC1nvpuq5n0B2-c<$VY3&xoXn_8(y%^-PC& zXp*9e-Ui*?4MprhodSCGHthGX7jOUUsU3x3%TDC*-&$cKjlxh@AEAV`J9>wTmv-RCbF*k7wLO{G%p&TYK(#p z@0oh^wDhI1MGwLaqo1Z}eArkOY%R6iyEg>yZQw&($=w&AA?>FQH51c$V4GWkbH-`* zX_7Z^m`*bCz;JQ!X)B)CH}OZl8~D6WS0N72=aEHO^Q=9~?<-r(^s3Cy(=Y}i(-B2N zN`WEe&PPo#uG`o!u~O$4b*JZqQDf0Zo!n-5vKGsp9z84Dc7lDi0UGZ8NVe39u9q%^ zom1b#~{WX4u=FR&KPBDwGl$^Vk?16VTaO}+05M5ar!=32k4}xxI zF+< z4g8;XlGUWg1`FOT3Q#Aq3eJ91wqfuz?KTm3=~z5iGd^5UJ^{q+iyhXZJZIx zrVYZXVY7#O`YUkeC=?(T{#IyJ+jw@V*MBpd=_ME=p4JVMez$nlQpNY=UdK8k{c(RiS(#q19$tO;`Gd91433aet+lIpu3DO#z-=xh-$dTW z(b6C@z%t|14S+V5`&?L@8mPmI>S-Gs!iIYkzB(gX5ObTFIy7@?ea@c>3Zow>@Fm9Y z`H^=6nEfv2#=|d|JcW8LP;5@F=qBullzO-qW!IyROk4T*iNu|#TIf{hB;58`{~}m{ zb22`Lqxtof%B^K@ieC@^(o_rkRYq6(dRb+0hdABqx7blxgA)( z@I9hFy`qKZ1;v9fr;&Fb#HAPgG~>rlIWG5xUHHPp51M2&3%Bp}vti+83<${#rZ3su zCua*mY8BoxbgRCKGy=f3?;I|268LKz8I3INsT955AO>nV9p57{0cp#On+|fXa_gQm z&B=TKp^#-wrPG?hnZU}c|d$JDmNix(+n*kb9GZk(eQHOb~5J(S}oKTpl%<)jC@ipj)Nd+P8M z3CfCZ=p19+`!3UETx$ZO@AwduNtYpB5it0sdn?$T-aol!e@a>DRk(pJXVK?(&?&B9 zyA8|tnHjE`E)nm>Wpf(@--8_Jl?I1%uycLG0Z2C{TT2pNw}|-eyAAK$t$P>NN>eln zycg;98}GZFP!tvFPD_S7AlEOo);3Ekbq!Ej;k!%f+nru}9(6HxavCsvA5c3NJJ-`Q zb8+ydTU{=VKIbkTA#Ysa=vvB-`G~EV<{K*a#L8(Tsd9`4mMiXq8SYh&l3}7-SG_Ho zJ-a>cY$>Il)h6yxP4H0|90TS{l^f)fwf8Las*80TAS`2QCe)Ak%;SzlycO4j1voF$ zv`V#DL#i82AOGy@}l;v?mF!8H2$=+bZL^xDR^QW z+J;&CdiW%kZ=;J|#RQ{g*m_5gO{$5rhLT?K(Q7ZwiWli7eD8%&-0a|Cn$(`#(w*)b zrmi?^i?8?MSIxSy4WJDg@`L@3`^W$#T52(!TvNFnQ;70J0g;iY5VeUES8ii{FxBj? zzzskw@~URPlPZo%bWLONa-74h({L645#|ZF2|7+_%0p>n%fh_B$m93Cu<}tQ-JZkO zDYL_iAksK+Y|V;g6+y<%UF@z#I&X0rtwC>$@hQE&B5Ml`gK3M{I1>H{SAyozB75zo z%&&Z(Swu`hBuk(wLIpng2R2qb6jfgl*xf;sg1WjPZTMk zz90#AH8RMJ1d(m(ZY#SNwrn?$9cuV4+;4|d0rHHXz)NS9r==KKKzTRLH zEAfvbdTqg4o;rWmsc!vMu4BrY#dF=#(^&dJ3D^leB4*LvhMMSq(e{?XZ7l1%uguKM z%nUIz$IQ%Z$IQ&kc8oDIGgHhcbIg_*;+P?3$njcx|MytXw<)cdq&bg zb@%%`0>8kP)3d}TSfpP{fd@?lj7FePWm^`p(wqHmsvqLuvShT_Ebx)f7RwnTJLDZY zS|pTkGOc46!rxcp4`fYPZhn-isGDM6x16lBP{&2bnd*nRrQeHnis3O%-NSVymSI~Q zNbQalM?a;W-lMJpIdcT_7e}oxW$e+-7qPf1cN*+-BXs@L;A-)r?u>#uNPW^@P19@p z12BhD9LMLv`BOu85=k@VT6Am< zc^tluv{w!;KJavSwKCya0!}o42$(u*4n0zz2U>3;s$+P4??ISnVUriH;hu8po|>$! z&jj5w#ywgZ5W&r`O6Y>?Myk$eN9ND)#73a^*uM9fT(uNVX*w{P{_@<9vUSl%sh++& zG-ZH1G4R4&sM0YCp4YRNbaNxG3*@-q!>K!-s=6tw6L5ZD9LnsKV}4i^BI_Prs;~=j zOWxXcS+5QS={Gn+PSHK^K}}s~HKR>x^Pw(JcsvJVw+zJ7rRM@W%-!_wA7%Z-YKb*~8QdLNzHyhCczK4GQ3ij3i2 zXkA0P9fYc3f%TvLP-N76Lu zNm{Y$<-B`swf4+1w3|Z~E6u>bRxU{-rIt?7V@rd`nC=Ye%WtL8d92l`UJIp1yEH9n zu~-wKNd5=V<-rr(CU-ks6iCha&>sR=ME;dBKtEPqbh=nj9V)T);tZ0WmZ?3H4u zxvSU>91Ba=o%e|s+bYWlD5t^#aV&YI9u5}@1zKF<$rg-7s0WH82{E&zFv#zH5_i7# zfGXOlvF)+fD6^?y>Qc*|jq;2Q-(n+PB^hS7gu)44m>OZfrG;#cV9f4-=X_wn zzjR&NWnEQ;c*CIXwXzktKau*3x$CbC9r(W5FK>KtQ0S7 zo~|b7jS}z7($Ao*CU5aT|9kY6y*sZcWrYXP;Ztc8l`A8Y)*^yCeef)Mmk94hr6g8I zIV=gJXY+|*@-PXCb3snJU?J0RSySLtnep}>?W7d2dp%*h59I~}BX$84c=&PirRYY=p3z; z^|n8sIfRsQ19C@FfG+IHjvv*E>A0uRYT-wk9O!IkJoMdz(g<>8o=~^4Ntg{=Jl2;d ztpwFbof-7XGjd)tiI=psbGzgW@#mRjf039;Ub9d9P6fL713*uc^3B~8RWh}2gSC_m z__5abBRWE8&r1M=2|1v3jD}1?__!C96?HT3e?Tf(C zfM5{$Zs~^xrQ?&SnR&s7m_1q;$~0m#x?j_{gc-`_WT>F-i

tGPK4P;Vqd2Ck7>zDh{zEP?o!m$w`fw~zplGmrQR|pAlnlObi4cvScz!@96EqAPO zve(sHd9C0;dK{5lt}R_Urz`7oOih~o#8bNY*da;w1784HQ(oq|GYoeynYAk|UT0-O zIpl25AWVlzB?Q50nAX3Cg|U4MvNdxDCDI}HU!}_ctCsES7vHCBJNuLz}ln@ zWd&sFta4}ItCv>#p^D3)I-Vz-G29=W zosMXH5he;cHG5cUQGT<1xE7*Z1jxq0P-yz7oiDBH!oxE-E@e~!-E=1g@;Q$dg@#yj z`ZY&BY4kc`w+ZC>+c`ucU6F;nl?#{#$qIq2R-F~H+{mAx^@XD3JpIWx;2IrSm5qaM zy!2CHE&xa*-4dE?D2p=__N5F`dlX8?A_vXWGw|PTqarlWD8BC~xN=Wv;T#2BiA3yL zv1w4rKZ+V>?cT92l!_Vfg{>r5p_lq*HUOEjj`dL1w=ZSC^UEKXq-GS=89Z9q{3ZS_ z0QHPiEyHId5`MWHDbw`&I32U~yRLAD$CNLMJca_8TZauke*q_m?9ezey;7L1V1~Am zuls`M{_;wdhhAo-as|E=)@r1Z+9Oc_vLaSE_Ya5-Hlyj({xJ=`k+BmIr&v-Uj?-ai za>;&*aSg_f_FeXRsyqM-0}Ol@D%w?bJ9>HhnCDV>xSUN8sFCRh=e>wGME|8!4O1QkDx5{&O-8}gT!R}*l zC;%fGoj+$NTY6&R45KL!BgqE+4lwN(vmUUi2SZWq0mlqJlwdQl`r;c{RNWwJMqS8} zqCRB%z~l|G?R}4zgXf+noD!j$-Xt*1p6g7z@K=F0t_akJjjwz`t>i8AdP)vz1rGJB*z{Z5r@FyiB0sA0kdjii4761^JfIwpK+>|P6H z6eosB*-l;pU<|CoLS$BG4j|FF8*o@uQjuVk|Fw!1EDY3{He^d&499kf5xVw555Q_2&OA&=k z5|Mq6H$-F~LTq}>M;Y}q`$eKe68Bfd**HNmniA3!Sjvy*wR-(84Td+fJU?M$WS7!F z~@i+<1klx|05PicX_=s4X_fjRTfGEGvx}^iN7| zaX-E((u1NZ6Sx0~>XmEmpYZ-nJljX( zsz7#uj2LP~>*FbS5)_NAITPCdKp6hIHBBx>kc9i=2+PU_nj#l```=b5e^tOPi_0lE}Yc;+?}7fDAI$p0br}Cuf$N4pMFJ=IGy%oRC%tb5u;0OLBcK za=`NW+avoL6XAbBaH)4p^1_zSTdJNbm+Gh)I7{K}d1=o7f&@we^}sF!WEHfZZZFtB zdXmN_u`c2Q|FucVq3rYmbx!P{D9-~p3CBr^&8K_D9kBkt>Gsb?LN?PyHtKM~{$W3f zE|$jXcj9)ve?^LNk~`DHB7~W%TfP#mb)kP%A~xc0NNpX+YU{Ps@&~}&qiRW- zQtsIQHCP~R@o$40K=Vrh4v{`Sql(2~*7PMtcg*ah9AiFt!(X9bp4@)`mA!*>VdXIr z)UA6AqTg-sDDui-^@xkrlX>Kw_q5?ma?|+%&kCpQ>j?ezaV7sLivJ2hQRH} zYviK1?XXZ6ixk923y#QN5V;zRw*U9D^N1#U=pI*T&@nfvPWmL6=U)tfwkq}rn^yI` z`j7}i27eF~xFZwL{5jg~XuD|<8t4Q9vNha8eu6n6h(Z{cr{{EZ*ma0OFy^UZY z%`gOI#(K--OfU%n2xHu!zavG~8sYb#Wq(8@FnuRi&3A$hk@!L%{^xH70HF6zK)n?< zo9CI+=%L;^8bNb$VCcfN5G4IqB+;G?q$$KTjnrVe^zXn5MxG^&h+m39|AKru&-mxx+wteW z{)R~w6u$GcJ+90A{x1fA_CPPIo_&j9IiZD6)t1+Yr>s(CQ~oz3#MRRz{c2!6Yp#5O zTd-AM$_UOuWBhLjp^eZ<;OBFYb)C9MyU{9nO!~9m>E8?hZ6kK3sZ&==-T_HG<6vTm zZnF*cuW$CF280e5?BsClkHC-f0YC!mB8BX>L5F#snuiPm{BMsGSSY5R^bN-D zi{$?YnLN4(WYR|qGLZjp4){kn7-h=m|Lv#x2mAlH`j_!17203Uo`2bR{*|@+mrDL4 zhX?f|iHB50MA?MZ**GY;@jrSI)DOK#z$vx!hX8&8%K;+)m1XP3XQXs4a3{0*UE-|(8E8)mJ(N4=f-dNRjW zDk8;kDbAg}M}s0a?-B2RHOR6gCo)oPydWkt=&n}aB!5po%QCjZi|n4!>))+uU|z+j zE120wN3nQKmQMs4+$_i)ws*I_8wQO^Cf_!c1 zFC*hw_&F}Y$Beyg&sWkRSgP@_vZ!?q*MBUMm~ZXAi%bxVX3QTzr|^7C2)wSqv-w9s zY#^NsF8^TcSeuA=ws1&ap!QDLKy0J%v`bu6@Y&%dBAGc^Ne-hWOcVY6@+-B@J&t(7eHptf%-EPNV(KZ-r%d zcNiYNX0q8>5ro~4cb2R8nr!^yej;WlMvx>Jt=0kcmA^6FY>51H=U4kfKlzU{htXb< zJPwfkGUBO2$q=mD22*Tr4;%&t(1)6Gg7uv%@O)=*R11{>z)AqUig#L`q4AvVmNYRn z!S%E47ufGOAsPR$he{%xZIjcAN#a_IRmXOT_|Ecc-=Bj3cEJYfvVb##fzsT&^KNy5#Lc?o?SR*G6Js7Dc7+o>3U>98 z+=2A&rSH=(G!X{G^$%ulk#$&4thJkGTp5;%3Zy12?zS#NGkje>=5EGOw?G5+C!W5hI`umg*HiK~Kk4EKV{5S1Rj#(_;RE@RA_Lp~9TM2BVl_i}F0#p7pO z7;xv|FO(C8bu*2e5O9p{1*DZXhc8IS%6~aG$NL)q85La1)2lJ29=f|B)M*Tzj{XC9)t+qktQhei8DNWC1yJ6c zZBUPyd$YGH7MuWyk~~2ahlgPsmVTL}Z`+KmV}2RhvfeDNC@0w8<%dYU5mvoiQ1r|S z?apr&7%7JYLwAkQx+O!4@3GI<)-CNYmjhi6f`Kx;KfoG)FngM_8&cEgbkV59 z!Zg0=UV@>Jgy2fUI(3=nEkCqj8_o~|Q7l;2-aAnxPecn^8J0HF5z&sR@bkLXbVQb~ zx6?7Ke(OT}b$hUHMFNpl)@HMR*&VG@fEzxcc=xWg(Q#tM%pfva2JCmd>C#-W1-3W| zT#{I%c#XcFDLo0vfz~Znl>#Q*_3U&7qwoOdmTU9S_r9n=q>o`$jLmsFU81^g#3j$% zW);kC^NjiEg$M?tx)Y8~_j)89!fX3xG~Y{?(l18GkJ9nqp+);Q5YO0_dNAD`fGOER zN-p)9%+qs051#!EzPQvS&f#Zeg%fY3nR6HJ@oRl{%g3X*cY=ID((;lQ))y%EAWuhK z@v?2_{qxJlCwFTZ6U(2jc?W!}kAFV$sSpZZLUS}FG!vf&Nusat*!pL5nlBy zXsVUTqoMIxc&8~i4$(#a{`xOoASB|Gxi4ZMBk>!l+|v!HCgm;)oge9>{aD89M{L zwVPeYVi6gaK3K33^{lzm7y0MX`70oVFyAV9Ct~f!a;qV>HYo)8Ixjcm z!Ak3##nY|<3aId(#qDs)7?kUJ6i?rr^L3B!j?MddS>tvrh%FjH~ z%3^lFd+IRK8+NI*6L-RLPZJs(!Ncz#CD#g~J{svA?d@%{S8~Wj_e7w;UAlMj4RuGx zolKLcVD14l zi@s~jIl-1?wkD8Ja4$JD$#Khxyz}OYYj^c0JguLA`nZH*zbFpZppk7 z7e|brIA)F#Vb_f*I*CJqLVtnxZZfT%ny`2aSZ4sYDcYu(j(pXq+tV z;AFjDA?Aj#uX-9sH~Kg1=b_R3_AimL@%(e3LhE)c(dpdsJrrol5v(em5?~zLBlU@P z6#f9JrT8yKiB1p>@Y?XKG zZ6o5vj64Ko1mo#--c~$Ry!9aT9X33-)2!{3E_MY%G)P1BUik`ex#kB8lHd3oWb;_9 z{sB0)yPkP2qAkTOMmQf_j^i>0ZA+Zjw>cd6y19)#IT-LI)Mnu2P8!Gs1_QZYSciS> z-tnvm2Hgqe)7Qeb1T2y_6YoFL{IPca0PNm|O_mbU-O#89Zon_(n%n%MqEDvJQoJW& zcyxR+BE@vKcZ}OjO<@n3%x(muZhZG0?|D5P+0HV@8k}#CNd<=l4tQqnF-CqWZ}g9U zN+>y9T{sIntw=Ix3v}7=X`*@L<=(pNainTS-|%~d^nP@No`6Ob-KX&&7b-Jw&b>2h zoNtWJyaTL+CxHF(|9O9EexY+2Zt+UfBeTytJ7vn_x3hbgaD@HV9L&ykmT<&y2{wNH zTjVPl$3*w|jlsSbN)t^xwSn&bqjpA#G>WbtAmq;K6l|70Pov-sJ{= zzDaw8-hH4S3`{PL%X{(r&yJyLZ!g~_CYVQQO$G<9Ar=_OtH6F0YTy?X&G#ZW#w6fZ z!KQV}S!9($KC-tV_l&Tz4eTww?L9q=$Gj7uNqcBeHfeJT-@Z>&wEg};U|U-vWM75L z+3eGxBb^VBnw=OIm!l5v(90_PyKr>U8qJ2+s!vq2>BQ?Oxfjrzo!Dc-x4q}*MzvdS z6UKPrSLPdU0j5}sx(|Dcv;2uh^Bj^$zeoh^O^F0umirCI^fmAEhy^O3!=78a*9KBv z=UN`tRUpClZiwW4`^d-{n49t2MB_Yx_6X?%=8JOk4aT1RwKjVS{o19rsSQ*6jX)Bi z`HAuCp16$JAum4rHk#mAv$uV(Wlw5}&9c?>%lIb;`I%Yx$>Vnsm4!2=$g5Q^{`N#K zzV?(Yzv!Llggw7(7YK_U4#z#$w(s&a)Are~xjKlf?~sPhJ%>3sIc63OV;+QTgV*N~ zTX^<)iUK=NtFeRMn)&eo@5jXj|isT1Vg#xHM{0T&q)-bt0^T$%faVBzP3RceIh44J160Y ztAW`qq==2Jntb~vzP1hX2>X5e=v|AwmGawLcN7}unPKN1yX%L+fa~${Duuf*0 z(Q78V`A6VhUNN<8apcvu2_toT(S)}L!h~mnd%g!Mx*xX7vu=!Uj1F_nI#1X)4>(d! z|NNMXjL-c8P({xbpqLoF3LvnPW^iN}z3n+`ATsy=0H~t)MorXLT*HKCjrI*2TpN!3 zc5RzIjAk@Ge*o&zJLC)0Lv~K*&B=}gL#;jWZ5#G&11DNVfz=c@cnC;6=Y3K3f0b=r zFEz;tQ~KwhN^JxZg+uLdUH3(7I3hZ)+NT&5X>E7~)@+#kRdK^U5+R?9QTtWTPWxM% z-bcX)`MZh#F1@+G{=4M8{{KM#c^g>2#r5Bae;2pE)c$K5`}ThTOMxGKR!X#w;qSj) z4*v7E6kuqm9}-j651RuOlaD@Y;on`>zeJ`VUDp0hlv2#?$L151%)`FNzB|au8;bG3 zLDiyKE#t?}7ZvZJqVG)LwvJQ;OTNng%GBaTmmk5~U5J<+uWc2q7E%vPz6S5+C z{CKP~)>eXKHy900Bh|r3@w5V7R8T)sWa*@s@X5M{XM0$=pJ}&fGq;$D;#{Jmm;BQg zG~#VXnVE@VR>N!X8FnT$Id>{!$O97a(HK8nM^TFTq2l%xQeRVw_dl5_6$K2hX_%S_ zh&m~)JtEClM=TKV@E?mK;J{%Z$0zJ;H=#;xKXOWC%-f414OrsnBl1j`Zk}QW4y>#u zJF_;-onUlW8zq|1{^tGA2SJ8^*gK+=%|);k*3T6#4bdTS&woZ+|44wZ$jOmXu5djz z9VsrX&z0@xZ)0msuE+KMvjQ{oDXbV^ed#OKLtn30MF32dxwrt z>JTG+6q^Q;IpdI252B!iRToKL$zj03*$3)`@rawLn;yb-S*u51Bl$0&1IwZp?F zf)Gyzrm=#%yd*n zJz)1M6OrROY#5$Xi|^D(X8_cJBclF(dx4;(ZiP&l>N6w%)n1)1*)sE`RdzxS7VqGo zwl0Vq8TQ0&EKtVunz3=0E`pK?L#c4Bzw4m85&zO=*C1p_3_X^p=Z(|fiOl2(^;>Ji zQ^**_juIW!+%oRO**wxNQ7^u2^QV`Z&zl4zci7R+V7D@^X~(|31YI%P!BGK_o{5;) zn$&z~+cQt%!`FPCo{8auI+?zzOeCee9r|Bk&yPRNoR97LS=6pJO>(}%ZGYa-5%VE* z(pJi3bUyGAAZxV>*1ee3I}o$k^$~j1&XR+l@l!&uZP6(noc6=^B|DTgA@YaXpHs?8 zXIxt5AD)Z>0yRWwi*C8}ITbO~oeV=Xu@hdG%Y|qK26wkZnscT;%6$;hrGaP;l`b?T z$pdcGXICpA&hyPZ46kv~ByC@IF?W;W9=h3*o_WWI3C^FxSLud@bP-%Vh`KbyDayT2 zini+pN7f*Wsw1*8*dMlRwyQlH+Ifi_QjRSOw8QSrQI|5#O}8g3^A>CUOS<=2 zj-y+K%_u0ts5#LJ#1%#=RfVK^_i)E6;q2zWx1vEb`a@W6lP#joC*|`d0xhm`v%xyE z;k2SBtGTztjM%98{^nIgyfxop`g7Mon;NbC8Th4~5|6atP;Qgrz) z$CK|L-q(9ci0Mj~i@Y{6y(Ie}5FdJ8PFvk#HY=!vAy&Uy_HbIsF+4JaAK0Fy?a1^& zFOmJ4zD%PRzp7PGN@~amIlO?8oU&omxsKt9lMA7NCe4M)p_9 z|12+k;Z}DtNClvk0cVO z|M-{wb+dcKLkHL!NSs|3IJ~>l+ae)q5D75-19?*$><*6&T@f>2KbD z_ygDjol)OzPjaLTYf>1Y=EXN%32%KkBrEeNy`wFod}uFaLVo$fQk1`AQm`racm*`A zA9S}h_+DdcXx#NZ>42my&O>%yr%4LLyR;?~I zci15F-$WF{;_USG{BJDhXhCgh?~j?VI0X4s18l7fU_9n+?!=cW1=#qIcuoFm|F~Xt5z`7fun%9qVJ3y&T~V++nFAlJRrBS1@x< z8yrNChXKc@uxo5R7weKHf2dH~yi9&UKc|%VJq2ca^f8vJyBEY#W*Yh!_4h>}@i;bW#T3O0y9dy?xx__HI> zqbb7{p~qB<;kN|)ny)q~Aqa(Qcqd%M-vfw6H{bdS%+|eT?LR0%T*Q+f><@Da_Zv|w zKBrYgu2y~#ue#WGh(!ke0Vrp-!JnM|wxzw{OI6TlCJAG%7AaJ|#U-;nU%UE~C#$OZDM(IKEgPv`82e38i&L&E^5y8I@qva-cH zge#DfG7g>nrMpJJlY>2dYm{W;7oFXLv=#I?6oeQ+zbwKl0hKZ5s$b9JfgOj}Z`7&u z3%?2G`t%)>j+SzjDd(Psjr1@PIdtM4?zE}dCq8mbgx@0c!e}NsH%X&40r7vPUh^a@={UYoyyu$o`)<)x_$W?1;__=9WHLkO0oI?XsL{wUGh$D7y zCSh}*^hM%H-6n6<9SFD+v?{a!52<9ld={3GQ(rKqXtihH;}E*f;+R^4z!`TFs&U!Q z-jiJx2Mzh(pE5}GMD~rgl^bkXO*rVcA+(^Qj5u&zbI=!-EA8zQCN#zv&1zZrJ0;mh zJS@wyc7XIFXhBD`v{3H!;Q*}Kemd)cPS+EsPa4|6BQZZazj#|wmK1CF2Gv$|R+`~i z!i%(aij%-v)5Ud65LMQryc!vwlo< zfs=Y|pcGI-%DEzqDgo8AE)K!B2M#N6$TKCv?cMR=bp(yx3#_er9o5Tz$l~E1)^zg8 z`Z6+vH92{i$#0uis^5AeTWL1&t%7uN96IUpAXwGQo4nnux%`%X1ZpxS$IY2F<)Jzq z-YB6I9|_+_1O$0;n7RvO&%v&z)U+{NpPI!KOv8buW|a8+?dd803dTCPTO}qY9a)zc z7U$EqHGHjh<~YQVGkW9C*P^ zvaFdhvC6xO(w_kBj!@5oHr9==1HbGW5Q*`KU~9oKcmPLHy3!2}TK4=W8qVPiBzBlx zZq9U?1IMZ6Z7TCD&_`|Pztd>7_3I}o zoLsK`Bpg}Puu}YYe%SI(55_7))ZtsBL?>EYSQhL+buJc#VE>A2n!)d)P_fMf4nwjU zaH_YR#MUsVEG<7dGs6${igYQzC&hWonM%$OCkOOJXL2&R^?S`S6Zf%1BRbnF?0v!h zN(o@Pzhlis>A^uY76~Pt6lKHqHkVh)HSH+SA>J1m~v3)#8X|~z0kBS zr$x1^t|A2(p)t5_f}nxdj6s3Wvo6PP%A74wN1gSo%}0dRdYCbg<5=jEv&kRNiba?;mv?txvBIMoO4gLKWC*gU7Y^oMuj{I;=JclS3WP$@$-oscURjVz5x*cAbzfpXs%XwTJx``RoJjlt)^E_(-T#J1e) z9D77qSo_RELC)ku{eGhShMx5k6sD*L^3Kh@4Y#+6Jr(a~cX%WgAc_s?QOGyXO6NCKjH(I-{Pi zT>?@kEBd5zdoFtaI(KK@uQ>crvlN={gQa9=F zjMW4MKUb8~TA(xxuJtE%W6o+&DCgw1T0t5i>BBhuVTNCi^wHXpt3il5RH@q?6(SD$ za`W9R@S>PBc7;sF_}EK=z#tAh`EA}%_;$z+sAunp>Odm76SkoaGH$=~NAhDdKJ>4R zwT(m4;g!B3yI)ny=%%kv0FtxmY~S}1C``KU=83AB2Z7Z91iVt0w32G&YYM* zSrVh`Rgb~C+RH$K3dXgMJ_1(fn;KI&z9hj+lb zvd@^zqEKqrXiTzKtUi~h6-zjcOMd;=K4Xz62M)l(E2~#Yg;@WTinhrRp&?Kil4>aE zOS2=J>PB?R5HUCErpmz`k%1<8P*E*WqXev2@{}QFTTE3V5IjsM2|xg;23QU$X&G5t z)C7&+^gN&yjdW!sK*%!Lgh{Fuj&{Y#(I~uMuZk2xzc&Cz^rZw{v7*WnJQ0~we zp?X}Q95v|L88{Xhe|#>2rbIutcr3%c+AGT)DC=s)Ka{BJs_6U@oh(zw=EENoe?P@R zg%q=mft87j`yvL!Fe&0ZL}s9=H2+-NFcf8A{Zqmkk~i&V8C;r6dD&(etfTeivbatBTCLf7%8i!Day zuyU~Gr#9%%>srcA;1jPR>R5HyM{tbVyuQ&)cM?Ug+MLI!2qx7aR6vIbJiQClkF+|bE@2tIG0g0kOS5nN_7@ONy7lHp3PzQa`g84@Sxi;%j;a^T?^ZKm`ZyZWQwp#=wusSn#@j#I^|{n2Weyc5y9?h#U7Icr$J|HIQ$IP1&KG8m+aEr9c5nU)mbu1CH7_ST; zaDJLInMM1gKAlzmETN1HzbHv2{vFRH;45{SHJQ5Tw^}jVptQatN;T`krowaTQsxeA z7>v~i1D;$OaT+%Io)yqh}Dnsk7LXUQcQy;Fw zEi0yA@MlyEkJkxQ!`aby2kan7fp4zN!*swj0eKckFvWuFkez{Rc!2!Rs0okWYer|R z*7_{F+V4Bdqz!I8z$2syyXG_R=&SZ5Uo);E8TbIJ4(9TvM^=k}Z@lMt2a^Pez0p6t=XTfmM{p zCN+KvuNW2*N_>rJ|E#`s1Zn_bN5J_TDZ}TSS>Cj-MjZj$4=;26&aB%`qzHy4I4zgT zT105wZ^`3_nyX7{44$(U@?Lw!jlMq@2P+5i>$fy;7J}$uaqxy)JoF9z0OYwU_`tQb zawj1yRhr=iTyHt>S%lCu28@L+j7f`h^S&C9$^JM@Zvcx^CD=ML!PM*DQCf^ys$mjL zQr;b9#$_^@!U@_a9#%VQ{Fd#Cm{X_SuDG{@{$!OIZEQKI?rE;2yawMmT1SfP9Z4S# zmS?WA0S?fROv8!^an#&dWY%ehNYCFQIJRJBu=LiKW!D_M#!8cXmPqfUQwUSDPX!uC z=rLJ%_HmG6FCK?SG?`6Rbl4%6A(F{hMs-A70{x_bR%v5F!EK1GD-W4x4Hzfvzh!t_ z``E(NSH~QW^x@&aYjgIeb#B?N1p4i-U;E23*|VzNX=sn~>x3^Wyhyx7%#RMSeubL3 zkBbyf4NA+Ccd9i1qEpAt$(7RAVcpaErhNw=<6$+tAyP~I-cUfNknxRTLdI=V1e%UM zC*@fgwWcD~AK3^4FbG={Z;NHCx#B3?zCcR4=sl3@Us09O;0~+^Cg(Rlgv@5P+b7i&zXLiu$Ci{ zy)fFHh3btjrXHZSrc2E{Ce95x%;-qeAEJkb8e~#9t2)X8F+7a=G0Td9z16=cYYcrV zR9Z9Y;))^Yu&hVHN#<_9$S_wUAY3y|7N&Z5$GmlRehUpl**ZuVGUZ!_kzyA|@{<26~;+*~zcr8YO zFL(8ppK^J_!+oP5G&1;cJFdJl6wHV0Tnfv(V)(`?ohS*7IHGv(oJN(sPIzeb&Kz)( z2tzXgldE_6Jto=Y_T3MddSAkKG8Q4HZ}Gco-RV##|WIcu6Gdn#g*&B$^>s{R7q_opmke`KI z4GS#^_e}PguO4-%wOQ?!t zo4ILABc0W;V*L}<5Om0)UMx;u%!NwDBEM}fW|WE)`f$UM&Q;4n?7vPD9%{LtTi+!{ z>*jt<+B&T&;)AbsBqkjKxgFl#5Xk(PXs|b_B9xS_qSl_?R2fhpVlhC4R71I^+f_v6{z_iHPMuQkdBE z`jb&c`l;+YeXH+qe3kUMPm*-KWmr8EsJrYGVcnC?tkdDuSUx@Fz>861zbZmugW>m0 z0O%7AU=d2IE~O)rVliJKS;-?G48d-+?Uas$_NZUv#J% zEpwk9VnxN5QqwsxtJM_>bn954%hFK3w+$YB3)=PH931vmUU>=K@7Wj$bY@e4p+GY+ z&8icSLIopRMEyx!+`|M_jIyky;`P7@&sJs*F%D{yeFNX#cV$5jCh|3S zH&}uKwTA~MePJAmMS&Bg!qz)DaCLEVRou_{4=a4_^PL)@5!3|kSd~F~7femNAzlU1 zwyAbG=ov|ShEPyhYpmcaUMFy4nRTsk5=bX5=$R7OW0MOyX$5pm3}2x?IZRGkDV1>2pY(UFFyKmh=X{@9;AFO0)Pu_ywMz z(T3$UN5|F$jRMqEp+LWnF&7|LFo7DZ9V1wyh!s@EFR-xc7HvwU`CB3B< zMJ!f!fhlUd8(@$HMHyldi>fA?gDG4!;wDsCcCL3Vvs5v}Y=A3AiK_r@J0iFWqVzb_ zYZ^;2exJl}N=h!b7Nvn`XbbKV$DjlB$B7RLo!lZ|QAK;)q8<#2$ZjEHio{o?J|Dl# zR%+X=zQ*c!duwz=#WC%Kpa zWgSLZ31zu@`$t)THM*9}Fyok$*KcDVYCDE?Eud~}Bx;Hg05BQKMvd$en$~`Vt^{G` zzh)_^7OmyfDM9cx{-aWbQ<+Uw<`vOKnBgca20pMh)qkV^0D1_HnCkLC+T-P!Q4FJG zU#vv}i`;OLsvX1}5a*dyxLyric>F0G#kSz2z$+D*jNGb{3(s(zSSu4=KbP+H8=*}s z-E$Y)1w(lA15AT>Q6WnCJh=B1k%FD^G6mCY#S>sbQ364bZ_E`7V7RZ}Y}u!3jK;VG z!zI%M9UQWN{{VP{AvP&(Q+eV&RbhWw_rxo=o4Rk*3zg9I3Ke&M-xGGjf>aqL(@vRE z5zFpIFleQyH>`>Nyw0 z#Y%wVw{oyBm~TARBeos2<|Gs$R0mZHtjC5Twt5*^%w@`eU_uSeTX4KYDn^_rzCP0( zwmLcf)NYr5;!#8VxA_WvRsGEV@U^@{qK+>9Bea>!dUY&bSyI}BGcLw; zEMi!Xq|B!T0$D85v$-DzL|%7B+b6k@aHF8MPOnjMu=v=KM~xI&_C=`u^@ ziA7EMesw8eFsp@w{$M%}Vr5f;wDLTD@LamSe3)dSR4fr%9uD>f^-?oOtmP- zm>5+?DBCn`0L3A}g8B4H4l+-bu%Sed1DU#uju zN45npP?u*tMT!FW_?T({7-4`D8yV-#MpuKpo0M1NKOv^#h4lXbsaR_nm{*BsF>0>J zkQ}j}(pzY?g}Ux!1NNDea4O(l98620D{!{8L|tIS1EUZCSy!u;p>I354iY79-0zsu zAO(iJV&!*|aH0NUy#P{@=t{rdEi9pd3bIuj%y)>QSUlt0PS{1btAI41RRGnc)Yz-G zFLeN?W%wp4zVe&Gyf;-SX;Zu4w>XZ5HYX-e^DkNxL>7<-8EO!e_7)MG5O+(Tq;5*R zI)Q0x%@13Y1klg9R3^$elv=NXBau%I;OZ$;X@+81V$S*8r^H%|)UVVi?eh>?Y;ugU zg=6Et+85M)07El*fdczN&_>3dJ77`dqgux&(xnJY%Cfw|)e&kk{{U(jlpX-yWym(w zgml`+3Uq zq9_}oq07X=LvVTiHwB48kwia>lmIy@_V;!hQ7zTGYZ0U763mAx_gzXwH(R6W5n8R~cMBUR#SqmCNnL! zn}3M1qejJb-XYPJZEGip-gKroh^DfKiZD6(TCXR2nLtv^>QpIAS;eqE1=SAVa(p3k zL1}#MGcrnVBKa}k$Tb@-`O7Vt18s8|*P&|j;xMR3Wk)G6@fpem5{D~KiJz9uX}$?l zt%0==TLo_UBjMdwGa$tS*}w0&RLQjE@dp%DY_3)a6y$pMIX)WMVh9H!RYf!eBQzEH z8eqX=)X8dx?Vr4=_8{stRgCWA>v^0;;M+0Y8<&Y@}=)1we$q3QV zQD5c?f?=kJ)rPzZUoSG3Fi}RLSi^+p9BNU)33`?g5KFAf@MCWUcEnd`9)FpE6b~C^ z5;$ZGRJhZ0;}X!IY^!Jr`$c0p9mc0%yyWxul(UluGSG{dV{vb0iGp5R^u$a#(j34I zyy!$9;x^XVwpmKWOFjy}-=E1~XA|s}n#@MniGwCxODTc^vAKL`cNH7i@fSFVhDv7} zmv?hIIhYyTUh5x)2(jjf(HO555Zb~%C(+*Js(a zh1XSyX_XlhIEjUt+yx@VQmX#|5}_jWF8bFb69g%Ow!u|kVO##xa!{07>UkAg5|L7_ zP|U(CqE)cgzGVTJ-W*KVTfw#MWze$ROCl*Sj}fAkgCj#IATL!bCI<5`DK)AF(#poD z4mJy@av={$85LN!^H^{XpzYRhv7eVBVwDOKyI3JS53&Hr7oMz)~njv1RFYOO|&wxi^W5 zm$}%~UpE%zP3#gCof*26g3E-hKv74yneiUYlhj7AdwGFeU(7RSLC08( zy>k^XQ?4d$D23X$re>~6T5g~Mnq4mvtE+m{4Xn22?jSZ_0^(7InqW}-!uK5^6PMZw zEH;;Tej->_o5ZjWCS1WL=(Je6iCFCNg^j49oc55A8enL;^X6i6LRieftRD)mqynrD zLT-Rxd;a2h3C(O^x@2jursJ5oXOX@p5S`9$W(r-jRinWbP=-rm!wKbVQNlvV@Ebxp zzE%uFZeeP2e4-OgQ(1<}UJ6vu82FADVgQo5MOc^|ml^-`*TCSg`Ew8l^#&nt&)Jslxzly>rY-AR5-a zKp3p_Bb|DPUM&^IDq#S^=?zhQ(t=*h2m^B{^r!ZvE!I_5De`VV#1OT9N%BF`dn8k}ZPRN1-RRsh>H=yI@CBMQTdz5s= z=N~_mytWNKdW2#NH161=Hm-(S z)H5QtOIPX)AlBspWzmM)5aqZmmL3dD9Biyq7;<$6O=FmDnZ>%87P7O7;##_cCd_<7 z(&3C=pwhXO85Y^pwGnP{#3-6e%5fVFuR(|0VaXuf*BFjiHaVNg(PF2UojpLaNSt#l zY3#&=x2kZ#VwTFDejx`_WbAn2D$NlslqHQ8z@9NG3WlnVq9`4}IJsa*U8uE&hkGsH z^BL!RA_H&;Rj&$}b&#kgLp*6L&!J;S_U98y24%pk6!kAKuPsX(sv{xJWvvCl`~FZA z;g1gz!lF4kd4!-VVCk8^42M!NUITa+{hcFUg5Io+%cd~4cr7y&iR4QFby)t zB9s9LJ{9?2o(keBk24k0=36C@2M&x?clyhVZ7BB!iu7OFIAWv`Je!K?dGVM=$UTnX z?km(cP9>}k=5^c^jloUgoW=wcRkxmCAa*(KTxCICh+nu2_v!+ytIm0a&`Zc>h=!_N z7;;Cc8mbsvF4n-QMch^38;wXKUZw+ybTmS#hTsUW!*=+HWT#2Yy>~-A;&42jJArK| zb-7PJb>>%8c?E~kB`$Dk&S6CgwZA~~EKmtvY=)Wo#+D8s_J9Q~^?X9P%PVozJiN@y z2CgXF%0xuq=c!hKHy<+kxF1*Lo!d;hVCF&0q79WQm#L()Xt}3wTbPsexnwU#h^q}= zCpC2jl!P*I7-*KRDpn9jk~eSw_6d--CRU-tLB$y65`!l0Hn3&scV+j!B;c^hAQp>PC`E(mu8i>|%Fp-K|`1-#oCRGZ4B zF)&a^a95UFlv-UuMb_G4yhmwS)672csKi>;4MsxWf0v_0X@@c27-az4f;hI!dWDgY z-D(k9$p$b)7k5>}>O4m~d`%((A@L9~tG#H6WUT7<8?AFNlvX*YX~uer?XObDK}EVC zwu0EN0`~z=qyp`?)D%TC-xC7&UEXCvoN)-&Y4a|RX?2oS4Yp;Cfdh9P4KEV#!-Z;s z019Qlx^64RCBsf*wKgHdSRjWZ%%^I+*VcPX5tQC)IBOTAGjKprH)P9EE``pF7d1G> z1BejX>dJ$Vhd7p4P~r}mWnbuKS_dYWBIIcBz;RvwKcf7n-~)6K8RiBF?V<34_tE)5O=t&8zm1k^D-37zO}-NntAcZ@`w_0OPo#;{+XwCq>5ZT*}QITm|L>mQhb8h}R7) z6Wg|Nsb!7!{%qGE;#&fT-Zc@HNHr4{%Ea+4!0I}M0cO)B)?%|Ln7#caF3q6+@oJ;tt1td zUmv^_-9*e_+ZR}e)h!1h@X^SgD zs_LHN{7*h(wX0N0<;*j2TiRYDOE=Oo^ohnC%F9de!rT|iHf;(%vCMXQjfHfT0HBvk zi}Nb;%yMX4WsJC4iEYtnX1IcaHPm7(Up2V>F0PX>LWO6PgVB!Rrg$66@hMi;xq>!> zhlrC@=32qbqyp9hKUmh8PBJj_}k9%~=OaUn|knrp;YTS`)Rv6%f~Na_!DyHvk*V&;%yimJ1d8 zj&D{2v@Y%Exj;K~cM8R6S91jXJSIf28IsX%gZjLJGWg1eo=Ea#b8*W6XCMD14@ zMhF_WmKnDEF^Nr^UUvl@tq>zgl2YNQ z)+o(Pu%fdZdGkH}V~~{TbqZn46nKD0vkA!!2y^OTs8e==6cu+6eTlrwB_RILI=BLk z_k4Pn(4)JHhgTwVHApK1GXg^#v1C>SRJdqonO5k43#Hf0si0>aLhRkKv4|Awsc?a# zm>O}2(Eznti$z+OQda}O%20=vY%LCZdLo!oLxLt8<0pal* z8+(~j3s{1xQL7q-n*)$EaWupBj*8!yEMnJKfeaOJ2&JX1)Y&MnBrTAxvx!R^a10P> zkM$D;68BJ|1G>1X)(dvub0sg`?p?k6fpjL0U}Kn+3-J*GEUT>k&}=B%f#wl3A7A_s zx>`mrOZ2zH<`W8U?J5U!P|O)u3>#9b6BF#=EM*&Nq0%X_%t?K-7>TwAg_nL(jITXK zNDb176dq-)9tbt4*D}be)+S;F-ziL-0k<;AzqGn3@{Fh{(_|Y;I)ZNY^8>Y^-P{zw z?99u8k1JBnZSftpyk_9CQQgcM7-w5ydVxaXEvoYlTQR}ZH6^Dl{iWiC23JJ7892lU zg$4fbAdAm){{V(B%kp_8RCEJ=A26ONH|9`kHHk_M3%;f0H|jdLGnkmU#KLK-$7C7F zHvaQ$Zds_z2gUab*gE1jUT?k12(fsqLflFQ@Jh3AS3%{NK+=i=0$4McJVAb(Fw;h0 zwdBGL(Y!%w6D4j~-)*ETxMkk{iv1e!`G#zF9ipo?Wa%<1OFrUam$1&`mjqn*6jx&0sbcNRZ^2Unn2 z#s$L}efuIiF~~@l6`;IzAF5oeiLrrbiKL3vd5X0HI_+%%hVG>y= zKJ_eGtrvCC#12_ag>Etg98?hqHs0nsnYj5H?{BQNk?EUYL);q*54dOtfMQaq+$pFz z#${-$pqarcxTqB@!f-)0g2MC-z)>wSy12s9+%AdT2V~SBwQg7&P?Rq+gQpA&V-NsV z-i{@q2i#l>z6by!jQ%7be?G;F@TmMcxtp^aIgF?to=DccpLms(Q;A@@#6~u|+)FVU zWWMGTElNwJFkGb?tJJ$|1;(0>IEP3(E>~fhcPRiHoZvr=00LOq} z{&|Wh{{Rzc*@$N`kQ*?Fy7k00&zj)-A|0G5Se~ zKpjEuE8H=*;e_KTy!(O3=u|PEG=N$b!Yo>J%y}l)5~#3)M_wh4leu6?ZmWqxGdbmt zSBUWn3P0&AA^FxMAMu&+O}L&J;GP+SUyn3FlsP{2E!p*hZ9Bxi;4Y6;!vV6r<|6WI z%o>qln>d4w=%W>JP=u1dZuKOs%G4xQBN^w3-Kut^q;FAhTrrr zz#v2OtVo~nY`XD0bHx(%8>qaIdY(A_Md};GQ&IJ8*NM>mrB(4PcN_8aiG#tH7C(f= zRQ)2pEMuLY6HxwQwE96RD&jX&!#ojso)5tB&llo^1P{;DNd6?diQ}Fb;+`4eo+*D5 z#V?Np@cs+I6R5pK^%vB<4h0Dpe+cnMAAqNcM0lkfPaN@29IJ`q{{Vs{1h4!mi5O$U zJRgDKo(bTd82C>Fd?O^lK*D;CKZt<<{0hE51xFLX2dD=V!#oqiJQKtC1QI|>mHz;X zQ6mhn$|K_+82Co~N5Vc4@Q;i~Qk_c0dX?%2s2-p?hl~eM9YFOD2zbNB9uPf32_mIR z{{YIVQojQa;Xm_XPNjO4>Qkvsr8<=AQ>k8|", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_completion_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) - llm = genai.GenerativeModel("gemini-1.5-flash") - await llm.generate_content_async( - "What is the argument for LeBron James being the GOAT?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=35, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "What is the argument for LeBron James being the GOAT?"}], - output_messages=[ - {"content": MOCK_COMPLETION_SIMPLE_1["candidates"][0]["content"]["parts"][0]["text"], "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 35}, - token_metrics={"input_tokens": 12, "output_tokens": 30, "total_tokens": 42}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_completion_error(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - llm = genai.GenerativeModel("gemini-1.5-flash") - llm._client = mock.Mock() - llm._client.generate_content.side_effect = InvalidArgument("Invalid API key. Please pass a valid API key.") - with pytest.raises(InvalidArgument): - llm.generate_content( - "What is the argument for LeBron James being the GOAT?", - generation_config=genai.types.GenerationConfig( - stop_sequences=["x"], max_output_tokens=35, temperature=1.0 - ), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - mock_llmobs_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "What is the argument for LeBron James being the GOAT?"}], - output_messages=[{"content": ""}], - error="google.api_core.exceptions.InvalidArgument", - error_message=span.get_tag("error.message"), - error_stack=span.get_tag("error.stack"), - metadata={"temperature": 1.0, "max_output_tokens": 35}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - ) - - async def test_completion_error_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - llm = genai.GenerativeModel("gemini-1.5-flash") - llm._async_client = mock.Mock() - llm._async_client.generate_content.side_effect = InvalidArgument( - "Invalid API key. Please pass a valid API key." - ) - with pytest.raises(InvalidArgument): - await llm.generate_content_async( - "What is the argument for LeBron James being the GOAT?", - generation_config=genai.types.GenerationConfig( - stop_sequences=["x"], max_output_tokens=35, temperature=1.0 - ), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - mock_llmobs_writer.enqueue.assert_called_with( - _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "What is the argument for LeBron James being the GOAT?"}], - output_messages=[{"content": ""}], - error="google.api_core.exceptions.InvalidArgument", - error_message=span.get_tag("error.message"), - error_stack=span.get_tag("error.stack"), - metadata={"temperature": 1.0, "max_output_tokens": 35}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - ) - - def test_completion_multiple_messages( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer - ): - mock_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) - llm = genai.GenerativeModel("gemini-1.5-flash") - llm.generate_content( - [ - {"role": "user", "parts": [{"text": "Hello world!"}]}, - {"role": "model", "parts": [{"text": "Great to meet you. What would you like to know?"}]}, - {"role": "user", "parts": [{"text": "Why is the sky blue?"}]}, - ], - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=35, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "Hello world!", "role": "user"}, - {"content": "Great to meet you. What would you like to know?", "role": "model"}, - {"content": "Why is the sky blue?", "role": "user"}, - ], - output_messages=[ - {"content": MOCK_COMPLETION_SIMPLE_2["candidates"][0]["content"]["parts"][0]["text"], "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 35}, - token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_completion_multiple_messages_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) - llm = genai.GenerativeModel("gemini-1.5-flash") - await llm.generate_content_async( - [ - {"role": "user", "parts": [{"text": "Hello world!"}]}, - {"role": "model", "parts": [{"text": "Great to meet you. What would you like to know?"}]}, - {"role": "user", "parts": [{"text": "Why is the sky blue?"}]}, - ], - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=35, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "Hello world!", "role": "user"}, - {"content": "Great to meet you. What would you like to know?", "role": "model"}, - {"content": "Why is the sky blue?", "role": "user"}, - ], - output_messages=[ - {"content": MOCK_COMPLETION_SIMPLE_2["candidates"][0]["content"]["parts"][0]["text"], "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 35}, - token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_chat_completion(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - mock_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) - llm = genai.GenerativeModel("gemini-1.5-flash") - chat = llm.start_chat( - history=[ - {"role": "user", "parts": "Hello world!"}, - {"role": "model", "parts": "Great to meet you. What would you like to know?"}, - ] - ) - chat.send_message( - "Why is the sky blue?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=35, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "Hello world!", "role": "user"}, - {"content": "Great to meet you. What would you like to know?", "role": "model"}, - {"content": "Why is the sky blue?", "role": "user"}, - ], - output_messages=[ - {"content": MOCK_COMPLETION_SIMPLE_2["candidates"][0]["content"]["parts"][0]["text"], "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 35}, - token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_chat_completion_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_2)) - llm = genai.GenerativeModel("gemini-1.5-flash") - chat = llm.start_chat( - history=[ - {"role": "user", "parts": "Hello world!"}, - {"role": "model", "parts": "Great to meet you. What would you like to know?"}, - ] - ) - await chat.send_message_async( - "Why is the sky blue?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=35, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "Hello world!", "role": "user"}, - {"content": "Great to meet you. What would you like to know?", "role": "model"}, - {"content": "Why is the sky blue?", "role": "user"}, - ], - output_messages=[ - {"content": MOCK_COMPLETION_SIMPLE_2["candidates"][0]["content"]["parts"][0]["text"], "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 35}, - token_metrics={"input_tokens": 24, "output_tokens": 35, "total_tokens": 59}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_completion_system_prompt(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - mock_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_SYSTEM)) - llm = genai.GenerativeModel( - "gemini-1.5-flash", - system_instruction="You are a die-hard Michael Jordan fan that always brings stats to the discussion.", - ) - llm.generate_content( - "What is the argument for LeBron James being the GOAT?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=50, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - { - "content": "You are a die-hard Michael Jordan fan that always brings stats to the discussion.", - "role": "system", - }, - {"content": "What is the argument for LeBron James being the GOAT?"}, - ], - output_messages=[ - { - "content": MOCK_COMPLETION_SIMPLE_SYSTEM["candidates"][0]["content"]["parts"][0]["text"], - "role": "model", - } - ], - metadata={"temperature": 1.0, "max_output_tokens": 50}, - token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_completion_system_prompt_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_SYSTEM)) - llm = genai.GenerativeModel( - "gemini-1.5-flash", - system_instruction="You are a die-hard Michael Jordan fan that always brings stats to the discussion.", - ) - await llm.generate_content_async( - "What is the argument for LeBron James being the GOAT?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=50, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - { - "content": "You are a die-hard Michael Jordan fan that always brings stats to the discussion.", - "role": "system", - }, - {"content": "What is the argument for LeBron James being the GOAT?"}, - ], - output_messages=[ - { - "content": MOCK_COMPLETION_SIMPLE_SYSTEM["candidates"][0]["content"]["parts"][0]["text"], - "role": "model", - }, - ], - metadata={"temperature": 1.0, "max_output_tokens": 50}, - token_metrics={"input_tokens": 29, "output_tokens": 45, "total_tokens": 74}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_completion_stream(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - mock_client.responses["stream_generate_content"] = [ - (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) - ] - llm = genai.GenerativeModel("gemini-1.5-flash") - response = llm.generate_content( - "Can you recite the alphabet?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=60, temperature=1.0), - stream=True, - ) - for _ in response: - pass - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Can you recite the alphabet?"}], - output_messages=[ - {"content": "".join(chunk["text"] for chunk in MOCK_COMPLETION_STREAM_CHUNKS), "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 60}, - token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_completion_stream_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["stream_generate_content"] = [ - _async_streamed_response(MOCK_COMPLETION_STREAM_CHUNKS) - ] - llm = genai.GenerativeModel("gemini-1.5-flash") - response = await llm.generate_content_async( - "Can you recite the alphabet?", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=60, temperature=1.0), - stream=True, - ) - async for _ in response: - pass - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Can you recite the alphabet?"}], - output_messages=[ - {"content": "".join(chunk["text"] for chunk in MOCK_COMPLETION_STREAM_CHUNKS), "role": "model"} - ], - metadata={"temperature": 1.0, "max_output_tokens": 60}, - token_metrics={"input_tokens": 6, "output_tokens": 52, "total_tokens": 58}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_completion_tool_call(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - mock_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL_CALL)) - llm = genai.GenerativeModel("gemini-1.5-flash", tools=[set_light_values]) - llm.generate_content( - "Dim the lights so the room feels cozy and warm.", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Dim the lights so the room feels cozy and warm."}], - output_messages=[ - { - "content": "", - "role": "model", - "tool_calls": [ - { - "name": "set_light_values", - "arguments": { - "fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50.0}] - }, - "tool_id": "", - "type": "function_call", - } - ], - } - ], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_completion_tool_call_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_TOOL_CALL)) - llm = genai.GenerativeModel("gemini-1.5-flash", tools=[set_light_values]) - await llm.generate_content_async( - "Dim the lights so the room feels cozy and warm.", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Dim the lights so the room feels cozy and warm."}], - output_messages=[ - { - "content": "", - "role": "model", - "tool_calls": [ - { - "name": "set_light_values", - "arguments": { - "fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50.0}] - }, - "tool_id": "", - "type": "function_call", - } - ], - } - ], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_gemini_completion_tool_stream( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer - ): - mock_client.responses["stream_generate_content"] = [ - (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) - ] - llm = genai.GenerativeModel("gemini-1.5-flash", tools=[set_light_values]) - response = llm.generate_content( - "Dim the lights so the room feels cozy and warm.", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - stream=True, - ) - for _ in response: - pass - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Dim the lights so the room feels cozy and warm."}], - output_messages=[ - { - "content": "", - "role": "model", - "tool_calls": [ - { - "name": "set_light_values", - "arguments": { - "fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50.0}] - }, - "tool_id": "", - "type": "function_call", - } - ], - } - ], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_gemini_completion_tool_stream_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - mock_client_async.responses["stream_generate_content"] = [ - _async_streamed_response(MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS) - ] - llm = genai.GenerativeModel("gemini-1.5-flash", tools=[set_light_values]) - response = await llm.generate_content_async( - "Dim the lights so the room feels cozy and warm.", - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - stream=True, - ) - async for _ in response: - pass - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[{"content": "Dim the lights so the room feels cozy and warm."}], - output_messages=[ - { - "content": "", - "role": "model", - "tool_calls": [ - { - "name": "set_light_values", - "arguments": { - "fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50.0}] - }, - "tool_id": "", - "type": "function_call", - } - ], - } - ], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 150, "output_tokens": 25, "total_tokens": 175}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - def test_gemini_completion_image(self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client, mock_tracer): - """Ensure passing images to generate_content() won't break patching.""" - img = Image.open(os.path.join(os.path.dirname(__file__), "test_data/apple.jpg")) - mock_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_IMG_CALL)) - llm = genai.GenerativeModel("gemini-1.5-flash") - llm.generate_content( - [img, "Return a bounding box for the apple. \n [ymin, xmin, ymax, xmax]"], - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "[Non-text content object: {}]".format(repr(img))}, - {"content": "Return a bounding box for the apple. \n [ymin, xmin, ymax, xmax]"}, - ], - output_messages=[{"content": "57 100 900 911", "role": "model"}], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) - - async def test_gemini_completion_image_async( - self, genai, ddtrace_global_config, mock_llmobs_writer, mock_client_async, mock_tracer - ): - """Ensure passing images to generate_content() won't break patching.""" - img = Image.open(os.path.join(os.path.dirname(__file__), "test_data/apple.jpg")) - mock_client_async.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_IMG_CALL)) - llm = genai.GenerativeModel("gemini-1.5-flash") - await llm.generate_content_async( - [img, "Return a bounding box for the apple. \n [ymin, xmin, ymax, xmax]"], - generation_config=genai.types.GenerationConfig(stop_sequences=["x"], max_output_tokens=30, temperature=1.0), - ) - span = mock_tracer.pop_traces()[0][0] - assert mock_llmobs_writer.enqueue.call_count == 1 - expected_llmobs_span_event = _expected_llmobs_llm_span_event( - span, - model_name="gemini-1.5-flash", - model_provider="google", - input_messages=[ - {"content": "[Non-text content object: {}]".format(repr(img))}, - {"content": "Return a bounding box for the apple. \n [ymin, xmin, ymax, xmax]"}, - ], - output_messages=[{"content": "57 100 900 911", "role": "model"}], - metadata={"temperature": 1.0, "max_output_tokens": 30}, - token_metrics={"input_tokens": 277, "output_tokens": 14, "total_tokens": 291}, - tags={"ml_app": "", "service": "tests.contrib.google_generativeai"}, - ) - mock_llmobs_writer.enqueue.assert_called_with(expected_llmobs_span_event) diff --git a/tests/contrib/google_generativeai/test_google_generativeai_patch.py b/tests/contrib/google_generativeai/test_google_generativeai_patch.py deleted file mode 100644 index a98ad7e2d6a..00000000000 --- a/tests/contrib/google_generativeai/test_google_generativeai_patch.py +++ /dev/null @@ -1,24 +0,0 @@ -from ddtrace.contrib.internal.google_generativeai.patch import get_version -from ddtrace.contrib.internal.google_generativeai.patch import patch -from ddtrace.contrib.internal.google_generativeai.patch import unpatch -from tests.contrib.patch import PatchTestCase - - -class TestGoogleGenerativeAIPatch(PatchTestCase.Base): - __integration_name__ = "google_generativeai" - __module_name__ = "google.generativeai" - __patch_func__ = patch - __unpatch_func__ = unpatch - __get_version__ = get_version - - def assert_module_patched(self, genai): - self.assert_wrapped(genai.GenerativeModel.generate_content) - self.assert_wrapped(genai.GenerativeModel.generate_content_async) - - def assert_not_module_patched(self, genai): - self.assert_not_wrapped(genai.GenerativeModel.generate_content) - self.assert_not_wrapped(genai.GenerativeModel.generate_content_async) - - def assert_not_module_double_patched(self, genai): - self.assert_not_double_wrapped(genai.GenerativeModel.generate_content) - self.assert_not_double_wrapped(genai.GenerativeModel.generate_content_async) diff --git a/tests/contrib/google_generativeai/utils.py b/tests/contrib/google_generativeai/utils.py deleted file mode 100644 index c2319d50327..00000000000 --- a/tests/contrib/google_generativeai/utils.py +++ /dev/null @@ -1,192 +0,0 @@ -import collections - -from google.generativeai import protos -import mock - - -MOCK_COMPLETION_SIMPLE_1 = { - "candidates": [ - { - "content": { - "parts": [ - { - "text": "The argument for LeBron James being the 'Greatest of All Time' (" - "GOAT) is multifaceted and involves a variety of factors. Here's a " - "breakdown" - } - ], - "role": "model", - }, - "finish_reason": 2, - } - ], - "usage_metadata": {"prompt_token_count": 12, "candidates_token_count": 30, "total_token_count": 42}, -} -MOCK_COMPLETION_SIMPLE_2 = { - "candidates": [ - { - "content": { - "parts": [ - { - "text": "The sky appears blue due to a phenomenon called **Rayleigh " - "scattering**. \nHere's how it works:* **Sunlight is made up of " - "all colors of the" - } - ], - "role": "model", - }, - "finish_reason": 2, - } - ], - "usage_metadata": {"prompt_token_count": 24, "candidates_token_count": 35, "total_token_count": 59}, -} -MOCK_COMPLETION_SIMPLE_SYSTEM = { - "candidates": [ - { - "content": { - "parts": [ - { - "text": "Look, I respect LeBron James. He's a phenomenal player, " - "an incredible athlete, and a great ambassador for the game. But " - "when it comes to the GOAT, the crown belongs to His Airness, " - "Michael Jordan!" - } - ], - "role": "model", - }, - "finish_reason": 2, - } - ], - "usage_metadata": {"prompt_token_count": 29, "candidates_token_count": 45, "total_token_count": 74}, -} -MOCK_COMPLETION_STREAM_CHUNKS = ( - {"text": "A", "usage_metadata": {"prompt_token_count": 6, "candidates_token_count": 1, "total_token_count": 7}}, - { - "text": ", B, C, D, E, F, G, H, I", - "usage_metadata": {"prompt_token_count": 6, "candidates_token_count": 17, "total_token_count": 23}, - }, - { - "text": ", J, K, L, M, N, O, P, Q", - "usage_metadata": {"prompt_token_count": 6, "candidates_token_count": 33, "total_token_count": 39}, - }, - { - "text": ", R, S, T, U, V, W, X, Y, Z.\n", - "usage_metadata": {"prompt_token_count": 6, "candidates_token_count": 52, "total_token_count": 58}, - }, -) -MOCK_COMPLETION_TOOL_CALL = { - "candidates": [ - { - "content": { - "parts": [ - { - "function_call": { - "name": "set_light_values", - "args": { - "fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50}] - }, - } - } - ], - "role": "model", - }, - "finish_reason": 2, - } - ], - "usage_metadata": {"prompt_token_count": 150, "candidates_token_count": 25, "total_token_count": 175}, -} -MOCK_CHAT_COMPLETION_TOOL_RESPONSE = { - "candidates": [ - { - "content": { - "parts": [ - {"text": "OK. I've dimmed the lights to 50% and set the color temperature to warm. How's that? \n"} - ], - "role": "model", - }, - "finish_reason": 2, - }, - ], - "usage_metadata": {"prompt_token_count": 206, "candidates_token_count": 27, "total_token_count": 233}, -} -MOCK_COMPLETION_TOOL_CALL_STREAM_CHUNKS = ( - { - "function_call": { - "name": "set_light_values", - "args": {"fields": [{"key": "color_temp", "value": "warm"}, {"key": "brightness", "value": 50}]}, - }, - "usage_metadata": {"prompt_token_count": 150, "candidates_token_count": 25, "total_token_count": 175}, - }, -) -MOCK_COMPLETION_IMG_CALL = { - "candidates": [{"content": {"parts": [{"text": "57 100 900 911"}], "role": "model"}, "finish_reason": 2}], - "usage_metadata": {"prompt_token_count": 277, "candidates_token_count": 14, "total_token_count": 291}, -} - - -class MockGenerativeModelClient: - def __init__(self): - self.responses = collections.defaultdict(list) - self._client_options = mock.Mock() - self._client_options.api_key = "" - - def generate_content(self, request, **kwargs): - return self.responses["generate_content"].pop(0) - - def stream_generate_content(self, request, **kwargs): - return self.responses["stream_generate_content"].pop(0) - - -class MockGenerativeModelAsyncClient: - def __init__(self): - self.responses = collections.defaultdict(list) - self._client = mock.Mock() - self._client_options = mock.Mock() - self._client._client_options = self._client_options - self._client_options.api_key = "" - - async def generate_content(self, request, **kwargs): - return self.responses["generate_content"].pop(0) - - async def stream_generate_content(self, request, **kwargs): - return self.responses["stream_generate_content"].pop(0) - - -def set_light_values(brightness, color_temp): - """Set the brightness and color temperature of a room light. (mock API). - Args: - brightness: Light level from 0 to 100. Zero is off and 100 is full brightness - color_temp: Color temperature of the light fixture, which can be `daylight`, `cool` or `warm`. - Returns: - A dictionary containing the set brightness and color temperature. - """ - return {"brightness": brightness, "colorTemperature": color_temp} - - -async def _async_streamed_response(mock_chunks): - """Return async streamed response chunks to be processed by the mock async client.""" - for chunk in mock_chunks: - yield _mock_completion_stream_chunk(chunk) - - -def _mock_completion_response(mock_completion_dict): - mock_content = protos.Content(mock_completion_dict["candidates"][0]["content"]) - return protos.GenerateContentResponse( - { - "candidates": [ - {"content": mock_content, "finish_reason": mock_completion_dict["candidates"][0]["finish_reason"]} - ], - "usage_metadata": mock_completion_dict["usage_metadata"], - } - ) - - -def _mock_completion_stream_chunk(chunk): - mock_content = None - if chunk.get("text"): - mock_content = protos.Content({"parts": [{"text": chunk["text"]}], "role": "model"}) - elif chunk.get("function_call"): - mock_content = protos.Content({"parts": [{"function_call": chunk["function_call"]}], "role": "model"}) - return protos.GenerateContentResponse( - {"candidates": [{"content": mock_content, "finish_reason": 2}], "usage_metadata": chunk["usage_metadata"]} - ) diff --git a/tests/llmobs/suitespec.yml b/tests/llmobs/suitespec.yml index 57e752e1ea7..f594efe0143 100644 --- a/tests/llmobs/suitespec.yml +++ b/tests/llmobs/suitespec.yml @@ -4,8 +4,6 @@ components: - ddtrace/contrib/internal/anthropic/* google_adk: - ddtrace/contrib/internal/google_adk/* - google_generativeai: - - ddtrace/contrib/internal/google_generativeai/* google_genai: - ddtrace/contrib/internal/google_genai/* vertexai: @@ -55,19 +53,6 @@ suites: - tests/contrib/google_adk/* runner: riot snapshot: true - google_generativeai: - parallelism: 1 - paths: - - '@bootstrap' - - '@core' - - '@tracing' - - '@contrib' - - '@google_generativeai' - - '@llmobs' - - tests/contrib/google_generativeai/* - - tests/snapshots/tests.contrib.google_generativeai.* - runner: riot - snapshot: true google_genai: parallelism: 1 paths: diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json deleted file mode 100644 index 67597a9ef4d..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 176000, - "start": 1752260216102575000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json deleted file mode 100644 index d40ce8ad422..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_error.json +++ /dev/null @@ -1,31 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 1, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "error.message": "400 Invalid API key. Please pass a valid API key.", - "error.stack": "Traceback (most recent call last):\n File \"/Users/jacob.simpher/go/src/github.com/DataDog/dd-trace-py/ddtrace/contrib/internal/google_generativeai/patch.py\", line 51, in traced_generate\n generations = func(*args, **kwargs)\n File \"/Users/jacob.simpher/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py31013_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_google-generativeai~070_pillow_google-ai-generativelanguage_vertexai/lib/python3.10/site-packages/google/generativeai/generative_models.py\", line 331, in generate_content\n response = self._client.generate_content(\n File \"/Users/jacob.simpher/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py31013_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_google-generativeai~070_pillow_google-ai-generativelanguage_vertexai/lib/python3.10/site-packages/mock/mock.py\", line 1190, in __call__\n return _mock_self._mock_call(*args, **kwargs)\n File \"/Users/jacob.simpher/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py31013_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_google-generativeai~070_pillow_google-ai-generativelanguage_vertexai/lib/python3.10/site-packages/mock/mock.py\", line 1194, in _mock_call\n return _mock_self._execute_mock_call(*args, **kwargs)\n File \"/Users/jacob.simpher/go/src/github.com/DataDog/dd-trace-py/.riot/venv_py31013_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_google-generativeai~070_pillow_google-ai-generativelanguage_vertexai/lib/python3.10/site-packages/mock/mock.py\", line 1251, in _execute_mock_call\n raise effect\ngoogle.api_core.exceptions.InvalidArgument: 400 Invalid API key. Please pass a valid API key.\n", - "error.type": "google.api_core.exceptions.InvalidArgument", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 1902000, - "start": 1752260216124732000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json deleted file mode 100644 index 4ca0cc7ed59..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_image.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 3668000, - "start": 1752260216359632000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json deleted file mode 100644 index 170138a02ef..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_multiple_messages.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 358000, - "start": 1752260216149341000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json deleted file mode 100644 index f53f69772b0..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_stream.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 1104000, - "start": 1752260216208097000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json deleted file mode 100644 index f75e740b8c4..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_system_prompt.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 176000, - "start": 1752260216187574000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json deleted file mode 100644 index 6a32e85f576..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_completion_tool_stream.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 335000, - "start": 1752260216313639000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json deleted file mode 100644 index f43670eb5a2..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_chat_completion.json +++ /dev/null @@ -1,56 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 125000, - "start": 1752260216291315000 - }], -[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 1, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 117000, - "start": 1752260216291724000 - }]] diff --git a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json b/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json deleted file mode 100644 index 58c850ee3f9..00000000000 --- a/tests/snapshots/tests.contrib.google_generativeai.test_google_generativeai.test_gemini_tool_completion.json +++ /dev/null @@ -1,28 +0,0 @@ -[[ - { - "name": "gemini.request", - "service": "tests.contrib.google_generativeai", - "resource": "GenerativeModel.generate_content", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "68715e7800000000", - "google_generativeai.request.model": "gemini-1.5-flash", - "google_generativeai.request.provider": "google", - "language": "python", - "runtime-id": "e72fd406a9a04657a973cf959e2935f5" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 66831 - }, - "duration": 201000, - "start": 1752260216268538000 - }]] From cb53a8a29a0aa933f305774d14d76fee9794e3ae Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Tue, 4 Nov 2025 13:25:42 -0500 Subject: [PATCH 21/42] chore(tracing): remove deprecated Span methods (#15039) ## Description Removes deprecated methods. ## Testing ## Risks ## Additional Notes It seems like there was still some usage of these APIs internally, or they were added after the deprecation went in. --- ddtrace/_trace/processor/resource_renaming.py | 2 +- ddtrace/_trace/span.py | 24 ------------------- ddtrace/appsec/_iast/_pytest_plugin.py | 2 +- ddtrace/appsec/_utils.py | 4 ++-- ddtrace/appsec/ai_guard/_api_client.py | 3 ++- ddtrace/internal/ci_visibility/encoder.py | 2 +- ...recated-span-methods-0e7bfc757ba64595.yaml | 8 +++++++ tests/appsec/contrib_appsec/utils.py | 2 +- tests/appsec/iast/iast_utils.py | 2 +- .../django_tests/test_iast_django.py | 2 +- .../pytest/test_pytest_early_config.py | 12 ++++++---- tests/contrib/pytest/utils.py | 2 +- 12 files changed, 27 insertions(+), 38 deletions(-) create mode 100644 releasenotes/notes/remove-deprecated-span-methods-0e7bfc757ba64595.yaml diff --git a/ddtrace/_trace/processor/resource_renaming.py b/ddtrace/_trace/processor/resource_renaming.py index cbe1a06be29..d0647c51e55 100644 --- a/ddtrace/_trace/processor/resource_renaming.py +++ b/ddtrace/_trace/processor/resource_renaming.py @@ -83,4 +83,4 @@ def on_span_finish(self, span: Span): if not is_404 and (not route or config._trace_resource_renaming_always_simplified_endpoint): url = span.get_tag(http.URL) endpoint = self.simplified_endpoint_computer.from_url(url) - span.set_tag_str(http.ENDPOINT, endpoint) + span._set_tag_str(http.ENDPOINT, endpoint) diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index fb5699a7d42..829ad8944d7 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -54,7 +54,6 @@ from ddtrace.internal.logger import get_logger from ddtrace.internal.settings._config import config from ddtrace.internal.utils.time import Time -from ddtrace.vendor.debtcollector import removals class SpanEvent: @@ -412,28 +411,10 @@ def _set_struct_tag(self, key: str, value: Dict[str, Any]) -> None: """ self._meta_struct[key] = value - @removals.remove(removal_version="4.0.0") - def set_struct_tag(self, key: str, value: Dict[str, Any]) -> None: - """ - DEPRECATED - - Set a tag key/value pair on the span meta_struct - Currently it will only be exported with V4 encoding - """ - self._set_struct_tag(key, value) - def _get_struct_tag(self, key: str) -> Optional[Dict[str, Any]]: """Return the given struct or None if it doesn't exist.""" return self._meta_struct.get(key, None) - @removals.remove(removal_version="4.0.0") - def get_struct_tag(self, key: str) -> Optional[Dict[str, Any]]: - """DEPRECATED - - Return the given struct or None if it doesn't exist. - """ - return self._get_struct_tag(key) - def _set_tag_str(self, key: _TagNameType, value: Text) -> None: """Set a value for a tag. Values are coerced to unicode in Python 2 and str in Python 3, with decoding errors in conversion being replaced with @@ -446,11 +427,6 @@ def _set_tag_str(self, key: _TagNameType, value: Text) -> None: raise e log.warning("Failed to set text tag '%s'", key, exc_info=True) - @removals.remove(message="use Span.set_tag instead", removal_version="4.0.0") - def set_tag_str(self, key: _TagNameType, value: Text) -> None: - """Deprecated: use `set_tag` instead.""" - self._set_tag_str(key, value) - def get_tag(self, key: _TagNameType) -> Optional[Text]: """Return the given tag or None if it doesn't exist.""" return self._meta.get(key, None) diff --git a/ddtrace/appsec/_iast/_pytest_plugin.py b/ddtrace/appsec/_iast/_pytest_plugin.py index 1de6aec1017..49f56a35322 100644 --- a/ddtrace/appsec/_iast/_pytest_plugin.py +++ b/ddtrace/appsec/_iast/_pytest_plugin.py @@ -31,7 +31,7 @@ def ddtrace_iast(request, ddspan): return # looking for IAST data in the span - dict_data = ddspan.get_struct_tag(IAST.STRUCT) + dict_data = ddspan._get_struct_tag(IAST.STRUCT) if dict_data is None: data = ddspan.get_tag(IAST.JSON) if data is None: diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index 1223dfa8675..71214903107 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -330,13 +330,13 @@ def get_user_info(self, login=False, email=False, name=False): def has_triggers(span) -> bool: if asm_config._use_metastruct_for_triggers: - return (span.get_struct_tag(APPSEC.STRUCT) or {}).get("triggers", None) is not None + return (span._get_struct_tag(APPSEC.STRUCT) or {}).get("triggers", None) is not None return span.get_tag(APPSEC.JSON) is not None def get_triggers(span) -> Any: if asm_config._use_metastruct_for_triggers: - return (span.get_struct_tag(APPSEC.STRUCT) or {}).get("triggers", None) + return (span._get_struct_tag(APPSEC.STRUCT) or {}).get("triggers", None) json_payload = span.get_tag(APPSEC.JSON) if json_payload: try: diff --git a/ddtrace/appsec/ai_guard/_api_client.py b/ddtrace/appsec/ai_guard/_api_client.py index 039279ae306..a551a8288dd 100644 --- a/ddtrace/appsec/ai_guard/_api_client.py +++ b/ddtrace/appsec/ai_guard/_api_client.py @@ -1,4 +1,5 @@ """AI Guard client for security evaluation of agentic AI workflows.""" + import json from typing import Any from typing import List @@ -211,7 +212,7 @@ def evaluate(self, messages: List[Message], options: Optional[Options] = None) - span.set_tag(AI_GUARD.TOOL_NAME_TAG, tool_name) else: span.set_tag(AI_GUARD.TARGET_TAG, "prompt") - span.set_struct_tag(AI_GUARD.STRUCT, {"messages": self._messages_for_meta_struct(messages)}) + span._set_struct_tag(AI_GUARD.STRUCT, {"messages": self._messages_for_meta_struct(messages)}) try: response = self._execute_request(f"{self._endpoint}/evaluate", payload) diff --git a/ddtrace/internal/ci_visibility/encoder.py b/ddtrace/internal/ci_visibility/encoder.py index 48ead25d960..65119d4df82 100644 --- a/ddtrace/internal/ci_visibility/encoder.py +++ b/ddtrace/internal/ci_visibility/encoder.py @@ -262,7 +262,7 @@ def put(self, item): spans_with_coverage = [ span for span in item - if COVERAGE_TAG_NAME in span.get_tags() or span.get_struct_tag(COVERAGE_TAG_NAME) is not None + if COVERAGE_TAG_NAME in span.get_tags() or span._get_struct_tag(COVERAGE_TAG_NAME) is not None ] # Also include session span for parent session ID lookup, even if it doesn't have coverage data session_span = next((span for span in item if span.get_tag(EVENT_TYPE) == SESSION_TYPE), None) diff --git a/releasenotes/notes/remove-deprecated-span-methods-0e7bfc757ba64595.yaml b/releasenotes/notes/remove-deprecated-span-methods-0e7bfc757ba64595.yaml new file mode 100644 index 00000000000..141b3a8c0d6 --- /dev/null +++ b/releasenotes/notes/remove-deprecated-span-methods-0e7bfc757ba64595.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + tracing: ``Span.set_tag_str`` has been removed, use ``Span.set_tag`` instead. + - | + tracing: ``Span.set_struct_tag`` has been removed. + - | + tracing: ``Span.get_struct_tag`` has been removed. diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index 9298e0ea047..ac5a9f6714a 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -86,7 +86,7 @@ def body(self, response) -> str: raise NotImplementedError def get_stack_trace(self, entry_span, namespace): - appsec_traces = entry_span().get_struct_tag(asm_constants.STACK_TRACE.TAG) or {} + appsec_traces = entry_span()._get_struct_tag(asm_constants.STACK_TRACE.TAG) or {} stacks = appsec_traces.get(namespace, []) return stacks diff --git a/tests/appsec/iast/iast_utils.py b/tests/appsec/iast/iast_utils.py index 952fe9000a8..ce59e25098c 100644 --- a/tests/appsec/iast/iast_utils.py +++ b/tests/appsec/iast/iast_utils.py @@ -154,7 +154,7 @@ def load_iast_report(span): else: iast_report_json = span.get_tag(IAST.JSON) if iast_report_json is None: - iast_report = span.get_struct_tag(IAST.STRUCT) + iast_report = span._get_struct_tag(IAST.STRUCT) else: iast_report = json.loads(iast_report_json) return iast_report diff --git a/tests/appsec/integrations/django_tests/test_iast_django.py b/tests/appsec/integrations/django_tests/test_iast_django.py index bbd32f4f162..d35f91cda03 100644 --- a/tests/appsec/integrations/django_tests/test_iast_django.py +++ b/tests/appsec/integrations/django_tests/test_iast_django.py @@ -28,7 +28,7 @@ def get_iast_stack_trace(root_span): - appsec_traces = root_span.get_struct_tag(STACK_TRACE.TAG) or {} + appsec_traces = root_span._get_struct_tag(STACK_TRACE.TAG) or {} stacks = appsec_traces.get("vulnerability", []) return stacks diff --git a/tests/contrib/pytest/test_pytest_early_config.py b/tests/contrib/pytest/test_pytest_early_config.py index b2c21a62ad5..9b1b2bfd7ad 100644 --- a/tests/contrib/pytest/test_pytest_early_config.py +++ b/tests/contrib/pytest/test_pytest_early_config.py @@ -44,7 +44,8 @@ def test_coverage_enabled_via_command_line_option(self): [suite_span] = _get_spans_from_list(spans, "suite") [test_span] = _get_spans_from_list(spans, "test") assert ( - suite_span.get_struct_tag(COVERAGE_TAG_NAME) is not None or test_span.get_tag(COVERAGE_TAG_NAME) is not None + suite_span._get_struct_tag(COVERAGE_TAG_NAME) is not None + or test_span.get_tag(COVERAGE_TAG_NAME) is not None ) def test_coverage_enabled_via_pytest_addopts_env_var(self): @@ -54,7 +55,8 @@ def test_coverage_enabled_via_pytest_addopts_env_var(self): [suite_span] = _get_spans_from_list(spans, "suite") [test_span] = _get_spans_from_list(spans, "test") assert ( - suite_span.get_struct_tag(COVERAGE_TAG_NAME) is not None or test_span.get_tag(COVERAGE_TAG_NAME) is not None + suite_span._get_struct_tag(COVERAGE_TAG_NAME) is not None + or test_span.get_tag(COVERAGE_TAG_NAME) is not None ) def test_coverage_enabled_via_addopts_ini_file_option(self): @@ -65,7 +67,8 @@ def test_coverage_enabled_via_addopts_ini_file_option(self): [suite_span] = _get_spans_from_list(spans, "suite") [test_span] = _get_spans_from_list(spans, "test") assert ( - suite_span.get_struct_tag(COVERAGE_TAG_NAME) is not None or test_span.get_tag(COVERAGE_TAG_NAME) is not None + suite_span._get_struct_tag(COVERAGE_TAG_NAME) is not None + or test_span.get_tag(COVERAGE_TAG_NAME) is not None ) def test_coverage_enabled_via_ddtrace_ini_file_option(self): @@ -76,5 +79,6 @@ def test_coverage_enabled_via_ddtrace_ini_file_option(self): [suite_span] = _get_spans_from_list(spans, "suite") [test_span] = _get_spans_from_list(spans, "test") assert ( - suite_span.get_struct_tag(COVERAGE_TAG_NAME) is not None or test_span.get_tag(COVERAGE_TAG_NAME) is not None + suite_span._get_struct_tag(COVERAGE_TAG_NAME) is not None + or test_span.get_tag(COVERAGE_TAG_NAME) is not None ) diff --git a/tests/contrib/pytest/utils.py b/tests/contrib/pytest/utils.py index 657c2f0b58c..b51058bf56e 100644 --- a/tests/contrib/pytest/utils.py +++ b/tests/contrib/pytest/utils.py @@ -34,7 +34,7 @@ def _get_tuples_from_segments(segments): def _get_span_coverage_data(span, use_plugin_v2=False): """Returns an abstracted view of the coverage data from the span that is independent of the coverage format.""" if use_plugin_v2: - tag_data = span.get_struct_tag(COVERAGE_TAG_NAME) + tag_data = span._get_struct_tag(COVERAGE_TAG_NAME) assert tag_data is not None, f"Coverage data not found in span {span}" return { file_data["filename"]: _get_tuples_from_bytearray(file_data["bitmap"]) for file_data in tag_data["files"] From 8bc45c5287c84b71017a07db3ce2eae74ceb6e64 Mon Sep 17 00:00:00 2001 From: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:33:30 -0800 Subject: [PATCH 22/42] chore: remove deprecated non_active_span argument (#14898) This change removes the deprecated `non_active_span` parameter to `HttpPropagator.inject`. Note the base branch, a staging area for breaking changes slated for 4.0. --------- Co-authored-by: brettlangdon --- .github/workflows/system-tests.yml | 6 +- .gitlab-ci.yml | 2 +- .../internal/grpc/client_interceptor.py | 3 +- ddtrace/propagation/http.py | 18 +---- .../non-active-span-3398e88b19eb94c3.yaml | 4 + tests/tracer/test_propagation.py | 81 +------------------ 6 files changed, 13 insertions(+), 101 deletions(-) create mode 100644 releasenotes/notes/non-active-span-3398e88b19eb94c3.yaml diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index d3a9a81a490..9e25e4a8673 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' + ref: 'f980721788378102f7ae7caef156a53b20feb093' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' + ref: 'f980721788378102f7ae7caef156a53b20feb093' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '4396f2a3e338544fb808e542ebcb83a8ed40af63' + ref: 'f980721788378102f7ae7caef156a53b20feb093' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index feb657085b8..22d4b992999 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "4396f2a3e338544fb808e542ebcb83a8ed40af63" + SYSTEM_TESTS_REF: "f980721788378102f7ae7caef156a53b20feb093" default: interruptible: true diff --git a/ddtrace/contrib/internal/grpc/client_interceptor.py b/ddtrace/contrib/internal/grpc/client_interceptor.py index 0d6ddf227a1..7c5ac0ef5e5 100644 --- a/ddtrace/contrib/internal/grpc/client_interceptor.py +++ b/ddtrace/contrib/internal/grpc/client_interceptor.py @@ -233,8 +233,7 @@ def _intercept_client_call(self, method_kind, client_call_details): # propagate distributed tracing headers if available headers = {} if config.grpc.distributed_tracing_enabled: - # NOTE: We need to pass the span to the HTTPPropagator since it isn't active at this point - HTTPPropagator.inject(span.context, headers, span) + HTTPPropagator.inject(span, headers) metadata.extend(headers.items()) diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 7780068aa6f..269e41d86f0 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -24,8 +24,6 @@ from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate from ..constants import AUTO_KEEP from ..constants import AUTO_REJECT @@ -1121,7 +1119,7 @@ def _resolve_contexts(contexts, styles_w_ctx, normalized_headers): return primary_context @staticmethod - def inject(context: Union[Context, Span], headers: Dict[str, str], non_active_span: Optional[Span] = None) -> None: + def inject(context: Union[Context, Span], headers: Dict[str, str]) -> None: """Inject Context attributes that have to be propagated as HTTP headers. Here is an example using `requests`:: @@ -1150,26 +1148,16 @@ def parent_call(): Span objects automatically trigger sampling decisions. Context objects should have sampling_priority set to avoid inconsistent downstream sampling. :param dict headers: HTTP headers to extend with tracing attributes. - :param Span non_active_span: **DEPRECATED** - Pass Span objects to the context parameter instead. """ - if non_active_span is not None: - # non_active_span is only used for sampling decisions, not to inject headers. - deprecate( - "The non_active_span parameter is deprecated", - message="Use the context parameter instead.", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) # Cannot rename context parameter due to backwards compatibility # Handle sampling and get context for header injection - span_context = HTTPPropagator._get_sampled_injection_context(context, non_active_span) + span_context = HTTPPropagator._get_sampled_injection_context(context, None) # Log a warning if we cannot determine a sampling decision before injecting headers. if span_context.span_id and span_context.trace_id and span_context.sampling_priority is None: log.debug( "Sampling decision not available. Downstream spans will not inherit a sampling priority: " - "args=(context=%s, ..., non_active_span=%s) detected span context=%s", + "args=(context=%s, ...) detected span context=%s", context, - non_active_span, span_context, ) diff --git a/releasenotes/notes/non-active-span-3398e88b19eb94c3.yaml b/releasenotes/notes/non-active-span-3398e88b19eb94c3.yaml new file mode 100644 index 00000000000..32eef0cfafe --- /dev/null +++ b/releasenotes/notes/non-active-span-3398e88b19eb94c3.yaml @@ -0,0 +1,4 @@ +--- +other: + - | + This change removes the deprecated non_active_span parameter to `HttpPropagator.inject` diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index eee54604355..a6ea2de15c2 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -3579,84 +3579,6 @@ def test_baggage_span_tags_wildcard(): assert "baggage.session.id" not in context._meta -def test_inject_non_active_span_parameter_deprecated(): - """Test that the non_active_span parameter triggers a deprecation warning.""" - headers = {} - with ddtracer.start_span("non_active_span") as span: - assert span.context.sampling_priority is None # No sampling decision yet - with pytest.warns() as warnings_list: - HTTPPropagator.inject(context=Context(), headers=headers, non_active_span=span) - assert span.context.sampling_priority is not None # Sampling should be triggered - assert not headers, f"No headers should be injected, Context is empty: {headers}" - - # Should capture exactly one deprecation warning - assert len(warnings_list) == 1 - assert "non_active_span parameter is deprecated" in str(warnings_list[0].message) - - -def test_inject_context_and_span_same_trace_deprecated(): - """Test injecting Context + non_active_span from the same trace (parent-child).""" - headers = {} - with ddtracer.trace("parent") as parent: - with ddtracer.start_span("child", child_of=parent) as non_active_child: - assert non_active_child.context.sampling_priority is None # No sampling yet - assert ddtracer.current_span() is not non_active_child # Child is not active - with mock.patch("ddtrace.propagation.http.log.debug") as mock_debug, pytest.warns() as warnings_list: - HTTPPropagator.inject( - context=non_active_child.context, headers=headers, non_active_span=non_active_child - ) - # Sampling decision should be set on root span even when child is used for propagation - assert parent.context.sampling_priority is not None - assert non_active_child.context.sampling_priority is not None - - mock_debug.assert_has_calls( - [ - mock.call( - "%s sampled before propagating trace: span_context=%s", - non_active_child._local_root, - non_active_child.context, - ) - ] - ) - assert headers.get("x-datadog-sampling-priority") == str(parent.context.sampling_priority) - # Parent span info propagated (context takes precedence over non_active_span) - # Non_active_span is only used to make a sampling decision, not to inject headers. - assert headers.get("x-datadog-parent-id") == str(non_active_child.span_id) - - # Should capture deprecation warning - assert len(warnings_list) == 1 - assert "non_active_span parameter is deprecated" in str(warnings_list[0].message) - - -def test_inject_context_and_span_different_trace_deprecated(): - """Test injecting Context + non_active_span from completely different traces.""" - headers = {} - with ddtracer.start_span("span1", child_of=None) as span1: - with ddtracer.start_span("span2", child_of=None) as span2: - with mock.patch("ddtrace.propagation.http.log.debug") as mock_debug, pytest.warns() as warnings_list: - HTTPPropagator.inject(context=span1.context, headers=headers, non_active_span=span2) - - mock_debug.assert_has_calls( - [ - mock.call( - "Sampling decision not available. Downstream spans will not inherit a sampling priority" - ": args=(context=%s, ..., non_active_span=%s) detected span context=%s", - span1.context, - span2, - span1.context, - ) - ] - ) - - # Span1 span info propagated (context takes precedence over Span2) - # non_active_span parameter is only used to make a sampling decision, not to inject headers. - assert headers.get("x-datadog-parent-id") == str(span1.span_id) - - # Should capture deprecation warning - assert len(warnings_list) == 1 - assert "non_active_span parameter is deprecated" in str(warnings_list[0].message) - - def test_inject_context_without_sampling_priority_active_trace(): """Test injecting a Context without sampling priority when there's an active trace.""" headers = {} @@ -3693,9 +3615,8 @@ def test_inject_context_without_sampling_priority_inactive_trace(): [ mock.call( "Sampling decision not available. Downstream spans will not inherit a sampling priority" - ": args=(context=%s, ..., non_active_span=%s) detected span context=%s", + ": args=(context=%s, ...) detected span context=%s", span.context, - None, span.context, ) ] From 4f3917488c8c41aabadbe7f189238b543aa0d42a Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Wed, 5 Nov 2025 09:43:11 -0800 Subject: [PATCH 23/42] update locks --- .riot/requirements/108bb1d.txt | 31 ------------------------ .riot/requirements/153a274.txt | 38 ----------------------------- .riot/requirements/15a8028.txt | 36 ---------------------------- .riot/requirements/6acdecb.txt | 31 ------------------------ .riot/requirements/7124b33.txt | 37 ---------------------------- .riot/requirements/9283280.txt | 37 ---------------------------- .riot/requirements/96471c4.txt | 37 ---------------------------- .riot/requirements/e5e91a5.txt | 44 ---------------------------------- .riot/requirements/f1461b7.txt | 38 ----------------------------- 9 files changed, 329 deletions(-) delete mode 100644 .riot/requirements/108bb1d.txt delete mode 100644 .riot/requirements/153a274.txt delete mode 100644 .riot/requirements/15a8028.txt delete mode 100644 .riot/requirements/6acdecb.txt delete mode 100644 .riot/requirements/7124b33.txt delete mode 100644 .riot/requirements/9283280.txt delete mode 100644 .riot/requirements/96471c4.txt delete mode 100644 .riot/requirements/e5e91a5.txt delete mode 100644 .riot/requirements/f1461b7.txt diff --git a/.riot/requirements/108bb1d.txt b/.riot/requirements/108bb1d.txt deleted file mode 100644 index 12b7109ac29..00000000000 --- a/.riot/requirements/108bb1d.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/108bb1d.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/153a274.txt b/.riot/requirements/153a274.txt deleted file mode 100644 index 9832f760415..00000000000 --- a/.riot/requirements/153a274.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/153a274.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/15a8028.txt b/.riot/requirements/15a8028.txt deleted file mode 100644 index 0085ed12591..00000000000 --- a/.riot/requirements/15a8028.txt +++ /dev/null @@ -1,36 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15a8028.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/6acdecb.txt b/.riot/requirements/6acdecb.txt deleted file mode 100644 index 3045a6a286d..00000000000 --- a/.riot/requirements/6acdecb.txt +++ /dev/null @@ -1,31 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/6acdecb.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -protobuf==5.29.5 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/7124b33.txt b/.riot/requirements/7124b33.txt deleted file mode 100644 index cf75058c534..00000000000 --- a/.riot/requirements/7124b33.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/7124b33.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn[gevent]==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 -zstandard==0.23.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/9283280.txt b/.riot/requirements/9283280.txt deleted file mode 100644 index ba08259d948..00000000000 --- a/.riot/requirements/9283280.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/9283280.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.29 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/96471c4.txt b/.riot/requirements/96471c4.txt deleted file mode 100644 index 73c2adc0e73..00000000000 --- a/.riot/requirements/96471c4.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/96471c4.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zstandard==0.23.0 diff --git a/.riot/requirements/e5e91a5.txt b/.riot/requirements/e5e91a5.txt deleted file mode 100644 index 4b75da14fa2..00000000000 --- a/.riot/requirements/e5e91a5.txt +++ /dev/null @@ -1,44 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e5e91a5.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gevent==24.2.1 -greenlet==3.1.1 -gunicorn[gevent]==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zope-event==5.0 -zope-interface==7.2 -zstandard==0.23.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/f1461b7.txt b/.riot/requirements/f1461b7.txt deleted file mode 100644 index 63023fb4133..00000000000 --- a/.riot/requirements/f1461b7.txt +++ /dev/null @@ -1,38 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f1461b7.in -# -attrs==25.3.0 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -gunicorn==23.0.0 -hypothesis==6.45.0 -importlib-metadata==8.5.0 -importlib-resources==6.4.5 -iniconfig==2.1.0 -jsonschema==4.23.0 -jsonschema-specifications==2023.12.1 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pkgutil-resolve-name==1.3.10 -pluggy==1.5.0 -protobuf==3.19.0 -py-cpuinfo==8.0.0 -pytest==8.3.5 -pytest-asyncio==0.21.1 -pytest-benchmark==4.0.0 -pytest-cov==5.0.0 -pytest-cpp==2.6.0 -pytest-mock==3.14.1 -pytest-randomly==3.15.0 -referencing==0.35.1 -rpds-py==0.20.1 -sortedcontainers==2.4.0 -tomli==2.3.0 -typing-extensions==4.13.2 -uwsgi==2.0.31 -zipp==3.20.2 -zstandard==0.23.0 From 3e9c7df6fd281a0f542e5c8ed3a3c14eda9d5656 Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Wed, 5 Nov 2025 14:08:45 -0500 Subject: [PATCH 24/42] feat(profiling): dont enable v1 profiler (#15108) ## Description ## Testing ## Risks ## Additional Notes Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- ddtrace/internal/settings/profiling.py | 30 +++----------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/ddtrace/internal/settings/profiling.py b/ddtrace/internal/settings/profiling.py index fab25cd7a8e..49b28158790 100644 --- a/ddtrace/internal/settings/profiling.py +++ b/ddtrace/internal/settings/profiling.py @@ -85,31 +85,6 @@ def _parse_profiling_enabled(raw: str) -> bool: return False -def _parse_v2_enabled(raw: str) -> bool: - if sys.version_info >= (3, 14): - return False - - # Parse the boolean value - raw_lc = raw.lower() - enabled = raw_lc in ("1", "true", "yes", "on") - - # Warn if user explicitly disabled v2 profiler (v1 is deprecated) - if raw_lc in ("false", "0", "no", "off"): - from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning - from ddtrace.vendor.debtcollector import deprecate - - deprecate( - "Setting DD_PROFILING_STACK_V2_ENABLED=false is deprecated", - message="The v1 stack profiler is deprecated and will be removed in a future version. " - "Please migrate to the v2 stack profiler by removing DD_PROFILING_STACK_V2_ENABLED=false " - "or setting it to true.", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - - return enabled - - def _parse_api_timeout_ms(raw: str) -> int: # Check if the deprecated DD_PROFILING_API_TIMEOUT is set (in seconds) deprecated_timeout = os.environ.get("DD_PROFILING_API_TIMEOUT") @@ -308,7 +283,6 @@ class ProfilingConfigStack(DDConfig): _v2_enabled = DDConfig.v( bool, "v2_enabled", - parser=_parse_v2_enabled, # Not yet supported on 3.14 default=sys.version_info < (3, 14), help_type="Boolean", @@ -441,9 +415,11 @@ class ProfilingConfigPytorch(DDConfig): logger.warning("Failed to load stack_v2 module (%s), falling back to v1 stack sampler", msg) telemetry_writer.add_log( TELEMETRY_LOG_LEVEL.ERROR, - "Failed to load stack_v2 module (%s), falling back to v1 stack sampler" % msg, + "Failed to load stack_v2 module (%s), disabling profiling" % msg, ) config.stack.v2_enabled = False + config.stack.enabled = False + config.enabled = False # Enrich tags with git metadata and DD_TAGS config.tags = _enrich_tags(config.tags) From c8f48e7d66f9b65198f63169e31e786dbd96d2e6 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Wed, 5 Nov 2025 19:39:40 +0000 Subject: [PATCH 25/42] chore(di): remove deprecated variable (#15061) Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- ddtrace/internal/settings/dynamic_instrumentation.py | 1 - .../di-remove-deprecated-var-d61cf16b8608c7bd.yaml | 5 +++++ tests/debugging/test_debugger.py | 12 ++++++------ 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/di-remove-deprecated-var-d61cf16b8608c7bd.yaml diff --git a/ddtrace/internal/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py index d5eaff1bf1b..99431165d5a 100644 --- a/ddtrace/internal/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -92,7 +92,6 @@ class DynamicInstrumentationConfig(DDConfig): default=1.0, # seconds help_type="Float", help="Interval in seconds for flushing the dynamic logs upload queue", - deprecations=[("upload.flush_interval", None, "4.0")], ) diagnostics_interval = DDConfig.v( diff --git a/releasenotes/notes/di-remove-deprecated-var-d61cf16b8608c7bd.yaml b/releasenotes/notes/di-remove-deprecated-var-d61cf16b8608c7bd.yaml new file mode 100644 index 00000000000..a6ff93015d5 --- /dev/null +++ b/releasenotes/notes/di-remove-deprecated-var-d61cf16b8608c7bd.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + dynamic instrumentation: removed the deprecated + ``DD_DYNAMIC_INSTRUMENTATION_UPLOAD_FLUSH_INTERVAL`` variable. diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index 54335f7a972..6ab1c33bc83 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -743,7 +743,7 @@ def test_debugger_function_probe_duration(duration): def test_debugger_condition_eval_then_rate_limit(stuff): - with debugger(upload_flush_interval=float("inf")) as d: + with debugger(upload_interval_seconds=float("inf")) as d: d.add_probes( create_snapshot_line_probe( probe_id="foo", @@ -771,7 +771,7 @@ def test_debugger_condition_eval_then_rate_limit(stuff): def test_debugger_condition_eval_error_get_reported_once(stuff): - with debugger(upload_flush_interval=float("inf")) as d: + with debugger(upload_interval_seconds=float("inf")) as d: d.add_probes( create_snapshot_line_probe( probe_id="foo", @@ -889,7 +889,7 @@ def __init__(self, age, name): def test_debugger_log_line_probe_generate_messages(stuff): - with debugger(upload_flush_interval=float("inf")) as d: + with debugger(upload_interval_seconds=float("inf")) as d: d.add_probes( create_log_line_probe( probe_id="foo", @@ -1073,7 +1073,7 @@ def test_debugger_function_probe_ordering(self): def test_debugger_modified_probe(stuff): - with debugger(upload_flush_interval=float("inf")) as d: + with debugger(upload_interval_seconds=float("inf")) as d: d.add_probes( create_log_line_probe( probe_id="foo", @@ -1131,7 +1131,7 @@ def test_debugger_continue_wrapping_after_first_failure(): def test_debugger_redacted_identifiers(): import tests.submod.stuff as stuff - with debugger(upload_flush_interval=float("inf")) as d: + with debugger(upload_interval_seconds=float("inf")) as d: d.add_probes( create_snapshot_line_probe( probe_id="foo", @@ -1230,7 +1230,7 @@ def test_debugger_redacted_identifiers(): def test_debugger_redaction_excluded_identifiers(): import tests.submod.stuff as stuff - with debugger(upload_flush_interval=float("inf"), redaction_excluded_identifiers=frozenset(["token"])) as d: + with debugger(upload_interval_seconds=float("inf"), redaction_excluded_identifiers=frozenset(["token"])) as d: d.add_probes( create_snapshot_line_probe( probe_id="foo", From 1f48e2c88d6fba364313880432aeb46834718437 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 6 Nov 2025 14:42:09 -0500 Subject: [PATCH 26/42] chore(tracing): move meta/metrics type checking to the encoder (#14982) ## Description The benefit here is we can do a first pass on meta/metrics to filter out bad values before we try to pack them, this way we can throw out individual bad values instead of throwing out the whole span/trace. ## Testing ## Risks This will have a negative performance impact for now until #14943 can be merged. The impact on the flask/django benchmarks is pretty low, so while this is showing a +20% increase locally for the encoder benchmarks, the real world impact on the flask/django microbenchmarks is <1% so even without #14943 it might be "ok" to introduce. Right now we do type checking in `set_tag`/`set_metric`. With this PR we will do the type checking there **and** in the encoder. Once #14943 merges we'll remove the type checking from `set_tag`/`set_metric` and only have it in the encoder. It should have the benefit of using the C-API for the type checking (faster), and we don't do type checking on spans that aren't going to get encoded. Note: the end result could be a tad slower on average still because we are keeping `for k, v in meta.items():` but then we are adding a `for k, v in filtered_meta_list:` so the additional iteration could bite us. It could be worth trying to find a way to pack the meta/metrics without first knowing the end size. ## Additional Notes --------- Co-authored-by: Emmett Butler --- ddtrace/internal/_encoding.pyx | 156 +++++++++++------- .../integration/test_integration_snapshots.py | 35 ++-- tests/tracer/test_encoders.py | 43 ++++- 3 files changed, 162 insertions(+), 72 deletions(-) diff --git a/ddtrace/internal/_encoding.pyx b/ddtrace/internal/_encoding.pyx index 63cf4348150..e8391ab6506 100644 --- a/ddtrace/internal/_encoding.pyx +++ b/ddtrace/internal/_encoding.pyx @@ -22,14 +22,17 @@ from ..constants import _ORIGIN_KEY as ORIGIN_KEY from .constants import SPAN_LINKS_KEY from .constants import SPAN_EVENTS_KEY from .constants import MAX_UINT_64BITS +from .logger import get_logger from .._trace._limits import MAX_SPAN_META_VALUE_LEN from .._trace._limits import TRUNCATED_SPAN_ATTRIBUTE_LEN -from ..internal.settings._agent import config as agent_config +from .settings._agent import config as agent_config DEF MSGPACK_ARRAY_LENGTH_PREFIX_SIZE = 5 DEF MSGPACK_STRING_TABLE_LENGTH_PREFIX_SIZE = 6 +cdef object log = get_logger(__name__) + cdef extern from "Python.h": const char* PyUnicode_AsUTF8(object o) @@ -699,63 +702,85 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): return ret return ret - cdef inline int _pack_meta(self, object meta, char *dd_origin, str span_events) except? -1: + cdef inline int _pack_meta( + self, object meta, char *dd_origin, str span_events, uint64_t span_id, + ) except? -1: cdef Py_ssize_t L cdef int ret cdef dict d + cdef list m - if PyDict_CheckExact(meta): - d = meta - L = len(d) + (dd_origin is not NULL) + (len(span_events) > 0) - if L > ITEM_LIMIT: - raise ValueError("dict is too large") + if not PyDict_CheckExact(meta): + raise TypeError("Unhandled meta type: %r" % type(meta)) - ret = msgpack_pack_map(&self.pk, L) - if ret == 0: - for k, v in d.items(): - ret = pack_text(&self.pk, k) - if ret != 0: - break - ret = pack_text(&self.pk, v) - if ret != 0: - break - if dd_origin is not NULL: - ret = pack_bytes(&self.pk, _ORIGIN_KEY, _ORIGIN_KEY_LEN) - if ret == 0: - ret = pack_bytes(&self.pk, dd_origin, strlen(dd_origin)) - if ret != 0: - return ret - if span_events: - ret = pack_text(&self.pk, SPAN_EVENTS_KEY) - if ret == 0: - ret = pack_text(&self.pk, span_events) - return ret + d = meta - raise TypeError("Unhandled meta type: %r" % type(meta)) + # Filter meta to only str/bytes values + m = [] + for k, v in d.items(): + if PyUnicode_Check(v) or PyBytesLike_Check(v): + m.append((k, v)) + else: + log.warning("[span ID %d] Meta key %r has non-string value %r, skipping", span_id, k, v) + + L = len(m) + (dd_origin is not NULL) + (len(span_events) > 0) + if L > ITEM_LIMIT: + raise ValueError("dict is too large") + + ret = msgpack_pack_map(&self.pk, L) + if ret == 0: + for k, v in m: + ret = pack_text(&self.pk, k) + if ret != 0: + break + ret = pack_text(&self.pk, v) + if ret != 0: + break + if dd_origin is not NULL: + ret = pack_bytes(&self.pk, _ORIGIN_KEY, _ORIGIN_KEY_LEN) + if ret == 0: + ret = pack_bytes(&self.pk, dd_origin, strlen(dd_origin)) + if ret != 0: + return ret + if span_events: + ret = pack_text(&self.pk, SPAN_EVENTS_KEY) + if ret == 0: + ret = pack_text(&self.pk, span_events) + return ret - cdef inline int _pack_metrics(self, object metrics) except? -1: + cdef inline int _pack_metrics(self, object metrics, uint64_t span_id) except? -1: cdef Py_ssize_t L cdef int ret cdef dict d + cdef list m - if PyDict_CheckExact(metrics): - d = metrics - L = len(d) - if L > ITEM_LIMIT: - raise ValueError("dict is too large") + if not PyDict_CheckExact(metrics): + raise TypeError("Unhandled metrics type: %r" % type(metrics)) - ret = msgpack_pack_map(&self.pk, L) - if ret == 0: - for k, v in d.items(): - ret = pack_text(&self.pk, k) - if ret != 0: - break - ret = pack_number(&self.pk, v) - if ret != 0: - break - return ret + d = metrics + m = [] + + # Filter metrics to only number values + for k, v in d.items(): + if PyLong_Check(v) or PyFloat_Check(v): + m.append((k, v)) + else: + log.warning("[span ID %d] Metric key %r has non-numeric value %r, skipping", span_id, k, v) - raise TypeError("Unhandled metrics type: %r" % type(metrics)) + L = len(m) + if L > ITEM_LIMIT: + raise ValueError("dict is too large") + + ret = msgpack_pack_map(&self.pk, L) + if ret == 0: + for k, v in m: + ret = pack_text(&self.pk, k) + if ret != 0: + break + ret = pack_number(&self.pk, v) + if ret != 0: + break + return ret cdef int pack_span(self, object span, unsigned long long trace_id_64bits, void *dd_origin) except? -1: cdef int ret @@ -763,6 +788,7 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): cdef int has_span_type cdef int has_meta cdef int has_metrics + cdef uint64_t span_id = span.span_id has_error = (span.error != 0) has_span_type = (span.span_type is not None) @@ -803,7 +829,7 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): ret = pack_bytes(&self.pk, b"span_id", 7) if ret != 0: return ret - ret = pack_number(&self.pk, span.span_id) + ret = pack_number(&self.pk, span_id) if ret != 0: return ret @@ -882,7 +908,7 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): span_events = "" if has_span_events and not self.top_level_span_event_encoding: span_events = json_dumps([vars(event)() for event in span._events]) - ret = self._pack_meta(span._meta, dd_origin, span_events) + ret = self._pack_meta(span._meta, dd_origin, span_events, span_id) if ret != 0: return ret @@ -909,7 +935,8 @@ cdef class MsgpackEncoderV04(MsgpackEncoderBase): ret = pack_bytes(&self.pk, b"metrics", 7) if ret != 0: return ret - ret = self._pack_metrics(span._metrics) + + ret = self._pack_metrics(span._metrics, span_id) if ret != 0: return ret @@ -1035,6 +1062,8 @@ cdef class MsgpackEncoderV05(MsgpackEncoderBase): cdef int pack_span(self, object span, unsigned long long trace_id_64bits, void *dd_origin) except? -1: cdef int ret + cdef list meta, metrics + cdef uint64_t span_id = span.span_id ret = msgpack_pack_array(&self.pk, 12) if ret != 0: @@ -1054,8 +1083,7 @@ cdef class MsgpackEncoderV05(MsgpackEncoderBase): if ret != 0: return ret - _ = span.span_id - ret = msgpack_pack_uint64(&self.pk, _ if _ is not None else 0) + ret = msgpack_pack_uint64(&self.pk, span_id if span_id is not None else 0) if ret != 0: return ret @@ -1089,14 +1117,22 @@ cdef class MsgpackEncoderV05(MsgpackEncoderBase): if span._events: span_events = json_dumps([vars(event)() for event in span._events]) + # Filter meta to only str/bytes values + meta = [] + for k, v in span._meta.items(): + if PyUnicode_Check(v) or PyBytesLike_Check(v): + meta.append((k, v)) + else: + log.warning("[span ID %d] Meta key %r has non-string value %r, skipping", span_id, k, v) + ret = msgpack_pack_map( &self.pk, - len(span._meta) + (dd_origin is not NULL) + (len(span_links) > 0) + (len(span_events) > 0) + len(meta) + (dd_origin is not NULL) + (len(span_links) > 0) + (len(span_events) > 0) ) if ret != 0: return ret - if span._meta: - for k, v in span._meta.items(): + if meta: + for k, v in meta: ret = self._pack_string(k) if ret != 0: return ret @@ -1125,11 +1161,19 @@ cdef class MsgpackEncoderV05(MsgpackEncoderBase): if ret != 0: return ret - ret = msgpack_pack_map(&self.pk, len(span._metrics)) + # Filter metrics to only number values + metrics = [] + for k, v in span._metrics.items(): + if PyLong_Check(v) or PyFloat_Check(v): + metrics.append((k, v)) + else: + log.warning("[span ID %d] Metric key %r has non-numeric value %r, skipping", span_id, k, v) + + ret = msgpack_pack_map(&self.pk, len(metrics)) if ret != 0: return ret - if span._metrics: - for k, v in span._metrics.items(): + if metrics: + for k, v in metrics: ret = self._pack_string(k) if ret != 0: return ret diff --git a/tests/integration/test_integration_snapshots.py b/tests/integration/test_integration_snapshots.py index 8bb70cf70a6..8785f814d78 100644 --- a/tests/integration/test_integration_snapshots.py +++ b/tests/integration/test_integration_snapshots.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import logging import os import mock @@ -216,42 +217,54 @@ def test_wrong_span_name_type_not_sent(): ({"env": "my-env", "tag1": "some_str_1", "tag2": "some_str_2", "tag3": [1, 2, 3]}), ({"env": "test-env", b"tag1": {"wrong_type": True}, b"tag2": "some_str_2", b"tag3": "some_str_3"}), ({"env": "my-test-env", "😐": "some_str_1", b"tag2": "some_str_2", "unicode": 12345}), + ({"env": set([1, 2, 3])}), + ({"env": None}), + ({"env": True}), + ({"env": 1.0}), ], ) @pytest.mark.parametrize("encoding", ["v0.4", "v0.5"]) def test_trace_with_wrong_meta_types_not_sent(encoding, meta, monkeypatch): """Wrong meta types should raise TypeErrors during encoding and fail to send to the agent.""" with override_global_config(dict(_trace_api=encoding)): - with mock.patch("ddtrace._trace.span.log") as log: + logger = logging.getLogger("ddtrace.internal._encoding") + with mock.patch.object(logger, "warning") as log_warning: with tracer.trace("root") as root: root._meta = meta for _ in range(299): with tracer.trace("child") as child: child._meta = meta - log.exception.assert_called_once_with("error closing trace") + + assert log_warning.call_count == 300 + log_warning.assert_called_with( + "[span ID %d] Meta key %r has non-string value %r, skipping", mock.ANY, mock.ANY, mock.ANY + ) @pytest.mark.parametrize( - "metrics", + "metrics,expected_warning_count", [ - ({"num1": 12345, "num2": 53421, "num3": 1, "num4": "not-a-number"}), - ({b"num1": 123.45, b"num2": [1, 2, 3], b"num3": 11.0, b"num4": 1.20}), - ({"😐": "123.45", b"num2": "1", "num3": {"is_number": False}, "num4": "12345"}), + ({"num1": 12345, "num2": 53421, "num3": 1, "num4": "not-a-number"}, 300), + ({b"num1": 123.45, b"num2": [1, 2, 3], b"num3": 11.0, b"num4": 1.20}, 300), + ({"😐": "123.45", b"num2": "1", "num3": {"is_number": False}, "num4": "12345"}, 1200), ], ) @pytest.mark.parametrize("encoding", ["v0.4", "v0.5"]) -@snapshot() -@pytest.mark.xfail -def test_trace_with_wrong_metrics_types_not_sent(encoding, metrics, monkeypatch): +def test_trace_with_wrong_metrics_types_not_sent(encoding, metrics, expected_warning_count): """Wrong metric types should raise TypeErrors during encoding and fail to send to the agent.""" with override_global_config(dict(_trace_api=encoding)): - with mock.patch("ddtrace._trace.span.log") as log: + logger = logging.getLogger("ddtrace.internal._encoding") + with mock.patch.object(logger, "warning") as log_warning: with tracer.trace("root") as root: root._metrics = metrics for _ in range(299): with tracer.trace("child") as child: child._metrics = metrics - log.exception.assert_called_once_with("error closing trace") + + assert log_warning.call_count == expected_warning_count + log_warning.assert_called_with( + "[span ID %d] Metric key %r has non-numeric value %r, skipping", mock.ANY, mock.ANY, mock.ANY + ) @pytest.mark.subprocess() diff --git a/tests/tracer/test_encoders.py b/tests/tracer/test_encoders.py index 079b3260c32..42d620069e3 100644 --- a/tests/tracer/test_encoders.py +++ b/tests/tracer/test_encoders.py @@ -4,6 +4,8 @@ import random import string import threading +from typing import Any +from typing import Dict from unittest import TestCase from hypothesis import given @@ -937,13 +939,9 @@ def _value(): {"start_ns": []}, {"duration_ns": {}}, {"span_type": 100}, - {"_meta": {"num": 100}}, - # Validating behavior with a context manager is a customer regression - {"_meta": {"key": _value()}}, - {"_metrics": {"key": "value"}}, ], ) -def test_encoding_invalid_data(data): +def test_encoding_invalid_data_raises(data): encoder = MsgpackEncoderV04(1 << 20, 1 << 20) span = Span(name="test") @@ -959,6 +957,41 @@ def test_encoding_invalid_data(data): assert (not encoded_traces) or (encoded_traces[0][0] is None) +@pytest.mark.parametrize( + "meta,metrics", + [ + ({"num": 100}, {}), + # Validating behavior with a context manager is a customer regression + ({"key": _value()}, {}), + ({}, {"key": "value"}), + ], +) +def test_encoding_invalid_data_ok(meta: Dict[str, Any], metrics: Dict[str, Any]): + """Encoding invalid meta/metrics data should not raise an exception""" + encoder = MsgpackEncoderV04(1 << 20, 1 << 20) + + span = Span(name="test") + span._meta = meta # type: ignore + span._metrics = metrics # type: ignore + + trace = [span] + encoder.put(trace) + + encoded_payloads = encoder.encode() + assert len(encoded_payloads) == 1 + + # Ensure it can be decoded properly + traces = msgpack.unpackb(encoded_payloads[0][0], raw=False) + assert len(traces) == 1 + assert len(traces[0]) == 1 + + # We didn't encode the invalid meta/metrics + for key in meta.keys(): + assert key not in traces[0][0]["meta"] + for key in metrics.keys(): + assert key not in traces[0][0]["metrics"] + + @allencodings def test_custom_msgpack_encode_thread_safe(encoding): class TracingThread(threading.Thread): From 43226f9500e9f634c74a6b4c5981b6e0704ca11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20De=20Ara=C3=BAjo?= Date: Fri, 7 Nov 2025 15:04:30 +0000 Subject: [PATCH 27/42] chore(ci_visibility): remove pytest_bdd and pytest_benchmark entry points (#15160) ## Description The `pytest_bdd` and `pytest_benchmark` integrations were deprecated, as their functionality is now provided by the `pytest` integration directly. This PR removes their pytest entry points. ## Testing Unit tests. ## Risks None. ## Additional Notes None. --- pyproject.toml | 2 -- ...emove-deprecated-pytest-entrypoints-5cb519a8a0858c9b.yaml | 5 +++++ tests/ci_visibility/test_cli.py | 2 -- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/ci_visibility-update-remove-deprecated-pytest-entrypoints-5cb519a8a0858c9b.yaml diff --git a/pyproject.toml b/pyproject.toml index 5ce0cfbe052..104d94a67de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,8 +64,6 @@ ddcontextvars_context = "ddtrace.internal.opentelemetry.context:DDRuntimeContext [project.entry-points.pytest11] ddtrace = "ddtrace.contrib.internal.pytest.plugin" -"ddtrace.pytest_bdd" = "ddtrace.contrib.internal.pytest_bdd.plugin" -"ddtrace.pytest_benchmark" = "ddtrace.contrib.internal.pytest_benchmark.plugin" [project.entry-points.'ddtrace.products'] "apm-tracing-rc" = "ddtrace.internal.remoteconfig.products.apm_tracing" diff --git a/releasenotes/notes/ci_visibility-update-remove-deprecated-pytest-entrypoints-5cb519a8a0858c9b.yaml b/releasenotes/notes/ci_visibility-update-remove-deprecated-pytest-entrypoints-5cb519a8a0858c9b.yaml new file mode 100644 index 00000000000..16b3fe1f872 --- /dev/null +++ b/releasenotes/notes/ci_visibility-update-remove-deprecated-pytest-entrypoints-5cb519a8a0858c9b.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + CI Visibility: Removed deprecated entry points for the ``pytest_benchmark`` and ``pytest_bdd`` integrations. These + plugins are now supported by the regular ``pytest`` integration. diff --git a/tests/ci_visibility/test_cli.py b/tests/ci_visibility/test_cli.py index a34f0f389be..9b879873656 100644 --- a/tests/ci_visibility/test_cli.py +++ b/tests/ci_visibility/test_cli.py @@ -31,8 +31,6 @@ def test_thing(): ], ["pytest", "-p", "no:ddtrace"], ["pytest", "-p", "ddtrace"], - ["pytest", "-p", "ddtrace", "-p", "ddtrace.pytest_bdd", "-p", "ddtrace.pytest_benchmark"], - ["pytest", "-p", "no:ddtrace", "-p", "no:ddtrace.pytest_bdd", "-p", "no:ddtrace.pytest_benchmark"], ] for command_args in commands_to_test: From 04a5ed8af38346b6bebb2e80529f3466406012e8 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 7 Nov 2025 10:25:39 -0500 Subject: [PATCH 28/42] chore(tracing): simplify Span tag/metric API typing (#14943) ## Description **4.0** This updates all tag and metric methods to explicitly be `dict[str, str]` and `dict[str, int float]` only. This removes the `value: Any` from `set_tag` which allows us to remove a lot of checks for things like if it is a numeric type or not. ## Testing ## Risks ## Additional Notes We needed to bump the `otelspan-add-tags` benchmark SLO because we also needed to add additional processing in the otel span `set_attribute` to delegate types between `set_tag` and `set_metric`. --------- Co-authored-by: Emmett Butler Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> --- ...-runner.microbenchmarks.fail-on-breach.yml | 2 +- ddtrace/_trace/context.py | 14 +++---- ddtrace/_trace/span.py | 40 ++++++++----------- ddtrace/_trace/types.py | 7 ---- .../utils_botocore/aws_payload_tagging.py | 2 +- ddtrace/debugging/_signal/tracing.py | 2 +- ddtrace/internal/ci_visibility/filters.py | 2 +- ddtrace/internal/ci_visibility/utils.py | 6 +-- ddtrace/internal/opentelemetry/span.py | 24 +++++++++-- ddtrace/internal/opentelemetry/trace.py | 1 - ddtrace/propagation/http.py | 20 +++------- ...icit-span-tag-typing-99abb4d3ec065a55.yaml | 18 +++++++++ .../integration/test_integration_snapshots.py | 15 ------- ...pan_with_large_bytes_attributes[v0.4].json | 25 ------------ ...pan_with_large_bytes_attributes[v0.5].json | 25 ------------ ...y.test_span.test_otel_span_attributes.json | 1 + tests/tracer/test_propagation.py | 21 ---------- tests/tracer/test_span.py | 17 -------- 18 files changed, 74 insertions(+), 168 deletions(-) create mode 100644 releasenotes/notes/explicit-span-tag-typing-99abb4d3ec065a55.yaml delete mode 100644 tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.4].json delete mode 100644 tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.5].json diff --git a/.gitlab/benchmarks/bp-runner.microbenchmarks.fail-on-breach.yml b/.gitlab/benchmarks/bp-runner.microbenchmarks.fail-on-breach.yml index b593d9b043a..cfb84263620 100644 --- a/.gitlab/benchmarks/bp-runner.microbenchmarks.fail-on-breach.yml +++ b/.gitlab/benchmarks/bp-runner.microbenchmarks.fail-on-breach.yml @@ -802,7 +802,7 @@ experiments: - max_rss_usage < 675.00 MB - name: otelspan-add-tags thresholds: - - execution_time < 314.00 ms + - execution_time < 344.80 ms - max_rss_usage < 675.00 MB - name: otelspan-get-context thresholds: diff --git a/ddtrace/_trace/context.py b/ddtrace/_trace/context.py index f4362755977..7b07b5bfb62 100644 --- a/ddtrace/_trace/context.py +++ b/ddtrace/_trace/context.py @@ -9,8 +9,6 @@ from typing import Tuple from ddtrace._trace._span_link import SpanLink -from ddtrace._trace.types import _MetaDictType -from ddtrace._trace.types import _MetricDictType from ddtrace.constants import _ORIGIN_KEY from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import _USER_ID_KEY @@ -25,8 +23,8 @@ _ContextState = Tuple[ Optional[int], # trace_id Optional[int], # span_id - _MetaDictType, # _meta - _MetricDictType, # _metrics + Dict[str, str], # _meta + Dict[str, NumericType], # _metrics List[SpanLink], # span_links Dict[str, Any], # baggage bool, # is_remote @@ -63,15 +61,15 @@ def __init__( span_id: Optional[int] = None, dd_origin: Optional[str] = None, sampling_priority: Optional[float] = None, - meta: Optional[_MetaDictType] = None, - metrics: Optional[_MetricDictType] = None, + meta: Optional[Dict[str, str]] = None, + metrics: Optional[Dict[str, NumericType]] = None, lock: Optional[threading.RLock] = None, span_links: Optional[List[SpanLink]] = None, baggage: Optional[Dict[str, Any]] = None, is_remote: bool = True, ): - self._meta: _MetaDictType = meta if meta is not None else {} - self._metrics: _MetricDictType = metrics if metrics is not None else {} + self._meta: Dict[str, str] = meta if meta is not None else {} + self._metrics: Dict[str, NumericType] = metrics if metrics is not None else {} self._baggage: Dict[str, Any] = baggage if baggage is not None else {} self.trace_id: Optional[int] = trace_id diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index 829ad8944d7..f988c973c87 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -20,9 +20,6 @@ from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace.context import Context from ddtrace._trace.types import _AttributeValueType -from ddtrace._trace.types import _MetaDictType -from ddtrace._trace.types import _MetricDictType -from ddtrace._trace.types import _TagNameType from ddtrace.constants import _SAMPLING_AGENT_DECISION from ddtrace.constants import _SAMPLING_LIMIT_DECISION from ddtrace.constants import _SAMPLING_RULE_DECISION @@ -189,9 +186,9 @@ def __init__( self.span_type = span_type self._span_api = span_api - self._meta: _MetaDictType = {} + self._meta: Dict[str, str] = {} self.error = 0 - self._metrics: _MetricDictType = {} + self._metrics: Dict[str, NumericType] = {} self._meta_struct: Dict[str, Dict[str, Any]] = {} @@ -333,7 +330,7 @@ def _set_sampling_decision_maker( self.context._meta[SAMPLING_DECISION_TRACE_TAG_KEY] = value return value - def set_tag(self, key: _TagNameType, value: Any = None) -> None: + def set_tag(self, key: str, value: Optional[str] = None) -> None: """Set a tag key/value pair on the span. Keys must be strings, values must be ``str``-able. @@ -343,11 +340,6 @@ def set_tag(self, key: _TagNameType, value: Any = None) -> None: :param value: Value to assign for the tag :type value: ``str``-able value """ - - if not isinstance(key, str): - log.warning("Ignoring tag pair %s:%s. Key must be a string.", key, value) - return - # Special case, force `http.status_code` as a string # DEV: `http.status_code` *has* to be in `meta` for metrics # calculated in the trace agent @@ -362,14 +354,14 @@ def set_tag(self, key: _TagNameType, value: Any = None) -> None: INT_TYPES = (net.TARGET_PORT,) if key in INT_TYPES and not val_is_an_int: try: - value = int(value) + value = int(value) # type: ignore val_is_an_int = True except (ValueError, TypeError): pass # Set integers that are less than equal to 2^53 as metrics - if value is not None and val_is_an_int and abs(value) <= 2**53: - self.set_metric(key, value) + if value is not None and val_is_an_int and abs(value) <= 2**53: # type: ignore + self.set_metric(key, value) # type: ignore return # All floats should be set as a metric @@ -393,8 +385,8 @@ def set_tag(self, key: _TagNameType, value: Any = None) -> None: # Set `_dd.measured` tag as a metric # DEV: `set_metric` will ensure it is an integer 0 or 1 if value is None: - value = 1 - self.set_metric(key, value) + value = 1 # type: ignore + self.set_metric(key, value) # type: ignore return try: @@ -415,7 +407,7 @@ def _get_struct_tag(self, key: str) -> Optional[Dict[str, Any]]: """Return the given struct or None if it doesn't exist.""" return self._meta_struct.get(key, None) - def _set_tag_str(self, key: _TagNameType, value: Text) -> None: + def _set_tag_str(self, key: str, value: str) -> None: """Set a value for a tag. Values are coerced to unicode in Python 2 and str in Python 3, with decoding errors in conversion being replaced with U+FFFD. @@ -427,15 +419,15 @@ def _set_tag_str(self, key: _TagNameType, value: Text) -> None: raise e log.warning("Failed to set text tag '%s'", key, exc_info=True) - def get_tag(self, key: _TagNameType) -> Optional[Text]: + def get_tag(self, key: str) -> Optional[str]: """Return the given tag or None if it doesn't exist.""" return self._meta.get(key, None) - def get_tags(self) -> _MetaDictType: + def get_tags(self) -> Dict[str, str]: """Return all tags.""" return self._meta.copy() - def set_tags(self, tags: Dict[_TagNameType, Any]) -> None: + def set_tags(self, tags: Dict[str, str]) -> None: """Set a dictionary of tags on the given span. Keys and values must be strings (or stringable) """ @@ -443,7 +435,7 @@ def set_tags(self, tags: Dict[_TagNameType, Any]) -> None: for k, v in iter(tags.items()): self.set_tag(k, v) - def set_metric(self, key: _TagNameType, value: NumericType) -> None: + def set_metric(self, key: str, value: NumericType) -> None: """This method sets a numeric tag value for the given key.""" # Enforce a specific constant for `_dd.measured` if key == _SPAN_MEASURED_KEY: @@ -473,7 +465,7 @@ def set_metric(self, key: _TagNameType, value: NumericType) -> None: del self._meta[key] self._metrics[key] = value - def set_metrics(self, metrics: _MetricDictType) -> None: + def set_metrics(self, metrics: Dict[str, NumericType]) -> None: """Set a dictionary of metrics on the given span. Keys must be must be strings (or stringable). Values must be numeric. """ @@ -481,7 +473,7 @@ def set_metrics(self, metrics: _MetricDictType) -> None: for k, v in metrics.items(): self.set_metric(k, v) - def get_metric(self, key: _TagNameType) -> Optional[NumericType]: + def get_metric(self, key: str) -> Optional[NumericType]: """Return the given metric or None if it doesn't exist.""" return self._metrics.get(key) @@ -494,7 +486,7 @@ def _add_on_finish_exception_callback(self, callback: Callable[["Span"], None]): """Add an errortracking related callback to the on_finish_callback array""" self._on_finish_callbacks.insert(0, callback) - def get_metrics(self) -> _MetricDictType: + def get_metrics(self) -> Dict[str, NumericType]: """Return all metrics.""" return self._metrics.copy() diff --git a/ddtrace/_trace/types.py b/ddtrace/_trace/types.py index f021420acde..21b2fc5e7af 100644 --- a/ddtrace/_trace/types.py +++ b/ddtrace/_trace/types.py @@ -1,14 +1,7 @@ -from typing import Dict from typing import Sequence -from typing import Text from typing import Union -from ddtrace.internal.compat import NumericType - -_TagNameType = Union[Text, bytes] -_MetaDictType = Dict[_TagNameType, Text] -_MetricDictType = Dict[_TagNameType, NumericType] _AttributeValueType = Union[ str, bool, diff --git a/ddtrace/_trace/utils_botocore/aws_payload_tagging.py b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py index 12aeaf94346..af8cee7a833 100644 --- a/ddtrace/_trace/utils_botocore/aws_payload_tagging.py +++ b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py @@ -198,7 +198,7 @@ def _tag_object(self, span: Span, key: str, obj: Any, depth: int = 0) -> None: """ # if we've hit the maximum allowed tags, mark the expansion as incomplete if self.current_tag_count >= config.botocore.get("payload_tagging_max_tags"): - span.set_tag(self._INCOMPLETE_TAG, True) + span.set_tag(self._INCOMPLETE_TAG, "True") return if obj is None: self.current_tag_count += 1 diff --git a/ddtrace/debugging/_signal/tracing.py b/ddtrace/debugging/_signal/tracing.py index e1c85b27746..c3ea22d84b4 100644 --- a/ddtrace/debugging/_signal/tracing.py +++ b/ddtrace/debugging/_signal/tracing.py @@ -50,7 +50,7 @@ def enter(self, scope: t.Mapping[str, t.Any]) -> None: ) span = self._span_cm.__enter__() - span.set_tags(probe.tags) # type: ignore[arg-type] + span.set_tags(probe.tags) span._set_tag_str(PROBE_ID_TAG_NAME, probe.probe_id) span._set_tag_str(_ORIGIN_KEY, "di") diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index 83297787a18..f4b96fbe88e 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -18,7 +18,7 @@ class TraceCiVisibilityFilter(TraceFilter): def __init__(self, tags, service): - # type: (Dict[Union[str, bytes], str], str) -> None + # type: (Dict[str, str], str) -> None self._tags = tags self._service = service diff --git a/ddtrace/internal/ci_visibility/utils.py b/ddtrace/internal/ci_visibility/utils.py index 68aae145a34..25319d65406 100644 --- a/ddtrace/internal/ci_visibility/utils.py +++ b/ddtrace/internal/ci_visibility/utils.py @@ -73,9 +73,9 @@ def _add_start_end_source_file_path_data_to_span( log.debug("Tried to collect source start/end lines for test method %s but an exception was raised", test_name) span._set_tag_str(test.SOURCE_FILE, source_file_path) if start_line: - span.set_tag(test.SOURCE_START, start_line) + span.set_metric(test.SOURCE_START, start_line) if end_line: - span.set_tag(test.SOURCE_END, end_line) + span.set_metric(test.SOURCE_END, end_line) def _add_pct_covered_to_span(coverage_data: dict, span: ddtrace.trace.Span): @@ -86,7 +86,7 @@ def _add_pct_covered_to_span(coverage_data: dict, span: ddtrace.trace.Span): if not isinstance(lines_pct_value, float): log.warning("Tried to add total covered percentage to session span but the format was unexpected") return - span.set_tag(test.TEST_LINES_PCT, lines_pct_value) + span.set_metric(test.TEST_LINES_PCT, lines_pct_value) def _generate_fully_qualified_test_name(test_module_path: str, test_suite_name: str, test_name: str) -> str: diff --git a/ddtrace/internal/opentelemetry/span.py b/ddtrace/internal/opentelemetry/span.py index 79dd6d9267f..f5a626fb49d 100644 --- a/ddtrace/internal/opentelemetry/span.py +++ b/ddtrace/internal/opentelemetry/span.py @@ -15,6 +15,7 @@ from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND +from ddtrace.internal.compat import ensure_text from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import flatten_key_value from ddtrace.internal.utils.formats import is_sequence @@ -38,11 +39,18 @@ def _ddmap(span, attribute, value): - # type: (DDSpan, str, Union[bytes, NumericType]) -> DDSpan + # type: (DDSpan, str, Union[str, bytes, NumericType]) -> DDSpan if attribute.startswith("meta") or attribute.startswith("metrics"): meta_key = attribute.split("'")[1] if len(attribute.split("'")) == 3 else None if meta_key: - span.set_tag(meta_key, value) + if meta_key == "http.status_code": + if isinstance(value, (int, float)): + value = str(value) + + if isinstance(value, (str, bytes)): + span.set_tag(meta_key, ensure_text(value)) + if isinstance(value, (int, float)): + span.set_metric(meta_key, value) else: setattr(span, attribute, value) return span @@ -182,7 +190,17 @@ def set_attribute(self, key, value): for k, v in flatten_key_value(key, value).items(): self._ddspan.set_tag(k, v) return - self._ddspan.set_tag(key, value) + if key == "http.status_code": + if isinstance(value, (int, float)): + value = str(value) + if isinstance(value, (str, bytes)): + value = ensure_text(value) + self._ddspan.set_tag(key, value) + elif isinstance(value, (int, float)): + self._ddspan.set_metric(key, value) + else: + # TODO: get rid of this usage, `set_tag` only takes str values + self._ddspan.set_tag(key, value) def add_event(self, name, attributes=None, timestamp=None): # type: (str, Optional[Attributes], Optional[int]) -> None diff --git a/ddtrace/internal/opentelemetry/trace.py b/ddtrace/internal/opentelemetry/trace.py index 20a9e86f6e0..28559726ea3 100644 --- a/ddtrace/internal/opentelemetry/trace.py +++ b/ddtrace/internal/opentelemetry/trace.py @@ -30,7 +30,6 @@ from opentelemetry.trace import Link as OtelLink # noqa:F401 from opentelemetry.util.types import AttributeValue as OtelAttributeValue # noqa:F401 - from ddtrace._trace.span import _MetaDictType # noqa:F401 from ddtrace.trace import Tracer as DDTracer # noqa:F401 diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 269e41d86f0..08bacd5451d 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -1,15 +1,12 @@ import itertools import re -from typing import Any # noqa:F401 from typing import Dict # noqa:F401 from typing import FrozenSet # noqa:F401 from typing import List # noqa:F401 from typing import Literal # noqa:F401 from typing import Optional # noqa:F401 -from typing import Text # noqa:F401 from typing import Tuple # noqa:F401 from typing import Union -from typing import cast # noqa:F401 import urllib.parse from ddtrace._trace._span_link import SpanLink @@ -17,7 +14,6 @@ from ddtrace._trace.span import Span # noqa:F401 from ddtrace._trace.span import _get_64_highest_order_bits_as_hex from ddtrace._trace.span import _get_64_lowest_order_bits_as_int -from ddtrace._trace.types import _MetaDictType from ddtrace.appsec._constants import APPSEC from ddtrace.internal import core from ddtrace.internal.settings._config import config @@ -282,11 +278,8 @@ def _inject(span_context, headers): # Only propagate trace tags which means ignoring the _dd.origin tags_to_encode = { - # DEV: Context._meta is a _MetaDictType but we need Dict[str, str] - ensure_text(k): ensure_text(v) - for k, v in span_context._meta.items() - if _DatadogMultiHeader._is_valid_datadog_trace_tag_key(k) - } # type: Dict[Text, Text] + k: v for k, v in span_context._meta.items() if _DatadogMultiHeader._is_valid_datadog_trace_tag_key(k) + } if tags_to_encode: try: @@ -382,10 +375,7 @@ def _extract(headers): span_id=int(parent_span_id) or None, # type: ignore[arg-type] sampling_priority=sampling_priority, # type: ignore[arg-type] dd_origin=origin, - # DEV: This cast is needed because of the type requirements of - # span tags and trace tags which are currently implemented using - # the same type internally (_MetaDictType). - meta=cast(_MetaDictType, meta), + meta=meta, ) except (TypeError, ValueError): log.debug( @@ -827,14 +817,14 @@ def _extract(headers): log.exception("received invalid w3c traceparent: %s ", tp) return None - meta = {W3C_TRACEPARENT_KEY: tp} # type: _MetaDictType + meta = {W3C_TRACEPARENT_KEY: tp} ts = _extract_header_value(_POSSIBLE_HTTP_HEADER_TRACESTATE, headers) return _TraceContext._get_context(trace_id, span_id, trace_flag, ts, meta) @staticmethod def _get_context(trace_id, span_id, trace_flag, ts, meta=None): - # type: (int, int, Literal[0,1], Optional[str], Optional[_MetaDictType]) -> Context + # type: (int, int, Literal[0,1], Optional[str], Optional[Dict[str, str]]) -> Context if meta is None: meta = {} origin = None diff --git a/releasenotes/notes/explicit-span-tag-typing-99abb4d3ec065a55.yaml b/releasenotes/notes/explicit-span-tag-typing-99abb4d3ec065a55.yaml new file mode 100644 index 00000000000..9c4787f6677 --- /dev/null +++ b/releasenotes/notes/explicit-span-tag-typing-99abb4d3ec065a55.yaml @@ -0,0 +1,18 @@ +--- +upgrade: + - | + tracing: ``Span.set_tag`` typing is now ``set_tag(key: str, value: Optional[str] = None) -> None`` + - | + tracing: ``Span.get_tag`` typing is now ``get_tag(key: str) -> Optional[str]`` + - | + tracing: ``Span.set_tags`` typing is now ``set_tags(tags: dict[str, str]) -> None`` + - | + tracing: ``Span.get_tags`` typing is now ``get_tags() -> dict[str, str]`` + - | + tracing: ``Span.set_metric`` typing is now ``set_metric(key: str, value: int | float) -> None`` + - | + tracing: ``Span.get_metric`` typing is now ``get_metric(key: str) -> Optional[int | float]`` + - | + tracing: ``Span.set_metrics`` typing is now ``set_metrics(metrics: Dict[str, int | float]) -> None`` + - | + tracing: ``Span.get_metrics`` typing is now ``get_metrics() -> dict[str, int | float]`` diff --git a/tests/integration/test_integration_snapshots.py b/tests/integration/test_integration_snapshots.py index 8785f814d78..2047386cbe2 100644 --- a/tests/integration/test_integration_snapshots.py +++ b/tests/integration/test_integration_snapshots.py @@ -344,21 +344,6 @@ def test_encode_span_with_large_string_attributes(encoding): span.set_tag(key="c" * 25001, value="d" * 2000) -@pytest.mark.parametrize("encoding", ["v0.4", "v0.5"]) -@pytest.mark.snapshot() -def test_encode_span_with_large_bytes_attributes(encoding): - from ddtrace import tracer - - with override_global_config(dict(_trace_api=encoding)): - name = b"a" * 25000 - resource = b"b" * 25001 - key = b"c" * 25001 - value = b"d" * 2000 - - with tracer.trace(name=name, resource=resource) as span: - span.set_tag(key=key, value=value) - - @pytest.mark.parametrize("encoding", ["v0.4", "v0.5"]) @pytest.mark.snapshot() def test_encode_span_with_large_unicode_string_attributes(encoding): diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.4].json b/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.4].json deleted file mode 100644 index 72421845bff..00000000000 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.4].json +++ /dev/null @@ -1,25 +0,0 @@ -[[ - { - "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "service": "tests.integration", - "resource": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "6827828300000000", - "language": "python", - "runtime-id": "b9add865029f4a57a1e5f2f108dcae5b" - }, - "metrics": { - "_dd.top_level": 1, - "_dd.tracer_kr": 0.19999999999999996, - "_sampling_priority_v1": 1, - "process_id": 36277 - }, - "duration": 109334, - "start": 1747419779274312637 - }]] diff --git a/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.5].json b/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.5].json deleted file mode 100644 index 8d95383c7aa..00000000000 --- a/tests/snapshots/tests.integration.test_integration_snapshots.test_encode_span_with_large_bytes_attributes[v0.5].json +++ /dev/null @@ -1,25 +0,0 @@ -[[ - { - "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "service": "tests.integration", - "resource": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "", - "error": 0, - "meta": { - "_dd.p.dm": "-0", - "_dd.p.tid": "6827828100000000", - "language": "python", - "runtime-id": "b9add865029f4a57a1e5f2f108dcae5b" - }, - "metrics": { - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "process_id": 36277 - }, - "duration": 116833, - "start": 1747419777808787387 - }]] diff --git a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json index 0fff2ecd8c9..8f1f87b1c17 100644 --- a/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json +++ b/tests/snapshots/tests.opentelemetry.test_span.test_otel_span_attributes.json @@ -12,6 +12,7 @@ "_dd.base_service": "tests.opentelemetry", "_dd.p.dm": "-0", "_dd.p.tid": "655529ab00000000", + "bytes_tag": "bstr", "language": "python", "real_string_tag": "rstr", "runtime-id": "e4724609efa84cf58424a8b1ef44b17d", diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index a6ea2de15c2..d9f93c4d490 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -194,27 +194,6 @@ def test_inject_tags_unicode(tracer): # noqa: F811 assert tags == set(["_dd.p.test=unicode"]) -def test_inject_tags_bytes(tracer): # noqa: F811 - """We properly encode when the meta key as long as it is just ascii characters""" - # Context._meta allows str and bytes for keys - # FIXME: W3C does not support byte headers - overrides = { - "_propagation_style_extract": [PROPAGATION_STYLE_DATADOG], - "_propagation_style_inject": [PROPAGATION_STYLE_DATADOG], - } - with override_global_config(overrides): - meta = {"_dd.p.test": b"bytes"} - ctx = Context(trace_id=1234, sampling_priority=2, dd_origin="synthetics", meta=meta) - tracer.context_provider.activate(ctx) - with tracer.trace("global_root_span") as span: - headers = {} - HTTPPropagator.inject(span.context, headers) - - # The ordering is non-deterministic, so compare as a list of tags - tags = set(headers[_HTTP_HEADER_TAGS].split(",")) - assert tags == set(["_dd.p.test=bytes"]) - - def test_inject_tags_unicode_error(tracer): # noqa: F811 """Unicode characters are not allowed""" meta = {"_dd.p.test": "unicode value ☺️"} diff --git a/tests/tracer/test_span.py b/tests/tracer/test_span.py index fcfd753255f..fa3221a6912 100644 --- a/tests/tracer/test_span.py +++ b/tests/tracer/test_span.py @@ -677,23 +677,6 @@ def test_set_tag_measured_change_value(): assert_is_measured(s) -@mock.patch("ddtrace._trace.span.log") -def test_span_key(span_log): - # Span tag keys must be strings - s = Span(name="test.span") - - s.set_tag(123, True) - span_log.warning.assert_called_once_with("Ignoring tag pair %s:%s. Key must be a string.", 123, True) - assert s.get_tag(123) is None - assert s.get_tag("123") is None - - span_log.reset_mock() - - s.set_tag(None, "val") - span_log.warning.assert_called_once_with("Ignoring tag pair %s:%s. Key must be a string.", None, "val") - assert s.get_tag(123.32) is None - - def test_spans_finished(): span = Span(None) assert span.finished is False From 2a565be54ba2baf3b3a00935f4405db3025ebe3a Mon Sep 17 00:00:00 2001 From: Taegyun Kim Date: Fri, 7 Nov 2025 10:32:32 -0500 Subject: [PATCH 29/42] chore(profiling): remove deprecated env var, `DD_PROFILING_API_TIMEOUT` (#15169) ## Description ## Testing ## Risks ## Additional Notes --- ddtrace/internal/settings/profiling.py | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/ddtrace/internal/settings/profiling.py b/ddtrace/internal/settings/profiling.py index 49b28158790..b9dc489dd17 100644 --- a/ddtrace/internal/settings/profiling.py +++ b/ddtrace/internal/settings/profiling.py @@ -85,27 +85,6 @@ def _parse_profiling_enabled(raw: str) -> bool: return False -def _parse_api_timeout_ms(raw: str) -> int: - # Check if the deprecated DD_PROFILING_API_TIMEOUT is set (in seconds) - deprecated_timeout = os.environ.get("DD_PROFILING_API_TIMEOUT") - if deprecated_timeout is not None: - from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning - from ddtrace.vendor.debtcollector import deprecate - - deprecate( - "DD_PROFILING_API_TIMEOUT is deprecated", - message="DD_PROFILING_API_TIMEOUT (in seconds) is deprecated and will be removed in version 4.0.0. " - "Please use DD_PROFILING_API_TIMEOUT_MS (in milliseconds) instead.", - category=DDTraceDeprecationWarning, - removal_version="4.0.0", - ) - # Convert seconds to milliseconds - return int(float(deprecated_timeout) * 1000) - - # Otherwise, use the raw value (in milliseconds) - return int(raw) - - def _update_git_metadata_tags(tags): """ Update profiler tags with git metadata @@ -226,7 +205,6 @@ class ProfilingConfig(DDConfig): api_timeout_ms = DDConfig.v( int, "api_timeout_ms", - parser=_parse_api_timeout_ms, default=10000, help_type="Integer", help="The timeout in milliseconds before dropping events if the HTTP API does not reply", From 067216b77f1bf8adccd07ef6e4e5d4c555427f07 Mon Sep 17 00:00:00 2001 From: vianney Date: Thu, 6 Nov 2025 14:45:02 +0100 Subject: [PATCH 30/42] chore(tests): update system tests (#15144) Update version of the system tests --- .github/workflows/system-tests.yml | 8 ++++---- .gitlab-ci.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 9e25e4a8673..426d8bc7e1d 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f980721788378102f7ae7caef156a53b20feb093' + ref: '12860dc3fa4a474907f18f8313518989c80772ce' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f980721788378102f7ae7caef156a53b20feb093' + ref: '12860dc3fa4a474907f18f8313518989c80772ce' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: 'f980721788378102f7ae7caef156a53b20feb093' + ref: '12860dc3fa4a474907f18f8313518989c80772ce' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: @@ -313,4 +313,4 @@ jobs: run: exit 0 - name: Fails if anything else failed if: needs.parametric.result != 'success' || needs.system-tests.result != 'success' - run: exit 1 \ No newline at end of file + run: exit 1 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22d4b992999..a1b76a1082d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "f980721788378102f7ae7caef156a53b20feb093" + SYSTEM_TESTS_REF: "12860dc3fa4a474907f18f8313518989c80772ce" default: interruptible: true From 6957976d9f50088850ef727ff1335579b5ef4ab1 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Thu, 6 Nov 2025 12:55:32 -0500 Subject: [PATCH 31/42] chore: update and pin library dependencies (#15040) ## Description ## Testing ## Risks ## Additional Notes --- lib-injection/sources/requirements.csv | 12 ++++++------ pyproject.toml | 12 ++++++------ requirements.csv | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib-injection/sources/requirements.csv b/lib-injection/sources/requirements.csv index 530adac941c..cfaaa668df1 100644 --- a/lib-injection/sources/requirements.csv +++ b/lib-injection/sources/requirements.csv @@ -5,9 +5,9 @@ bytecode,>=0.15.1,python_version~='3.12.0' bytecode,>=0.14.0,python_version~='3.11.0' bytecode,>=0.13.0,python_version<'3.11' envier,~=0.6.1, -legacy-cgi,>=2.0.0,python_version>='3.13.0' -opentelemetry-api,>=1, -protobuf,>=3, -wrapt,>=1, -opentracing,>=2.0.0, -opentelemetry-exporter-otlp,>=1.0.0, +legacy-cgi,">=2,<3",python_version>='3.13.0' +opentelemetry-api,">=1,<2", +protobuf,">=3,<7", +wrapt,">=1,<3", +opentracing,">=2,<3", +opentelemetry-exporter-otlp,">=1,<2", diff --git a/pyproject.toml b/pyproject.toml index 104d94a67de..89837cda571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,18 +41,18 @@ dependencies = [ "bytecode>=0.14.0; python_version~='3.11.0'", "bytecode>=0.13.0; python_version<'3.11'", "envier~=0.6.1", - "legacy-cgi>=2.0.0; python_version>='3.13.0'", - "opentelemetry-api>=1", - "protobuf>=3", - "wrapt>=1", + "legacy-cgi>=2,<3; python_version>='3.13.0'", + "opentelemetry-api>=1,<2", + "protobuf>=3,<7", + "wrapt>=1,<3", ] [project.optional-dependencies] opentracing = [ - "opentracing>=2.0.0", + "opentracing>=2,<3", ] opentelemetry = [ - "opentelemetry-exporter-otlp>=1.0.0", + "opentelemetry-exporter-otlp>=1,<2", ] [project.scripts] diff --git a/requirements.csv b/requirements.csv index 530adac941c..cfaaa668df1 100644 --- a/requirements.csv +++ b/requirements.csv @@ -5,9 +5,9 @@ bytecode,>=0.15.1,python_version~='3.12.0' bytecode,>=0.14.0,python_version~='3.11.0' bytecode,>=0.13.0,python_version<'3.11' envier,~=0.6.1, -legacy-cgi,>=2.0.0,python_version>='3.13.0' -opentelemetry-api,>=1, -protobuf,>=3, -wrapt,>=1, -opentracing,>=2.0.0, -opentelemetry-exporter-otlp,>=1.0.0, +legacy-cgi,">=2,<3",python_version>='3.13.0' +opentelemetry-api,">=1,<2", +protobuf,">=3,<7", +wrapt,">=1,<3", +opentracing,">=2,<3", +opentelemetry-exporter-otlp,">=1,<2", From 35da428d4df78e82cbf4250953bddb816c7ea132 Mon Sep 17 00:00:00 2001 From: Quinna Halim Date: Thu, 6 Nov 2025 15:38:51 -0500 Subject: [PATCH 32/42] fix: modify error tracking exceptions (#15148) ## Description For [APMS-17683](https://datadoghq.atlassian.net/browse/APMS-17683) customer was having an issue where their custom exception object was unhashable, resulting in a `TypeError` when trying to use the object as a key. This fix changes so that we store the exception object id, not the exception object. ## Testing ## Risks ## Additional Notes [APMS-17683]: https://datadoghq.atlassian.net/browse/APMS-17683?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- .../_handled_exceptions/collector.py | 16 +++++++----- ...-tracking-exceptions-6be5aa66cb4cd2ef.yaml | 4 +++ tests/errortracking/_test_functions.py | 18 +++++++++++++ .../errortracking/test_handled_exceptions.py | 25 +++++++++++++++++-- 4 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/modify-error-tracking-exceptions-6be5aa66cb4cd2ef.yaml diff --git a/ddtrace/errortracking/_handled_exceptions/collector.py b/ddtrace/errortracking/_handled_exceptions/collector.py index baec2549626..9358b17646b 100644 --- a/ddtrace/errortracking/_handled_exceptions/collector.py +++ b/ddtrace/errortracking/_handled_exceptions/collector.py @@ -21,7 +21,8 @@ def _add_span_events(span: Span) -> None: a span event for every handled exceptions, we store them in the span and add them when the span finishes. """ - span_exc_events = list(HandledExceptionCollector.get_exception_events(span.span_id).values()) + exception_data = HandledExceptionCollector.get_exception_events(span.span_id).values() + span_exc_events = [event for _exc, event in exception_data] if span_exc_events: span._set_tag_str(SPAN_EVENTS_HAS_EXCEPTION, "true") span._events.extend(span_exc_events) @@ -30,13 +31,14 @@ def _add_span_events(span: Span) -> None: def _on_span_exception(span, _exc_msg, exc_val, _exc_tb): exception_events = HandledExceptionCollector.get_exception_events(span.span_id) - if exception_events and exc_val in exception_events: - del exception_events[exc_val] + exc_id = id(exc_val) + if exception_events and exc_id in exception_events: + del exception_events[exc_id] class HandledExceptionCollector(Service): _instance: t.Optional["HandledExceptionCollector"] = None - _span_exception_events: t.Dict[int, t.Dict[Exception, SpanEvent]] = {} + _span_exception_events: t.Dict[int, t.Dict[int, t.Tuple[Exception, SpanEvent]]] = {} def __init__(self) -> None: super(HandledExceptionCollector, self).__init__() @@ -111,8 +113,10 @@ def capture_exception_event(cls, span: Span, exc: Exception, event: SpanEvent): events_dict = cls._span_exception_events.setdefault(span_id, {}) if not events_dict: span._add_on_finish_exception_callback(_add_span_events) - if exc in events_dict or len(events_dict) < COLLECTOR_MAX_SIZE_PER_SPAN: - events_dict[exc] = event + exc_id = id(exc) + if exc_id in events_dict or len(events_dict) < COLLECTOR_MAX_SIZE_PER_SPAN: + # Store both exception and event to keep exception alive and prevent ID reuse + events_dict[exc_id] = (exc, event) @classmethod def get_exception_events(cls, span_id: int): diff --git a/releasenotes/notes/modify-error-tracking-exceptions-6be5aa66cb4cd2ef.yaml b/releasenotes/notes/modify-error-tracking-exceptions-6be5aa66cb4cd2ef.yaml new file mode 100644 index 00000000000..ec9b90a0884 --- /dev/null +++ b/releasenotes/notes/modify-error-tracking-exceptions-6be5aa66cb4cd2ef.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + Error Tracking: Modifies the way exception events are stored such that the exception id is stored instead of the exception object, to prevent TypeErrors with custom exception objects. diff --git a/tests/errortracking/_test_functions.py b/tests/errortracking/_test_functions.py index 8ddcc53d61f..de800ab3fa9 100644 --- a/tests/errortracking/_test_functions.py +++ b/tests/errortracking/_test_functions.py @@ -1,6 +1,24 @@ import asyncio +class UnhashableException(Exception): + def __init__(self, message, mutable_data): + super().__init__(message) + self.mutable_data = mutable_data + + def __eq__(self, other): + # This makes the exception unhashable if __hash__ is not defined + return isinstance(other, UnhashableException) and str(self) == str(other) + + +def test_unhashable_exception_f(value): + try: + raise UnhashableException("unhashable error", {"key": "value"}) + except UnhashableException: + value = 10 + return value + + def test_basic_try_except_f(value): try: raise ValueError("auto caught error") diff --git a/tests/errortracking/test_handled_exceptions.py b/tests/errortracking/test_handled_exceptions.py index ad32b447bab..c3d9f4081c3 100644 --- a/tests/errortracking/test_handled_exceptions.py +++ b/tests/errortracking/test_handled_exceptions.py @@ -91,7 +91,9 @@ def test_handle_same_error_multiple_times(self): ) # This asserts that the reported span event contained the info # of the last time the error is handled - assert "line 30" in self.spans[0]._events[0].attributes["exception.stacktrace"] + assert "line 48" in self.spans[0]._events[0].attributes["exception.stacktrace"], ( + self.spans[0]._events[0].attributes["exception.stacktrace"] + ) @run_in_subprocess(env_overrides=dict(DD_ERROR_TRACKING_HANDLED_ERRORS="all")) def test_handled_same_error_different_type(self): @@ -159,11 +161,30 @@ def test_handled_in_parent_span(self): self.spans[0].assert_span_event_attributes( 0, {"exception.type": "builtins.ValueError", "exception.message": "auto caught error"} ) - assert "line 72" in self.spans[0]._events[0].attributes["exception.stacktrace"] + assert "line 100" in self.spans[0]._events[0].attributes["exception.stacktrace"], ( + self.spans[0]._events[0].attributes["exception.stacktrace"] + ) assert self.spans[1].name == "child_span" assert len(self.spans[1]._events) == 0 + @run_in_subprocess(env_overrides=dict(DD_ERROR_TRACKING_HANDLED_ERRORS="all")) + def test_unhashable_exception(self): + """Test that unhashable exceptions (e.g., with mutable attributes) are handled correctly.""" + self._run_error_test( + "test_unhashable_exception_f", + initial_value=0, + expected_value=10, + expected_events=[ + [ + { + "exception.type": "tests.errortracking._test_functions.UnhashableException", + "exception.message": "unhashable error", + } + ] + ], + ) + @skipif_errortracking_not_supported class UserCodeErrorTestCases(TracerTestCase): From 32bb3ffed88b41e2d8574560210d954603f53bdc Mon Sep 17 00:00:00 2001 From: kyle Date: Thu, 6 Nov 2025 15:43:57 -0500 Subject: [PATCH 33/42] fix(llmobs): support HTTPS_PROXY setting (#15130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The built in `http.client` that we use does not support the HTTP{S}_PROXY environment variable (or proxying in general). AI tells me that the fix is pretty straightforward by extending the client. ## Testing I manually validated this solution by installing [squid](https://www.squid-cache.org/) locally with a small test app: ``` # demonstrate the proxy working locally with curl (.venv) ~/d/dd-trace-py ❯❯❯ HTTPS_PROXY=http://127.0.0.1:3128 curl https://google.com (.venv) ~/d/dd-trace-py ❯❯❯ tail -n 1 /opt/homebrew/var/logs/squid/access.log 1762370301.555 185 127.0.0.1 TCP_TUNNEL/200 7488 CONNECT google.com:443 - HIER_DIRECT/142.250.81.238 - (.venv) ~/d/dd-trace-py ❯❯❯ HTTPS_PROXY=http://127.0.0.1:3128 ddtrace-run python openai-app.py (.venv) ~/d/dd-trace-py ❯❯❯ tail -n 2 /opt/homebrew/var/logs/squid/access.log 1762370369.733 169 127.0.0.1 TCP_TUNNEL/200 4049 CONNECT llmobs-intake.datadoghq.com:443 - HIER_DIRECT/3.233.158.217 - 1762370369.845 3857 127.0.0.1 TCP_TUNNEL/200 6469 CONNECT api.openai.com:443 - HIER_DIRECT/162.159.140.245 - ``` and confirmed the trace is sent successfully: image ## Risks No impact to non-`HTTPS_PROXY` setting usage so risk is pretty limited. ## Additional Notes --- ddtrace/llmobs/_http.py | 49 +++++++++++++++++++ ddtrace/llmobs/_writer.py | 2 +- .../llmobs-https-proxy-9ffc06b9f87df81b.yaml | 4 ++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 ddtrace/llmobs/_http.py create mode 100644 releasenotes/notes/llmobs-https-proxy-9ffc06b9f87df81b.yaml diff --git a/ddtrace/llmobs/_http.py b/ddtrace/llmobs/_http.py new file mode 100644 index 00000000000..67af618a4ad --- /dev/null +++ b/ddtrace/llmobs/_http.py @@ -0,0 +1,49 @@ +import os +import ssl +from typing import Optional +from urllib.parse import urlparse + +from ddtrace.internal.http import HTTPConnection +from ddtrace.internal.http import HTTPSConnection +from ddtrace.internal.uds import UDSHTTPConnection +from ddtrace.internal.utils.http import DEFAULT_TIMEOUT +from ddtrace.internal.utils.http import ConnectionType +from ddtrace.internal.utils.http import verify_url + + +class ProxiedHTTPSConnection(HTTPSConnection): + """ + The built-in http.client in Python doesn't respect HTTPS_PROXY (even tho other clients like requests and curl do). + + This implementation simply extends the client with support for basic proxies. + """ + + def __init__( + self, host: str, port: Optional[int] = None, context: Optional[ssl.SSLContext] = None, **kwargs + ) -> None: + if "HTTPS_PROXY" in os.environ: + tunnel_port = port or 443 + proxy = urlparse(os.environ["HTTPS_PROXY"]) + proxy_host = proxy.hostname or "" + # Default to 3128 (Squid's default port, de facto standard for HTTP proxies) + proxy_port = proxy.port or 3128 + super().__init__(proxy_host, proxy_port, **kwargs) + self.set_tunnel(host, tunnel_port) + else: + super().__init__(host, port, **kwargs) + + +def get_connection(url: str, timeout: float = DEFAULT_TIMEOUT) -> ConnectionType: + """Return an HTTP connection to the given URL.""" + parsed = verify_url(url) + hostname = parsed.hostname or "" + path = parsed.path or "/" + + if parsed.scheme == "https": + return ProxiedHTTPSConnection.with_base_path(hostname, parsed.port, base_path=path, timeout=timeout) + elif parsed.scheme == "http": + return HTTPConnection.with_base_path(hostname, parsed.port, base_path=path, timeout=timeout) + elif parsed.scheme == "unix": + return UDSHTTPConnection(path, hostname, parsed.port, timeout=timeout) + + raise ValueError("Unsupported protocol '%s'" % parsed.scheme) diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 89e861da8ef..1b4d0f2462b 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -27,7 +27,6 @@ from ddtrace.internal.periodic import PeriodicService from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.utils.http import Response -from ddtrace.internal.utils.http import get_connection from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter from ddtrace.llmobs import _telemetry as telemetry from ddtrace.llmobs._constants import AGENTLESS_EVAL_BASE_URL @@ -46,6 +45,7 @@ from ddtrace.llmobs._experiment import JSONType from ddtrace.llmobs._experiment import Project from ddtrace.llmobs._experiment import UpdatableDatasetRecord +from ddtrace.llmobs._http import get_connection from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.types import _Meta from ddtrace.llmobs.types import _SpanLink diff --git a/releasenotes/notes/llmobs-https-proxy-9ffc06b9f87df81b.yaml b/releasenotes/notes/llmobs-https-proxy-9ffc06b9f87df81b.yaml new file mode 100644 index 00000000000..2cf585ad075 --- /dev/null +++ b/releasenotes/notes/llmobs-https-proxy-9ffc06b9f87df81b.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + LLM Observability: add support for HTTPS_PROXY. From 0db2b008e086e212e5e28c5494b453211398ec74 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 7 Nov 2025 03:32:28 -0500 Subject: [PATCH 34/42] chore: remove legacy-cgi dependency (#15161) This is a test only dependency, should not have been added as a package dependency. --- .../requirements/{1d07e79.txt => 1156e38.txt} | 23 ++++----- .../requirements/{2a056ee.txt => 1193154.txt} | 19 ++++---- .riot/requirements/132305d.txt | 46 ++++++++++++++++++ .../requirements/{d65bf1a.txt => 13404e3.txt} | 21 +++++---- .../requirements/{1757cd0.txt => 1528286.txt} | 21 +++++---- .../requirements/{492b83f.txt => 1532cbc.txt} | 23 ++++----- .riot/requirements/169ce58.txt | 45 ++++++++++++++++++ .../requirements/{1b86c06.txt => 1b0c82a.txt} | 30 ++++++------ .../requirements/{15b674e.txt => 1b4f196.txt} | 23 ++++----- .../requirements/{13632f0.txt => 1be8615.txt} | 21 +++++---- .riot/requirements/1ce0711.txt | 24 ---------- .../requirements/{8ce955f.txt => 1dae29b.txt} | 30 ++++++------ .riot/requirements/1e457f1.txt | 29 ++++++++++++ .riot/requirements/1e70094.txt | 42 ----------------- .riot/requirements/5ec239b.txt | 27 +++++++++++ .../requirements/{1f2170d.txt => 7365790.txt} | 22 ++++----- .../requirements/{16fd7e4.txt => 7628925.txt} | 21 +++++---- .riot/requirements/788c304.txt | 27 ----------- .../requirements/{f8f7938.txt => 936e77e.txt} | 32 ++++++------- .../requirements/{19a7f12.txt => c7373fb.txt} | 21 +++++---- .../requirements/{aa06e19.txt => e3ebed7.txt} | 23 ++++----- .../requirements/{1f75ac4.txt => f30334b.txt} | 23 ++++----- lib-injection/sources/requirements.csv | 1 - pyproject.toml | 1 - requirements.csv | 1 - riotfile.py | 47 +++++++++++++++++-- tests/lib_injection/conftest.py | 3 ++ 27 files changed, 376 insertions(+), 270 deletions(-) rename .riot/requirements/{1d07e79.txt => 1156e38.txt} (78%) rename .riot/requirements/{2a056ee.txt => 1193154.txt} (82%) create mode 100644 .riot/requirements/132305d.txt rename .riot/requirements/{d65bf1a.txt => 13404e3.txt} (78%) rename .riot/requirements/{1757cd0.txt => 1528286.txt} (78%) rename .riot/requirements/{492b83f.txt => 1532cbc.txt} (75%) create mode 100644 .riot/requirements/169ce58.txt rename .riot/requirements/{1b86c06.txt => 1b0c82a.txt} (54%) rename .riot/requirements/{15b674e.txt => 1b4f196.txt} (76%) rename .riot/requirements/{13632f0.txt => 1be8615.txt} (77%) delete mode 100644 .riot/requirements/1ce0711.txt rename .riot/requirements/{8ce955f.txt => 1dae29b.txt} (55%) create mode 100644 .riot/requirements/1e457f1.txt delete mode 100644 .riot/requirements/1e70094.txt create mode 100644 .riot/requirements/5ec239b.txt rename .riot/requirements/{1f2170d.txt => 7365790.txt} (63%) rename .riot/requirements/{16fd7e4.txt => 7628925.txt} (78%) delete mode 100644 .riot/requirements/788c304.txt rename .riot/requirements/{f8f7938.txt => 936e77e.txt} (69%) rename .riot/requirements/{19a7f12.txt => c7373fb.txt} (67%) rename .riot/requirements/{aa06e19.txt => e3ebed7.txt} (65%) rename .riot/requirements/{1f75ac4.txt => f30334b.txt} (63%) diff --git a/.riot/requirements/1d07e79.txt b/.riot/requirements/1156e38.txt similarity index 78% rename from .riot/requirements/1d07e79.txt rename to .riot/requirements/1156e38.txt index 9a0af3995a6..599f4a6472f 100644 --- a/.riot/requirements/1d07e79.txt +++ b/.riot/requirements/1156e38.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1d07e79.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1156e38.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 django==3.2.25 django-configurations==2.5.1 @@ -18,8 +18,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -31,16 +32,16 @@ pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 pytz==2025.2 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/2a056ee.txt b/.riot/requirements/1193154.txt similarity index 82% rename from .riot/requirements/2a056ee.txt rename to .riot/requirements/1193154.txt index 4bd599a7371..ad497bd7e84 100644 --- a/.riot/requirements/2a056ee.txt +++ b/.riot/requirements/1193154.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/2a056ee.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1193154.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 +certifi==2025.10.5 +charset-normalizer==3.4.4 coverage[toml]==7.10.7 dill==0.4.0 django==3.2.25 @@ -18,8 +18,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -31,16 +32,16 @@ pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 pytz==2025.2 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/132305d.txt b/.riot/requirements/132305d.txt new file mode 100644 index 00000000000..a2483e423ad --- /dev/null +++ b/.riot/requirements/132305d.txt @@ -0,0 +1,46 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/132305d.in +# +asgiref==3.8.1 +attrs==25.3.0 +bcrypt==4.2.1 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.6.1 +dill==0.4.0 +django==3.2.25 +django-configurations==2.5.1 +exceptiongroup==1.3.0 +gevent==22.10.2 +greenlet==3.1.1 +gunicorn==23.0.0 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +legacy-cgi==2.6.4 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.5.0 +pylibmc==1.6.3 +pytest==8.3.5 +pytest-cov==5.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.14.1 +pytz==2025.2 +pyyaml==6.0.3 +requests==2.32.4 +six==1.17.0 +sortedcontainers==2.4.0 +sqlparse==0.5.3 +tomli==2.3.0 +typing-extensions==4.13.2 +urllib3==2.2.3 +zope-event==5.0 +zope-interface==7.2 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==75.3.2 diff --git a/.riot/requirements/d65bf1a.txt b/.riot/requirements/13404e3.txt similarity index 78% rename from .riot/requirements/d65bf1a.txt rename to .riot/requirements/13404e3.txt index 71a02aa041d..56a3a95a69d 100644 --- a/.riot/requirements/d65bf1a.txt +++ b/.riot/requirements/13404e3.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/d65bf1a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13404e3.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 django==3.2.25 django-configurations==2.5.1 @@ -17,8 +17,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -30,14 +31,14 @@ pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 pytz==2025.2 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/1757cd0.txt b/.riot/requirements/1528286.txt similarity index 78% rename from .riot/requirements/1757cd0.txt rename to .riot/requirements/1528286.txt index 0ed8bc8063a..41183d70861 100644 --- a/.riot/requirements/1757cd0.txt +++ b/.riot/requirements/1528286.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1757cd0.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1528286.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 django==3.2.25 django-configurations==2.5.1 @@ -17,8 +17,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -30,14 +31,14 @@ pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 pytz==2025.2 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/492b83f.txt b/.riot/requirements/1532cbc.txt similarity index 75% rename from .riot/requirements/492b83f.txt rename to .riot/requirements/1532cbc.txt index 52c200e905f..c70adced5b6 100644 --- a/.riot/requirements/492b83f.txt +++ b/.riot/requirements/1532cbc.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/492b83f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1532cbc.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 -django==4.2.24 +django==4.2.26 django-configurations==2.5.1 gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -29,14 +30,14 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/169ce58.txt b/.riot/requirements/169ce58.txt new file mode 100644 index 00000000000..dea4faae8ac --- /dev/null +++ b/.riot/requirements/169ce58.txt @@ -0,0 +1,45 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/169ce58.in +# +attrs==25.4.0 +beautifulsoup4==4.14.2 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 +hupper==1.12.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pastedeploy==3.1.0 +plaster==1.1.2 +plaster-pastedeploy==1.0.1 +pluggy==1.6.0 +pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app +pygments==2.19.2 +pyramid==2.0.2 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +requests==2.32.5 +sortedcontainers==2.4.0 +soupsieve==2.8 +translationstring==1.4 +typing-extensions==4.15.0 +urllib3==2.5.0 +venusian==3.1.1 +waitress==3.0.2 +webob==1.8.9 +webtest==3.0.7 +zope-deprecation==6.0 +zope-interface==8.0.1 + +# The following packages are considered to be unsafe in a requirements file: +setuptools==80.9.0 diff --git a/.riot/requirements/1b86c06.txt b/.riot/requirements/1b0c82a.txt similarity index 54% rename from .riot/requirements/1b86c06.txt rename to .riot/requirements/1b0c82a.txt index 68de1371257..b0dc497c59b 100644 --- a/.riot/requirements/1b86c06.txt +++ b/.riot/requirements/1b0c82a.txt @@ -2,26 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b86c06.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b0c82a.in # -attrs==24.2.0 -certifi==2024.8.30 -coverage[toml]==7.6.1 -h11==0.14.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +h11==0.16.0 httpcore==0.12.3 httpx==0.17.1 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 +mock==5.2.0 opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.3 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pytest==8.4.2 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 rfc3986[idna2008]==1.5.0 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/15b674e.txt b/.riot/requirements/1b4f196.txt similarity index 76% rename from .riot/requirements/15b674e.txt rename to .riot/requirements/1b4f196.txt index 511bf0efaad..3234f36194a 100644 --- a/.riot/requirements/15b674e.txt +++ b/.riot/requirements/1b4f196.txt @@ -2,23 +2,24 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/15b674e.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1b4f196.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 -django==5.2.6 +django==5.2.8 django-configurations==2.5.1 gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -29,14 +30,14 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/13632f0.txt b/.riot/requirements/1be8615.txt similarity index 77% rename from .riot/requirements/13632f0.txt rename to .riot/requirements/1be8615.txt index 03a2eced3a1..a9123b77121 100644 --- a/.riot/requirements/13632f0.txt +++ b/.riot/requirements/1be8615.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13632f0.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1be8615.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 django==4.0.10 django-configurations==2.5.1 @@ -17,8 +17,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -29,14 +30,14 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/1ce0711.txt b/.riot/requirements/1ce0711.txt deleted file mode 100644 index 6721b5e5b0b..00000000000 --- a/.riot/requirements/1ce0711.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1ce0711.in -# -attrs==24.2.0 -beautifulsoup4==4.12.3 -coverage[toml]==7.6.1 -hypothesis==6.45.0 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sortedcontainers==2.4.0 -soupsieve==2.6 -waitress==3.0.0 -webob==1.8.8 -webtest==3.0.1 diff --git a/.riot/requirements/8ce955f.txt b/.riot/requirements/1dae29b.txt similarity index 55% rename from .riot/requirements/8ce955f.txt rename to .riot/requirements/1dae29b.txt index 6a3a0e63588..66949a0c578 100644 --- a/.riot/requirements/8ce955f.txt +++ b/.riot/requirements/1dae29b.txt @@ -2,27 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/8ce955f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1dae29b.in # -anyio==4.6.0 -attrs==24.2.0 -certifi==2024.8.30 -coverage[toml]==7.6.1 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 h11==0.14.0 httpcore==0.16.3 httpx==0.23.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 +mock==5.2.0 opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.3 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pytest==8.4.2 pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 rfc3986[idna2008]==1.5.0 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e457f1.txt b/.riot/requirements/1e457f1.txt new file mode 100644 index 00000000000..1a87bf9ed58 --- /dev/null +++ b/.riot/requirements/1e457f1.txt @@ -0,0 +1,29 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e457f1.in +# +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pytest==8.4.2 +pytest-asyncio==0.21.1 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +sniffio==1.3.1 +sortedcontainers==2.4.0 diff --git a/.riot/requirements/1e70094.txt b/.riot/requirements/1e70094.txt deleted file mode 100644 index d4db5f4a288..00000000000 --- a/.riot/requirements/1e70094.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e70094.in -# -attrs==24.2.0 -beautifulsoup4==4.12.3 -certifi==2024.8.30 -charset-normalizer==3.3.2 -coverage[toml]==7.6.1 -hupper==1.12.1 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pastedeploy==3.1.0 -plaster==1.1.2 -plaster-pastedeploy==1.0.1 -pluggy==1.5.0 -pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app -pyramid==2.0.2 -pytest==8.3.3 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -requests==2.32.3 -sortedcontainers==2.4.0 -soupsieve==2.6 -translationstring==1.4 -urllib3==2.2.3 -venusian==3.1.0 -waitress==3.0.0 -webob==1.8.8 -webtest==3.0.1 -zope-deprecation==5.0 -zope-interface==7.0.3 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.1.0 diff --git a/.riot/requirements/5ec239b.txt b/.riot/requirements/5ec239b.txt new file mode 100644 index 00000000000..32d340473d7 --- /dev/null +++ b/.riot/requirements/5ec239b.txt @@ -0,0 +1,27 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/5ec239b.in +# +attrs==25.4.0 +beautifulsoup4==4.14.2 +coverage[toml]==7.11.0 +hypothesis==6.45.0 +iniconfig==2.3.0 +legacy-cgi==2.6.4 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 +sortedcontainers==2.4.0 +soupsieve==2.8 +typing-extensions==4.15.0 +waitress==3.0.2 +webob==1.8.9 +webtest==3.0.7 diff --git a/.riot/requirements/1f2170d.txt b/.riot/requirements/7365790.txt similarity index 63% rename from .riot/requirements/1f2170d.txt rename to .riot/requirements/7365790.txt index af670a8fa3f..ec4c60233a7 100644 --- a/.riot/requirements/1f2170d.txt +++ b/.riot/requirements/7365790.txt @@ -2,26 +2,26 @@ # This file is autogenerated by pip-compile with Python 3.14 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f2170d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/7365790.in # -attrs==25.3.0 -beautifulsoup4==4.13.5 -coverage[toml]==7.10.5 +attrs==25.4.0 +beautifulsoup4==4.14.2 +coverage[toml]==7.11.0 hypothesis==6.45.0 -iniconfig==2.1.0 -legacy-cgi==2.6.3 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 pygments==2.19.2 -pytest==8.4.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 sortedcontainers==2.4.0 soupsieve==2.8 typing-extensions==4.15.0 waitress==3.0.2 webob==1.8.9 -webtest==3.0.6 +webtest==3.0.7 diff --git a/.riot/requirements/16fd7e4.txt b/.riot/requirements/7628925.txt similarity index 78% rename from .riot/requirements/16fd7e4.txt rename to .riot/requirements/7628925.txt index d71e4a8044e..70c0b92bb5e 100644 --- a/.riot/requirements/16fd7e4.txt +++ b/.riot/requirements/7628925.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/16fd7e4.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/7628925.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 dill==0.4.0 django==3.2.25 django-configurations==2.5.1 @@ -17,8 +17,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -30,14 +31,14 @@ pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 pytz==2025.2 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/788c304.txt b/.riot/requirements/788c304.txt deleted file mode 100644 index 36e1cd013d9..00000000000 --- a/.riot/requirements/788c304.txt +++ /dev/null @@ -1,27 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/788c304.in -# -anyio==4.6.0 -attrs==24.2.0 -certifi==2024.8.30 -coverage[toml]==7.6.1 -h11==0.14.0 -httpcore==1.0.6 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.0.0 -mock==5.1.0 -opentracing==2.4.0 -packaging==24.1 -pluggy==1.5.0 -pytest==8.3.3 -pytest-asyncio==0.21.1 -pytest-cov==5.0.0 -pytest-mock==3.14.0 -pytest-randomly==3.15.0 -sniffio==1.3.1 -sortedcontainers==2.4.0 diff --git a/.riot/requirements/f8f7938.txt b/.riot/requirements/936e77e.txt similarity index 69% rename from .riot/requirements/f8f7938.txt rename to .riot/requirements/936e77e.txt index 01681085fbf..4107dce2a1c 100644 --- a/.riot/requirements/f8f7938.txt +++ b/.riot/requirements/936e77e.txt @@ -2,18 +2,18 @@ # This file is autogenerated by pip-compile with Python 3.14 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/f8f7938.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/936e77e.in # -attrs==25.3.0 -beautifulsoup4==4.13.5 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.5 +attrs==25.4.0 +beautifulsoup4==4.14.2 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.0 hupper==1.12.1 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -legacy-cgi==2.6.3 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -24,10 +24,10 @@ pluggy==1.6.0 pserve-test-app @ file:///home/bits/project/tests/contrib/pyramid/pserve_app pygments==2.19.2 pyramid==2.0.2 -pytest==8.4.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 requests==2.32.5 sortedcontainers==2.4.0 soupsieve==2.8 @@ -37,9 +37,9 @@ urllib3==2.5.0 venusian==3.1.1 waitress==3.0.2 webob==1.8.9 -webtest==3.0.6 -zope-deprecation==5.1 -zope-interface==7.2 +webtest==3.0.7 +zope-deprecation==6.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/19a7f12.txt b/.riot/requirements/c7373fb.txt similarity index 67% rename from .riot/requirements/19a7f12.txt rename to .riot/requirements/c7373fb.txt index c29f1032f1d..af5e1efc78c 100644 --- a/.riot/requirements/19a7f12.txt +++ b/.riot/requirements/c7373fb.txt @@ -2,27 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.14 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/19a7f12.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/c7373fb.in # -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.10.5 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 h11==0.16.0 httpcore==0.12.3 httpx==0.17.1 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 pygments==2.19.2 -pytest==8.4.1 +pytest==8.4.2 pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 rfc3986[idna2008]==1.5.0 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/aa06e19.txt b/.riot/requirements/e3ebed7.txt similarity index 65% rename from .riot/requirements/aa06e19.txt rename to .riot/requirements/e3ebed7.txt index f04eaaf513e..5c9e744b6c8 100644 --- a/.riot/requirements/aa06e19.txt +++ b/.riot/requirements/e3ebed7.txt @@ -2,28 +2,29 @@ # This file is autogenerated by pip-compile with Python 3.14 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/aa06e19.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e3ebed7.in # -anyio==4.10.0 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.10.5 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 h11==0.14.0 httpcore==0.16.3 httpx==0.23.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 pygments==2.19.2 -pytest==8.4.1 +pytest==8.4.2 pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 rfc3986[idna2008]==1.5.0 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/.riot/requirements/1f75ac4.txt b/.riot/requirements/f30334b.txt similarity index 63% rename from .riot/requirements/1f75ac4.txt rename to .riot/requirements/f30334b.txt index b7524d8b17c..cd571effabf 100644 --- a/.riot/requirements/1f75ac4.txt +++ b/.riot/requirements/f30334b.txt @@ -2,27 +2,28 @@ # This file is autogenerated by pip-compile with Python 3.14 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1f75ac4.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f30334b.in # -anyio==4.10.0 -attrs==25.3.0 -certifi==2025.8.3 -coverage[toml]==7.10.5 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +coverage[toml]==7.11.0 h11==0.16.0 httpcore==1.0.9 httpx==0.28.1 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 pygments==2.19.2 -pytest==8.4.1 +pytest==8.4.2 pytest-asyncio==0.21.1 -pytest-cov==6.2.1 -pytest-mock==3.14.1 -pytest-randomly==3.16.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +pytest-randomly==4.0.1 sniffio==1.3.1 sortedcontainers==2.4.0 diff --git a/lib-injection/sources/requirements.csv b/lib-injection/sources/requirements.csv index cfaaa668df1..e90bd670d63 100644 --- a/lib-injection/sources/requirements.csv +++ b/lib-injection/sources/requirements.csv @@ -5,7 +5,6 @@ bytecode,>=0.15.1,python_version~='3.12.0' bytecode,>=0.14.0,python_version~='3.11.0' bytecode,>=0.13.0,python_version<'3.11' envier,~=0.6.1, -legacy-cgi,">=2,<3",python_version>='3.13.0' opentelemetry-api,">=1,<2", protobuf,">=3,<7", wrapt,">=1,<3", diff --git a/pyproject.toml b/pyproject.toml index 89837cda571..8334017b8b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ dependencies = [ "bytecode>=0.14.0; python_version~='3.11.0'", "bytecode>=0.13.0; python_version<'3.11'", "envier~=0.6.1", - "legacy-cgi>=2,<3; python_version>='3.13.0'", "opentelemetry-api>=1,<2", "protobuf>=3,<7", "wrapt>=1,<3", diff --git a/requirements.csv b/requirements.csv index cfaaa668df1..e90bd670d63 100644 --- a/requirements.csv +++ b/requirements.csv @@ -5,7 +5,6 @@ bytecode,>=0.15.1,python_version~='3.12.0' bytecode,>=0.14.0,python_version~='3.11.0' bytecode,>=0.13.0,python_version<'3.11' envier,~=0.6.1, -legacy-cgi,">=2,<3",python_version>='3.13.0' opentelemetry-api,">=1,<2", protobuf,">=3,<7", wrapt,">=1,<3", diff --git a/riotfile.py b/riotfile.py index 197cf437643..44fd72b791b 100644 --- a/riotfile.py +++ b/riotfile.py @@ -267,20 +267,32 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( pys=["3.9", "3.10", "3.11", "3.12", "3.13"], - pkgs={"django": "~=3.2"}, + pkgs={"django": "~=3.2", "legacy-cgi": latest}, ), Venv( pys=["3.9", "3.10", "3.11", "3.12", "3.13"], pkgs={"django": "==4.0.10"}, ), + Venv( + pys=["3.13"], + pkgs={"django": "==4.0.10", "legacy-cgi": latest}, + ), Venv( pys=["3.9", "3.10", "3.11", "3.12", "3.13"], pkgs={"django": "~=4.2"}, ), Venv( - pys=["3.10", "3.13"], + pys=["3.13"], + pkgs={"django": "~=4.2", "legacy-cgi": latest}, + ), + Venv( + pys=["3.10"], pkgs={"django": "~=5.1"}, ), + Venv( + pys=["3.13"], + pkgs={"django": "~=5.1", "legacy-cgi": latest}, + ), ], ), Venv( @@ -1553,12 +1565,20 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT command="pytest {cmdargs} tests/contrib/wsgi", venvs=[ Venv( - pys=select_pys(), + pys=select_pys(max_version="3.12"), pkgs={ "WebTest": latest, "pytest-randomly": latest, }, ), + Venv( + pys=select_pys(min_version="3.13"), + pkgs={ + "WebTest": latest, + "pytest-randomly": latest, + "legacy-cgi": latest, + }, + ), ], ), Venv( @@ -1673,9 +1693,18 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv( # pyramid added support for Python 3.10/3.11 in 2.1 # FIXME[python-3.12]: blocked on venusian release https://github.com/Pylons/venusian/issues/85 - pys=select_pys(min_version="3.10"), + pys=select_pys(min_version="3.10", max_version="3.12"), + pkgs={ + "pyramid": [latest], + }, + ), + Venv( + # pyramid added support for Python 3.10/3.11 in 2.1 + # FIXME[python-3.12]: blocked on venusian release https://github.com/Pylons/venusian/issues/85 + pys=select_pys(min_version="3.13"), pkgs={ "pyramid": [latest], + "legacy-cgi": latest, }, ), ], @@ -2048,7 +2077,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( name="httpx", - pys=select_pys(), command="pytest {cmdargs} tests/contrib/httpx", pkgs={ "pytest-asyncio": "==0.21.1", @@ -2059,6 +2087,15 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT latest, ], }, + venvs=[ + Venv(pys=select_pys(max_version="3.12")), + Venv( + pys=select_pys(min_version="3.13"), + pkgs={ + "legacy-cgi": latest, + }, + ), + ], ), Venv( name="urllib3", diff --git a/tests/lib_injection/conftest.py b/tests/lib_injection/conftest.py index 0042f84aaf8..fa7ed195bbe 100644 --- a/tests/lib_injection/conftest.py +++ b/tests/lib_injection/conftest.py @@ -157,6 +157,9 @@ def _create_test_venv(packages_to_install=None): spec = f"{package}=={version}" if version else package install_specs.append(spec) + if package == "bottle" and sys.version_info >= (3, 13): + install_specs.append("legacy-cgi") + if install_specs: test_install_cmd = [pip_executable, "install", "--no-cache-dir"] + install_specs subprocess.run( From 890b7b870c2fc8e251ee57e40c3c882250faae41 Mon Sep 17 00:00:00 2001 From: Christophe Papazian <114495376+christophe-papazian@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:30:07 +0100 Subject: [PATCH 35/42] feat(aap): blocking id (#15042) - new blocking id feature - libddwaf update (required for this feature) - refactor of blocking configuration with dedicated type - tests updated - will also be validated by system tests https://github.com/DataDog/system-tests/pull/5674 APPSEC-59798 --- .github/workflows/system-tests.yml | 6 +- .gitlab-ci.yml | 2 +- ddtrace/appsec/_asm_request_context.py | 31 ++++++---- ddtrace/appsec/_constants.py | 1 + ddtrace/appsec/_handlers.py | 38 ++++++------- ddtrace/appsec/_processor.py | 3 +- ddtrace/appsec/_utils.py | 20 +++++++ ddtrace/contrib/internal/django/response.py | 40 +++++++------ ddtrace/contrib/internal/flask/patch.py | 25 ++++----- ddtrace/internal/constants.py | 4 +- ddtrace/internal/utils/__init__.py | 12 +++- ddtrace/internal/utils/http.py | 17 +++--- .../notes/block_id-551e443afd917aa6.yaml | 4 ++ setup.py | 2 +- tests/appsec/appsec/test_appsec_utils.py | 56 +++++++++++++------ tests/appsec/contrib_appsec/utils.py | 48 +++++++++------- .../django_tests/test_appsec_django.py | 15 +++-- .../flask_tests/test_appsec_flask.py | 7 ++- .../test_appsec_flask_telemetry.py | 6 +- .../django/test_django_appsec_snapshots.py | 18 ++++-- .../flask/test_appsec_flask_snapshot.py | 22 +++----- ...st_appsec_body_no_collection_snapshot.json | 2 +- ...appsec_cookies_no_collection_snapshot.json | 2 +- ...cessor.test_appsec_span_tags_snapshot.json | 2 +- ...appsec_span_tags_snapshot_with_errors.json | 2 +- ..._appsec_snapshots.test_appsec_enabled.json | 2 +- ..._snapshots.test_appsec_enabled_attack.json | 2 +- ...pshots.test_request_ipblock_match_403.json | 2 +- ...s.test_request_ipblock_match_403_json.json | 2 +- ...hots.test_request_ipblock_nomatch_200.json | 2 +- ...atch_403[flask_appsec_good_rules_env].json | 2 +- ..._403[flask_appsec_good_rules_env]_220.json | 2 +- ...403_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- ..._osspawn[flask_appsec_good_rules_env].json | 2 +- ...pawn[flask_appsec_good_rules_env]_220.json | 2 +- ...ossystem[flask_appsec_good_rules_env].json | 2 +- ...stem[flask_appsec_good_rules_env]_220.json | 2 +- ...enoshell[flask_appsec_good_rules_env].json | 2 +- ...hell[flask_appsec_good_rules_env]_220.json | 2 +- ...ateshell[flask_appsec_good_rules_env].json | 2 +- ...hell[flask_appsec_good_rules_env]_220.json | 2 +- ...200_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- ...403_json[flask_appsec_good_rules_env].json | 2 +- ...json[flask_appsec_good_rules_env]_220.json | 2 +- 46 files changed, 256 insertions(+), 171 deletions(-) create mode 100644 releasenotes/notes/block_id-551e443afd917aa6.yaml diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 426d8bc7e1d..4e6f1f306d5 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '12860dc3fa4a474907f18f8313518989c80772ce' + ref: '279a4f17c9392157cdc106e627c2b57c2233899b' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '12860dc3fa4a474907f18f8313518989c80772ce' + ref: '279a4f17c9392157cdc106e627c2b57c2233899b' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '12860dc3fa4a474907f18f8313518989c80772ce' + ref: '279a4f17c9392157cdc106e627c2b57c2233899b' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a1b76a1082d..dfe5b8dc860 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "12860dc3fa4a474907f18f8313518989c80772ce" + SYSTEM_TESTS_REF: "279a4f17c9392157cdc106e627c2b57c2233899b" default: interruptible: true diff --git a/ddtrace/appsec/_asm_request_context.py b/ddtrace/appsec/_asm_request_context.py index 522ea5d6ad9..51f0957658b 100644 --- a/ddtrace/appsec/_asm_request_context.py +++ b/ddtrace/appsec/_asm_request_context.py @@ -18,6 +18,7 @@ from ddtrace.appsec._constants import APPSEC from ddtrace.appsec._constants import SPAN_DATA_NAMES from ddtrace.appsec._constants import Constant_Class +from ddtrace.appsec._utils import Block_config from ddtrace.appsec._utils import Telemetry_result from ddtrace.appsec._utils import get_triggers from ddtrace.contrib.internal.trace_utils_base import _normalize_tag_name @@ -104,7 +105,7 @@ def __init__(self, span: Optional[Span] = None, rc_products: str = ""): self.telemetry: Telemetry_result = Telemetry_result() self.addresses_sent: Set[str] = set() self.waf_triggers: List[Dict[str, Any]] = [] - self.blocked: Optional[Dict[str, Any]] = None + self.blocked: Optional[Block_config] = None self.finalized: bool = False self.api_security_reported: int = 0 self.rc_products: str = rc_products @@ -126,11 +127,11 @@ def is_blocked() -> bool: return env.blocked is not None -def get_blocked() -> Dict[str, Any]: +def get_blocked() -> Optional[Block_config]: env = _get_asm_context() if env is None: - return {} - return env.blocked or {} + return None + return env.blocked or None def get_entry_span() -> Optional[Span]: @@ -200,15 +201,11 @@ def _use_html(headers) -> bool: def _ctype_from_headers(block_config, headers) -> None: """compute MIME type of the blocked response and store it in the block config""" - desired_type = block_config.get("type", "auto") - if desired_type == "auto": - block_config["content-type"] = "text/html" if _use_html(headers) else "application/json" - else: - block_config["content-type"] = "text/html" if block_config["type"] == "html" else "application/json" + if (block_config.type == "auto" and _use_html(headers)) or block_config.type == "html": + block_config.content_type = "text/html" -def set_blocked(blocked: Dict[str, Any]) -> None: - blocked = blocked.copy() +def set_blocked(blocked: Block_config) -> None: env = _get_asm_context() if env is None: logger.warning(WARNING_TAGS.SET_BLOCKED_NO_ASM_CONTEXT, extra=log_extra, stack_info=True) @@ -217,6 +214,16 @@ def set_blocked(blocked: Dict[str, Any]) -> None: env.blocked = blocked +def set_blocked_dict(block: Union[Dict[str, Any], Block_config, None]) -> None: + if isinstance(block, dict): + blocked = Block_config(**block) + elif block is None: + blocked = Block_config() + else: + blocked = block + set_blocked(blocked) + + def update_span_metrics(span: Span, name: str, value: Union[float, int]) -> None: span.set_metric(name, value + (span.get_metric(name) or 0.0)) @@ -713,7 +720,7 @@ def asm_listen(): core.on("asgi.start_response", _call_waf) core.on("asgi.finalize_response", _set_headers_and_response) - core.on("asm.set_blocked", set_blocked) + core.on("asm.set_blocked", set_blocked_dict) core.on("asm.get_blocked", get_blocked, "block_config") core.on("context.ended.wsgi.__call__", _on_context_ended) diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py index 4f859bf5e11..aedc7db5b99 100644 --- a/ddtrace/appsec/_constants.py +++ b/ddtrace/appsec/_constants.py @@ -125,6 +125,7 @@ class APPSEC(metaclass=Constant_Class): ERROR_MESSAGE: Literal["_dd.appsec.error.message"] = "_dd.appsec.error.message" UNSUPPORTED_EVENT_TYPE: Literal["_dd.appsec.unsupported_event_type"] = "_dd.appsec.unsupported_event_type" SERVERLESS_TRACER_ENABLED: Literal["_dd.appsec.serverless.tracer"] = "_dd.appsec.serverless.tracer" + SECURITY_RESPONSE_ID: Literal["[security_response_id]"] = "[security_response_id]" TELEMETRY_OFF_NAME = "OFF" diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index 435ce46b39f..ac7e1cdc3f9 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -2,7 +2,9 @@ import json from typing import Any from typing import Dict +from typing import List from typing import Optional +from typing import Tuple from typing import Union from ddtrace._trace.span import Span @@ -15,6 +17,7 @@ from ddtrace.appsec._http_utils import extract_cookies_from_headers from ddtrace.appsec._http_utils import normalize_headers from ddtrace.appsec._http_utils import parse_http_body +from ddtrace.appsec._utils import Block_config from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.trace_utils_base import _get_request_header_user_agent from ddtrace.contrib.internal.trace_utils_base import _set_url_tag @@ -292,24 +295,23 @@ def _on_grpc_server_data(headers, request_message, method, metadata): set_waf_address(SPAN_DATA_NAMES.GRPC_SERVER_REQUEST_METADATA, dict(metadata)) -def _wsgi_make_block_content(ctx, construct_url): +def _wsgi_make_block_content(ctx, construct_url) -> Tuple[int, List[Tuple[str, str]], bytes]: middleware = ctx.get_item("middleware") req_span = ctx.get_item("req_span") headers = ctx.get_item("headers") environ = ctx.get_item("environ") if req_span is None: raise ValueError("request span not found") - block_config = get_blocked() - desired_type = block_config.get("type", "auto") + block_config: Block_config = get_blocked() or Block_config() ctype = None - if desired_type == "none": - content = "" - resp_headers = [("content-type", "text/plain; charset=utf-8"), ("location", block_config.get("location", ""))] + if block_config.type == "none": + content = b"" + resp_headers = [("content-type", "text/plain; charset=utf-8"), ("location", block_config.location)] else: - ctype = block_config.get("content-type", "application/json") - content = http_utils._get_blocked_template(ctype).encode("UTF-8") + ctype = block_config.content_type + content = http_utils._get_blocked_template(ctype, block_config.block_id).encode("UTF-8") resp_headers = [("content-type", ctype)] - status = block_config.get("status_code", 403) + status = block_config.status_code try: req_span._set_tag_str(RESPONSE_HEADERS + ".content-length", str(len(content))) if ctype is not None: @@ -332,28 +334,26 @@ def _wsgi_make_block_content(ctx, construct_url): return status, resp_headers, content -def _asgi_make_block_content(ctx, url): +def _asgi_make_block_content(ctx, url) -> Tuple[int, List[Tuple[bytes, bytes]], bytes]: middleware = ctx.get_item("middleware") req_span = ctx.get_item("req_span") headers = ctx.get_item("headers") environ = ctx.get_item("environ") if req_span is None: raise ValueError("request span not found") - block_config = get_blocked() - desired_type = block_config.get("type", "auto") + block_config = get_blocked() or Block_config() ctype = None - if desired_type == "none": - content = "" + if block_config.type == "none": + content = b"" resp_headers = [ (b"content-type", b"text/plain; charset=utf-8"), - (b"location", block_config.get("location", "").encode()), + (b"location", block_config.location.encode()), ] else: - ctype = block_config.get("content-type", "application/json") - content = http_utils._get_blocked_template(ctype).encode("UTF-8") + content = http_utils._get_blocked_template(block_config.content_type, block_config.block_id).encode("UTF-8") # ctype = f"{ctype}; charset=utf-8" can be considered at some point - resp_headers = [(b"content-type", ctype.encode())] - status = block_config.get("status_code", 403) + resp_headers = [(b"content-type", block_config.content_type.encode())] + status = block_config.status_code try: req_span._set_tag_str(RESPONSE_HEADERS + ".content-length", str(len(content))) if ctype is not None: diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index aa58b9ae5be..f09944d351d 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -35,6 +35,7 @@ from ddtrace.appsec._exploit_prevention.stack_traces import report_stack from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.appsec._utils import Binding_error +from ddtrace.appsec._utils import Block_config from ddtrace.appsec._utils import DDWaf_result from ddtrace.constants import _ORIGIN_KEY from ddtrace.constants import _RUNTIME_FAMILY @@ -341,7 +342,7 @@ def _waf_action( log.debug("[DDAS-011-00] ASM In-App WAF returned: %s. Timeout %s", waf_results.data, waf_results.timeout) if blocked: - _asm_request_context.set_blocked(blocked) + _asm_request_context.set_blocked(Block_config(**blocked)) allowed = True if waf_results.keep: diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index 71214903107..ba44b628c1c 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -154,6 +154,26 @@ def __init__(self) -> None: self.durations: Dict[str, float] = collections.defaultdict(float) +class Block_config: + __slots__ = ["block_id", "grpc_status_code", "status_code", "type", "content_type", "location"] + + def __init__( + self, + type: str = "auto", # noqa: A002 + status_code: int = 403, + grpc_status_code: int = 10, + security_response_id: str = "default", + location: str = "", + **_kwargs, + ) -> None: + self.block_id: str = security_response_id + self.grpc_status_code: int = grpc_status_code + self.status_code: int = status_code + self.type: str = type + self.location = location.replace(APPSEC.SECURITY_RESPONSE_ID, security_response_id) + self.content_type: str = "application/json" + + class Telemetry_result: __slots__ = [ "blocked", diff --git a/ddtrace/contrib/internal/django/response.py b/ddtrace/contrib/internal/django/response.py index e5f8d9966bb..ee02cef07a1 100644 --- a/ddtrace/contrib/internal/django/response.py +++ b/ddtrace/contrib/internal/django/response.py @@ -30,6 +30,7 @@ from ddtrace.internal.schema import schematize_url_operation from ddtrace.internal.schema.span_attribute_schema import SpanDirection from ddtrace.internal.settings.integration import IntegrationConfig +from ddtrace.internal.utils import Block_config from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils import get_blocked from ddtrace.internal.utils import http as http_utils @@ -119,27 +120,24 @@ def traced_get_response(func: FunctionType, args: Tuple[Any, ...], kwargs: Dict[ response = None - def blocked_response(): - block_config = get_blocked() or {} - desired_type = block_config.get("type", "auto") - status = block_config.get("status_code", 403) - if desired_type == "none": - response = HttpResponse("", status=status) - location = block_config.get("location", "") - if location: - response["location"] = location + def blocked_response(block_config: Block_config): + if block_config.type == "none": + response = HttpResponse("", status=block_config.status_code) + if block_config.location: + response["location"] = block_config.location else: - ctype = block_config.get("content-type", "application/json") - content = http_utils._get_blocked_template(ctype) - response = HttpResponse(content, content_type=ctype, status=status) + content = http_utils._get_blocked_template(block_config.content_type, block_config.block_id) + response = HttpResponse( + content, content_type=block_config.content_type, status=block_config.status_code + ) response.content = content response["Content-Length"] = len(content.encode()) utils._after_request_tags(pin, ctx.span, request, response) return response try: - if get_blocked(): - response = blocked_response() + if block_config := get_blocked(): + response = blocked_response(block_config) else: query = request.META.get("QUERY_STRING", "") uri = utils.get_request_uri(request) @@ -158,25 +156,25 @@ def blocked_response(): ) core.dispatch("django.start_response.post", ("Django",)) - if get_blocked(): - response = blocked_response() + if block_config := get_blocked(): + response = blocked_response(block_config) else: try: response = func(*args, **kwargs) except BlockingException as e: set_blocked(e.args[0]) - response = blocked_response() + response = blocked_response(e.args[0]) return response - if get_blocked(): - response = blocked_response() + if block_config := get_blocked(): + response = blocked_response(block_config) finally: core.dispatch("django.finalize_response.pre", (ctx, utils._after_request_tags, request, response)) if not get_blocked(): core.dispatch("django.finalize_response", ("Django",)) - if get_blocked(): - response = blocked_response() + if block_config := get_blocked(): + response = blocked_response(block_config) return response diff --git a/ddtrace/contrib/internal/flask/patch.py b/ddtrace/contrib/internal/flask/patch.py index ae5c3bed917..3e9aee5850d 100644 --- a/ddtrace/contrib/internal/flask/patch.py +++ b/ddtrace/contrib/internal/flask/patch.py @@ -125,7 +125,7 @@ def _wrapped_start_response(self, start_response, ctx, status_code, headers, exc core.dispatch("flask.start_response.pre", (flask.request, ctx, config.flask, status_code, headers)) if not get_blocked(): core.dispatch("flask.start_response", ("Flask",)) - if get_blocked(): + if block_config := get_blocked(): # response code must be set here, or it will be too late result_content = core.dispatch_with_results( # ast-grep-ignore: core-dispatch-with-results "flask.block.request.content", () @@ -134,16 +134,13 @@ def _wrapped_start_response(self, start_response, ctx, status_code, headers, exc _, status, response_headers = result_content.value result = start_response(str(status), response_headers) else: - block_config = get_blocked() - desired_type = block_config.get("type", "auto") - status = block_config.get("status_code", 403) - if desired_type == "none": - response_headers = [] - else: - ctype = block_config.get("content-type", "application/json") - response_headers = [("content-type", ctype)] - result = start_response(str(status), response_headers) - core.dispatch("flask.start_response.blocked", (ctx, config.flask, response_headers, status)) + response_headers = ( + [] if block_config.type == "none" else [("content-type", block_config.content_type)] + ) + result = start_response(str(block_config.status_code), response_headers) + core.dispatch( + "flask.start_response.blocked", (ctx, config.flask, response_headers, block_config.status_code) + ) else: result = start_response(status_code, headers) else: @@ -549,8 +546,10 @@ def _wrap(code_or_exception, f): def _block_request_callable(call): set_blocked() core.dispatch("flask.blocked_request_callable", (call,)) - ctype = get_blocked().get("content-type", "application/json") - abort(flask.Response(http_utils._get_blocked_template(ctype), content_type=ctype, status=403)) + block_config = get_blocked() + ctype = block_config.content_type if block_config else "application/json" + block_id = block_config.block_id if block_config else "(default)" + abort(flask.Response(http_utils._get_blocked_template(ctype, block_id), content_type=ctype, status=403)) def request_patcher(name): diff --git a/ddtrace/internal/constants.py b/ddtrace/internal/constants.py index fc572d0ffaa..e492bc7652e 100644 --- a/ddtrace/internal/constants.py +++ b/ddtrace/internal/constants.py @@ -55,8 +55,8 @@ DEFAULT_MAX_PAYLOAD_SIZE = 20 << 20 # 20 MB DEFAULT_PROCESSING_INTERVAL = 1.0 DEFAULT_REUSE_CONNECTIONS = False -BLOCKED_RESPONSE_HTML = """You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

""" # noqa: E501 -BLOCKED_RESPONSE_JSON = '{"errors":[{"title":"You\'ve been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}]}' # noqa: E501 +BLOCKED_RESPONSE_HTML = """You've been blocked

Sorry, you cannot access this page. Please contact the customer service team.

Security Response ID: [security_response_id]

""" # noqa: E501 +BLOCKED_RESPONSE_JSON = """{"errors":[{"title":"You've been blocked","detail":"Sorry, you cannot access this page. Please contact the customer service team. Security provided by Datadog."}],"security_response_id":"[security_response_id]"}""" # noqa: E501 HTTP_REQUEST_BLOCKED = "http.request.blocked" RESPONSE_HEADERS = "http.response.headers" HTTP_REQUEST_QUERY = "http.request.query" diff --git a/ddtrace/internal/utils/__init__.py b/ddtrace/internal/utils/__init__.py index 24781c63eef..e5bf0862851 100644 --- a/ddtrace/internal/utils/__init__.py +++ b/ddtrace/internal/utils/__init__.py @@ -2,6 +2,7 @@ from typing import Dict # noqa:F401 from typing import List # noqa:F401 from typing import Optional # noqa:F401 +from typing import Protocol # noqa:F401 from typing import Tuple # noqa:F401 from typing import Union # noqa:F401 @@ -78,7 +79,16 @@ def _get_metas_to_propagate(context): return [(k, v) for k, v in context._meta.items() if isinstance(k, str) and k.startswith("_dd.p.")] -def get_blocked() -> Optional[Dict[str, Any]]: +class Block_config(Protocol): + block_id: str + grpc_status_code: int + status_code: int + type: str + location: str + content_type: str + + +def get_blocked() -> Optional[Block_config]: # local import to avoid circular dependency from ddtrace.internal import core diff --git a/ddtrace/internal/utils/http.py b/ddtrace/internal/utils/http.py index 3324febb756..ea1aaa13bf2 100644 --- a/ddtrace/internal/utils/http.py +++ b/ddtrace/internal/utils/http.py @@ -320,11 +320,14 @@ def verify_url(url: str) -> parse.ParseResult: _HTML_BLOCKED_TEMPLATE_CACHE = None # type: Optional[str] _JSON_BLOCKED_TEMPLATE_CACHE = None # type: Optional[str] +_RESPONSE_ID_TEMPLATE = "[security_response_id]" -def _get_blocked_template(accept_header_value): - # type: (str) -> str +def _format_template(template: str, security_response_id: str) -> str: + return template.replace(_RESPONSE_ID_TEMPLATE, security_response_id) + +def _get_blocked_template(accept_header_value: str, security_response_id: str) -> str: global _HTML_BLOCKED_TEMPLATE_CACHE global _JSON_BLOCKED_TEMPLATE_CACHE @@ -334,10 +337,10 @@ def _get_blocked_template(accept_header_value): need_html_template = True if need_html_template and _HTML_BLOCKED_TEMPLATE_CACHE: - return _HTML_BLOCKED_TEMPLATE_CACHE + return _format_template(_HTML_BLOCKED_TEMPLATE_CACHE, security_response_id) if not need_html_template and _JSON_BLOCKED_TEMPLATE_CACHE: - return _JSON_BLOCKED_TEMPLATE_CACHE + return _format_template(_JSON_BLOCKED_TEMPLATE_CACHE, security_response_id) if need_html_template: template_path = os.getenv("DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML") @@ -353,17 +356,17 @@ def _get_blocked_template(accept_header_value): _HTML_BLOCKED_TEMPLATE_CACHE = content else: _JSON_BLOCKED_TEMPLATE_CACHE = content - return content + return _format_template(content, security_response_id) except (OSError, IOError) as e: # noqa: B014 log.warning("Could not load custom template at %s: %s", template_path, str(e)) # No user-defined template at this point if need_html_template: _HTML_BLOCKED_TEMPLATE_CACHE = BLOCKED_RESPONSE_HTML - return BLOCKED_RESPONSE_HTML + return _format_template(_HTML_BLOCKED_TEMPLATE_CACHE, security_response_id) _JSON_BLOCKED_TEMPLATE_CACHE = BLOCKED_RESPONSE_JSON - return BLOCKED_RESPONSE_JSON + return _format_template(_JSON_BLOCKED_TEMPLATE_CACHE, security_response_id) def parse_form_params(body: str) -> Dict[str, Union[str, List[str]]]: diff --git a/releasenotes/notes/block_id-551e443afd917aa6.yaml b/releasenotes/notes/block_id-551e443afd917aa6.yaml new file mode 100644 index 00000000000..fc88ba94b5b --- /dev/null +++ b/releasenotes/notes/block_id-551e443afd917aa6.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + AAP: This introduces security response id for easy identification of blocking responses. diff --git a/setup.py b/setup.py index 75ab1f457ac..3dfb7dbbcf8 100644 --- a/setup.py +++ b/setup.py @@ -98,7 +98,7 @@ CURRENT_OS = platform.system() -LIBDDWAF_VERSION = "1.29.0" +LIBDDWAF_VERSION = "1.30.0" # DEV: update this accordingly when src/native upgrades libdatadog dependency. # libdatadog v15.0.0 requires rust 1.78. diff --git a/tests/appsec/appsec/test_appsec_utils.py b/tests/appsec/appsec/test_appsec_utils.py index 5c8107d2c77..e158bd74f35 100644 --- a/tests/appsec/appsec/test_appsec_utils.py +++ b/tests/appsec/appsec/test_appsec_utils.py @@ -8,6 +8,10 @@ from tests.utils import override_env +SECID: str = "[security_response_id]" +BLOCK_ID: str = "012038019238-123213" + + @pytest.fixture(autouse=True) def reset_template_caches(): utils._HTML_BLOCKED_TEMPLATE_CACHE = None @@ -21,29 +25,41 @@ def reset_template_caches(): def test_get_blocked_template_no_env_var_html(): with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML="", DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON="")): - assert utils._get_blocked_template("text/html") == BLOCKED_RESPONSE_HTML + assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_HTML, BLOCK_ID + ) def test_get_blocked_template_no_env_var_json(): with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML="", DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON="")): - assert utils._get_blocked_template("other") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("application/json") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template(None) == BLOCKED_RESPONSE_JSON + assert utils._get_blocked_template("other", BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) + assert utils._get_blocked_template("application/json", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_JSON, BLOCK_ID + ) + assert utils._get_blocked_template("", BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) + assert utils._get_blocked_template(None, BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) def test_get_blocked_template_user_file_missing_html(): with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML="missing.html")): - assert utils._get_blocked_template("text/html") == BLOCKED_RESPONSE_HTML - assert utils._get_blocked_template("") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("application/json") == BLOCKED_RESPONSE_JSON + assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_HTML, BLOCK_ID + ) + assert utils._get_blocked_template("", BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) + assert utils._get_blocked_template("application/json", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_JSON, BLOCK_ID + ) def test_get_blocked_template_user_file_missing_json(): with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON="missing.json")): - assert utils._get_blocked_template("") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("application/json") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("text/html") == BLOCKED_RESPONSE_HTML + assert utils._get_blocked_template("", BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) + assert utils._get_blocked_template("application/json", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_JSON, BLOCK_ID + ) + assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_HTML, BLOCK_ID + ) def test_get_blocked_template_user_file_exists_html(): @@ -51,16 +67,20 @@ def test_get_blocked_template_user_file_exists_html(): with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_HTML=template_path)): with open(template_path, "r") as test_template_html: html_content = test_template_html.read() - assert utils._get_blocked_template("text/html") == html_content - assert utils._get_blocked_template("") == BLOCKED_RESPONSE_JSON - assert utils._get_blocked_template("application/json") == BLOCKED_RESPONSE_JSON + assert utils._get_blocked_template("text/html", BLOCK_ID) == html_content + assert utils._get_blocked_template("", BLOCK_ID) == utils._format_template(BLOCKED_RESPONSE_JSON, BLOCK_ID) + assert utils._get_blocked_template("application/json", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_JSON, BLOCK_ID + ) def test_get_blocked_template_user_file_exists_json(): template_path = os.path.join(os.path.dirname(__file__), "blocking_template_json.json") with override_env(dict(DD_APPSEC_HTTP_BLOCKED_TEMPLATE_JSON=template_path)): with open(template_path, "r") as test_template_json: - json_content = test_template_json.read() - assert utils._get_blocked_template("") == json_content - assert utils._get_blocked_template("application/json") == json_content - assert utils._get_blocked_template("text/html") == BLOCKED_RESPONSE_HTML + json_content = utils._format_template(test_template_json.read(), BLOCK_ID) + assert utils._get_blocked_template("", BLOCK_ID) == json_content + assert utils._get_blocked_template("application/json", BLOCK_ID) == json_content + assert utils._get_blocked_template("text/html", BLOCK_ID) == utils._format_template( + BLOCKED_RESPONSE_HTML, BLOCK_ID + ) diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py index ac5a9f6714a..2c0da84a486 100644 --- a/tests/appsec/contrib_appsec/utils.py +++ b/tests/appsec/contrib_appsec/utils.py @@ -17,12 +17,15 @@ from ddtrace.appsec._utils import get_triggers from ddtrace.internal import constants from ddtrace.internal.settings.asm import config as asm_config +from ddtrace.internal.utils.http import _format_template import tests.appsec.rules as rules from tests.utils import DummyTracer from tests.utils import override_env from tests.utils import override_global_config +SECID: str = "[security_response_id]" + try: from ddtrace.appsec import track_user_sdk as _track_user_sdk # noqa: F401 @@ -98,11 +101,12 @@ def check_for_stack_trace(self, entry_span): assert stack_ids == stack_id_in_triggers, f"stack_ids={stack_ids}, stack_id_in_triggers={stack_id_in_triggers}" return exploit - def check_single_rule_triggered(self, rule_id: str, entry_span): + def check_single_rule_triggered(self, rule_id: str, entry_span) -> str: triggers = get_triggers(entry_span()) assert triggers is not None, "no appsec struct in root span" result = [t["rule"]["id"] for t in triggers] assert result == [rule_id], f"result={result}, expected={[rule_id]}" + return triggers[0].get("security_response_id", None) def check_rules_triggered(self, rule_id: List[str], entry_span): triggers = get_triggers(entry_span()) @@ -532,12 +536,14 @@ def test_request_ipblock( response = interface.client.get("/", headers=headers) if blocked and asm_enabled: assert self.status(response) == 403 - assert self.body(response) == getattr(constants, body, None) assert get_entry_span_tag("actor.ip") == rules._IP.BLOCKED assert get_entry_span_tag(http.STATUS_CODE) == "403" assert get_entry_span_tag(http.URL) == "http://localhost:8000/" assert get_entry_span_tag(http.METHOD) == "GET" - self.check_single_rule_triggered("blk-001-001", entry_span) + block_id = self.check_single_rule_triggered("blk-001-001", entry_span) + assert self.body(response) == _format_template(getattr(constants, body, ""), block_id), self.body( + response + ) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == content_type @@ -671,8 +677,8 @@ def test_request_suspicious_request_block_match_method( if asm_enabled and method == "get": assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-006", entry_span) + block_id = self.check_single_rule_triggered("tst-037-006", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -704,8 +710,8 @@ def test_request_suspicious_request_block_match_uri( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-002", entry_span) + block_id = self.check_single_rule_triggered("tst-037-002", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -767,8 +773,8 @@ def test_request_suspicious_request_block_match_path_params( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-007", entry_span) + block_id = self.check_single_rule_triggered("tst-037-007", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -811,8 +817,8 @@ def test_request_suspicious_request_block_match_query_params( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-001", entry_span) + block_id = self.check_single_rule_triggered("tst-037-001", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -850,8 +856,8 @@ def test_request_suspicious_request_block_match_request_headers( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-004", entry_span) + block_id = self.check_single_rule_triggered("tst-037-004", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -889,8 +895,8 @@ def test_request_suspicious_request_block_match_request_cookies( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered("tst-037-008", entry_span) + block_id = self.check_single_rule_triggered("tst-037-008", entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -932,8 +938,8 @@ def test_request_suspicious_request_block_match_response_status( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered(blocked, entry_span) + block_id = self.check_single_rule_triggered(blocked, entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -985,8 +991,8 @@ def test_request_suspicious_request_block_match_response_headers( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered(blocked, entry_span) + block_id = self.check_single_rule_triggered(blocked, entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" @@ -1056,8 +1062,8 @@ def test_request_suspicious_request_block_match_request_body( if asm_enabled and blocked: assert self.status(response) == 403 assert get_entry_span_tag(http.STATUS_CODE) == "403" - assert self.body(response) == constants.BLOCKED_RESPONSE_JSON - self.check_single_rule_triggered(blocked, entry_span) + block_id = self.check_single_rule_triggered(blocked, entry_span) + assert self.body(response) == _format_template(constants.BLOCKED_RESPONSE_JSON, block_id) assert ( get_entry_span_tag(asm_constants.SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES + ".content-type") == "application/json" diff --git a/tests/appsec/integrations/django_tests/test_appsec_django.py b/tests/appsec/integrations/django_tests/test_appsec_django.py index bfbf10ca11c..1786ee542a8 100644 --- a/tests/appsec/integrations/django_tests/test_appsec_django.py +++ b/tests/appsec/integrations/django_tests/test_appsec_django.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import contextlib +import re import pytest @@ -54,8 +55,11 @@ def test_request_block_request_callable(client, test_spans, tracer): ) # Should not block by IP, but the block callable is called directly inside that view assert result.status_code == 403 - as_bytes = bytes(constants.BLOCKED_RESPONSE_JSON, "utf-8") - assert result.content == as_bytes + body = result.content.decode() + body_parsed = re.sub( + r'"security_response_id":"[-0-9a-z]+"', r'"security_response_id":"[security_response_id]"', body + ) + assert body_parsed == constants.BLOCKED_RESPONSE_JSON assert root.get_tag(http.STATUS_CODE) == "403" assert root.get_tag(http.URL) == "http://testserver/appsec/block/" assert root.get_tag(http.METHOD) == "GET" @@ -116,8 +120,11 @@ def test_request_userblock_403(client, test_spans, tracer): client, test_spans, tracer, url="/appsec/checkuser/%s/" % _BLOCKED_USER ) assert result.status_code == 403 - as_bytes = bytes(constants.BLOCKED_RESPONSE_JSON, "utf-8") - assert result.content == as_bytes + body = result.content.decode() + body_parsed = re.sub( + r'"security_response_id":"[-0-9a-z]+"', r'"security_response_id":"[security_response_id]"', body + ) + assert body_parsed == constants.BLOCKED_RESPONSE_JSON, (body_parsed, constants.BLOCKED_RESPONSE_JSON) assert root.get_tag(http.STATUS_CODE) == "403" assert root.get_tag(http.URL) == "http://testserver/appsec/checkuser/%s/" % _BLOCKED_USER assert root.get_tag(http.METHOD) == "GET" diff --git a/tests/appsec/integrations/flask_tests/test_appsec_flask.py b/tests/appsec/integrations/flask_tests/test_appsec_flask.py index c96817145e0..c262aca4f33 100644 --- a/tests/appsec/integrations/flask_tests/test_appsec_flask.py +++ b/tests/appsec/integrations/flask_tests/test_appsec_flask.py @@ -5,6 +5,7 @@ from ddtrace.contrib.internal.sqlite3.patch import patch from ddtrace.ext import http from ddtrace.internal import constants +from ddtrace.internal.utils.http import _format_template from tests.appsec.appsec_utils import flask_server from tests.appsec.integrations.flask_tests.utils import _PORT import tests.appsec.rules as rules @@ -49,7 +50,7 @@ def test_route(): resp = self.client.get("/block", headers={"X-REAL-IP": rules._IP.DEFAULT}) # Should not block by IP but since the route is calling block_request it will be blocked assert resp.status_code == 403 - assert get_response_body(resp) == constants.BLOCKED_RESPONSE_JSON + assert get_response_body(resp) == _format_template(constants.BLOCKED_RESPONSE_JSON, "default") root_span = self.pop_spans()[0] assert root_span.get_tag(http.STATUS_CODE) == "403" assert root_span.get_tag(http.URL) == "http://localhost/block" @@ -71,7 +72,7 @@ def test_route(user_id): self._aux_appsec_prepare_tracer() resp = self.client.get("/checkuser/%s" % _BLOCKED_USER) assert resp.status_code == 403 - assert get_response_body(resp) == constants.BLOCKED_RESPONSE_JSON + assert get_response_body(resp) == _format_template(constants.BLOCKED_RESPONSE_JSON, "default") root_span = self.pop_spans()[0] assert root_span.get_tag(http.STATUS_CODE) == "403" assert root_span.get_tag(http.URL) == "http://localhost/checkuser/%s" % _BLOCKED_USER @@ -83,7 +84,7 @@ def test_route(user_id): resp = self.client.get("/checkuser/%s" % _BLOCKED_USER, headers={"Accept": "text/html"}) assert resp.status_code == 403 - assert get_response_body(resp) == constants.BLOCKED_RESPONSE_HTML + assert get_response_body(resp) == _format_template(constants.BLOCKED_RESPONSE_HTML, "default") resp = self.client.get("/checkuser/%s" % _ALLOWED_USER, headers={"Accept": "text/html"}) assert resp.status_code == 200 diff --git a/tests/appsec/integrations/flask_tests/test_appsec_flask_telemetry.py b/tests/appsec/integrations/flask_tests/test_appsec_flask_telemetry.py index 2dfb2df190e..6eb00886678 100644 --- a/tests/appsec/integrations/flask_tests/test_appsec_flask_telemetry.py +++ b/tests/appsec/integrations/flask_tests/test_appsec_flask_telemetry.py @@ -1,8 +1,10 @@ +import json from urllib.parse import urlencode import pytest from ddtrace.internal.constants import BLOCKED_RESPONSE_JSON +from ddtrace.internal.utils.http import _format_template from tests.appsec.appsec.test_telemetry import _assert_generate_metrics import tests.appsec.rules as rules from tests.contrib.flask import BaseFlaskTestCase @@ -24,7 +26,9 @@ def test_telemetry_metrics_block(self): resp = self.client.get("/", headers={"X-Real-Ip": rules._IP.BLOCKED}) assert resp.status_code == 403 if hasattr(resp, "text"): - assert resp.text == BLOCKED_RESPONSE_JSON + assert resp.text + content = json.loads(resp.text) + assert resp.text == _format_template(BLOCKED_RESPONSE_JSON, content["security_response_id"]) _assert_generate_metrics( self.telemetry_writer._namespace.flush(), diff --git a/tests/contrib/django/test_django_appsec_snapshots.py b/tests/contrib/django/test_django_appsec_snapshots.py index e5ec5610e35..cefc0076d65 100644 --- a/tests/contrib/django/test_django_appsec_snapshots.py +++ b/tests/contrib/django/test_django_appsec_snapshots.py @@ -1,5 +1,6 @@ from contextlib import contextmanager import os +import re import subprocess import sys @@ -79,6 +80,7 @@ def daphne_client(django_asgi, additional_env=None): "meta." + FINGERPRINTING.ENDPOINT, "meta." + FINGERPRINTING.SESSION, "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ] ) def test_appsec_enabled(): @@ -112,6 +114,7 @@ def test_appsec_enabled(): "meta." + FINGERPRINTING.ENDPOINT, "meta." + FINGERPRINTING.SESSION, "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ] ) def test_appsec_enabled_attack(): @@ -139,6 +142,7 @@ def test_appsec_enabled_attack(): APPSEC_JSON_TAG, "metrics._dd.appsec.event_rules.loaded", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ] ) def test_request_ipblock_nomatch_200(): @@ -176,6 +180,7 @@ def test_request_ipblock_nomatch_200(): "metrics._dd.appsec.rasp.rule.eval", "metrics._dd.appsec.event_rules.loaded", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ] ) def test_request_ipblock_match_403(): @@ -194,8 +199,9 @@ def test_request_ipblock_match_403(): }, ) assert result.status_code == 403 - as_bytes = bytes(constants.BLOCKED_RESPONSE_HTML, "utf-8") - assert result.content == as_bytes + body = result.content.decode() + body_parsed = re.sub(r"Response ID: [-0-9a-z]+", r"Response ID: [security_response_id]", body) + assert body_parsed == constants.BLOCKED_RESPONSE_HTML @pytest.mark.skipif( @@ -219,6 +225,7 @@ def test_request_ipblock_match_403(): "metrics._dd.appsec.rasp.rule.eval", "metrics._dd.appsec.event_rules.loaded", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ] ) def test_request_ipblock_match_403_json(): @@ -236,5 +243,8 @@ def test_request_ipblock_match_403_json(): }, ) assert result.status_code == 403 - as_bytes = bytes(constants.BLOCKED_RESPONSE_JSON, "utf-8") - assert result.content == as_bytes + body = result.content.decode() + body_parsed = re.sub( + r'"security_response_id":"[-0-9a-z]+"', r'"security_response_id":"[security_response_id]"', body + ) + assert body_parsed == constants.BLOCKED_RESPONSE_JSON diff --git a/tests/contrib/flask/test_appsec_flask_snapshot.py b/tests/contrib/flask/test_appsec_flask_snapshot.py index 959b7e52127..65359ceaaf1 100644 --- a/tests/contrib/flask/test_appsec_flask_snapshot.py +++ b/tests/contrib/flask/test_appsec_flask_snapshot.py @@ -12,8 +12,6 @@ from ddtrace.appsec._constants import APPSEC from ddtrace.contrib.internal.flask.patch import flask_version -from ddtrace.internal.constants import BLOCKED_RESPONSE_HTML -from ddtrace.internal.constants import BLOCKED_RESPONSE_JSON from ddtrace.internal.utils.retry import RetryError import tests.appsec.rules as rules from tests.webclient import Client @@ -133,6 +131,7 @@ def flask_client(flask_command, flask_port, flask_wsgi_application, flask_env_ar "metrics._dd.appsec.waf.duration_ext", "meta.span.kind", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -140,10 +139,6 @@ def flask_client(flask_command, flask_port, flask_wsgi_application, flask_env_ar def test_flask_ipblock_match_403(flask_client): resp = flask_client.get("/", headers={"X-Real-Ip": rules._IP.BLOCKED, "ACCEPT": "text/html"}) assert resp.status_code == 403 - if hasattr(resp, "text"): - assert resp.text == BLOCKED_RESPONSE_HTML - else: - assert resp.data == BLOCKED_RESPONSE_HTML.encode("utf-8") @pytest.mark.snapshot( @@ -168,6 +163,7 @@ def test_flask_ipblock_match_403(flask_client): "metrics._dd.appsec.waf.duration_ext", "meta.span.kind", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -175,10 +171,6 @@ def test_flask_ipblock_match_403(flask_client): def test_flask_ipblock_match_403_json(flask_client): resp = flask_client.get("/", headers={"X-Real-Ip": rules._IP.BLOCKED}) assert resp.status_code == 403 - if hasattr(resp, "text"): - assert resp.text == BLOCKED_RESPONSE_JSON - else: - assert resp.data == BLOCKED_RESPONSE_JSON.encode("utf-8") @pytest.mark.snapshot( @@ -202,6 +194,7 @@ def test_flask_ipblock_match_403_json(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -209,10 +202,6 @@ def test_flask_ipblock_match_403_json(flask_client): def test_flask_userblock_match_403_json(flask_client): resp = flask_client.get("/checkuser/%s" % _BLOCKED_USER) assert resp.status_code == 403 - if hasattr(resp, "text"): - assert resp.text == BLOCKED_RESPONSE_JSON - else: - assert resp.data == BLOCKED_RESPONSE_JSON.encode("utf-8") @pytest.mark.snapshot( @@ -236,6 +225,7 @@ def test_flask_userblock_match_403_json(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -266,6 +256,7 @@ def test_flask_userblock_match_200_json(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -297,6 +288,7 @@ def test_flask_processexec_ossystem(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -329,6 +321,7 @@ def test_flask_processexec_osspawn(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) @@ -360,6 +353,7 @@ def test_flask_processexec_subprocesscommunicateshell(flask_client): "metrics._dd.appsec.waf.duration", "metrics._dd.appsec.waf.duration_ext", "meta._dd.appsec.rc_products", + "meta.http.response.headers.content-length", ], variants={"220": flask_version >= (2, 2, 0), "": flask_version < (2, 2, 0)}, ) diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json index b0dbf5f223c..df035acf3b6 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_body_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.14.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.origin": "appsec", "_dd.p.dm": "-5", "_dd.p.ts": "02", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json index 278e77e6820..554bde0aa9a 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_cookies_no_collection_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.14.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.origin": "appsec", "_dd.p.dm": "-5", "_dd.p.ts": "02", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json index a05a18a7ae0..cefa16d2854 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.version": "1.14.2", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"nfd-000-006\",\n \"name\": \"Detect failed attempt to fetch sensitive files\",\n \"tags\": {\n \"capec\": \"1000/118/169\",\n \"category\": \"attack_attempt\",\n \"confidence\": \"1\",\n \"cwe\": \"200\",\n \"type\": \"security_scanner\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"^404$\",\n \"parameters\": [\n {\n \"address\": \"server.response.status\",\n \"highlight\": [\n \"404\"\n ],\n \"key_path\": [],\n \"value\": \"404\"\n }\n ]\n },\n {\n \"operator\": \"match_regex\",\n \"operator_value\": \"\\\\.(cgi|bat|dll|exe|key|cert|crt|pem|der|pkcs|pkcs|pkcs[0-9]*|nsf|jsa|war|java|class|vb|vba|so|git|svn|hg|cvs)([^a-zA-Z0-9_]|$)\",\n \"parameters\": [\n {\n \"address\": \"server.request.uri.raw\",\n \"highlight\": [\n \".git\"\n ],\n \"key_path\": [],\n \"value\": \"/.git\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "tests.appsec.appsec", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json index 43e594f9f5e..cdacd2d1550 100644 --- a/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json +++ b/tests/snapshots/tests.appsec.appsec.test_processor.test_appsec_span_tags_snapshot_with_errors.json @@ -10,7 +10,7 @@ "meta": { "_dd.appsec.event_rules.errors": "{\"missing key 'conditions'\": [\"crs-913-110\"], \"missing key 'tags'\": [\"crs-942-100\"]}", "_dd.appsec.event_rules.version": "5.5.5", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "tests.appsec.appsec", "_dd.p.dm": "-0", "_dd.runtime_family": "python", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json index f35a688d9ea..526b0b30a45 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "1.14.2", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index bc06b6475be..8f2fc75b91a 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -14,7 +14,7 @@ "_dd.appsec.fp.http.network": "net-0-0000000000", "_dd.appsec.fp.session": "ssn----", "_dd.appsec.rc_products": "[] u:0 r:2", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json index b939c79f57d..71ee59c5935 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":10192376353237234254}]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json index f0a9cb3c3dc..911de13bf0d 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_match_403_json.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[{\"rule\":{\"id\":\"blk-001-001\",\"name\":\"Block IP addresses\",\"on_match\":[\"block\"],\"tags\":{\"category\":\"blocking\",\"type\":\"ip_addresses\"}},\"rule_matches\":[{\"operator\":\"ip_match\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"http.client_ip\",\"key_path\":[],\"value\":\"8.8.4.4\",\"highlight\":[\"8.8.4.4\"]}]}],\"span_id\":865087550764298227}]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json index 0b51aced1dd..07eb1460e87 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_request_ipblock_nomatch_200.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json index 14ca6d4eb2e..e4d3d70eebb 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json index 39c79045d9a..b004511a626 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json index 530f7ac4650..5908a42da80 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json index 2096ee24ac2..6fcefc50a2f 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_ipblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-001\",\n \"name\": \"Block IP addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"blocking\",\n \"type\": \"ip_addresses\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"ip_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"http.client_ip\",\n \"highlight\": [\n \"8.8.4.4\"\n ],\n \"key_path\": [],\n \"value\": \"8.8.4.4\"\n }\n ]\n }\n ]\n }\n]}", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json index 62638eb4f0d..0b32987b1a6 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json index 98702dc3a4f..bf3ec3120b7 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_osspawn[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json index fde603dcda1..f718c7ac36f 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json index 834eeb17f91..f098028f22e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_ossystem[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json index d3a11bf55a4..650c70becc8 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json index 439bad7214d..eb0f8d894f5 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicatenoshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json index b1103ad83bc..337d1d953e1 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env].json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json index 04f69a84a12..b152c5bb100 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_processexec_subprocesscommunicateshell[flask_appsec_good_rules_env]_220.json @@ -10,7 +10,7 @@ "error": 0, "meta": { "_dd.appsec.event_rules.version": "rules_good", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json index a4844d23951..2c08944d99f 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env].json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.user.collection_mode": "sdk", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json index 472321b0b97..34cde7ccd3e 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_200_json[flask_appsec_good_rules_env]_220.json @@ -11,7 +11,7 @@ "meta": { "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.user.collection_mode": "sdk", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.p.dm": "-0", "_dd.p.tid": "654a694400000000", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json index 2bdc196a8d8..5b450f10604 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env].json @@ -12,7 +12,7 @@ "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.user.collection_mode": "sdk", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", diff --git a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json index dc8c69abb2d..4adccbc9a6c 100644 --- a/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json +++ b/tests/snapshots/tests.contrib.flask.test_appsec_flask_snapshot.test_flask_userblock_match_403_json[flask_appsec_good_rules_env]_220.json @@ -12,7 +12,7 @@ "_dd.appsec.event_rules.version": "rules_good", "_dd.appsec.json": "{\"triggers\":[\n {\n \"rule\": {\n \"id\": \"blk-001-002\",\n \"name\": \"Block User Addresses\",\n \"on_match\": [\n \"block\"\n ],\n \"tags\": {\n \"category\": \"security_response\",\n \"type\": \"block_user\"\n }\n },\n \"rule_matches\": [\n {\n \"operator\": \"exact_match\",\n \"operator_value\": \"\",\n \"parameters\": [\n {\n \"address\": \"usr.id\",\n \"highlight\": [\n \"123456\"\n ],\n \"key_path\": [],\n \"value\": \"123456\"\n }\n ]\n }\n ]\n }\n]}", "_dd.appsec.user.collection_mode": "sdk", - "_dd.appsec.waf.version": "1.29.0", + "_dd.appsec.waf.version": "1.30.0", "_dd.base_service": "", "_dd.origin": "appsec", "_dd.p.dm": "-5", From 18b79e8243300c04d500e567c0596c9116c9a31d Mon Sep 17 00:00:00 2001 From: "T. Kowalski" Date: Fri, 7 Nov 2025 14:31:05 +0100 Subject: [PATCH 36/42] fix(profiling): track running asyncio loop if it exists (#15120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description https://datadoghq.atlassian.net/browse/PROF-12842 This PR updates the wrapping and thread-registering logic for the Profiler in order to track the running loop when it exists. This is needed because otherwise, importing/starting the Profiler after starting a Task (or a loop more generally) will make us blind to the existing running loop. Currently, we `wrap` the `asyncio.set_event_loop` function to capture when the Event Loop is first set (or is swapped). However, if the `_asyncio` module that sets up wrapping is imported/executed _after_ the loop has been set, we will miss that first call to `set_event_loop` and be blind to `asyncio` Tasks until the Event Loop is changed (which in many cases never happens). Note that we also need to execute the "find loop and track it" logic when we start the Profiler generally speaking, as in this case we may have tried (earlier) to call `track_event_loop` but that would have failed as no thread was registered in the Profiler. I added four tests that account for various edge cases. Unfortunately, currently, two of them fail (marked them as `xfail`) and there is no way to correctly fix them. The issue is that we can only get _the current running loop_ and not _the current (non-running) event loop_. In other words, if an event loop is created and set in `asyncio`, and immediately after the Profiler is started without a Task having first been started, we will not be able to see that loop from the initialisation code and we will thus not be able to observe it from the Profiler thread. In short, what works is the most common case: * ✅ Import Profiler, start Profiler, import asyncio, start Tasks * ✅ Import asyncio, Import Profiler, start Profiler, start Tasks * ✅ Import asyncio, Import Profiler, start Tasks (from within the Tasks) * 🚫 Import asyncio, Import Profiler, create (non running) event loop, start Profiler, start Task * 🚫 Import asyncio, Import Profiler, create (non running) event loop, create Task, start Profiler It is OK to start with that as I really consider the latter two to be edge cases. **Example: today we miss all `asyncio` data with the following code** ```py # 0. Profiler is NOT imported here, no watching is set up import os import asyncio async def my_coroutine(n): await asyncio.sleep(n) # 0. Function is defined, not run, Profiler is still not imported async def main(): # 3. We get here, import the Profiler module (and _asyncio as well) # We also start watching for set_event_loop_calls – we don't see the existing loop from ddtrace.profiling import Profiler prof = Profiler() prof.start() # Should be as early as possible, eg before other imports, to ensure everything is profiled EXECUTION_TIME_SEC = int(os.environ.get("EXECUTION_TIME_SEC", "2")) t = asyncio.create_task(my_coroutine(EXECUTION_TIME_SEC / 2)) await asyncio.gather(t, my_coroutine(EXECUTION_TIME_SEC)) # 4. Interestingly, we detect a set_event_loop call here, but it's # being set to None before exiting # 1. This is executed first if __name__ == "__main__": # 2. This implicitly creates and set the Event Loop asyncio.run(main()) ``` ## Testing I have tested this in `prof-correctness` (initially just replicated that it _did not_ work) and it now works as expected. I will be adding more correctness tests, one with a "top of file" import and Profiler start, one with a "top of file import" and "in-code Profiler start", and one with both an "in-code file import" and "in-code Profiler start". I also added four new tests to make sure we catch different edge cases with order of imports and order of task/profiler starts. Currently, two of them are marked as `XFAILED` because there is no way to reliably make them pass. --- .../datadog/profiling/stack_v2/__init__.pyi | 2 +- ddtrace/profiling/_asyncio.py | 58 +- ddtrace/profiling/collector/threading.py | 5 + ...-existing-event-loop-6842ff15328dae9c.yaml | 4 + .../collector/test_stack_asyncio.py | 758 ++++++++++++++++++ 5 files changed, 815 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/profiling-fix-untracked-existing-event-loop-6842ff15328dae9c.yaml diff --git a/ddtrace/internal/datadog/profiling/stack_v2/__init__.pyi b/ddtrace/internal/datadog/profiling/stack_v2/__init__.pyi index 1b80c96033f..16ab608f206 100644 --- a/ddtrace/internal/datadog/profiling/stack_v2/__init__.pyi +++ b/ddtrace/internal/datadog/profiling/stack_v2/__init__.pyi @@ -4,7 +4,7 @@ from typing import Optional, Sequence, Union def register_thread(id: int, native_id: int, name: str) -> None: ... # noqa: A002 def unregister_thread(name: str) -> None: ... -def track_asyncio_loop(thread_id: int, loop: asyncio.AbstractEventLoop) -> None: ... +def track_asyncio_loop(thread_id: int, loop: Optional[asyncio.AbstractEventLoop]) -> None: ... def link_tasks(parent: asyncio.AbstractEventLoop, child: asyncio.Task) -> None: ... def init_asyncio( current_tasks: Sequence[asyncio.Task], diff --git a/ddtrace/profiling/_asyncio.py b/ddtrace/profiling/_asyncio.py index 8176c5daf9f..b307917aa39 100644 --- a/ddtrace/profiling/_asyncio.py +++ b/ddtrace/profiling/_asyncio.py @@ -20,6 +20,8 @@ THREAD_LINK = None # type: typing.Optional[_threading._ThreadLink] +ASYNCIO_IMPORTED = False + def current_task(loop: typing.Union["asyncio.AbstractEventLoop", None] = None) -> typing.Union["asyncio.Task", None]: return None @@ -35,10 +37,51 @@ def _task_get_name(task: "asyncio.Task") -> str: return "Task-%d" % id(task) +def _call_init_asyncio(asyncio: ModuleType) -> None: + from asyncio import tasks as asyncio_tasks + + if sys.hexversion >= 0x030C0000: + scheduled_tasks = asyncio_tasks._scheduled_tasks.data # type: ignore[attr-defined] + eager_tasks = asyncio_tasks._eager_tasks # type: ignore[attr-defined] + else: + scheduled_tasks = asyncio_tasks._all_tasks.data # type: ignore[attr-defined] + eager_tasks = None + + stack_v2.init_asyncio(asyncio_tasks._current_tasks, scheduled_tasks, eager_tasks) # type: ignore[attr-defined] + + +def link_existing_loop_to_current_thread() -> None: + global ASYNCIO_IMPORTED + + # Only proceed if asyncio is actually imported and available + # Don't rely solely on ASYNCIO_IMPORTED global since it persists across forks + if not ASYNCIO_IMPORTED or "asyncio" not in sys.modules: + return + + import asyncio + + # Only track if there's actually a running loop + running_loop: typing.Union["asyncio.AbstractEventLoop", None] = None + try: + running_loop = asyncio.get_running_loop() + except RuntimeError: + # No existing loop to track, nothing to do + return + + # We have a running loop, track it + assert THREAD_LINK is not None # nosec: assert is used for typing + THREAD_LINK.clear_threads(set(sys._current_frames().keys())) + THREAD_LINK.link_object(running_loop) + stack_v2.track_asyncio_loop(typing.cast(int, ddtrace_threading.current_thread().ident), running_loop) + _call_init_asyncio(asyncio) + + @ModuleWatchdog.after_module_imported("asyncio") -def _(asyncio): - # type: (ModuleType) -> None +def _(asyncio: ModuleType) -> None: global THREAD_LINK + global ASYNCIO_IMPORTED + + ASYNCIO_IMPORTED = True if hasattr(asyncio, "current_task"): globals()["current_task"] = asyncio.current_task @@ -55,7 +98,7 @@ def _(asyncio): if THREAD_LINK is None: THREAD_LINK = _threading._ThreadLink() - init_stack_v2 = config.stack.v2_enabled and stack_v2.is_available + init_stack_v2: bool = config.stack.v2_enabled and stack_v2.is_available @partial(wrap, sys.modules["asyncio.events"].BaseDefaultEventLoopPolicy.set_event_loop) def _(f, args, kwargs): @@ -89,14 +132,7 @@ def _(f, args, kwargs): for child in children: stack_v2.link_tasks(parent, child) - if sys.hexversion >= 0x030C0000: - scheduled_tasks = asyncio.tasks._scheduled_tasks.data - eager_tasks = asyncio.tasks._eager_tasks - else: - scheduled_tasks = asyncio.tasks._all_tasks.data - eager_tasks = None - - stack_v2.init_asyncio(asyncio.tasks._current_tasks, scheduled_tasks, eager_tasks) + _call_init_asyncio(asyncio) def get_event_loop_for_thread(thread_id: int) -> typing.Union["asyncio.AbstractEventLoop", None]: diff --git a/ddtrace/profiling/collector/threading.py b/ddtrace/profiling/collector/threading.py index 5fd530f9a49..28a4b49fc96 100644 --- a/ddtrace/profiling/collector/threading.py +++ b/ddtrace/profiling/collector/threading.py @@ -68,3 +68,8 @@ def thread_bootstrap_inner(self, *args, **kwargs): # Instrument any living threads for thread_id, thread in ddtrace_threading._active.items(): # type: ignore[attr-defined] stack_v2.register_thread(thread_id, thread.native_id, thread.name) + + # Import _asyncio to ensure asyncio post-import wrappers are initialised + from ddtrace.profiling import _asyncio # noqa: F401 + + _asyncio.link_existing_loop_to_current_thread() diff --git a/releasenotes/notes/profiling-fix-untracked-existing-event-loop-6842ff15328dae9c.yaml b/releasenotes/notes/profiling-fix-untracked-existing-event-loop-6842ff15328dae9c.yaml new file mode 100644 index 00000000000..65fbc824b89 --- /dev/null +++ b/releasenotes/notes/profiling-fix-untracked-existing-event-loop-6842ff15328dae9c.yaml @@ -0,0 +1,4 @@ +fixes: + - | + profiling: this fix resolves an issue where importing the profiler module after an asyncio Event Loop had been + started would make the Profiler blind to the existing Event Loop and its Tasks. \ No newline at end of file diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling_v2/collector/test_stack_asyncio.py index 4f6eff7482a..a6375569b9d 100644 --- a/tests/profiling_v2/collector/test_stack_asyncio.py +++ b/tests/profiling_v2/collector/test_stack_asyncio.py @@ -119,3 +119,761 @@ async def hello(): ], ), ) + + +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_start_profiler_from_process_before_importing_asyncio", + ), + err=None, +) +# For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test) +def test_asyncio_start_profiler_from_process_before_importing_asyncio(): + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + import asyncio + import os + import sys + import time + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + 2 + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) + + +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_start_profiler_from_process_before_starting_loop", + ), + err=None, +) +# For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test) +def test_asyncio_start_profiler_from_process_before_starting_loop(): + import asyncio + import os + import sys + import time + + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + 2 + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) + + +@pytest.mark.xfail(reason="This test fails because there's no way to get the current loop if it's not already running.") +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_start_profiler_from_process_after_creating_loop", + ), + err=None, +) +# For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test) +def test_asyncio_start_profiler_from_process_after_creating_loop(): + import asyncio + import os + import sys + import time + + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + 2 + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) + + +@pytest.mark.xfail(reason="This test fails because there's no way to get the current loop if it's not already running.") +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_import_profiler_from_process_after_starting_loop", + ), + err=None, +) +# For macOS: err=None ignores expected stderr from tracer failing to connect to agent (not relevant to this test) +def test_asyncio_import_profiler_from_process_after_starting_loop(): + import asyncio + import os + import sys + import time + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) + + +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_start_profiler_from_process_after_creating_loop_and_task", + ), + err=None, +) +def test_asyncio_start_profiler_from_process_after_task_start(): + # NOW import profiling modules - this should track the existing loop + import asyncio + import os + import sys + import time + + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Start profiler after loop is already running + assert asyncio.get_running_loop() is loop + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, p, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, p, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) + + +@pytest.mark.subprocess( + env=dict( + DD_PROFILING_OUTPUT_PPROF="/tmp/test_asyncio_start_profiler_from_process_after_task_start", + ), + err=None, +) +def test_asyncio_import_and_start_profiler_from_process_after_task_start(): + import asyncio + import os + import sys + import time + + # Start an asyncio loop BEFORE importing profiler modules + # This simulates the bug scenario where a loop exists before profiling is enabled + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + async def my_function(): + async def background_task_func() -> None: + """Background task that runs in the existing loop.""" + await asyncio.sleep(1.5) + + # Create and start a task in the existing loop + background_task = loop.create_task(background_task_func(), name="background") + assert background_task is not None + + # Start profiler after loop is already running + assert asyncio.get_running_loop() is loop + + # NOW import profiling modules - this should track the existing loop + from ddtrace.internal.datadog.profiling import stack_v2 + from ddtrace.profiling import profiler + + assert stack_v2.is_available, stack_v2.failure_msg + + p = profiler.Profiler() + assert p._profiler._stack_v2_enabled + p.start() + + # Run tasks that should be tracked + sleep_time = 0.2 + loop_run_time = 0.75 + + async def tracked_task() -> None: + start_time = time.time() + while time.time() < start_time + loop_run_time: + await asyncio.sleep(sleep_time) + + async def main_task(): + t1 = asyncio.create_task(tracked_task(), name="tracked 1") + t2 = asyncio.create_task(tracked_task(), name="tracked 2") + await tracked_task() + await asyncio.sleep(0.25) + return t1, t2 + + result = await main_task() + + await background_task + + return tracked_task, background_task_func, p, result + + main_task = loop.create_task(my_function(), name="main") + tracked_task_def, background_task_def, p, (t1, t2) = loop.run_until_complete(main_task) + + p.stop() + + t1_name = t1.get_name() + t2_name = t2.get_name() + + assert t1_name == "tracked 1" + assert t2_name == "tracked 2" + + from tests.profiling.collector import pprof_utils + + output_filename = os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid()) + profile = pprof_utils.parse_newest_profile(output_filename) + + samples = pprof_utils.get_samples_with_label_key(profile, "task name") + assert len(samples) > 0, "No task names found - existing loop was not tracked!" + + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_BACKGROUND = f"{my_function.__name__}..{background_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_BACKGROUND = background_task_def.__name__ + EXPECTED_FILENAME_BACKGROUND = os.path.basename(background_task_def.__code__.co_filename) + EXPECTED_LINE_NO_BACKGROUND = background_task_def.__code__.co_firstlineno + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name="background", + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_BACKGROUND, + filename=EXPECTED_FILENAME_BACKGROUND, + line_no=EXPECTED_LINE_NO_BACKGROUND, + ), + ], + ), + ) + + # Verify specific tasks are in the profile + if sys.version_info >= (3, 11): + EXPECTED_FUNCTION_NAME_TRACKED = f"{my_function.__name__}..{tracked_task_def.__name__}" + else: + EXPECTED_FUNCTION_NAME_TRACKED = tracked_task_def.__name__ + EXPECTED_FILENAME_TRACKED = os.path.basename(tracked_task_def.__code__.co_filename) + EXPECTED_LINE_NO_TRACKED = tracked_task_def.__code__.co_firstlineno + 3 + + pprof_utils.assert_profile_has_sample( + profile, + samples, + expected_sample=pprof_utils.StackEvent( + thread_name="MainThread", + task_name=t1_name, + locations=[ + pprof_utils.StackLocation( + function_name=EXPECTED_FUNCTION_NAME_TRACKED, + filename=EXPECTED_FILENAME_TRACKED, + line_no=EXPECTED_LINE_NO_TRACKED, + ) + ], + ), + ) From ecc2de83116eb7a6108b941809d0c1f24fed735f Mon Sep 17 00:00:00 2001 From: Alberto Vara Date: Fri, 7 Nov 2025 14:36:11 +0100 Subject: [PATCH 37/42] chore(iast): fix iast multiprocess issues and add mcp streaming tests (#15141) This PR addresses a IAST stability issues in multiprocess contexts and adds comprehensive validation for streaming request handling with MCP (Model Context Protocol) servers - **IAST Disabled in Subprocesses:** When an active request runs in a subprocess with open socket connections (e.g., streaming endpoints), IAST experienced memory corruption and segmentation faults - **Improved Code Injection Detection:** Enhanced retrieval of globals and locals in the `eval()` function wrapper - **MCP Server Streaming Validation:** Added comprehensive test suite for MCP servers (Model Context Protocol) with FastAPI. Validated IAST functionality with: - HTTP/SSE (Server-Sent Events) bidirectional streaming - In-memory MCP connections - Multiple concurrent streaming operations - Header tainting through streaming requests - Vulnerability detection (CMDI) during streaming - **Streaming Request Safety**: Validates IAST handles streaming responses without crashes - **MCP Protocol Support**: Full test coverage for MCP servers (critical for AI/LLM applications) - **Subprocess Stability**: Prevents memory corruption in subprocess contexts - **Better Code Injection Detection**: Enhanced eval() wrapper for improved vulnerability detection --- .riot/requirements/1021fa1.txt | 43 -- .../requirements/{19fed8a.txt => 10f75ab.txt} | 32 +- .../requirements/{1828fcc.txt => 1235f1e.txt} | 14 +- .riot/requirements/1489f78.txt | 56 ++ .riot/requirements/1554154.txt | 41 -- .riot/requirements/18474a9.txt | 40 ++ .../requirements/{1a0cd9b.txt => 196a8f0.txt} | 32 +- .../requirements/{a0d16bc.txt => 197fd3a.txt} | 15 +- .riot/requirements/19df299.txt | 41 -- .../requirements/{5368b32.txt => 1a4e2ef.txt} | 15 +- .../requirements/{9a1f267.txt => 1acb1cd.txt} | 15 +- .../requirements/{731d25f.txt => 1af899c.txt} | 18 +- .../requirements/{68a2945.txt => 1c3d896.txt} | 39 +- .../requirements/{1ef982c.txt => 1cd7717.txt} | 15 +- .../requirements/{38066f4.txt => 1e050b8.txt} | 15 +- .../requirements/{1c28390.txt => 1e9125b.txt} | 32 +- .riot/requirements/1fbd93b.txt | 43 -- .riot/requirements/30d009a.txt | 40 ++ .../requirements/{393e41d.txt => 31333df.txt} | 18 +- .riot/requirements/34febf3.txt | 56 ++ .../requirements/{e8dd8b5.txt => 70806d5.txt} | 37 +- .../requirements/{10b37a9.txt => e385023.txt} | 15 +- .../requirements/{1b3095b.txt => e86781c.txt} | 35 +- .../requirements/{e05f887.txt => f444071.txt} | 14 +- .riot/requirements/fd02a34.txt | 59 ++ ddtrace/appsec/_iast/__init__.py | 80 +++ ddtrace/appsec/_iast/_ast/ast_patching.py | 2 +- .../_iast/taint_sinks/code_injection.py | 48 +- riotfile.py | 7 +- .../test_code_injection_inspect_regression.py | 262 ++++++++ .../test_anyio_thread_safety.py | 151 +++++ .../test_initializer_thread_safety.py | 114 ++++ .../test_multiprocessing_tracer_iast_env.py | 63 +- .../test_native_taint_range_fork.py | 335 ---------- .../test_native_taint_range_gevent_fork.py | 520 ---------------- .../iast/test_fork_handler_regression.py | 357 +++++++++++ .../test_multiprocessing_eval_integration.py | 212 +++++++ .../appsec/integrations/fastapi_tests/app.py | 33 + .../integrations/fastapi_tests/mcp_app.py | 127 ++++ .../test_iast_fastapi_testagent.py | 111 +++- .../fastapi_tests/test_iast_mcp_testagent.py | 585 ++++++++++++++++++ tests/appsec/suitespec.yml | 2 +- 42 files changed, 2578 insertions(+), 1211 deletions(-) delete mode 100644 .riot/requirements/1021fa1.txt rename .riot/requirements/{19fed8a.txt => 10f75ab.txt} (52%) rename .riot/requirements/{1828fcc.txt => 1235f1e.txt} (81%) create mode 100644 .riot/requirements/1489f78.txt delete mode 100644 .riot/requirements/1554154.txt create mode 100644 .riot/requirements/18474a9.txt rename .riot/requirements/{1a0cd9b.txt => 196a8f0.txt} (52%) rename .riot/requirements/{a0d16bc.txt => 197fd3a.txt} (68%) delete mode 100644 .riot/requirements/19df299.txt rename .riot/requirements/{5368b32.txt => 1a4e2ef.txt} (80%) rename .riot/requirements/{9a1f267.txt => 1acb1cd.txt} (68%) rename .riot/requirements/{731d25f.txt => 1af899c.txt} (68%) rename .riot/requirements/{68a2945.txt => 1c3d896.txt} (51%) rename .riot/requirements/{1ef982c.txt => 1cd7717.txt} (68%) rename .riot/requirements/{38066f4.txt => 1e050b8.txt} (80%) rename .riot/requirements/{1c28390.txt => 1e9125b.txt} (58%) delete mode 100644 .riot/requirements/1fbd93b.txt create mode 100644 .riot/requirements/30d009a.txt rename .riot/requirements/{393e41d.txt => 31333df.txt} (67%) create mode 100644 .riot/requirements/34febf3.txt rename .riot/requirements/{e8dd8b5.txt => 70806d5.txt} (54%) rename .riot/requirements/{10b37a9.txt => e385023.txt} (68%) rename .riot/requirements/{1b3095b.txt => e86781c.txt} (51%) rename .riot/requirements/{e05f887.txt => f444071.txt} (81%) create mode 100644 .riot/requirements/fd02a34.txt create mode 100644 tests/appsec/iast/taint_sinks/test_code_injection_inspect_regression.py create mode 100644 tests/appsec/iast/taint_tracking/test_anyio_thread_safety.py create mode 100644 tests/appsec/iast/taint_tracking/test_initializer_thread_safety.py delete mode 100644 tests/appsec/iast/taint_tracking/test_native_taint_range_fork.py delete mode 100644 tests/appsec/iast/taint_tracking/test_native_taint_range_gevent_fork.py create mode 100644 tests/appsec/iast/test_fork_handler_regression.py create mode 100644 tests/appsec/iast/test_multiprocessing_eval_integration.py create mode 100644 tests/appsec/integrations/fastapi_tests/mcp_app.py create mode 100644 tests/appsec/integrations/fastapi_tests/test_iast_mcp_testagent.py diff --git a/.riot/requirements/1021fa1.txt b/.riot/requirements/1021fa1.txt deleted file mode 100644 index e14b2cfae78..00000000000 --- a/.riot/requirements/1021fa1.txt +++ /dev/null @@ -1,43 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.9 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1021fa1.in -# -annotated-types==0.7.0 -anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.1.8 -coverage[toml]==7.10.7 -exceptiongroup==1.3.0 -fastapi==0.118.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==3.0.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-multipart==0.0.20 -requests==2.32.5 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.48.0 -tomli==2.2.1 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -urllib3==2.5.0 -uvicorn==0.33.0 diff --git a/.riot/requirements/19fed8a.txt b/.riot/requirements/10f75ab.txt similarity index 52% rename from .riot/requirements/19fed8a.txt rename to .riot/requirements/10f75ab.txt index f5870d782b2..0b44732d09b 100644 --- a/.riot/requirements/19fed8a.txt +++ b/.riot/requirements/10f75ab.txt @@ -2,38 +2,52 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/19fed8a.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/10f75ab.in # annotated-types==0.7.0 anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 +cryptography==46.0.3 fastapi==0.114.2 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 +httpx-sse==0.4.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 markupsafe==3.0.3 +mcp==1.20.0 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 pygments==2.19.2 +pyjwt[crypto]==2.10.1 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 +python-dotenv==1.2.1 python-multipart==0.0.20 +referencing==0.37.0 requests==2.32.5 +rpds-py==0.28.0 sniffio==1.3.1 sortedcontainers==2.4.0 +sse-starlette==3.0.3 starlette==0.38.6 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/1828fcc.txt b/.riot/requirements/1235f1e.txt similarity index 81% rename from .riot/requirements/1828fcc.txt rename to .riot/requirements/1235f1e.txt index e38924269d0..42076a649d7 100644 --- a/.riot/requirements/1828fcc.txt +++ b/.riot/requirements/1235f1e.txt @@ -2,12 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1828fcc.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1235f1e.in # anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.1.8 coverage[toml]==7.10.7 exceptiongroup==1.3.0 @@ -16,7 +17,7 @@ h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jinja2==3.1.6 markupsafe==3.0.3 @@ -27,6 +28,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 @@ -34,7 +36,7 @@ requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.20.4 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 uvicorn==0.33.0 diff --git a/.riot/requirements/1489f78.txt b/.riot/requirements/1489f78.txt new file mode 100644 index 00000000000..101cd34a3f8 --- /dev/null +++ b/.riot/requirements/1489f78.txt @@ -0,0 +1,56 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1489f78.in +# +annotated-doc==0.0.3 +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 +click==8.3.0 +coverage[toml]==7.11.0 +cryptography==46.0.3 +fastapi==0.121.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.27.2 +httpx-sse==0.4.3 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +markupsafe==3.0.3 +mcp==1.20.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 +pygments==2.19.2 +pyjwt[crypto]==2.10.1 +pytest==8.4.2 +pytest-asyncio==1.2.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +python-dotenv==1.2.1 +python-multipart==0.0.20 +referencing==0.37.0 +requests==2.32.5 +rpds-py==0.28.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sse-starlette==3.0.3 +starlette==0.49.3 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==2.5.0 +uvicorn==0.33.0 diff --git a/.riot/requirements/1554154.txt b/.riot/requirements/1554154.txt deleted file mode 100644 index 63c1117576f..00000000000 --- a/.riot/requirements/1554154.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/1554154.in -# -annotated-types==0.7.0 -anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.3.0 -coverage[toml]==7.10.7 -fastapi==0.118.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==3.0.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-multipart==0.0.20 -requests==2.32.5 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.48.0 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -urllib3==2.5.0 -uvicorn==0.33.0 diff --git a/.riot/requirements/18474a9.txt b/.riot/requirements/18474a9.txt new file mode 100644 index 00000000000..8fcd85fe4fe --- /dev/null +++ b/.riot/requirements/18474a9.txt @@ -0,0 +1,40 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/18474a9.in +# +anyio==3.7.1 +attrs==25.3.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.1.8 +coverage[toml]==7.6.1 +exceptiongroup==1.3.0 +fastapi==0.86.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jinja2==3.1.6 +markupsafe==2.1.5 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.5.0 +pydantic==1.10.24 +pytest==8.3.5 +pytest-asyncio==0.24.0 +pytest-cov==5.0.0 +pytest-mock==3.14.1 +python-multipart==0.0.20 +requests==2.32.4 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.20.4 +tomli==2.3.0 +typing-extensions==4.13.2 +urllib3==2.2.3 +uvicorn==0.33.0 diff --git a/.riot/requirements/1a0cd9b.txt b/.riot/requirements/196a8f0.txt similarity index 52% rename from .riot/requirements/1a0cd9b.txt rename to .riot/requirements/196a8f0.txt index 195a1f37389..3419ac87846 100644 --- a/.riot/requirements/1a0cd9b.txt +++ b/.riot/requirements/196a8f0.txt @@ -2,38 +2,52 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/1a0cd9b.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/196a8f0.in # annotated-types==0.7.0 anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 +cryptography==46.0.3 fastapi==0.114.2 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 +httpx-sse==0.4.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 markupsafe==3.0.3 +mcp==1.20.0 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 pygments==2.19.2 +pyjwt[crypto]==2.10.1 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 +python-dotenv==1.2.1 python-multipart==0.0.20 +referencing==0.37.0 requests==2.32.5 +rpds-py==0.28.0 sniffio==1.3.1 sortedcontainers==2.4.0 +sse-starlette==3.0.3 starlette==0.38.6 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/a0d16bc.txt b/.riot/requirements/197fd3a.txt similarity index 68% rename from .riot/requirements/a0d16bc.txt rename to .riot/requirements/197fd3a.txt index 5de62210931..ec2367bba1f 100644 --- a/.riot/requirements/a0d16bc.txt +++ b/.riot/requirements/197fd3a.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/a0d16bc.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/197fd3a.in # anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.94.1 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/19df299.txt b/.riot/requirements/19df299.txt deleted file mode 100644 index 8a02d88b052..00000000000 --- a/.riot/requirements/19df299.txt +++ /dev/null @@ -1,41 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.13 -# by the following command: -# -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/19df299.in -# -annotated-types==0.7.0 -anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.3.0 -coverage[toml]==7.10.7 -fastapi==0.118.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==3.0.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-multipart==0.0.20 -requests==2.32.5 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.48.0 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -urllib3==2.5.0 -uvicorn==0.33.0 diff --git a/.riot/requirements/5368b32.txt b/.riot/requirements/1a4e2ef.txt similarity index 80% rename from .riot/requirements/5368b32.txt rename to .riot/requirements/1a4e2ef.txt index a8752a4a904..e7ee4b63042 100644 --- a/.riot/requirements/5368b32.txt +++ b/.riot/requirements/1a4e2ef.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/5368b32.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1a4e2ef.in # anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.94.1 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/9a1f267.txt b/.riot/requirements/1acb1cd.txt similarity index 68% rename from .riot/requirements/9a1f267.txt rename to .riot/requirements/1acb1cd.txt index eb965d05047..b2a3ec31cdb 100644 --- a/.riot/requirements/9a1f267.txt +++ b/.riot/requirements/1acb1cd.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/9a1f267.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1acb1cd.in # anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.94.1 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/731d25f.txt b/.riot/requirements/1af899c.txt similarity index 68% rename from .riot/requirements/731d25f.txt rename to .riot/requirements/1af899c.txt index 69796d952df..4265c6ab50a 100644 --- a/.riot/requirements/731d25f.txt +++ b/.riot/requirements/1af899c.txt @@ -2,22 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/731d25f.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1af899c.in # anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 exceptiongroup==1.3.0 fastapi==0.94.1 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -27,6 +28,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 @@ -34,7 +36,7 @@ requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.26.1 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 uvicorn==0.33.0 diff --git a/.riot/requirements/68a2945.txt b/.riot/requirements/1c3d896.txt similarity index 51% rename from .riot/requirements/68a2945.txt rename to .riot/requirements/1c3d896.txt index de5e3dd0ae1..8efc222d6ee 100644 --- a/.riot/requirements/68a2945.txt +++ b/.riot/requirements/1c3d896.txt @@ -1,43 +1,42 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/68a2945.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c3d896.in # annotated-types==0.7.0 -anyio==4.11.0 +anyio==4.5.2 attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.1.8 -coverage[toml]==7.10.7 +coverage[toml]==7.6.1 exceptiongroup==1.3.0 fastapi==0.114.2 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jinja2==3.1.6 -markupsafe==3.0.3 +markupsafe==2.1.5 mock==5.2.0 opentracing==2.4.0 packaging==25.0 -pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-mock==3.15.1 +pluggy==1.5.0 +pydantic==2.10.6 +pydantic-core==2.27.2 +pytest==8.3.5 +pytest-asyncio==0.24.0 +pytest-cov==5.0.0 +pytest-mock==3.14.1 python-multipart==0.0.20 -requests==2.32.5 +requests==2.32.4 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.38.6 -tomli==2.2.1 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -urllib3==2.5.0 +tomli==2.3.0 +typing-extensions==4.13.2 +urllib3==2.2.3 uvicorn==0.33.0 diff --git a/.riot/requirements/1ef982c.txt b/.riot/requirements/1cd7717.txt similarity index 68% rename from .riot/requirements/1ef982c.txt rename to .riot/requirements/1cd7717.txt index 6c2ddcb260e..bbec235b2f0 100644 --- a/.riot/requirements/1ef982c.txt +++ b/.riot/requirements/1cd7717.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/1ef982c.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1cd7717.in # anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.86.0 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/38066f4.txt b/.riot/requirements/1e050b8.txt similarity index 80% rename from .riot/requirements/38066f4.txt rename to .riot/requirements/1e050b8.txt index 43571bcd343..366c9af0dce 100644 --- a/.riot/requirements/38066f4.txt +++ b/.riot/requirements/1e050b8.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/38066f4.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e050b8.in # anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.86.0 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/1c28390.txt b/.riot/requirements/1e9125b.txt similarity index 58% rename from .riot/requirements/1c28390.txt rename to .riot/requirements/1e9125b.txt index bff6c6b21bc..d9210f21dc5 100644 --- a/.riot/requirements/1c28390.txt +++ b/.riot/requirements/1e9125b.txt @@ -2,38 +2,52 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c28390.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e9125b.in # annotated-types==0.7.0 anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 +cryptography==46.0.3 fastapi==0.114.2 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 +httpx-sse==0.4.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 markupsafe==3.0.3 +mcp==1.20.0 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 pygments==2.19.2 +pyjwt[crypto]==2.10.1 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 +python-dotenv==1.2.1 python-multipart==0.0.20 +referencing==0.37.0 requests==2.32.5 +rpds-py==0.28.0 sniffio==1.3.1 sortedcontainers==2.4.0 +sse-starlette==3.0.3 starlette==0.38.6 typing-extensions==4.15.0 typing-inspection==0.4.2 diff --git a/.riot/requirements/1fbd93b.txt b/.riot/requirements/1fbd93b.txt deleted file mode 100644 index 2c5cf911d28..00000000000 --- a/.riot/requirements/1fbd93b.txt +++ /dev/null @@ -1,43 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/1fbd93b.in -# -annotated-types==0.7.0 -anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.3.0 -coverage[toml]==7.10.7 -exceptiongroup==1.3.0 -fastapi==0.118.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==3.0.3 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 -pygments==2.19.2 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-mock==3.15.1 -python-multipart==0.0.20 -requests==2.32.5 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.48.0 -tomli==2.2.1 -typing-extensions==4.15.0 -typing-inspection==0.4.2 -urllib3==2.5.0 -uvicorn==0.33.0 diff --git a/.riot/requirements/30d009a.txt b/.riot/requirements/30d009a.txt new file mode 100644 index 00000000000..44259583d11 --- /dev/null +++ b/.riot/requirements/30d009a.txt @@ -0,0 +1,40 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/30d009a.in +# +anyio==4.5.2 +attrs==25.3.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 +click==8.1.8 +coverage[toml]==7.6.1 +exceptiongroup==1.3.0 +fastapi==0.94.1 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.27.2 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.1.0 +jinja2==3.1.6 +markupsafe==2.1.5 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.5.0 +pydantic==1.10.24 +pytest==8.3.5 +pytest-asyncio==0.24.0 +pytest-cov==5.0.0 +pytest-mock==3.14.1 +python-multipart==0.0.20 +requests==2.32.4 +sniffio==1.3.1 +sortedcontainers==2.4.0 +starlette==0.26.1 +tomli==2.3.0 +typing-extensions==4.13.2 +urllib3==2.2.3 +uvicorn==0.33.0 diff --git a/.riot/requirements/393e41d.txt b/.riot/requirements/31333df.txt similarity index 67% rename from .riot/requirements/393e41d.txt rename to .riot/requirements/31333df.txt index ce4f8cb8143..95d3d8377a2 100644 --- a/.riot/requirements/393e41d.txt +++ b/.riot/requirements/31333df.txt @@ -2,22 +2,23 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/393e41d.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/31333df.in # anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 exceptiongroup==1.3.0 fastapi==0.86.0 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -27,6 +28,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 @@ -34,7 +36,7 @@ requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.20.4 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 uvicorn==0.33.0 diff --git a/.riot/requirements/34febf3.txt b/.riot/requirements/34febf3.txt new file mode 100644 index 00000000000..7809c2c0934 --- /dev/null +++ b/.riot/requirements/34febf3.txt @@ -0,0 +1,56 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/34febf3.in +# +annotated-doc==0.0.3 +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 +click==8.3.0 +coverage[toml]==7.11.0 +cryptography==46.0.3 +fastapi==0.121.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.27.2 +httpx-sse==0.4.3 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +markupsafe==3.0.3 +mcp==1.20.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 +pygments==2.19.2 +pyjwt[crypto]==2.10.1 +pytest==8.4.2 +pytest-asyncio==1.2.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +python-dotenv==1.2.1 +python-multipart==0.0.20 +referencing==0.37.0 +requests==2.32.5 +rpds-py==0.28.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sse-starlette==3.0.3 +starlette==0.49.3 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==2.5.0 +uvicorn==0.33.0 diff --git a/.riot/requirements/e8dd8b5.txt b/.riot/requirements/70806d5.txt similarity index 54% rename from .riot/requirements/e8dd8b5.txt rename to .riot/requirements/70806d5.txt index 482b2199c3d..fd2a43e7933 100644 --- a/.riot/requirements/e8dd8b5.txt +++ b/.riot/requirements/70806d5.txt @@ -2,39 +2,54 @@ # This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e8dd8b5.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/70806d5.in # +annotated-doc==0.0.3 annotated-types==0.7.0 anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 -fastapi==0.118.0 +coverage[toml]==7.11.0 +cryptography==46.0.3 +fastapi==0.121.0 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 +httpx-sse==0.4.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 markupsafe==3.0.3 +mcp==1.20.0 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 pygments==2.19.2 +pyjwt[crypto]==2.10.1 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 +python-dotenv==1.2.1 python-multipart==0.0.20 +referencing==0.37.0 requests==2.32.5 +rpds-py==0.28.0 sniffio==1.3.1 sortedcontainers==2.4.0 -starlette==0.48.0 +sse-starlette==3.0.3 +starlette==0.49.3 typing-extensions==4.15.0 typing-inspection==0.4.2 urllib3==2.5.0 diff --git a/.riot/requirements/10b37a9.txt b/.riot/requirements/e385023.txt similarity index 68% rename from .riot/requirements/10b37a9.txt rename to .riot/requirements/e385023.txt index bbf22bcec7c..e6821c95f8d 100644 --- a/.riot/requirements/10b37a9.txt +++ b/.riot/requirements/e385023.txt @@ -2,21 +2,21 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/10b37a9.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e385023.in # anyio==3.7.1 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 fastapi==0.86.0 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 markupsafe==3.0.3 mock==5.2.0 @@ -26,6 +26,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 diff --git a/.riot/requirements/1b3095b.txt b/.riot/requirements/e86781c.txt similarity index 51% rename from .riot/requirements/1b3095b.txt rename to .riot/requirements/e86781c.txt index 6c6148a7eed..6e83e9b5cc6 100644 --- a/.riot/requirements/1b3095b.txt +++ b/.riot/requirements/e86781c.txt @@ -2,41 +2,56 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --cert=None --client-cert=None --index-url=None --no-annotate --pip-args=None .riot/requirements/1b3095b.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e86781c.in # annotated-types==0.7.0 anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.0 -coverage[toml]==7.10.7 +coverage[toml]==7.11.0 +cryptography==46.0.3 exceptiongroup==1.3.0 fastapi==0.114.2 h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 +httpx-sse==0.4.3 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 markupsafe==3.0.3 +mcp==1.20.0 mock==5.2.0 opentracing==2.4.0 packaging==25.0 pluggy==1.6.0 -pydantic==2.11.9 -pydantic-core==2.33.2 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 pygments==2.19.2 +pyjwt[crypto]==2.10.1 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 +python-dotenv==1.2.1 python-multipart==0.0.20 +referencing==0.37.0 requests==2.32.5 +rpds-py==0.28.0 sniffio==1.3.1 sortedcontainers==2.4.0 +sse-starlette==3.0.3 starlette==0.38.6 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 typing-inspection==0.4.2 urllib3==2.5.0 diff --git a/.riot/requirements/e05f887.txt b/.riot/requirements/f444071.txt similarity index 81% rename from .riot/requirements/e05f887.txt rename to .riot/requirements/f444071.txt index 1efeed94bcf..40e1ab08ba4 100644 --- a/.riot/requirements/e05f887.txt +++ b/.riot/requirements/f444071.txt @@ -2,12 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/e05f887.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/f444071.in # anyio==4.11.0 -attrs==25.3.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +charset-normalizer==3.4.4 click==8.1.8 coverage[toml]==7.10.7 exceptiongroup==1.3.0 @@ -16,7 +17,7 @@ h11==0.16.0 httpcore==1.0.9 httpx==0.27.2 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 jinja2==3.1.6 markupsafe==3.0.3 @@ -27,6 +28,7 @@ pluggy==1.6.0 pydantic==1.10.24 pygments==2.19.2 pytest==8.4.2 +pytest-asyncio==1.2.0 pytest-cov==7.0.0 pytest-mock==3.15.1 python-multipart==0.0.20 @@ -34,7 +36,7 @@ requests==2.32.5 sniffio==1.3.1 sortedcontainers==2.4.0 starlette==0.26.1 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 uvicorn==0.33.0 diff --git a/.riot/requirements/fd02a34.txt b/.riot/requirements/fd02a34.txt new file mode 100644 index 00000000000..668c4343c3e --- /dev/null +++ b/.riot/requirements/fd02a34.txt @@ -0,0 +1,59 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/fd02a34.in +# +annotated-doc==0.0.3 +annotated-types==0.7.0 +anyio==4.11.0 +attrs==25.4.0 +backports-asyncio-runner==1.2.0 +certifi==2025.10.5 +cffi==2.0.0 +charset-normalizer==3.4.4 +click==8.3.0 +coverage[toml]==7.11.0 +cryptography==46.0.3 +exceptiongroup==1.3.0 +fastapi==0.121.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.27.2 +httpx-sse==0.4.3 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +jinja2==3.1.6 +jsonschema==4.25.1 +jsonschema-specifications==2025.9.1 +markupsafe==3.0.3 +mcp==1.20.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pycparser==2.23 +pydantic==2.12.4 +pydantic-core==2.41.5 +pydantic-settings==2.11.0 +pygments==2.19.2 +pyjwt[crypto]==2.10.1 +pytest==8.4.2 +pytest-asyncio==1.2.0 +pytest-cov==7.0.0 +pytest-mock==3.15.1 +python-dotenv==1.2.1 +python-multipart==0.0.20 +referencing==0.37.0 +requests==2.32.5 +rpds-py==0.28.0 +sniffio==1.3.1 +sortedcontainers==2.4.0 +sse-starlette==3.0.3 +starlette==0.49.3 +tomli==2.3.0 +typing-extensions==4.15.0 +typing-inspection==0.4.2 +urllib3==2.5.0 +uvicorn==0.33.0 diff --git a/ddtrace/appsec/_iast/__init__.py b/ddtrace/appsec/_iast/__init__.py index 32f8fd23488..3d6c4e2c402 100644 --- a/ddtrace/appsec/_iast/__init__.py +++ b/ddtrace/appsec/_iast/__init__.py @@ -32,6 +32,7 @@ def wrapped_function(wrapped, instance, args, kwargs): import sys import types +from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger from ddtrace.internal.module import ModuleWatchdog from ddtrace.internal.settings.asm import config as asm_config @@ -44,6 +45,83 @@ def wrapped_function(wrapped, instance, args, kwargs): _IAST_TO_BE_LOADED = True _iast_propagation_enabled = False +_fork_handler_registered = False + + +def _disable_iast_after_fork(): + """ + Conditionally disable IAST in forked child processes to prevent segmentation faults. + + This fork handler differentiates between two types of forks: + + 1. **Early forks (web framework workers)**: Gunicorn, uvicorn, Django, Flask workers + fork BEFORE IAST initializes any state. These are safe - IAST remains enabled. + + 2. **Late forks (multiprocessing)**: multiprocessing.Process forks AFTER IAST has + initialized state. These inherit corrupted native extension state and must have + IAST disabled to prevent segmentation faults. + + Detection logic: + - If IAST has active request contexts when fork occurs → Late fork → Disable IAST + - If IAST has no active state → Early fork (worker) → Keep IAST enabled + + This is critical for multiprocessing compatibility while maintaining IAST coverage + in web framework workers. The native extension state (taint maps, context slots, + object pools, shared_ptr references) cannot be safely used across fork boundaries + when it exists, but is safe to initialize fresh in clean workers. + + For late forks, the child process: + - Clears all C++ taint maps and context slots + - Resets the Python-level IAST_CONTEXT + - Disables IAST by setting asm_config._iast_enabled = False + + This prevents segmentation faults in multiprocessing while allowing IAST to work + in web framework workers. + """ + if not asm_config._iast_enabled: + return + + try: + # Import locally to avoid issues if the module hasn't been loaded yet + from ddtrace.appsec._iast._iast_request_context_base import IAST_CONTEXT + from ddtrace.appsec._iast._iast_request_context_base import is_iast_request_enabled + from ddtrace.appsec._iast._taint_tracking._context import clear_all_request_context_slots + + if not is_iast_request_enabled(): + # No active context - this is an early fork (web framework worker) + # IAST can be safely initialized fresh in this child process + log.debug("IAST fork handler: No active context, keeping IAST enabled (web worker fork)") + return + + # Active context exists - this is a late fork (multiprocessing) + # Native state is corrupted, must disable IAST + log.debug("IAST fork handler: Active context detected, disabling IAST (multiprocessing fork)") + + # Clear C++ side: all taint maps and context slots + clear_all_request_context_slots() + # Clear Python side: reset the context ID + IAST_CONTEXT.set(None) + + # Disable IAST to prevent segmentation faults + asm_config._iast_enabled = False + + except Exception as e: + log.debug("Error in IAST fork handler: %s", e, exc_info=True) + + +def _register_fork_handler(): + """ + Register the fork handler if IAST is enabled and it hasn't been registered yet. + + This is called during IAST initialization to ensure the fork handler is only + registered once and only when IAST is actually being used. + """ + global _fork_handler_registered + + if not _fork_handler_registered and asm_config._iast_enabled: + forksafe.register(_disable_iast_after_fork) + _fork_handler_registered = True + log.debug("IAST fork safety handler registered") def ddtrace_iast_flask_patch(): @@ -101,6 +179,7 @@ def enable_iast_propagation(): log.debug("iast::instrumentation::starting IAST") ModuleWatchdog.register_pre_exec_module_hook(_should_iast_patch, _exec_iast_patched_module) _iast_propagation_enabled = True + _register_fork_handler() def _iast_pytest_activation(): @@ -148,5 +227,6 @@ def load_iast(): """Lazily load the iast module listeners.""" global _IAST_TO_BE_LOADED if _IAST_TO_BE_LOADED: + _register_fork_handler() iast_listen() _IAST_TO_BE_LOADED = False diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index 24fa2b224dc..47651c08047 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -71,7 +71,7 @@ def initialize_iast_lists(): import importlib.metadata as metadata result = set(metadata.packages_distributions()) iastpatch.set_packages_distributions(result) - except ImportError: + except (ImportError, AttributeError): # If metadata module is not available, the C extension will handle # first-party detection gracefully by returning False log.debug("Could not import metadata module for first-party detection") diff --git a/ddtrace/appsec/_iast/taint_sinks/code_injection.py b/ddtrace/appsec/_iast/taint_sinks/code_injection.py index 55cbc86eb90..45e19f63545 100644 --- a/ddtrace/appsec/_iast/taint_sinks/code_injection.py +++ b/ddtrace/appsec/_iast/taint_sinks/code_injection.py @@ -63,27 +63,39 @@ def _iast_coi(wrapped, instance, args, kwargs): # See ddtrace/internal/iast/product.py for detailed explanation. import inspect - caller_frame = None - if len(args) > 1: - func_globals = args[1] - elif kwargs.get("globals"): - func_globals = kwargs.get("globals") - else: - frames = inspect.currentframe() - caller_frame = frames.f_back - func_globals = caller_frame.f_globals - + func_globals = None + func_locals = None func_locals_copy_to_check = None - if len(args) > 2: - func_locals = args[2] - elif kwargs.get("locals"): - func_locals = kwargs.get("locals") + + # Check if inspect.currentframe is available (not available in some Python implementations) + if not hasattr(inspect, "currentframe"): + # Use provided globals/locals or None defaults + func_globals = args[1] if len(args) > 1 else kwargs.get("globals") + func_locals = args[2] if len(args) > 2 else kwargs.get("locals") else: - if caller_frame is None: + caller_frame = None + if len(args) > 1: + func_globals = args[1] + elif kwargs.get("globals"): + func_globals = kwargs.get("globals") + else: frames = inspect.currentframe() - caller_frame = frames.f_back - func_locals = caller_frame.f_locals - func_locals_copy_to_check = func_locals.copy() if func_locals else None + if frames is not None: + caller_frame = frames.f_back + func_globals = caller_frame.f_globals + + if len(args) > 2: + func_locals = args[2] + elif kwargs.get("locals"): + func_locals = kwargs.get("locals") + else: + if caller_frame is None: + frames = inspect.currentframe() + if frames is not None: + caller_frame = frames.f_back + if caller_frame is not None: + func_locals = caller_frame.f_locals + func_locals_copy_to_check = func_locals.copy() if func_locals else None except Exception as e: iast_propagation_sink_point_debug_log(f"Error in _iast_code_injection. {e}") return wrapped(*args, **kwargs) diff --git a/riotfile.py b/riotfile.py index 44fd72b791b..cb6ac8c1bcf 100644 --- a/riotfile.py +++ b/riotfile.py @@ -304,6 +304,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "jinja2": latest, "httpx": "<0.28.0", "uvicorn": "==0.33.0", + "pytest-asyncio": latest, }, env={ "DD_TRACE_AGENT_URL": "http://testagent:9126", @@ -320,7 +321,11 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( pys=select_pys(min_version="3.9", max_version="3.13"), - pkgs={"fastapi": ["==0.94.1", "~=0.114.2", latest]}, + pkgs={"fastapi": "==0.94.1"}, + ), + Venv( + pys=select_pys(min_version="3.10", max_version="3.13"), + pkgs={"fastapi": ["~=0.114.2", latest], "mcp": "==1.20.0"}, ), ], ), diff --git a/tests/appsec/iast/taint_sinks/test_code_injection_inspect_regression.py b/tests/appsec/iast/taint_sinks/test_code_injection_inspect_regression.py new file mode 100644 index 00000000000..1412bb2b40f --- /dev/null +++ b/tests/appsec/iast/taint_sinks/test_code_injection_inspect_regression.py @@ -0,0 +1,262 @@ +""" +Regression tests for code injection with inspect.currentframe() safety. + +These tests verify that code injection instrumentation handles edge cases where +inspect.currentframe() returns None, preventing AttributeError when accessing +f_back and other crashes. +""" +import sys +from unittest import mock + +import pytest + +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from ddtrace.appsec._iast.constants import VULN_CODE_INJECTION +from tests.appsec.iast.iast_utils import _get_iast_data +from tests.appsec.iast.iast_utils import _iast_patched_module + + +mod = _iast_patched_module("tests.appsec.iast.fixtures.taint_sinks.code_injection") + + +class TestCodeInjectionInspectSafety: + """Test that code injection handles inspect.currentframe() edge cases.""" + + def test_eval_with_currentframe_returning_none(self, iast_context_defaults): + """ + Regression test: Verify eval handles currentframe() returning None. + + In some Python implementations (Jython, IronPython), currentframe() + may exist but return None. This tests the None check. + """ + code_string = '"test" + "123"' + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + # Mock currentframe to return None + with mock.patch("inspect.currentframe", return_value=None): + # Should fall back to using provided args/kwargs or None + result = mod.pt_eval(tainted_string) + assert result == "test123" + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + assert data["vulnerabilities"][0]["type"] == VULN_CODE_INJECTION + + def test_eval_with_no_globals_no_locals_currentframe_none(self, iast_context_defaults): + """ + Test eval with no globals/locals arguments when currentframe returns None. + + This is the specific case that was causing segfaults - when eval is called + without globals/locals and currentframe returns None. + """ + code_string = "2 + 2" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + with mock.patch("inspect.currentframe", return_value=None): + # This should not crash + result = eval(tainted_string) + assert result == 4 + + data = _get_iast_data() + # Should still detect the vulnerability + assert len(data["vulnerabilities"]) == 1 + + def test_eval_with_globals_currentframe_none(self, iast_context_defaults): + """ + Test eval with explicit globals when currentframe returns None. + + Even if currentframe fails, explicit globals should work fine. + """ + code_string = "x * 2" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + test_globals = {"x": 10} + + with mock.patch("inspect.currentframe", return_value=None): + result = eval(tainted_string, test_globals) + assert result == 20 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + assert data["vulnerabilities"][0]["type"] == VULN_CODE_INJECTION + + def test_eval_with_globals_and_locals_currentframe_none(self, iast_context_defaults): + """ + Test eval with both globals and locals when currentframe returns None. + """ + code_string = "x + y" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + test_globals = {"x": 5} + test_locals = {"y": 10} + + with mock.patch("inspect.currentframe", return_value=None): + result = eval(tainted_string, test_globals, test_locals) + assert result == 15 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + def test_eval_kwargs_globals_currentframe_none(self, iast_context_defaults): + """ + Test eval with globals as kwarg when currentframe returns None. + """ + code_string = "value * 3" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + with mock.patch("inspect.currentframe", return_value=None): + result = eval(tainted_string, globals={"value": 7}) + assert result == 21 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + def test_eval_kwargs_locals_currentframe_none(self, iast_context_defaults): + """ + Test eval with both globals and locals as kwargs when currentframe returns None. + """ + code_string = "a + b" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + with mock.patch("inspect.currentframe", return_value=None): + result = eval(tainted_string, {"a": 100}, locals={"b": 200}) + assert result == 300 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + def test_eval_in_function_currentframe_none(self, iast_context_defaults): + """ + Test eval inside a function when currentframe returns None. + + When currentframe returns None, we can't extract the caller's locals, + so the eval must be self-contained or use explicit globals/locals. + """ + + def test_function(): + code_string = "10 + 5" # Self-contained expression + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + with mock.patch("inspect.currentframe", return_value=None): + # Without currentframe, eval with None globals/locals + # works for self-contained expressions + result = eval(tainted_string) + return result + + result = test_function() + assert result == 15 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + @pytest.mark.skipif(sys.version_info < (3, 8), reason="Mock spec issues with older Python") + def test_eval_no_globals_extraction_on_currentframe_none(self, iast_context_defaults): + """ + Verify that when currentframe returns None, we don't try to access f_back. + + This is the specific bug fix - without the None check, we'd try to access + frames.f_back which would raise AttributeError. + """ + code_string = "10 + 20" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + # Create a mock that explicitly returns None + with mock.patch("inspect.currentframe", return_value=None) as mock_frame: + result = eval(tainted_string) + assert result == 30 + + # Verify currentframe was called (attempting to get frame info) + assert mock_frame.called + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + def test_multiple_eval_calls_with_currentframe_issues(self, iast_context_defaults): + """ + Test multiple eval calls with various currentframe scenarios. + + This ensures the fix is stable across multiple invocations. + """ + test_cases = [ + ("1 + 1", 2), + ("5 * 5", 25), + ("100 - 50", 50), + ] + + with mock.patch("inspect.currentframe", return_value=None): + for code_string, expected in test_cases: + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + result = eval(tainted_string) + assert result == expected + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == len(test_cases) + + +class TestCodeInjectionInspectEdgeCases: + """Additional edge case tests for inspect-related functionality.""" + + def test_eval_with_frame_but_no_f_back(self, iast_context_defaults): + """ + Test when currentframe returns a frame but it has no f_back. + + This can happen at the top level of a script. + """ + code_string = "42" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + # Create a mock frame without f_back + mock_frame = mock.MagicMock() + mock_frame.f_back = None + mock_frame.f_globals = {} + mock_frame.f_locals = {} + + with mock.patch("inspect.currentframe", return_value=mock_frame): + # Should handle gracefully + try: + result = eval(tainted_string, {"__builtins__": __builtins__}) + assert result == 42 + except AttributeError: + pytest.fail("Should not raise AttributeError when f_back is None") + + def test_eval_lambda_with_currentframe_none(self, iast_context_defaults): + """ + Test eval creating lambda functions when currentframe returns None. + """ + code_string = "lambda x: x * 2" + tainted_string = taint_pyobject( + code_string, source_name="path", source_value=code_string, source_origin=OriginType.PATH + ) + + with mock.patch("inspect.currentframe", return_value=None): + func = eval(tainted_string) + assert callable(func) + assert func(5) == 10 + + data = _get_iast_data() + assert len(data["vulnerabilities"]) == 1 + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/appsec/iast/taint_tracking/test_anyio_thread_safety.py b/tests/appsec/iast/taint_tracking/test_anyio_thread_safety.py new file mode 100644 index 00000000000..44310e2bc51 --- /dev/null +++ b/tests/appsec/iast/taint_tracking/test_anyio_thread_safety.py @@ -0,0 +1,151 @@ +""" +Test that IAST works correctly with anyio's multi-threaded async operations. + +The issue was that anyio creates multiple threads for async operations, and when +these threads accessed the Initializer's object pools concurrently, race conditions +occurred because the pools weren't thread-safe. This led to segfaults. + +This test verifies the fix by simulating anyio-like concurrent access patterns. +""" +import pytest + + +# Skip all tests in this module if anyio is not installed +pytest.importorskip("anyio") + +import anyio + +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context_base import _iast_start_request +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject + + +@pytest.mark.asyncio +async def test_anyio_concurrent_taint_operations(): + """Test IAST with anyio's concurrent async operations.""" + + oce.reconfigure() + _iast_start_request() + + async def async_taint_worker(worker_id, iterations=50): + """Async worker that creates tainted objects.""" + for i in range(iterations): + # This exercises the thread-safe object pool + data = taint_pyobject( + f"anyio_worker_{worker_id}_data_{i}", + f"anyio_worker_{worker_id}_source_{i}", + f"anyio_worker_{worker_id}_value_{i}", + OriginType.PARAMETER, + ) + # Yield to allow other tasks to run + await anyio.sleep(0.001) + del data + + # Create multiple concurrent tasks + async with anyio.create_task_group() as tg: + for i in range(10): + tg.start_soon(async_taint_worker, i) + + # If we got here without segfault, the thread-safety fix worked + assert True, "Anyio concurrent operations completed successfully" + + +@pytest.mark.asyncio +async def test_anyio_memory_streams(): + """Test IAST with anyio memory streams (the specific failing case).""" + + oce.reconfigure() + _iast_start_request() + + async def producer(send_stream): + """Producer that sends tainted data.""" + for i in range(100): + data = taint_pyobject( + f"stream_data_{i}", + f"stream_source_{i}", + f"stream_value_{i}", + OriginType.PARAMETER, + ) + await send_stream.send(data) + await send_stream.aclose() + + async def consumer(receive_stream): + """Consumer that receives tainted data.""" + async for item in receive_stream: + # Process the tainted item + _ = item + + # This pattern is similar to what Starlette/FastAPI use with anyio + send_stream, receive_stream = anyio.create_memory_object_stream() + + async with anyio.create_task_group() as tg: + tg.start_soon(producer, send_stream) + tg.start_soon(consumer, receive_stream) + + # If we got here without segfault, the thread-safety fix worked + assert True, "Anyio memory stream operations completed successfully" + + +@pytest.mark.asyncio +async def test_anyio_event_creation(): + """Test IAST with anyio Event creation (the specific line in the stack trace).""" + + oce.reconfigure() + _iast_start_request() + + async def worker_with_events(worker_id): + """Worker that creates events and tainted objects.""" + for i in range(50): + # Create an anyio Event (this was failing in the stack trace) + event = anyio.Event() + + # Create tainted data while event exists + data = taint_pyobject( + f"event_worker_{worker_id}_data_{i}", + f"event_worker_{worker_id}_source_{i}", + f"event_worker_{worker_id}_value_{i}", + OriginType.PARAMETER, + ) + + # Set and wait on event + event.set() + await event.wait() + + del data + + # Run multiple workers concurrently + async with anyio.create_task_group() as tg: + for i in range(10): + tg.start_soon(worker_with_events, i) + + # If we got here without segfault, the thread-safety fix worked + assert True, "Anyio event creation with IAST completed successfully" + + +def test_anyio_blocking_portal(): + """Test IAST with anyio's BlockingPortal (runs async code from sync context).""" + + oce.reconfigure() + _iast_start_request() + + async def async_operation(value): + """Async operation that taints data.""" + data = taint_pyobject( + f"portal_{value}", + f"portal_source_{value}", + f"portal_value_{value}", + OriginType.PARAMETER, + ) + await anyio.sleep(0.01) + return data + + # BlockingPortal allows running async code from synchronous code + # This can trigger multi-threading issues + with anyio.from_thread.start_blocking_portal() as portal: + results = [] + for i in range(20): + result = portal.call(async_operation, i) + results.append(result) + + assert len(results) == 20, "All portal operations should complete" diff --git a/tests/appsec/iast/taint_tracking/test_initializer_thread_safety.py b/tests/appsec/iast/taint_tracking/test_initializer_thread_safety.py new file mode 100644 index 00000000000..6b1e3e67f20 --- /dev/null +++ b/tests/appsec/iast/taint_tracking/test_initializer_thread_safety.py @@ -0,0 +1,114 @@ +""" +Test thread-safety and fork behavior of IAST. + +The Initializer class uses object pooling for performance. +This test verifies: +1. Concurrent access from multiple threads doesn't cause issues +2. IAST is properly disabled in forked child processes to prevent segfaults +""" +import os +import sys +import threading + +import pytest + +from ddtrace.appsec._iast import oce +from ddtrace.appsec._iast._iast_request_context_base import _iast_start_request +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject + + +def test_initializer_thread_safety(): + """Test that Initializer's object pools are thread-safe.""" + + errors = [] + results = [] + + def worker(worker_id, iterations=100): + """Worker function that creates tainted objects.""" + try: + oce.reconfigure() + _iast_start_request() + + for i in range(iterations): + # This exercises the object pool allocation/deallocation + data = taint_pyobject( + f"worker_{worker_id}_data_{i}", + f"worker_{worker_id}_source_{i}", + f"worker_{worker_id}_value_{i}", + OriginType.PARAMETER, + ) + # Let the string go out of scope to trigger deallocation + del data + + results.append({"worker_id": worker_id, "success": True}) + + except Exception as e: + errors.append({"worker_id": worker_id, "error": str(e)}) + + # Create multiple threads that will concurrently access the object pools + threads = [] + num_threads = 10 + + for i in range(num_threads): + thread = threading.Thread(target=worker, args=(i,)) + threads.append(thread) + thread.start() + + # Wait for all threads to complete + for thread in threads: + thread.join(timeout=30) + + # Verify all threads completed successfully + assert len(errors) == 0, f"Thread errors occurred: {errors}" + assert len(results) == num_threads, f"Expected {num_threads} results, got {len(results)}" + + +@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") +def test_initializer_reset_after_fork(): + """ + Test that IAST is disabled in forked child processes. + + When a process forks, the native extension state cannot be safely used. + The fork handler disables IAST in the child to prevent segmentation faults. + This test verifies that the child process doesn't crash when IAST is disabled. + """ + + # Setup parent with IAST enabled + oce.reconfigure() + _iast_start_request() + _ = taint_pyobject( + "parent_data", + "parent_source", + "parent_value", + OriginType.PARAMETER, + ) + + pid = os.fork() + + if pid == 0: + # Child process - IAST should be disabled by fork handler + try: + # These calls should not crash even though IAST is disabled + oce.reconfigure() + _iast_start_request() + + # taint_pyobject should be a no-op since IAST is disabled + _ = taint_pyobject( + "child_data", + "child_source", + "child_value", + OriginType.COOKIE, + ) + + # If we got here without segfault, IAST was safely disabled + os._exit(0) + + except Exception as e: + print(f"Child error: {e}", file=sys.stderr) + os._exit(1) + + else: + # Parent process + _, status = os.waitpid(pid, 0) + assert status == 0, "Child process should exit successfully with IAST disabled" diff --git a/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py b/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py index b06c13abb19..b8c8387981a 100644 --- a/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py +++ b/tests/appsec/iast/taint_tracking/test_multiprocessing_tracer_iast_env.py @@ -10,34 +10,54 @@ from ddtrace.appsec._iast._taint_tracking import is_tainted from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject from tests.appsec.iast.iast_utils import _end_iast_context_and_oce +from tests.appsec.iast.iast_utils import _start_iast_context_and_oce def _child_check(q: Queue): - """Subprocess entrypoint: report tracer and IAST env status back to parent via Queue.""" + """ + Subprocess entrypoint: verify IAST is disabled in forked child processes. + + Reports tracer and IAST status back to parent via Queue. + """ try: from ddtrace.internal.settings.asm import config as asm_config from ddtrace.trace import tracer + # Start IAST context in child process + # Note: This will be a no-op since IAST is disabled after fork + _start_iast_context_and_oce() + text = "text_to_taint" text2 = b"text_to_taint" * 100 + # These taint operations will be no-ops since IAST is disabled after fork tainted_text = taint_pyobject( text, source_name="request_body", source_value=text, source_origin=OriginType.PARAMETER ) tainted_text2 = taint_pyobject( text2, source_name="request_body", source_value=text2, source_origin=OriginType.PARAMETER ) - # Fork once before reporting results to the parent Queue. - # This exercises IAST and tracer state post-fork in a grandchild process. + # Fork again to verify nested fork behavior import os as _os pid = _os.fork() if pid == 0: - # In forked grandchild: perform a quick no-op check to ensure no crash. + # In forked grandchild: IAST is disabled by fork handler assert bool(tracer.enabled) - assert is_tainted(tainted_text) - assert is_tainted(tainted_text2) + + # After fork, IAST is disabled, so objects are NOT tainted + assert not is_tainted(tainted_text), "IAST disabled after fork, so not tainted" + assert not is_tainted(tainted_text2), "IAST disabled after fork, so not tainted" + + # Attempting to start IAST in grandchild should be a no-op + _start_iast_context_and_oce() + grandchild_text = taint_pyobject( + "grandchild_data", source_name="test", source_value="test", source_origin=OriginType.PARAMETER + ) + # IAST is disabled, so taint_pyobject is a no-op + assert not is_tainted(grandchild_text) + _end_iast_context_and_oce() - assert not is_tainted(tainted_text) + assert not is_tainted(grandchild_text) # Exit immediately without touching the parent's Queue _os._exit(0) else: @@ -66,7 +86,22 @@ def _child_check(q: Queue): @pytest.mark.skipif(os.name == "nt", reason="multiprocessing fork semantics differ on Windows") def test_subprocess_has_tracer_running_and_iast_env(monkeypatch): - """Verify a multiprocessing child sees tracer enabled and DD_IAST_ENABLED env propagated.""" + """ + Verify IAST is disabled in late fork multiprocessing scenarios. + + This test simulates multiprocessing.Process forking after IAST has active state. + The fork handler should detect the active context and disable IAST in the child + to prevent segmentation faults. + + This test verifies: + - Tracer remains enabled in child + - DD_IAST_ENABLED env variable is propagated + - IAST is disabled (_iast_enabled = False) due to active context at fork time + - taint_pyobject operations are no-ops (return non-tainted objects) + + Note: Web framework workers (early forks) that fork before IAST state exists + will keep IAST enabled - this is tested separately in integration tests. + """ # Ensure tracer and IAST are enabled for the test monkeypatch.setenv("DD_IAST_ENABLED", "true") @@ -84,10 +119,12 @@ def test_subprocess_has_tracer_running_and_iast_env(monkeypatch): # Tracer should be enabled in child when DD_TRACE_ENABLED=true assert result["tracer_enabled"] is True - # DD_IAST_ENABLED env should be visible and true in child + # DD_IAST_ENABLED env should be visible and true in child (env is inherited) assert result["iast_env"] in ("true", "True", "1") - assert result["text_is_tainted"] is True - assert result["text_is_tainted2"] is True - # IAST enablement flag should reflect env - assert result["iast_enabled_flag"] is True + # But IAST should NOT actually taint objects (disabled for safety) + assert result["text_is_tainted"] is False + assert result["text_is_tainted2"] is False + + # IAST enablement flag should be False (disabled by fork handler) + assert result["iast_enabled_flag"] is False diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range_fork.py b/tests/appsec/iast/taint_tracking/test_native_taint_range_fork.py deleted file mode 100644 index 69db89ebe17..00000000000 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range_fork.py +++ /dev/null @@ -1,335 +0,0 @@ -# -*- coding: utf-8 -*- -from multiprocessing import Process -from multiprocessing import Queue -import os -import sys -import time -import uuid - -import pytest - -from ddtrace.appsec._iast._iast_request_context_base import _get_iast_context_id -from ddtrace.appsec._iast._iast_request_context_base import _num_objects_tainted_in_request -from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._taint_tracking._context import debug_taint_map -from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject -from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect -from tests.appsec.iast.iast_utils import _end_iast_context_and_oce -from tests.appsec.iast.iast_utils import _start_iast_context_and_oce - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork_taint_isolation(): - """Test that taint tracking state is properly isolated between parent and child processes.""" - - def child_process_work(queue): - """Work function for child process to validate taint isolation.""" - try: - # Create context in child process - _start_iast_context_and_oce() - - # Verify child starts with clean state - initial_count = _num_objects_tainted_in_request() - queue.put(("child_initial_count", initial_count)) - - # Create tainted objects in child - child_tainted = taint_pyobject( - "child_data", source_name="child_source", source_value="child_value", source_origin=OriginType.PARAMETER - ) - - child_count = _num_objects_tainted_in_request() - queue.put(("child_tainted_count", child_count)) - - # Verify child can access its own tainted data - child_ranges = get_ranges(child_tainted) - queue.put(("child_ranges_exist", len(child_ranges) > 0)) - - # Create more complex taint operations - child_str2 = taint_pyobject( - "child_data2", source_name="child_source2", source_value="child_value2", source_origin=OriginType.COOKIE - ) - - # Test taint propagation in child - child_combined = add_aspect(child_tainted, child_str2) - combined_ranges = get_ranges(child_combined) - queue.put(("child_combined_ranges", len(combined_ranges))) - - final_count = _num_objects_tainted_in_request() - queue.put(("child_final_count", final_count)) - - # Get debug info from child - child_debug_map = debug_taint_map(_get_iast_context_id()) - queue.put(("child_debug_map_empty", child_debug_map == "[]")) - - except Exception as e: - queue.put(("child_error", str(e))) - - # Parent process setup - _start_iast_context_and_oce() - - # Create tainted objects in parent before fork - parent_tainted = taint_pyobject( - "parent_data", source_name="parent_source", source_value="parent_value", source_origin=OriginType.HEADER_NAME - ) - - parent_initial_count = _num_objects_tainted_in_request() - assert parent_initial_count == 1 - - # Verify parent can access its tainted data - parent_ranges = get_ranges(parent_tainted) - assert len(parent_ranges) > 0 - - # Fork the process - queue = Queue() - child_process = Process(target=child_process_work, args=(queue,)) - child_process.start() - child_process.join() - - # Collect results from child - child_results = {} - while not queue.empty(): - key, value = queue.get() - child_results[key] = value - - # Verify child process results - assert "child_error" not in child_results, f"Child process error: {child_results.get('child_error')}" - - # Child should start with clean state (thread-local storage should be fresh) - assert child_results["child_initial_count"] == 1, "Child should start with no tainted objects" - - # Child should be able to create its own tainted objects - assert child_results["child_tainted_count"] == 2, "Child should have 1 tainted object after creation" - assert child_results["child_ranges_exist"] is True, "Child should be able to access its tainted ranges" - - # Child should be able to perform taint operations - assert child_results["child_combined_ranges"] >= 2, "Child should have combined ranges from both sources" - assert child_results["child_final_count"] >= 2, "Child should have multiple tainted objects" - - # Verify parent state is unchanged after child execution - parent_final_count = _num_objects_tainted_in_request() - assert parent_final_count == parent_initial_count, "Parent taint count should be unchanged" - - # Parent should still be able to access its original tainted data - parent_ranges_after = get_ranges(parent_tainted) - assert len(parent_ranges_after) > 0, "Parent should still have access to its tainted data" - assert parent_ranges_after == parent_ranges, "Parent ranges should be unchanged" - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork_multiple_children(): - """Test taint isolation with multiple child processes.""" - - def child_worker(child_id, queue): - """Worker function for each child process.""" - try: - _start_iast_context_and_oce() - - # Each child creates unique tainted data - child_data = f"child_{child_id}_data" - tainted_obj = taint_pyobject( - child_data, - source_name=f"child_{child_id}_source", - source_value=f"child_{child_id}_value", - source_origin=OriginType.PARAMETER, - ) - - # Verify isolation - count = _num_objects_tainted_in_request() - ranges = get_ranges(tainted_obj) - - queue.put((child_id, count, len(ranges) > 0)) - - except Exception as e: - queue.put((child_id, "error", str(e))) - - # Parent setup - _start_iast_context_and_oce() - parent_tainted = taint_pyobject( # noqa: F841 - "parent_shared_data", source_name="parent_source", source_value="parent_value", source_origin=OriginType.BODY - ) - - parent_count = _num_objects_tainted_in_request() - assert parent_count == 1 - - # Create multiple child processes - num_children = 3 - queue = Queue() - children = [] - - for i in range(num_children): - child = Process(target=child_worker, args=(i, queue)) - children.append(child) - child.start() - - # Wait for all children - for child in children: - child.join() - - # Collect results - child_results = {} - while not queue.empty(): - result = queue.get() - if len(result) == 3: - child_id, count, has_ranges = result - child_results[child_id] = {"count": count, "has_ranges": has_ranges} - else: - child_id, error_type, error_msg = result - child_results[child_id] = {"error": f"{error_type}: {error_msg}"} - - # Verify all children worked independently - for i in range(num_children): - assert i in child_results, f"Child {i} should have reported results" - assert "error" not in child_results[i], f"Child {i} should not have errors: {child_results[i]}" - assert child_results[i]["count"] == 2, f"Child {i} should have exactly 1 tainted object" - assert child_results[i]["has_ranges"] is True, f"Child {i} should have taint ranges" - - # Verify parent is unchanged - final_parent_count = _num_objects_tainted_in_request() - assert final_parent_count == parent_count, "Parent taint count should remain unchanged" - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork_with_os_fork(): - """Test fork safety using os.fork() directly.""" - - # Parent setup - _start_iast_context_and_oce() - parent_data = taint_pyobject( - "parent_before_fork", source_name="parent_source", source_value="parent_value", source_origin=OriginType.PATH - ) - - parent_count_before = _num_objects_tainted_in_request() - assert parent_count_before == 1 - - # Fork using os.fork() - pid = os.fork() - - if pid == 0: - # Child process - try: - # Child should have clean thread-local state - child_count_initial = _num_objects_tainted_in_request() - # Verify child isolation 1 - assert child_count_initial == 1, f"Child should start with 0 tainted objects, got {child_count_initial}" - # Create new context in child (should be isolated) - _start_iast_context_and_oce() - - # Create child-specific tainted data - num_objects = 4 - for _ in range(num_objects): - data = f"child_after_fork_{uuid.uuid4()}" - print(data) - child_data = taint_pyobject( - data, source_name="child_source", source_value=data, source_origin=OriginType.COOKIE - ) - - child_count_after = _num_objects_tainted_in_request() - - # Verify child isolation 4 - assert ( - child_count_after == num_objects - ), f"Child should have 1 tainted object after creation, got {child_count_after}" - - # Verify child can access its own data - child_ranges = get_ranges(child_data) - assert len(child_ranges) > 0, "Child should be able to access its tainted ranges" - - # Test that child cannot access parent's pre-fork data - - parent_ranges_in_child = get_ranges(parent_data) - # If we can get ranges, they should be empty due to isolation - assert ( - len(parent_ranges_in_child) == 1 - ), f"Child should not have access to parent's pre-fork taint data: {len(parent_ranges_in_child)}" - - # Child exits successfully - os._exit(0) - - except Exception as e: - print(f"Child process error: {e}", file=sys.stderr) - os._exit(1) - - else: - # Parent process - # Wait for child to complete - _, status = os.waitpid(pid, 0) - assert os.WEXITSTATUS(status) == 0, f"Child exit code was {os.WEXITSTATUS(status)}" - # Verify parent state is preserved - parent_count_after = _num_objects_tainted_in_request() - assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - # Verify parent can still access its data - parent_ranges = get_ranges(parent_data) - assert len(parent_ranges) > 0, "Parent should still have access to its tainted data" - - -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork_context_reset_isolation(): - """Test that context resets in parent don't affect child processes.""" - - def child_with_context_operations(queue): - """Child process that performs various context operations.""" - try: - _start_iast_context_and_oce() - - # Create initial tainted data - data1 = taint_pyobject("child_data1", "source1", "value1", OriginType.PARAMETER) # noqa: F841 - count1 = _num_objects_tainted_in_request() - - # Reset and recreate context - _end_iast_context_and_oce() - _start_iast_context_and_oce() - - # Should start fresh - count_after_reset = _num_objects_tainted_in_request() - - # Create new tainted data - data2 = taint_pyobject("child_data2", "source2", "value2", OriginType.HEADER_NAME) # noqa: F841 - count2 = _num_objects_tainted_in_request() - - queue.put({"initial_count": count1, "count_after_reset": count_after_reset, "final_count": count2}) - - except Exception as e: - queue.put({"error": str(e)}) - - # Parent setup with context - _start_iast_context_and_oce() - _ = taint_pyobject("parent_data", "parent_source", "parent_value", OriginType.BODY) - - # Start child process - queue = Queue() - child = Process(target=child_with_context_operations, args=(queue,)) - child.start() - - # While child is running, perform parent operations - time.sleep(0.1) # Give child time to start - - # Reset parent context - _end_iast_context_and_oce() - _start_iast_context_and_oce() - - # Create new parent data - _ = taint_pyobject("parent_data2", "parent_source2", "parent_value2", OriginType.COOKIE) - parent_final_count = _num_objects_tainted_in_request() - - # Wait for child to complete - child.join() - - # Get child results - child_result = queue.get() - assert "error" not in child_result, f"Child error: {child_result.get('error')}" - - # Verify child operated independently - assert child_result["initial_count"] == 2, "Child should have had 1 tainted object initially" - assert child_result["count_after_reset"] == 0, "Child should have 0 objects after reset" - assert child_result["final_count"] == 1, "Child should have 1 object after recreating context" - - # Verify parent operations were independent - assert parent_final_count == 1, "Parent should have 1 tainted object after reset and recreation" - - -if __name__ == "__main__": - # Run a simple test when executed directly - test_fork_taint_isolation() - print("Fork taint isolation test passed!") diff --git a/tests/appsec/iast/taint_tracking/test_native_taint_range_gevent_fork.py b/tests/appsec/iast/taint_tracking/test_native_taint_range_gevent_fork.py deleted file mode 100644 index 2ad64413cb6..00000000000 --- a/tests/appsec/iast/taint_tracking/test_native_taint_range_gevent_fork.py +++ /dev/null @@ -1,520 +0,0 @@ -# -*- coding: utf-8 -*- -from multiprocessing import Process -from multiprocessing import Queue -import os -import subprocess -import sys - -import pytest - - -# Import gevent and monkey patch before other imports -try: - import gevent - import gevent.monkey - - GEVENT_AVAILABLE = True -except ImportError: - GEVENT_AVAILABLE = False - -from ddtrace.appsec._iast._iast_request_context_base import _iast_start_request -from ddtrace.appsec._iast._iast_request_context_base import _num_objects_tainted_in_request -from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject -from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect - - -@pytest.mark.skipif(not GEVENT_AVAILABLE, reason="gevent not available") -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_gevent_monkey_patch_os_fork(): - """Test that taint tracking works correctly with gevent monkey patched os.fork().""" - - def run_with_gevent_monkey_patch(): - """Function to run with gevent monkey patching enabled.""" - # Apply gevent monkey patching - gevent.monkey.patch_all() - - # Parent setup - _iast_start_request() - parent_data = taint_pyobject( - "parent_gevent_data", - source_name="parent_gevent_source", - source_value="parent_gevent_value", - source_origin=OriginType.PARAMETER, - ) - - parent_count_before = _num_objects_tainted_in_request() - assert parent_count_before == 1 - - # Fork using monkey-patched os.fork() - pid = os.fork() - - if pid == 0: - # Child process - try: - # Child should have clean thread-local state - child_count_initial = _num_objects_tainted_in_request() - # Verify child isolation 1 - assert child_count_initial == 1, f"Child should start with 0 tainted objects, got {child_count_initial}" - # Create new context in child - _iast_start_request() - - # Create child-specific tainted data - child_data = taint_pyobject( - "child_gevent_data", - source_name="child_gevent_source", - source_value="child_gevent_value", - source_origin=OriginType.COOKIE, - ) - - child_count_after = _num_objects_tainted_in_request() - - # Verify child isolation - assert child_count_after == 1, "Child should have 1 tainted object after creation" - - # Verify child can access its own data - child_ranges = get_ranges(child_data) - assert len(child_ranges) > 0, "Child should be able to access its tainted ranges" - - # Test that child cannot access parent's pre-fork data - parent_ranges_in_child = get_ranges(parent_data) - assert len(parent_ranges_in_child) == 0, "Child should not have access to parent's pre-fork taint data" - - # Child exits successfully - os._exit(0) - - except Exception as e: - print(f"Child process error: {e}", file=sys.stderr) - os._exit(1) - - else: - # Parent process - # Wait for child to complete - _, status = os.waitpid(pid, 0) - assert status == 0, "Child process should exit successfully" - - # Verify parent state is preserved - parent_count_after = _num_objects_tainted_in_request() - assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - # Verify parent can still access its data - parent_ranges = get_ranges(parent_data) - assert len(parent_ranges) > 0, "Parent should still have access to its tainted data" - - # Run the test - run_with_gevent_monkey_patch() - - -@pytest.mark.skipif(not GEVENT_AVAILABLE, reason="gevent not available") -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_gevent_monkey_patch_subprocess(): - """Test that taint tracking works correctly with gevent monkey patched subprocess.""" - - # Create a subprocess script that tests taint isolation - subprocess_script = """ -import sys -sys.path.insert(0, "/home/alberto.vara/projects/dd-python/dd-trace-py") - -from ddtrace.appsec._iast._taint_tracking import OriginType -from ddtrace.appsec._iast._taint_tracking import get_ranges -from ddtrace.appsec._iast._iast_request_context_base import _iast_start_request -from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject - -# Test subprocess isolation -_iast_start_request() - -# Verify subprocess starts with clean state -initial_count = _num_objects_tainted_in_request() -assert initial_count == 0, f"Subprocess should start with 0 tainted objects, got {initial_count}" - -# Create tainted data in subprocess -subprocess_data = taint_pyobject( - "subprocess_data", - source_name="subprocess_source", - source_value="subprocess_value", - source_origin=OriginType.BODY -) - -subprocess_count = _num_objects_tainted_in_request() -# TODO(APPSEC-58375): subprocess_count should be equal to 1 -assert subprocess_count == 0, f"Subprocess should have 1 tainted object, got {subprocess_count}" - -# Verify subprocess can access its own data -subprocess_ranges = get_ranges(subprocess_data) -# TODO(APPSEC-58375): len(subprocess_ranges) should be greater than zero -assert len(subprocess_ranges) == 0, "Subprocess should be able to access its tainted ranges" - -print("SUBPROCESS_SUCCESS") -""" # noqa: W291 - - # Apply gevent monkey patching - gevent.monkey.patch_all() - - # Parent setup - _iast_start_request() - parent_data = taint_pyobject( - "parent_subprocess_data", - source_name="parent_subprocess_source", - source_value="parent_subprocess_value", - source_origin=OriginType.HEADER_NAME, - ) - - parent_count_before = _num_objects_tainted_in_request() - assert parent_count_before == 1 - - # Run subprocess using monkey-patched subprocess - result = subprocess.run([sys.executable, "-c", subprocess_script], capture_output=True, text=True, timeout=30) - - # Verify subprocess executed successfully - assert result.returncode == 0, f"Subprocess failed with stderr: {result.stderr}" - assert "SUBPROCESS_SUCCESS" in result.stdout, "Subprocess should complete successfully" - - # Verify parent state is unchanged - parent_count_after = _num_objects_tainted_in_request() - assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - # Verify parent can still access its data - parent_ranges = get_ranges(parent_data) - assert len(parent_ranges) > 0, "Parent should still have access to its tainted data" - - -@pytest.mark.skipif(not GEVENT_AVAILABLE, reason="gevent not available") -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_gevent_greenlets_with_fork(): - """Test taint tracking with gevent greenlets and fork operations.""" - - def greenlet_worker(worker_id, results): - """Worker function that runs in a gevent greenlet.""" - try: - _iast_start_request() - - # Create tainted data in greenlet - greenlet_data = taint_pyobject( - f"greenlet_{worker_id}_data", - source_name=f"greenlet_{worker_id}_source", - source_value=f"greenlet_{worker_id}_value", - source_origin=OriginType.PARAMETER, - ) - - greenlet_count = _num_objects_tainted_in_request() - greenlet_ranges = get_ranges(greenlet_data) - - # Simulate some async work - gevent.sleep(0.01) - - # Fork from within greenlet - pid = os.fork() - - if pid == 0: - # Child process - try: - # Child should have clean state - child_count = _num_objects_tainted_in_request() - - assert child_count == 1, f"Child should start clean, got {child_count}" - # Create child context - _iast_start_request() - - # Create child tainted data - child_data = taint_pyobject( - f"child_from_greenlet_{worker_id}", - source_name=f"child_greenlet_{worker_id}_source", - source_value=f"child_greenlet_{worker_id}_value", - source_origin=OriginType.COOKIE, - ) - - child_final_count = _num_objects_tainted_in_request() - child_ranges = get_ranges(child_data) - - # Verify child isolation - assert child_final_count == 1, f"Child should have 1 object, got {child_final_count}" - assert len(child_ranges) > 0, "Child should have taint ranges" - - os._exit(0) - - except Exception as e: - print(f"Child error in greenlet {worker_id}: {e}", file=sys.stderr) - os._exit(1) - - else: - # Parent greenlet continues - _, status = os.waitpid(pid, 0) - - results[worker_id] = { - "greenlet_count": greenlet_count, - "greenlet_has_ranges": len(greenlet_ranges) > 0, - "child_exit_status": status, - } - - except Exception as e: - results[worker_id] = {"error": str(e)} - - # Apply gevent monkey patching - gevent.monkey.patch_all() - - # Parent setup - _iast_start_request() - parent_data = taint_pyobject( - "parent_greenlet_data", - source_name="parent_greenlet_source", - source_value="parent_greenlet_value", - source_origin=OriginType.PATH, - ) - - parent_count_before = _num_objects_tainted_in_request() - - # Create multiple greenlets that will fork - results = {} - greenlets = [] - - for i in range(3): - greenlet = gevent.spawn(greenlet_worker, i, results) - greenlets.append(greenlet) - - # Wait for all greenlets to complete - gevent.joinall(greenlets, timeout=30) - - # Verify all greenlets completed successfully - for i in range(3): - assert i in results, f"Greenlet {i} should have reported results" - assert "error" not in results[i], f"Greenlet {i} should not have errors: {results[i]}" - assert results[i]["greenlet_count"] == 1, f"Greenlet {i} should have 1 tainted object" - assert results[i]["greenlet_has_ranges"] is True, f"Greenlet {i} should have taint ranges" - assert results[i]["child_exit_status"] == 0, f"Child from greenlet {i} should exit successfully" - - # Verify parent state is unchanged - parent_count_after = _num_objects_tainted_in_request() - assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - parent_ranges = get_ranges(parent_data) - # TODO(APPSEC-58375): len(parent_ranges) should be greater than 0 - assert len(parent_ranges) == 0, "Parent should still have access to its tainted data" - - -@pytest.mark.skipif(not GEVENT_AVAILABLE, reason="gevent not available") -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_gevent_monkey_patch_multiprocessing(): - """Test taint tracking with gevent monkey patched multiprocessing.""" - - def multiprocessing_worker(queue): - """Worker function for multiprocessing with gevent.""" - try: - # Apply gevent monkey patching in worker - gevent.monkey.patch_all() - - _iast_start_request() - - # Create tainted data in worker process - worker_data = taint_pyobject( - "multiprocessing_worker_data", - source_name="multiprocessing_worker_source", - source_value="multiprocessing_worker_value", - source_origin=OriginType.BODY, - ) - - worker_count = _num_objects_tainted_in_request() - worker_ranges = get_ranges(worker_data) - - # Test greenlet within multiprocessing worker - def greenlet_in_worker(): - greenlet_data = taint_pyobject( - "greenlet_in_worker_data", - source_name="greenlet_in_worker_source", - source_value="greenlet_in_worker_value", - source_origin=OriginType.COOKIE, - ) - return get_ranges(greenlet_data) - - greenlet = gevent.spawn(greenlet_in_worker) - greenlet_ranges = greenlet.get(timeout=10) - - final_count = _num_objects_tainted_in_request() - - queue.put( - { - "worker_count": worker_count, - "worker_has_ranges": len(worker_ranges) > 0, - "greenlet_has_ranges": len(greenlet_ranges) > 0, - "final_count": final_count, - } - ) - - except Exception as e: - queue.put({"error": str(e)}) - - # Apply gevent monkey patching in parent - gevent.monkey.patch_all() - - # Parent setup - _iast_start_request() - parent_data = taint_pyobject( - "parent_multiprocessing_data", - source_name="parent_multiprocessing_source", - source_value="parent_multiprocessing_value", - source_origin=OriginType.HEADER_NAME, - ) - - parent_count_before = _num_objects_tainted_in_request() - - # Start multiprocessing worker - queue = Queue() - worker = Process(target=multiprocessing_worker, args=(queue,)) - worker.start() - worker.join(timeout=30) - - # Get results from worker - assert not queue.empty(), "Worker should have produced results" - worker_result = queue.get() - - # Verify worker results - assert "error" not in worker_result, f"Worker error: {worker_result.get('error')}" - assert worker_result["worker_count"] == 1, "Worker should have 1 tainted object initially" - assert worker_result["worker_has_ranges"] is True, "Worker should have taint ranges" - assert worker_result["greenlet_has_ranges"] is True, "Greenlet in worker should have taint ranges" - assert worker_result["final_count"] == 2, "Worker should have 2 tainted objects total" - - # Verify parent state is unchanged - parent_count_after = _num_objects_tainted_in_request() - assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - parent_ranges = get_ranges(parent_data) - assert len(parent_ranges) > 0, "Parent should still have access to its tainted data" - - -@pytest.mark.skipif(not GEVENT_AVAILABLE, reason="gevent not available") -@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_gevent_context_switching_with_taint(): - """Test that taint tracking works correctly with gevent context switching.""" - - def context_switching_worker(worker_id, shared_results): - """Worker that performs context switches while manipulating taint data.""" - try: - _iast_start_request() - - # Create initial tainted data - data1 = taint_pyobject( - f"worker_{worker_id}_data1", - source_name=f"worker_{worker_id}_source1", - source_value=f"worker_{worker_id}_value1", - source_origin=OriginType.PARAMETER, - ) - - # Yield control to other greenlets - gevent.sleep(0.001) - - # Verify data is still accessible after context switch - ranges1 = get_ranges(data1) - count1 = _num_objects_tainted_in_request() - - # Create more tainted data - data2 = taint_pyobject( - f"worker_{worker_id}_data2", - source_name=f"worker_{worker_id}_source2", - source_value=f"worker_{worker_id}_value2", - source_origin=OriginType.COOKIE, - ) - - # Another context switch - gevent.sleep(0.001) - - # Combine tainted data - combined = add_aspect(data1, data2) - combined_ranges = get_ranges(combined) - final_count = _num_objects_tainted_in_request() - - # Fork from within context-switching greenlet - pid = os.fork() - - if pid == 0: - # Child process - try: - child_count = _num_objects_tainted_in_request() # noqa: F841 - _iast_start_request() - - child_data = taint_pyobject( # noqa: F841 - f"child_worker_{worker_id}_data", - source_name=f"child_worker_{worker_id}_source", - source_value=f"child_worker_{worker_id}_value", - source_origin=OriginType.BODY, - ) - - child_final_count = _num_objects_tainted_in_request() # noqa: F841 - - # Verify child isolation - # assert child_count == 0, f"Child should start clean" - # assert child_final_count == 1, f"Child should have 1 object" - - os._exit(0) - - except Exception as e: - print(f"Child error in context switching worker {worker_id}: {e}", file=sys.stderr) - os._exit(1) - - else: - # Parent continues - _, status = os.waitpid(pid, 0) - - shared_results[worker_id] = { - "count1": count1, - "ranges1_exist": len(ranges1) > 0, - "combined_ranges": len(combined_ranges), - "final_count": final_count, - "child_status": status, - } - - except Exception as e: - shared_results[worker_id] = {"error": str(e)} - - # Apply gevent monkey patching - gevent.monkey.patch_all() - - # Parent setup - _iast_start_request() - parent_data = taint_pyobject( # noqa: F841 - "parent_context_switch_data", - source_name="parent_context_switch_source", - source_value="parent_context_switch_value", - source_origin=OriginType.PATH, - ) - - parent_count_before = _num_objects_tainted_in_request() # noqa: F841 - - # Create multiple context-switching greenlets - shared_results = {} - greenlets = [] - - for i in range(5): - greenlet = gevent.spawn(context_switching_worker, i, shared_results) - greenlets.append(greenlet) - - # Wait for all greenlets - gevent.joinall(greenlets, timeout=30) - - # Verify all workers completed successfully - for i in range(5): - assert i in shared_results, f"Worker {i} should have reported results" - result = shared_results[i] - assert "error" not in result, f"Worker {i} should not have errors: {result}" - # TODO(APPSEC-58375): - # assert result["count1"] == 1, f"Worker {i} should have 1 object initially" - # assert result["ranges1_exist"] is True, f"Worker {i} should have ranges after context switch" - # assert result["combined_ranges"] >= 2, f"Worker {i} should have combined ranges" - assert result["final_count"] >= 2, f"Worker {i} should have multiple objects" - assert result["child_status"] == 0, f"Child from worker {i} should exit successfully" - - # Verify parent state is unchanged - # TODO(APPSEC-58375): parent_count_after = _num_objects_tainted_in_request() - # TODO(APPSEC-58375): assert parent_count_after == parent_count_before, "Parent taint count should be unchanged" - - # TODO(APPSEC-58375): parent_ranges = get_ranges(parent_data) - # TODO(APPSEC-58375): assert len(parent_ranges) > 0, "Parent should still have access to its tainted data" - - -if __name__ == "__main__": - if GEVENT_AVAILABLE: - # Run a simple test when executed directly - test_gevent_monkey_patch_os_fork() - print("Gevent monkey patch os.fork test passed!") - else: - print("Gevent not available, skipping tests") diff --git a/tests/appsec/iast/test_fork_handler_regression.py b/tests/appsec/iast/test_fork_handler_regression.py new file mode 100644 index 00000000000..8ea9632e24e --- /dev/null +++ b/tests/appsec/iast/test_fork_handler_regression.py @@ -0,0 +1,357 @@ +""" +Regression tests for IAST fork handler to prevent segmentation faults. + +These tests verify that IAST correctly handles two types of forks: + +1. **Early forks (web workers)**: Forks that happen BEFORE IAST has any active state. + These are safe - IAST remains enabled in the child and can initialize fresh. + Example: gunicorn/uvicorn worker processes. + +2. **Late forks (multiprocessing)**: Forks that happen AFTER IAST has active contexts. + These inherit corrupted native state and must have IAST disabled in the child. + Example: multiprocessing.Process with IAST already running. + +The fork handler detects which type of fork occurred by checking for active contexts. +""" +from multiprocessing import Process +from multiprocessing import Queue +import os +import sys + +import pytest + +from ddtrace.appsec._iast._iast_request_context_base import _num_objects_tainted_in_request +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from tests.appsec.iast.iast_utils import _end_iast_context_and_oce +from tests.appsec.iast.iast_utils import _start_iast_context_and_oce + + +def test_fork_handler_callable(iast_context_defaults): + """Verify that _reset_iast_after_fork is callable and disables IAST.""" + from ddtrace.appsec._iast import _disable_iast_after_fork + from ddtrace.settings.asm import config as asm_config + + # Should not raise any exception + try: + original_state = asm_config._iast_enabled + _disable_iast_after_fork() + # Fork handler should disable IAST + assert asm_config._iast_enabled is False, "IAST should be disabled after fork" + # Restore for other tests + asm_config._iast_enabled = original_state + except Exception as e: + pytest.fail(f"Fork handler raised unexpected exception: {e}") + + +def test_fork_handler_with_active_context(iast_context_defaults): + """Verify fork handler disables IAST and clears context when active.""" + from ddtrace.appsec._iast import _disable_iast_after_fork + from ddtrace.appsec._iast._taint_tracking import is_tainted + from ddtrace.settings.asm import config as asm_config + + _start_iast_context_and_oce() + + # Create some tainted objects + tainted = taint_pyobject("test_data", source_name="test", source_value="test", source_origin=OriginType.PARAMETER) + assert is_tainted(tainted), "Should be tainted before fork" + + # Reset simulates what happens after fork - IAST is disabled + original_state = asm_config._iast_enabled + _disable_iast_after_fork() + + # IAST should now be disabled + assert asm_config._iast_enabled is False, "IAST should be disabled after fork" + + # After reset, we should be able to call these safely (they're no-ops now) + _end_iast_context_and_oce() + + # Restore for other tests + asm_config._iast_enabled = original_state + + +def test_multiprocessing_with_iast_no_segfault(iast_context_defaults): + """ + Regression test: Verify that late forks (multiprocessing) safely disable IAST. + + This simulates multiprocessing.Process forking AFTER IAST has active contexts. + The fork handler should detect the active state and disable IAST in the child + to prevent segmentation faults from corrupted native extension state. + """ + + def child_process_work(queue): + """Child process where IAST should be disabled.""" + try: + from ddtrace.appsec._iast._taint_tracking import is_tainted + from ddtrace.settings.asm import config as asm_config + + # Start IAST in child (will be a no-op since IAST is disabled) + _start_iast_context_and_oce() + + # IAST should be disabled in child + is_enabled = asm_config._iast_enabled + + # Taint operations should be no-ops (not crash) + tainted_str = taint_pyobject( + "child_data", source_name="child_source", source_value="value", source_origin=OriginType.PARAMETER + ) + + # Since IAST is disabled, count should be 0 and object not tainted + count = _num_objects_tainted_in_request() + is_obj_tainted = is_tainted(tainted_str) + + queue.put(("success", is_enabled, count, is_obj_tainted)) + + except Exception as e: + queue.put(("error", str(e), type(e).__name__)) + + # Parent setup - IAST works normally + _start_iast_context_and_oce() + _ = taint_pyobject( + "parent_data", source_name="parent", source_value="value", source_origin=OriginType.HEADER_NAME + ) # noqa: F841 + + # Fork a child process + queue = Queue() + child = Process(target=child_process_work, args=(queue,)) + child.start() + child.join(timeout=5) + + # Verify child didn't crash (the main goal) + assert child.exitcode == 0, f"Child process crashed with exit code {child.exitcode}" + + # Verify child completed successfully + result = queue.get(timeout=1) + assert result[0] == "success", f"Child process failed: {result}" + assert result[1] is False, "IAST should be disabled in child" + assert result[2] == 0, "Child should have 0 tainted objects (IAST disabled)" + assert result[3] is False, "Objects should not be tainted in child (IAST disabled)" + + +def test_multiple_fork_operations(iast_context_defaults): + """ + Test that multiple sequential fork operations don't cause segfaults. + + Each child process should have IAST safely disabled by the fork handler, + ensuring no crashes occur even with multiple forks. + """ + + def simple_child_work(queue, child_id): + """Simple child process work - IAST will be disabled.""" + try: + from ddtrace.settings.asm import config as asm_config + + # These should be safe no-ops since IAST is disabled + _start_iast_context_and_oce() + taint_pyobject(f"data_{child_id}", "source", "value", OriginType.PARAMETER) + + # Verify IAST is disabled + is_enabled = asm_config._iast_enabled + queue.put(("success", child_id, is_enabled)) + except Exception as e: + queue.put(("error", child_id, str(e))) + + _start_iast_context_and_oce() + + num_children = 5 + queue = Queue() + children = [] + + for i in range(num_children): + child = Process(target=simple_child_work, args=(queue, i)) + children.append(child) + child.start() + + # Wait for all children + for child in children: + child.join(timeout=5) + assert child.exitcode == 0, f"Child {child.pid} crashed" + + # Verify all completed successfully without IAST enabled + results = [] + while not queue.empty(): + results.append(queue.get(timeout=1)) + + assert len(results) == num_children, f"Expected {num_children} results, got {len(results)}" + for result in results: + assert result[0] == "success", f"Child failed: {result}" + assert result[2] is False, f"IAST should be disabled in child {result[1]}" + + +def test_fork_with_os_fork_no_segfault(iast_context_defaults): + """ + Test that os.fork() directly doesn't cause segfaults. + + This is a direct test of fork safety - IAST is disabled in the child + to prevent any native extension corruption issues. + """ + from ddtrace.appsec._iast._taint_tracking import is_tainted + + _start_iast_context_and_oce() + parent_data = taint_pyobject("parent", "source", "value", OriginType.PATH) + assert is_tainted(parent_data), "Should be tainted in parent" + + pid = os.fork() + + if pid == 0: + # Child process - IAST should be disabled + try: + from ddtrace.settings.asm import config as asm_config + + # IAST should be disabled after fork + if asm_config._iast_enabled: + print("ERROR: IAST should be disabled in child", file=sys.stderr) + os._exit(1) + + # These should not segfault (they're no-ops) + _start_iast_context_and_oce() + child_data = taint_pyobject("child", "source", "value", OriginType.PARAMETER) + + # Since IAST is disabled, nothing should be tainted + count = _num_objects_tainted_in_request() + if count != 0: + print(f"ERROR: Expected 0 tainted objects, got {count}", file=sys.stderr) + os._exit(1) + + if is_tainted(child_data): + print("ERROR: Object should not be tainted (IAST disabled)", file=sys.stderr) + os._exit(1) + + os._exit(0) + except Exception as e: + print(f"Child error: {e}", file=sys.stderr) + os._exit(1) + else: + # Parent process + _, status = os.waitpid(pid, 0) + exit_code = os.WEXITSTATUS(status) + assert exit_code == 0, f"Child process failed with exit code {exit_code}" + + +def test_fork_handler_clears_state(iast_context_defaults): + """ + Verify that the fork handler disables IAST and clears state. + + The fork handler clears all taint tracking state and disables IAST + to prevent segmentation faults from corrupted native extension state. + """ + from ddtrace.appsec._iast import _disable_iast_after_fork + from ddtrace.appsec._iast._taint_tracking import is_tainted + from ddtrace.settings.asm import config as asm_config + + _start_iast_context_and_oce() + tainted = taint_pyobject("test", "source", "value", OriginType.PARAMETER) + assert is_tainted(tainted), "Should be tainted before fork" + + # Manually call the fork handler (simulating what happens after fork) + original_state = asm_config._iast_enabled + _disable_iast_after_fork() + + # IAST should now be disabled + assert asm_config._iast_enabled is False, "IAST should be disabled after fork" + + # After reset, these should be safe no-ops + _end_iast_context_and_oce() + _start_iast_context_and_oce() + + # taint_pyobject should be a no-op (IAST disabled) + tainted2 = taint_pyobject("test2", "source2", "value2", OriginType.PARAMETER) + count = _num_objects_tainted_in_request() + assert count == 0, "Should have 0 tainted objects (IAST disabled)" + assert not is_tainted(tainted2), "Should not be tainted (IAST disabled)" + + _end_iast_context_and_oce() + + # Restore for other tests + asm_config._iast_enabled = original_state + + +def test_eval_in_forked_process(iast_context_defaults): + """ + Regression test: Verify that eval() doesn't crash in forked processes. + + With IAST disabled in the child, eval() should work normally without + any instrumentation or tainting. + """ + + def child_eval_work(queue): + """Child process with IAST disabled.""" + try: + from ddtrace.appsec._iast._taint_tracking import is_tainted + from ddtrace.settings.asm import config as asm_config + + # IAST should be disabled, so this is a no-op + _start_iast_context_and_oce() + + # Test eval - taint_pyobject is a no-op since IAST is disabled + code = "1 + 1" + tainted_code = taint_pyobject(code, "code_source", code, OriginType.PARAMETER) + + # Code should not be tainted (IAST disabled) + is_obj_tainted = is_tainted(tainted_code) + + # This should not crash + result = eval(tainted_code) + + is_enabled = asm_config._iast_enabled + + queue.put(("success", result, is_enabled, is_obj_tainted)) + except Exception as e: + queue.put(("error", str(e), type(e).__name__)) + + _start_iast_context_and_oce() + + queue = Queue() + child = Process(target=child_eval_work, args=(queue,)) + child.start() + child.join(timeout=5) + + assert child.exitcode == 0, f"Child crashed with exit code {child.exitcode}" + + result = queue.get(timeout=1) + assert result[0] == "success", f"Child eval failed: {result}" + assert result[1] == 2, "Eval should return correct result" + assert result[2] is False, "IAST should be disabled in child" + assert result[3] is False, "Code should not be tainted (IAST disabled)" + + +def test_early_fork_keeps_iast_enabled(): + """ + Test that early forks (web workers) keep IAST enabled. + + This simulates the behavior of web framework workers like gunicorn/uvicorn + that fork BEFORE IAST has any active context. In this case, IAST should + remain enabled in the child and work normally. + """ + from ddtrace.appsec._iast import _disable_iast_after_fork + from ddtrace.appsec._iast._taint_tracking import is_tainted + from ddtrace.settings.asm import config as asm_config + + # Ensure IAST is enabled but NO context is active (simulating early fork) + # Don't call _start_iast_context_and_oce() - this simulates pre-fork state + + original_state = asm_config._iast_enabled + asm_config._iast_enabled = True + + # Call the fork handler - should detect no active context and keep IAST enabled + _disable_iast_after_fork() + + # IAST should still be enabled (early fork scenario) + assert asm_config._iast_enabled is True, "IAST should remain enabled for early forks" + + # Now we can initialize IAST fresh in this "worker" + _start_iast_context_and_oce() + + # IAST should work normally + tainted = taint_pyobject("worker_data", "source", "value", OriginType.PARAMETER) + assert is_tainted(tainted), "IAST should work in early fork (web worker)" + + count = _num_objects_tainted_in_request() + assert count > 0, "Should have tainted objects in early fork" + + _end_iast_context_and_oce() + asm_config._iast_enabled = original_state + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/appsec/iast/test_multiprocessing_eval_integration.py b/tests/appsec/iast/test_multiprocessing_eval_integration.py new file mode 100644 index 00000000000..3ab0100e671 --- /dev/null +++ b/tests/appsec/iast/test_multiprocessing_eval_integration.py @@ -0,0 +1,212 @@ +""" +Integration test for multiprocessing + eval with IAST. + +This test simulates the real-world scenario from dd-source that was causing +segmentation faults: a web server using multiprocessing workers with IAST enabled +performing code evaluation. +""" +from multiprocessing import Process +from multiprocessing import Queue +import os +import sys + +import pytest + +from ddtrace.appsec._iast._taint_tracking import OriginType +from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject +from tests.appsec.iast.iast_utils import _start_iast_context_and_oce + + +@pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") +class TestMultiprocessingEvalIntegration: + """ + Integration test combining multiprocessing and eval with IAST. + + This reproduces the dd-source test scenario that was causing segfaults. + """ + + def test_uvicorn_style_worker_with_eval(self): + """ + Simulate a uvicorn-style worker process that performs eval operations. + + This is the regression test for the segfault reported in dd-source + when running with uvicorn multiprocessing workers. + """ + + def worker_process(worker_id, queue): + """ + Simulates a web server worker that: + 1. Starts IAST context (happens on request) + 2. Performs code evaluation (like MCP handler) + 3. Should not segfault + """ + try: + # Initialize IAST in worker (happens automatically in real scenario) + _start_iast_context_and_oce() + + # Simulate request handling with tainted input + user_input = f"worker_{worker_id}_result" + tainted_input = taint_pyobject( + user_input, source_name="request_param", source_value=user_input, source_origin=OriginType.PARAMETER + ) + + # Perform eval operations like MCP handler + code = f'"{tainted_input}" + "_processed"' + result = eval(code) + + # More complex eval scenarios + math_expr = "2 ** 8" + tainted_math = taint_pyobject( + math_expr, source_name="calculation", source_value=math_expr, source_origin=OriginType.BODY + ) + calc_result = eval(tainted_math) + + queue.put( + { + "status": "success", + "worker_id": worker_id, + "result": result, + "calc_result": calc_result, + } + ) + + except Exception as e: + queue.put({"status": "error", "worker_id": worker_id, "error": str(e), "type": type(e).__name__}) + + # Simulate multiple workers (like uvicorn --workers 4) + num_workers = 4 + queue = Queue() + workers = [] + + for i in range(num_workers): + worker = Process(target=worker_process, args=(i, queue)) + workers.append(worker) + worker.start() + + # Wait for all workers + for worker in workers: + worker.join(timeout=10) + assert worker.exitcode == 0, f"Worker {worker.pid} crashed with exit code {worker.exitcode}" + + # Collect results + results = [] + while not queue.empty(): + results.append(queue.get(timeout=1)) + + # Verify all workers completed successfully + assert len(results) == num_workers, f"Expected {num_workers} results, got {len(results)}" + + for result in results: + assert result["status"] == "success", f"Worker {result.get('worker_id')} failed: {result}" + assert "worker_" in result["result"] + assert result["calc_result"] == 256 + + def test_direct_fork_with_eval_no_crash(self): + """ + Direct test using os.fork() with eval operations. + + Most direct reproduction of the segfault scenario. + """ + _start_iast_context_and_oce() + + # Create tainted data in parent + parent_code = "100 + 200" + parent_tainted = taint_pyobject( + parent_code, source_name="parent", source_value=parent_code, source_origin=OriginType.PARAMETER + ) + + # Verify parent works + parent_result = eval(parent_tainted) + assert parent_result == 300 + + # Fork + pid = os.fork() + + if pid == 0: + # Child process + try: + # Start new IAST context in child + _start_iast_context_and_oce() + + # Perform eval in child - this previously caused segfault + child_code = "50 * 4" + child_tainted = taint_pyobject( + child_code, source_name="child", source_value=child_code, source_origin=OriginType.BODY + ) + + child_result = eval(child_tainted) + assert child_result == 200, f"Expected 200, got {child_result}" + + # Multiple eval calls to stress test + for i in range(5): + expr = f"{i} + {i * 10}" + tainted_expr = taint_pyobject( + expr, source_name=f"test_{i}", source_value=expr, source_origin=OriginType.PARAMETER + ) + _ = eval(tainted_expr) + + os._exit(0) + + except Exception as e: + print(f"Child process error: {e}", file=sys.stderr) + import traceback + + traceback.print_exc() + os._exit(1) + else: + # Parent process + _, status = os.waitpid(pid, 0) + exit_code = os.WEXITSTATUS(status) + assert exit_code == 0, f"Child process crashed with exit code {exit_code}" + + # Verify parent still works after fork + more_parent_code = "1000 - 500" + more_parent_tainted = taint_pyobject( + more_parent_code, source_name="parent2", source_value=more_parent_code, source_origin=OriginType.PATH + ) + more_parent_result = eval(more_parent_tainted) + assert more_parent_result == 500 + + def test_sequential_workers_stress_test(self): + """ + Stress test: Multiple workers created sequentially. + + This ensures the fork handler is stable across many fork operations. + """ + + def simple_eval_worker(queue, iteration): + """Simple worker that just does eval.""" + try: + _start_iast_context_and_oce() + + code = f"{iteration} * 2" + tainted = taint_pyobject(code, "src", code, OriginType.PARAMETER) + result = eval(tainted) + + queue.put(("success", iteration, result)) + except Exception as e: + queue.put(("error", iteration, str(e))) + + num_iterations = 10 + queue = Queue() + + for i in range(num_iterations): + worker = Process(target=simple_eval_worker, args=(queue, i)) + worker.start() + worker.join(timeout=5) + + assert worker.exitcode == 0, f"Worker {i} crashed" + + # Verify all completed + results = [] + while not queue.empty(): + results.append(queue.get(timeout=1)) + + assert len(results) == num_iterations + for status, iteration, result in results: + assert status == "success", f"Iteration {iteration} failed" + assert result == iteration * 2 + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/appsec/integrations/fastapi_tests/app.py b/tests/appsec/integrations/fastapi_tests/app.py index 53ae324bc3f..6c7f0dba49e 100644 --- a/tests/appsec/integrations/fastapi_tests/app.py +++ b/tests/appsec/integrations/fastapi_tests/app.py @@ -11,6 +11,7 @@ from fastapi import Request from fastapi.responses import JSONResponse from fastapi.responses import Response +from fastapi.responses import StreamingResponse import requests from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import RequestResponseEndpoint @@ -248,6 +249,38 @@ def vulnerable_request_downstream(port: int = 8050): response = http_.request("GET", f"http://0.0.0.0:{port}/returnheaders") return response.data + @app.get("/iast-stream") + async def iast_stream(data: str = "stream_data"): + """Test endpoint for streaming response with IAST. + + Returns a streaming response using tainted data to test IAST with streaming. + """ + + async def stream_generator(): + # Yield tainted data in chunks + yield data.encode() + yield b" " + yield b"streamed" + + return StreamingResponse(stream_generator(), media_type="text/plain") + + @app.post("/iast-stream-cmdi") + async def iast_stream_cmdi(command: str = Form(...)): + """Test endpoint for streaming response with CMDI vulnerability. + + Accepts form data and uses it in a command injection sink, then returns streaming response. + """ + # Trigger CMDI vulnerability + subp = subprocess.Popen(args=["ls", "-la", command]) + subp.communicate() + subp.wait() + + async def stream_generator(): + yield b"Command executed: " + yield command.encode() + + return StreamingResponse(stream_generator(), media_type="text/plain") + return app diff --git a/tests/appsec/integrations/fastapi_tests/mcp_app.py b/tests/appsec/integrations/fastapi_tests/mcp_app.py new file mode 100644 index 00000000000..186f3ce3f6b --- /dev/null +++ b/tests/appsec/integrations/fastapi_tests/mcp_app.py @@ -0,0 +1,127 @@ +"""FastAPI app with MCP server for IAST testing. + +This app provides both regular HTTP endpoints and MCP tools to test IAST +with different communication patterns. +""" +import subprocess + +from fastapi import FastAPI +from fastapi import Form +from fastapi import Request +from mcp.server.fastmcp import FastMCP + +from ddtrace.appsec._iast._iast_request_context_base import is_iast_request_enabled + + +def get_app(): + """Create a FastAPI app with MCP server endpoints for IAST testing.""" + app = FastAPI() + + # Create MCP server instance + mcp = FastMCP(name="IASTTestServer") + + @mcp.tool(description="Execute a command (vulnerable)") + def execute_command(command: str) -> str: + """Execute a command - intentionally vulnerable for IAST testing.""" + # This triggers CMDI vulnerability + subp = subprocess.Popen(args=["ls", "-la", command]) + subp.communicate() + subp.wait() + return f"Command executed: {command}" + + @mcp.tool(description="Get weather for a location") + def get_weather(location: str) -> str: + """Get weather information for a location.""" + return f"Weather in {location} is 72°F" + + @mcp.tool(description="A simple calculator tool") + def calculator(operation: str, a: int, b: int) -> dict: + """Perform arithmetic operations.""" + if operation == "add": + return {"result": a + b} + elif operation == "multiply": + return {"result": a * b} + return {"result": 0} + + @app.get("/") + async def index(): + """Health check endpoint.""" + return {"status": "ok", "iast_enabled": is_iast_request_enabled()} + + @app.get("/health") + async def health(): + """Health check endpoint.""" + return {"status": "ok"} + + @app.get("/shutdown") + async def shutdown(): + """Shutdown endpoint for test cleanup.""" + from ddtrace import tracer + + tracer.shutdown() + return {"status": "shutting down"} + + @app.post("/iast-cmdi-form") + async def iast_cmdi_form(command: str = Form(...)): + """Regular CMDI endpoint for baseline comparison.""" + subp = subprocess.Popen(args=["ls", "-la", command]) + subp.communicate() + subp.wait() + return {"status": "ok", "command": command} + + @app.get("/iast-cmdi-header") + async def iast_cmdi_header(request: Request): + """CMDI endpoint using tainted header value. + + This endpoint reads a custom header and uses it in a command execution, + testing IAST's ability to detect tainted headers leading to CMDI. + """ + # Get the custom header - this should be tainted by IAST + command = request.headers.get("X-Command", "default") + + # Use the tainted header value in a subprocess (vulnerable) + subp = subprocess.Popen(args=["ls", "-la", command]) + subp.communicate() + subp.wait() + + return { + "status": "ok", + "command_from_header": command, + "user_agent": request.headers.get("User-Agent", ""), + "referer": request.headers.get("Referer", ""), + } + + @app.get("/iast-header-echo") + async def iast_header_echo(request: Request): + """Echo headers back to test header tainting without vulnerability. + + Returns all relevant headers to verify they are properly tainted. + """ + return { + "headers": { + "user_agent": request.headers.get("User-Agent", ""), + "referer": request.headers.get("Referer", ""), + "x_custom": request.headers.get("X-Custom-Header", ""), + "x_command": request.headers.get("X-Command", ""), + "accept": request.headers.get("Accept", ""), + "accept_language": request.headers.get("Accept-Language", ""), + }, + "iast_enabled": is_iast_request_enabled(), + } + + # Mount MCP's SSE app for streaming + # FastMCP provides an sse_app() method that returns a Starlette app + # Get the SSE app from FastMCP - pass None as mount_path since we're mounting it ourselves + mcp_sse_starlette_app = mcp.sse_app(mount_path=None) + + # Mount the MCP SSE app to our FastAPI app at /iast/mcp + app.mount("/iast/mcp", mcp_sse_starlette_app) + + # Store the MCP server on the app for access in tests + app.state.mcp_server = mcp + + return app + + +# Create the app instance for uvicorn +app = get_app() diff --git a/tests/appsec/integrations/fastapi_tests/test_iast_fastapi_testagent.py b/tests/appsec/integrations/fastapi_tests/test_iast_fastapi_testagent.py index 519a8afc7af..aba4a41fb5c 100644 --- a/tests/appsec/integrations/fastapi_tests/test_iast_fastapi_testagent.py +++ b/tests/appsec/integrations/fastapi_tests/test_iast_fastapi_testagent.py @@ -13,6 +13,12 @@ from tests.appsec.integrations.utils_testagent import _get_span +# Common environment configuration for IAST tests +IAST_ENV = { + "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.app."), +} + + def test_iast_header_injection_secure_attack(iast_test_token): """Test that header injection is prevented in a secure FastAPI endpoint.""" with uvicorn_server( @@ -163,10 +169,7 @@ def test_iast_cmdi_form_request_fastapi(iast_test_token): Request object rather than using Form(...) in the signature. """ - env = { - "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.app."), - } - with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=env) as context: + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=IAST_ENV) as context: _, fastapi_client, pid = context response = fastapi_client.post( @@ -208,10 +211,7 @@ def test_iast_cmdi_form_multiple_fastapi(iast_test_token): (command and flag). The vulnerable value is "command". """ - env = { - "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.app."), - } - with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=env) as context: + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=IAST_ENV) as context: _, fastapi_client, pid = context response = fastapi_client.post( @@ -390,10 +390,7 @@ def test_iast_cmdi_bodies_fastapi(body, content_type, iast_test_token): """Parametrized body encodings to validate that IAST taints http.request.body in FastAPI and still reports CMDI on the vulnerable sink in tests/appsec/integrations/fastapi_tests/app.py:cmdi_body """ - env = { - "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.app."), - } - with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=env) as context: + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=IAST_ENV) as context: _, fastapi_client, pid = context response = fastapi_client.post( @@ -446,10 +443,7 @@ def test_iast_cmdi_bodies_fastapi_no_vulnerabilities(body, content_type, iast_te and still reports CMDI on the vulnerable sink in tests/appsec/integrations/fastapi_tests/app.py:cmdi_body """ - env = { - "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.app."), - } - with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=env) as context: + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=IAST_ENV) as context: _, fastapi_client, pid = context response = fastapi_client.post( @@ -473,3 +467,88 @@ def test_iast_cmdi_bodies_fastapi_no_vulnerabilities(body, content_type, iast_te assert len(spans_with_iast) >= 0, f"Invalid number of spans ({len(spans_with_iast)}):\n{spans_with_iast}" assert len(vulnerabilities) == 0, f"Invalid number of vulnerabilities ({len(vulnerabilities)}):\n{vulnerabilities}" + + +def test_iast_stream_fastapi(iast_test_token): + """Test IAST with streaming responses in FastAPI. + + Validates that IAST can handle streaming responses without segfaults or crashes. + Also validates middleware correctly processes headers during streaming. + """ + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 IAST-Test/1.0", + "Referer": "https://example.com/previous-page", + "Accept": "text/plain, application/json", + "Accept-Language": "en-US,en;q=0.9", + "Accept-Encoding": "gzip, deflate, br", + "X-Custom-Header": "tainted-custom-value", + "X-Request-ID": "streaming-test-123", + } + + response = fastapi_client.get( + "/iast-stream", + params={"data": "tainted_stream_data"}, + headers=headers, + ) + assert response.status_code == 200 + content = response.text + assert "tainted_stream_data" in content + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + assert len(spans_with_iast) >= 1 + + +def test_iast_stream_cmdi_fastapi(iast_test_token): + """Test IAST CMDI detection with streaming responses in FastAPI. + + Validates that IAST can detect CMDI vulnerabilities even when the response is streamed. + Also validates middleware correctly processes headers during streaming with POST requests. + """ + with uvicorn_server(iast_enabled="true", token=iast_test_token, port=8050, env=IAST_ENV) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (Testing; Streaming; CMDI-Detection)", + "Referer": "https://test.datadoghq.com/iast-cmdi-test", + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.9,es;q=0.8", + "Accept-Encoding": "gzip, deflate", + "X-Test-Session": "cmdi-streaming-test", + "X-IAST-Test": "command-injection-stream", + "Origin": "https://test.datadoghq.com", + "Content-Type": "application/x-www-form-urlencoded", + } + + response = fastapi_client.post( + "/iast-stream-cmdi", + data={"command": "path_traversal_test_file.txt"}, + headers=headers, + ) + assert response.status_code == 200 + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + vulnerabilities = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + iast_data = load_iast_report(span) + if iast_data and iast_data.get("vulnerabilities"): + vulnerabilities.append(iast_data.get("vulnerabilities")) + + assert len(spans_with_iast) >= 1 + assert len(vulnerabilities) == 1 + assert len(vulnerabilities[0]) == 1 + vulnerability = vulnerabilities[0][0] + assert vulnerability["type"] == VULN_CMDI + assert vulnerability["hash"] diff --git a/tests/appsec/integrations/fastapi_tests/test_iast_mcp_testagent.py b/tests/appsec/integrations/fastapi_tests/test_iast_mcp_testagent.py new file mode 100644 index 00000000000..2c9f8e6fea4 --- /dev/null +++ b/tests/appsec/integrations/fastapi_tests/test_iast_mcp_testagent.py @@ -0,0 +1,585 @@ +"""IAST tests for MCP server to detect segfaults with MCP tool calls. + +This test module validates IAST functionality with MCP (Model Context Protocol) servers. +MCP uses streaming connections internally, which may expose issues with IAST's context +management and taint tracking. + +Includes both in-memory MCP connections and HTTP/SSE-based streaming tests. +""" +from contextlib import asynccontextmanager +import sys + +import pytest + + +# Skip all tests in this module if mcp is not installed +pytest.importorskip("mcp") + +from mcp import ClientSession +from mcp.client.sse import sse_client +from mcp.shared.memory import create_connected_server_and_client_session + +from tests.appsec.appsec_utils import uvicorn_server +from tests.appsec.iast.iast_utils import load_iast_report +from tests.appsec.integrations.utils_testagent import _get_span + + +# Common environment configuration for MCP IAST tests +MCP_IAST_ENV = { + "_DD_IAST_PATCH_MODULES": ("benchmarks.," "tests.appsec.," "tests.appsec.integrations.fastapi_tests.mcp_app."), +} + + +# AIDEV-NOTE: This test is designed to detect segfaults that may occur when IAST +# interacts with MCP tool calls. MCP uses streaming internally for communication +# between client and server, which may trigger context management issues in IAST. + + +HOST = "0.0.0.0" + + +@asynccontextmanager +async def mcp_client_session(port: int): + """Create an MCP client session using SSE for HTTP/SSE-based streaming tests. + + This uses SSE (Server-Sent Events) for bidirectional streaming communication, + which is the real-world way MCP servers communicate over HTTP. + + Note: FastMCP's SSE app has routes at /sse and /messages, so when mounted at + /iast/mcp, the SSE endpoint is at /iast/mcp/sse. + """ + async with sse_client(f"http://{HOST}:{port}/iast/mcp/sse") as (read_stream, write_stream): + async with ClientSession(read_stream, write_stream) as session: + yield session + + +def test_iast_mcp_baseline(iast_test_token): + """Baseline test: Regular CMDI endpoint works with IAST. + + Also validates that headers are properly processed by the middleware. + """ + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-Test; Baseline)", + "Referer": "https://test.datadoghq.com/mcp-baseline", + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9", + "X-Test-Session": "mcp-baseline-test", + } + + # Test health endpoint with headers + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + assert response.json()["status"] == "ok" + + # Test regular CMDI endpoint with headers + headers["Content-Type"] = "application/x-www-form-urlencoded" + response = fastapi_client.post( + "/iast-cmdi-form", + data={"command": "path_traversal_test_file.txt"}, + headers=headers, + ) + assert response.status_code == 200 + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + vulnerabilities = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + iast_data = load_iast_report(span) + if iast_data and iast_data.get("vulnerabilities"): + vulnerabilities.append(iast_data.get("vulnerabilities")) + + assert len(spans_with_iast) >= 1 + assert len(vulnerabilities) >= 1 + + +@pytest.mark.asyncio +async def test_iast_mcp_tool_call_safe(iast_test_token): + """Test MCP tool call (safe tool) with IAST enabled using in-memory connection. + + This test validates that IAST can handle MCP tool calls without causing segmentation faults. + Uses in-memory MCP connection which still exercises streaming internally. + """ + from tests.appsec.integrations.fastapi_tests.mcp_app import get_app + + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-Test; Safe-Tool)", + "Referer": "https://test.datadoghq.com/mcp-safe-tool", + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9", + "X-Test-Session": "mcp-safe-tool-test", + "X-Custom-Header": "safe-tool-testing", + } + + # First ensure server is ready with headers + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + + # Now test MCP tools directly with in-memory connection + app = get_app() + mcp_server = app.state.mcp_server + + async with create_connected_server_and_client_session(mcp_server._mcp_server) as client: + # Call the calculator tool (non-vulnerable) + result = await client.call_tool("calculator", {"operation": "add", "a": 10, "b": 20}) + assert result is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + # We should have at least one span with IAST enabled from the HTTP request + assert len(spans_with_iast) >= 1 + + +@pytest.mark.asyncio +async def test_iast_mcp_vulnerable_tool(iast_test_token): + """Test MCP vulnerable tool with IAST detection using in-memory connection. + + This test validates that IAST can detect vulnerabilities (CMDI) when MCP tools + are called. This is critical for detecting segfaults with MCP + IAST interaction. + + NOTE: MCP tool calls via in-memory connection may not be traced the same way as + HTTP requests, so vulnerability detection may differ from HTTP endpoint tests. + """ + from tests.appsec.integrations.fastapi_tests.mcp_app import get_app + + env = {**MCP_IAST_ENV, "DD_TRACE_DEBUG": "true"} + + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=env, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-Test; Vulnerable-Tool)", + "Referer": "https://test.datadoghq.com/mcp-vulnerable", + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9,es;q=0.8", + "X-Test-Session": "mcp-vulnerable-test", + "X-IAST-Test": "cmdi-detection", + "Content-Type": "application/x-www-form-urlencoded", + } + + # Test baseline HTTP endpoint first with headers + response = fastapi_client.post( + "/iast-cmdi-form", + data={"command": "path_traversal_test_file.txt"}, + headers=headers, + ) + assert response.status_code == 200 + + # Now test MCP tool directly + app = get_app() + mcp_server = app.state.mcp_server + + async with create_connected_server_and_client_session(mcp_server._mcp_server) as client: + # Call the vulnerable execute_command tool + result = await client.call_tool("execute_command", {"command": "test_mcp_file.txt"}) + assert result is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + vulnerabilities = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + iast_data = load_iast_report(span) + if iast_data and iast_data.get("vulnerabilities"): + vulnerabilities.append(iast_data.get("vulnerabilities")) + + # We should have at least one span with IAST enabled + assert len(spans_with_iast) >= 1 + # We should detect CMDI vulnerability from the HTTP endpoint + assert len(vulnerabilities) >= 1 + + +@pytest.mark.asyncio +async def test_iast_mcp_multiple_tool_calls(iast_test_token): + """Test multiple MCP tool calls to stress test IAST with MCP interaction. + + This test makes multiple consecutive MCP tool calls to detect potential + memory issues or segfaults with repeated MCP operations under IAST. + """ + from tests.appsec.integrations.fastapi_tests.mcp_app import get_app + + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-Test; Multiple-Calls)", + "Referer": "https://test.datadoghq.com/mcp-multiple", + "Accept": "*/*", + "Accept-Language": "en-US,en;q=0.9", + "X-Test-Session": "mcp-multiple-calls", + "X-Request-Count": "multiple", + } + + # Ensure server is ready with headers + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + + # Test multiple MCP tool calls + app = get_app() + mcp_server = app.state.mcp_server + + async with create_connected_server_and_client_session(mcp_server._mcp_server) as client: + # Call multiple tools in sequence + result1 = await client.call_tool("calculator", {"operation": "add", "a": 5, "b": 10}) + assert result1 is not None + + result2 = await client.call_tool("get_weather", {"location": "New York"}) + assert result2 is not None + + result3 = await client.call_tool("calculator", {"operation": "multiply", "a": 3, "b": 7}) + assert result3 is not None + + # Finally call the vulnerable one + result4 = await client.call_tool("execute_command", {"command": "test_file.txt"}) + assert result4 is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + # Should have at least one span with IAST from HTTP request + assert len(spans_with_iast) >= 1 + + +def test_iast_mcp_header_tainting(iast_test_token): + """Test that HTTP headers are properly tainted by IAST. + + This test validates that IAST correctly taints incoming HTTP headers, + which is critical for detecting header-based vulnerabilities. + """ + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (Header-Tainting-Test)", + "Referer": "https://test.datadoghq.com/header-tainting", + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9,fr;q=0.8", + "X-Custom-Header": "tainted_custom_value_12345", + "X-Command": "test_header_command", + "X-Test-Session": "header-tainting-test", + "Origin": "https://test.datadoghq.com", + } + + # Call the header echo endpoint to verify headers are tainted + response = fastapi_client.get("/iast-header-echo", headers=headers) + assert response.status_code == 200 + + response_data = response.json() + assert response_data["iast_enabled"] is True + assert response_data["headers"]["user_agent"] == headers["User-Agent"] + assert response_data["headers"]["referer"] == headers["Referer"] + assert response_data["headers"]["x_custom"] == headers["X-Custom-Header"] + assert response_data["headers"]["x_command"] == headers["X-Command"] + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + # Should have at least one span with IAST enabled + assert len(spans_with_iast) >= 1 + + +def test_iast_mcp_header_cmdi(iast_test_token): + """Test CMDI detection using tainted header value. + + This test validates that IAST can detect CMDI vulnerabilities when the + malicious input comes from HTTP headers rather than query params or body. + This is critical for comprehensive security testing. + """ + env = {**MCP_IAST_ENV, "DD_TRACE_DEBUG": "true"} + + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8051, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=env, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (Header-CMDI-Test)", + "Referer": "https://test.datadoghq.com/header-cmdi", + "Accept": "application/json", + "Accept-Language": "en-US,en;q=0.9", + "X-Command": "path_traversal_test_file.txt", # This is the vulnerable value + "X-Test-Session": "header-cmdi-test", + "X-IAST-Test": "cmdi-via-header", + } + + # Call the endpoint that uses header value in command execution + response = fastapi_client.get("/iast-cmdi-header", headers=headers) + assert response.status_code == 200 + + response_data = response.json() + assert response_data["status"] == "ok" + assert response_data["command_from_header"] == headers["X-Command"] + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + vulnerabilities = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + iast_data = load_iast_report(span) + if iast_data and iast_data.get("vulnerabilities"): + vulnerabilities.append(iast_data.get("vulnerabilities")) + + # Should have at least one span with IAST enabled + assert len(spans_with_iast) >= 1 + + # Should detect the CMDI vulnerability from the header + assert len(vulnerabilities) >= 1, f"Expected CMDI vulnerability from header, got: {vulnerabilities}" + + # Verify it's a CMDI vulnerability + from ddtrace.appsec._iast.constants import VULN_CMDI + + found_cmdi = False + for vuln_list in vulnerabilities: + for vuln in vuln_list: + if vuln["type"] == VULN_CMDI: + found_cmdi = True + assert vuln["hash"] + # The evidence should show the tainted header value + break + + assert found_cmdi, f"Expected CMDI vulnerability but got: {vulnerabilities}" + + +@pytest.mark.asyncio +async def test_iast_mcp_sse_streaming_safe_tool(iast_test_token): + """Test MCP tool call via HTTP/SSE streaming with IAST enabled (safe tool). + + This test uses the real HTTP/SSE transport to communicate with the MCP server, + testing IAST's ability to handle actual streaming connections. This is critical + for detecting segfaults that only occur with real HTTP/SSE streaming. + """ + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8052, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-SSE-Test; Safe-Tool)", + "Referer": "https://test.datadoghq.com/mcp-sse-safe", + "Accept": "text/event-stream", + "X-Test-Session": "mcp-sse-safe-test", + } + + # Ensure server is ready + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + + # Now test MCP via HTTP/SSE streaming + async with mcp_client_session(8052) as session: + # Initialize the session + await session.initialize() + + # List available tools + tools_resp = await session.list_tools() + assert len(tools_resp.tools) >= 3 # calculator, get_weather, execute_command + + # Call the calculator tool (safe) + tool_result = await session.call_tool("calculator", {"operation": "add", "a": 15, "b": 27}) + assert not tool_result.isError + assert tool_result.content[0].text is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + # Should have spans with IAST from HTTP requests + assert len(spans_with_iast) >= 1 + + +@pytest.mark.asyncio +async def test_iast_mcp_sse_streaming_vulnerable_tool(iast_test_token): + """Test MCP vulnerable tool via HTTP/SSE streaming with IAST detection. + + **MOST CRITICAL TEST** - This test uses real HTTP/SSE streaming to call + a vulnerable MCP tool. This is where segfaults are most likely to occur + because it combines: + - Real HTTP/SSE bidirectional streaming + - IAST taint tracking + - MCP protocol handling + - Vulnerable subprocess execution + + If segfaults occur with streaming, they will likely happen here. + """ + env = {**MCP_IAST_ENV, "DD_TRACE_DEBUG": "true"} + + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8052, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=env, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-SSE-Test; Vulnerable-Tool)", + "Referer": "https://test.datadoghq.com/mcp-sse-vulnerable", + "Accept": "text/event-stream", + "X-Test-Session": "mcp-sse-vulnerable-test", + "X-IAST-Test": "cmdi-via-sse-streaming", + } + + # Ensure server is ready + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + + # Test MCP vulnerable tool via HTTP/SSE streaming + async with mcp_client_session(8052) as session: + # Initialize the session + await session.initialize() + + # Call the vulnerable execute_command tool + tool_result = await session.call_tool("execute_command", {"command": "sse_stream_test_file.txt"}) + # The tool may return error or success, but should not crash + assert tool_result is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + vulnerabilities = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + iast_data = load_iast_report(span) + if iast_data and iast_data.get("vulnerabilities"): + vulnerabilities.append(iast_data.get("vulnerabilities")) + + # Should have spans with IAST + assert len(spans_with_iast) >= 1 + + # Should detect CMDI vulnerability (may be from HTTP endpoint or MCP tool) + # Note: Vulnerability detection via MCP tools over SSE may differ from HTTP endpoints + assert len(vulnerabilities) >= 0 # Relaxed assertion - main goal is no segfault + + +@pytest.mark.asyncio +async def test_iast_mcp_sse_streaming_multiple_calls(iast_test_token): + """Test multiple MCP tool calls via HTTP/SSE streaming to stress test IAST. + + This test makes multiple consecutive calls through a real HTTP/SSE streaming + connection to detect memory leaks, context corruption, or segfaults that only + manifest with repeated streaming operations. + """ + with uvicorn_server( + python_cmd=sys.executable, + iast_enabled="true", + token=iast_test_token, + port=8052, + app="tests.appsec.integrations.fastapi_tests.mcp_app:app", + env=MCP_IAST_ENV, + ) as context: + _, fastapi_client, pid = context + + headers = { + "User-Agent": "DatadogIAST/1.0 (MCP-SSE-Test; Multiple-Calls)", + "Referer": "https://test.datadoghq.com/mcp-sse-multiple", + "Accept": "text/event-stream", + "X-Test-Session": "mcp-sse-multiple-test", + } + + # Ensure server is ready + response = fastapi_client.get("/", headers=headers) + assert response.status_code == 200 + + # Make multiple MCP calls via HTTP/SSE streaming + async with mcp_client_session(8052) as session: + # Initialize + await session.initialize() + + # Call multiple tools in sequence + result1 = await session.call_tool("calculator", {"operation": "add", "a": 10, "b": 20}) + assert not result1.isError + + result2 = await session.call_tool("get_weather", {"location": "San Francisco"}) + assert not result2.isError + + result3 = await session.call_tool("calculator", {"operation": "multiply", "a": 5, "b": 8}) + assert not result3.isError + + # Finally call the vulnerable one + result4 = await session.call_tool("execute_command", {"command": "multi_stream_test.txt"}) + assert result4 is not None + + response_tracer = _get_span(iast_test_token) + spans_with_iast = [] + for trace in response_tracer: + for span in trace: + if span.get("metrics", {}).get("_dd.iast.enabled") == 1.0: + spans_with_iast.append(span) + + # Should have spans with IAST from HTTP requests + assert len(spans_with_iast) >= 1 diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml index 39782d2ef8b..8eea002fc4b 100644 --- a/tests/appsec/suitespec.yml +++ b/tests/appsec/suitespec.yml @@ -169,7 +169,7 @@ suites: - testagent timeout: 30m appsec_integrations_fastapi: - parallelism: 17 + parallelism: 21 paths: - '@bootstrap' - '@core' From ca86f21fa45ef6ade23a21810f0486a3d8a6dcc4 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Fri, 7 Nov 2025 12:22:25 -0500 Subject: [PATCH 38/42] ci: use pre-built wheels in serverless benchmarks (#15164) ## Description Have serverless benchmarks depend on publishing to S3, and also make sure the index file we publish is valid html5 otherwise some versions of pip will show a warning message. ## Testing ## Risks ## Additional Notes --- .gitlab/benchmarks/serverless.yml | 3 ++- .gitlab/package.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab/benchmarks/serverless.yml b/.gitlab/benchmarks/serverless.yml index d6d4685558e..51f6ba43e84 100644 --- a/.gitlab/benchmarks/serverless.yml +++ b/.gitlab/benchmarks/serverless.yml @@ -6,7 +6,8 @@ benchmark-serverless: trigger: project: DataDog/serverless-tools strategy: depend - needs: [] + needs: + - job: publish-wheels-to-s3 rules: - if: $RELEASE_ALLOW_BENCHMARK_FAILURES == "true" allow_failure: true diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 7d7eb5270e0..151664c18c1 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -128,7 +128,7 @@ publish-wheels-to-s3: generate_index_html() { local outfile="$1" { - echo "" + echo "" for w in "${WHEELS[@]}"; do fname="$(basename "$w")" enc_fname="${fname//+/%2B}" From a5b7b162e8df9d2d3b1213623c207dbdc5f49a75 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 7 Nov 2025 09:44:15 -0800 Subject: [PATCH 39/42] update locks and systests --- .github/workflows/system-tests.yml | 8 +++--- .gitlab-ci.yml | 2 +- .riot/requirements/132305d.txt | 46 ------------------------------ .riot/requirements/13632f0.txt | 39 +++++++++++++++++++++++++ .riot/requirements/18474a9.txt | 40 -------------------------- .riot/requirements/1c3d896.txt | 42 --------------------------- .riot/requirements/30d009a.txt | 40 -------------------------- .riot/requirements/492b83f.txt | 39 +++++++++++++++++++++++++ 8 files changed, 83 insertions(+), 173 deletions(-) delete mode 100644 .riot/requirements/132305d.txt create mode 100644 .riot/requirements/13632f0.txt delete mode 100644 .riot/requirements/18474a9.txt delete mode 100644 .riot/requirements/1c3d896.txt delete mode 100644 .riot/requirements/30d009a.txt create mode 100644 .riot/requirements/492b83f.txt diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 4e6f1f306d5..9c989709d3d 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -45,7 +45,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '279a4f17c9392157cdc106e627c2b57c2233899b' + ref: '38b9edcd63a1158f1a51e0430770052341b9cfdb' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 @@ -90,7 +90,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '279a4f17c9392157cdc106e627c2b57c2233899b' + ref: '38b9edcd63a1158f1a51e0430770052341b9cfdb' - name: Build runner uses: ./.github/actions/install_runner @@ -275,7 +275,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '279a4f17c9392157cdc106e627c2b57c2233899b' + ref: '38b9edcd63a1158f1a51e0430770052341b9cfdb' - name: Download wheels to binaries directory uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: @@ -313,4 +313,4 @@ jobs: run: exit 0 - name: Fails if anything else failed if: needs.parametric.result != 'success' || needs.system-tests.result != 'success' - run: exit 1 + run: exit 1 \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dfe5b8dc860..0d5854d41bc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,7 +14,7 @@ variables: DD_VPA_TEMPLATE: "vpa-template-cpu-p70-10percent-2x-oom-min-cap" # CI_DEBUG_SERVICES: "true" # Automatically managed, use scripts/update-system-tests-version to update - SYSTEM_TESTS_REF: "279a4f17c9392157cdc106e627c2b57c2233899b" + SYSTEM_TESTS_REF: "38b9edcd63a1158f1a51e0430770052341b9cfdb" default: interruptible: true diff --git a/.riot/requirements/132305d.txt b/.riot/requirements/132305d.txt deleted file mode 100644 index a2483e423ad..00000000000 --- a/.riot/requirements/132305d.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/132305d.in -# -asgiref==3.8.1 -attrs==25.3.0 -bcrypt==4.2.1 -certifi==2025.10.5 -charset-normalizer==3.4.4 -coverage[toml]==7.6.1 -dill==0.4.0 -django==3.2.25 -django-configurations==2.5.1 -exceptiongroup==1.3.0 -gevent==22.10.2 -greenlet==3.1.1 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.11 -iniconfig==2.1.0 -legacy-cgi==2.6.4 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pylibmc==1.6.3 -pytest==8.3.5 -pytest-cov==5.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.14.1 -pytz==2025.2 -pyyaml==6.0.3 -requests==2.32.4 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -tomli==2.3.0 -typing-extensions==4.13.2 -urllib3==2.2.3 -zope-event==5.0 -zope-interface==7.2 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==75.3.2 diff --git a/.riot/requirements/13632f0.txt b/.riot/requirements/13632f0.txt new file mode 100644 index 00000000000..e09d2a9e4b4 --- /dev/null +++ b/.riot/requirements/13632f0.txt @@ -0,0 +1,39 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/13632f0.in +# +asgiref==3.10.0 +attrs==25.4.0 +bcrypt==4.2.1 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.1 +dill==0.4.0 +django==4.0.10 +django-configurations==2.5.1 +gevent==25.9.1 +greenlet==3.2.4 +gunicorn==23.0.0 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pylibmc==1.6.3 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.15.1 +pyyaml==6.0.3 +requests==2.32.5 +six==1.17.0 +sortedcontainers==2.4.0 +sqlparse==0.5.3 +urllib3==2.5.0 +zope-event==6.1 +zope-interface==8.0.1 diff --git a/.riot/requirements/18474a9.txt b/.riot/requirements/18474a9.txt deleted file mode 100644 index 8fcd85fe4fe..00000000000 --- a/.riot/requirements/18474a9.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/18474a9.in -# -anyio==3.7.1 -attrs==25.3.0 -certifi==2025.10.5 -charset-normalizer==3.4.4 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.86.0 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.11 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.24 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.20.4 -tomli==2.3.0 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/1c3d896.txt b/.riot/requirements/1c3d896.txt deleted file mode 100644 index 8efc222d6ee..00000000000 --- a/.riot/requirements/1c3d896.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1c3d896.in -# -annotated-types==0.7.0 -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.10.5 -charset-normalizer==3.4.4 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.114.2 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.11 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==2.10.6 -pydantic-core==2.27.2 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.38.6 -tomli==2.3.0 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/30d009a.txt b/.riot/requirements/30d009a.txt deleted file mode 100644 index 44259583d11..00000000000 --- a/.riot/requirements/30d009a.txt +++ /dev/null @@ -1,40 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.8 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/30d009a.in -# -anyio==4.5.2 -attrs==25.3.0 -certifi==2025.10.5 -charset-normalizer==3.4.4 -click==8.1.8 -coverage[toml]==7.6.1 -exceptiongroup==1.3.0 -fastapi==0.94.1 -h11==0.16.0 -httpcore==1.0.9 -httpx==0.27.2 -hypothesis==6.45.0 -idna==3.11 -iniconfig==2.1.0 -jinja2==3.1.6 -markupsafe==2.1.5 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.5.0 -pydantic==1.10.24 -pytest==8.3.5 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 -pytest-mock==3.14.1 -python-multipart==0.0.20 -requests==2.32.4 -sniffio==1.3.1 -sortedcontainers==2.4.0 -starlette==0.26.1 -tomli==2.3.0 -typing-extensions==4.13.2 -urllib3==2.2.3 -uvicorn==0.33.0 diff --git a/.riot/requirements/492b83f.txt b/.riot/requirements/492b83f.txt new file mode 100644 index 00000000000..659390d6b9d --- /dev/null +++ b/.riot/requirements/492b83f.txt @@ -0,0 +1,39 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/492b83f.in +# +asgiref==3.10.0 +attrs==25.4.0 +bcrypt==4.2.1 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.1 +dill==0.4.0 +django==4.2.26 +django-configurations==2.5.1 +gevent==25.9.1 +greenlet==3.2.4 +gunicorn==23.0.0 +hypothesis==6.45.0 +idna==3.11 +iniconfig==2.3.0 +mock==5.2.0 +opentracing==2.4.0 +packaging==25.0 +pluggy==1.6.0 +pygments==2.19.2 +pylibmc==1.6.3 +pytest==8.4.2 +pytest-cov==7.0.0 +pytest-django[testing]==3.10.0 +pytest-mock==3.15.1 +pyyaml==6.0.3 +requests==2.32.5 +six==1.17.0 +sortedcontainers==2.4.0 +sqlparse==0.5.3 +urllib3==2.5.0 +zope-event==6.1 +zope-interface==8.0.1 From 433c748a8cc404632def6f111bf23ef221af023d Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 7 Nov 2025 10:08:01 -0800 Subject: [PATCH 40/42] merge oopsie --- tests/appsec/suitespec.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml index 8eea002fc4b..39782d2ef8b 100644 --- a/tests/appsec/suitespec.yml +++ b/tests/appsec/suitespec.yml @@ -169,7 +169,7 @@ suites: - testagent timeout: 30m appsec_integrations_fastapi: - parallelism: 21 + parallelism: 17 paths: - '@bootstrap' - '@core' From 846730bf4d367140d24a9ccd55bb30bc5c152a7e Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 7 Nov 2025 19:15:01 +0000 Subject: [PATCH 41/42] chore(er): remove deprecated variable (#15060) Co-authored-by: Emmett Butler Co-authored-by: Emmett Butler <723615+emmettbutler@users.noreply.github.com> Co-authored-by: Brett Langdon --- ddtrace/internal/settings/exception_replay.py | 1 - .../notes/er-deprecate-env-var-58386e5884e0de10.yaml | 5 +++++ tests/debugging/exception/test_replay.py | 12 ------------ 3 files changed, 5 insertions(+), 13 deletions(-) create mode 100644 releasenotes/notes/er-deprecate-env-var-58386e5884e0de10.yaml diff --git a/ddtrace/internal/settings/exception_replay.py b/ddtrace/internal/settings/exception_replay.py index 9dc72b4d51d..bc5d0f0dd81 100644 --- a/ddtrace/internal/settings/exception_replay.py +++ b/ddtrace/internal/settings/exception_replay.py @@ -10,7 +10,6 @@ class ExceptionReplayConfig(DDConfig): default=False, help_type="Boolean", help="Enable automatic capturing of exception debugging information", - deprecations=[("debugging.enabled", None, "3.0")], ) max_frames = DDConfig.v( int, diff --git a/releasenotes/notes/er-deprecate-env-var-58386e5884e0de10.yaml b/releasenotes/notes/er-deprecate-env-var-58386e5884e0de10.yaml new file mode 100644 index 00000000000..e93aa2a5187 --- /dev/null +++ b/releasenotes/notes/er-deprecate-env-var-58386e5884e0de10.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + exception replay: removed the deprecated ``DD_EXCEPTION_DEBUGGING_ENABLED`` + variable. diff --git a/tests/debugging/exception/test_replay.py b/tests/debugging/exception/test_replay.py index cb5da2a288e..65055504d27 100644 --- a/tests/debugging/exception/test_replay.py +++ b/tests/debugging/exception/test_replay.py @@ -57,18 +57,6 @@ def test_exception_replay_config_enabled(monkeypatch): assert er_config.enabled -def test_exception_replay_config_enabled_deprecated(monkeypatch): - monkeypatch.setenv("DD_EXCEPTION_DEBUGGING_ENABLED", "1") - - er_config = ExceptionReplayConfig() - assert er_config.enabled - - monkeypatch.setenv("DD_EXCEPTION_REPLAY_ENABLED", "false") - - er_config = ExceptionReplayConfig() - assert not er_config.enabled - - def test_exception_chain_ident(): def a(v, d=None): if not v: From a4b42e974c9396fb6c7d9643de2b1f6a6647d9a5 Mon Sep 17 00:00:00 2001 From: Emmett Butler Date: Fri, 7 Nov 2025 15:31:17 -0800 Subject: [PATCH 42/42] fix failing tests --- .../requirements/{118fd10.txt => 1072660.txt} | 19 +++++---- .riot/requirements/1b4f196.txt | 15 +------ .../requirements/{13632f0.txt => 6c3e5ec.txt} | 5 ++- .../requirements/{c48b250.txt => 6c76bd7.txt} | 26 ++++++------ .riot/requirements/bebf559.txt | 42 ------------------- .../requirements/{199a155.txt => e712306.txt} | 28 ++++++------- riotfile.py | 2 +- .../iast/test_fork_handler_regression.py | 16 +++---- 8 files changed, 49 insertions(+), 104 deletions(-) rename .riot/requirements/{118fd10.txt => 1072660.txt} (82%) rename .riot/requirements/{13632f0.txt => 6c3e5ec.txt} (88%) rename .riot/requirements/{c48b250.txt => 6c76bd7.txt} (66%) delete mode 100644 .riot/requirements/bebf559.txt rename .riot/requirements/{199a155.txt => e712306.txt} (67%) diff --git a/.riot/requirements/118fd10.txt b/.riot/requirements/1072660.txt similarity index 82% rename from .riot/requirements/118fd10.txt rename to .riot/requirements/1072660.txt index 702baae7aab..583f1bbe640 100644 --- a/.riot/requirements/118fd10.txt +++ b/.riot/requirements/1072660.txt @@ -2,13 +2,13 @@ # This file is autogenerated by pip-compile with Python 3.9 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/118fd10.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1072660.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 +certifi==2025.10.5 +charset-normalizer==3.4.4 coverage[toml]==7.10.7 dill==0.4.0 django==4.0.10 @@ -18,8 +18,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 +idna==3.11 iniconfig==2.1.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -30,16 +31,16 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 zope-event==6.0 -zope-interface==8.0 +zope-interface==8.0.1 # The following packages are considered to be unsafe in a requirements file: setuptools==80.9.0 diff --git a/.riot/requirements/1b4f196.txt b/.riot/requirements/1b4f196.txt index e546aa28aea..d9ca0210e41 100644 --- a/.riot/requirements/1b4f196.txt +++ b/.riot/requirements/1b4f196.txt @@ -2,24 +2,16 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -<<<<<<<< HEAD:.riot/requirements/1b4f196.txt # pip-compile --allow-unsafe --no-annotate .riot/requirements/1b4f196.in -======== -# pip-compile --allow-unsafe --no-annotate .riot/requirements/1532cbc.in ->>>>>>>> main:.riot/requirements/1532cbc.txt # asgiref==3.10.0 attrs==25.4.0 bcrypt==4.2.1 certifi==2025.10.5 charset-normalizer==3.4.4 -coverage[toml]==7.11.0 +coverage[toml]==7.11.1 dill==0.4.0 -<<<<<<<< HEAD:.riot/requirements/1b4f196.txt django==5.2.8 -======== -django==4.2.26 ->>>>>>>> main:.riot/requirements/1532cbc.txt django-configurations==2.5.1 gevent==25.9.1 greenlet==3.2.4 @@ -44,8 +36,5 @@ six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 -zope-event==6.0 +zope-event==6.1 zope-interface==8.0.1 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==80.9.0 diff --git a/.riot/requirements/13632f0.txt b/.riot/requirements/6c3e5ec.txt similarity index 88% rename from .riot/requirements/13632f0.txt rename to .riot/requirements/6c3e5ec.txt index e09d2a9e4b4..ce24be968f5 100644 --- a/.riot/requirements/13632f0.txt +++ b/.riot/requirements/6c3e5ec.txt @@ -1,8 +1,8 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/13632f0.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6c3e5ec.in # asgiref==3.10.0 attrs==25.4.0 @@ -19,6 +19,7 @@ gunicorn==23.0.0 hypothesis==6.45.0 idna==3.11 iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 diff --git a/.riot/requirements/c48b250.txt b/.riot/requirements/6c76bd7.txt similarity index 66% rename from .riot/requirements/c48b250.txt rename to .riot/requirements/6c76bd7.txt index 2d957b44797..1e823950d21 100644 --- a/.riot/requirements/c48b250.txt +++ b/.riot/requirements/6c76bd7.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.11 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/c48b250.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/6c76bd7.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.1 dill==0.4.0 django==4.0.10 django-configurations==2.5.1 @@ -17,8 +17,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -29,14 +30,11 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 urllib3==2.5.0 -zope-event==6.0 -zope-interface==8.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==80.9.0 +zope-event==6.1 +zope-interface==8.0.1 diff --git a/.riot/requirements/bebf559.txt b/.riot/requirements/bebf559.txt deleted file mode 100644 index c88dcfdf8da..00000000000 --- a/.riot/requirements/bebf559.txt +++ /dev/null @@ -1,42 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.12 -# by the following command: -# -# pip-compile --allow-unsafe --no-annotate .riot/requirements/bebf559.in -# -asgiref==3.9.1 -attrs==25.3.0 -bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 -dill==0.4.0 -django==4.0.10 -django-configurations==2.5.1 -gevent==25.9.1 -greenlet==3.2.4 -gunicorn==23.0.0 -hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.2 -pylibmc==1.6.3 -pytest==8.4.2 -pytest-cov==7.0.0 -pytest-django[testing]==3.10.0 -pytest-mock==3.15.1 -pyyaml==6.0.2 -requests==2.32.5 -six==1.17.0 -sortedcontainers==2.4.0 -sqlparse==0.5.3 -urllib3==2.5.0 -zope-event==6.0 -zope-interface==8.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==80.9.0 diff --git a/.riot/requirements/199a155.txt b/.riot/requirements/e712306.txt similarity index 67% rename from .riot/requirements/199a155.txt rename to .riot/requirements/e712306.txt index f190975ed10..d549e4be124 100644 --- a/.riot/requirements/199a155.txt +++ b/.riot/requirements/e712306.txt @@ -2,14 +2,14 @@ # This file is autogenerated by pip-compile with Python 3.10 # by the following command: # -# pip-compile --allow-unsafe --no-annotate .riot/requirements/199a155.in +# pip-compile --allow-unsafe --no-annotate .riot/requirements/e712306.in # -asgiref==3.9.1 -attrs==25.3.0 +asgiref==3.10.0 +attrs==25.4.0 bcrypt==4.2.1 -certifi==2025.8.3 -charset-normalizer==3.4.3 -coverage[toml]==7.10.7 +certifi==2025.10.5 +charset-normalizer==3.4.4 +coverage[toml]==7.11.1 dill==0.4.0 django==4.0.10 django-configurations==2.5.1 @@ -18,8 +18,9 @@ gevent==25.9.1 greenlet==3.2.4 gunicorn==23.0.0 hypothesis==6.45.0 -idna==3.10 -iniconfig==2.1.0 +idna==3.11 +iniconfig==2.3.0 +legacy-cgi==2.6.4 mock==5.2.0 opentracing==2.4.0 packaging==25.0 @@ -30,16 +31,13 @@ pytest==8.4.2 pytest-cov==7.0.0 pytest-django[testing]==3.10.0 pytest-mock==3.15.1 -pyyaml==6.0.2 +pyyaml==6.0.3 requests==2.32.5 six==1.17.0 sortedcontainers==2.4.0 sqlparse==0.5.3 -tomli==2.2.1 +tomli==2.3.0 typing-extensions==4.15.0 urllib3==2.5.0 -zope-event==6.0 -zope-interface==8.0 - -# The following packages are considered to be unsafe in a requirements file: -setuptools==80.9.0 +zope-event==6.1 +zope-interface==8.0.1 diff --git a/riotfile.py b/riotfile.py index dec5ebc4a82..c673535cbf9 100644 --- a/riotfile.py +++ b/riotfile.py @@ -271,7 +271,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT ), Venv( pys=["3.9", "3.10", "3.11", "3.12", "3.13"], - pkgs={"django": "==4.0.10"}, + pkgs={"django": "==4.0.10", "legacy-cgi": latest}, ), Venv( pys=["3.13"], diff --git a/tests/appsec/iast/test_fork_handler_regression.py b/tests/appsec/iast/test_fork_handler_regression.py index 8ea9632e24e..a7145e35878 100644 --- a/tests/appsec/iast/test_fork_handler_regression.py +++ b/tests/appsec/iast/test_fork_handler_regression.py @@ -30,7 +30,7 @@ def test_fork_handler_callable(iast_context_defaults): """Verify that _reset_iast_after_fork is callable and disables IAST.""" from ddtrace.appsec._iast import _disable_iast_after_fork - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # Should not raise any exception try: @@ -48,7 +48,7 @@ def test_fork_handler_with_active_context(iast_context_defaults): """Verify fork handler disables IAST and clears context when active.""" from ddtrace.appsec._iast import _disable_iast_after_fork from ddtrace.appsec._iast._taint_tracking import is_tainted - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config _start_iast_context_and_oce() @@ -83,7 +83,7 @@ def child_process_work(queue): """Child process where IAST should be disabled.""" try: from ddtrace.appsec._iast._taint_tracking import is_tainted - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # Start IAST in child (will be a no-op since IAST is disabled) _start_iast_context_and_oce() @@ -139,7 +139,7 @@ def test_multiple_fork_operations(iast_context_defaults): def simple_child_work(queue, child_id): """Simple child process work - IAST will be disabled.""" try: - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # These should be safe no-ops since IAST is disabled _start_iast_context_and_oce() @@ -196,7 +196,7 @@ def test_fork_with_os_fork_no_segfault(iast_context_defaults): if pid == 0: # Child process - IAST should be disabled try: - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # IAST should be disabled after fork if asm_config._iast_enabled: @@ -237,7 +237,7 @@ def test_fork_handler_clears_state(iast_context_defaults): """ from ddtrace.appsec._iast import _disable_iast_after_fork from ddtrace.appsec._iast._taint_tracking import is_tainted - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config _start_iast_context_and_oce() tainted = taint_pyobject("test", "source", "value", OriginType.PARAMETER) @@ -278,7 +278,7 @@ def child_eval_work(queue): """Child process with IAST disabled.""" try: from ddtrace.appsec._iast._taint_tracking import is_tainted - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # IAST should be disabled, so this is a no-op _start_iast_context_and_oce() @@ -325,7 +325,7 @@ def test_early_fork_keeps_iast_enabled(): """ from ddtrace.appsec._iast import _disable_iast_after_fork from ddtrace.appsec._iast._taint_tracking import is_tainted - from ddtrace.settings.asm import config as asm_config + from ddtrace.internal.settings.asm import config as asm_config # Ensure IAST is enabled but NO context is active (simulating early fork) # Don't call _start_iast_context_and_oce() - this simulates pre-fork state