-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
simplifying middleware #2252
simplifying middleware #2252
Conversation
How do you propose to vote? Using github reactions? I've delayed updating docs until we agree this is required. Also if we are changing to this, perhaps we should give some kind of indication how long old-style middleware will continue to work. |
Codecov Report
@@ Coverage Diff @@
## master #2252 +/- ##
==========================================
+ Coverage 97.23% 97.24% +<.01%
==========================================
Files 39 39
Lines 8179 8190 +11
Branches 1430 1433 +3
==========================================
+ Hits 7953 7964 +11
Misses 98 98
Partials 128 128
Continue to review full report at Codecov.
|
Hmm, in your proposal all middlewares are either new-styled or old ones. |
ok, I'll extend it to check every middleware. |
ok, now old and new middleware can be mixed. |
aiohttp/web.py
Outdated
@@ -272,6 +274,20 @@ def _make_request(self, message, payload, protocol, writer, task, | |||
self._loop, | |||
client_max_size=self._client_max_size) | |||
|
|||
def _prepare_middleware(self): | |||
for m in reversed(self._middlewares): | |||
sig = inspect.signature(m) |
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 don't like signature inspecting. The check is too weak.
In my mind we should mark a new style middleware very explicitly.
Let's wrap it by @newstylemiddleware
decorator.
The decorator will add __middleware_version__
attr to decorated object with value 1
.
@samuelcolvin what do you think?
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.
Seems ugly, but I agree the signature approach is brittle.
I'll add it unless anyone can think of a third, less cleaner approach?
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.
instead of the decorator we could use a type annotation for new style middleware? So if you want to use new style middleware you have to use
async def my_middleware(request, app) -> Request:
...
After all, this is what type annotations are for.
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.
We could then add a depeciation warning in future if middleware doesn't include a type annotation declaring its return type.
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.
Yep, type annotations is even better than decorator!
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.
why not?
It's there as a language feature, we can use it if we want.
We can implement the decorator solution too in case some people can't (eg. 3.4) or don't want to use type annotations.
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.
@samuelcolvin yes, new and old styles are different by return type, now I see it.
The language feature's usage is static type checking only. Technically you could use it for different purposes but this is discouraged.
Strings as type annotations are supported now by typing
and will become mandatory in next Pythons. Stub files make situation even more complex.
Let's use decorator approach.
@kxepal could you explain your idea?
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.
Genuine question: "but this is discouraged", can you point me to where that's stated? I wasn't aware of it.
I really don't see that stub files have have relevance here, AFAIK they're particular to mypy. I was just suggesting using function.__annotations__
, same as another application might use function.__name__
. Stub files and strings as type annotations have no bearing on this.
However, I see a real point to not using type annotations: developers assume they're either advisory or for static type analysis; therefore it doesn't make much sense to use them for business logic in one place in the code.
I'll implement the decorator solution now.
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.
@samuelcolvin I don't remember exact blogpost or article but Guido told many times about his vision for annotations: previously it was not utilized by python stdlib etc., people used to apply them on they own.
But now annotations are for type checking only. Other approaches are still supported to not break existing code (and will be supported at least for decade) though.
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.
def old_style_middleware(app, handler):
async def middleware_handler(request):
...
return middleware_handler
@middleware
def new_style_middleware(request, handler):
...
where middleware
is something like
def middleware(func):
def old_style_middleware(app, handler):
async def middleware_handler(request):
return await func(request, handler)
return middleware_handler
return old_style_middleware
Or whatever it should be to create new style arguments. But, since aiohttp implements middleware
decorator, it may dictate what it should decorate and what to pass for decorated function.
Thought, I still not quite follow what's wrong with current approach. Yes, it's a bit verbose, but quite simple and explicit.
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.
Looks almost perfect!
Would you update documentation as well?
def _prepare_middleware(self): | ||
for m in reversed(self._middlewares): | ||
if getattr(m, '__middleware_version__', None) == 1: | ||
# first argument is request or r and this is |
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.
The comment is irrelevant
aiohttp/web_middlewares.py
Outdated
@@ -23,6 +24,11 @@ def _check_request_resolves(request, path): | |||
return False, request | |||
|
|||
|
|||
def new_middleware(f): |
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.
Hmm.
I don't like the name. We have new_middleware
and have no old_middleware
.
Mybe just @middleware
is enough?
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 agree that I don't like new_middleware
, I'll switch to middleware
.
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.
+1
*middleware*. For example, here's a trivial *middleware factory*:: | ||
A *middleware* is just a coroutine that can modify either the request or | ||
response. For example, here's a simple *middleware* which appends | ||
``' wink'`` to the response:: |
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.
The example doesn't work with streamed responses and websockets. Maybe we should mention it (in brackets).
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.
Perfect! |
Passing except for codecov. I can't merge. Can I suggest configuring codecov so it's not this brittle? |
That's because line https://github.com/aio-libs/aiohttp/pull/2252/files#diff-724d27c86f7c189969e6112c6976feacR62 is only partially covered but I could live with it. @samuelcolvin if you'll cover the line -- I very appreciate it. Hmm, maybe I'm wrong -- feel free to make a PR with codecov config improvement. |
I don't want to here, I thought about it but it's not really related to this PR (the line was there before, with just the indent changed). |
Accepted |
Thanks for such valuable improvement, @samuelcolvin |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a [new issue] for related bugs. |
if you've got here following the the depreciation warning comment: see middleware docs for details on upgrading
What do these changes do?
This simplifies middleware so middlewares are simple coroutines rather than coroutine factories returning coroutines.
Simply:
Old style middleware:
Continues to work with a depreciation warning.
Are there changes in behavior for the user?
They can simplify their middleware if they wish
Rationale
Related issue number
#2225
Checklist
changes
folder<issue_id>.<type>
for example (588.bug)issue_id
change it to the pr id after creating the pr.feature
: Signifying a new feature..bugfix
: Signifying a bug fix..doc
: Signifying a documentation improvement..removal
: Signifying a deprecation or removal of public API..misc
: A ticket has been closed, but it is not of interest to users.