Skip to content

Commit 0da62aa

Browse files
Kludexxrmxemdneto
authored
Add type hints to Starlette instrumentation (#3045)
* Add type hints to Starlette instrumentation * format * Add changelog * Add changelog * Remove pyright ignore --------- Co-authored-by: Riccardo Magliocchetti <[email protected]> Co-authored-by: Emídio Neto <[email protected]>
1 parent 142b86c commit 0da62aa

File tree

3 files changed

+44
-26
lines changed

3 files changed

+44
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313

1414
### Added
1515

16+
- `opentelemetry-instrumentation-starlette` Add type hints to the instrumentation
17+
([#3045](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3045))
1618
- `opentelemetry-distro` default to OTLP log exporter.
1719
([#3042](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/3042))
1820
- `opentelemetry-instrumentation-sqlalchemy` Update unit tests to run with SQLALchemy 2

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

Whitespace-only changes.

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

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,9 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
170170
---
171171
"""
172172

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

175177
from starlette import applications
176178
from starlette.routing import Match
@@ -184,18 +186,29 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
184186
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
185187
from opentelemetry.instrumentation.starlette.package import _instruments
186188
from opentelemetry.instrumentation.starlette.version import __version__
187-
from opentelemetry.metrics import get_meter
189+
from opentelemetry.metrics import MeterProvider, get_meter
188190
from opentelemetry.semconv.trace import SpanAttributes
189-
from opentelemetry.trace import get_tracer
191+
from opentelemetry.trace import TracerProvider, get_tracer
190192
from opentelemetry.util.http import get_excluded_urls
191193

194+
if TYPE_CHECKING:
195+
from typing import TypedDict, Unpack
196+
197+
class InstrumentKwargs(TypedDict, total=False):
198+
tracer_provider: TracerProvider
199+
meter_provider: MeterProvider
200+
server_request_hook: ServerRequestHook
201+
client_request_hook: ClientRequestHook
202+
client_response_hook: ClientResponseHook
203+
204+
192205
_excluded_urls = get_excluded_urls("STARLETTE")
193206

194207

195208
class StarletteInstrumentor(BaseInstrumentor):
196-
"""An instrumentor for starlette
209+
"""An instrumentor for Starlette.
197210
198-
See `BaseInstrumentor`
211+
See `BaseInstrumentor`.
199212
"""
200213

201214
_original_starlette = None
@@ -206,8 +219,8 @@ def instrument_app(
206219
server_request_hook: ServerRequestHook = None,
207220
client_request_hook: ClientRequestHook = None,
208221
client_response_hook: ClientResponseHook = None,
209-
meter_provider=None,
210-
tracer_provider=None,
222+
meter_provider: MeterProvider | None = None,
223+
tracer_provider: TracerProvider | None = None,
211224
):
212225
"""Instrument an uninstrumented Starlette application."""
213226
tracer = get_tracer(
@@ -253,7 +266,7 @@ def uninstrument_app(app: applications.Starlette):
253266
def instrumentation_dependencies(self) -> Collection[str]:
254267
return _instruments
255268

256-
def _instrument(self, **kwargs):
269+
def _instrument(self, **kwargs: Unpack[InstrumentKwargs]):
257270
self._original_starlette = applications.Starlette
258271
_InstrumentedStarlette._tracer_provider = kwargs.get("tracer_provider")
259272
_InstrumentedStarlette._server_request_hook = kwargs.get(
@@ -269,7 +282,7 @@ def _instrument(self, **kwargs):
269282

270283
applications.Starlette = _InstrumentedStarlette
271284

272-
def _uninstrument(self, **kwargs):
285+
def _uninstrument(self, **kwargs: Any):
273286
"""uninstrumenting all created apps by user"""
274287
for instance in _InstrumentedStarlette._instrumented_starlette_apps:
275288
self.uninstrument_app(instance)
@@ -278,14 +291,14 @@ def _uninstrument(self, **kwargs):
278291

279292

280293
class _InstrumentedStarlette(applications.Starlette):
281-
_tracer_provider = None
282-
_meter_provider = None
294+
_tracer_provider: TracerProvider | None = None
295+
_meter_provider: MeterProvider | None = None
283296
_server_request_hook: ServerRequestHook = None
284297
_client_request_hook: ClientRequestHook = None
285298
_client_response_hook: ClientResponseHook = None
286-
_instrumented_starlette_apps = set()
299+
_instrumented_starlette_apps: set[applications.Starlette] = set()
287300

288-
def __init__(self, *args, **kwargs):
301+
def __init__(self, *args: Any, **kwargs: Any):
289302
super().__init__(*args, **kwargs)
290303
tracer = get_tracer(
291304
__name__,
@@ -318,21 +331,22 @@ def __del__(self):
318331
_InstrumentedStarlette._instrumented_starlette_apps.remove(self)
319332

320333

321-
def _get_route_details(scope):
334+
def _get_route_details(scope: dict[str, Any]) -> str | None:
322335
"""
323-
Function to retrieve Starlette route from scope.
336+
Function to retrieve Starlette route from ASGI scope.
324337
325338
TODO: there is currently no way to retrieve http.route from
326339
a starlette application from scope.
327340
See: https://github.com/encode/starlette/pull/804
328341
329342
Args:
330-
scope: A Starlette scope
343+
scope: The ASGI scope that contains the Starlette application in the "app" key.
344+
331345
Returns:
332-
A string containing the route or None
346+
The path to the route if found, otherwise None.
333347
"""
334-
app = scope["app"]
335-
route = None
348+
app = cast(applications.Starlette, scope["app"])
349+
route: str | None = None
336350

337351
for starlette_route in app.routes:
338352
match, _ = starlette_route.matches(scope)
@@ -344,18 +358,20 @@ def _get_route_details(scope):
344358
return route
345359

346360

347-
def _get_default_span_details(scope):
348-
"""
349-
Callback to retrieve span name and attributes from scope.
361+
def _get_default_span_details(
362+
scope: dict[str, Any],
363+
) -> tuple[str, dict[str, Any]]:
364+
"""Callback to retrieve span name and attributes from ASGI scope.
350365
351366
Args:
352-
scope: A Starlette scope
367+
scope: The ASGI scope that contains the Starlette application in the "app" key.
368+
353369
Returns:
354-
A tuple of span name and attributes
370+
A tuple of span name and attributes.
355371
"""
356372
route = _get_route_details(scope)
357-
method = scope.get("method", "")
358-
attributes = {}
373+
method: str = scope.get("method", "")
374+
attributes: dict[str, Any] = {}
359375
if route:
360376
attributes[SpanAttributes.HTTP_ROUTE] = route
361377
if method and route: # http

0 commit comments

Comments
 (0)