Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 196 additions & 105 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,171 +1,262 @@
![Ably Pub/Sub Python Header](images/pythonSDK-github.png)
[![PyPI version](https://badge.fury.io/py/ably-pubsub-server.svg)](https://pypi.org/project/ably-pubsub-server/)
[![License](https://img.shields.io/github/license/ably/ably-pubsub-python)](https://github.com/ably/ably-pubsub-python/blob/main/LICENSE)

[![PyPI: ably-pubsub-server](https://img.shields.io/pypi/v/ably-pubsub-server?label=ably-pubsub-server)](https://pypi.org/project/ably-pubsub-server/)
[![License](https://badgen.net/github/license/ably/ably-pubsub-python)](./LICENSE)

# Ably Pub/Sub Python SDK

Build any realtime experience using Ably’s Pub/Sub Python SDK.
The Python SDK for [Ably Pub/Sub](https://ably.com/docs/basics): publish and subscribe to channels,
and use message history, presence and LiveObjects, from servers.

Ably Pub/Sub provides flexible APIs that deliver features such as pub-sub messaging, message history, presence, and push notifications. Utilizing Ably’s realtime messaging platform, applications benefit from its highly performant, reliable, and scalable infrastructure.
## Overview

Find out more:
This SDK provides the server package only, for trusted backends authenticating with an API key.
There is no device package; for end-user devices, use a device SDK such as
[JavaScript](https://github.com/ably/ably-pubsub-js), [Swift/Objective-C](https://github.com/ably/ably-pubsub-cocoa),
[Java/Android](https://github.com/ably/ably-pubsub-java), [.NET](https://github.com/ably/ably-pubsub-dotnet)
or [Flutter](https://github.com/ably/ably-pubsub-flutter).

* [Ably Pub/Sub docs.](https://ably.com/docs/basics)
* [Ably Pub/Sub examples.](https://ably.com/examples?product=pubsub)
| Package | Use it for | Entry point |
|---------|-----------|-------------|
| `ably-pubsub-server` | Servers and other trusted backend environments | `create_realtime_client`, `create_http_client` |

---
## Requirements

## Getting started
Python 3.8 or later, on Linux, macOS or Windows.

Everything you need to get started with Ably:
## Installation

* [Getting started with Pub/Sub using Python.](https://ably.com/docs/getting-started/python)
* [SDK Setup for Python.](https://ably.com/docs/getting-started/setup?lang=python)
```bash
pip install ably-pubsub-server
```

---
Add the `crypto` extra for channel encryption, or the `vcdiff` extra for delta compression:

## Supported platforms
```bash
pip install "ably-pubsub-server[crypto]"
pip install "ably-pubsub-server[vcdiff]"
```

Ably aims to support a wide range of platforms. If you experience any compatibility issues, open an issue in the repository or contact [Ably support](https://ably.com/support).
### Migrating from 3.x

The following platforms are supported:
Version 4.0 renames the package and creates clients with factory functions.

| Platform | Support |
|----------|--------------------------|
| Python | Python 3.7+ through 3.14 |
| | 3.x | 4.0 |
|---|---|---|
| Package | `ably` | `ably-pubsub-server` |
| Import | `from ably import AblyRealtime, AblyRest` | `from ably.pubsub.server import create_realtime_client, create_http_client` |
| Realtime client | `AblyRealtime('key')` | `create_realtime_client(key='key')` |
| HTTP client | `AblyRest('key')` | `create_http_client(key='key')` |

> [!NOTE]
> This SDK works across all major operating platforms (Linux, macOS, Windows) as long as Python 3.7+ is available.
See [UPDATING.md](./UPDATING.md#version-3x-to-400) for all other changes.

> [!IMPORTANT]
> SDK versions < 2.0.0 are [deprecated](https://ably.com/docs/platform/deprecate/protocol-v1).
## Quick start

---
Use an API key on a server. The following code connects, subscribes to a channel, publishes a
message to it and waits for the message to arrive.

## Installation
```python
import asyncio

To get started with your project, install the package:
from ably.pubsub.server import create_realtime_client

```sh
pip install ably-pubsub-server

async def main():
async with create_realtime_client(key='xxxxxx.yyyyyy:zzzzzzzz') as realtime:
channel = realtime.channels.get('test-channel')
received = asyncio.get_running_loop().create_future()

def on_message(message):
if not received.done():
received.set_result(message)

# subscribe() attaches the channel and returns once it is attached.
await channel.subscribe(on_message)
await channel.publish('test-event', 'hello world')

message = await received
print(f'Received: {message.data}')


asyncio.run(main())
```

The package installs into `ably.pubsub`, and the whole public API is reached
through `ably.pubsub.server`:
To publish over HTTP without a realtime connection, create an HTTP client instead:

```python
from ably.pubsub.server import create_http_client, create_realtime_client
```
import asyncio

from ably.pubsub.server import create_http_client


async def main():
async with create_http_client(key='xxxxxx.yyyyyy:zzzzzzzz') as client:
await client.channels.get('test-channel').publish('test-event', 'hello world')


Clients are built by these factories rather than by constructing a class, so
that the package a client comes from names the side your application runs on.
Annotate against the prototypes they return, `PubSubHttpClient` and
`PubSubRealtimeClient`.
asyncio.run(main())
```

The synchronous, HTTP-only flavour lives alongside it:
To publish from code that does not run an event loop, use the synchronous HTTP client:

```python
from ably.pubsub.server.sync import create_http_client

with create_http_client(key='xxxxxx.yyyyyy:zzzzzzzz') as client:
client.channels.get('test-channel').publish('test-event', 'hello world')
```

Your server also issues the tokens that devices authenticate with. Return a token request from
the endpoint your devices use as their `authUrl`:

```python
import asyncio
import json

from ably.pubsub.server import create_http_client


async def create_token_request_json(client_id):
async with create_http_client(key='xxxxxx.yyyyyy:zzzzzzzz') as client:
# Authenticate the caller first. Grant only the operations this user needs.
token_request = await client.auth.create_token_request({
'client_id': client_id,
'capability': {'test-channel': ['publish', 'subscribe', 'history']},
})
# Return this JSON with the content type application/json.
return json.dumps(token_request.to_dict())


print(asyncio.run(create_token_request_json('me')))
```

Both `ably` and `ably.pubsub` are [namespace packages](https://peps.python.org/pep-0420/)
shared with the other `ably-*` distributions, so neither exports anything
itself — always import from `ably.pubsub.server`.
See [token authentication](https://ably.com/docs/auth/token) for how devices use it.

## Common configuration

> [!NOTE]
Install [Python](https://www.python.org/downloads/) version 3.8 or greater.
Pass options to both factories as keyword arguments.

## Usage
### Endpoint

The following code connects to Ably's realtime messaging service, subscribes to a channel to receive messages, and publishes a test message to that same channel.
`endpoint` selects the Ably host for HTTP requests and the realtime connection. It replaces the
3.x `environment`, `rest_host` and `realtime_host` options.

```python
from ably.pubsub.server import create_realtime_client
realtime = create_realtime_client(key='xxxxxx.yyyyyy:zzzzzzzz', endpoint='nonprod:sandbox')
```

- Never put a port in `endpoint`: `localhost:8080` is not a hostname and does not resolve.
- Set the port with `tls_port`, or with `port` when TLS is off.
- With TLS off (`tls=False`), use token authentication.

See [UPDATING.md](./UPDATING.md#deprecated-and-duplicate-options-are-removed) for how the old
options map to `endpoint`.

# Initialize Ably Realtime client
async with create_realtime_client(key='your-ably-api-key', client_id='me') as realtime_client:
# Wait for connection to be established
await realtime_client.connection.once_async('connected')
print('Connected to Ably')

# Get a reference to the 'test-channel' channel
channel = realtime_client.channels.get('test-channel')

# Subscribe to all messages published to this channel
def on_message(message):
print(f'Received message: {message.data}')

await channel.subscribe(on_message)

# Publish a test message to the channel
await channel.publish('test-event', 'hello world')
### Logging

The SDK logs through Python's standard [`logging`](https://docs.python.org/3/library/logging.html)
module, to the `ably.pubsub` logger. Its records follow your application's logging configuration.
Set the level and add a handler on that logger:

```python
import logging

logger = logging.getLogger('ably.pubsub')
logger.setLevel(logging.DEBUG)
logger.addHandler(logging.StreamHandler())
```

### LiveObjects
The 3.x `log_level` option is [removed](./UPDATING.md#the-log_level-option-is-removed).

### Delta compression

To receive [delta-compressed](https://ably.com/docs/channels/options/deltas) messages:

LiveObjects keeps shared, mutable state on a channel: maps and counters that every client
attached to it reads, updates and subscribes to. The channel needs the object modes, and
`channel.object.get()` attaches it, waits for the objects to sync, and returns the root map.
Values are read through a typed view of their path — `as_live_map()`, `as_live_counter()` or
`as_primitive()` — and reads are synchronous; writes are awaited.
- Install the `vcdiff` extra.
- Pass an `AblyVCDiffDecoder` to the realtime client as `vcdiff_decoder`.
- Set the `delta` channel parameter to `vcdiff`.

```python
from ably.pubsub.server import ChannelMode, ChannelOptions, create_realtime_client
from ably.pubsub.server.liveobjects import LiveCounter, LiveMap
from ably.pubsub.server import AblyVCDiffDecoder, ChannelOptions, create_realtime_client

realtime = create_realtime_client(key='xxxxxx.yyyyyy:zzzzzzzz', vcdiff_decoder=AblyVCDiffDecoder())
channel = realtime.channels.get('test-channel', ChannelOptions(params={'delta': 'vcdiff'}))
```

async with create_realtime_client(key='your-ably-api-key') as realtime_client:
channel = realtime_client.channels.get(
'my-objects',
ChannelOptions(modes=[ChannelMode.OBJECT_SUBSCRIBE, ChannelMode.OBJECT_PUBLISH]),
)
## Live Objects

# Attach, wait for the objects to sync, and get the root map
root = await channel.object.get()
[Ably LiveObjects](https://ably.com/docs/liveobjects) provides realtime, collaborative data
structures that synchronize state across clients. Use it through `channel.object`:

# Create objects by setting them on the root
await root.set('visits', LiveCounter.create(0))
await root.set('profile', LiveMap.create({'name': 'Alice', 'theme': 'dark'}))
- Get the channel with the `OBJECT_SUBSCRIBE` and `OBJECT_PUBLISH` modes.
- Call `channel.object.get()`. It attaches the channel, waits for the objects to sync and returns
the root map.

# Read through a typed view of the path
visits = root.get('visits').as_live_counter()
print(visits.value()) # 0.0
print(root.at('profile.name').as_primitive().value(str)) # Alice
```python
import asyncio

from ably.pubsub.server import ChannelMode, ChannelOptions, create_realtime_client
from ably.pubsub.server.liveobjects import LiveCounter

# Subscribe to changes at a path
def on_change(event):
print(f'{event.object.path()} changed')

subscription = root.get('visits').subscribe(on_change)
async def main():
async with create_realtime_client(key='xxxxxx.yyyyyy:zzzzzzzz') as realtime:
channel = realtime.channels.get(
'test-channel',
ChannelOptions(modes=[ChannelMode.OBJECT_SUBSCRIBE, ChannelMode.OBJECT_PUBLISH]),
)
root = await channel.object.get()

# Mutate
await visits.increment(5)
await root.set('visits', LiveCounter.create(0))
visits = root.get('visits').as_live_counter()
await visits.increment(5)
print(f'Visits: {visits.value()}')

# Batch several writes into a single message
async with root.get('profile').as_live_map().batch() as profile:
profile.set('name', 'Bob')
profile.remove('theme')

subscription.unsubscribe()
asyncio.run(main())
```

## Releases
Read a value through a typed view: `as_live_map()`, `as_live_counter()` or `as_primitive()`.
Reads are synchronous; writes are awaited. See the
[LiveObjects documentation](https://ably.com/docs/liveobjects).

## Platform notes

### Asynchronous and synchronous clients

- The clients from `ably.pubsub.server` use `asyncio`. Create the realtime client inside a running
event loop.
- Close each client with `async with` or `await client.close()`.
- The synchronous client from `ably.pubsub.server.sync` covers HTTP only. There is no synchronous
realtime client.

The [CHANGELOG.md](https://github.com/ably/ably-pubsub-python/blob/main/CHANGELOG.md) contains details of the latest releases for this SDK. You can also view all Ably releases on [changelog.ably.com](https://changelog.ably.com).
## Known limitations

---
- Connection recovery (the 3.x `recover` option) is not supported.
- Subscription filters and derived channels are not supported.
- Realtime presence has no `history()`. Use `channel.presence.history()` on an HTTP client.
- [Token revocation](https://ably.com/docs/auth/revocation) is not supported.
- A Python client cannot be a [push notification](https://ably.com/docs/push) target. The push
admin API is supported.

## Contribute
## Further reading

Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines to contribute to Ably.
- [Getting started with Pub/Sub using Python](https://ably.com/docs/getting-started/python)
- [Ably Pub/Sub documentation](https://ably.com/docs/basics) and [examples](https://ably.com/examples?product=pubsub)
- [Token authentication](https://ably.com/docs/auth/token)
- [UPDATING.md](./UPDATING.md): migrating from 3.x
- [CHANGELOG.md](./CHANGELOG.md): release notes; all Ably releases are listed on [changelog.ably.com](https://changelog.ably.com)

---
## Contributing

## Support, feedback, and troubleshooting
Read [CONTRIBUTING.md](./CONTRIBUTING.md) before contributing; it also describes the release
process.

For help or technical support, visit Ably's [support page](https://ably.com/support) or [GitHub Issues](https://github.com/ably/ably-pubsub-python/issues) for community-reported bugs and discussions.
## Support, feedback and troubleshooting

### Full Realtime support unavailable
For help, visit Ably's [support page](https://ably.com/support). Report bugs and compatibility
problems on [GitHub Issues](https://github.com/ably/ably-pubsub-python/issues).

This SDK currently supports only [Ably REST](https://ably.com/docs/rest) and basic realtime message subscriptions. To access full [Ably Realtime](https://ably.com/docs/realtime) features in Python, consider using the [MQTT adapter](https://ably.com/docs/mqtt).
## License

Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE).
Loading