|
1 | | - |
2 | | -[](https://pypi.org/project/ably-pubsub-server/) |
3 | | -[](https://github.com/ably/ably-pubsub-python/blob/main/LICENSE) |
| 1 | + |
4 | 2 |
|
| 3 | +[](https://pypi.org/project/ably-pubsub-server/) |
| 4 | +[](https://github.com/ably/ably-pubsub-python/blob/main/LICENSE) |
5 | 5 |
|
6 | 6 | # Ably Pub/Sub Python SDK |
7 | 7 |
|
8 | | -Build any realtime experience using Ably’s Pub/Sub Python SDK. |
| 8 | +The Python SDK for [Ably Pub/Sub](https://ably.com/docs/basics): publish and subscribe to channels, |
| 9 | +and use message history, presence and LiveObjects, from servers. |
9 | 10 |
|
10 | | -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. |
| 11 | +## Overview |
11 | 12 |
|
12 | | -Find out more: |
| 13 | +This SDK provides the server package only, for trusted backends authenticating with an API key. |
| 14 | +There is no device package; for end-user devices, use a device SDK such as |
| 15 | +[JavaScript](https://github.com/ably/ably-pubsub-js), [Swift/Objective-C](https://github.com/ably/ably-pubsub-cocoa), |
| 16 | +[Java/Android](https://github.com/ably/ably-pubsub-java), [.NET](https://github.com/ably/ably-pubsub-dotnet) |
| 17 | +or [Flutter](https://github.com/ably/ably-pubsub-flutter). |
13 | 18 |
|
14 | | -* [Ably Pub/Sub docs.](https://ably.com/docs/basics) |
15 | | -* [Ably Pub/Sub examples.](https://ably.com/examples?product=pubsub) |
| 19 | +| Package | Use it for | Entry point | |
| 20 | +|---------|-----------|-------------| |
| 21 | +| `ably-pubsub-server` | Servers and other trusted backend environments | `create_realtime_client`, `create_http_client` | |
16 | 22 |
|
17 | | ---- |
| 23 | +## Requirements |
18 | 24 |
|
19 | | -## Getting started |
| 25 | +Python 3.8 or later (Linux, macOS and Windows). |
20 | 26 |
|
21 | | -Everything you need to get started with Ably: |
| 27 | +## Installation |
22 | 28 |
|
23 | | -* [Getting started with Pub/Sub using Python.](https://ably.com/docs/getting-started/python) |
24 | | -* [SDK Setup for Python.](https://ably.com/docs/getting-started/setup?lang=python) |
| 29 | +```bash |
| 30 | +pip install ably-pubsub-server |
| 31 | +``` |
25 | 32 |
|
26 | | ---- |
| 33 | +For channel encryption or delta compression, install the `crypto` or `vcdiff` extra, for example |
| 34 | +`pip install "ably-pubsub-server[crypto]"`. |
27 | 35 |
|
28 | | -## Supported platforms |
| 36 | +## Quick start |
29 | 37 |
|
30 | | -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). |
| 38 | +Use an API key on a server. The samples read it from the `ABLY_API_KEY` environment variable; set it |
| 39 | +to an API key from your [Ably dashboard](https://ably.com/accounts/any/apps/any/app_keys). The |
| 40 | +following code connects, subscribes to a channel, publishes a message to it and waits for the |
| 41 | +message to arrive. |
31 | 42 |
|
32 | | -The following platforms are supported: |
| 43 | +```python |
| 44 | +import asyncio |
| 45 | +import os |
33 | 46 |
|
34 | | -| Platform | Support | |
35 | | -|----------|--------------------------| |
36 | | -| Python | Python 3.7+ through 3.14 | |
| 47 | +from ably.pubsub.server import create_realtime_client |
37 | 48 |
|
38 | | -> [!NOTE] |
39 | | -> This SDK works across all major operating platforms (Linux, macOS, Windows) as long as Python 3.7+ is available. |
40 | 49 |
|
41 | | -> [!IMPORTANT] |
42 | | -> SDK versions < 2.0.0 are [deprecated](https://ably.com/docs/platform/deprecate/protocol-v1). |
| 50 | +async def main(): |
| 51 | + ably_api_key = os.environ['ABLY_API_KEY'] |
| 52 | + async with create_realtime_client(key=ably_api_key) as realtime: |
| 53 | + channel = realtime.channels.get('test-channel') |
| 54 | + received = asyncio.get_running_loop().create_future() |
43 | 55 |
|
44 | | ---- |
| 56 | + def on_message(message): |
| 57 | + if not received.done(): |
| 58 | + received.set_result(message) |
45 | 59 |
|
46 | | -## Installation |
| 60 | + # subscribe() attaches the channel and returns once it is attached. |
| 61 | + await channel.subscribe(on_message) |
| 62 | + await channel.publish('test-event', 'hello world') |
47 | 63 |
|
48 | | -To get started with your project, install the package: |
| 64 | + message = await received |
| 65 | + print(f'Received: {message.data}') |
49 | 66 |
|
50 | | -```sh |
51 | | -pip install ably-pubsub-server |
| 67 | + |
| 68 | +asyncio.run(main()) |
52 | 69 | ``` |
53 | 70 |
|
54 | | -The package installs into `ably.pubsub`, and the whole public API is reached |
55 | | -through `ably.pubsub.server`: |
| 71 | +Create the realtime client inside a running event loop. Close every client with `async with` or |
| 72 | +`await client.close()`. |
| 73 | + |
| 74 | +To publish over HTTP without a realtime connection, create an HTTP client instead: |
56 | 75 |
|
57 | 76 | ```python |
58 | | -from ably.pubsub.server import create_http_client, create_realtime_client |
| 77 | +import asyncio |
| 78 | +import os |
| 79 | + |
| 80 | +from ably.pubsub.server import create_http_client |
| 81 | + |
| 82 | + |
| 83 | +async def main(): |
| 84 | + ably_api_key = os.environ['ABLY_API_KEY'] |
| 85 | + async with create_http_client(key=ably_api_key) as client: |
| 86 | + await client.channels.get('test-channel').publish('test-event', 'hello world') |
| 87 | + |
| 88 | + |
| 89 | +asyncio.run(main()) |
59 | 90 | ``` |
60 | 91 |
|
61 | | -Clients are built by these factories rather than by constructing a class, so |
62 | | -that the package a client comes from names the side your application runs on. |
63 | | -Annotate against the prototypes they return, `PubSubHttpClient` and |
64 | | -`PubSubRealtimeClient`. |
| 92 | +For code without an event loop, `ably.pubsub.server.sync.create_http_client` creates a synchronous |
| 93 | +HTTP client. There is no synchronous realtime client. |
65 | 94 |
|
66 | | -The synchronous, HTTP-only flavour lives alongside it: |
| 95 | +> [!IMPORTANT] |
| 96 | +> Server clients must authenticate with an API key. To use a token instead, see [Server clients and token authentication](https://ably.com/docs/auth/token#server-token-auth). |
| 97 | +
|
| 98 | +The following endpoint issues token requests. Devices using a device SDK set `authUrl` to its URL, |
| 99 | +for example `https://your-server.example.com/ably-auth`: |
67 | 100 |
|
68 | 101 | ```python |
69 | | -from ably.pubsub.server.sync import create_http_client |
| 102 | +import os |
| 103 | +from contextlib import asynccontextmanager |
| 104 | + |
| 105 | +from ably.pubsub.server import create_http_client |
| 106 | +from fastapi import FastAPI |
| 107 | + |
| 108 | +ably_api_key = os.environ['ABLY_API_KEY'] |
| 109 | +client = create_http_client(key=ably_api_key) |
| 110 | + |
| 111 | + |
| 112 | +@asynccontextmanager |
| 113 | +async def lifespan(app): |
| 114 | + async with client: # closes the client when the app shuts down |
| 115 | + yield |
| 116 | + |
| 117 | + |
| 118 | +app = FastAPI(lifespan=lifespan) |
| 119 | + |
| 120 | + |
| 121 | +@app.get('/ably-auth') |
| 122 | +async def ably_auth(): |
| 123 | + # Authenticate the caller first. Grant only the operations this user needs. |
| 124 | + user_id = 'me' # the signed-in user's ID, from your session |
| 125 | + token_request = await client.auth.create_token_request({ |
| 126 | + 'client_id': user_id, |
| 127 | + 'capability': {'test-channel': ['publish', 'subscribe', 'history']}, |
| 128 | + }) |
| 129 | + return token_request.to_dict() |
70 | 130 | ``` |
71 | 131 |
|
72 | | -Both `ably` and `ably.pubsub` are [namespace packages](https://peps.python.org/pep-0420/) |
73 | | -shared with the other `ably-*` distributions, so neither exports anything |
74 | | -itself — always import from `ably.pubsub.server`. |
| 132 | +See [token authentication](https://ably.com/docs/auth/token) for how devices use it. |
| 133 | + |
| 134 | +## Migrating from 3.x |
| 135 | + |
| 136 | +Version 4.0 renames the package and creates clients with factory functions. |
| 137 | + |
| 138 | +| | 3.x | 4.0 | |
| 139 | +|---|---|---| |
| 140 | +| Package | `ably` | `ably-pubsub-server` | |
| 141 | +| Import | `from ably import AblyRealtime, AblyRest` | `from ably.pubsub.server import create_realtime_client, create_http_client` | |
| 142 | +| Realtime client | `AblyRealtime('key')` | `create_realtime_client(key='key')` | |
| 143 | +| HTTP client | `AblyRest('key')` | `create_http_client(key='key')` | |
| 144 | + |
| 145 | +See [UPDATING.md](https://github.com/ably/ably-pubsub-python/blob/main/UPDATING.md#version-3x-to-400) for all other changes. |
| 146 | + |
| 147 | +## Common configuration |
75 | 148 |
|
76 | | -> [!NOTE] |
77 | | -Install [Python](https://www.python.org/downloads/) version 3.8 or greater. |
| 149 | +Pass options to both factories as keyword arguments. |
78 | 150 |
|
79 | | -## Usage |
| 151 | +### Endpoint |
80 | 152 |
|
81 | | -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. |
| 153 | +`endpoint` selects the Ably host for HTTP requests and the realtime connection. It replaces the |
| 154 | +3.x `environment`, `rest_host` and `realtime_host` options. |
82 | 155 |
|
83 | 156 | ```python |
84 | | -from ably.pubsub.server import create_realtime_client |
| 157 | +ably_api_key = os.environ['ABLY_API_KEY'] |
| 158 | +realtime = create_realtime_client(key=ably_api_key, endpoint='nonprod:sandbox') |
| 159 | +``` |
| 160 | + |
| 161 | +- Never put a port in `endpoint`: `localhost:8080` is not a hostname and does not resolve. |
| 162 | +- Set the port with `tls_port`, or with `port` when TLS is off. |
| 163 | +- With TLS off (`tls=False`), use token authentication. |
| 164 | + |
| 165 | +See [UPDATING.md](https://github.com/ably/ably-pubsub-python/blob/main/UPDATING.md#deprecated-and-duplicate-options-are-removed) for how the old |
| 166 | +options map to `endpoint`. |
| 167 | + |
| 168 | +### Logging |
| 169 | + |
| 170 | +The SDK logs through Python's standard [`logging`](https://docs.python.org/3/library/logging.html) |
| 171 | +module, to the `ably.pubsub` logger. Set the level and add a handler on that logger: |
85 | 172 |
|
86 | | -# Initialize Ably Realtime client |
87 | | -async with create_realtime_client(key='your-ably-api-key', client_id='me') as realtime_client: |
88 | | - # Wait for connection to be established |
89 | | - await realtime_client.connection.once_async('connected') |
90 | | - print('Connected to Ably') |
91 | | - |
92 | | - # Get a reference to the 'test-channel' channel |
93 | | - channel = realtime_client.channels.get('test-channel') |
94 | | - |
95 | | - # Subscribe to all messages published to this channel |
96 | | - def on_message(message): |
97 | | - print(f'Received message: {message.data}') |
98 | | - |
99 | | - await channel.subscribe(on_message) |
100 | | - |
101 | | - # Publish a test message to the channel |
102 | | - await channel.publish('test-event', 'hello world') |
| 173 | +```python |
| 174 | +import logging |
| 175 | + |
| 176 | +logger = logging.getLogger('ably.pubsub') |
| 177 | +logger.setLevel(logging.DEBUG) |
| 178 | +logger.addHandler(logging.StreamHandler()) |
103 | 179 | ``` |
104 | 180 |
|
105 | | -### LiveObjects |
| 181 | +The 3.x `log_level` option is [removed](https://github.com/ably/ably-pubsub-python/blob/main/UPDATING.md#the-log_level-option-is-removed). |
| 182 | + |
| 183 | +### Delta compression |
106 | 184 |
|
107 | | -LiveObjects keeps shared, mutable state on a channel: maps and counters that every client |
108 | | -attached to it reads, updates and subscribes to. The channel needs the object modes, and |
109 | | -`channel.object.get()` attaches it, waits for the objects to sync, and returns the root map. |
110 | | -Values are read through a typed view of their path — `as_live_map()`, `as_live_counter()` or |
111 | | -`as_primitive()` — and reads are synchronous; writes are awaited. |
| 185 | +To receive [delta-compressed](https://ably.com/docs/channels/options/deltas) messages: |
| 186 | + |
| 187 | +- Install the `vcdiff` extra. |
| 188 | +- Pass an `AblyVCDiffDecoder` to the realtime client as `vcdiff_decoder`. |
| 189 | +- Set the `delta` channel parameter to `vcdiff`. |
112 | 190 |
|
113 | 191 | ```python |
114 | | -from ably.pubsub.server import ChannelMode, ChannelOptions, create_realtime_client |
115 | | -from ably.pubsub.server.liveobjects import LiveCounter, LiveMap |
| 192 | +import os |
116 | 193 |
|
117 | | -async with create_realtime_client(key='your-ably-api-key') as realtime_client: |
118 | | - channel = realtime_client.channels.get( |
119 | | - 'my-objects', |
120 | | - ChannelOptions(modes=[ChannelMode.OBJECT_SUBSCRIBE, ChannelMode.OBJECT_PUBLISH]), |
121 | | - ) |
| 194 | +from ably.pubsub.server import AblyVCDiffDecoder, ChannelOptions, create_realtime_client |
122 | 195 |
|
123 | | - # Attach, wait for the objects to sync, and get the root map |
124 | | - root = await channel.object.get() |
| 196 | +ably_api_key = os.environ['ABLY_API_KEY'] |
| 197 | +realtime = create_realtime_client(key=ably_api_key, vcdiff_decoder=AblyVCDiffDecoder()) |
| 198 | +channel = realtime.channels.get('test-channel', ChannelOptions(params={'delta': 'vcdiff'})) |
| 199 | +``` |
125 | 200 |
|
126 | | - # Create objects by setting them on the root |
127 | | - await root.set('visits', LiveCounter.create(0)) |
128 | | - await root.set('profile', LiveMap.create({'name': 'Alice', 'theme': 'dark'})) |
| 201 | +## Live Objects |
129 | 202 |
|
130 | | - # Read through a typed view of the path |
131 | | - visits = root.get('visits').as_live_counter() |
132 | | - print(visits.value()) # 0.0 |
133 | | - print(root.at('profile.name').as_primitive().value(str)) # Alice |
| 203 | +Use [LiveObjects](https://ably.com/docs/liveobjects) through `channel.object`. Get the channel with |
| 204 | +the `OBJECT_SUBSCRIBE` and `OBJECT_PUBLISH` modes. `channel.object.get()` waits for the objects to |
| 205 | +sync and returns the root map. |
134 | 206 |
|
135 | | - # Subscribe to changes at a path |
136 | | - def on_change(event): |
137 | | - print(f'{event.object.path()} changed') |
| 207 | +```python |
| 208 | +import asyncio |
| 209 | +import os |
138 | 210 |
|
139 | | - subscription = root.get('visits').subscribe(on_change) |
| 211 | +from ably.pubsub.server import ChannelMode, ChannelOptions, create_realtime_client |
| 212 | +from ably.pubsub.server.liveobjects import LiveCounter |
140 | 213 |
|
141 | | - # Mutate |
142 | | - await visits.increment(5) |
143 | 214 |
|
144 | | - # Batch several writes into a single message |
145 | | - async with root.get('profile').as_live_map().batch() as profile: |
146 | | - profile.set('name', 'Bob') |
147 | | - profile.remove('theme') |
| 215 | +async def main(): |
| 216 | + ably_api_key = os.environ['ABLY_API_KEY'] |
| 217 | + async with create_realtime_client(key=ably_api_key) as realtime: |
| 218 | + channel = realtime.channels.get( |
| 219 | + 'test-channel', |
| 220 | + ChannelOptions(modes=[ChannelMode.OBJECT_SUBSCRIBE, ChannelMode.OBJECT_PUBLISH]), |
| 221 | + ) |
| 222 | + root = await channel.object.get() |
148 | 223 |
|
149 | | - subscription.unsubscribe() |
150 | | -``` |
| 224 | + await root.set('visits', LiveCounter.create(0)) |
| 225 | + visits = root.get('visits').as_live_counter() |
| 226 | + await visits.increment(5) |
| 227 | + print(f'Visits: {visits.value()}') |
151 | 228 |
|
152 | | -## Releases |
153 | 229 |
|
154 | | -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). |
| 230 | +asyncio.run(main()) |
| 231 | +``` |
| 232 | + |
| 233 | +## Known limitations |
155 | 234 |
|
156 | | ---- |
| 235 | +- Connection recovery (the 3.x `recover` option) is not supported. |
| 236 | +- Subscription filters and derived channels are not supported. |
| 237 | +- Realtime presence has no `history()`. Use `channel.presence.history()` on an HTTP client. |
| 238 | +- [Token revocation](https://ably.com/docs/auth/revocation) is not supported. |
| 239 | +- A Python client cannot be a [push notification](https://ably.com/docs/push) target. The push |
| 240 | + admin API is supported. |
157 | 241 |
|
158 | | -## Contribute |
| 242 | +## Further reading |
159 | 243 |
|
160 | | -Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines to contribute to Ably. |
| 244 | +- [Getting started with Pub/Sub using Python](https://ably.com/docs/getting-started/python) |
| 245 | +- [Ably Pub/Sub documentation](https://ably.com/docs/basics) and [examples](https://ably.com/examples?product=pubsub) |
| 246 | +- [Token authentication](https://ably.com/docs/auth/token) |
| 247 | +- [UPDATING.md](https://github.com/ably/ably-pubsub-python/blob/main/UPDATING.md): migrating from 3.x |
| 248 | +- [CHANGELOG.md](https://github.com/ably/ably-pubsub-python/blob/main/CHANGELOG.md): release notes; all Ably releases are listed on [changelog.ably.com](https://changelog.ably.com) |
161 | 249 |
|
162 | | ---- |
| 250 | +## Contributing |
163 | 251 |
|
164 | | -## Support, feedback, and troubleshooting |
| 252 | +Read [CONTRIBUTING.md](https://github.com/ably/ably-pubsub-python/blob/main/CONTRIBUTING.md) before contributing; it also describes the release |
| 253 | +process. |
165 | 254 |
|
166 | | -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. |
| 255 | +## Support, feedback and troubleshooting |
167 | 256 |
|
168 | | -### Full Realtime support unavailable |
| 257 | +For help, visit Ably's [support page](https://ably.com/support). Report bugs and compatibility |
| 258 | +problems on [GitHub Issues](https://github.com/ably/ably-pubsub-python/issues). |
169 | 259 |
|
170 | | -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). |
| 260 | +## License |
171 | 261 |
|
| 262 | +Licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/ably/ably-pubsub-python/blob/main/LICENSE). |
0 commit comments