diff --git a/CHANGELOG.md b/CHANGELOG.md index 793f50c0..b6012be6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Change Log +## [4.0.0](https://github.com/ably/ably-python/tree/v4.0.0) (unreleased) + +[Full Changelog](https://github.com/ably/ably-python/compare/v3.1.2...v4.0.0) + +> This entry describes work in progress on the `integration/v4` branch. Nothing +> is published under these names yet; the heading loses "(unreleased)" when +> 4.0.0 ships. + +### Breaking change + +Version 4.0.0 applies [PDR-091b](https://ably.atlassian.net/wiki/spaces/product/pages/5362810886) +and splits the SDK into new distributions. The `ably` package is superseded: it +receives security and critical-bug fixes only for one year from the 4.0.0 +release date, and is then end-of-life. + +- **New distributions.** `ably-pubsub-server` is the public package for servers + and other trusted environments. It is built on `ably-pubsub-core`, an internal + package that must never be depended on directly; the two are versioned and + released in lockstep, with the server pinning the core to the exact same + version. +- **New import namespace.** `ably` becomes `ably_pubsub.server` (and + `ably.sync` becomes `ably_pubsub.server.sync`). Both installs can coexist in + one environment while migrating, since the import packages differ. +- **Factory doors replace the constructors.** `AblyRest(...)` becomes + `ably_pubsub.server.create_http_client(...)`, `AblyRealtime(...)` becomes + `ably_pubsub.server.create_realtime_client(...)`, and `AblyRestSync(...)` + becomes `ably_pubsub.server.sync.create_http_client(...)`. Each takes exactly + the arguments the constructor it replaces took. +- **Agent identifier.** Clients now report + `ably-pubsub-python/4.0.0 python/ ably-pubsub-server`. The + `ably-pubsub-server` flag is what declares the server side, which is how the + platform exempts these connections from monthly-active-user counting. A new + `agents` client option lets a layered SDK add its own entries. +- **Python floor raised.** `requires-python` is now `>=3.8`; supported versions + are 3.8 through 3.14. +- **`LONG_DESCRIPTION.rst` removed.** Each distribution's PyPI page now renders + its own `README.md`. + +For detailed migration instructions, please refer to the [Upgrading Guide](UPDATING.md). + ## [3.1.2](https://github.com/ably/ably-python/tree/v3.1.2) [Full Changelog](https://github.com/ably/ably-python/compare/v3.1.1...v3.1.2) diff --git a/README.md b/README.md index 4ee29fd5..0e215068 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![Ably Pub/Sub Python Header](images/pythonSDK-github.png) -[![PyPI version](https://badge.fury.io/py/ably.svg)](https://pypi.org/project/ably/) +[![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-python)](https://github.com/ably/ably-python/blob/main/LICENSE) @@ -25,6 +25,28 @@ Everything you need to get started with Ably: --- +## Packages + +Python is a server-side language, so this repository publishes one package for +applications to install, plus the shared implementation it is built on: + +| Distribution | For | Import | +| --- | --- | --- | +| `ably-pubsub-server` | Servers, backends, jobs — any trusted environment holding an API key | `ably_pubsub.server` | +| `ably-pubsub-core` | **Internal.** Ably's own packages only — never depend on it directly | — | + +Installing the package that names the side your code runs on is more than a +naming convention: clients created by `ably-pubsub-server` declare themselves as +server-side on the wire, and server connections are exempt from monthly-active-user +counting. + +Nothing under `ably_pubsub.core` is public API — its module layout and the names +within it may change in any release. Everything supported is re-exported from +`ably_pubsub.server`. The two distributions are versioned and released in +lockstep: `ably-pubsub-server` pins `ably-pubsub-core` to the exact same version. + +--- + ## Supported platforms 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). @@ -33,13 +55,13 @@ The following platforms are supported: | Platform | Support | |----------|--------------------------| -| Python | Python 3.7+ through 3.14 | +| Python | Python 3.8 through 3.14 | > [!NOTE] -> This SDK works across all major operating platforms (Linux, macOS, Windows) as long as Python 3.7+ is available. +> This SDK works across all major operating platforms (Linux, macOS, Windows) as long as Python 3.8 or greater is available. > [!IMPORTANT] -> SDK versions < 2.0.0 are [deprecated](https://ably.com/docs/platform/deprecate/protocol-v1). +> The `ably` package (3.x and earlier) is superseded by `ably-pubsub-server`. See [Migrating from `ably` 3.x](#migrating-from-ably-3x). --- @@ -48,36 +70,100 @@ The following platforms are supported: To get started with your project, install the package: ```sh -pip install ably +pip install ably-pubsub-server ``` > [!NOTE] Install [Python](https://www.python.org/downloads/) version 3.8 or greater. +Optional extras: `crypto` for channel encryption, `vcdiff` for delta decoding, +and `oldcrypto` for the legacy `pycrypto` backend. + +```sh +pip install "ably-pubsub-server[crypto]" +``` + +--- + ## Usage -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. +Clients are created through the factory functions in `ably_pubsub.server`. They +take exactly the arguments the client constructors take, and return the same +clients. + +### Realtime client + +Connects to Ably's realtime messaging service, subscribes to a channel to +receive messages, and publishes a test message to that same channel. ```python -# Initialize Ably Realtime client -async with AblyRealtime('your-ably-api-key', client_id='me') as realtime_client: - # Wait for connection to be established +import asyncio +from ably_pubsub.server import create_realtime_client + + +async def main(): + realtime_client = create_realtime_client(key='your-ably-api-key', client_id='me') 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') + + await realtime_client.close() + +asyncio.run(main()) +``` + +### HTTP client + +For publishing, history, presence reads, stats and token issuing, with no +persistent connection: + +```python +import asyncio +from ably_pubsub.server import create_http_client + + +async def main(): + async with create_http_client(key='your-ably-api-key') as client: + channel = client.channels.get('test-channel') + await channel.publish('test-event', 'hello world') + +asyncio.run(main()) ``` +### Synchronous HTTP client + +For code that has no event loop. There is no synchronous realtime client. + +```python +from ably_pubsub.server.sync import create_http_client + +client = create_http_client(key='your-ably-api-key') +client.channels.get('test-channel').publish('test-event', 'hello world') +client.close() +``` + +--- + +## Migrating from `ably` 3.x + +Version 4.0.0 moves the SDK to the `ably-pubsub-server` distribution and the +`ably_pubsub.server` import namespace. For most applications the change is +confined to the install line, the import, and the constructor call. +[UPDATING.md](./UPDATING.md) has the full mapping table and worked examples. + +The `ably` package receives security and critical-bug fixes only for one year +from the 4.0.0 release, and is then end-of-life. The two packages can be +installed side by side while you migrate — they use different import packages. + +--- + ## Releases 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). @@ -93,8 +179,3 @@ Read the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines to contribute to Ably. ## Support, feedback, and troubleshooting 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. - -### Full Realtime support unavailable - -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). - diff --git a/UPDATING.md b/UPDATING.md index 4b4dd719..a89b866b 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -1,5 +1,163 @@ # Upgrade / Migration Guide +## Version 3.x (`ably`) to 4.0.0 (`ably-pubsub-server`) + +> **Status: draft.** The public API naming is still under review; the class and +> function names in this section may change before the 4.0.0 GA release. + +Version 4.0.0 splits the SDK into new distributions, following +[PDR-091b](https://ably.atlassian.net/wiki/spaces/product/pages/5362810886). The +`ably` package is superseded: it receives security and critical-bug fixes only +for one year from the 4.0.0 release date, and is then end-of-life. + +### Why + +Under MAU-based pricing the platform must classify every connection as +device-side or server-side. The new packages declare which side they are on +automatically, as part of the agent identifier they put on the wire. The old +`ably` constructors cannot: nothing in them says where the code runs, so once +MAU pricing is live they are rejected on MAU-enabled accounts because the +platform cannot classify them. + +Python is a server-side language, so there is a single new public distribution, +`ably-pubsub-server`, whose factory functions are the only recommended entry +points. It is built on `ably-pubsub-core`, an internal distribution you should +never depend on directly. The objects the factories return are the same clients +as today — channels, presence, history, auth and error handling are unchanged. +For most applications the migration is confined to the install line, the import, +and the constructor call. + +### Mapping + +| 3.x (`ably`) | 4.0 (`ably-pubsub-server`) | +| --- | --- | +| `pip install ably` | `pip install ably-pubsub-server` | +| `pip install ably[crypto]` | `pip install ably-pubsub-server[crypto]` | +| `from ably import AblyRest` | `from ably_pubsub.server import create_http_client` | +| `AblyRest(key=...)` | `create_http_client(key=...)` | +| `from ably import AblyRealtime` | `from ably_pubsub.server import create_realtime_client` | +| `AblyRealtime(key=...)` | `create_realtime_client(key=...)` | +| `from ably.sync import AblyRestSync` | `from ably_pubsub.server.sync import create_http_client` | +| `AblyRestSync(key=...)` | `create_http_client(key=...)` (from `ably_pubsub.server.sync`) | +| `from ably import X` (any name exported by `ably`) | `from ably_pubsub.server import X` | +| `from ably.types.channeloptions import ChannelOptions` | `from ably_pubsub.server import ChannelOptions` | +| `from ably.util.crypto import CipherParams` | `from ably_pubsub.server import CipherParams` | + +#### Deep imports + +In 3.x many types could only be reached by importing the submodule they were +defined in. In 4.0 all of them are re-exported from `ably_pubsub.server`, so the +submodule path goes away entirely — **the flat import is the supported one**. +Nothing under `ably_pubsub.core` is public API. + +| 3.x deep import | 4.0 | +| --- | --- | +| `from ably.types.message import Message, MessageAnnotations` | `from ably_pubsub.server import Message, MessageAnnotations` | +| `from ably.types.presence import Presence, PresenceMessage, PresenceAction` | `from ably_pubsub.server import Presence, PresenceMessage, PresenceAction` | +| `from ably.types.tokenrequest import TokenRequest` | `from ably_pubsub.server import TokenRequest` | +| `from ably.types.tokendetails import TokenDetails` | `from ably_pubsub.server import TokenDetails` | +| `from ably.types.channeldetails import ChannelDetails, ChannelStatus, ChannelOccupancy, ChannelMetrics` | `from ably_pubsub.server import ChannelDetails, ChannelStatus, ChannelOccupancy, ChannelMetrics` | +| `from ably.types.channelstate import ChannelState, ChannelStateChange` | `from ably_pubsub.server import ChannelState, ChannelStateChange` | +| `from ably.types.connectionstate import ConnectionState, ConnectionEvent, ConnectionStateChange` | `from ably_pubsub.server import ConnectionState, ConnectionEvent, ConnectionStateChange` | +| `from ably.types.stats import Stats` | `from ably_pubsub.server import Stats` | +| `from ably.http.paginatedresult import PaginatedResult, HttpPaginatedResponse` | `from ably_pubsub.server import PaginatedResult, HttpPaginatedResponse` | +| `from ably.rest.channel import Channel` | `from ably_pubsub.server import Channel` | +| `from ably.realtime.channel import RealtimeChannel` | `from ably_pubsub.server import RealtimeChannel` | +| `from ably.realtime.connection import Connection` | `from ably_pubsub.server import Connection` | +| `from ably.realtime.presence import RealtimePresence` | `from ably_pubsub.server import RealtimePresence` | + +The full supported surface of `ably_pubsub.server`: + +``` +AblyAuthException, AblyException, AblyRealtime, AblyRest, AblyVCDiffDecoder, +Annotation, AnnotationAction, Auth, Capability, Channel, ChannelDetails, +ChannelMetrics, ChannelMode, ChannelOccupancy, ChannelOptions, ChannelState, +ChannelStateChange, ChannelStatus, CipherParams, Connection, ConnectionEvent, +ConnectionState, ConnectionStateChange, DeviceDetails, HttpPaginatedResponse, +IncompatibleClientIdException, Message, MessageAction, MessageAnnotations, +MessageOperation, MessageVersion, Options, PaginatedResult, Presence, +PresenceAction, PresenceMessage, PublishResult, Push, PushChannelSubscription, +RealtimeChannel, RealtimePresence, SERVER_AGENT_IDENTIFIER, Stats, +TokenDetails, TokenRequest, UpdateDeleteResult, VCDiffDecoder, +create_http_client, create_realtime_client +``` + +`ably_pubsub.server.sync` re-exports the same set minus the realtime types, with +the synchronous flavours under their `Sync` names: `AblyRestSync`, `AuthSync`, +`PushSync`, `ChannelSync`, `PaginatedResultSync` and +`HttpPaginatedResponseSync`. There is no synchronous realtime client, so +`AblyRealtime`, `RealtimeChannel`, `RealtimePresence`, `Connection` and the +channel/connection state types are not there. + +Two names you may go looking for and not find, in either version: + +- **`TokenParams` is not a class in this SDK.** Token params are plain + dictionaries, for example + `await auth.request_token(token_params={'ttl': 3600000, 'client_id': 'me'})`. +- **There is no separate `ErrorInfo` type.** `AblyException` is the equivalent + and carries `code` and `status_code`; it is exported from + `ably_pubsub.server`. + +### Example + +```python +# 3.x +from ably import AblyRest + +client = AblyRest(key='your-api-key') + +# 4.0 +from ably_pubsub.server import create_http_client + +client = create_http_client(key='your-api-key') +``` + +```python +# 3.x +from ably import AblyRealtime + +client = AblyRealtime(key='your-api-key', client_id='me') + +# 4.0 +from ably_pubsub.server import create_realtime_client + +client = create_realtime_client(key='your-api-key', client_id='me') +``` + +```python +# 3.x +from ably.sync import AblyRestSync + +client = AblyRestSync(key='your-api-key') + +# 4.0 +from ably_pubsub.server.sync import create_http_client + +client = create_http_client(key='your-api-key') +``` + +The factories take exactly the keyword arguments the old constructors took: +`create_http_client(key=None, token=None, token_details=None, **options)` and +`create_realtime_client(key=None, loop=None, **options)`, where `**options` is +the same client options as before. The only argument that behaves differently is +`agents`: your entries are preserved, but the package's own `ably-pubsub-server` +entry is always added, so the wire agent reads +`ably-pubsub-python/4.0.0 python/3.x ably-pubsub-server`. + +### Packaging changes + +- **Python floor.** `requires-python` is now `>=3.8` (it was `>=3.7`, though 3.7 + was already untested). Supported versions are 3.8 through 3.14. +- **Extras.** The same extras exist under the new name: + `ably-pubsub-server[crypto]`, `ably-pubsub-server[vcdiff]` and + `ably-pubsub-server[oldcrypto]`. +- **Two distributions.** `ably-pubsub-server` depends on `ably-pubsub-core` + pinned to the exact same version; the two are always released in lockstep. Do + not install or import `ably-pubsub-core` directly. +- **Side-by-side install is safe.** `ably` and `ably-pubsub-server` use + different import packages (`ably` and `ably_pubsub`), so both can be installed + in one environment while you migrate. + ## Version 2.x to 3.0.0 The 3.0.0 version of ably-python introduces several breaking changes to improve the realtime experience and align the API with the Ably specification. These include: diff --git a/core/README.md b/core/README.md index 6c3a8697..6b4f15f4 100644 --- a/core/README.md +++ b/core/README.md @@ -2,21 +2,18 @@ **This is an internal implementation package. Do not depend on it directly.** -`ably-pubsub-core` holds the shared implementation of Ably's Pub/Sub SDK for -Python: the HTTP and realtime clients, channels, presence, authentication, -encryption and the message types. It is published so that the packages -applications *do* install can depend on one shared implementation, pinned to an -exact version. - -Install the package that names the side your code runs on instead: - -| Where your code runs | Install | Import | -| --- | --- | --- | -| A server or other trusted environment | `ably-pubsub-server` | `ably_pubsub.server` | - -Nothing under `ably_pubsub.core` is public API. Its module layout, and the -names within it, may change in any release — including patch releases — without -a deprecation cycle. The supported surface is what `ably_pubsub.server` +`ably-pubsub-core` holds the shared implementation of [Ably](https://ably.com)'s +Pub/Sub SDK for Python: the HTTP and realtime clients, channels, presence, +authentication, encryption and the message types. It is published only so that +the packages applications *do* install can share one implementation. + +Install [`ably-pubsub-server`](https://pypi.org/project/ably-pubsub-server/) +instead — it is the public package for servers and other trusted environments, +and it depends on this one at an exact pinned version, released in lockstep. + +Nothing under `ably_pubsub.core` is public API. Its module layout, and the names +within it, may change in any release — including patch releases — without a +deprecation cycle. The supported surface is what `ably_pubsub.server` re-exports. `ably_pubsub` is a [PEP 420](https://peps.python.org/pep-0420/) namespace diff --git a/server/README.md b/server/README.md index a67f195e..07046be2 100644 --- a/server/README.md +++ b/server/README.md @@ -7,9 +7,20 @@ Ably is the platform that powers synchronized digital experiences in realtime. Pub/Sub is its foundational product: publish and subscribe messaging over channels, presence, history, and token issuing. -Choosing the package by side matters beyond naming: connections made by this -package declare themselves as server-side on the wire, and server connections -are exempt from monthly-active-user counting. +## What this package is for + +Install this package when your code runs somewhere you control and trust: a +backend service, an API, a worker or a scheduled job — anywhere that can hold an +API key. It is not for browsers, mobile apps, or anything else shipped to an end +user. + +Choosing the package by side matters beyond naming. Clients created here stamp +`ably-pubsub-server` into the agent identifier they put on the wire — the wire +shape is `ably-pubsub-python/4.0.0 python/3.x ably-pubsub-server` — and that +flag is how the platform classifies the connection as server-side and exempts it +from monthly-active-user counting. + +Requires Python 3.8 or greater. ## Installation @@ -17,8 +28,8 @@ are exempt from monthly-active-user counting. pip install ably-pubsub-server ``` -Optional extras: `crypto` (channel encryption, via pycryptodome) and `vcdiff` -(delta decoding). +Optional extras: `crypto` (channel encryption, via pycryptodome), `vcdiff` +(delta decoding) and `oldcrypto` (the legacy pycrypto backend). ```shell pip install "ably-pubsub-server[crypto]" @@ -58,6 +69,7 @@ from ably_pubsub.server.sync import create_http_client client = create_http_client(key='your-api-key') client.channels.get('some-channel').publish('greeting', 'hello') +client.close() ``` ### Realtime client @@ -82,12 +94,24 @@ asyncio.run(main()) There is no synchronous realtime client. +Both factories take exactly the arguments the underlying client constructors +take: `create_http_client(key=None, token=None, token_details=None, **options)` +and `create_realtime_client(key=None, loop=None, **options)`. + +## Documentation + +- [Ably Pub/Sub docs](https://ably.com/docs/basics) +- [Getting started with Pub/Sub using Python](https://ably.com/docs/getting-started/python) +- [Ably Pub/Sub examples](https://ably.com/examples?product=pubsub) + ## Relationship to other packages - `ably-pubsub-core` — the shared implementation this package is built on. It is an internal package; do not import `ably_pubsub.core` directly. Everything supported is re-exported from `ably_pubsub.server`. -- `ably` — the 3.x package this one replaces. See +- `ably` — the 3.x package this one replaces. It receives security and + critical-bug fixes only for one year from the 4.0.0 release, and is then + end-of-life. The migration is usually three lines: see [UPDATING.md](https://github.com/ably/ably-python/blob/main/UPDATING.md). ## Contributing