_decorator.py 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. # Copyright The OpenTelemetry Authors
  2. # SPDX-License-Identifier: Apache-2.0
  3. import contextlib
  4. import functools
  5. import inspect
  6. from collections.abc import Callable, Iterator
  7. from typing import TYPE_CHECKING, Generic, TypeVar
  8. V = TypeVar("V")
  9. R = TypeVar("R") # Return type
  10. Pargs = TypeVar("Pargs") # Generic type for arguments
  11. Pkwargs = TypeVar("Pkwargs") # Generic type for arguments
  12. # We don't actually depend on typing_extensions but we can use it in CI with this conditional
  13. # import. ParamSpec can be imported directly from typing after python 3.9 is dropped
  14. # https://peps.python.org/pep-0612/.
  15. if TYPE_CHECKING:
  16. from typing_extensions import ParamSpec
  17. P = ParamSpec("P") # Generic type for all arguments
  18. class _AgnosticContextManager(
  19. contextlib._GeneratorContextManager[R],
  20. Generic[R],
  21. ): # pylint: disable=protected-access
  22. """Context manager that can decorate both async and sync functions.
  23. This is an overridden version of the contextlib._GeneratorContextManager
  24. class that will decorate async functions with an async context manager
  25. to end the span AFTER the entire async function coroutine finishes.
  26. Else it will report near zero spans durations for async functions.
  27. We are overriding the contextlib._GeneratorContextManager class as
  28. reimplementing it is a lot of code to maintain and this class (even if it's
  29. marked as protected) doesn't seems like to be evolving a lot.
  30. For more information, see:
  31. https://github.com/open-telemetry/opentelemetry-python/pull/3633
  32. """
  33. def __enter__(self) -> R:
  34. """Reimplementing __enter__ to avoid the type error.
  35. The original __enter__ method returns Any type, but we want to return R.
  36. """
  37. del self.args, self.kwds, self.func # type: ignore
  38. try:
  39. return next(self.gen) # type: ignore
  40. except StopIteration:
  41. raise RuntimeError("generator didn't yield") from None
  42. def __call__(self, func: V) -> V: # pyright: ignore [reportIncompatibleMethodOverride]
  43. if inspect.iscoroutinefunction(func):
  44. @functools.wraps(func) # type: ignore
  45. async def async_wrapper(*args: Pargs, **kwargs: Pkwargs) -> R: # pyright: ignore [reportInvalidTypeVarUse]
  46. with self._recreate_cm(): # type: ignore
  47. return await func(*args, **kwargs) # type: ignore
  48. return async_wrapper # type: ignore
  49. return super().__call__(func) # type: ignore
  50. def _agnosticcontextmanager(
  51. func: "Callable[P, Iterator[R]]",
  52. ) -> "Callable[P, _AgnosticContextManager[R]]":
  53. @functools.wraps(func)
  54. def helper(*args: Pargs, **kwargs: Pkwargs) -> _AgnosticContextManager[R]: # pyright: ignore [reportInvalidTypeVarUse]
  55. return _AgnosticContextManager(func, args, kwargs) # pyright: ignore [reportArgumentType]
  56. # Ignoring the type to keep the original signature of the function
  57. return helper # type: ignore[return-value]