actioncable-client 0.3.1a2
ActionCable Client for Python 3
Requires Python: >=3.9

# ActionCable Client - A Python 3 client for Rails' Action Cable

[![pipeline status](https://gitlab.com/liant-sasu/actioncable-client/badges/main/pipeline.svg?ignore_skipped=true)](https://gitlab.com/liant-sasu/actioncable-client/-/commits/main)
[![coverage report](https://gitlab.com/liant-sasu/actioncable-client/badges/main/coverage.svg)](https://gitlab.com/liant-sasu/actioncable-client/-/commits/main)
[![Latest Release](https://gitlab.com/liant-sasu/actioncable-client/-/badges/release.svg)](https://gitlab.com/liant-sasu/actioncable-client/-/releases)

This library handles the connections and subscriptions and monitors the connection. It removes the underlaying websocket layer.

Currently authentication is not managed by this library.

## Credits

The project originate [this](https://github.com/tobiasfeistmantl/python-actioncable-zwei) that was
stalled for more than 5 years.

## ROADMAP

See [Roadmap file](https://gitlab.com/liant-sasu/actioncable-client/-/commits/main/ROADMAP.md)

## Get started

```shell
sudo pip3 install actioncable_client
```

## Example usage

### Setup the connection

Setup the connection to your Action Cable server.

```python
from actioncable_client.connection import Connection
# We advise you to use OAuth2 connections, but you can use Bearer Token or any other authentication mechanism.
# Here we use the [deviceFlow](https://www.rfc-editor.org/rfc/rfc8628)
from requests_oauth2client import OAuth2Client, OAuth2DeviceCodeAuth
from urllib.parse import urlparse

# just to get URL from string
basehost = 'https://example.com'
basehost_uri = urlparse(basehost)
secured = basehost_uri.schema == 'https'

# Create the authentication for OAuth2
client = OAuth2Client(
    token_endpoint='https://example.com/oauth/token',
    device_authorization_endpoint='https://example.com/oauth/authorize_device',
    client_id='7afe93b6-6063-11ef-b51a-9ea0527474b9', # The UUID of your application
    # If you accept testing agains not https servers, don't forget to set this to true
    # This is because requests_oauth2client do not accept custom ports (and they are right)
    testing=(not secured or (basehost_uri.port is not None and basehost.port != 443))
)
# Get your device code to display This is linked to the Device Flow
# I put a device_id which will get authorization from the resource_owner to acces with a given scope
device_auth_resp = self._client.authorize_device(device_id='7afe93b6-6063-11ef-b51a-9ea0527474b9', scopes='my_scope')

connection = Connection(
    url=f'{'wss' if secured else 'ws'}://{basehost_uri.netloc}/cable',
    # This will force the origin. If header is a BaseOAuth2RenewableTokenAuth subclass like
    # OAuth2DeviceCodeAuth is, then it will take the auth server as origin by default (so let it blank)
    origin='https://example2.com',
    header=OAuth2DeviceCodeAuth(client, device_auth_resp)
)
connection.connect()
```

### Subscribe to a channel

```python
from actioncable_client.subscription import Subscription

subscription = Subscription(connection, identifier={'channel': 'YourChannelCLassName'})

def on_receive(message: dict):
  print('New message arrived!')
  print('Action: {} | Data: {}'.format(message['action'], message['data']))

subscription.on_receive(callback=on_receive)
subscription.create()
```

### Send data

```python
from actioncable_client.message import Message

message = Message(action='update_something', data={'something': 'important'})

subscription.send(message)
```

### Unsubscribe

```python
subscription.remove()
```

### Using ActionCable Channel's interface

Create a Channel class inheriting from ActionBaseChannel class
```python
from actioncable_client.action_base_channel import ActionBaseChannel

from typing import Any
import logging
logger = logging.getLogger(__name__)

class MyChannel(ActionBaseChannel):
    def hello(self, data: dict[str, Any]):
        logger.info(f"Just received a Hello command with: {data}")
    def do_hello(self, data={}):
        self.transmit(action="hello", data=data)
```

Then after or before the connection just instantiate it.

```python
my_channel = AncDeviceChannel(self._cable_connection)
```

It will subscribe to the ActionCable server on the channel named 'MyChannel'. You'll get all the `hello`
actions from the server.
And by calling the `do_hello` with some data you'll transmit the data in parameter to the action you
specified (here `hello` on the server).

```python
my_channel.do_hello({'from': 'My device'})
```

### Close connection

```python
connection.disconnect()
```

## Development

Pull it up!

## You need help?

Ask a question on [StackOverflow](https://stackoverflow.com/) with the tag 'actioncable-client'.

## Contribution

Create pull requests on GitLab and help us to improve this package. There are some guidelines to follow:

* Follow the conventions
* Test all your implementations
* Document methods which aren't self-explaining
* try to follow the [Roadmap](https://gitlab.com/liant-sasu/actioncable-client/-/commits/main/ROADMAP.md)

Copyright (c) 2024 Liant SASU, MIT license