supabase / realtime-py

A Python Client for Phoenix Channels
MIT License
132 stars 55 forks source link

Unable to use channels to SEND from Python to Phoenix? #39

Open vegabook opened 2 years ago

vegabook commented 2 years ago

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.

anand2312 commented 2 years ago

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?

vegabook commented 2 years ago

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.

anand2312 commented 2 years ago

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

vegabook commented 2 years ago

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.

anand2312 commented 2 years ago

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

karatemir commented 2 years ago

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

silentsilas commented 2 years ago

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

ben-selas commented 1 year ago

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

silentsilas commented 1 year ago

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.

artificial-cassiano commented 1 year ago

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)
bitnom commented 1 year ago

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

vegabook commented 1 year ago

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?

leshems commented 1 year ago

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?

MindsightsAI commented 1 year ago

I think it implemented in #68

vegabook commented 1 year ago

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

github-actions[bot] commented 2 weeks ago

This issue is stale because it has been open for 365 days with no activity.