-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Signals #401
Comments
I support the idea but maybe with slightly different API details. For now |
Yep, happy to work on a pull request, just wanted to make sure I wasn't heading in the wrong direction first :-). |
Oh, forgot to mention: Wasn't actually suggesting it use function annotations, but thought it was useful syntax for expressing argument types. |
How do you want to use function annotations in signal context? |
Oh, I didn't have any particular desire to use them, sorry. Should have said "thought it was useful syntax for expressing argument types in writing up the proposal". Might be handy for documentation purposes, but that'd be a library-wide style thing. |
I suggest not use annotations for now. After Python 3.5 release we may return to discussion bearing in mind PEP 484. I suggest using My English is so far from be fluent but I feel the difference is important. Maybe you may suggest better word. |
My current implementation makes |
Backward incompatibility is a big problem. Better to avoid it. |
It also means that all the tests that use mocks for |
I don't care about tests: we can change those if needed. |
Yes. I couldn't see an alternative to making start a coroutine, unless all signals are to be blocking too (which makes them less useful if they've got to do IO). Current WIP at https://github.com/alexsdutton/aiohttp/tree/signals. I've updated the tests to treat |
We won't change 'start' method. @asvetlov what is the reason for clone()? |
@fafhrd91 I dislike mutating request object from arbitrary point. I believe it makes a mess quickly. |
-1 for clone, you can not mutate request at arbitrary point in any case. In reality you can mutate it safely only in middleware after that it does not matter I am in general -1 for anything without confirmed improvements. |
@fafhrd91 are you -1 for signals in general? |
So, my actual use-case is implementing RFC4559. This says:
To handle this case, one needs to be able to set response headers on arbitrary responses. The code I have at the moment is https://github.com/ox-it/apiox-core/blob/master/apiox/core/middleware/negotiate.py#L28-L44. (Negotiate auth is admittedly Not Done Right, on account of the |
@asvetlov i'm fine with signals. but we can not change start() method, this will brake all production code. |
my proposal is to make signals support coroutine by creating different type of signal instances and fire coroutine signal before handler call. also instead of identifying signals by string, let's make signal instances on Application object. something like: class Signal:
def register_handler(...):
...
def unregister_handler(...):
...
def fire(*args, **kwargs):
...
class SignalWithCoroutines(Signal):
@asyncio.coroutine
def fire(*args, **kwargs):
...
class web.Application:
def __init__(...):
...
self.onBeforeHandler = SignalWithCoroutine()
self.onResponseStart = Signal()
class Response:
def start(self, request):
...
request.app.onResponseStart.fire(self, request=request) |
I'd worry that making them attributes on the app would clutter it. Those that want to define their own signals would either have to attach them to the app, or keep track of them some other way. If it's desirable to eventually have |
I'm fine with splitting signal types into regular functions and coroutines. From my taste |
Sorry for the radio silence. I'll have a go at implementing something along those lines soon. |
So, here's a new branch: https://github.com/alexsdutton/aiohttp/tree/signals-v2 I've created an AsyncSignal, but haven't used it anywhere. Is this looking more like it? |
Looking on flask I see two mechanisms for the same thing:
Flask's signals are asynchronous in term that their execution order is undetermenistic and signal handlers don't return values. Callbacks are opposite, they are ordered and may return response object to prevent callback chain execution. Citation from flask docs:
Do we need both? How to process, say, default-expect-handler via signals? |
Let's just do signals for now |
@alexsdutton I believe |
@asvetlov How do you mean? Having the person defining the signal construct a |
I suggest syntax like |
Isn't that a bit verbose? You'd only be able to check for signature equality, which could cause trouble down the line if you want to pass extra arguments in a backwards-compatible manner — developers would be able to use |
I'm also less convinced about the sensibleness of making the dispatch order arbitrary. In the CORS (#516) scenario, one would want the CORS signal handler to come after most other handlers in case one of those other handlers has set a header that the CORS handler needs to expose. Do others agree that it would make sense to dispatch in reverse order, i.e.::
… would add the Should I'd also suggest changing the app finish stuff to use the signals implementation (with backwards compatibility, of course). Good idea? |
Yes, signal handlers should be ordered -- as middlewares are. |
Sounds plausible :-) |
I've tried to implement |
I'm happy with that. I've only counted five methods that would need overriding if one does want to later implement signature checking ( |
@asvetlov I've just pushed to my signals-v2 branch with a list-based implementation. |
Fixed by #562 |
To support altering headers before responses are sent, among other things, there should be a signals framework so people can hook into the response process. This would be extensible for other uses too. This issue follows on from #393.
The proposal is:
Create a new
signals
module, containing aSignal
class. Its constructor takes a single argument,arguments
, saying which arguments must be passed when dispatching the signal. This class has a method for checking that potential callbacks have a compatible signature. The module also contains instances of this class for individual types of signal (e.g.response_start = Signal({'request', 'response'})
— I can't yet think of what other signals might be useful).Application
gets a new_signals = defaultdict(list)
and a@property
that exposes a view of it. It gets aadd_signal_callback(signal : Signal, callback : Callable)
method, which checks that the callback is compatible (see above) and coerces the calback to a coroutine, before appending the callback toself._signals[signal]
.Signal dispatching is done by
Appllication.dispatch_signal(signal : Signal, kwargs : dict)
, which iterates through the registered signals, calling each in turn.Response.start()
is modified to dispatch theresponse_start
signal after this line.I don't think there's any need to create signals for the request creation phase, as the request can be modified safely with middleware.
The use-case of adding response headers from middleware becomes this slightly contrived example:
The text was updated successfully, but these errors were encountered: