@@ -97,15 +97,22 @@ def GET(self):
9797
9898.. code-block:: python
9999
100+ from wsgiref.types import WSGIEnvironment, StartResponse
101+ from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware
102+
103+ def app(environ: WSGIEnvironment, start_response: StartResponse):
104+ start_response("200 OK", [("Content-Type", "text/plain"), ("Content-Length", "13")])
105+ return [b"Hello, World!"]
106+
100107 def request_hook(span: Span, environ: WSGIEnvironment):
101108 if span and span.is_recording():
102109 span.set_attribute("custom_user_attribute_from_request_hook", "some-value")
103110
104- def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: List ):
111+ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_headers: list[tuple[str, str]] ):
105112 if span and span.is_recording():
106113 span.set_attribute("custom_user_attribute_from_response_hook", "some-value")
107114
108- OpenTelemetryMiddleware(request_hook=request_hook, response_hook=response_hook)
115+ OpenTelemetryMiddleware(app, request_hook=request_hook, response_hook=response_hook)
109116
110117Capture HTTP request and response headers
111118*****************************************
@@ -207,10 +214,13 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
207214---
208215"""
209216
217+ from __future__ import annotations
218+
210219import functools
211- import typing
212220import wsgiref .util as wsgiref_util
213221from timeit import default_timer
222+ from typing import TYPE_CHECKING , Any , Callable , Iterable , TypeVar , cast
223+ from wsgiref .types import StartResponse
214224
215225from opentelemetry import context , trace
216226from opentelemetry .instrumentation ._semconv import (
@@ -240,14 +250,15 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
240250)
241251from opentelemetry .instrumentation .utils import _start_internal_or_server_span
242252from opentelemetry .instrumentation .wsgi .version import __version__
243- from opentelemetry .metrics import get_meter
253+ from opentelemetry .metrics import MeterProvider , get_meter
244254from opentelemetry .propagators .textmap import Getter
245255from opentelemetry .semconv .attributes .error_attributes import ERROR_TYPE
246256from opentelemetry .semconv .metrics import MetricInstruments
247257from opentelemetry .semconv .metrics .http_metrics import (
248258 HTTP_SERVER_REQUEST_DURATION ,
249259)
250260from opentelemetry .semconv .trace import SpanAttributes
261+ from opentelemetry .trace import TracerProvider
251262from opentelemetry .trace .status import Status , StatusCode
252263from opentelemetry .util .http import (
253264 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS ,
@@ -262,15 +273,23 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
262273 sanitize_method ,
263274)
264275
276+ if TYPE_CHECKING :
277+ from wsgiref .types import WSGIApplication , WSGIEnvironment
278+
279+
280+ T = TypeVar ("T" )
281+ RequestHook = Callable [[trace .Span , WSGIEnvironment ], None ]
282+ ResponseHook = Callable [
283+ [trace .Span , WSGIEnvironment , str , list [tuple [str , str ]]], None
284+ ]
285+
265286_HTTP_VERSION_PREFIX = "HTTP/"
266287_CARRIER_KEY_PREFIX = "HTTP_"
267288_CARRIER_KEY_PREFIX_LEN = len (_CARRIER_KEY_PREFIX )
268289
269290
270- class WSGIGetter (Getter [dict ]):
271- def get (
272- self , carrier : dict , key : str
273- ) -> typing .Optional [typing .List [str ]]:
291+ class WSGIGetter (Getter [dict [str , Any ]]):
292+ def get (self , carrier : dict [str , Any ], key : str ) -> list [str ] | None :
274293 """Getter implementation to retrieve a HTTP header value from the
275294 PEP3333-conforming WSGI environ
276295
@@ -287,7 +306,7 @@ def get(
287306 return [value ]
288307 return None
289308
290- def keys (self , carrier ):
309+ def keys (self , carrier : dict [ str , Any ] ):
291310 return [
292311 key [_CARRIER_KEY_PREFIX_LEN :].lower ().replace ("_" , "-" )
293312 for key in carrier
@@ -298,26 +317,19 @@ def keys(self, carrier):
298317wsgi_getter = WSGIGetter ()
299318
300319
301- def setifnotnone (dic , key , value ):
302- if value is not None :
303- dic [key ] = value
304-
305-
306320# pylint: disable=too-many-branches
307-
308-
309321def collect_request_attributes (
310- environ ,
311- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
322+ environ : WSGIEnvironment ,
323+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
312324):
313325 """Collects HTTP request attributes from the PEP3333-conforming
314326 WSGI environ and returns a dictionary to be used as span creation attributes.
315327 """
316- result = {}
328+ result : dict [ str , str | None ] = {}
317329 _set_http_method (
318330 result ,
319331 environ .get ("REQUEST_METHOD" , "" ),
320- sanitize_method (environ .get ("REQUEST_METHOD" , "" )),
332+ sanitize_method (cast ( str , environ .get ("REQUEST_METHOD" , "" ) )),
321333 sem_conv_opt_in_mode ,
322334 )
323335 # old semconv v1.12.0
@@ -385,7 +397,7 @@ def collect_request_attributes(
385397 return result
386398
387399
388- def collect_custom_request_headers_attributes (environ ):
400+ def collect_custom_request_headers_attributes (environ : WSGIEnvironment ):
389401 """Returns custom HTTP request headers which are configured by the user
390402 from the PEP3333-conforming WSGI environ to be used as span creation attributes as described
391403 in the specification https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers
@@ -411,7 +423,9 @@ def collect_custom_request_headers_attributes(environ):
411423 )
412424
413425
414- def collect_custom_response_headers_attributes (response_headers ):
426+ def collect_custom_response_headers_attributes (
427+ response_headers : list [tuple [str , str ]],
428+ ):
415429 """Returns custom HTTP response headers which are configured by the user from the
416430 PEP3333-conforming WSGI environ as described in the specification
417431 https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers
@@ -422,7 +436,7 @@ def collect_custom_response_headers_attributes(response_headers):
422436 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
423437 )
424438 )
425- response_headers_dict = {}
439+ response_headers_dict : dict [ str , str ] = {}
426440 if response_headers :
427441 for key , val in response_headers :
428442 key = key .lower ()
@@ -440,7 +454,8 @@ def collect_custom_response_headers_attributes(response_headers):
440454 )
441455
442456
443- def _parse_status_code (resp_status ):
457+ # TODO: Used only on the `opentelemetry-instrumentation-pyramid` package - It can be moved there.
458+ def _parse_status_code (resp_status : str ) -> int | None :
444459 status_code , _ = resp_status .split (" " , 1 )
445460 try :
446461 return int (status_code )
@@ -449,7 +464,7 @@ def _parse_status_code(resp_status):
449464
450465
451466def _parse_active_request_count_attrs (
452- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
467+ req_attrs , sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT
453468):
454469 return _filter_semconv_active_request_count_attr (
455470 req_attrs ,
@@ -460,7 +475,8 @@ def _parse_active_request_count_attrs(
460475
461476
462477def _parse_duration_attrs (
463- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
478+ req_attrs : dict [str , str | None ],
479+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
464480):
465481 return _filter_semconv_duration_attrs (
466482 req_attrs ,
@@ -471,11 +487,11 @@ def _parse_duration_attrs(
471487
472488
473489def add_response_attributes (
474- span ,
475- start_response_status ,
476- response_headers ,
477- duration_attrs = None ,
478- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
490+ span : trace . Span ,
491+ start_response_status : str ,
492+ response_headers : list [ tuple [ str , str ]] ,
493+ duration_attrs : dict [ str , str | None ] | None = None ,
494+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
479495): # pylint: disable=unused-argument
480496 """Adds HTTP response attributes to span using the arguments
481497 passed to a PEP3333-conforming start_response callable.
@@ -501,7 +517,7 @@ def add_response_attributes(
501517 )
502518
503519
504- def get_default_span_name (environ ) :
520+ def get_default_span_name (environ : WSGIEnvironment ) -> str :
505521 """
506522 Default span name is the HTTP method and URL path, or just the method.
507523 https://github.com/open-telemetry/opentelemetry-specification/pull/3165
@@ -512,10 +528,12 @@ def get_default_span_name(environ):
512528 Returns:
513529 The span name.
514530 """
515- method = sanitize_method (environ .get ("REQUEST_METHOD" , "" ).strip ())
531+ method = sanitize_method (
532+ cast (str , environ .get ("REQUEST_METHOD" , "" )).strip ()
533+ )
516534 if method == "_OTHER" :
517535 return "HTTP"
518- path = environ .get ("PATH_INFO" , "" ).strip ()
536+ path = cast ( str , environ .get ("PATH_INFO" , "" ) ).strip ()
519537 if method and path :
520538 return f"{ method } { path } "
521539 return method
@@ -542,11 +560,11 @@ class OpenTelemetryMiddleware:
542560
543561 def __init__ (
544562 self ,
545- wsgi ,
546- request_hook = None ,
547- response_hook = None ,
548- tracer_provider = None ,
549- meter_provider = None ,
563+ wsgi : WSGIApplication ,
564+ request_hook : RequestHook | None = None ,
565+ response_hook : ResponseHook | None = None ,
566+ tracer_provider : TracerProvider | None = None ,
567+ meter_provider : MeterProvider | None = None ,
550568 ):
551569 # initialize semantic conventions opt-in if needed
552570 _OpenTelemetrySemanticConventionStability ._initialize ()
@@ -593,14 +611,19 @@ def __init__(
593611
594612 @staticmethod
595613 def _create_start_response (
596- span ,
597- start_response ,
598- response_hook ,
599- duration_attrs ,
600- sem_conv_opt_in_mode ,
614+ span : trace . Span ,
615+ start_response : StartResponse ,
616+ response_hook : Callable [[ str , list [ tuple [ str , str ]]], None ] | None ,
617+ duration_attrs : dict [ str , str | None ] ,
618+ sem_conv_opt_in_mode : _StabilityMode ,
601619 ):
602620 @functools .wraps (start_response )
603- def _start_response (status , response_headers , * args , ** kwargs ):
621+ def _start_response (
622+ status : str ,
623+ response_headers : list [tuple [str , str ]],
624+ * args : Any ,
625+ ** kwargs : Any ,
626+ ):
604627 add_response_attributes (
605628 span ,
606629 status ,
@@ -621,7 +644,9 @@ def _start_response(status, response_headers, *args, **kwargs):
621644 return _start_response
622645
623646 # pylint: disable=too-many-branches
624- def __call__ (self , environ , start_response ):
647+ def __call__ (
648+ self , environ : WSGIEnvironment , start_response : StartResponse
649+ ):
625650 """The WSGI application
626651
627652 Args:
@@ -703,7 +728,9 @@ def __call__(self, environ, start_response):
703728# Put this in a subfunction to not delay the call to the wrapped
704729# WSGI application (instrumentation should change the application
705730# behavior as little as possible).
706- def _end_span_after_iterating (iterable , span , token ):
731+ def _end_span_after_iterating (
732+ iterable : Iterable [T ], span : trace .Span , token : object
733+ ) -> Iterable [T ]:
707734 try :
708735 with trace .use_span (span ):
709736 yield from iterable
@@ -717,10 +744,8 @@ def _end_span_after_iterating(iterable, span, token):
717744
718745
719746# TODO: inherit from opentelemetry.instrumentation.propagators.Setter
720-
721-
722747class ResponsePropagationSetter :
723- def set (self , carrier , key , value ): # pylint: disable=no-self-use
748+ def set (self , carrier : list [ tuple [ str , T ]], key : str , value : T ): # pylint: disable=no-self-use
724749 carrier .append ((key , value ))
725750
726751
0 commit comments