Skip to content

Integration branch for ably-pubsub-server v4 package - #693

Merged
ttypic merged 83 commits into
mainfrom
integration/v4
Nov 2, 2026
Merged

ttypic merged 83 commits into
mainfrom
integration/v4

Conversation

@ttypic

@ttypic ttypic commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

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

@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too 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.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: be3be40d-bf89-4a73-9cda-f071d46d21d4
Commits

Reviewing files that changed from the base of the PR and between 7932088 and 3088bb1.

Files ignored due to path filters: 1
  • uv.lock is excluded by !**/*.lock
Files selected for processing: 321
  • .claude/skills/release/SKILL.md
  • .claude/skills/uts-to-python/SKILL.md
  • .github/workflows/release.yml
  • .gitignore
  • CLAUDE.md
  • CONTRIBUTING.md
  • LONG_DESCRIPTION.rst
  • README.md
  • UPDATING.md
  • ably/__init__.py
  • ably/pubsub/http/__init__.py
  • ably/pubsub/http/annotations.py
  • ably/pubsub/http/auth.py
  • ably/pubsub/http/channel.py
  • ably/pubsub/http/http.py
  • ably/pubsub/http/push.py
  • ably/pubsub/liveobjects/__init__.py
  • ably/pubsub/liveobjects/batch.py
  • ably/pubsub/liveobjects/defaults.py
  • ably/pubsub/liveobjects/enums.py
  • ably/pubsub/liveobjects/instance.py
  • ably/pubsub/liveobjects/livecounter.py
  • ably/pubsub/liveobjects/livemap.py
  • ably/pubsub/liveobjects/liveobject.py
  • ably/pubsub/liveobjects/objectid.py
  • ably/pubsub/liveobjects/objectmessage.py
  • ably/pubsub/liveobjects/objectspool.py
  • ably/pubsub/liveobjects/pathobject.py
  • ably/pubsub/liveobjects/pathobjectsubscriptionregister.py
  • ably/pubsub/liveobjects/publicmessage.py
  • ably/pubsub/liveobjects/realtimeobject.py
  • ably/pubsub/liveobjects/subscription.py
  • ably/pubsub/liveobjects/syncobjectspool.py
  • ably/pubsub/liveobjects/valuetypes.py
  • ably/pubsub/prototypes.py
  • ably/pubsub/realtime/__init__.py
  • ably/pubsub/realtime/annotations.py
  • ably/pubsub/realtime/channel.py
  • ably/pubsub/realtime/connection.py
  • ably/pubsub/realtime/connectionmanager.py
  • ably/pubsub/realtime/presence.py
  • ably/pubsub/realtime/presencemap.py
  • ably/pubsub/realtime/realtime.py
  • ably/pubsub/request/__init__.py
  • ably/pubsub/request/http.py
  • ably/pubsub/request/httputils.py
  • ably/pubsub/request/paginatedresult.py
  • ably/pubsub/scripts/__init__.py
  • ably/pubsub/scripts/unasync.py
  • ably/pubsub/server/__init__.py
  • ably/pubsub/server/liveobjects.py
  • ably/pubsub/server/sync.py
  • ably/pubsub/transport/__init__.py
  • ably/pubsub/transport/defaults.py
  • ably/pubsub/transport/websockettransport.py
  • ably/pubsub/types/__init__.py
  • ably/pubsub/types/annotation.py
  • ably/pubsub/types/authoptions.py
  • ably/pubsub/types/batch.py
  • ably/pubsub/types/capability.py
  • ably/pubsub/types/channeldetails.py
  • ably/pubsub/types/channelmode.py
  • ably/pubsub/types/channeloptions.py
  • ably/pubsub/types/channelstate.py
  • ably/pubsub/types/channelsubscription.py
  • ably/pubsub/types/connectiondetails.py
  • ably/pubsub/types/connectionerrors.py
  • ably/pubsub/types/connectionstate.py
  • ably/pubsub/types/device.py
  • ably/pubsub/types/flags.py
  • ably/pubsub/types/message.py
  • ably/pubsub/types/mixins.py
  • ably/pubsub/types/operations.py
  • ably/pubsub/types/options.py
  • ably/pubsub/types/presence.py
  • ably/pubsub/types/stats.py
  • ably/pubsub/types/testoptions.py
  • ably/pubsub/types/tokendetails.py
  • ably/pubsub/types/tokenrequest.py
  • ably/pubsub/types/typedbuffer.py
  • ably/pubsub/util/__init__.py
  • ably/pubsub/util/case.py
  • ably/pubsub/util/clock.py
  • ably/pubsub/util/construction.py
  • ably/pubsub/util/crypto.py
  • ably/pubsub/util/encoding.py
  • ably/pubsub/util/eventemitter.py
  • ably/pubsub/util/exceptions.py
  • ably/pubsub/util/helper.py
  • ably/pubsub/util/nocrypto.py
  • ably/pubsub/vcdiff/__init__.py
  • ably/pubsub/vcdiff/defaultvcdiffdecoder.py
  • ably/pubsub/version.py
  • ably/rest/rest.py
  • ably/types/options.py
  • pyproject.toml
  • test/ably/http/authheaders_test.py
  • test/ably/http/authorizeparams_test.py
  • test/ably/http/authurlparams_test.py
  • test/ably/http/encoders_test.py
  • test/ably/http/httpannotations_test.py
  • test/ably/http/httpauth_test.py
  • test/ably/http/httpbatch_test.py
  • test/ably/http/httpcapability_test.py
  • test/ably/http/httpchannelhistory_test.py
  • test/ably/http/httpchannelmutablemessages_test.py
  • test/ably/http/httpchannelpublish_test.py
  • test/ably/http/httpchannels_test.py
  • test/ably/http/httpchannelstatus_test.py
  • test/ably/http/httpcrypto_test.py
  • test/ably/http/httphttp_test.py
  • test/ably/http/httpinit_test.py
  • test/ably/http/httppaginatedresult_test.py
  • test/ably/http/httppresence_test.py
  • test/ably/http/httppush_test.py
  • test/ably/http/httprequest_test.py
  • test/ably/http/httpstats_test.py
  • test/ably/http/httptime_test.py
  • test/ably/http/httptoken_test.py
  • test/ably/realtime/deltadecoding_test.py
  • test/ably/realtime/eventemitter_test.py
  • test/ably/realtime/presencemap_test.py
  • test/ably/realtime/realtimeannotations_test.py
  • test/ably/realtime/realtimeauth_test.py
  • test/ably/realtime/realtimechannel_publish_test.py
  • test/ably/realtime/realtimechannel_test.py
  • test/ably/realtime/realtimechannel_vcdiff_test.py
  • test/ably/realtime/realtimechannelmutablemessages_test.py
  • test/ably/realtime/realtimeconnection_test.py
  • test/ably/realtime/realtimeinit_test.py
  • test/ably/realtime/realtimepresence_test.py
  • test/ably/realtime/realtimeresume_test.py
  • test/ably/testapp.py
  • test/ably/utils.py
  • test/unit/annotation_test.py
  • test/unit/batch_test.py
  • test/unit/channel_options_test.py
  • test/unit/channels_release_test.py
  • test/unit/clock_test.py
  • test/unit/connectionmanager_test.py
  • test/unit/http_test.py
  • test/unit/liveobjects/__init__.py
  • test/unit/liveobjects/batch_test.py
  • test/unit/liveobjects/conftest.py
  • test/unit/liveobjects/realtime_object_test.py
  • test/unit/liveobjects/subscription_test.py
  • test/unit/liveobjects/typed_views_test.py
  • test/unit/liveobjects/value_types_test.py
  • test/unit/message_test.py
  • test/unit/mutable_message_test.py
  • test/unit/options_test.py
  • test/unit/paginatedresult_test.py
  • test/unit/presence_action_test.py
  • test/unit/pubsub_server_test.py
  • test/unit/push_types_test.py
  • test/unit/stats_test.py
  • test/unit/sync_class_names_test.py
  • test/unit/token_ttl_test.py
  • test/unit/websockettransport_test.py
  • test/uts/README.md
  • test/uts/deviations.md
  • test/uts/helpers/client.py
  • test/uts/helpers/clock.py
  • test/uts/helpers/mock_http_test.py
  • test/uts/helpers/mock_websocket.py
  • test/uts/helpers/mock_websocket_test.py
  • test/uts/helpers/presence.py
  • test/uts/helpers/sandbox.py
  • test/uts/objects/__init__.py
  • test/uts/objects/helpers/__init__.py
  • test/uts/objects/helpers/standard_test_pool.py
  • test/uts/objects/helpers/standard_test_pool_test.py
  • test/uts/objects/integration/__init__.py
  • test/uts/objects/integration/conftest.py
  • test/uts/objects/integration/objects_gc_test.py
  • test/uts/objects/integration/objects_lifecycle_test.py
  • test/uts/objects/integration/objects_sync_test.py
  • test/uts/objects/integration/proxy/__init__.py
  • test/uts/objects/integration/proxy/conftest.py
  • test/uts/objects/integration/proxy/objects_faults_test.py
  • test/uts/objects/unit/__init__.py
  • test/uts/objects/unit/instance_test.py
  • test/uts/objects/unit/internal_live_counter_api_test.py
  • test/uts/objects/unit/internal_live_counter_test.py
  • test/uts/objects/unit/internal_live_map_api_test.py
  • test/uts/objects/unit/internal_live_map_test.py
  • test/uts/objects/unit/live_object_subscribe_test.py
  • test/uts/objects/unit/object_id_test.py
  • test/uts/objects/unit/objects_pool_test.py
  • test/uts/objects/unit/parent_references_test.py
  • test/uts/objects/unit/path_object_mutations_test.py
  • test/uts/objects/unit/path_object_subscribe_test.py
  • test/uts/objects/unit/path_object_test.py
  • test/uts/objects/unit/public_object_message_test.py
  • test/uts/objects/unit/realtime_object_test.py
  • test/uts/objects/unit/value_types_test.py
  • test/uts/realtime/integration/auth/token_renewal_test.py
  • test/uts/realtime/integration/auth/token_request_test.py
  • test/uts/realtime/integration/auth_test.py
  • test/uts/realtime/integration/channel_history_test.py
  • test/uts/realtime/integration/channels/channel_attach_test.py
  • test/uts/realtime/integration/channels/channel_publish_test.py
  • test/uts/realtime/integration/channels/channel_subscribe_test.py
  • test/uts/realtime/integration/connection/connection_failures_test.py
  • test/uts/realtime/integration/connection_lifecycle_test.py
  • test/uts/realtime/integration/delta_decoding_test.py
  • test/uts/realtime/integration/mutable_messages_test.py
  • test/uts/realtime/integration/presence/presence_sync_test.py
  • test/uts/realtime/integration/presence_lifecycle_test.py
  • test/uts/realtime/integration/proxy/auth_reauth_test.py
  • test/uts/realtime/integration/proxy/channel_faults_test.py
  • test/uts/realtime/integration/proxy/connection_open_failures_test.py
  • test/uts/realtime/integration/proxy/connection_resume_test.py
  • test/uts/realtime/integration/proxy/heartbeat_test.py
  • test/uts/realtime/integration/proxy/presence_reentry_test.py
  • test/uts/realtime/integration/proxy/rest_faults_test.py
  • test/uts/realtime/unit/auth/auth_callback_errors_test.py
  • test/uts/realtime/unit/auth/connection_auth_test.py
  • test/uts/realtime/unit/auth/realtime_authorize_test.py
  • test/uts/realtime/unit/auth/token_expiry_non_renewable_test.py
  • test/uts/realtime/unit/channels/channel_additional_attached_test.py
  • test/uts/realtime/unit/channels/channel_annotations_test.py
  • test/uts/realtime/unit/channels/channel_attach_test.py
  • test/uts/realtime/unit/channels/channel_attributes_test.py
  • test/uts/realtime/unit/channels/channel_connection_state_test.py
  • test/uts/realtime/unit/channels/channel_delta_decoding_test.py
  • test/uts/realtime/unit/channels/channel_detach_test.py
  • test/uts/realtime/unit/channels/channel_error_test.py
  • test/uts/realtime/unit/channels/channel_get_message_test.py
  • test/uts/realtime/unit/channels/channel_history_test.py
  • test/uts/realtime/unit/channels/channel_message_versions_test.py
  • test/uts/realtime/unit/channels/channel_options_test.py
  • test/uts/realtime/unit/channels/channel_properties_test.py
  • test/uts/realtime/unit/channels/channel_publish_pending_test.py
  • test/uts/realtime/unit/channels/channel_publish_test.py
  • test/uts/realtime/unit/channels/channel_server_initiated_detach_test.py
  • test/uts/realtime/unit/channels/channel_state_events_test.py
  • test/uts/realtime/unit/channels/channel_subscribe_test.py
  • test/uts/realtime/unit/channels/channel_update_delete_message_test.py
  • test/uts/realtime/unit/channels/channel_when_state_test.py
  • test/uts/realtime/unit/channels/channels_collection_test.py
  • test/uts/realtime/unit/channels/message_field_population_test.py
  • test/uts/realtime/unit/client/realtime_client_test.py
  • test/uts/realtime/unit/client/realtime_request_test.py
  • test/uts/realtime/unit/client/realtime_stats_test.py
  • test/uts/realtime/unit/client/realtime_time_test.py
  • test/uts/realtime/unit/client/realtime_timeouts_test.py
  • test/uts/realtime/unit/connection/auto_connect_test.py
  • test/uts/realtime/unit/connection/backoff_jitter_test.py
  • test/uts/realtime/unit/connection/connection_failures_test.py
  • test/uts/realtime/unit/connection/connection_id_key_test.py
  • test/uts/realtime/unit/connection/connection_open_failures_test.py
  • test/uts/realtime/unit/connection/connection_ping_test.py
  • test/uts/realtime/unit/connection/connection_recovery_test.py
  • test/uts/realtime/unit/connection/error_reason_test.py
  • test/uts/realtime/unit/connection/fallback_hosts_test.py
  • test/uts/realtime/unit/connection/forwards_compatibility_test.py
  • test/uts/realtime/unit/connection/heartbeat_test.py
  • test/uts/realtime/unit/connection/server_initiated_reauth_test.py
  • test/uts/realtime/unit/connection/update_events_test.py
  • test/uts/realtime/unit/connection/when_state_test.py
  • test/uts/realtime/unit/presence/local_presence_map_test.py
  • test/uts/realtime/unit/presence/presence_map_test.py
  • test/uts/realtime/unit/presence/presence_sync_test.py
  • test/uts/realtime/unit/presence/realtime_presence_channel_state_test.py
  • test/uts/realtime/unit/presence/realtime_presence_enter_test.py
  • test/uts/realtime/unit/presence/realtime_presence_get_test.py
  • test/uts/realtime/unit/presence/realtime_presence_history_test.py
  • test/uts/realtime/unit/presence/realtime_presence_reentry_test.py
  • test/uts/realtime/unit/presence/realtime_presence_subscribe_test.py
  • test/uts/rest/integration/auth_test.py
  • test/uts/rest/integration/batch_presence_test.py
  • test/uts/rest/integration/mutable_messages_test.py
  • test/uts/rest/integration/pagination_test.py
  • test/uts/rest/integration/presence_test.py
  • test/uts/rest/integration/proxy/rest_fallback_test.py
  • test/uts/rest/integration/publish_test.py
  • test/uts/rest/integration/push_admin_test.py
  • test/uts/rest/integration/push_channels_test.py
  • test/uts/rest/integration/revoke_tokens_test.py
  • test/uts/rest/integration/time_stats_test.py
  • test/uts/rest/unit/auth/auth_callback_test.py
  • test/uts/rest/unit/auth/auth_scheme_test.py
  • test/uts/rest/unit/auth/authorize_test.py
  • test/uts/rest/unit/auth/client_id_test.py
  • test/uts/rest/unit/auth/revoke_tokens_test.py
  • test/uts/rest/unit/auth/token_details_test.py
  • test/uts/rest/unit/auth/token_renewal_test.py
  • test/uts/rest/unit/auth/token_request_params_test.py
  • test/uts/rest/unit/batch_presence_test.py
  • test/uts/rest/unit/batch_publish_test.py
  • test/uts/rest/unit/channel/annotations_test.py
  • test/uts/rest/unit/channel/get_message_test.py
  • test/uts/rest/unit/channel/history_test.py
  • test/uts/rest/unit/channel/idempotency_test.py
  • test/uts/rest/unit/channel/message_versions_test.py
  • test/uts/rest/unit/channel/publish_result_test.py
  • test/uts/rest/unit/channel/publish_test.py
  • test/uts/rest/unit/channel/rest_channel_attributes_test.py
  • test/uts/rest/unit/channel/update_delete_message_test.py
  • test/uts/rest/unit/channels_collection_test.py
  • test/uts/rest/unit/encoding/message_encoding_test.py
  • test/uts/rest/unit/encoding/msgpack_interop_test.py
  • test/uts/rest/unit/fallback_test.py
  • test/uts/rest/unit/logging_test.py
  • test/uts/rest/unit/presence/rest_presence_test.py
  • test/uts/rest/unit/push/push_admin_publish_test.py
  • test/uts/rest/unit/push/push_channel_subscriptions_test.py
  • test/uts/rest/unit/push/push_channels_test.py
  • test/uts/rest/unit/push/push_device_registrations_test.py
  • test/uts/rest/unit/request_test.py
  • test/uts/rest/unit/rest_client_test.py
  • test/uts/rest/unit/stats_test.py
  • test/uts/rest/unit/time_test.py
  • test/uts/rest/unit/types/error_types_test.py
  • test/uts/rest/unit/types/message_types_test.py
  • test/uts/rest/unit/types/mutable_message_types_test.py
  • test/uts/rest/unit/types/options_types_test.py
  • test/uts/rest/unit/types/paginated_result_test.py
  • test/uts/rest/unit/types/presence_message_types_test.py
  • test/uts/rest/unit/types/token_types_test.py

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Review effort: Lite
Findings: 1 High severity · 4 Medium severity

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.

Comment thread .github/workflows/release.yml Resolved
Comment thread README.md Resolved
Comment thread UPDATING.md Resolved
Comment thread ably/pubsub/version.py Resolved
Comment thread pyproject.toml Resolved
ttypic and others added 8 commits October 8, 2026 15:37
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>
owenpearson and others added 9 commits October 8, 2026 17:09
`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>
owenpearson and others added 24 commits October 28, 2026 10:43
…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
@ttypic
ttypic requested a review from owenpearson October 30, 2026 18:03
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ttypic
ttypic merged commit 55a891e into main Nov 2, 2026
10 checks passed
@ttypic
ttypic deleted the integration/v4 branch November 2, 2026 11:09

This branch was successfully deployed

1 active deployment
staging/pull/693/features — 3088bb10 Deployed Nov 2, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants