miguelgrinberg / python-socketio

Python Socket.IO server and client
MIT License
3.86k stars 574 forks source link

Multiple Listeners for Single Event #906

Closed StormSurge95 closed 1 year ago

StormSurge95 commented 2 years ago

In the javascript version of socketio, it is possible to set multiple listeners to the same socket event; as well as setting a listener to only respond to an event once. I think it'd be nice to reflect this feature within python if at all possible.

miguelgrinberg commented 2 years ago

This creates some unpredictability that I prefer to avoid. For example: do all the handlers for an event run in parallel? Do they run one after another? In which order?

If you implement this yourself by having a single event handler that calls all these actions you want to happen, then you have full control and can implement the concurrency model explicitly. I'm not saying I'm full against it, but there needs to be a Pythonic solution for this feature to be included.

StormSurge95 commented 2 years ago

Ah I hadn't thought about the unpredictability. Imo the best solution would be a complete mimicry of the nature of how javascript operates multiple listeners; however, I have no idea how it does that so I can't really offer a good solution in that regard.

miguelgrinberg commented 2 years ago

I disagree. JavaScript does things in a way that is familiar to JavaScript developers. I do not want to alienate Python developers by making this project too JavaScripty.

I mean, in JavaScript there is a very well established tradition of setting up event listeners, you can add and remove at any time, and as you pointed out, also have multiple handlers. Python does not have any of this. I've decided to take a Pythonic approach and use decorated event handlers, which is the most familiar pattern in this language for event handlers.

StormSurge95 commented 2 years ago

That makes sense. I'm still extremely amateur when it comes to developing full-scale projects like this. The only other solution I can think of would be to allow functionality of lists of handlers; and then have them operate in whatever order they are in the list. If an event passes data, that data is provided to each handler in the list for that particular event. For the regular client, handlers could operate one after another; and for the async client, handlers could be async and started one after another and operate conurrently. I'm sure there's issues I can't think of with this solution; but maybe it would work (or at the very least be something I could do myself as you suggested earlier).

miguelgrinberg commented 2 years ago

I think the majority of cases can be handled with a single event handler that does all the things that you want to do:

@sio.event
def my_event(data):
    do_thing_1()
    do_thing_2()
    do_thing_3()

Is this solution insufficient for your needs?

StormSurge95 commented 2 years ago

Just a slight bit. The issue is that I I may need do_thing_1() to happen every time while I only need do_thing_2() to happen when I first start and only need do_thing_3() after a specific type of request. I did find a bit of a workaround though by simply having all of my handlers be within scope of the class at large (as opposed to within scope of a single function) and setting/resetting handlers as necessary. It may not be super efficient, but at least it works (so far).

miguelgrinberg commented 2 years ago

This package does not have the ability to remove handlers either. Another thing that is very familiar to JavaScript developers, but unusual in Python.

vpmartin commented 1 month ago

Commenting after almost 2 years this issue has been closed, but multiple listeners for a single event applies totally to my use case. Not expecting anything out of this, just to explain a very niche POV.

I am working on a quantitative trading framework in Python, which is completely modular and using python-socketio as the main information transmission bus.

To give more details, all of the different parts of the framework are completely agnostic of the other parts. The data source for the live trading data; the Trading Strategy that evaluates said market data; the main trading engine that uses decision from the Strategy to send buy/sell signals; and the API wrappers taking those buy/sell signals to execute trades on online platforms.

There are also utility objects gravitating around this system- such as one that listens to buy/sell signals and live market data to save to a database, or another one that listens to buy/sell signals to send notifications.

These objects are interchangeable: the market data could come from a CSV file or an RF feed instead of a websocket connection to a trading platform, and the system wouldn't even know. To do so, handlers are declared dynamically using Client.on(event, handler) based on a class they inherit: the MarketSubscriber will listen to market data, the SignalSubscriber to buy/sell signals, etc. Thus, I cannot directly oversee the creation of those handlers.

In this specific case, the utility that saves in the database and the API wrapper were both listening to the same events: one to save the data, the other to execute trades.

I discovered after many many hours of wondering "why is this not triggering" that Client.handlers[my_namespace] is a dict that maps an event to a function. Hence me finding this issue on Google.

I agree it's a pretty niche use case, and maybe Socket.IO isn't the proper protocol? But I've been working with it for a few years now and never had any problem.

Cheers for the good work anyway! python-socketio is great.

miguelgrinberg commented 3 weeks ago

@vpmartin you can easily implement this in your application using the observer pattern. There are many implementations of this pattern available for Python.