diff --git a/README.md b/README.md index ff091307..a42450a6 100644 --- a/README.md +++ b/README.md @@ -1,364 +1,99 @@ -ably-python ------------ +![Ably Pub/Sub Python Header](images/pythonSDK-github.png) +[![PyPI version](https://badge.fury.io/py/ably.svg)](https://pypi.org/project/ably/) +[![License](https://img.shields.io/github/license/ably/ably-python)](https://github.com/ably/ably-python/blob/main/LICENSE) -![.github/workflows/check.yml](https://github.com/ably/ably-python/workflows/.github/workflows/check.yml/badge.svg) -[![Features](https://github.com/ably/ably-python/actions/workflows/features.yml/badge.svg)](https://github.com/ably/ably-python/actions/workflows/features.yml) -[![PyPI version](https://badge.fury.io/py/ably.svg)](https://badge.fury.io/py/ably) -## Overview +# Ably Pub/Sub Python SDK -This is a Python client library for Ably. The library currently targets the [Ably 2.0 client library specification](https://sdk.ably.com/builds/ably/specification/main/features/). +Build any realtime experience using Ably’s Pub/Sub Python SDK. -## Running example +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. -```python -import asyncio -from ably import AblyRest +Find out more: -async def main(): - async with AblyRest('api:key') as ably: - channel = ably.channels.get("channel_name") +* [Ably Pub/Sub docs.](https://ably.com/docs/basics) +* [Ably Pub/Sub examples.](https://ably.com/examples?product=pubsub) -if __name__ == "__main__": - asyncio.run(main()) -``` +--- -## Installation +## Getting started -### Via PyPI +Everything you need to get started with Ably: -The client library is available as a [PyPI](https://pypi.python.org/pypi/ably) package. +* [Getting started with Pub/Sub using Python.](https://ably.com/docs/getting-started/python) -``` -pip install ably -``` +--- -Or, if you need encryption features: +## Supported platforms -``` -pip install 'ably[crypto]' -``` +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). -### Via GitHub +The following platforms are supported: -``` -git clone --recurse-submodules https://github.com/ably/ably-python.git -cd ably-python -python setup.py install -``` - -## Upgrade / Migration Guide - -Please see our [Upgrade / Migration Guide](UPDATING.md) for notes on changes you need to make to your code to update it to use the new APIs when migrating from older versions. - -## Usage - -### Using the Rest API +| Platform | Support | +|----------|---------| +| Python | Python 3.7+ through 3.13 | -> [!NOTE] -> Please note that since version 2.0.2 we also provide a synchronous variant of the REST interface which is can be accessed as `from ably.sync import AblyRestSync`. +> [!NOTE] +> This SDK works across all major operating platforms (Linux, macOS, Windows) as long as Python 3.7+ is available. -All examples assume a client and/or channel has been created in one of the following ways: +> [!IMPORTANT] +> SDK versions < 2.0.0-beta.6 will be [deprecated](https://ably.com/docs/platform/deprecate/protocol-v1) from November 1, 2025. -With closing the client manually: -```python -from ably import AblyRest +--- -async def main(): - client = AblyRest('api:key') - channel = client.channels.get('channel_name') - await client.close() -``` - -When using the client as a context manager, this will ensure that client is properly closed -while leaving the `with` block: - -```python -from ably import AblyRest - -async def main(): - async with AblyRest('api:key') as ably: - channel = ably.channels.get("channel_name") -``` - -You can define the logging level for the whole library, and override for a -specific module: -```python -import logging -import ably - -logging.getLogger('ably').setLevel(logging.WARNING) -logging.getLogger('ably.rest.auth').setLevel(logging.INFO) -``` -You need to add a handler to see any output: -```python -logger = logging.getLogger('ably') -logger.addHandler(logging.StreamHandler()) -``` -### Publishing a message to a channel - -```python -await channel.publish('event', 'message') -``` - -If you need to add metadata when publishing a message, you can use the `Message` constructor to create a message with custom fields: -```python -from ably.types.message import Message - -message_object = Message(name="message_name", - data="payload", - extras={"headers": {"metadata_key": "metadata_value"}}) -await channel.publish(message_object) -``` - -### Querying the History - -```python -message_page = await channel.history() # Returns a PaginatedResult -message_page.items # List with messages from this page -message_page.has_next() # => True, indicates there is another page -next_page = await message_page.next() # Returns a next page -next_page.items # List with messages from the second page -``` - -### Current presence members on a channel - -```python -members_page = await channel.presence.get() # Returns a PaginatedResult -members_page.items -members_page.items[0].client_id # client_id of first member present -``` - -### Querying the presence history - -```python -presence_page = await channel.presence.history() # Returns a PaginatedResult -presence_page.items -presence_page.items[0].client_id # client_id of first member -``` - -### Getting the channel status - -```python -channel_status = await channel.status() # Returns a ChannelDetails object -channel_status.channel_id # Channel identifier -channel_status.status # ChannelStatus object -channel_status.status.occupancy # ChannelOccupancy object -channel_status.status.occupancy.metrics # ChannelMetrics object -``` - -### Symmetric end-to-end encrypted payloads on a channel - -When a 128 bit or 256 bit key is provided to the library, all payloads are encrypted and decrypted automatically using that key on the channel. The secret key is never transmitted to Ably and thus it is the developer's responsibility to distribute a secret key to both publishers and subscribers. - -```python -key = ably.util.crypto.generate_random_key() -channel = rest.channels.get('communication', cipher={'key': key}) -channel.publish(u'unencrypted', u'encrypted secret payload') -messages_page = await channel.history() -messages_page.items[0].data #=> "sensitive data" -``` - -### Generate a Token - -Tokens are issued by Ably and are readily usable by any client to connect to Ably: - -```python -token_details = await client.auth.request_token() -token_details.token # => "xVLyHw.CLchevH3hF....MDh9ZC_Q" -new_client = AblyRest(token=token_details) -await new_client.close() -``` - -### Generate a TokenRequest - -Token requests are issued by your servers and signed using your private API key. This is the preferred method of authentication as no secrets are ever shared, and the token request can be issued to trusted clients without communicating with Ably. - -```python -token_request = await client.auth.create_token_request( - { - 'client_id': 'jim', - 'capability': {'channel1': '"*"'}, - 'ttl': 3600 * 1000, # ms - } -) -# => {"id": ..., -# "clientId": "jim", -# "ttl": 3600000, -# "timestamp": ..., -# "capability": "{\"*\":[\"*\"]}", -# "nonce": ..., -# "mac": ...} - -new_client = AblyRest(token=token_request) -await new_client.close() -``` - -### Fetching your application's stats - -```python -stats = await client.stats() # Returns a PaginatedResult -stats.items -await client.close() -``` - -### Fetching the Ably service time - -```python -await client.time() -await client.close() -``` - -## Using the realtime client - -### Create a client using an API key - -```python -from ably import AblyRealtime - - -# Create a client using an Ably API key -async def main(): - client = AblyRealtime('api:key') -``` - -### Create a client using token auth - -```python -# Create a client using kwargs, which must contain at least one auth option -# the available auth options are key, token, token_details, auth_url, and auth_callback -# see https://www.ably.com/docs/rest/usage#client-options for more details -from ably import AblyRealtime -from ably import AblyRest -async def main(): - rest_client = AblyRest('api:key') - token_details = rest_client.request_token() - client = AblyRealtime(token_details=token_details) -``` - -### Subscribe to connection state changes - -```python -# subscribe to 'failed' connection state -client.connection.on('failed', listener) - -# subscribe to 'connected' connection state -client.connection.on('connected', listener) - -# subscribe to all connection state changes -client.connection.on(listener) - -# wait for the next state change -await client.connection.once_async() - -# wait for the connection to become connected -await client.connection.once_async('connected') -``` - -```python -# subscribe to 'failed' connection state -client.connection.on('failed', listener) - -# subscribe to 'connected' connection state -client.connection.on('connected', listener) - -# subscribe to all connection state changes -client.connection.on(listener) - -# wait for the next state change -await client.connection.once_async() - -# wait for the connection to become connected -await client.connection.once_async('connected') -``` - -### Get a realtime channel instance - -```python -channel = client.channels.get('channel_name') -``` - -### Subscribing to messages on a channel - -```python - -def listener(message): - print(message.data) - -# Subscribe to messages with the 'event' name -await channel.subscribe('event', listener) - -# Subscribe to all messages on a channel -await channel.subscribe(listener) -``` - -Note that `channel.subscribe` is a coroutine function and will resolve when the channel is attached - -### Unsubscribing from messages on a channel - -```python -# unsubscribe the listener from the channel -channel.unsubscribe('event', listener) - -# unsubscribe all listeners from the channel -channel.unsubscribe() -``` +## Installation -### Attach to a channel +To get started with your project, install the package: -```python -await channel.attach() +```sh +pip install ably ``` -### Detach from a channel +> [!NOTE] +Install [Python](https://www.python.org/downloads/) version 3.8 or greater. -```python -await channel.detach() -``` +## Usage -### Managing a connection +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. ```python -# Establish a realtime connection. -# Explicitly calling connect() is unnecessary unless the autoConnect attribute of the ClientOptions object is false -client.connect() - -# Close a connection -await client.close() - -# Send a ping -time_in_ms = await client.connection.ping() +# Initialize Ably Realtime client +async with AblyRealtime('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') ``` -## Resources - -Visit https://ably.com/docs for a complete API reference and more examples. - -## Requirements - -This SDK supports Python 3.7+. - -We regression-test the SDK against a selection of Python versions (which we update over time, -but usually consists of mainstream and widely used versions). Please refer to [check.yml](.github/workflows/check.yml) -for the set of versions that currently undergo CI testing. +## Releases -## Known Limitations +The [CHANGELOG.md](https://github.com/ably/ably-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). -Currently, this SDK only supports [Ably REST](https://ably.com/docs/rest) and realtime message subscription as documented above. -However, you can use the [MQTT adapter](https://ably.com/docs/mqtt) to implement [Ably's Realtime](https://ably.com/docs/realtime) features using Python. +--- -See [our roadmap for this SDK](roadmap.md) for more information. +## Contribute -## Support, feedback and troubleshooting +Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines to contribute to Ably. -Please visit https://ably.com/support for access to our knowledge base and to ask for any assistance. +--- -You can also view the [community reported GitHub issues](https://github.com/ably/ably-python/issues). +## Support, feedback, and troubleshooting -To see what has changed in recent versions of Bundler, see the [CHANGELOG](CHANGELOG.md). +For help or technical support, visit Ably's [support page](https://ably.com/support) or [GitHub Issues](https://github.com/ably/ably-python/issues) for community-reported bugs and discussions. -If you find any compatibility issues, please [do raise an issue](https://github.com/ably/ably-python/issues/new) in this repository or [contact Ably customer support](https://ably.com/support) for advice. +### Full Realtime support unavailable -## Contributing +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). -For guidance on how to contribute to this project, see [CONTRIBUTING.md](https://github.com/ably/ably-python/blob/main/CONTRIBUTING.md) diff --git a/images/pythonSDK-github.png b/images/pythonSDK-github.png new file mode 100644 index 00000000..1fd7f1be Binary files /dev/null and b/images/pythonSDK-github.png differ