Skip to content
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

Merged
merged 12 commits into from
Oct 12, 2017
Merged

simplifying middleware #2252

merged 12 commits into from
Oct 12, 2017

Conversation

samuelcolvin
Copy link
Member

@samuelcolvin samuelcolvin commented Sep 7, 2017

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:

from aiohttp.web import middleware

@middleware
async def my_middleware(request, handler):
    return await handler(request)

Old style middleware:

async def middleware_factory(app, handler):
    async def middleware_handler(request):
        return await handler(request)
    return middleware_handler

Continues to work with a depreciation warning.

Are there changes in behavior for the user?

They can simplify their middleware if they wish

Rationale

  1. The new style is cleaner, less code, quicker to read, quicker to write.
  2. Old style leaves a big bad bear trap for developers to fall into: assuming the outer coroutine is only called once and using it for setup code.

Related issue number

#2225

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • Add a new news fragment into the changes folder
    • name it <issue_id>.<type> for example (588.bug)
    • if you don't have an issue_id change it to the pr id after creating the pr
    • ensure type is one of the following:
      • .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.
    • Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."

@samuelcolvin
Copy link
Member Author

@asvetlov

Then we'll open a kind of voting for collecting feedback from other devs.

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-io
Copy link

codecov-io commented Sep 8, 2017

Codecov Report

Merging #2252 into master will increase coverage by <.01%.
The diff coverage is 97.22%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
aiohttp/web.py 99.66% <100%> (+0.01%) ⬆️
aiohttp/web_middlewares.py 97.36% <95.83%> (+0.14%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 20854d6...c7df4b4. Read the comment docs.

@asvetlov
Copy link
Member

asvetlov commented Sep 8, 2017

Hmm, in your proposal all middlewares are either new-styled or old ones.
It's impossible to combine new-style middleware from some third-party library with old-fashioned one.
Therefore it looks like very hard backward compatibility breaker.

@samuelcolvin
Copy link
Member Author

ok, I'll extend it to check every middleware.

@samuelcolvin
Copy link
Member Author

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)
Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member Author

@samuelcolvin samuelcolvin Oct 11, 2017

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.

Copy link
Member Author

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.

Copy link
Member

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!

Copy link
Member Author

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.

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asvetlov

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.

Copy link
Member

@asvetlov asvetlov left a 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is irrelevant

@@ -23,6 +24,11 @@ def _check_request_resolves(request, path):
return False, request


def new_middleware(f):
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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::
Copy link
Member

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).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

asvetlov
asvetlov previously approved these changes Oct 12, 2017
@asvetlov
Copy link
Member

Perfect!
Feel free to merge after making spell checker happy.
Hint: use does not instead of doesn't

@samuelcolvin
Copy link
Member Author

Passing except for codecov. I can't merge.

Can I suggest configuring codecov so it's not this brittle?

@asvetlov
Copy link
Member

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.
If not -- just say "don't want", I'll merge the PR as is.

Hmm, maybe I'm wrong -- feel free to make a PR with codecov config improvement.

@samuelcolvin
Copy link
Member Author

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).

@asvetlov
Copy link
Member

Accepted

@asvetlov asvetlov merged commit 872bea1 into aio-libs:master Oct 12, 2017
@asvetlov
Copy link
Member

Thanks for such valuable improvement, @samuelcolvin

@lock
Copy link

lock bot commented Oct 28, 2019

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 feel like there's important points made in this discussion, please include those exceprts into that [new issue].
[new issue]: https://github.com/aio-libs/aiohttp/issues/new

@lock lock bot added the outdated label Oct 28, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 28, 2019
@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Oct 28, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bot:chronographer:provided There is a change note present in this PR outdated
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants