realtime-py icon indicating copy to clipboard operation
realtime-py copied to clipboard

Unable to use channels to SEND from Python to Phoenix?

Open vegabook opened this issue 3 years ago • 16 comments

Documentation and examples show how to listen from messages from Phoenix, but sending to phoenix is not documented, nor is it clear that this is even possible.

🐘 tbrowne@suprabonds:~$ ipy
Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
Type 'copyright', 'credits' or 'license' for more information
IPython 8.0.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from realtime.connection import Socket
In [2]: import inspect
In [3]: def c1(payload):
   ...:     print("C1", payload)
In [4]: URL = "ws://localhost:4000/socket/websocket"
In [5]: s = Socket(URL)
In [6]: s.connect()  # this works fine as I can see it working on Phoenix side
2022-06-19 18:20:06,987:INFO - Connection was successful
In [7]: chan1 = s.set_channel("ping")
In [8]: chan1.join().on("UPDATE", c1)
Out[8]: <realtime.channel.Channel at 0x7f988a08f3d0>
In [9]: for a in inspect.getmembers(chan1):
   ...:     print(a)
('__class__', <class 'realtime.channel.Channel'>)
('__delattr__', <method-wrapper '__delattr__' of Channel object at 0x7f988a08f3d0>)
('__dict__', {'socket': <realtime.connection.Socket object at 0x7f988a0bef70>, 'topic': 'ping', 'params': {}, 'listeners': [CallbackListener(event='UPDATE', callback=<function c1 at 0x7f988b056f70>)], 'joined': False})
('__dir__', <built-in method __dir__ of Channel object at 0x7f988a08f3d0>)
('__doc__', '\n    `Channel` is an abstraction for a topic listener for an existing socket connection.\n    Each Channel has its own topic and a list of event-callbacks that responds to messages.\n    Should only be instantiated through `connection.Socket().set_chanel(topic)`\n    Topic-Channel has a 1-many relationship.\n    ')
('__eq__', <method-wrapper '__eq__' of Channel object at 0x7f988a08f3d0>)
('__format__', <built-in method __format__ of Channel object at 0x7f988a08f3d0>)
('__ge__', <method-wrapper '__ge__' of Channel object at 0x7f988a08f3d0>)
('__getattribute__', <method-wrapper '__getattribute__' of Channel object at 0x7f988a08f3d0>)
('__gt__', <method-wrapper '__gt__' of Channel object at 0x7f988a08f3d0>)
('__hash__', <method-wrapper '__hash__' of Channel object at 0x7f988a08f3d0>)
('__init__', <bound method Channel.__init__ of <realtime.channel.Channel object at 0x7f988a08f3d0>>)
('__init_subclass__', <built-in method __init_subclass__ of type object at 0x2071780>)
('__le__', <method-wrapper '__le__' of Channel object at 0x7f988a08f3d0>)
('__lt__', <method-wrapper '__lt__' of Channel object at 0x7f988a08f3d0>)
('__module__', 'realtime.channel')
('__ne__', <method-wrapper '__ne__' of Channel object at 0x7f988a08f3d0>)
('__new__', <built-in method __new__ of type object at 0x9075a0>)
('__reduce__', <built-in method __reduce__ of Channel object at 0x7f988a08f3d0>)
('__reduce_ex__', <built-in method __reduce_ex__ of Channel object at 0x7f988a08f3d0>)
('__repr__', <method-wrapper '__repr__' of Channel object at 0x7f988a08f3d0>)
('__setattr__', <method-wrapper '__setattr__' of Channel object at 0x7f988a08f3d0>)
('__sizeof__', <built-in method __sizeof__ of Channel object at 0x7f988a08f3d0>)
('__str__', <method-wrapper '__str__' of Channel object at 0x7f988a08f3d0>)
('__subclasshook__', <built-in method __subclasshook__ of type object at 0x2071780>)
('__weakref__', None)
('_join', <bound method Channel._join of <realtime.channel.Channel object at 0x7f988a08f3d0>>)
('join', <bound method Channel.join of <realtime.channel.Channel object at 0x7f988a08f3d0>>)
('joined', False)
('listeners', [CallbackListener(event='UPDATE', callback=<function c1 at 0x7f988b056f70>)])
('off', <bound method Channel.off of <realtime.channel.Channel object at 0x7f988a08f3d0>>)
('on', <bound method Channel.on of <realtime.channel.Channel object at 0x7f988a08f3d0>>)
('params', {})
('socket', <realtime.connection.Socket object at 0x7f988a0bef70>)
('topic', 'ping')

As you can see Python inspect module does not show anything on channel chan1 that might look like a method for sending. Could you elucidate on if / how one can use this library for two-way comms? Thanks.

vegabook avatar Jun 20 '22 12:06 vegabook

What are you trying to achieve? I'm not very knowledgeable with supabase realtime but I thought it only supported sending you realtime updates as they happen to the database, what functionality exists that would need you sending something to the phoenix server?

anand2312 avatar Jun 21 '22 01:06 anand2312

What are you trying to achieve? I'm not very knowledgeable with supabase realtime but I thought it only supported sending you realtime updates as they happen to the database, what functionality exists that would need you sending something to the phoenix server?

Phoenix has fully bidirectional clients for multiple languages but none for python. This seems the closest but is unidirectional. I wish to communicate from python to phoenix both ways and this library looks like a good starting point. My use case is unrelated to supabase.

vegabook avatar Jun 24 '22 11:06 vegabook

Ah got it. This repo has sort of been on the backburner for us while we worked on the other supabase - python features, hence the lack of features. Sadly I don't have the time right now to implement this feature, but I'd be happy to review and merge any PRs.

Unrelated but I am curious -- does your usecase leverage asyncio, or is it all sync code? I ask this because right now this project does some hacky stuff to provide a sync interface to the otherwise async websockets methods and I was planning on making this repo async only too.

@vegabook

anand2312 avatar Jun 24 '22 12:06 anand2312

Ah got it. This repo has sort of been on the backburner for us while we worked on the other supabase - python features, hence the lack of features. Sadly I don't have the time right now to implement this feature, but I'd be happy to review and merge any PRs.

Unrelated but I am curious -- does your usecase leverage asyncio, or is it all sync code? I ask this because right now this project does some hacky stuff to provide a sync interface to the otherwise async websockets methods and I was planning on making this repo async only too.

@vegabook

I'm quite happy to go fully async, so if you wanted to let me know where the hacks are, also happy to take a look at those.

I will take a stab at putting in some basic bidirectionality too.

vegabook avatar Jun 24 '22 17:06 vegabook

@vegabook searching for loop.run_until_complete in realtime/connection.py should get you most of the places where we wrap async calls

anand2312 avatar Jun 26 '22 15:06 anand2312

see: https://supabase.com/blog/supabase-realtime-multiplayer-general-availability

karatemir avatar Aug 19 '22 22:08 karatemir

@vegabook Have you found a viable solution for this? I'll be giving https://github.com/wwww-wwww/phxsocket a whirl this weekend, as I also need a bidirectional Phoenix Channel client.

silentsilas avatar Sep 09 '22 23:09 silentsilas

@silentsilas Did you manage to make phxsocket work with supabase?

ben-selas avatar Nov 07 '22 12:11 ben-selas

Ah I just needed to be able to run a Python script, and send/receive messages from it. Elixir's Port ended up doing the job without needing to modify my scripts to use Websockets.

silentsilas avatar Nov 07 '22 14:11 silentsilas

Is it possible to have a channe.send() script? I'm not familiar with the Websocket structures Supabase uses, I tried to run a simple "websocket.send" using websocket-client library, but couldn't figure out how the channels work.

URL = f"wss://{SUPABASE_ID}.supabase.co/realtime/v1/websocket?apikey={API_KEY}&vsn=1.0.0"
# Create a WebSocket connection to the specified URL
ws = websocket.WebSocket()
ws.connect(URL)

# Send a message over the WebSocket connection
ws.send("Hello, World!")

🔼 this is received by supabase's server (I can see the number of messages being increased) but the other Python client doesn't see these messages

    channel_1 = s.set_channel("*")
    channel_1.join().on("*", callback1)

artificial-cassiano avatar Dec 16 '22 05:12 artificial-cassiano

We need this. For Python to be on the backburner is not great. I will dedicate work to this but it's difficult to know exactly what supabase is expecting. Websockets are not hard. I wish the site had a raw Websockets version of the docs. Can we at least get that?

edit: just reversing the js client

bitnom avatar May 06 '23 02:05 bitnom

We need this. For Python to be on the backburner is not great. I will dedicate work to this but it's difficult to know exactly what supabase is expecting. Websockets are not hard. I wish the site had a raw Websockets version of the docs. Can we at least get that?

edit: just reversing the js client

Did you have any luck with this?

vegabook avatar Jul 15 '23 15:07 vegabook

Can't we just add .send method to channels.py to support that?

... await self.socket.ws_connection.send(message)

Wouldn't that do the job?

leshems avatar Jul 16 '23 09:07 leshems

I think it implemented in #68

MindsightsAI avatar Oct 20 '23 08:10 MindsightsAI

Using this blog I implemented it myself from scratch and it works very well.

vegabook avatar Oct 20 '23 22:10 vegabook