@@ -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,12 @@ 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 , Dict , Iterable , TypeVar , cast
214223
215224from opentelemetry import context , trace
216225from opentelemetry .instrumentation ._semconv import (
@@ -240,14 +249,15 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
240249)
241250from opentelemetry .instrumentation .utils import _start_internal_or_server_span
242251from opentelemetry .instrumentation .wsgi .version import __version__
243- from opentelemetry .metrics import get_meter
252+ from opentelemetry .metrics import MeterProvider , get_meter
244253from opentelemetry .propagators .textmap import Getter
245254from opentelemetry .semconv .attributes .error_attributes import ERROR_TYPE
246255from opentelemetry .semconv .metrics import MetricInstruments
247256from opentelemetry .semconv .metrics .http_metrics import (
248257 HTTP_SERVER_REQUEST_DURATION ,
249258)
250259from opentelemetry .semconv .trace import SpanAttributes
260+ from opentelemetry .trace import TracerProvider
251261from opentelemetry .trace .status import Status , StatusCode
252262from opentelemetry .util .http import (
253263 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS ,
@@ -262,15 +272,23 @@ def response_hook(span: Span, environ: WSGIEnvironment, status: str, response_he
262272 sanitize_method ,
263273)
264274
275+ if TYPE_CHECKING :
276+ from wsgiref .types import StartResponse , WSGIApplication , WSGIEnvironment
277+
278+
279+ T = TypeVar ("T" )
280+ RequestHook = Callable [[trace .Span , "WSGIEnvironment" ], None ]
281+ ResponseHook = Callable [
282+ [trace .Span , "WSGIEnvironment" , str , "list[tuple[str, str]]" ], None
283+ ]
284+
265285_HTTP_VERSION_PREFIX = "HTTP/"
266286_CARRIER_KEY_PREFIX = "HTTP_"
267287_CARRIER_KEY_PREFIX_LEN = len (_CARRIER_KEY_PREFIX )
268288
269289
270- class WSGIGetter (Getter [dict ]):
271- def get (
272- self , carrier : dict , key : str
273- ) -> typing .Optional [typing .List [str ]]:
290+ class WSGIGetter (Getter [Dict [str , Any ]]):
291+ def get (self , carrier : dict [str , Any ], key : str ) -> list [str ] | None :
274292 """Getter implementation to retrieve a HTTP header value from the
275293 PEP3333-conforming WSGI environ
276294
@@ -287,7 +305,7 @@ def get(
287305 return [value ]
288306 return None
289307
290- def keys (self , carrier ):
308+ def keys (self , carrier : dict [ str , Any ] ):
291309 return [
292310 key [_CARRIER_KEY_PREFIX_LEN :].lower ().replace ("_" , "-" )
293311 for key in carrier
@@ -298,26 +316,19 @@ def keys(self, carrier):
298316wsgi_getter = WSGIGetter ()
299317
300318
301- def setifnotnone (dic , key , value ):
302- if value is not None :
303- dic [key ] = value
304-
305-
306319# pylint: disable=too-many-branches
307-
308-
309320def collect_request_attributes (
310- environ ,
311- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
321+ environ : WSGIEnvironment ,
322+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
312323):
313324 """Collects HTTP request attributes from the PEP3333-conforming
314325 WSGI environ and returns a dictionary to be used as span creation attributes.
315326 """
316- result = {}
327+ result : dict [ str , str | None ] = {}
317328 _set_http_method (
318329 result ,
319330 environ .get ("REQUEST_METHOD" , "" ),
320- sanitize_method (environ .get ("REQUEST_METHOD" , "" )),
331+ sanitize_method (cast ( str , environ .get ("REQUEST_METHOD" , "" ) )),
321332 sem_conv_opt_in_mode ,
322333 )
323334 # old semconv v1.12.0
@@ -385,7 +396,7 @@ def collect_request_attributes(
385396 return result
386397
387398
388- def collect_custom_request_headers_attributes (environ ):
399+ def collect_custom_request_headers_attributes (environ : WSGIEnvironment ):
389400 """Returns custom HTTP request headers which are configured by the user
390401 from the PEP3333-conforming WSGI environ to be used as span creation attributes as described
391402 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 +422,9 @@ def collect_custom_request_headers_attributes(environ):
411422 )
412423
413424
414- def collect_custom_response_headers_attributes (response_headers ):
425+ def collect_custom_response_headers_attributes (
426+ response_headers : list [tuple [str , str ]],
427+ ):
415428 """Returns custom HTTP response headers which are configured by the user from the
416429 PEP3333-conforming WSGI environ as described in the specification
417430 https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#http-request-and-response-headers
@@ -422,7 +435,7 @@ def collect_custom_response_headers_attributes(response_headers):
422435 OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS
423436 )
424437 )
425- response_headers_dict = {}
438+ response_headers_dict : dict [ str , str ] = {}
426439 if response_headers :
427440 for key , val in response_headers :
428441 key = key .lower ()
@@ -440,7 +453,8 @@ def collect_custom_response_headers_attributes(response_headers):
440453 )
441454
442455
443- def _parse_status_code (resp_status ):
456+ # TODO: Used only on the `opentelemetry-instrumentation-pyramid` package - It can be moved there.
457+ def _parse_status_code (resp_status : str ) -> int | None :
444458 status_code , _ = resp_status .split (" " , 1 )
445459 try :
446460 return int (status_code )
@@ -449,7 +463,7 @@ def _parse_status_code(resp_status):
449463
450464
451465def _parse_active_request_count_attrs (
452- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
466+ req_attrs , sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT
453467):
454468 return _filter_semconv_active_request_count_attr (
455469 req_attrs ,
@@ -460,7 +474,8 @@ def _parse_active_request_count_attrs(
460474
461475
462476def _parse_duration_attrs (
463- req_attrs , sem_conv_opt_in_mode = _StabilityMode .DEFAULT
477+ req_attrs : dict [str , str | None ],
478+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
464479):
465480 return _filter_semconv_duration_attrs (
466481 req_attrs ,
@@ -471,11 +486,11 @@ def _parse_duration_attrs(
471486
472487
473488def add_response_attributes (
474- span ,
475- start_response_status ,
476- response_headers ,
477- duration_attrs = None ,
478- sem_conv_opt_in_mode = _StabilityMode .DEFAULT ,
489+ span : trace . Span ,
490+ start_response_status : str ,
491+ response_headers : list [ tuple [ str , str ]] ,
492+ duration_attrs : dict [ str , str | None ] | None = None ,
493+ sem_conv_opt_in_mode : _StabilityMode = _StabilityMode .DEFAULT ,
479494): # pylint: disable=unused-argument
480495 """Adds HTTP response attributes to span using the arguments
481496 passed to a PEP3333-conforming start_response callable.
@@ -497,7 +512,7 @@ def add_response_attributes(
497512 )
498513
499514
500- def get_default_span_name (environ ) :
515+ def get_default_span_name (environ : WSGIEnvironment ) -> str :
501516 """
502517 Default span name is the HTTP method and URL path, or just the method.
503518 https://github.com/open-telemetry/opentelemetry-specification/pull/3165
@@ -508,10 +523,12 @@ def get_default_span_name(environ):
508523 Returns:
509524 The span name.
510525 """
511- method = sanitize_method (environ .get ("REQUEST_METHOD" , "" ).strip ())
526+ method = sanitize_method (
527+ cast (str , environ .get ("REQUEST_METHOD" , "" )).strip ()
528+ )
512529 if method == "_OTHER" :
513530 return "HTTP"
514- path = environ .get ("PATH_INFO" , "" ).strip ()
531+ path = cast ( str , environ .get ("PATH_INFO" , "" ) ).strip ()
515532 if method and path :
516533 return f"{ method } { path } "
517534 return method
@@ -538,11 +555,11 @@ class OpenTelemetryMiddleware:
538555
539556 def __init__ (
540557 self ,
541- wsgi ,
542- request_hook = None ,
543- response_hook = None ,
544- tracer_provider = None ,
545- meter_provider = None ,
558+ wsgi : WSGIApplication ,
559+ request_hook : RequestHook | None = None ,
560+ response_hook : ResponseHook | None = None ,
561+ tracer_provider : TracerProvider | None = None ,
562+ meter_provider : MeterProvider | None = None ,
546563 ):
547564 # initialize semantic conventions opt-in if needed
548565 _OpenTelemetrySemanticConventionStability ._initialize ()
@@ -589,14 +606,19 @@ def __init__(
589606
590607 @staticmethod
591608 def _create_start_response (
592- span ,
593- start_response ,
594- response_hook ,
595- duration_attrs ,
596- sem_conv_opt_in_mode ,
609+ span : trace . Span ,
610+ start_response : StartResponse ,
611+ response_hook : Callable [[ str , list [ tuple [ str , str ]]], None ] | None ,
612+ duration_attrs : dict [ str , str | None ] ,
613+ sem_conv_opt_in_mode : _StabilityMode ,
597614 ):
598615 @functools .wraps (start_response )
599- def _start_response (status , response_headers , * args , ** kwargs ):
616+ def _start_response (
617+ status : str ,
618+ response_headers : list [tuple [str , str ]],
619+ * args : Any ,
620+ ** kwargs : Any ,
621+ ):
600622 add_response_attributes (
601623 span ,
602624 status ,
@@ -617,7 +639,9 @@ def _start_response(status, response_headers, *args, **kwargs):
617639 return _start_response
618640
619641 # pylint: disable=too-many-branches
620- def __call__ (self , environ , start_response ):
642+ def __call__ (
643+ self , environ : WSGIEnvironment , start_response : StartResponse
644+ ):
621645 """The WSGI application
622646
623647 Args:
@@ -699,7 +723,9 @@ def __call__(self, environ, start_response):
699723# Put this in a subfunction to not delay the call to the wrapped
700724# WSGI application (instrumentation should change the application
701725# behavior as little as possible).
702- def _end_span_after_iterating (iterable , span , token ):
726+ def _end_span_after_iterating (
727+ iterable : Iterable [T ], span : trace .Span , token : object
728+ ) -> Iterable [T ]:
703729 try :
704730 with trace .use_span (span ):
705731 yield from iterable
@@ -713,10 +739,8 @@ def _end_span_after_iterating(iterable, span, token):
713739
714740
715741# TODO: inherit from opentelemetry.instrumentation.propagators.Setter
716-
717-
718742class ResponsePropagationSetter :
719- def set (self , carrier , key , value ): # pylint: disable=no-self-use
743+ def set (self , carrier : list [ tuple [ str , T ]], key : str , value : T ): # pylint: disable=no-self-use
720744 carrier .append ((key , value ))
721745
722746
0 commit comments