diff --git a/sentry_sdk/integrations/_falcon_async_py3.py b/sentry_sdk/integrations/_falcon_async_py3.py new file mode 100644 index 0000000000..188d93e618 --- /dev/null +++ b/sentry_sdk/integrations/_falcon_async_py3.py @@ -0,0 +1,34 @@ +""" +This file was created so we could have a process_request_async method on our +SentryFalconMiddleware class, so we could properly support ASGI with Falcon. +However, since process_request_async needs to be an async function, we cannot +define it in Python 2.7, as this produces a syntax error. + +So, we have split the process_request_async implementation into this separate +file, which can only be loaded in Python 3 and above. + +TODO: If we ever remove Python 2.7 support, we should delete this file and move +the process_request_async implementation to the SentryFalconMiddleware class +in the falcon.py file. +""" + +from typing import Any + + +class _SentryFalconMiddlewareBase: + """ + The SentryFalconMiddleware should inherit from this class in Python 3. The only + purpose of this class is to provide an implementation of process_request_async which + simply calls process_request. + """ + + def process_request(self, req: Any, resp: Any, *args: Any, **kwargs: Any) -> None: + """ + SentryFalconMiddleware will override this method and provide the actual implementation. + """ + pass + + async def process_request_async( + self, req: Any, resp: Any, *args: Any, **kwargs: Any + ) -> None: + self.process_request(req, resp, *args, **kwargs) diff --git a/sentry_sdk/integrations/falcon.py b/sentry_sdk/integrations/falcon.py index 1bb79428f1..f26d198671 100644 --- a/sentry_sdk/integrations/falcon.py +++ b/sentry_sdk/integrations/falcon.py @@ -13,6 +13,15 @@ from sentry_sdk._types import TYPE_CHECKING +from sentry_sdk._compat import PY2 + +if PY2: + _SentryFalconMiddlewareBase = object +else: + # Support ASGI with a process_request_async method, which can only be loaded in Python 3, + # since async methods are invalid syntax in Python 2.7. + from sentry_sdk.integrations._falcon_async_py3 import _SentryFalconMiddlewareBase + if TYPE_CHECKING: from typing import Any from typing import Dict @@ -97,7 +106,7 @@ def json(self): return self.request._media -class SentryFalconMiddleware(object): +class SentryFalconMiddleware(_SentryFalconMiddlewareBase): """Captures exceptions in Falcon requests and send to Sentry""" def process_request(self, req, resp, *args, **kwargs): @@ -205,15 +214,13 @@ def _patch_prepare_middleware(): # type: () -> None original_prepare_middleware = falcon_helpers.prepare_middleware - def sentry_patched_prepare_middleware( - middleware=None, independent_middleware=False - ): - # type: (Any, Any) -> Any + def sentry_patched_prepare_middleware(middleware=None, *args, **kwargs): + # type: (Any, *Any, **Any) -> Any hub = Hub.current integration = hub.get_integration(FalconIntegration) if integration is not None: middleware = [SentryFalconMiddleware()] + (middleware or []) - return original_prepare_middleware(middleware, independent_middleware) + return original_prepare_middleware(middleware, *args, **kwargs) falcon_helpers.prepare_middleware = sentry_patched_prepare_middleware diff --git a/tests/integrations/falcon/test_falcon.py b/tests/integrations/falcon/test_falcon.py index dd7aa80dfe..fe014b8687 100644 --- a/tests/integrations/falcon/test_falcon.py +++ b/tests/integrations/falcon/test_falcon.py @@ -12,9 +12,20 @@ from sentry_sdk.integrations.falcon import FalconIntegration from sentry_sdk.integrations.logging import LoggingIntegration +try: + import falcon.asgi +except ImportError: + # Only test WSGI app + APP_CONSTRUCTORS = [falcon.API] +else: + # Test ASGI and WSGI apps + APP_CONSTRUCTORS = [falcon.App, falcon.asgi.App] + + +@pytest.fixture(params=APP_CONSTRUCTORS) +def make_app(request, sentry_init): + app_constructor = request.param -@pytest.fixture -def make_app(sentry_init): def inner(): class MessageResource: def on_get(self, req, resp): @@ -26,7 +37,7 @@ def on_get(self, req, resp, message_id): sentry_sdk.capture_message("hi") resp.media = "hi" - app = falcon.API() + app = app_constructor() app.add_route("/message", MessageResource()) app.add_route("/message/{message_id:int}", MessageByIdResource())