-
-
Notifications
You must be signed in to change notification settings - Fork 760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace current WSGIMiddleware implementation by a2wsgi one #1825
Conversation
I kept most of wsgi tests as a backwards compatibility validation, and that's also why I kept the wsgi module, to avoid breaking someone importing it directly. If that's not needed I can drop the module and the tests for it and move it inside the config as the previous PR was. I also added a test for the missing module case since in #177 there was no adhesion to push towards wsgi feature so I understood it as an optional feature, that is part of the [standard] package and not the minimal one. |
Yup, all there, also tested running a django/takahe instance replacing fully gunicorn by uvicorn and doing some image uploads and everything is running fine |
I'm not sure if I was clear. I mean: is it possible to achieve a fix with less footprint? |
Oh okay, I can try some things, but for sure we need a body that streams content on demand, I'll write a small test scenario with a bigfile that is more reproducible than trying things manually and try to reduce the scope of this change. |
Yeah, there is not much that can be done to reduce footprint. WSGI expects a file like structure in the Another viable path to follow is a deprecation warning on wsgi interface + adding a pluggable interface option + docs on how to plug a2wsgi if maintaining wsgi isn't a focus of uvicorn. |
I'll see this tomorrow. Thanks! Feliz ano novo Humberto! 😁 |
Alright! Feliz ano novo pra vc tb 😁 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@abersheeran If you have time, can you check if this is fine?
pyproject.toml
Outdated
@@ -36,6 +36,7 @@ dependencies = [ | |||
|
|||
[project.optional-dependencies] | |||
standard = [ | |||
"a2wsgi>=1.6.0,<2.0.0", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a2wsgi
as uvicorn
dependency, and change the documentation as mentioned on #1303 (comment).
Notes about this decision:
a2wsgi
is less than 15kB.a2wsgi
is already the recommendedWSGIMiddleware
onStarlette
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, is fine. 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the change will be the move from optional dependencies to the base dependecies, is that it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and to add on the documentation that Uvicorn is also a WSGI server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Et voilà 🙂
Since WSGIMiddleware
converts WSGI into ASGI I described it as an ASGI first web server
what do you think?
I gave a bit more thought about this. This is what I suggest us to do:
The idea here is to not add another minimal dependency - as we always want to avoid this as much as possible - and to not break our users. What do you think @humrochagf ? |
Sounds good 🙂 |
uvicorn/middleware/wsgi.py
Outdated
try: | ||
from a2wsgi import WSGIMiddleware # type: ignore # noqa | ||
except ImportError: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not the most attractive solution, but it's the best option to deprecate it without touching the config module since for testing purposes we can't reload the config module import table without breaking unrelated tests that share the config module state.
It also keeps backward compatibility with someone importing it directly in code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try: | |
from a2wsgi import WSGIMiddleware # type: ignore # noqa | |
except ImportError: | |
pass | |
try: | |
from a2wsgi import WSGIMiddleware # type: ignore # noqa | |
except ModuleNotFound: | |
WSGIMiddleware = _WSGIMiddleware |
I think it's all there, the only weird thing is that when running uvicorn command without a2wsgi I don't see the deprecation warning, but pytest detects it. Is there any kind of filter to not log it in terminal? Update: If I change the filter in my client code to show it, then I can see the deprecation warning: import warnings
warnings.simplefilter("always") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure about the documentation.
Can we just add a small note on https://www.uvicorn.org/settings/#application-interface mentioning that the user should install a2wsgi
for WSGI support, and that the Uvicorn's native WSGI integration is deprecated?
uvicorn/middleware/wsgi.py
Outdated
try: | ||
from a2wsgi import WSGIMiddleware # type: ignore # noqa | ||
except ImportError: | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try: | |
from a2wsgi import WSGIMiddleware # type: ignore # noqa | |
except ImportError: | |
pass | |
try: | |
from a2wsgi import WSGIMiddleware # type: ignore # noqa | |
except ModuleNotFound: | |
WSGIMiddleware = _WSGIMiddleware |
tests/middleware/test_wsgi.py
Outdated
def environ_switcher( | ||
async_test: Callable[[], Awaitable[None]] | ||
) -> Callable[[bool], Awaitable[None]]: | ||
from uvicorn.middleware import wsgi | ||
|
||
async def test_wrapper(use_a2wsgi: bool) -> None: | ||
if use_a2wsgi: | ||
await async_test() | ||
else: | ||
with mock.patch.dict(sys.modules, {"a2wsgi": None}): | ||
reload(wsgi) | ||
|
||
await async_test() | ||
|
||
reload(wsgi) | ||
|
||
return test_wrapper | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we do this, and avoid the @environ_switcher
magic, and the individual parametrize on each test.
def environ_switcher( | |
async_test: Callable[[], Awaitable[None]] | |
) -> Callable[[bool], Awaitable[None]]: | |
from uvicorn.middleware import wsgi | |
async def test_wrapper(use_a2wsgi: bool) -> None: | |
if use_a2wsgi: | |
await async_test() | |
else: | |
with mock.patch.dict(sys.modules, {"a2wsgi": None}): | |
reload(wsgi) | |
await async_test() | |
reload(wsgi) | |
return test_wrapper | |
@pytest.fixtures(param=wsgi._WSGIMiddleware, a2wsgi.WSGIMiddleware) | |
def wsgi_middleware_class(request): | |
return request.param |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, much better now 😄
@@ -34,7 +55,7 @@ def echo_body(environ: Environ, start_response: StartResponse) -> List[bytes]: | |||
return [output] | |||
|
|||
|
|||
def raise_exception(environ: Environ, start_response: StartResponse) -> RuntimeError: | |||
def raise_exception(environ: Environ, start_response: StartResponse) -> List[bytes]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand why you did this, but I don't think it's the right type. It should be NoReturn
🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the typechecker perspective WSGIMiddleware expects a function that has List[bytes]
as return type, if it aways fails into exception is not that important.
This type error started to be more clear because the a2wsgi WSGIMiddleware is less permissive in terms of functions it accepts.
docs/index.md
Outdated
Uvicorn offer support to WSGI applications throught `WSGIMiddleware` which converts | ||
your WSGI into ASGI application seamlessly just by running it with `--interface wsgi` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the first mention to the WSGIMiddleware
on the documentation? I think this is just an implementation detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also cut it and put just the mention in the settings like you said
docs/index.md
Outdated
The present WSGIMiddleware will be deprecated in favour of [a2wsgi](https://github.com/abersheeran/a2wsgi). | ||
If you are using Uvicorn to run WSGI applications, please add it as part of your project requirements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be removed, but this PR already deprecates it.
The present WSGIMiddleware will be deprecated in favour of [a2wsgi](https://github.com/abersheeran/a2wsgi). | |
If you are using Uvicorn to run WSGI applications, please add it as part of your project requirements. | |
The present WSGIMiddleware is deprecated in favour of [a2wsgi](https://github.com/abersheeran/a2wsgi). | |
If you are using Uvicorn to run WSGI applications, please add it as part of your project requirements. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, updated the docs
docs/index.md
Outdated
Run the server: | ||
|
||
```shell | ||
$ uvicorn --interface wsgi example:app |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the --interface
needed, or does uvicorn infers it? I never used WSGI with uvicorn. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It fails to infer it, maybe a bug but it falls into asgi2 case that also uses a function instead of a coroutine in the config code:
...
elif inspect.isfunction(self.loaded_app):
use_asgi_3 = asyncio.iscoroutinefunction(self.loaded_app)
...
ok, I'll need to stop for today, tomorrow I'll fix what is missing |
Alright! |
Just saw your test simplification, didn't knew fixtures could behave like parametrize |
Done, updated the tests with the code simplification |
I'm on the phone, but I can comment some stuff. There are two things that I think we can improve before merging:
Besides this, everything is cool here. Sorry for the round-trips here. |
Oh, no worries and all done 🙂 Had to ignore 2 kinds of type error codes: uvicorn/middleware/wsgi.py:202: error: Cannot assign to a type [misc]
uvicorn/middleware/wsgi.py:202: error: Incompatible types in assignment (expression has type "Type[_WSGIMiddleware]", variable has type "Type[WSGIMiddleware]") [assignment] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @humrochagf 🙏
Feel free to merge it :)
Yay, can I delete the branch after the merge as well? |
It's in your fork anyway 👀 |
Oh right, the way the ui presented it I thought it had a branch copy in encode/uvicorn side 😜 |
Hello!
This should fix issue #371
And this is also a continuation of PR #1303
Edit by @Kludex: