3434"""
3535from __future__ import annotations
3636
37+ # opentelemetry packages are available at runtime but not in the linter env
38+ # pylint: disable=import-error
39+
3740import builtins
3841import os
3942from typing import Optional , Set
@@ -62,7 +65,8 @@ def force_flush(timeout_millis: int = 5000) -> bool:
6265# pytest plugin entry point
6366# ---------------------------------------------------------------------------
6467
65- def pytest_configure (config ) -> None :
68+
69+ def pytest_configure (config ) -> None : # pylint: disable=unused-argument
6670 """Called once per worker process before test collection starts."""
6771 if not _TRACEPARENT :
6872 return
@@ -73,6 +77,7 @@ def pytest_configure(config) -> None:
7377# Custom headers propagator
7478# ---------------------------------------------------------------------------
7579
80+
7681class _TraceIdSpanIdPropagator :
7782 """
7883 Minimal propagator that injects 'trace-id' and 'span-id' headers
@@ -84,9 +89,37 @@ class _TraceIdSpanIdPropagator:
8489 _FIELDS : Set [str ] = {"trace-id" , "span-id" }
8590
8691 def inject (self , carrier , context : Optional [object ] = None , setter = None ) -> None :
92+ """
93+ Minimal propagator for injecting custom 'trace-id' and 'span-id' headers.
94+
95+ This class implements a propagator conforming to the OpenTelemetry propagators API,
96+ but tailored for systems that expect lowercase, hyphenated 'trace-id' and 'span-id'
97+ headers instead of standard OTel formats.
98+
99+ The propagator only performs injection (outbound context propagation) and does not
100+ extract or parse incoming headers (extract() is a no-op). This is useful for test
101+ environments or systems with custom trace context expectations.
102+
103+ Attributes:
104+ _FIELDS (Set[str]): The set of field names injected by this propagator.
105+
106+ Methods:
107+ inject(carrier, context=None, setter=None):
108+ Injects 'trace-id' and 'span-id' headers into the provided carrier
109+ using the current span context.
110+
111+ extract(carrier, context=None, getter=None):
112+ No-op. Returns the context unmodified.
113+
114+ fields:
115+ Returns the set of header names injected by this propagator.
116+ """
117+ # pylint: disable=import-outside-toplevel
87118 from opentelemetry import trace as otel_trace
88119 from opentelemetry .propagators .textmap import default_setter
89120
121+ # pylint: enable=import-outside-toplevel
122+
90123 if setter is None :
91124 setter = default_setter
92125
@@ -97,27 +130,44 @@ def inject(self, carrier, context: Optional[object] = None, setter=None) -> None
97130 setter .set (carrier , "trace-id" , format (span_ctx .trace_id , "032x" ))
98131 setter .set (carrier , "span-id" , format (span_ctx .span_id , "016x" ))
99132
100- def extract (self , carrier , context = None , getter = None ):
133+ def extract (self , _carrier , context = None , _getter = None ):
134+ """
135+ Returns the docstring for the _TraceIdSpanIdPropagator class.
136+
137+ :return: The class docstring.
138+ :rtype: str
139+ """
101140 return context
102141
103142 @property
104143 def fields (self ) -> Set [str ]:
144+ """
145+ Returns the docstring for the _TraceIdSpanIdPropagator class.
146+
147+ :return: The class docstring as a string.
148+ :rtype: str
149+ """
105150 return self ._FIELDS
106151
107152
108153# ---------------------------------------------------------------------------
109154# Internal helpers
110155# ---------------------------------------------------------------------------
111156
157+
112158def _attach_root_context () -> None :
113159 """
114160 Reconstruct the root SpanContext from the bash-generated TRACEPARENT env
115161 var and attach it to the running context so every span created during the
116162 test session inherits the same trace ID.
117163 """
118- from opentelemetry import trace as otel_trace , context as otel_context
164+ # pylint: disable=import-outside-toplevel
165+ from opentelemetry import trace as otel_trace
166+ from opentelemetry import context as otel_context
119167 from opentelemetry .trace import NonRecordingSpan , SpanContext , TraceFlags
120168
169+ # pylint: enable=import-outside-toplevel
170+
121171 parts = _TRACEPARENT .split ("-" )
122172 if len (parts ) != 4 :
123173 return
@@ -134,51 +184,86 @@ def _attach_root_context() -> None:
134184 pass
135185
136186
137- def _bootstrap_otel () -> None :
138- global _provider
139- from opentelemetry import trace as otel_trace , propagate
187+ def _create_provider ():
188+ """Build a TracerProvider with a service-name resource and return it."""
189+ # pylint: disable=import-outside-toplevel
190+ from opentelemetry .sdk .resources import SERVICE_NAME , Resource
140191 from opentelemetry .sdk .trace import TracerProvider
141- from opentelemetry .sdk .resources import Resource , SERVICE_NAME
142- from opentelemetry .propagators .composite import CompositePropagator
143- from opentelemetry .propagators .b3 import B3MultiFormat
144- from opentelemetry .trace .propagation .tracecontext import TraceContextTextMapPropagator
192+
193+ # pylint: enable=import-outside-toplevel
145194
146195 service_name = os .environ .get ("OTEL_SERVICE_NAME" , "atp3-python-runner" )
147196 resource = Resource ({SERVICE_NAME : service_name })
148- provider = TracerProvider (resource = resource )
149- _provider = provider
197+ return TracerProvider (resource = resource )
198+
150199
200+ def _add_otlp_exporter (provider ) -> None :
201+ """Attach a BatchSpanProcessor with OTLPSpanExporter if an endpoint is configured."""
151202 otlp_endpoint = os .environ .get ("OTEL_EXPORTER_OTLP_ENDPOINT" )
152- if otlp_endpoint :
153- from opentelemetry .exporter .otlp .proto .http .trace_exporter import OTLPSpanExporter
154- from opentelemetry .sdk .trace .export import BatchSpanProcessor
155- provider .add_span_processor (BatchSpanProcessor (OTLPSpanExporter ()))
203+ if not otlp_endpoint :
204+ return
205+ # pylint: disable=import-outside-toplevel
206+ from opentelemetry .exporter .otlp .proto .http .trace_exporter import OTLPSpanExporter
207+ from opentelemetry .sdk .trace .export import BatchSpanProcessor
156208
157- otel_trace .set_tracer_provider (provider )
209+ # pylint: enable=import-outside-toplevel
210+
211+ provider .add_span_processor (BatchSpanProcessor (OTLPSpanExporter ()))
212+
213+
214+ def _setup_propagators (provider ) -> None :
215+ """Register the tracer provider and composite propagator globally."""
216+ # pylint: disable=import-outside-toplevel
217+ from opentelemetry import propagate
218+ from opentelemetry import trace as otel_trace
219+ from opentelemetry .propagators .b3 import B3MultiFormat
220+ from opentelemetry .propagators .composite import CompositePropagator
221+ from opentelemetry .trace .propagation .tracecontext import (
222+ TraceContextTextMapPropagator ,
223+ )
224+
225+ # pylint: enable=import-outside-toplevel
158226
159- composite = CompositePropagator ([
160- TraceContextTextMapPropagator (),
161- B3MultiFormat (),
162- _TraceIdSpanIdPropagator (),
163- ])
227+ otel_trace .set_tracer_provider (provider )
228+ composite = CompositePropagator (
229+ [
230+ TraceContextTextMapPropagator (),
231+ B3MultiFormat (),
232+ _TraceIdSpanIdPropagator (),
233+ ]
234+ )
164235 propagate .set_global_textmap (composite )
165236
166- _attach_root_context ()
167237
238+ def _instrument_http () -> None :
239+ """Auto-instrument outgoing HTTP calls via requests and urllib3 if available."""
168240 try :
241+ # pylint: disable=import-outside-toplevel
169242 from opentelemetry .instrumentation .requests import RequestsInstrumentor
243+
244+ # pylint: enable=import-outside-toplevel
170245 RequestsInstrumentor ().instrument ()
171246 except ImportError as e :
172247 print ("RequestsInstrumentation import error, skipping tracing." , e .name , e .path )
173- pass
174248
175249 try :
250+ # pylint: disable=import-outside-toplevel
176251 from opentelemetry .instrumentation .urllib3 import URLLib3Instrumentor
252+
253+ # pylint: enable=import-outside-toplevel
177254 URLLib3Instrumentor ().instrument ()
178255 except ImportError as e :
179256 print ("URLLib3Instrumentation import error, skipping tracing." , e .name , e .path )
180- pass
181257
258+
259+ def _bootstrap_otel () -> None :
260+ """Orchestrate OTel initialisation: provider, exporter, propagators, instrumentation."""
261+ global _provider # pylint: disable=global-statement
262+ _provider = _create_provider ()
263+ _add_otlp_exporter (_provider )
264+ _setup_propagators (_provider )
265+ _attach_root_context ()
266+ _instrument_http ()
182267 _patch_print ()
183268
184269
@@ -190,7 +275,11 @@ def _patch_print() -> None:
190275 _orig = builtins .print
191276
192277 def _traced (* args , ** kwargs ):
278+ # pylint: disable=import-outside-toplevel
193279 from opentelemetry import trace as otel_trace
280+
281+ # pylint: enable=import-outside-toplevel
282+
194283 span_ctx = otel_trace .get_current_span ().get_span_context ()
195284 if span_ctx and span_ctx .is_valid :
196285 _orig (f"[traceId={ format (span_ctx .trace_id , '032x' )} ]" , * args , ** kwargs )
0 commit comments