Repository navigation
Integration branch for ably-pubsub-server v4 package - #693
Conversation
|
Important Review skippedToo many files! This PR contains 321 files, which is 221 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files.
|
e7182e2 to
4ace8e7
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
One or more issues must be addressed before approval.
Review effort: Lite
Findings: 1
Open (5)
What changed in this PR
Splits the SDK into the ably-pubsub-server distribution and updates public imports, factories, synchronous generation, documentation, and tests.
Changes:
- Migrates modules and tests to
ably.pubsub. - Adds factory/prototype-based client construction.
- Updates packaging, documentation, and release configuration.
| File | Description |
|---|---|
| UPDATING.md | Updated as part of this pull request. |
| test/uts/rest/unit/types/token_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/presence_message_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/paginated_result_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/options_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/mutable_message_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/message_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/types/error_types_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/time_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/stats_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/rest_client_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/push/push_device_registrations_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/push/push_channels_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/push/push_channel_subscriptions_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/push/push_admin_publish_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/presence/rest_presence_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/fallback_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/encoding/msgpack_interop_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/encoding/message_encoding_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channels_collection_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/update_delete_message_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/rest_channel_attributes_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/publish_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/publish_result_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/message_versions_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/idempotency_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/history_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/get_message_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/channel/annotations_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/batch_publish_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/batch_presence_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/token_renewal_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/token_details_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/revoke_tokens_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/client_id_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/authorize_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/auth_scheme_test.py | Updated as part of this pull request. |
| test/uts/rest/unit/auth/auth_callback_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/time_stats_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/revoke_tokens_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/push_channels_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/push_admin_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/publish_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/proxy/rest_fallback_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/presence_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/pagination_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/mutable_messages_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/batch_presence_test.py | Updated as part of this pull request. |
| test/uts/rest/integration/auth_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_subscribe_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_reentry_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_history_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_get_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_enter_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/realtime_presence_channel_state_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/presence_sync_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/presence_map_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/presence/local_presence_map_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/when_state_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/update_events_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/server_initiated_reauth_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/heartbeat_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/forwards_compatibility_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/fallback_hosts_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/error_reason_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/connection_recovery_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/connection_ping_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/connection_open_failures_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/connection_id_key_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/connection_failures_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/backoff_jitter_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/connection/auto_connect_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/client/realtime_timeouts_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/client/realtime_time_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/client/realtime_stats_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/client/realtime_request_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/client/realtime_client_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/message_field_population_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channels_collection_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_when_state_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_update_delete_message_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_subscribe_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_state_events_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_server_initiated_detach_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_publish_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_publish_pending_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_properties_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_options_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_message_versions_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_history_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_get_message_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_error_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_detach_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_delta_decoding_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_connection_state_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_attributes_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_attach_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_annotations_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/channels/channel_additional_attached_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/auth/token_expiry_non_renewable_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/auth/realtime_authorize_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/auth/connection_auth_test.py | Updated as part of this pull request. |
| test/uts/realtime/unit/auth/auth_callback_errors_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/rest_faults_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/presence_reentry_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/heartbeat_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/connection_resume_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/connection_open_failures_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/channel_faults_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/proxy/auth_reauth_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/presence/presence_sync_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/presence_lifecycle_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/mutable_messages_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/delta_decoding_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/connection/connection_failures_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/connection_lifecycle_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/channels/channel_subscribe_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/channels/channel_publish_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/channels/channel_attach_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/channel_history_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/auth/token_request_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/auth/token_renewal_test.py | Updated as part of this pull request. |
| test/uts/realtime/integration/auth_test.py | Updated as part of this pull request. |
| test/uts/README.md | Updated as part of this pull request. |
| test/uts/helpers/sandbox.py | Updated as part of this pull request. |
| test/uts/helpers/presence.py | Updated as part of this pull request. |
| test/uts/helpers/mock_websocket.py | Updated as part of this pull request. |
| test/uts/helpers/mock_websocket_test.py | Updated as part of this pull request. |
| test/uts/helpers/mock_http_test.py | Updated as part of this pull request. |
| test/uts/helpers/clock.py | Updated as part of this pull request. |
| test/uts/helpers/client.py | Updated as part of this pull request. |
| test/unit/websockettransport_test.py | Updated as part of this pull request. |
| test/unit/pubsub_server_test.py | Updated as part of this pull request. |
| test/unit/options_test.py | Updated as part of this pull request. |
| test/unit/mutable_message_test.py | Updated as part of this pull request. |
| test/unit/message_test.py | Updated as part of this pull request. |
| test/unit/http_test.py | Updated as part of this pull request. |
| test/unit/clock_test.py | Updated as part of this pull request. |
| test/unit/annotation_test.py | Updated as part of this pull request. |
| test/ably/utils.py | Updated as part of this pull request. |
| test/ably/testapp.py | Updated as part of this pull request. |
| test/ably/realtime/realtimeresume_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimepresence_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimeinit_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimeconnection_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimechannelmutablemessages_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimechannel_vcdiff_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimechannel_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimechannel_publish_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimeauth_test.py | Updated as part of this pull request. |
| test/ably/realtime/realtimeannotations_test.py | Updated as part of this pull request. |
| test/ably/realtime/presencemap_test.py | Updated as part of this pull request. |
| test/ably/realtime/eventemitter_test.py | Updated as part of this pull request. |
| test/ably/realtime/deltadecoding_test.py | Updated as part of this pull request. |
| test/ably/http/httptoken_test.py | Updated as part of this pull request. |
| test/ably/http/httptime_test.py | Updated as part of this pull request. |
| test/ably/http/httpstats_test.py | Updated as part of this pull request. |
| test/ably/http/httprequest_test.py | Updated as part of this pull request. |
| test/ably/http/httppush_test.py | Updated as part of this pull request. |
| test/ably/http/httppresence_test.py | Updated as part of this pull request. |
| test/ably/http/httppaginatedresult_test.py | Updated as part of this pull request. |
| test/ably/http/httphttp_test.py | Updated as part of this pull request. |
| test/ably/http/httpcrypto_test.py | Updated as part of this pull request. |
| test/ably/http/httpchannelstatus_test.py | Updated as part of this pull request. |
| test/ably/http/httpchannels_test.py | Updated as part of this pull request. |
| test/ably/http/httpchannelpublish_test.py | Updated as part of this pull request. |
| test/ably/http/httpchannelmutablemessages_test.py | Updated as part of this pull request. |
| test/ably/http/httpchannelhistory_test.py | Updated as part of this pull request. |
| test/ably/http/httpcapability_test.py | Updated as part of this pull request. |
| test/ably/http/httpannotations_test.py | Updated as part of this pull request. |
| test/ably/http/encoders_test.py | Updated as part of this pull request. |
| README.md | Updated as part of this pull request. |
| pyproject.toml | Updated as part of this pull request. |
| LONG_DESCRIPTION.rst | Updated as part of this pull request. |
| CONTRIBUTING.md | Updated as part of this pull request. |
| ably/vcdiff/__init__.py | Updated as part of this pull request. |
| ably/pubsub/version.py | Updated as part of this pull request. |
| ably/pubsub/vcdiff/defaultvcdiffdecoder.py | Updated as part of this pull request. |
| ably/pubsub/vcdiff/__init__.py | Updated as part of this pull request. |
| ably/pubsub/util/nocrypto.py | Updated as part of this pull request. |
| ably/pubsub/util/helper.py | Updated as part of this pull request. |
| ably/pubsub/util/exceptions.py | Updated as part of this pull request. |
| ably/pubsub/util/eventemitter.py | Updated as part of this pull request. |
| ably/pubsub/util/encoding.py | Updated as part of this pull request. |
| ably/pubsub/util/crypto.py | Updated as part of this pull request. |
| ably/pubsub/util/construction.py | Updated as part of this pull request. |
| ably/pubsub/util/clock.py | Updated as part of this pull request. |
| ably/pubsub/util/case.py | Updated as part of this pull request. |
| ably/pubsub/util/__init__.py | Updated as part of this pull request. |
| ably/pubsub/types/typedbuffer.py | Updated as part of this pull request. |
| ably/pubsub/types/tokenrequest.py | Updated as part of this pull request. |
| ably/pubsub/types/tokendetails.py | Updated as part of this pull request. |
| ably/pubsub/types/testoptions.py | Updated as part of this pull request. |
| ably/pubsub/types/stats.py | Updated as part of this pull request. |
| ably/pubsub/types/presence.py | Updated as part of this pull request. |
| ably/pubsub/types/options.py | Updated as part of this pull request. |
| ably/pubsub/types/operations.py | Updated as part of this pull request. |
| ably/pubsub/types/mixins.py | Updated as part of this pull request. |
| ably/pubsub/types/message.py | Updated as part of this pull request. |
| ably/pubsub/types/flags.py | Updated as part of this pull request. |
| ably/pubsub/types/device.py | Updated as part of this pull request. |
| ably/pubsub/types/connectionstate.py | Updated as part of this pull request. |
| ably/pubsub/types/connectionerrors.py | Updated as part of this pull request. |
| ably/pubsub/types/connectiondetails.py | Updated as part of this pull request. |
| ably/pubsub/types/channelsubscription.py | Updated as part of this pull request. |
| ably/pubsub/types/channelstate.py | Updated as part of this pull request. |
| ably/pubsub/types/channeloptions.py | Updated as part of this pull request. |
| ably/pubsub/types/channelmode.py | Updated as part of this pull request. |
| ably/pubsub/types/channeldetails.py | Updated as part of this pull request. |
| ably/pubsub/types/capability.py | Updated as part of this pull request. |
| ably/pubsub/types/authoptions.py | Updated as part of this pull request. |
| ably/pubsub/types/annotation.py | Updated as part of this pull request. |
| ably/pubsub/types/__init__.py | Updated as part of this pull request. |
| ably/pubsub/transport/websockettransport.py | Updated as part of this pull request. |
| ably/pubsub/transport/defaults.py | Updated as part of this pull request. |
| ably/pubsub/transport/__init__.py | Updated as part of this pull request. |
| ably/pubsub/server/sync.py | Updated as part of this pull request. |
| ably/pubsub/scripts/unasync.py | Updated as part of this pull request. |
| ably/pubsub/scripts/__init__.py | Updated as part of this pull request. |
| ably/pubsub/request/paginatedresult.py | Updated as part of this pull request. |
| ably/pubsub/request/httputils.py | Updated as part of this pull request. |
| ably/pubsub/request/http.py | Updated as part of this pull request. |
| ably/pubsub/request/__init__.py | Updated as part of this pull request. |
| ably/pubsub/realtime/realtime.py | Updated as part of this pull request. |
| ably/pubsub/realtime/presencemap.py | Updated as part of this pull request. |
| ably/pubsub/realtime/presence.py | Updated as part of this pull request. |
| ably/pubsub/realtime/connectionmanager.py | Updated as part of this pull request. |
| ably/pubsub/realtime/connection.py | Updated as part of this pull request. |
| ably/pubsub/realtime/channel.py | Updated as part of this pull request. |
| ably/pubsub/realtime/annotations.py | Updated as part of this pull request. |
| ably/pubsub/realtime/__init__.py | Updated as part of this pull request. |
| ably/pubsub/prototypes.py | Updated as part of this pull request. |
| ably/pubsub/http/push.py | Updated as part of this pull request. |
| ably/pubsub/http/http.py | Updated as part of this pull request. |
| ably/pubsub/http/channel.py | Updated as part of this pull request. |
| ably/pubsub/http/auth.py | Updated as part of this pull request. |
| ably/pubsub/http/annotations.py | Updated as part of this pull request. |
| ably/pubsub/http/__init__.py | Updated as part of this pull request. |
| ably/__init__.py | Updated as part of this pull request. |
| .gitignore | Updated as part of this pull request. |
| .github/workflows/release.yml | Updated as part of this pull request. |
| .claude/skills/uts-to-python/SKILL.md | Updated as part of this pull request. |
| .claude/skills/release/SKILL.md | Updated as part of this pull request. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Publish the SDK as `ably-pubsub-server` rather than `ably`, installing into `ably/pubsub` so that further per-product distributions can share the `ably` namespace. All existing code moves to `ably/pubsub` unchanged; the public API is now reached through `ably.pubsub.server`. Neither `ably` nor `ably.pubsub` carries an `__init__.py` — both are PEP 420 namespace packages. This matters for the next distribution: a regular package wins outright over any namespace portion during import resolution, so an `ably/pubsub/__init__.py` here would silently shadow an `ably/pubsub/client` installed under a different sys.path entry, which is exactly what an editable checkout of this repo alongside a released sibling looks like. Consequences of having no package root to put things in: - `api_version` and `lib_version` move to `ably/pubsub/version.py`, as `http/httputils.py` and `types/options.py` read them and core code must not depend on the server package. - The NullHandler silencing the library's loggers moves to `ably/pubsub/util/__init__.py`, the one module every entry point imports. - unasync writes the sync tree to `ably/pubsub/sync` and maps `ably.pubsub.server` onto the hand-written `ably.pubsub.server.sync`. Also fix `HttpUtils.default_headers()`, which read `api_version` and `lib_version` off the `ably` namespace and so raised AttributeError. Only the integration tests cover the Ably-Agent header, so the unit suite was silent on it. Tests import from `ably.pubsub.server`; UPDATING.md covers the 4.0.0 migration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expose create_http_client() and create_realtime_client() as the only way to build a client, so that the package a client comes from names the side the application runs on. AblyRest and AblyRealtime are no longer exported from ably.pubsub.server and raise TypeError when constructed directly, naming the factory to reach for instead; a ContextVar opened by the factories is what separates a permitted construction from a direct one. Callers who annotated against the client classes should use the prototypes the factories return. ably/pubsub/prototypes.py declares RestClient and RealtimeClient as typing.Protocol definitions of the client surface, which the concrete classes satisfy structurally. They are called prototypes because "protocol" already means the Ably wire protocol here. - typing.Protocol makes 3.8 the minimum supported Python, which the CI matrix and README already assumed. requires-python and the classifiers now say so, which in turn retires two dead sys.version_info blocks in the tests. - Expand the factory docstrings to cover every client option, grouped by concern and derived from Options and AuthOptions rather than copied from the constructors. - Drop `keep_alive` from the AblyRest docstring. It has been documented since the constructor was written, but no option of that name exists and passing it raises TypeError. test/unit/pubsub_server_test.py covers the factories, the guard — including that permission does not leak past a factory call — the classes being unexported, and the concrete clients satisfying their prototypes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The distribution was renamed to ably-pubsub-server, so the agent header should no longer identify the library as ably-python. Report the SDK as ably-pubsub-python and add a versionless ably-pubsub-server agent entry so requests are attributable to the server package. Update the Ably-Agent assertion in the REST HTTP tests to match the new header format. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nflicts - Updated `protocol_members` to handle protocol attribute detection more accurately, relying on `typing._get_protocol_attrs` where available, avoiding unintended attribute inclusion. - Renamed `ably` test client to `app_setup_client` to prevent shadowing the imported `ably` package and ensure correct resolution during test execution. - Adjusted `pyproject.toml` to configure `ruff` for consistent lint behavior in both local and CI environments.
The public entry point is create_http_client, but everything behind it was named "rest": the ably.pubsub.rest package, AblyRest, and the RestClient prototype. Rename that surface to "http" so the names a caller reads match the name they call. The transport package already holding ably.pubsub.http — Http, HttpUtils and PaginatedResult — moves to ably.pubsub.request to free the name, which also describes it better: it models individual requests, not the client. - ably/pubsub/rest/ -> ably/pubsub/http/, with rest.py -> http.py - AblyRest -> AblyHttp, RestAnnotations -> HttpAnnotations, RestChannels -> HttpChannels - the exported prototypes RestClient and RealtimeClient become PubSubHttpClient and PubSubRealtimeClient - test/ably/rest/ -> test/ably/http/, rest*_test.py -> http*_test.py, TestRest* -> TestHttp* unasync's rename list, mocked-path replacements and test directories follow the move, so the generated synchronous flavour lands at ably.pubsub.sync.http. The spec-defined options rest_host and idempotent_rest_publishing keep their names, as does "REST" where it refers to the Ably API rather than to a symbol. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing connected AblyHttp and AblyRealtime to the prototypes the factories return. A hasattr loop in the tests was the only check, and it was weak enough to call a class with no implemented members conformant. Have each client name its prototype as a base class, so the relationship is stated where the client is defined and a type checker verifies it. That alone would weaken the runtime check, because an unimplemented member then resolves to the prototype's `...` stub and returns None rather than raising, so the conformance tests are strengthened to match: - the prototype must actually appear in the client's MRO - every member must be defined by the client itself, found by dropping the prototype's own MRO so an inherited stub cannot satisfy it - parameter names, kinds and requiredness must match the prototype, as must whether the member is awaitable The synchronous client is covered too, since unasync generates both sides of that pair. Annotations are excluded from the signature comparison: the prototype spells its types as strings under `from __future__ import annotations` while the clients evaluate theirs, and return types are a type checker's concern. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The clients now declare their prototype as a base class, so calling them AblyHttp and AblyRealtime left the two halves of each pair reading as unrelated types. Name each class after what it is: the default implementation of its prototype. - AblyHttp -> DefaultPubSubHttpClient - AblyRealtime -> DefaultPubSubRealtimeClient - AblyHttpSync -> DefaultPubSubHttpClientSync, through unasync's rename list Neither name is exported, and both are reachable only through create_http_client and create_realtime_client; each class docstring now says as much. Tidy the 4.0.0 migration notes in passing. They no longer spell out the internal rename, which was never part of the public API. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The library now reports itself as `ably-pubsub-python/<version>`, which the two Ably-Agent tests that came in from main did not expect. - The mock websocket helper test looked for `ably-python` and now looks for the new name. - `RSC7d/ably-agent-header-format-0` asserts `ably-[a-z]+/<semver>`, which rejects a hyphenated library name. RSC7d1 puts no limit on the characters of a product key, so the specification is at fault rather than the SDK. The test is gated with `@spec_error` and the fault is recorded in deviations.md. `update_message()` takes a `Message`, and the migration guide tells users to import types like it from `ably.pubsub.server`, but neither server package exported it. Export it from both. The README passed the API key positionally to `create_realtime_client`, which accepts keyword arguments only and so raised TypeError. Pass it as `key=`, and say in the migration guide that the factories no longer accept a positional key. The release job still read the version from `dist/ably-*.tar.gz` and pointed both environments at `/p/ably`. The sdist is now `ably_pubsub_server-<version>.tar.gz`, so the tag check would never have matched it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`batch_publish` (RSC22) posts one `BatchPublishSpec`, or a list of them, to `/messages`, encoding each message per RSL4 and applying RSL1k1 to each spec separately when idempotent REST publishing is on (RSC22d). The server answers with an array holding a `BatchResult` per spec, so a single spec gets the one element back and a list gets the list. A spec naming no channels or carrying no messages is refused locally with the 400/40000 the server would answer it with. `batch_presence` (RSC24) sends the channel names comma-joined in the `channels` parameter of a GET to `/presence`, and returns the server's `BatchResult`. The server leaves `presence` out for a channel with no members, which is read as an empty list. Both methods are declared on `PubSubHttpClient`, so the realtime and synchronous clients carry them too. `BatchResult`, `BatchPublishSpec` and the four per-channel result types (BAR2, BSP2, BPR2, BPF2, BGR2, BGF2) are exported from `ably.pubsub.server` and `ably.pubsub.server.sync`. A dict with `channels` and `messages` keys stands in for a `BatchPublishSpec`, as a dict does for the push admin types. The RSL1k1 id assignment moves out of `Channel` into `assign_idempotent_ids`, which both publish paths share. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 44 batch Test IDs (47 cases) across `rest/unit` and `rest/integration` drop their `@deviation` gates and pass. They now build `BatchPublishSpec` and check result types with `isinstance`, as the specifications write them. Every `batch_publish.md` mock answers in the legacy format, a flat array of per-channel results, which the sandbox sends only to a client sending `X-Ably-Version: 2` or none. At version 5, which ably-python sends, `POST /messages` answers every batch, mixed and all-failure ones included, with 201 and an array holding one `BatchResult` envelope per spec, a single spec sent as a bare object included. The fixtures are corrected to that shape through `batch_result()`, and the assertions stand. Two RSL4c3 assertions compared a stringified JSON payload byte for byte; they now parse it, as the encoding tests do, since the specification does not fix the whitespace. deviations.md drops the batch row from Unimplemented features, and rewrites the envelope entry: it had concluded that `batch_presence.md` was the specification to revisit, and the server says it is `batch_publish.md`. The header and measured counts are re-measured: 172 gated cases, the RSC7d `Ably-Agent` test among them, 1047 passing derived cases, and 130 helper cases where 122 had been recorded. Three Smaller faults rows that had been superseded by their filed copies are removed. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The batch publish and token revocation fixtures written in the response format below protocol version 3 are filed as ably/specification#559. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4.0 makes ably.pubsub.server the whole public API, but its export list was carried over from 3.x's top-level ably package, which only re-exported CipherParams. 3.x users reached generate_random_key and get_default_params through ably.util.crypto, a submodule that 4.0 moves and declares internal, so 4.0 left no public way to generate a key (RSE2) or build default cipher params. Export both from ably.pubsub.server and ably.pubsub.server.sync, show the 3.x to 4.0 import in UPDATING.md, and make the missing-crypto error name the ably-pubsub-server[crypto] extra instead of the old ably[crypto].
Export generate_random_key and get_default_params from ably.pubsub.server
feat: add batch publish and batch presence to the HTTP client
Derives the fifteen specifications under uts/objects/unit, 322 tests, and the shared helper uts/objects/helpers/standard_test_pool.md describes: the standard pool, its wire builders, the synced-channel setup and the REST provisioning the integration tier uses. The pure specifications drive the CRDT objects, the pool and the sync state machine directly; the rest run against a mock websocket through channel.object. The public API is reached through the typed views of LODR-061, so a specification's untyped value(), set() or entries() reads as as_live_counter().value(), as_live_map().set() and so on. Where a specification step cannot fail, or reads state before the frame that changes it has been processed, the test carries the corrected step under a UTS SPEC ERROR comment and keeps the specification's own assertions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Derives the three sandbox specifications under uts/objects/integration and the proxy specification under uts/objects/integration/proxy, 17 tests. The sandbox ones run under both protocols, against an app of their own; the proxy ones authenticate with a locally signed JWT, as the realtime proxy tier does, and fault the objects sync through uts-proxy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Adds the ably.pubsub.objects package: the protocol v6 object message wire types, with their JSON and msgpack encodings; the LWW map and counter CRDTs with their tombstones, parent references and garbage collection; the objects pool and the sync sequence that replaces it on attach, buffering operations until the sync completes; object id generation; the evaluation of LiveMap and LiveCounter creation values into create operations; and the public ObjectMessage the subscription events carry. A RealtimeObject can be built with no channel, which is how the pure unit specifications drive the sync state machine. The path-based public API, publishing and the channel integration follow in later commits and are stubs here. Also adds the HAS_OBJECTS flag, the OBJECT_SUBSCRIBE and OBJECT_PUBLISH channel modes, and the siteCode and objectsGCGracePeriod connection details. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…onse remove and remove_where on push.admin.device_registrations and push.admin.channel_subscriptions returned the HTTP response of the DELETE, and every PaginatedResult carried the HTTP response it was built from as a public response attribute, so the library's public surface handed out the underlying httpx.Response. RSH1b4, RSH1b5, RSH1c4 and RSH1c5 give these removals no return value, and nothing in TG or HP asks for the response. The four removals now return None, and a failed one still raises AblyException. PaginatedResult no longer keeps the response; HttpPaginatedResponse reads its status code and headers from it once, at construction, so status_code, success, error_code, error_message and headers work as before. This is a breaking change: callers that checked remove()'s status_code must stop, since success is the absence of an exception, and callers that read result.response must read the dedicated HttpPaginatedResponse attributes instead. The UTS tests assert that the removals return None, which closes the push remove return-value note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeviceDetails.push was the dict the server sent, where PCD7 types it as a DevicePushDetails with the recipient, state and errorReason of PCP2-PCP4. And DeviceDetails.from_dict and PushChannelSubscription.from_dict passed every key of the decoded object to the constructor with cls(**obj), so a field the server added that the class did not know raised TypeError and failed the whole get, list or save. DeviceDetails.push is now a DevicePushDetails, built from a dict or taken as one, which validates the recipient's transportType as DeviceDetails used to and decodes errorReason into an AblyException. Both from_dict methods, and DevicePushDetails', now ignore fields they do not model. DevicePushDetails lives beside DeviceDetails in ably.pubsub.types.device and is not added to ably.pubsub.server, whose exports are still to be decided. This is a breaking change: device.push['recipient'] now raises TypeError, and callers must read device.push.recipient, .state and .error_reason. A device can still be built or saved with push given as a dict. A misspelled key in a dict passed to save() is now ignored rather than rejected. The UTS tests build devices with DevicePushDetails as the specification does, which closes the DevicePushDetails missing-accessor row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
channel.options was the dict ChannelOptions.to_dict() produced, and the cipher a channel encrypted with lived beside it in a separate __cipher that the REST options setter only ever overwrote when the dict had a 'cipher' key. to_dict() leaves a None cipher out, so the cipher could never be removed: set_options(ChannelOptions(params=...)) on an encrypted channel, or a repeat channels.get with options that left the cipher out, kept encrypting while channel.options showed no cipher. REST Presence also captured the channel's cipher once, when it was built. channel.options is now the ChannelOptions last set (TB2, RTS3), and the cipher is derived from it. Setting options replaces them whole, as ably-js, java, cocoa, go, dotnet and ruby do, and rebuilds the cached cipher, so options without a cipher turn encryption off (RTL16, RSL7, RTS3c, RSN3c). Every reader, REST Presence and the LiveObjects RTO2b mode check included, now reads the current options. The REST channel gains set_options (RSL7), and channels.get(name, ChannelOptions(...)) works on REST, where it raised TypeError. The kwargs and dict forms channels.get already took keep working. A reattach compared the whole options, cipher included. Per RTL16a and RTS3c1 only params or modes that differ from the current ones now make an attaching or attached channel reattach, so set_options reattaches, and channels.get raises 40000, only for those. A change of cipher alone no longer raises, and repeating the same params is still accepted, as test_channel_get_channel_with_same_params expects. The spec's "provided" is read as provided and different, as ably-js reads it. This is a breaking change. Read channel.options.params rather than channel.options['params'], and pass the cipher again whenever setting the options of an encrypted channel. The two RSL7 UTS tests are un-gated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Channels.__getattr__ answered every attribute the collection did not
define with self.get(name), and the realtime Channels inherited it. So
hasattr(client.channels, 'x') was always true and created a channel
called x, a typo became a phantom channel rather than an AttributeError,
and client.channels.get_derived(...) failed with "'RealtimeChannel'
object is not callable".
It is removed, so an unknown attribute raises AttributeError on both
clients. This is a breaking change: client.channels.my_channel no longer
returns a channel; use client.channels.get('my_channel'), or
client.channels['my_channel'], instead. The legacy tests that relied on
it, and the deviations.md notes about it, are updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HTTP channel built its path with parse.quote_plus, which is form
encoding and writes a space as +. In a URL path the Ably service reads +
as a literal plus. Measured against a throwaway sandbox app, a publish to
/channels/rsl2+probe+<id>/messages was stored in a channel named
"rsl2+probe+<id>", which history read back through either + or %2B,
while /channels/rsl2%20probe%20<id>/messages reached "rsl2 probe <id>",
the channel a realtime client attaches to. So every HTTP operation on a
channel whose name has a space went to another channel. ably-js
(encodeURIComponent) and ably-java (HttpUtils.encodeURIComponent) both
send %20. Channel.status interpolated the name with no escaping at all,
so a name with "/", "?" or "#" addressed the wrong resource.
Channel names are now percent-encoded with parse.quote (RSL2), status()
escapes the name the same way (RSL8), REST presence and annotations
build on the channel's own base path rather than encoding the name
again, and message serials use parse.quote too. ":" is still left
literal, as RFC 3986 allows in a path segment.
This is a breaking change for HTTP data stored under a name with a
space: 3.x stored it in the "+" channel, which is still reachable as
channels.get('my+channel'). The two deviations.md rows for RSL2's space
and RSL8's escaping are removed and the RSL2 UTS test asserts %20. REST
presence used to escape ":" as %3A too, so the legacy presence tests now
mock the path with the literal ":" that is sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
attach() raised only when the attach ended in SUSPENDED or FAILED. When the connection closed during the attach, RTL3b moved the channel to DETACHED and attach() returned None, as if it had attached; so did an attach overtaken by a detach, which returned on the DETACHING state change. RTL4d fails the attach in every case but ATTACHED. A SUSPENDED or FAILED change with no reason also made it raise None, which Python reports as a TypeError. attach() now raises unless the channel attaches: the reason for the state it moved to, else an AblyException with code 90001, as _ensure_active (RTL33b1) already did, which now just calls attach(). The two attaches nobody awaits, the reattach of a SUSPENDED channel when the connection reconnects and the implicit attach of a presence enter or update (RTP8d), go through _attach_implicitly, which logs a failure rather than leaving an exception unretrieved on the task. subscribe and the presence and annotation subscribes await their attach, so they raise in the same cases. This is a breaking change: code that closes the client, or detaches, while an attach is pending must expect attach() and subscribe() to raise. The RTL3b test asserts the failure the specification has, closing its deviation, and the RTL5i test that overtakes an attach with a detach now asserts the rejection the specification also allows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
detach() requested DETACHING whatever the connection's state, and _check_pending_state only sends the DETACH once the connection is CONNECTED, so from CONNECTING, DISCONNECTED or any other state but CONNECTED it awaited a state change nothing would produce, and never returned. It also raised 90001 first whenever the connection was CLOSING or FAILED, which are the old RTL5g, and from CONNECTING called connect(), which is the old RTL5h; specification 5.0.0 replaced both with RTL5l. detach() now follows RTL5 in order: nothing to do from INITIALIZED or DETACHED (RTL5a), 90001 from FAILED (RTL5b), straight to DETACHED from SUSPENDED (RTL5j), and, when the connection is in any state but CONNECTED, straight to DETACHED with no DETACH sent (RTL5l). Only a CONNECTED connection sends the DETACH and waits for the DETACHED (RTL5d). A CLOSING or CLOSED connection has already moved its channels to DETACHED (RTL3b) and a FAILED one to FAILED (RTL3a), so those cases now land on RTL5a and RTL5b rather than the 90001 RTL5g raised. This matters more now that release() requires a detached channel. It is not a breaking change: a call that hung now returns, and one that raised for a CLOSING connection now returns for its already DETACHED channel. The two RTL5l UTS tests are un-gated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ConnectionManager.ping admitted only CONNECTED and CONNECTING and raised 40000 at once from DISCONNECTED, where RTN13d defers a ping requested while CONNECTING or DISCONNECTED until the connection is CONNECTED, and RTN13b fails it only from INITIALIZED, SUSPENDED, CLOSING, CLOSED or FAILED. It also entered asyncio.wait_for as soon as ping() was called, so the whole connecting period was charged to realtimeRequestTimeout and a deferred ping could time out before its HEARTBEAT went out, where RTN13c with RTN13d counts the timeout from the HEARTBEAT. ping() now admits DISCONNECTED too. A deferred ping waits until its heartbeat is sent, which happens on CONNECTED as before, or until a state that fails pending pings (SUSPENDED, CLOSING, CLOSED, FAILED) fails it, and only then starts the realtimeRequestTimeout wait. This is a behaviour change rather than a new API: a ping from DISCONNECTED that raised 40000 at once now waits for the connection, up to the connectionStateTtl after which it suspends and the ping fails. Wrap the call in asyncio.wait_for to bound it. The three gated RTN13 UTS tests now pass and are un-gated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set_options on an ATTACHED channel whose params or modes changed sent the ATTACH through _attach_impl() and then awaited the internal state emitter. The channel never left ATTACHED, so the server's ATTACHED took the RTL12 branch of _on_message, which emits "update" on the public emitter only, and set_options never returned. It raised the state change's reason, None included, for SUSPENDED and FAILED, and returned for any other state. The reattach now goes through _request_state(ATTACHING), so the channel passes through ATTACHING and the ATTACHED that answers ends the wait, which then raises as attach() does unless the channel attached (RTL16a, RTL4d). On an ATTACHING channel the new ATTACH goes out and the wait ends with the pending attach. Not a breaking change; a call that hung now returns. The RTL16a UTS test is un-gated, and the two attach tests that ran set_options as a task and cancelled it now await it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TO3l1, TO3l2, TO3l7, TO3l10 and TO3l11 count disconnectedRetryTimeout, suspendedRetryTimeout, channelRetryTimeout, fallbackRetryTimeout and realtimeRequestTimeout in milliseconds, and ably-python took realtime_request_timeout, disconnected_retry_timeout, suspended_retry_timeout, channel_retry_timeout and fallback_retry_timeout in milliseconds too. Its three HTTP timing options took seconds, so a client's options came in two units with nothing in their names to say which. The five options now take seconds, as an int or a float, and are renamed realtime_request_timeout_seconds, disconnected_retry_timeout_seconds, suspended_retry_timeout_seconds, channel_retry_timeout_seconds and fallback_retry_timeout_seconds, defaulting to 10, 15, 30, 15 and 600. They read back from Options under the new names. The clock still counts milliseconds, so Options converts each one in a single place, seconds_to_ms, behind a private _<option>_ms property that the connection manager, the channel, the transport and the HTTP fallback cache schedule with. It rounds to the microsecond rather than truncating, so 0.0015 is 1.5 ms and 0.1 is exactly 100. The ping timeout, which runs on the event loop's clock, reads the seconds directly. The Defaults move to seconds under the new names; the internal millisecond values in Defaults are unchanged. Every UTS test that sets one of these options divides the specification's value by 1000, and the RTC7 defaults test reads the seconds back. test/uts/deviations.md records the renamed options as an intentional naming-and-unit deviation under Adapted Tests. No gated test changes, so its counts do not. This is a breaking change: passing an old name raises TypeError, so a 3.x value fails at construction rather than meaning a timeout a thousand times too long. Callers should rename the option and divide its value by 1000, as UPDATING.md shows. Token ttl, ConnectionDetails, Auth.time_offset, timestamps and the result of ping() stay milliseconds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TO3l3, TO3l4 and TO3l6 count httpOpenTimeout, httpRequestTimeout and httpMaxRetryDuration in milliseconds. ably-python's http_open_timeout, http_request_timeout and http_max_retry_duration have always been seconds, with nothing in their names to say so. A caller following the specification's units passed http_request_timeout=3000 and got a deadline of fifty minutes: rest/proxy/RSC15l2/timeout-triggers-fallback-0, which does exactly that, sat out the proxy's 20-second delay on the primary host and never fell back, so it was gated as an open bug. The three options are renamed http_open_timeout_seconds, http_request_timeout_seconds and http_max_retry_duration_seconds, with their units and defaults of 4, 10 and 15 unchanged, to match the realtime timing options. They read back from Options, and from the Http properties, under the new names, and still reach the httpx Timeout and the RSC15 retry budget as seconds. The RSC15l2 test now passes httpRequestTimeout: 3000 as http_request_timeout_seconds=3, and is un-gated: it passes against the proxy, timing out at about three seconds and succeeding on the fallback. test/uts/deviations.md removes its Failing Tests entry and candidate issue I.3, records the HTTP options with the others as an intentional naming-and-unit deviation, keeps the rest of #709 open, and updates its counts for one fewer gated test and REST root cause. This is a breaking change: passing an old name raises TypeError. Callers should rename the option and keep its value, as UPDATING.md shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The two commits before this one rename the eight timing options with a _seconds suffix. Options passes the keyword arguments it does not know on to AuthOptions, so a 3.x name already raised TypeError at construction, but with Python's bare "unexpected keyword argument", which says nothing about the rename or the change of unit. Options now checks for the eight 3.x names before anything else and raises a TypeError naming the replacement. For the five options that took milliseconds it also converts the value passed, for instance "`disconnected_retry_timeout` was renamed to `disconnected_retry_timeout_seconds` in 4.0 and is now in seconds (15000 ms → 15)", falling back to the 3.x default where the value is not a finite number; for the three HTTP options it says the unit is unchanged. The factories reach it through Options, so it covers both clients and Options itself. It is still a TypeError, so nothing that raised before stops raising or raises something else, and a missing credential is still reported first, as 40106. UPDATING.md shows the messages and the eight renames together, and test/uts/deviations.md notes the hint. No UTS test passes an old name, so the counts do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Options stored whatever it was given for a timing option, and nothing checked it until it was used. A string or a negative number failed later or not at all, and a timedelta passed to a realtime option was accepted and then raised a TypeError inside the timer task, where it was only logged, so the retry it was meant to schedule never ran. Options now validates the eight _seconds options once their defaults are resolved, and the setters of the three HTTP ones validate too. Each must be a finite, non-negative int or float; a bool, a string, a negative number, NaN, an infinity or a timedelta raises InvalidArgumentException with status 400 and code 40000, the code push.admin.publish already uses for invalid arguments, which is also a ValueError. A timedelta's message says to pass its total_seconds(). Zero is accepted, and no magnitude is warned about. None still means the default, since validation runs after the defaults are resolved. The factory docstrings name the 40000 and the TypeError a renamed option raises. UPDATING.md and test/uts/deviations.md describe the accepted values. No UTS test passes an invalid value, so the counts do not change. This is a breaking change: a client given an invalid timing option now fails to construct instead of misbehaving later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TK2a counts a token's ttl in milliseconds. A ttl given as a timedelta was converted through total_seconds() and int(), which truncates, so 1,482 of the whole-millisecond values from 0 to 200,000 were sent one millisecond short: timedelta(milliseconds=1001) was requested as 1000. Both conversions did this, the one in create_token_request and the one that sends the token params to an auth_url. Both now go through ttl_ms, which divides by timedelta(milliseconds=1) and so stays in integer arithmetic. A ttl given as a number is sent as before. New unit tests check every millisecond up to 200 seconds and both call sites. No UTS test changes state, so deviations.md does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TP2 makes PresenceAction an enum, and MessageAction and AnnotationAction already are IntEnums that decoding wraps wire ints into. PresenceAction was a plain class of int constants, decoded actions stayed bare ints, and the realtime presence code monkey-patched a name lookup onto the class. Exported as it was, an annotation of PresenceAction would reject PresenceAction.ENTER under a type checker, because that is an int. PresenceAction is now an IntEnum with the same members and values. PresenceMessage.from_encoded wraps a known wire int into its member, so presence received over HTTP, in a batch presence result, or over a realtime connection, and the members the presence map stores, hold PresenceAction members; the LEAVEs and PRESENTs the library builds already used them. An action this version does not know is kept as the value the server sent, as before, so the presence map still ignores it. to_encoded sends int(action), so the wire value stays a plain int in both JSON and MessagePack. The event name a presence message is emitted under now comes from the member's name, through a module function in realtime/presence.py rather than the monkey-patched _action_name. A member still compares and hashes equal to its int, so == 2 keeps working. New unit tests cover the members, decoding of every action over both trees, the HTTP and batch presence paths, encoding to JSON and MessagePack, and the realtime presence map, SYNC and synthesized LEAVEs. No UTS test changes outcome, so deviations.md does not change. The uts-to-python skill's note on presence event names, which described the monkey-patch, now says where the names come from. This is a breaking change: type(action) is int no longer holds, and repr() of an action, and str() before Python 3.11, give the member rather than the number, as UPDATING.md describes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
unasync's rename_classes gives the generated synchronous flavour of a class a Sync suffix, and covered the clients, Auth, Channels, Channel, Push, PushAdmin, Http and the paginated results. It left out the HTTP channel's Presence and HttpAnnotations and the push admin's PushDeviceRegistrations and PushChannelSubscriptions, so those four generated classes shared their __name__ with the async ones, and a sync client handed out ChannelSync, PushAdminSync and so on beside an unsuffixed Presence. The next commit exports the handle classes, and the sync names have to be settled before they become public. The four are added to rename_classes, so the sync tree now has PresenceSync, HttpAnnotationsSync, PushDeviceRegistrationsSync and PushChannelSubscriptionsSync, and every handle a sync client hands out ends in Sync. Data types, enums, exceptions and the PubSubHttpClient prototype keep their names in both trees. Renaming a class changes the name its private attributes are mangled under. Each of the four reads its own private attributes only inside its class body, and nothing in the library or the tests reaches one as _Presence__x, _HttpAnnotations__x, _PushDeviceRegistrations__x or _PushChannelSubscriptions__x, so no _TOKEN_REPLACE entry is needed like the existing _Channel__publish_request_body one; a comment on rename_classes now says when one is. The generated tree differs from before only in those four names and their uses, and the generated test/ably/sync/http/sync_httpchannels_test.py now imports and checks PresenceSync. New unit tests check that every handle class a sync client hands out has the suffix and is the generated class, that none shares a name with its async class, and that the renamed classes read their own private state. The UTS suite does not use the sync tree, so its counts and deviations.md do not change. This is a breaking change for code that names these four sync classes, as UPDATING.md describes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flavour ably.pubsub.server is the only import path 4.0 promises, and UPDATING.md told users to import from it rather than from submodules, but its __all__ had 42 names and left out most of the types a client hands out. A channel.on() listener gets a ChannelStateChange, a connection.on() listener a ConnectionStateChange, presence.get() returns PresenceMessages, channel.status() a ChannelDetails, and a user annotating a parameter as a RealtimeChannel had nowhere public to import it from. Every 3.x user rewrites their imports for 4.0, so whatever is missing that day gets written as an internal path. ably.pubsub.server now also exports ChannelState, ChannelStateChange, ConnectionState, ConnectionEvent and ConnectionStateChange; PresenceMessage, PresenceAction and Stats; TokenRequest, ChannelDetails, ChannelStatus, ChannelOccupancy and ChannelMetrics; the classes of the objects a client hands out, Channel, Presence, HttpAnnotations, RealtimeChannel, RealtimePresence, RealtimeAnnotations, PushAdmin, PushDeviceRegistrations and PushChannelSubscriptions, beside the Push it already exported; and InvalidArgumentException and DevicePushDetails. That is 66 names. The HTTP channel and presence classes keep their own names, Channel and Presence. ably.pubsub.server.sync exports the same set, 54 names, apart from the realtime client's: the five state types, RealtimeChannel, RealtimePresence and RealtimeAnnotations join the four realtime names it already left out. Each export is the generated class under its own name, so the handle classes are ChannelSync, PresenceSync, HttpAnnotationsSync, PushAdminSync, PushDeviceRegistrationsSync and PushChannelSubscriptionsSync, and the data types keep their plain names. Every export, in both modules, is the internal object itself rather than a copy or a subclass. ConnectionDetails, MessageAnnotations, an ErrorInfo alias and ResponseHeaders stay unexported for now. New unit tests pin both __all__ lists, star-import each module, check every export is the object its defining module holds, check every async export has a sync counterpart or is on an explicit realtime-only list, and check the handle classes are the types the clients hand out. UPDATING.md lists the new names with a before and after import, gives the earlier sections' examples their imports, and no longer implies that every type is exported. The UTS suite imports from the internal modules, so its counts and deviations.md do not change. Not a breaking change: names are only added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pubsub: breaking changes to exceptions
pubsub: breaking changes to auth and tokens
pubsub: breaking changes to HTTP and client options
pubsub: breaking changes to values and types returned
pubsub: breaking changes to channels and channel operations
pubsub: breaking changes to timing option names and units
pubsub: breaking changes to exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


Integration branch contains packages split, renaming and deletion of deprecated methods