From f763061ed9d9e99d85b3e95adc3ed63b623fc4a0 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 27 Sep 2019 23:22:07 +0200 Subject: [PATCH] ref: Remove all exemptions from mypy.ini (#516) * ref: Remove all exemptions from mypy.ini * fix: Revert buggy logic --- mypy.ini | 108 +------------------ sentry_sdk/_compat.py | 11 +- sentry_sdk/_types.py | 3 + sentry_sdk/integrations/_wsgi_common.py | 22 +++- sentry_sdk/integrations/asgi.py | 12 +++ sentry_sdk/integrations/atexit.py | 2 + sentry_sdk/integrations/aws_lambda.py | 22 +++- sentry_sdk/integrations/beam.py | 46 ++++++-- sentry_sdk/integrations/bottle.py | 24 +++-- sentry_sdk/integrations/celery.py | 31 +++++- sentry_sdk/integrations/django/__init__.py | 16 ++- sentry_sdk/integrations/django/middleware.py | 27 ++++- sentry_sdk/integrations/django/templates.py | 6 +- sentry_sdk/integrations/excepthook.py | 23 +++- sentry_sdk/integrations/falcon.py | 21 +++- sentry_sdk/integrations/flask.py | 11 +- sentry_sdk/integrations/gnu_backtrace.py | 1 + sentry_sdk/integrations/pyramid.py | 8 +- sentry_sdk/integrations/redis.py | 6 ++ sentry_sdk/integrations/rq.py | 5 +- sentry_sdk/integrations/sanic.py | 6 +- sentry_sdk/integrations/serverless.py | 39 ++++++- sentry_sdk/integrations/sqlalchemy.py | 4 +- sentry_sdk/integrations/stdlib.py | 21 ++++ sentry_sdk/integrations/threading.py | 20 +++- sentry_sdk/integrations/tornado.py | 17 +-- sentry_sdk/integrations/wsgi.py | 13 +-- sentry_sdk/scope.py | 48 +++++---- sentry_sdk/serializer.py | 6 +- sentry_sdk/utils.py | 23 ++-- 30 files changed, 390 insertions(+), 212 deletions(-) diff --git a/mypy.ini b/mypy.ini index 92eec0830b..fe79116e71 100644 --- a/mypy.ini +++ b/mypy.ini @@ -8,7 +8,7 @@ check_untyped_defs = True disallow_any_generics = True ; disallow_any_unimported = True disallow_incomplete_defs = True -; disallow_subclassing_any = True +disallow_subclassing_any = True ; disallow_untyped_calls = True disallow_untyped_decorators = True disallow_untyped_defs = True @@ -26,112 +26,6 @@ warn_unused_ignores = True ; Do not use wildcards in module paths, otherwise added modules will ; automatically have the same set of relaxed rules as the rest -[mypy-sentry_sdk._compat] -disallow_untyped_defs = False - -[mypy-sentry_sdk.scope] -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.django] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.django.middleware] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.bottle] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.flask] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.asgi] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.falcon] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.aws_lambda] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.pyramid] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.celery] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.beam] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.sanic] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.tornado] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.atexit] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations._wsgi_common] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.wsgi] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.serverless] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.excepthook] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.threading] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.stdlib] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.sqlalchemy] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.rq] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.redis] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.gnu_backtrace] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.integrations.django.templates] -disallow_any_generics = False -disallow_untyped_defs = False - -[mypy-sentry_sdk.utils] -disallow_any_generics = False -disallow_untyped_defs = False - [mypy-django.*] ignore_missing_imports = True [mypy-pyramid.*] diff --git a/sentry_sdk/_compat.py b/sentry_sdk/_compat.py index c94ef6debb..1d6c06d110 100644 --- a/sentry_sdk/_compat.py +++ b/sentry_sdk/_compat.py @@ -8,6 +8,10 @@ from typing import Any from typing import Type + from typing import TypeVar + + T = TypeVar("T") + PY2 = sys.version_info[0] == 2 @@ -23,6 +27,7 @@ iteritems = lambda x: x.iteritems() # noqa: B301 def implements_str(cls): + # type: (T) -> T cls.__unicode__ = cls.__str__ cls.__str__ = lambda x: unicode(x).encode("utf-8") # noqa return cls @@ -40,10 +45,8 @@ def implements_str(cls): int_types = (int,) # noqa iteritems = lambda x: x.items() - def _identity(x): - return x - def implements_str(x): + # type: (T) -> T return x def reraise(tp, value, tb=None): @@ -55,8 +58,10 @@ def reraise(tp, value, tb=None): def with_metaclass(meta, *bases): + # type: (Any, *Any) -> Any class metaclass(type): def __new__(cls, name, this_bases, d): + # type: (Any, Any, Any, Any) -> Any return meta(name, bases, d) return type.__new__(metaclass, "temporary_class", (), {}) diff --git a/sentry_sdk/_types.py b/sentry_sdk/_types.py index 99654e9aac..6f9af8d312 100644 --- a/sentry_sdk/_types.py +++ b/sentry_sdk/_types.py @@ -26,3 +26,6 @@ EventProcessor = Callable[[Event, Hint], Optional[Event]] ErrorProcessor = Callable[[Event, ExcInfo], Optional[Event]] BreadcrumbProcessor = Callable[[Breadcrumb, BreadcrumbHint], Optional[Breadcrumb]] + + # https://github.com/python/mypy/issues/5710 + NotImplementedType = Any diff --git a/sentry_sdk/integrations/_wsgi_common.py b/sentry_sdk/integrations/_wsgi_common.py index cb626a5788..3deb48f33d 100644 --- a/sentry_sdk/integrations/_wsgi_common.py +++ b/sentry_sdk/integrations/_wsgi_common.py @@ -79,12 +79,15 @@ def content_length(self): return 0 def cookies(self): + # type: () -> Dict[str, Any] raise NotImplementedError() def raw_data(self): + # type: () -> Optional[Union[str, bytes]] raise NotImplementedError() def form(self): + # type: () -> Optional[Dict[str, Any]] raise NotImplementedError() def parsed_body(self): @@ -110,28 +113,37 @@ def is_json(self): def json(self): # type: () -> Optional[Any] try: - if self.is_json(): - raw_data = self.raw_data() - if not isinstance(raw_data, text_type): - raw_data = raw_data.decode("utf-8") + if not self.is_json(): + return None + + raw_data = self.raw_data() + if raw_data is None: + return None + + if isinstance(raw_data, text_type): return json.loads(raw_data) + else: + return json.loads(raw_data.decode("utf-8")) except ValueError: pass return None def files(self): + # type: () -> Optional[Dict[str, Any]] raise NotImplementedError() def size_of_file(self, file): + # type: (Any) -> int raise NotImplementedError() def env(self): + # type: () -> Dict[str, Any] raise NotImplementedError() def _is_json_content_type(ct): - # type: (str) -> bool + # type: (Optional[str]) -> bool mt = (ct or "").split(";", 1)[0] return ( mt == "application/json" diff --git a/sentry_sdk/integrations/asgi.py b/sentry_sdk/integrations/asgi.py index efbbe0ad38..c531b5bd00 100644 --- a/sentry_sdk/integrations/asgi.py +++ b/sentry_sdk/integrations/asgi.py @@ -16,6 +16,9 @@ if MYPY: from typing import Dict from typing import Any + from typing import Optional + + from sentry_sdk._types import Event, Hint _asgi_middleware_applied = ContextVar("sentry_asgi_middleware_applied") @@ -38,12 +41,15 @@ class SentryAsgiMiddleware: __slots__ = ("app",) def __init__(self, app): + # type: (Any) -> None self.app = app def __call__(self, scope, receive=None, send=None): + # type: (Any, Any, Any) -> Any if receive is None or send is None: async def run_asgi2(receive, send): + # type: (Any, Any) -> Any return await self._run_app( scope, lambda: self.app(scope)(receive, send) ) @@ -53,6 +59,7 @@ async def run_asgi2(receive, send): return self._run_app(scope, lambda: self.app(scope, receive, send)) async def _run_app(self, scope, callback): + # type: (Any, Any) -> Any if _asgi_middleware_applied.get(False): return await callback() @@ -88,6 +95,7 @@ async def _run_app(self, scope, callback): _asgi_middleware_applied.set(False) def event_processor(self, event, hint, asgi_scope): + # type: (Event, Hint, Any) -> Optional[Event] request_info = event.setdefault("request", {}) if asgi_scope["type"] in ("http", "websocket"): @@ -107,6 +115,7 @@ def event_processor(self, event, hint, asgi_scope): return event def get_url(self, scope): + # type: (Any) -> str """ Extract URL from the ASGI scope, without also including the querystring. """ @@ -128,12 +137,14 @@ def get_url(self, scope): return path def get_query(self, scope): + # type: (Any) -> Any """ Extract querystring from the ASGI scope, in the format that the Sentry protocol expects. """ return urllib.parse.unquote(scope["query_string"].decode("latin-1")) def get_headers(self, scope): + # type: (Any) -> Dict[str, Any] """ Extract headers from the ASGI scope, in the format that the Sentry protocol expects. """ @@ -148,6 +159,7 @@ def get_headers(self, scope): return headers def get_transaction(self, scope): + # type: (Any) -> Optional[str] """ Return a transaction string to identify the routed endpoint. """ diff --git a/sentry_sdk/integrations/atexit.py b/sentry_sdk/integrations/atexit.py index ecaa82b4d6..3d0eca811d 100644 --- a/sentry_sdk/integrations/atexit.py +++ b/sentry_sdk/integrations/atexit.py @@ -17,6 +17,7 @@ def default_callback(pending, timeout): + # type: (int, int) -> None """This is the default shutdown callback that is set on the options. It prints out a message to stderr that informs the user that some events are still pending and the process is waiting for them to flush out. @@ -46,6 +47,7 @@ def setup_once(): # type: () -> None @atexit.register def _shutdown(): + # type: () -> None logger.debug("atexit: got shutdown signal") hub = Hub.main integration = hub.get_integration(AtexitIntegration) diff --git a/sentry_sdk/integrations/aws_lambda.py b/sentry_sdk/integrations/aws_lambda.py index c96f9ab03b..2ab385fa7b 100644 --- a/sentry_sdk/integrations/aws_lambda.py +++ b/sentry_sdk/integrations/aws_lambda.py @@ -15,10 +15,19 @@ if MYPY: from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import EventProcessor, Event, Hint + + F = TypeVar("F", bound=Callable[..., Any]) def _wrap_handler(handler): + # type: (F) -> F def sentry_handler(event, context, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(AwsLambdaIntegration) if integration is None: @@ -45,10 +54,11 @@ def sentry_handler(event, context, *args, **kwargs): hub.capture_event(event, hint=hint) reraise(*exc_info) - return sentry_handler + return sentry_handler # type: ignore def _drain_queue(): + # type: () -> None with capture_internal_exceptions(): hub = Hub.current integration = hub.get_integration(AwsLambdaIntegration) @@ -87,6 +97,7 @@ def setup_once(): old_handle_event_request = lambda_bootstrap.handle_event_request def sentry_handle_event_request(request_handler, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any request_handler = _wrap_handler(request_handler) return old_handle_event_request(request_handler, *args, **kwargs) @@ -95,6 +106,7 @@ def sentry_handle_event_request(request_handler, *args, **kwargs): old_handle_http_request = lambda_bootstrap.handle_http_request def sentry_handle_http_request(request_handler, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any request_handler = _wrap_handler(request_handler) return old_handle_http_request(request_handler, *args, **kwargs) @@ -106,6 +118,7 @@ def sentry_handle_http_request(request_handler, *args, **kwargs): old_to_json = lambda_bootstrap.to_json def sentry_to_json(*args, **kwargs): + # type: (*Any, **Any) -> Any _drain_queue() return old_to_json(*args, **kwargs) @@ -127,11 +140,13 @@ def sentry_handle_event_request( # type: ignore # even when the SDK is initialized inside of the handler def _wrap_post_function(f): + # type: (F) -> F def inner(*args, **kwargs): + # type: (*Any, **Any) -> Any _drain_queue() return f(*args, **kwargs) - return inner + return inner # type: ignore lambda_bootstrap.LambdaRuntimeClient.post_invocation_result = _wrap_post_function( lambda_bootstrap.LambdaRuntimeClient.post_invocation_result @@ -142,7 +157,9 @@ def inner(*args, **kwargs): def _make_request_event_processor(aws_event, aws_context): + # type: (Any, Any) -> EventProcessor def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] extra = event.setdefault("extra", {}) extra["lambda"] = { "remaining_time_in_millis": aws_context.get_remaining_time_in_millis(), @@ -187,6 +204,7 @@ def event_processor(event, hint): def _get_url(event, context): + # type: (Any, Any) -> str path = event.get("path", None) headers = event.get("headers", {}) host = headers.get("Host", None) diff --git a/sentry_sdk/integrations/beam.py b/sentry_sdk/integrations/beam.py index 3098f04929..7252746a7f 100644 --- a/sentry_sdk/integrations/beam.py +++ b/sentry_sdk/integrations/beam.py @@ -9,6 +9,21 @@ from sentry_sdk.utils import capture_internal_exceptions, event_from_exception from sentry_sdk.integrations import Integration from sentry_sdk.integrations.logging import ignore_logger +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + from typing import Iterator + from typing import TypeVar + from typing import Optional + from typing import Callable + + from sentry_sdk.client import Client + from sentry_sdk._types import ExcInfo + + T = TypeVar("T") + F = TypeVar("F", bound=Callable[..., Any]) + WRAPPED_FUNC = "_wrapped_{}_" INSPECT_FUNC = "_inspect_{}" # Required format per apache_beam/transforms/core.py @@ -37,6 +52,7 @@ def setup_once(): old_init = ParDo.__init__ def sentry_init_pardo(self, fn, *args, **kwargs): + # type: (ParDo, Any, *Any, **Any) -> Any # Do not monkey patch init twice if not getattr(self, "_sentry_is_patched", False): for func_name in function_patches: @@ -63,12 +79,14 @@ def sentry_init_pardo(self, fn, *args, **kwargs): def _wrap_inspect_call(cls, func_name): + # type: (Any, Any) -> Any from apache_beam.typehints.decorators import getfullargspec # type: ignore if not hasattr(cls, func_name): return None def _inspect(self): + # type: (Any) -> Any """ Inspect function overrides the way Beam gets argspec. """ @@ -94,6 +112,7 @@ def _inspect(self): def _wrap_task_call(func): + # type: (F) -> F """ Wrap task call with a try catch to get exceptions. Pass the client on to raise_exception so it can get rebinded. @@ -102,6 +121,7 @@ def _wrap_task_call(func): @wraps(func) def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any try: gen = func(*args, **kwargs) except Exception: @@ -112,25 +132,32 @@ def _inner(*args, **kwargs): return _wrap_generator_call(gen, client) setattr(_inner, USED_FUNC, True) - return _inner + return _inner # type: ignore def _capture_exception(exc_info, hub): + # type: (ExcInfo, Hub) -> None """ Send Beam exception to Sentry. """ integration = hub.get_integration(BeamIntegration) - if integration: - client = hub.client - event, hint = event_from_exception( - exc_info, - client_options=client.options, - mechanism={"type": "beam", "handled": False}, - ) - hub.capture_event(event, hint=hint) + if integration is None: + return + + client = hub.client + if client is None: + return + + event, hint = event_from_exception( + exc_info, + client_options=client.options, + mechanism={"type": "beam", "handled": False}, + ) + hub.capture_event(event, hint=hint) def raise_exception(client): + # type: (Optional[Client]) -> None """ Raise an exception. If the client is not in the hub, rebind it. """ @@ -144,6 +171,7 @@ def raise_exception(client): def _wrap_generator_call(gen, client): + # type: (Iterator[T], Optional[Client]) -> Iterator[T] """ Wrap the generator to handle any failures. """ diff --git a/sentry_sdk/integrations/bottle.py b/sentry_sdk/integrations/bottle.py index 27fe084832..93ca96ea34 100644 --- a/sentry_sdk/integrations/bottle.py +++ b/sentry_sdk/integrations/bottle.py @@ -13,7 +13,6 @@ from sentry_sdk._types import MYPY if MYPY: - from sentry_sdk.integrations.wsgi import _ScopedResponse from typing import Any from typing import Dict @@ -21,6 +20,8 @@ from typing import Optional from bottle import FileUpload, FormsDict, LocalRequest # type: ignore + from sentry_sdk._types import EventProcessor + from bottle import Bottle, Route, request as bottle_request, HTTPResponse @@ -47,7 +48,7 @@ def setup_once(): old_app = Bottle.__call__ def sentry_patched_wsgi_app(self, environ, start_response): - # type: (Any, Dict[str, str], Callable) -> _ScopedResponse + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse hub = Hub.current integration = hub.get_integration(BottleIntegration) @@ -64,6 +65,7 @@ def sentry_patched_wsgi_app(self, environ, start_response): old_handle = Bottle._handle def _patched_handle(self, environ): + # type: (Bottle, Dict[str, Any]) -> Any hub = Hub.current integration = hub.get_integration(BottleIntegration) if integration is None: @@ -90,6 +92,7 @@ def _patched_handle(self, environ): old_make_callback = Route._make_callback def patched_make_callback(self, *args, **kwargs): + # type: (Route, *object, **object) -> Any hub = Hub.current integration = hub.get_integration(BottleIntegration) prepared_callback = old_make_callback(self, *args, **kwargs) @@ -100,20 +103,19 @@ def patched_make_callback(self, *args, **kwargs): client = hub.client # type: Any def wrapped_callback(*args, **kwargs): - def capture_exception(exception): - event, hint = event_from_exception( - exception, - client_options=client.options, - mechanism={"type": "bottle", "handled": False}, - ) - hub.capture_event(event, hint=hint) + # type: (*object, **object) -> Any try: res = prepared_callback(*args, **kwargs) except HTTPResponse: raise except Exception as exception: - capture_exception(exception) + event, hint = event_from_exception( + exception, + client_options=client.options, + mechanism={"type": "bottle", "handled": False}, + ) + hub.capture_event(event, hint=hint) raise exception return res @@ -155,7 +157,7 @@ def size_of_file(self, file): def _make_request_event_processor(app, request, integration): - # type: (Bottle, LocalRequest, BottleIntegration) -> Callable + # type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor def inner(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] diff --git a/sentry_sdk/integrations/celery.py b/sentry_sdk/integrations/celery.py index c95be9eb8b..5ff864f7aa 100644 --- a/sentry_sdk/integrations/celery.py +++ b/sentry_sdk/integrations/celery.py @@ -20,6 +20,13 @@ if MYPY: from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import EventProcessor, Event, Hint, ExcInfo + + F = TypeVar("F", bound=Callable[..., Any]) CELERY_CONTROL_FLOW_EXCEPTIONS = (Retry, Ignore, Reject) @@ -40,6 +47,7 @@ def setup_once(): old_build_tracer = trace.build_tracer def sentry_build_tracer(name, task, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> Any if not getattr(task, "_sentry_is_patched", False): # Need to patch both methods because older celery sometimes # short-circuits to task.run if it thinks it's safe. @@ -66,8 +74,10 @@ def sentry_build_tracer(name, task, *args, **kwargs): def _wrap_apply_async(task, f): + # type: (Any, F) -> F @functools.wraps(f) def apply_async(*args, **kwargs): + # type: (*Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(CeleryIntegration) if integration is not None and integration.propagate_traces: @@ -84,10 +94,12 @@ def apply_async(*args, **kwargs): else: return f(*args, **kwargs) - return apply_async + return apply_async # type: ignore def _wrap_tracer(task, f): + # type: (Any, F) -> F + # Need to wrap tracer for pushing the scope before prerun is sent, and # popping it after postrun is sent. # @@ -96,6 +108,7 @@ def _wrap_tracer(task, f): # crashes. @functools.wraps(f) def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any hub = Hub.current if hub.get_integration(CeleryIntegration) is None: return f(*args, **kwargs) @@ -117,10 +130,12 @@ def _inner(*args, **kwargs): with hub.start_span(span): return f(*args, **kwargs) - return _inner + return _inner # type: ignore def _wrap_task_call(task, f): + # type: (Any, F) -> F + # Need to wrap task call because the exception is caught before we get to # see it. Also celery's reported stacktrace is untrustworthy. @@ -129,6 +144,7 @@ def _wrap_task_call(task, f): # https://github.com/getsentry/sentry-python/issues/421 @functools.wraps(f) def _inner(*args, **kwargs): + # type: (*Any, **Any) -> Any try: return f(*args, **kwargs) except Exception: @@ -137,11 +153,13 @@ def _inner(*args, **kwargs): _capture_exception(task, exc_info) reraise(*exc_info) - return _inner + return _inner # type: ignore def _make_event_processor(task, uuid, args, kwargs, request=None): + # type: (Any, Any, Any, Any, Optional[Any]) -> EventProcessor def event_processor(event, hint): + # type: (Event, Hint) -> Optional[Event] with capture_internal_exceptions(): extra = event.setdefault("extra", {}) extra["celery-job"] = { @@ -165,6 +183,7 @@ def event_processor(event, hint): def _capture_exception(task, exc_info): + # type: (Any, ExcInfo) -> None hub = Hub.current if hub.get_integration(CeleryIntegration) is None: @@ -187,10 +206,13 @@ def _capture_exception(task, exc_info): with capture_internal_exceptions(): with hub.configure_scope() as scope: - scope.span.set_failure() + if scope.span is not None: + scope.span.set_failure() def _patch_worker_exit(): + # type: () -> None + # Need to flush queue before worker shutdown because a crashing worker will # call os._exit from billiard.pool import Worker # type: ignore @@ -198,6 +220,7 @@ def _patch_worker_exit(): old_workloop = Worker.workloop def sentry_workloop(*args, **kwargs): + # type: (*Any, **Any) -> Any try: return old_workloop(*args, **kwargs) finally: diff --git a/sentry_sdk/integrations/django/__init__.py b/sentry_sdk/integrations/django/__init__.py index 826132cb91..0ea688aed4 100644 --- a/sentry_sdk/integrations/django/__init__.py +++ b/sentry_sdk/integrations/django/__init__.py @@ -17,6 +17,7 @@ from typing import Dict from typing import Optional from typing import Union + from typing import List from django.core.handlers.wsgi import WSGIRequest from django.http.response import HttpResponse @@ -24,7 +25,7 @@ from django.utils.datastructures import MultiValueDict from sentry_sdk.integrations.wsgi import _ScopedResponse - from sentry_sdk._types import Event, Hint + from sentry_sdk._types import Event, Hint, EventProcessor, NotImplementedType try: @@ -98,7 +99,7 @@ def setup_once(): old_app = WSGIHandler.__call__ def sentry_patched_wsgi_handler(self, environ, start_response): - # type: (Any, Dict[str, str], Callable) -> _ScopedResponse + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse if Hub.current.get_integration(DjangoIntegration) is None: return old_app(self, environ, start_response) @@ -187,6 +188,7 @@ def process_django_templates(event, hint): @add_global_repr_processor def _django_queryset_repr(value, hint): + # type: (Any, Dict[str, Any]) -> Union[NotImplementedType, str] try: # Django 1.6 can fail to import `QuerySet` when Django settings # have not yet been initialized. @@ -221,6 +223,7 @@ def _django_queryset_repr(value, hint): def _patch_drf(): + # type: () -> None """ Patch Django Rest Framework for more/better request data. DRF's request type is a wrapper around Django's request type. The attribute we're @@ -263,6 +266,7 @@ def _patch_drf(): old_drf_initial = APIView.initial def sentry_patched_drf_initial(self, request, *args, **kwargs): + # type: (APIView, Any, *Any, **Any) -> Any with capture_internal_exceptions(): request._request._sentry_drf_request_backref = weakref.ref( request @@ -274,6 +278,7 @@ def sentry_patched_drf_initial(self, request, *args, **kwargs): def _patch_channels(): + # type: () -> None try: from channels.http import AsgiHandler # type: ignore except ImportError: @@ -293,6 +298,7 @@ def _patch_channels(): old_app = AsgiHandler.__call__ def sentry_patched_asgi_handler(self, receive, send): + # type: (AsgiHandler, Any, Any) -> Any if Hub.current.get_integration(DjangoIntegration) is None: return old_app(receive, send) @@ -306,7 +312,7 @@ def sentry_patched_asgi_handler(self, receive, send): def _make_event_processor(weak_request, integration): - # type: (Callable[[], WSGIRequest], DjangoIntegration) -> Callable + # type: (Callable[[], WSGIRequest], DjangoIntegration) -> EventProcessor def event_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] # if the request is gone we are fine not logging the data from @@ -374,9 +380,11 @@ def files(self): return self.request.FILES def size_of_file(self, file): + # type: (Any) -> int return file.size def parsed_body(self): + # type: () -> Optional[Dict[str, Any]] try: return self.request.data except AttributeError: @@ -424,6 +432,7 @@ def install_sql_hook(): return def execute(self, sql, params=None): + # type: (CursorWrapper, Any, Optional[Any]) -> Any hub = Hub.current if hub.get_integration(DjangoIntegration) is None: return real_execute(self, sql, params) @@ -434,6 +443,7 @@ def execute(self, sql, params=None): return real_execute(self, sql, params) def executemany(self, sql, param_list): + # type: (CursorWrapper, Any, List[Any]) -> Any hub = Hub.current if hub.get_integration(DjangoIntegration) is None: return real_executemany(self, sql, param_list) diff --git a/sentry_sdk/integrations/django/middleware.py b/sentry_sdk/integrations/django/middleware.py index 99624f074c..ab76f9c2b3 100644 --- a/sentry_sdk/integrations/django/middleware.py +++ b/sentry_sdk/integrations/django/middleware.py @@ -9,6 +9,15 @@ from sentry_sdk import Hub from sentry_sdk.utils import ContextVar, transaction_from_function +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + from typing import Callable + from typing import TypeVar + + F = TypeVar("F", bound=Callable[..., Any]) + _import_string_should_wrap_middleware = ContextVar( "import_string_should_wrap_middleware" ) @@ -20,11 +29,13 @@ def patch_django_middlewares(): + # type: () -> None from django.core.handlers import base old_import_string = getattr(base, import_string_name) def sentry_patched_import_string(dotted_path): + # type: (str) -> Any rv = old_import_string(dotted_path) if _import_string_should_wrap_middleware.get(None): @@ -37,6 +48,7 @@ def sentry_patched_import_string(dotted_path): old_load_middleware = base.BaseHandler.load_middleware def sentry_patched_load_middleware(self): + # type: (base.BaseHandler) -> Any _import_string_should_wrap_middleware.set(True) try: return old_load_middleware(self) @@ -47,11 +59,14 @@ def sentry_patched_load_middleware(self): def _wrap_middleware(middleware, middleware_name): + # type: (Any, str) -> Any from sentry_sdk.integrations.django import DjangoIntegration def _get_wrapped_method(old_method): + # type: (F) -> F @wraps(old_method) def sentry_wrapped_method(*args, **kwargs): + # type: (*Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(DjangoIntegration) if integration is None or not integration.middleware_spans: @@ -71,16 +86,18 @@ def sentry_wrapped_method(*args, **kwargs): span.set_tag("django.middleware_name", middleware_name) return old_method(*args, **kwargs) - return sentry_wrapped_method + return sentry_wrapped_method # type: ignore class SentryWrappingMiddleware(object): def __init__(self, *args, **kwargs): + # type: (*Any, **Any) -> None self._inner = middleware(*args, **kwargs) self._call_method = None # We need correct behavior for `hasattr()`, which we can only determine # when we have an instance of the middleware we're wrapping. def __getattr__(self, method_name): + # type: (str) -> Any if method_name not in ( "process_request", "process_view", @@ -96,9 +113,11 @@ def __getattr__(self, method_name): return rv def __call__(self, *args, **kwargs): - if self._call_method is None: - self._call_method = _get_wrapped_method(self._inner.__call__) - return self._call_method(*args, **kwargs) + # type: (*Any, **Any) -> Any + f = self._call_method + if f is None: + self._call_method = f = _get_wrapped_method(self._inner.__call__) + return f(*args, **kwargs) if hasattr(middleware, "__name__"): SentryWrappingMiddleware.__name__ = middleware.__name__ diff --git a/sentry_sdk/integrations/django/templates.py b/sentry_sdk/integrations/django/templates.py index 94c8eaf159..2285644909 100644 --- a/sentry_sdk/integrations/django/templates.py +++ b/sentry_sdk/integrations/django/templates.py @@ -6,6 +6,8 @@ from typing import Any from typing import Dict from typing import Optional + from typing import Iterator + from typing import Tuple try: # support Django 1.9 @@ -33,7 +35,7 @@ def get_template_frame_from_exception(exc_value): if isinstance(exc_value, TemplateSyntaxError) and hasattr(exc_value, "source"): source = exc_value.source if isinstance(source, (tuple, list)) and isinstance(source[0], Origin): - return _get_template_frame_from_source(source) + return _get_template_frame_from_source(source) # type: ignore return None @@ -71,6 +73,7 @@ def _get_template_frame_from_debug(debug): def _linebreak_iter(template_source): + # type: (str) -> Iterator[int] yield 0 p = template_source.find("\n") while p >= 0: @@ -79,6 +82,7 @@ def _linebreak_iter(template_source): def _get_template_frame_from_source(source): + # type: (Tuple[Origin, Tuple[int, int]]) -> Optional[Dict[str, Any]] if not source: return None diff --git a/sentry_sdk/integrations/excepthook.py b/sentry_sdk/integrations/excepthook.py index 7791de31db..294a94bf6a 100644 --- a/sentry_sdk/integrations/excepthook.py +++ b/sentry_sdk/integrations/excepthook.py @@ -9,6 +9,20 @@ if MYPY: from typing import Callable from typing import Any + from typing import Type + + from types import TracebackType + + from mypy_extensions import Arg + + Excepthook = Callable[ + [ + Arg(Type[BaseException], "type_"), + Arg(BaseException, "value"), + Arg(TracebackType, "traceback"), + ], + None, + ] class ExcepthookIntegration(Integration): @@ -33,8 +47,9 @@ def setup_once(): def _make_excepthook(old_excepthook): - # type: (Callable) -> Callable - def sentry_sdk_excepthook(exctype, value, traceback): + # type: (Excepthook) -> Excepthook + def sentry_sdk_excepthook(type_, value, traceback): + # type: (Type[BaseException], BaseException, TracebackType) -> None hub = Hub.current integration = hub.get_integration(ExcepthookIntegration) @@ -44,13 +59,13 @@ def sentry_sdk_excepthook(exctype, value, traceback): with capture_internal_exceptions(): event, hint = event_from_exception( - (exctype, value, traceback), + (type_, value, traceback), client_options=client.options, mechanism={"type": "excepthook", "handled": False}, ) hub.capture_event(event, hint=hint) - return old_excepthook(exctype, value, traceback) + return old_excepthook(type_, value, traceback) return sentry_sdk_excepthook diff --git a/sentry_sdk/integrations/falcon.py b/sentry_sdk/integrations/falcon.py index 06dbb1d21c..bf644b99c4 100644 --- a/sentry_sdk/integrations/falcon.py +++ b/sentry_sdk/integrations/falcon.py @@ -12,24 +12,32 @@ if MYPY: from typing import Any - from typing import Callable from typing import Dict + from typing import Optional + + from sentry_sdk._types import EventProcessor class FalconRequestExtractor(RequestExtractor): def env(self): + # type: () -> Dict[str, Any] return self.request.env def cookies(self): + # type: () -> Dict[str, Any] return self.request.cookies def form(self): + # type: () -> None return None # No such concept in Falcon def files(self): + # type: () -> None return None # No such concept in Falcon def raw_data(self): + # type: () -> Optional[str] + # As request data can only be read once we won't make this available # to Sentry. Just send back a dummy string in case there was a # content length. @@ -41,6 +49,7 @@ def raw_data(self): return None def json(self): + # type: () -> Optional[Dict[str, Any]] try: return self.request.media except falcon.errors.HTTPBadRequest: @@ -55,6 +64,7 @@ class SentryFalconMiddleware(object): """Captures exceptions in Falcon requests and send to Sentry""" def process_request(self, req, resp, *args, **kwargs): + # type: (Any, Any, *Any, **Any) -> None hub = Hub.current integration = hub.get_integration(FalconIntegration) if integration is None: @@ -89,9 +99,11 @@ def setup_once(): def _patch_wsgi_app(): + # type: () -> None original_wsgi_app = falcon.API.__call__ def sentry_patched_wsgi_app(self, env, start_response): + # type: (falcon.API, Any, Any) -> Any hub = Hub.current integration = hub.get_integration(FalconIntegration) if integration is None: @@ -107,9 +119,11 @@ def sentry_patched_wsgi_app(self, env, start_response): def _patch_handle_exception(): + # type: () -> None original_handle_exception = falcon.API._handle_exception def sentry_patched_handle_exception(self, *args): + # type: (falcon.API, *Any) -> Any # NOTE(jmagnusson): falcon 2.0 changed falcon.API._handle_exception # method signature from `(ex, req, resp, params)` to # `(req, resp, ex, params)` @@ -140,11 +154,13 @@ def sentry_patched_handle_exception(self, *args): def _patch_prepare_middleware(): + # type: () -> None original_prepare_middleware = falcon.api_helpers.prepare_middleware def sentry_patched_prepare_middleware( middleware=None, independent_middleware=False ): + # type: (Any, Any) -> Any hub = Hub.current integration = hub.get_integration(FalconIntegration) if integration is not None: @@ -155,11 +171,12 @@ def sentry_patched_prepare_middleware( def _is_falcon_http_error(ex): + # type: (BaseException) -> bool return isinstance(ex, (falcon.HTTPError, falcon.http_status.HTTPStatus)) def _make_request_event_processor(req, integration): - # type: (falcon.Request, FalconIntegration) -> Callable + # type: (falcon.Request, FalconIntegration) -> EventProcessor def inner(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] diff --git a/sentry_sdk/integrations/flask.py b/sentry_sdk/integrations/flask.py index 03b47f26bb..7b30b0787b 100644 --- a/sentry_sdk/integrations/flask.py +++ b/sentry_sdk/integrations/flask.py @@ -11,7 +11,6 @@ from sentry_sdk._types import MYPY if MYPY: - from sentry_sdk.integrations.wsgi import _ScopedResponse from typing import Any from typing import Dict @@ -21,6 +20,8 @@ from typing import Union from typing import Callable + from sentry_sdk._types import EventProcessor + try: import flask_login # type: ignore except ImportError: @@ -61,7 +62,7 @@ def setup_once(): old_app = Flask.__call__ def sentry_patched_wsgi_app(self, environ, start_response): - # type: (Any, Dict[str, str], Callable) -> _ScopedResponse + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse if Hub.current.get_integration(FlaskIntegration) is None: return old_app(self, environ, start_response) @@ -125,7 +126,7 @@ def env(self): return self.request.environ def cookies(self): - # type: () -> ImmutableTypeConversionDict + # type: () -> ImmutableTypeConversionDict[Any, Any] return self.request.cookies def raw_data(self): @@ -141,9 +142,11 @@ def files(self): return self.request.files def is_json(self): + # type: () -> bool return self.request.is_json def json(self): + # type: () -> Any return self.request.get_json() def size_of_file(self, file): @@ -152,7 +155,7 @@ def size_of_file(self, file): def _make_request_event_processor(app, weak_request, integration): - # type: (Flask, Callable[[], Request], FlaskIntegration) -> Callable + # type: (Flask, Callable[[], Request], FlaskIntegration) -> EventProcessor def inner(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] request = weak_request() diff --git a/sentry_sdk/integrations/gnu_backtrace.py b/sentry_sdk/integrations/gnu_backtrace.py index 6671de95f2..e0ec110547 100644 --- a/sentry_sdk/integrations/gnu_backtrace.py +++ b/sentry_sdk/integrations/gnu_backtrace.py @@ -42,6 +42,7 @@ def setup_once(): # type: () -> None @add_global_event_processor def process_gnu_backtrace(event, hint): + # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] with capture_internal_exceptions(): return _process_gnu_backtrace(event, hint) diff --git a/sentry_sdk/integrations/pyramid.py b/sentry_sdk/integrations/pyramid.py index 464f01cfc0..8e0cea1957 100644 --- a/sentry_sdk/integrations/pyramid.py +++ b/sentry_sdk/integrations/pyramid.py @@ -28,6 +28,7 @@ from webob.compat import cgi_FieldStorage # type: ignore from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import EventProcessor if getattr(Request, "authenticated_userid", None): @@ -83,6 +84,7 @@ def sentry_patched_handle_request(self, request, *args, **kwargs): old_invoke_exception_view = Request.invoke_exception_view def sentry_patched_invoke_exception_view(self, *args, **kwargs): + # type: (Request, *Any, **Any) -> Any rv = old_invoke_exception_view(self, *args, **kwargs) if ( @@ -100,13 +102,14 @@ def sentry_patched_invoke_exception_view(self, *args, **kwargs): old_wsgi_call = Router.__call__ def sentry_patched_wsgi_call(self, environ, start_response): - # type: (Any, Dict[str, str], Callable) -> _ScopedResponse + # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse hub = Hub.current integration = hub.get_integration(PyramidIntegration) if integration is None: return old_wsgi_call(self, environ, start_response) def sentry_patched_inner_wsgi_call(environ, start_response): + # type: (Dict[str, Any], Callable[..., Any]) -> Any try: return old_wsgi_call(self, environ, start_response) except Exception: @@ -143,6 +146,7 @@ def _capture_exception(exc_info): class PyramidRequestExtractor(RequestExtractor): def url(self): + # type: () -> str return self.request.path_url def env(self): @@ -183,7 +187,7 @@ def size_of_file(self, postdata): def _make_event_processor(weak_request, integration): - # type: (Callable[[], Request], PyramidIntegration) -> Callable + # type: (Callable[[], Request], PyramidIntegration) -> EventProcessor def event_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] request = weak_request() diff --git a/sentry_sdk/integrations/redis.py b/sentry_sdk/integrations/redis.py index 93711dc371..3eb1869329 100644 --- a/sentry_sdk/integrations/redis.py +++ b/sentry_sdk/integrations/redis.py @@ -4,6 +4,11 @@ from sentry_sdk.utils import capture_internal_exceptions from sentry_sdk.integrations import Integration +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + class RedisIntegration(Integration): identifier = "redis" @@ -16,6 +21,7 @@ def setup_once(): old_execute_command = redis.StrictRedis.execute_command def sentry_patched_execute_command(self, name, *args, **kwargs): + # type: (redis.StrictRedis, str, *Any, **Any) -> Any hub = Hub.current if hub.get_integration(RedisIntegration) is None: diff --git a/sentry_sdk/integrations/rq.py b/sentry_sdk/integrations/rq.py index dc7f8bd58a..f34afeb93e 100644 --- a/sentry_sdk/integrations/rq.py +++ b/sentry_sdk/integrations/rq.py @@ -21,6 +21,7 @@ from rq.job import Job from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import EventProcessor class RqIntegration(Integration): @@ -71,6 +72,7 @@ def sentry_patched_perform_job(self, job, *args, **kwargs): old_handle_exception = Worker.handle_exception def sentry_patched_handle_exception(self, job, *exc_info, **kwargs): + # type: (Worker, Any, *Any, **Any) -> Any _capture_exception(exc_info) # type: ignore return old_handle_exception(self, job, *exc_info, **kwargs) @@ -79,6 +81,7 @@ def sentry_patched_handle_exception(self, job, *exc_info, **kwargs): old_enqueue_job = Queue.enqueue_job def sentry_patched_enqueue_job(self, job, **kwargs): + # type: (Queue, Any, **Any) -> Any hub = Hub.current if hub.get_integration(RqIntegration) is not None: job.meta["_sentry_trace_headers"] = dict( @@ -91,7 +94,7 @@ def sentry_patched_enqueue_job(self, job, **kwargs): def _make_event_processor(weak_job): - # type: (Callable[[], Job]) -> Callable + # type: (Callable[[], Job]) -> EventProcessor def event_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] job = weak_job() diff --git a/sentry_sdk/integrations/sanic.py b/sentry_sdk/integrations/sanic.py index cc097577ba..301685443e 100644 --- a/sentry_sdk/integrations/sanic.py +++ b/sentry_sdk/integrations/sanic.py @@ -26,6 +26,7 @@ from typing import Optional from typing import Union from typing import Tuple + from typing import Dict from sanic.request import Request, RequestParameters @@ -98,7 +99,7 @@ def sentry_router_get(self, request): old_error_handler_lookup = ErrorHandler.lookup def sentry_error_handler_lookup(self, exception): - # type: (Any, Exception) -> Optional[Callable] + # type: (Any, Exception) -> Optional[object] _capture_exception(exception) old_error_handler = old_error_handler_lookup(self, exception) @@ -193,6 +194,7 @@ def content_length(self): return len(self.request.body) def cookies(self): + # type: () -> Dict[str, str] return dict(self.request.cookies) def raw_data(self): @@ -204,6 +206,7 @@ def form(self): return self.request.form def is_json(self): + # type: () -> bool raise NotImplementedError() def json(self): @@ -215,4 +218,5 @@ def files(self): return self.request.files def size_of_file(self, file): + # type: (Any) -> int return len(file.body or ()) diff --git a/sentry_sdk/integrations/serverless.py b/sentry_sdk/integrations/serverless.py index 0e20d73437..6dd90b43d0 100644 --- a/sentry_sdk/integrations/serverless.py +++ b/sentry_sdk/integrations/serverless.py @@ -6,10 +6,45 @@ from sentry_sdk._compat import reraise +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + from typing import Callable + from typing import TypeVar + from typing import Union + from typing import Optional + + from typing import overload + + F = TypeVar("F", bound=Callable[..., Any]) + +else: + + def overload(x): + # type: (F) -> F + return x + + +@overload +def serverless_function(f, flush=True): + # type: (F, bool) -> F + pass + + +@overload # noqa def serverless_function(f=None, flush=True): + # type: (None, bool) -> Callable[[F], F] + pass + + +def serverless_function(f=None, flush=True): # noqa + # type: (Optional[F], bool) -> Union[F, Callable[[F], F]] def wrapper(f): + # type: (F) -> F @functools.wraps(f) def inner(*args, **kwargs): + # type: (*Any, **Any) -> Any with Hub(Hub.current) as hub: with hub.configure_scope() as scope: scope.clear_breadcrumbs() @@ -22,7 +57,7 @@ def inner(*args, **kwargs): if flush: _flush_client() - return inner + return inner # type: ignore if f is None: return wrapper @@ -31,6 +66,7 @@ def inner(*args, **kwargs): def _capture_and_reraise(): + # type: () -> None exc_info = sys.exc_info() hub = Hub.current if hub is not None and hub.client is not None: @@ -45,6 +81,7 @@ def _capture_and_reraise(): def _flush_client(): + # type: () -> None hub = Hub.current if hub is not None: hub.flush() diff --git a/sentry_sdk/integrations/sqlalchemy.py b/sentry_sdk/integrations/sqlalchemy.py index 882498a612..f29df414cb 100644 --- a/sentry_sdk/integrations/sqlalchemy.py +++ b/sentry_sdk/integrations/sqlalchemy.py @@ -54,7 +54,9 @@ def _before_cursor_execute( def _after_cursor_execute(conn, cursor, statement, *args): # type: (Any, Any, Any, *Any) -> None - ctx_mgr = getattr(conn, "_sentry_sql_span_manager", None) # type: ContextManager + ctx_mgr = getattr( + conn, "_sentry_sql_span_manager", None + ) # type: ContextManager[Any] if ctx_mgr is not None: conn._sentry_sql_span_manager = None diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index f0e4ba971a..39d5c3e1e8 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -9,6 +9,17 @@ from sentry_sdk.tracing import EnvironHeaders, record_http_request from sentry_sdk.utils import capture_internal_exceptions, safe_repr +from sentry_sdk._types import MYPY + +if MYPY: + from typing import Any + from typing import Callable + from typing import Dict + from typing import Optional + from typing import List + + from sentry_sdk._types import Event, Hint + try: from httplib import HTTPConnection # type: ignore @@ -34,6 +45,7 @@ def setup_once(): @add_global_event_processor def add_python_runtime_context(event, hint): + # type: (Event, Hint) -> Optional[Event] if Hub.current.get_integration(StdlibIntegration) is not None: contexts = event.setdefault("contexts", {}) if isinstance(contexts, dict) and "runtime" not in contexts: @@ -48,6 +60,7 @@ def _install_httplib(): real_getresponse = HTTPConnection.getresponse def putrequest(self, method, url, *args, **kwargs): + # type: (HTTPConnection, str, str, *Any, **Any) -> Any hub = Hub.current if hub.get_integration(StdlibIntegration) is None: return real_putrequest(self, method, url, *args, **kwargs) @@ -83,6 +96,7 @@ def putrequest(self, method, url, *args, **kwargs): return rv def getresponse(self, *args, **kwargs): + # type: (HTTPConnection, *Any, **Any) -> Any recorder = getattr(self, "_sentrysdk_recorder", None) if recorder is None: @@ -115,6 +129,7 @@ def getresponse(self, *args, **kwargs): def _init_argument(args, kwargs, name, position, setdefault_callback=None): + # type: (List[Any], Dict[Any, Any], str, int, Optional[Callable[[Any], Any]]) -> Any """ given (*args, **kwargs) of a function call, retrieve (and optionally set a default for) an argument by either name or position. @@ -145,9 +160,12 @@ def _init_argument(args, kwargs, name, position, setdefault_callback=None): def _install_subprocess(): + # type: () -> None old_popen_init = subprocess.Popen.__init__ def sentry_patched_popen_init(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> None + hub = Hub.current if hub.get_integration(StdlibIntegration) is None: return old_popen_init(self, *a, **kw) # type: ignore @@ -194,6 +212,7 @@ def sentry_patched_popen_init(self, *a, **kw): old_popen_wait = subprocess.Popen.wait def sentry_patched_popen_wait(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> Any hub = Hub.current if hub.get_integration(StdlibIntegration) is None: @@ -208,6 +227,7 @@ def sentry_patched_popen_wait(self, *a, **kw): old_popen_communicate = subprocess.Popen.communicate def sentry_patched_popen_communicate(self, *a, **kw): + # type: (subprocess.Popen[Any], *Any, **Any) -> Any hub = Hub.current if hub.get_integration(StdlibIntegration) is None: @@ -221,4 +241,5 @@ def sentry_patched_popen_communicate(self, *a, **kw): def get_subprocess_traceparent_headers(): + # type: () -> EnvironHeaders return EnvironHeaders(os.environ, prefix="SUBPROCESS_") diff --git a/sentry_sdk/integrations/threading.py b/sentry_sdk/integrations/threading.py index 34503a715c..b750257e2a 100644 --- a/sentry_sdk/integrations/threading.py +++ b/sentry_sdk/integrations/threading.py @@ -7,16 +7,24 @@ from sentry_sdk._compat import reraise from sentry_sdk._types import MYPY from sentry_sdk.integrations import Integration -from sentry_sdk.utils import event_from_exception +from sentry_sdk.utils import event_from_exception, capture_internal_exceptions if MYPY: from typing import Any + from typing import TypeVar + from typing import Callable + from typing import Optional + + from sentry_sdk._types import ExcInfo + + F = TypeVar("F", bound=Callable[..., Any]) class ThreadingIntegration(Integration): identifier = "threading" def __init__(self, propagate_hub=False): + # type: (bool) -> None self.propagate_hub = propagate_hub @staticmethod @@ -25,6 +33,7 @@ def setup_once(): old_start = Thread.start def sentry_start(self, *a, **kw): + # type: (Thread, *Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(ThreadingIntegration) if integration is not None: @@ -38,7 +47,9 @@ def sentry_start(self, *a, **kw): # # In threading module, using current_thread API will access current thread instance # without holding it to avoid a reference cycle in an easier way. - self.run = _wrap_run(hub_, self.run.__func__) + with capture_internal_exceptions(): + new_run = _wrap_run(hub_, getattr(self.run, "__func__", self.run)) + self.run = new_run # type: ignore return old_start(self, *a, **kw) # type: ignore @@ -46,7 +57,9 @@ def sentry_start(self, *a, **kw): def _wrap_run(parent_hub, old_run_func): + # type: (Optional[Hub], F) -> F def run(*a, **kw): + # type: (*Any, **Any) -> Any hub = parent_hub or Hub.current with hub: try: @@ -55,10 +68,11 @@ def run(*a, **kw): except Exception: reraise(*_capture_exception()) - return run + return run # type: ignore def _capture_exception(): + # type: () -> ExcInfo hub = Hub.current exc_info = sys.exc_info() diff --git a/sentry_sdk/integrations/tornado.py b/sentry_sdk/integrations/tornado.py index 551bf2ace0..495d05a968 100644 --- a/sentry_sdk/integrations/tornado.py +++ b/sentry_sdk/integrations/tornado.py @@ -24,11 +24,12 @@ if MYPY: from typing import Any - from typing import List from typing import Optional from typing import Dict from typing import Callable + from sentry_sdk._types import EventProcessor + class TornadoIntegration(Integration): identifier = "tornado" @@ -60,7 +61,7 @@ def setup_once(): # Starting Tornado 6 RequestHandler._execute method is a standard Python coroutine (async/await) # In that case our method should be a coroutine function too async def sentry_execute_request_handler(self, *args, **kwargs): - # type: (Any, *List, **Any) -> Any + # type: (Any, *Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(TornadoIntegration) if integration is None: @@ -76,8 +77,9 @@ async def sentry_execute_request_handler(self, *args, **kwargs): else: - @coroutine + @coroutine # type: ignore def sentry_execute_request_handler(self, *args, **kwargs): + # type: (RequestHandler, *Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(TornadoIntegration) if integration is None: @@ -124,7 +126,7 @@ def _capture_exception(ty, value, tb): def _make_event_processor(weak_handler): - # type: (Callable[[], RequestHandler]) -> Callable + # type: (Callable[[], RequestHandler]) -> EventProcessor def tornado_processor(event, hint): # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] handler = weak_handler() @@ -171,7 +173,7 @@ def content_length(self): return len(self.request.body) def cookies(self): - # type: () -> Dict + # type: () -> Dict[str, str] return {k: v.value for k, v in iteritems(self.request.cookies)} def raw_data(self): @@ -179,7 +181,7 @@ def raw_data(self): return self.request.body def form(self): - # type: () -> Optional[Any] + # type: () -> Dict[str, Any] return { k: [v.decode("latin1", "replace") for v in vs] for k, vs in iteritems(self.request.body_arguments) @@ -190,8 +192,9 @@ def is_json(self): return _is_json_content_type(self.request.headers.get("content-type")) def files(self): - # type: () -> Dict + # type: () -> Dict[str, Any] return {k: v[0] for k, v in iteritems(self.request.files) if v} def size_of_file(self, file): + # type: (Any) -> int return len(file.body or ()) diff --git a/sentry_sdk/integrations/wsgi.py b/sentry_sdk/integrations/wsgi.py index 0c0615733b..597cc22a50 100644 --- a/sentry_sdk/integrations/wsgi.py +++ b/sentry_sdk/integrations/wsgi.py @@ -16,7 +16,6 @@ if MYPY: from typing import Callable from typing import Dict - from typing import List from typing import Iterator from typing import Any from typing import Tuple @@ -24,6 +23,7 @@ from typing import TypeVar from sentry_sdk.utils import ExcInfo + from sentry_sdk._types import EventProcessor T = TypeVar("T") U = TypeVar("U") @@ -85,11 +85,11 @@ class SentryWsgiMiddleware(object): __slots__ = ("app",) def __init__(self, app): - # type: (Callable) -> None + # type: (Callable[[Dict[str, str], Callable[..., Any]], Any]) -> None self.app = app def __call__(self, environ, start_response): - # type: (Dict[str, str], Callable) -> _ScopedResponse + # type: (Dict[str, str], Callable[..., Any]) -> _ScopedResponse if _wsgi_middleware_applied.get(False): return self.app(environ, start_response) @@ -219,7 +219,7 @@ class _ScopedResponse(object): __slots__ = ("_response", "_hub") def __init__(self, hub, response): - # type: (Hub, List[bytes]) -> None + # type: (Hub, Iterator[bytes]) -> None self._hub = hub self._response = response @@ -239,9 +239,10 @@ def __iter__(self): yield chunk def close(self): + # type: () -> None with self._hub: try: - self._response.close() + self._response.close() # type: ignore except AttributeError: pass except BaseException: @@ -249,7 +250,7 @@ def close(self): def _make_wsgi_event_processor(environ): - # type: (Dict[str, str]) -> Callable + # type: (Dict[str, str]) -> EventProcessor # It's a bit unfortunate that we have to extract and parse the request data # from the environ so eagerly, but there are a few good reasons for this. # diff --git a/sentry_sdk/scope.py b/sentry_sdk/scope.py index f7ce41d0f7..d935adf8f1 100644 --- a/sentry_sdk/scope.py +++ b/sentry_sdk/scope.py @@ -21,9 +21,13 @@ Event, EventProcessor, ErrorProcessor, + ExcInfo, Hint, + Type, ) + from sentry_sdk.tracing import Span + F = TypeVar("F", bound=Callable[..., Any]) T = TypeVar("T") @@ -37,6 +41,7 @@ def add_global_event_processor(processor): def _attr_setter(fn): + # type: (Any) -> Any return property(fset=fn, doc=fn.__doc__) @@ -85,18 +90,38 @@ def __init__(self): self._name = None # type: Optional[str] self.clear() + def clear(self): + # type: () -> None + """Clears the entire scope.""" + self._level = None # type: Optional[str] + self._fingerprint = None # type: Optional[List[str]] + self._transaction = None # type: Optional[str] + self._user = None # type: Optional[Dict[str, Any]] + + self._tags = {} # type: Dict[str, Any] + self._contexts = {} # type: Dict[str, Dict[str, Any]] + self._extras = {} # type: Dict[str, Any] + + self.clear_breadcrumbs() + self._should_capture = True + + self._span = None # type: Optional[Span] + @_attr_setter def level(self, value): + # type: (Optional[str]) -> None """When set this overrides the level.""" self._level = value @_attr_setter def fingerprint(self, value): + # type: (Optional[List[str]]) -> None """When set this overrides the default fingerprint.""" self._fingerprint = value @_attr_setter def transaction(self, value): + # type: (Optional[str]) -> None """When set this forces a specific transaction name to be set.""" self._transaction = value if self._span: @@ -104,16 +129,19 @@ def transaction(self, value): @_attr_setter def user(self, value): + # type: (Dict[str, Any]) -> None """When set a specific user is bound to the scope.""" self._user = value @property def span(self): + # type: () -> Optional[Span] """Get/set current tracing span.""" return self._span @span.setter def span(self, span): + # type: (Optional[Span]) -> None self._span = span if span is not None and span.transaction: self._transaction = span.transaction @@ -166,23 +194,6 @@ def remove_extra( """Removes a specific extra key.""" self._extras.pop(key, None) - def clear(self): - # type: () -> None - """Clears the entire scope.""" - self._level = None - self._fingerprint = None - self._transaction = None - self._user = None - - self._tags = {} # type: Dict[str, Any] - self._contexts = {} # type: Dict[str, Dict[str, Any]] - self._extras = {} # type: Dict[str, Any] - - self.clear_breadcrumbs() - self._should_capture = True - - self._span = None - def clear_breadcrumbs(self): # type: () -> None """Clears breadcrumb buffer.""" @@ -208,7 +219,7 @@ def add_event_processor( def add_error_processor( self, func, # type: ErrorProcessor - cls=None, # type: Optional[type] + cls=None, # type: Optional[Type[BaseException]] ): # type: (...) -> None """Register a scope local error processor on the scope. @@ -222,6 +233,7 @@ def add_error_processor( real_func = func def func(event, exc_info): + # type: (Event, ExcInfo) -> Optional[Event] try: is_inst = isinstance(exc_info[1], cls_) except Exception: diff --git a/sentry_sdk/serializer.py b/sentry_sdk/serializer.py index feae13f5ea..9eedcedfce 100644 --- a/sentry_sdk/serializer.py +++ b/sentry_sdk/serializer.py @@ -22,9 +22,9 @@ from typing import Union from typing import Generator - # https://github.com/python/mypy/issues/5710 - _NotImplemented = Any - ReprProcessor = Callable[[Any, Dict[str, Any]], Union[_NotImplemented, str]] + from sentry_sdk._types import NotImplementedType + + ReprProcessor = Callable[[Any, Dict[str, Any]], Union[NotImplementedType, str]] Segment = Union[str, int] diff --git a/sentry_sdk/utils.py b/sentry_sdk/utils.py index ec6ab1fccc..de21c148a1 100644 --- a/sentry_sdk/utils.py +++ b/sentry_sdk/utils.py @@ -11,18 +11,18 @@ from sentry_sdk._types import MYPY if MYPY: + from types import FrameType + from types import TracebackType from typing import Any from typing import Callable from typing import Dict - from typing import Iterator from typing import Generator + from typing import Iterator from typing import List from typing import Optional from typing import Set from typing import Tuple from typing import Union - from types import FrameType - from types import TracebackType import sentry_sdk @@ -439,7 +439,7 @@ def single_exception_from_error_tuple( exc_type, # type: Optional[type] exc_value, # type: Optional[BaseException] tb, # type: Optional[TracebackType] - client_options=None, # type: Optional[dict] + client_options=None, # type: Optional[Dict[str, Any]] mechanism=None, # type: Optional[Dict[str, Any]] ): # type: (...) -> Dict[str, Any] @@ -512,7 +512,7 @@ def walk_exception_chain(exc_info): def exceptions_from_error_tuple( exc_info, # type: ExcInfo - client_options=None, # type: Optional[dict] + client_options=None, # type: Optional[Dict[str, Any]] mechanism=None, # type: Optional[Dict[str, Any]] ): # type: (...) -> List[Dict[str, Any]] @@ -560,7 +560,7 @@ def iter_event_frames(event): def handle_in_app(event, in_app_exclude=None, in_app_include=None): - # type: (Dict[str, Any], Optional[List], Optional[List]) -> Dict[str, Any] + # type: (Dict[str, Any], Optional[List[str]], Optional[List[str]]) -> Dict[str, Any] for stacktrace in iter_event_stacktraces(event): handle_in_app_impl( stacktrace.get("frames"), @@ -572,7 +572,7 @@ def handle_in_app(event, in_app_exclude=None, in_app_include=None): def handle_in_app_impl(frames, in_app_exclude, in_app_include): - # type: (Any, Optional[List], Optional[List]) -> Optional[Any] + # type: (Any, Optional[List[str]], Optional[List[str]]) -> Optional[Any] if not frames: return None @@ -625,7 +625,7 @@ def exc_info_from_error(error): def event_from_exception( exc_info, # type: Union[BaseException, ExcInfo] - client_options=None, # type: Optional[dict] + client_options=None, # type: Optional[Dict[str, Any]] mechanism=None, # type: Optional[Dict[str, Any]] ): # type: (...) -> Tuple[Dict[str, Any], Dict[str, Any]] @@ -645,7 +645,7 @@ def event_from_exception( def _module_in_set(name, set): - # type: (str, Optional[List]) -> bool + # type: (str, Optional[List[str]]) -> bool if not set: return False for item in set or (): @@ -699,7 +699,7 @@ def _is_threading_local_monkey_patched(): def _get_contextvars(): - # () -> (bool, Type) + # type: () -> Tuple[bool, type] """ Try to import contextvars and use it if it's deemed safe. We should not use contextvars if gevent or eventlet have patched thread locals, as @@ -724,13 +724,16 @@ class ContextVar(object): # Super-limited impl of ContextVar def __init__(self, name): + # type: (str) -> None self._name = name self._local = local() def get(self, default): + # type: (Any) -> Any return getattr(self._local, "value", default) def set(self, value): + # type: (Any) -> None setattr(self._local, "value", value) return False, ContextVar