Python client for Phoenix channels, or alternatively, native websocket server for Phoenix?

How can I talk to Phoenix channels from a remote Python client?

I see this but it seems incomplete and I cannot get it to work.

I want to stream live data from an application that only has Python, C++ or Java clients. No javascript at all. I want to stream it into a phoenix server. How can I do this using channels since there is no native Python client. I see something from Supabase but docs all seem geared towards talking to their service.

Can someone point me to a complete example of Python talking to a Phoenix channel?

Alternative would be for Phoenix to expose a native websocket server without having to dig into Cowboy? Does this exist?

What are my options? Keep in mind must travel over http[s] as these is the only ports that are open.

Phoenix channels are a “custom” but transport agnostic protocol. They’re not “phoenix’s websocket implementation”. You can use websockets or long polling or any additional transport protocol you’d implement for talking to channels.

Phoenix does not implement websockets – just like it doesn’t handle http. Cowboy handles both of those concerns. Though I’m not sure what you’ve against integrating with cowboy. It’s a behaviour to implement with just a handful of callbacks: Nine Nines: cowboy_websocket(3)
From there you could use pubsub to talk to channels.

1 Like

The easiest way is to wrap the python client into a small CLI tool and let the elixir part talk to it via a port. You don’t need Phoenix Channels.

I fail to see why you can’t use the realtime library. You can definitely use it with a vanilla Phoenix application provided that you generate your sockets and channels. The only thing I would mention is that you’ll have to use Python 3.9.x, not 3.10.x as there’s an issue here: Python 3.10 support (loop parameter was removed from Lock()) · Issue #916 · aaugustin/websockets · GitHub

The documentation shows how you can connect to a locally hosted Phoenix application and I’ve tested it; it definitely works as intended.

1 Like

needs to go over http port 80 or 443 from behind a firewall. Don’t want to start port forwarding those as they’re needed by other appliations and ssh protocol almost certainly blocked anyway even on those ports.

I see thank you. I’ll take another look.

Elixir port is communicating via stdin/stdout.

http transport mandatory.

sorry for the pedantry, but personally I think thatt Phoenix easily exposes Cowboy’s http via routes and controllers etc in clean abstracted ways, whereas nothing analogous exists for websockets which for me is a bit of a surprise.

Probably we have some misunderstanding here. You said:

I want to stream live data from an application that only has Python, C++ or Java clients.

I took it as you have an existing application that you can access it via a python client. Now you want to access the same data from your Elixir application that you are building. So my suggestion is to wrap the python client with a small CLI tool and ship the small tool together with your elixir application. Since the CLI tool and the elixir application are running on the same machine, they can talk through port, right? Whatever means that the python client talks to the old application or how the elixir application is accessed from the outside are beside the point.

yeah unfortunately I should probably have specified earlier in the question that the client is remote, rather than pushing the http requirement to the end. Anyway that’s the way it its: has to be http.

I think I’ll try supabase’s realtime.py again. Thanks for the help.

it says it’s in alpha status, but maybe it’s a good starting point: GitHub - obmarg/chunnel: A python client for phoenix channels.

Also a list of clients Phoenix channels clients list | homesynck-server Java and CPP have a couple each to try I guess

1 Like

There’s also this: Raw Websockets in Phoenix - Binary Noggin

In case you don’t want to use the phx channel, but to roll your own protocol on top of raw websockets

1 Like

Would you mind providing the direct link to the document you are referring to?
I could not see how the link you provided show how we could connect to a locally hosted Phoenix app.

Thanks :slight_smile:

I’m using the recommended usage but nowhere does it show how to actually send messages to a channel from python.

Also using python inspect module I don’t get any channel methods that look like a send:

🐘 xxxx@yyyy:~$ 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')

Any idea on how to send using chan1 above? @beepbeepbopbop?