Skip to content

Commit 45989bb

Browse files
committed
Add type hints to Starlette instrumentation
1 parent 5c5fc73 commit 45989bb

File tree

2 files changed

+45
-40
lines changed
  • instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation

2 files changed

+45
-40
lines changed

instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/py.typed

Whitespace-only changes.

instrumentation/opentelemetry-instrumentation-starlette/src/opentelemetry/instrumentation/starlette/__init__.py

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
169169
API
170170
---
171171
"""
172+
# pyright: reportPrivateUsage=false
172173

173-
from typing import Collection
174+
from __future__ import annotations
175+
176+
from typing import TYPE_CHECKING, Any, Collection, cast
174177

175178
from starlette import applications
176179
from starlette.routing import Match
@@ -184,18 +187,29 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
184187
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
185188
from opentelemetry.instrumentation.starlette.package import _instruments
186189
from opentelemetry.instrumentation.starlette.version import __version__
187-
from opentelemetry.metrics import get_meter
190+
from opentelemetry.metrics import MeterProvider, get_meter
188191
from opentelemetry.semconv.trace import SpanAttributes
189-
from opentelemetry.trace import get_tracer
192+
from opentelemetry.trace import TracerProvider, get_tracer
190193
from opentelemetry.util.http import get_excluded_urls
191194

195+
if TYPE_CHECKING:
196+
from typing import NotRequired, TypedDict, Unpack
197+
198+
class InstrumentKwargs(TypedDict):
199+
tracer_provider: NotRequired[TracerProvider]
200+
meter_provider: NotRequired[MeterProvider]
201+
server_request_hook: NotRequired[ServerRequestHook]
202+
client_request_hook: NotRequired[ClientRequestHook]
203+
client_response_hook: NotRequired[ClientResponseHook]
204+
205+
192206
_excluded_urls = get_excluded_urls("STARLETTE")
193207

194208

195209
class StarletteInstrumentor(BaseInstrumentor):
196-
"""An instrumentor for starlette
210+
"""An instrumentor for Starlette.
197211
198-
See `BaseInstrumentor`
212+
See `BaseInstrumentor`.
199213
"""
200214

201215
_original_starlette = None
@@ -206,8 +220,8 @@ def instrument_app(
206220
server_request_hook: ServerRequestHook = None,
207221
client_request_hook: ClientRequestHook = None,
208222
client_response_hook: ClientResponseHook = None,
209-
meter_provider=None,
210-
tracer_provider=None,
223+
meter_provider: MeterProvider | None = None,
224+
tracer_provider: TracerProvider | None = None,
211225
):
212226
"""Instrument an uninstrumented Starlette application."""
213227
tracer = get_tracer(
@@ -242,34 +256,24 @@ def instrument_app(
242256

243257
@staticmethod
244258
def uninstrument_app(app: applications.Starlette):
245-
app.user_middleware = [
246-
x
247-
for x in app.user_middleware
248-
if x.cls is not OpenTelemetryMiddleware
249-
]
259+
app.user_middleware = [x for x in app.user_middleware if x.cls is not OpenTelemetryMiddleware]
250260
app.middleware_stack = app.build_middleware_stack()
251261
app._is_instrumented_by_opentelemetry = False
252262

253263
def instrumentation_dependencies(self) -> Collection[str]:
254264
return _instruments
255265

256-
def _instrument(self, **kwargs):
266+
def _instrument(self, **kwargs: Unpack[InstrumentKwargs]):
257267
self._original_starlette = applications.Starlette
258268
_InstrumentedStarlette._tracer_provider = kwargs.get("tracer_provider")
259-
_InstrumentedStarlette._server_request_hook = kwargs.get(
260-
"server_request_hook"
261-
)
262-
_InstrumentedStarlette._client_request_hook = kwargs.get(
263-
"client_request_hook"
264-
)
265-
_InstrumentedStarlette._client_response_hook = kwargs.get(
266-
"client_response_hook"
267-
)
269+
_InstrumentedStarlette._server_request_hook = kwargs.get("server_request_hook")
270+
_InstrumentedStarlette._client_request_hook = kwargs.get("client_request_hook")
271+
_InstrumentedStarlette._client_response_hook = kwargs.get("client_response_hook")
268272
_InstrumentedStarlette._meter_provider = kwargs.get("_meter_provider")
269273

270274
applications.Starlette = _InstrumentedStarlette
271275

272-
def _uninstrument(self, **kwargs):
276+
def _uninstrument(self, **kwargs: Any):
273277
"""uninstrumenting all created apps by user"""
274278
for instance in _InstrumentedStarlette._instrumented_starlette_apps:
275279
self.uninstrument_app(instance)
@@ -278,14 +282,14 @@ def _uninstrument(self, **kwargs):
278282

279283

280284
class _InstrumentedStarlette(applications.Starlette):
281-
_tracer_provider = None
282-
_meter_provider = None
285+
_tracer_provider: TracerProvider | None = None
286+
_meter_provider: MeterProvider | None = None
283287
_server_request_hook: ServerRequestHook = None
284288
_client_request_hook: ClientRequestHook = None
285289
_client_response_hook: ClientResponseHook = None
286-
_instrumented_starlette_apps = set()
290+
_instrumented_starlette_apps: set[applications.Starlette] = set()
287291

288-
def __init__(self, *args, **kwargs):
292+
def __init__(self, *args: Any, **kwargs: Any):
289293
super().__init__(*args, **kwargs)
290294
tracer = get_tracer(
291295
__name__,
@@ -318,21 +322,22 @@ def __del__(self):
318322
_InstrumentedStarlette._instrumented_starlette_apps.remove(self)
319323

320324

321-
def _get_route_details(scope):
325+
def _get_route_details(scope: dict[str, Any]) -> str | None:
322326
"""
323-
Function to retrieve Starlette route from scope.
327+
Function to retrieve Starlette route from ASGI scope.
324328
325329
TODO: there is currently no way to retrieve http.route from
326330
a starlette application from scope.
327331
See: https://github.com/encode/starlette/pull/804
328332
329333
Args:
330-
scope: A Starlette scope
334+
scope: The ASGI scope that contains the Starlette application in the "app" key.
335+
331336
Returns:
332-
A string containing the route or None
337+
The path to the route if found, otherwise None.
333338
"""
334-
app = scope["app"]
335-
route = None
339+
app = cast(applications.Starlette, scope["app"])
340+
route: str | None = None
336341

337342
for starlette_route in app.routes:
338343
match, _ = starlette_route.matches(scope)
@@ -344,18 +349,18 @@ def _get_route_details(scope):
344349
return route
345350

346351

347-
def _get_default_span_details(scope):
348-
"""
349-
Callback to retrieve span name and attributes from scope.
352+
def _get_default_span_details(scope: dict[str, Any]) -> tuple[str, dict[str, Any]]:
353+
"""Callback to retrieve span name and attributes from ASGI scope.
350354
351355
Args:
352-
scope: A Starlette scope
356+
scope: The ASGI scope that contains the Starlette application in the "app" key.
357+
353358
Returns:
354-
A tuple of span name and attributes
359+
A tuple of span name and attributes.
355360
"""
356361
route = _get_route_details(scope)
357-
method = scope.get("method", "")
358-
attributes = {}
362+
method: str = scope.get("method", "")
363+
attributes: dict[str, Any] = {}
359364
if route:
360365
attributes[SpanAttributes.HTTP_ROUTE] = route
361366
if method and route: # http

0 commit comments

Comments
 (0)