BlackSheep
BlackSheep copied to clipboard
Unable to integrate asgi3 middlewares
Describe the bug A description of what the bug is, possibly including how to reproduce it.
I am trying to include SentryAsgiMiddleware.
I have to hack it a little as per the known issue as:
class SentryAsgi3Middleware1(SentryAsgiMiddleware):
"""SentryAsgi3Middleware"""
# pylint: disable=method-hidden
async def __call__(self, *args, **kwargs):
"""Override __call__ slot"""
return await self._run_asgi3(*args, **kwargs)
But the problem arrises when a URL is actually called.
TypeError: SentryAsgiMiddleware._run_asgi3() missing 1 required positional argument: 'send'
Using https://github.com/encode/starlette/blob/b8ea367b4304a98653ec8ce9c794ad0ba6dcaf4b/starlette/middleware/base.py as reference, if I change the function to:
class SentryAsgi3Middleware(SentryAsgiMiddleware):
"""SentryAsgi3Middleware"""
# pylint: disable=method-hidden
async def __call__(self, scope: Request, receive: Callable[[Request], Awaitable[Response]], send: Callable[[Response], Awaitable[None]]) -> Response:
"""Override __call__ slot"""
return await self._run_asgi3(*args, **kwargs)
I get AttributeError: 'SentryAsgi3Middleware' object has no attribute '__qualname__'
which is AmbiguousMethodSignatureError
so somewhere normalize_middleware
is failing to properly convert it.
Hi @virajkanwade,
Sorry for the late response. Yours is a very good question and this should have been documented explicitly.
BlackSheep never had support for ASGI middlewares, because ASGI request scope is all dict
, list
, and scalar values, while BlackSheep always handled Request
and Response
classes, even before being made compatible with ASGI for the main request-response cycle.
I suspect that it won't be possible to add support for ASGI middlewares to BlackSheep, because it really needs to handle instances of Request
and Response
types, not dict
. Adding support for a single middleware might be simple, but not so adding support for multiple ASGI middlewares in a row, for example, or mixed with BlackSheep specific middlewares.
Hi @RobertoPrevato
Found a sample code for Starlette. Inside the sample asgi3 middleware def call:
❯ python app.py ─╯
INFO: Started server process [8701]
INFO: Waiting for application startup.
File "/private/tmp/timing-starlette-asgi-example/app.py", line 44, in <module>
uvicorn.run(app)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/main.py", line 463, in run
server.run()
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
return asyncio.run(self.serve(sockets=sockets))
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
await app(scope, self.receive, self.send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 87, in __call__
await self.app(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/timing_asgi/middleware.py", line 31, in __call__
traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}, 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
2022-05-23 22:24:25 DEBUG [timing_asgi.middleware:46] ASGI scope of type lifespan is not supported yet
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
File "/private/tmp/timing-starlette-asgi-example/app.py", line 44, in <module>
uvicorn.run(app)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/main.py", line 463, in run
server.run()
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
return asyncio.run(self.serve(sockets=sockets))
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 366, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 100, in __call__
await self.app(scope, receive, _send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/timing_asgi/middleware.py", line 31, in __call__
traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 63904), 'scheme': 'http', 'method': 'GET', 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'127.0.0.1:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x107029060>>
<function ServerErrorMiddleware.__call__.<locals>._send at 0x107050940>
INFO: 127.0.0.1:63904 - "GET / HTTP/1.1" 200 OK
^CINFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [8701]
Similar call stack for Blacksheep application call
❯ bin/run_dev ─╯
INFO: Will watch for changes in these directories: ['/private/tmp/blacksheep-app']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [16277] using statreload
INFO: Started server process [16289]
INFO: Waiting for application startup.
File "<string>", line 1, in <module>
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 129, in _main
return self._bootstrap(parent_sentinel)
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
target(sockets=sockets)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
return asyncio.run(self.serve(sockets=sockets))
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
await app(scope, self.receive, self.send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
services {<class 'configuration.common.Configuration'>: <rodi.InstanceProvider object at 0x10e33df90>, 'Configuration': <rodi.InstanceProvider object at 0x10e33df90>, <class 'app.routes.organization.Organizations'>: <rodi.FactoryTypeProvider object at 0x10e33e200>, 'Organizations': <rodi.FactoryTypeProvider object at 0x10e33e200>, 'configuration': <rodi.InstanceProvider object at 0x10e33df90>, 'organizations': <rodi.FactoryTypeProvider object at 0x10e33e200>}
binders [<blacksheep.server.bindings.RequestBinder object at 0x10e33f010>, <blacksheep.server.bindings.QueryBinder object at 0x10e33f0a0>, <blacksheep.server.bindings.QueryBinder object at 0x10e33efe0>]
INFO: Application startup complete.
File "<string>", line 1, in <module>
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 116, in spawn_main
exitcode = _main(fd, parent_sentinel)
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/spawn.py", line 129, in _main
return self._bootstrap(parent_sentinel)
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 315, in _bootstrap
self.run()
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/multiprocessing/process.py", line 108, in run
self._target(*self._args, **self._kwargs)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/subprocess.py", line 76, in subprocess_started
target(sockets=sockets)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/server.py", line 60, in run
return asyncio.run(self.serve(sockets=sockets))
File "/Users/vkanwade/.pyenv/versions/3.10.3/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 64658), 'scheme': 'http', 'root_path': '', 'headers': [(b'host', b'localhost:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'method': 'GET', 'path': '/health', 'raw_path': b'/health', 'query_string': b''}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
<bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
INFO: 127.0.0.1:64658 - "GET /health HTTP/1.1" 200 OK
^CINFO: Shutting down
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [16289]
INFO: Stopping reloader process [16277]
key areas of interest
starlette:
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
await app(scope, self.receive, self.send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}, 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x106fb8f70>>
blacksheep:
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/lifespan/on.py", line 84, in main
await app(scope, self.receive, self.send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
traceback.print_stack()
{'type': 'lifespan', 'asgi': {'version': '3.0', 'spec_version': '2.0'}}
<bound method LifespanOn.receive of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
<bound method LifespanOn.send of <uvicorn.lifespan.on.LifespanOn object at 0x10c19e110>>
As you can see, blacksheep too gets the same scope, receive and send as starlette in application.call. So I guess we just need to identify asgi3 middleware and somehow pass through the receive and send to it?
starlette:
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 366, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/private/tmp/timing-starlette-asgi-example/.venv/lib/python3.10/site-packages/starlette/applications.py", line 133, in __call__
await self.error_middleware(scope, receive, send)
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 63904), 'scheme': 'http', 'method': 'GET', 'root_path': '', 'path': '/', 'raw_path': b'/', 'query_string': b'', 'headers': [(b'host', b'127.0.0.1:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'app': <starlette.applications.Starlette object at 0x105d73d30>}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.h11_impl.RequestResponseCycle object at 0x107029060>>
<function ServerErrorMiddleware.__call__.<locals>._send at 0x107050940>
blacksheep:
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 372, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 75, in __call__
return await self.app(scope, receive, send)
File "/Users/vkanwade/Library/Caches/pypoetry/virtualenvs/blacksheep-app/lib/python3.10/site-packages/blacksheep/server/application.py", line 762, in __call__
traceback.print_stack()
{'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('127.0.0.1', 8000), 'client': ('127.0.0.1', 64658), 'scheme': 'http', 'root_path': '', 'headers': [(b'host', b'localhost:8000'), (b'user-agent', b'curl/7.79.1'), (b'accept', b'*/*')], 'method': 'GET', 'path': '/health', 'raw_path': b'/health', 'query_string': b''}
<bound method RequestResponseCycle.receive of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
<bound method RequestResponseCycle.send of <uvicorn.protocols.http.httptools_impl.RequestResponseCycle object at 0x10e3a1c60>>
Here too scope is similar in both. receive is same. Starlette seems to be overriding send.