Skip to content

Conversation

@ttypic
Copy link
Contributor

@ttypic ttypic commented Jan 15, 2026

  • Updated ConnectionManager and MessageQueue to process PublishResult during acknowledgments (ACK/NACK).
  • Extended send_protocol_message to return PublishResult for publish tracking.
  • Bumped default protocol_version to 5.
  • Added tests for message update, delete, append operations, and PublishResult handling.

Summary by CodeRabbit

  • New Features

    • Message publishing now returns detailed acknowledgement results instead of simple confirmation.
    • Added update, delete, and append operations for managing real-time channel messages.
    • Protocol version upgraded to support new message capabilities.
  • Tests

    • New comprehensive test suite for mutable message operations across different transports.

✏️ Tip: You can customize this high-level summary in your review settings.

- Updated `ConnectionManager` and `MessageQueue` to process `PublishResult` during acknowledgments (ACK/NACK).
- Extended `send_protocol_message` to return `PublishResult` for publish tracking.
- Bumped default `protocol_version` to 5.
- Added tests for message update, delete, append operations, and PublishResult handling.
@ttypic ttypic requested a review from owenpearson January 15, 2026 11:45
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Walkthrough

The PR introduces support for mutable messages in realtime channels by upgrading the protocol version to 5, adding message update/delete/append operations, returning PublishResult objects from publish calls, and propagating results through the connection layer with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Protocol & Transport Infrastructure
ably/transport/defaults.py, ably/transport/websockettransport.py
Protocol version bumped from "2" to "5"; ACK handling in websocket transport now extracts and converts optional res field to PublishResult list before passing to connection manager.
Connection Message Handling
ably/realtime/connectionmanager.py
Introduced PublishResult typing; PendingMessage.future now typed as asyncio.Future[PublishResult] | None; complete_messages signature extended with res: list[PublishResult] | None parameter; send_protocol_message and related methods return PublishResult | None; on_ack updated to accept and forward results.
Realtime Channel API
ably/realtime/realtime_channel.py
publish() return type changed from None to PublishResult; added _send_update() internal method handling update/delete/append operations with message versioning; added public convenience methods update_message(), delete_message(), append_message() and REST delegation methods get_message(), get_message_versions(); Channels.get() extended to accept **kwargs for channel options.
Mutable Messages Test Suite
test/ably/realtime/realtimechannelmutablemessages_test.py
New comprehensive test class with 11 test methods covering publish/update/delete/append operations across JSON and MsgPack transports, including success paths, validation failures, encryption, and end-to-end workflows.

Sequence Diagram

sequenceDiagram
    participant App
    participant RealtimeChannel
    participant ConnectionManager
    participant WebSocketTransport
    participant Server

    App->>RealtimeChannel: publish() or update_message()
    RealtimeChannel->>ConnectionManager: send_protocol_message()
    ConnectionManager->>WebSocketTransport: send(ProtocolMessage)
    WebSocketTransport->>Server: WebSocket frame with ACK request

    Server->>WebSocketTransport: ACK with optional res (PublishResult[])
    WebSocketTransport->>ConnectionManager: on_ack(serial, count, res)
    ConnectionManager->>ConnectionManager: complete_messages(serial, count, res)
    ConnectionManager->>ConnectionManager: Pair results via zip_longest()
    ConnectionManager->>App: Return Future[PublishResult]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops through versions, now at five,
Mutable messages come alive!
Update, delete, append with grace,
PublishResults mark the race,
Whiskers twitch—mutable dreams thrive! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary change: adding realtime mutable message support, which is evident in the new update_message, delete_message, and append_message methods.
Linked Issues check ✅ Passed The pull request implements realtime mutable message support with updates to ConnectionManager, PublishResult handling, protocol version bump to 5, and comprehensive test coverage as implied by issue AIT-258.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing realtime mutable message support: ConnectionManager updates for PublishResult, channel methods for message operations, protocol version bump, and integration tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AIT-258/realtime-edits-deletes

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@test/ably/realtime/realtimechannelmutablemessages_test.py`:
- Around line 182-191: The test calls channel.publish with a keyword
(channel.publish(messages=messages)) but the publish implementation inspects
only positional args (checks len(args)), so kwargs are ignored and cause a
ValueError; change the call to pass the messages list positionally
(channel.publish(messages)) so publish receives the messages via args and the
test will exercise publish correctly.
🧹 Nitpick comments (2)
ably/realtime/connectionmanager.py (1)

99-109: Consider handling mismatched lengths between completed messages and results.

Using zip_longest ensures all completed messages are processed even when res_list is shorter or empty. However, if res_list is longer than completed_messages, msg will be None for the extra items, which could cause an AttributeError when accessing msg.future.

♻️ Suggested fix to guard against None msg
                 # Default res to empty list if None
                 res_list = res if res is not None else []
                 for (msg, publish_result) in zip_longest(completed_messages, res_list):
+                    if msg is None:
+                        continue
                     if msg.future and not msg.future.done():
                         if err:
                             msg.future.set_exception(err)
ably/realtime/realtime_channel.py (1)

964-981: Potential ambiguity when both options and kwargs are provided.

The current logic converts kwargs to ChannelOptions only when options is None. If a user accidentally provides both options and kwargs, the kwargs will be silently ignored. Consider raising an error or warning in this case.

♻️ Suggested fix to warn about ignored kwargs
         # Convert kwargs to ChannelOptions if provided
         if kwargs and not options:
             options = ChannelOptions(**kwargs)
+        elif kwargs and options:
+            log.warning("Channels.get(): kwargs ignored when options is provided")
         elif options and isinstance(options, dict):
             options = ChannelOptions.from_dict(options)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff2552 and 7c26134.

📒 Files selected for processing (5)
  • ably/realtime/connectionmanager.py
  • ably/realtime/realtime_channel.py
  • ably/transport/defaults.py
  • ably/transport/websockettransport.py
  • test/ably/realtime/realtimechannelmutablemessages_test.py
🧰 Additional context used
🧬 Code graph analysis (2)
ably/transport/websockettransport.py (2)
ably/realtime/connectionmanager.py (3)
  • ably (780-781)
  • on_ack (468-479)
  • count (59-61)
ably/types/operations.py (1)
  • PublishResult (48-67)
ably/realtime/connectionmanager.py (2)
ably/types/operations.py (1)
  • PublishResult (48-67)
ably/util/exceptions.py (1)
  • AblyException (9-90)
🪛 Gitleaks (8.30.0)
test/ably/realtime/realtimechannelmutablemessages_test.py

[high] 158-158: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: check (3.14)
  • GitHub Check: check (3.12)
  • GitHub Check: check (3.11)
  • GitHub Check: check (3.8)
  • GitHub Check: check (3.13)
  • GitHub Check: check (3.10)
  • GitHub Check: check (3.9)
🔇 Additional comments (19)
ably/transport/websockettransport.py (2)

15-15: LGTM!

Import of PublishResult is correctly added to support the new ACK handling flow.


176-179: LGTM!

The ACK handling correctly extracts the optional res field, converts each item to a PublishResult object when present, and passes it to the connection manager. The null-safety check before conversion is appropriate.

ably/realtime/connectionmanager.py (7)

7-7: LGTM!

Import of zip_longest is appropriate for aligning completed messages with their corresponding PublishResult entries.


34-34: LGTM!

Updated type annotation correctly reflects that the future now resolves to a PublishResult instead of None.


63-78: LGTM!

The updated signature correctly accepts the res parameter and the docstring accurately describes the new behavior.


216-253: LGTM!

The send_protocol_message method correctly returns the PublishResult when awaiting ACK-required messages and None otherwise. The flow handles both queued and immediate sends appropriately.


255-273: LGTM!

The _send_protocol_message_on_connected_state method correctly propagates the PublishResult from the future when ACK is required.


468-479: LGTM!

The on_ack method correctly accepts and forwards the res parameter to complete_messages.


493-493: LGTM!

Correctly passes None for res and the error to complete_messages for NACK handling.

ably/realtime/realtime_channel.py (5)

13-15: LGTM!

Imports are correctly added to support the new mutable message operations.


394-494: LGTM!

The publish method correctly returns the PublishResult from the connection manager, enabling callers to access message serials for subsequent update/delete operations.


526-606: Consider using MessageAction enum for action.name in log statement.

The _send_update implementation correctly validates serial presence, constructs the message with version metadata, handles encryption, and returns the UpdateDeleteResult. The logic is sound.

One minor observation: Line 588 uses action.name in the log, which assumes MessageAction is an enum with a name attribute - this should work correctly with Python enums.


608-672: LGTM!

The public convenience methods update_message, delete_message, and append_message are well-documented and correctly delegate to _send_update with the appropriate MessageAction values.


674-718: LGTM!

The REST delegation methods correctly call the parent Channel methods, providing a unified API for accessing messages via the REST API from the realtime channel.

test/ably/realtime/realtimechannelmutablemessages_test.py (4)

15-23: LGTM!

Good use of parametrization to test both JSON and MsgPack transports. The fixture properly configures the Ably client based on the transport type.


154-176: Test key is appropriate for testing - not a real secret.

The static analysis tool flagged 'keyfordecrypt_16' as a potential API key, but this is a test encryption key used only in unit tests and is not a real secret. This is a false positive.


266-278: LGTM!

The helper method wait_until_message_with_action_appears correctly uses polling to wait for a message with a specific action, with proper exception handling.


280-289: LGTM!

The helper method wait_until_get_all_message_version correctly polls for version history until the expected count is reached.

ably/transport/defaults.py (1)

2-2: Protocol version 5 is properly supported and already tested.

Ably Protocol v5 is officially supported with comprehensive test coverage for mutable messages (updates, deletes, appends) in both REST and realtime channels. The integration in connectionmanager.py correctly passes the protocol version, and the test infrastructure includes the required mutable messages support.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +182 to +191
messages = [
Message('event1', 'data1'),
Message('event2', 'data2'),
Message('event3', 'data3')
]

result = await channel.publish(messages=messages)
assert result is not None
assert hasattr(result, 'serials')
assert len(result.serials) == 3
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "realtime_channel.py" -type f

Repository: ably/ably-python

Length of output: 95


🏁 Script executed:

ast-grep --pattern $'async def publish(self, $$$) {
  $$$
}'

Repository: ably/ably-python

Length of output: 42


🏁 Script executed:

ast-grep --pattern $'async def publish(self, $_):
  $$$
'

Repository: ably/ably-python

Length of output: 42


🏁 Script executed:

wc -l ./ably/realtime/realtime_channel.py

Repository: ably/ably-python

Length of output: 100


🏁 Script executed:

rg -n "async def publish" ./ably/realtime/realtime_channel.py -A 5

Repository: ably/ably-python

Length of output: 299


🏁 Script executed:

sed -n '394,450p' ./ably/realtime/realtime_channel.py

Repository: ably/ably-python

Length of output: 2380


The channel.publish(messages=messages) call at line 188 is incorrect.

The publish method signature at realtime_channel.py:394 accepts *args and **kwargs, but the implementation only processes positional arguments via len(args). When called with a keyword argument messages=messages, args will be empty, causing a ValueError: publish() expects either (name, data) or a message object or array of messages.

The correct invocation is:

result = await channel.publish(messages)
🤖 Prompt for AI Agents
In `@test/ably/realtime/realtimechannelmutablemessages_test.py` around lines 182 -
191, The test calls channel.publish with a keyword
(channel.publish(messages=messages)) but the publish implementation inspects
only positional args (checks len(args)), so kwargs are ignored and cause a
ValueError; change the call to pass the messages list positionally
(channel.publish(messages)) so publish receives the messages via args and the
test will exercise publish correctly.

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.

2 participants