From 8d310eb95d9e6fcfe6e0ccd83aaf3f7ca5cc5444 Mon Sep 17 00:00:00 2001 From: Colin B Date: Wed, 4 Mar 2026 11:19:22 -0800 Subject: [PATCH 1/3] Exclude test packages from setuptools find configuration in pyproject.toml --- integrations/langchain-py/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/integrations/langchain-py/pyproject.toml b/integrations/langchain-py/pyproject.toml index 579802b7..002a02f9 100644 --- a/integrations/langchain-py/pyproject.toml +++ b/integrations/langchain-py/pyproject.toml @@ -33,6 +33,7 @@ package-dir = { "" = "src" } [tool.setuptools.packages.find] where = ["src"] +exclude = ["tests*"] [tool.uv.workspace] From d66be165029ec54e97b6245d154535ff2dbf49fc Mon Sep 17 00:00:00 2001 From: Colin B Date: Wed, 4 Mar 2026 11:32:08 -0800 Subject: [PATCH 2/3] Update pyproject.toml to disable package data inclusion in setuptools configuration --- integrations/langchain-py/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/integrations/langchain-py/pyproject.toml b/integrations/langchain-py/pyproject.toml index 002a02f9..6aa1e850 100644 --- a/integrations/langchain-py/pyproject.toml +++ b/integrations/langchain-py/pyproject.toml @@ -30,6 +30,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools] package-dir = { "" = "src" } +include-package-data = false [tool.setuptools.packages.find] where = ["src"] From 2425499eb53461dbbe5275539d7dca477c2ebf1a Mon Sep 17 00:00:00 2001 From: Colin B Date: Wed, 4 Mar 2026 11:49:39 -0800 Subject: [PATCH 3/3] feat: add _on_ending hook support to span processors - Implemented _on_ending method in AISpanProcessor and BraintrustSpanProcessor to forward pre-end hooks. - Added a helper function _forward_on_ending to handle compatibility with different OpenTelemetry SDK versions. - Updated tests to verify the presence and functionality of the new _on_ending method. --- py/src/braintrust/otel/__init__.py | 23 +++++++++++++++++++++++ py/src/braintrust/test_otel.py | 5 +++++ 2 files changed, 28 insertions(+) diff --git a/py/src/braintrust/otel/__init__.py b/py/src/braintrust/otel/__init__.py index 71648f01..fec3a4cd 100644 --- a/py/src/braintrust/otel/__init__.py +++ b/py/src/braintrust/otel/__init__.py @@ -43,6 +43,18 @@ def get_tracer_provider(): FILTER_PREFIXES = ("gen_ai.", "braintrust.", "llm.", "ai.", "traceloop.") +def _forward_on_ending(processor, span) -> None: + """ + Forward OpenTelemetry's optional _on_ending hook when available. + + Newer OpenTelemetry SDK versions call _on_ending before on_end. Older SDK + versions may not implement the hook on wrapped processors. + """ + on_ending = getattr(processor, "_on_ending", None) + if callable(on_ending): + on_ending(span) + + class AISpanProcessor: """ A span processor that filters spans to only export filtered telemetry. @@ -79,6 +91,13 @@ def on_end(self, span): if self._should_keep_filtered_span(span): self._processor.on_end(span) + def _on_ending(self, span): + """ + Forward pre-end hook for kept spans when the wrapped processor supports it. + """ + if self._should_keep_filtered_span(span): + _forward_on_ending(self._processor, span) + def shutdown(self): """Shutdown the inner processor.""" self._processor.shutdown() @@ -322,6 +341,10 @@ def on_end(self, span): """Forward span end events to the inner processor.""" self._processor.on_end(span) + def _on_ending(self, span): + """Forward pre-end hook when the wrapped processor supports it.""" + _forward_on_ending(self._processor, span) + def shutdown(self): """Shutdown the inner processor.""" self._processor.shutdown() diff --git a/py/src/braintrust/test_otel.py b/py/src/braintrust/test_otel.py index a0aa4fb1..2706cf62 100644 --- a/py/src/braintrust/test_otel.py +++ b/py/src/braintrust/test_otel.py @@ -191,7 +191,9 @@ def test_braintrust_otel_filter_ai_spans_environment_variable(): # Verify it has the expected attributes assert hasattr(filter_processor, "_processor") assert hasattr(filter_processor, "_custom_filter") + assert hasattr(filter_processor, "_on_ending") assert hasattr(filter_processor, "_should_keep_filtered_span") + assert callable(filter_processor._on_ending) assert callable(filter_processor._should_keep_filtered_span) finally: @@ -216,10 +218,12 @@ def test_braintrust_span_processor_class(): # Should have the span processor interface assert hasattr(processor, "on_start") assert hasattr(processor, "on_end") + assert hasattr(processor, "_on_ending") assert hasattr(processor, "shutdown") assert hasattr(processor, "force_flush") assert callable(processor.on_start) assert callable(processor.on_end) + assert callable(processor._on_ending) assert callable(processor.shutdown) assert callable(processor.force_flush) @@ -235,6 +239,7 @@ def test_braintrust_span_processor_class(): # Should have the same interface assert hasattr(processor_with_filtering, "on_start") assert hasattr(processor_with_filtering, "on_end") + assert hasattr(processor_with_filtering, "_on_ending") assert hasattr(processor_with_filtering, "shutdown") assert hasattr(processor_with_filtering, "force_flush")