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

    • Publish now returns detailed server acknowledgement results.
    • Add message update, delete, and append operations plus retrieval of message versions.
    • Channel get() accepts additional channel option forms and preserves reattachment behavior.
    • Protocol version upgraded to enable new message capabilities.
  • Tests

    • New comprehensive tests covering mutable message operations (publish/update/delete/append) across transports and encryption.

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

@ttypic ttypic requested a review from owenpearson January 15, 2026 11:45
@coderabbitai
Copy link

coderabbitai bot commented Jan 15, 2026

Walkthrough

Adds realtime mutable-message support: protocol bumped to 5, publish now returns server PublishResult, new update/delete/append message flows, ACK handling extended to propagate PublishResult lists, and tests added for mutable-message behaviors.

Changes

Cohort / File(s) Summary
Protocol & Transport
ably/transport/defaults.py, ably/transport/websockettransport.py
Protocol version changed from "2" to "5". WebSocket transport now parses optional res from ACK ProtocolMessages into PublishResult instances and forwards res to ConnectionManager.on_ack.
Connection Layer
ably/realtime/connectionmanager.py
Introduced PublishResult typing across pending message futures; complete_messages(...) now accepts `res: list[PublishResult]
Realtime Channel API
ably/realtime/realtime_channel.py
publish() now returns PublishResult. Added _send_update() to perform update/delete/append via websocket and public helpers update_message(), delete_message(), append_message(). Added REST delegation stubs get_message() and get_message_versions(). Channels.get() accepts extra channel option kwargs.
Tests
test/ably/realtime/realtimechannelmutablemessages_test.py, test/ably/realtime/realtimechannel_publish_test.py
New comprehensive pytest suite exercising publish/update/delete/append across transports and edge cases; updated publish test call-sites to supply res arg to on_ack where needed.

Sequence Diagram(s)

sequenceDiagram
    participant App as App
    participant Channel as RealtimeChannel
    participant Conn as ConnectionManager
    participant WS as WebSocketTransport
    participant Server as Server

    App->>Channel: publish() / update_message() / delete_message() / append_message()
    Channel->>Conn: send_protocol_message(ProtocolMessage, await_ack=True)
    Conn->>WS: emit WebSocket frame
    WS->>Server: send ProtocolMessage
    Server->>WS: ACK (serial, count, optional res[])
    WS->>Conn: on_ack(serial, count, res[])
    Conn->>Conn: complete_messages(serial, count, res)
    Conn->>Channel: resolve pending futures with PublishResult(s)
    Channel->>App: return PublishResult / UpdateDeleteResult
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hopped from two up to five tonight,

Messages mutable, shiny and bright.
PublishResult squeaks, ACKs in a row,
Update, delete, append — watch them grow.
Thump-thump, the rabbit celebrates the flow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.85% 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 'AIT-258 feat: add Realtime mutable message support' directly and clearly describes the main feature being added in the PR.
Linked Issues check ✅ Passed The PR implements support for mutable messages in Realtime, including update, delete, and append operations as well as PublishResult tracking for messages.
Out of Scope Changes check ✅ Passed All changes directly support realtime mutable message functionality: ConnectionManager updates for PublishResult handling, RealtimeChannel methods for update/delete/append, protocol version bump for v5 support, and comprehensive 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.

@github-actions github-actions bot temporarily deployed to staging/pull/660/features January 15, 2026 11:46 Inactive
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.

@ttypic ttypic force-pushed the AIT-196/rest-edits-deletes branch from 8ff2552 to 393693c Compare January 16, 2026 16:58
- 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.
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/ably/realtime/realtimechannel_publish_test.py (1)

456-458: Assertion may fail after the PublishResult changes.

With the updated complete_messages implementation, when res is None, the future is resolved with an empty PublishResult() object, not None. This assertion should be updated.

🔧 Suggested fix
         # Future should be resolved
         result = await asyncio.wait_for(publish_future, timeout=1)
-        assert result is None
+        assert result is not None
+        assert result.serials == []
♻️ Duplicate comments (1)
test/ably/realtime/realtimechannelmutablemessages_test.py (1)

182-191: The channel.publish(messages=messages) call is incorrect.

The publish method only processes positional arguments via len(args). Calling with messages=messages as a keyword argument will result in an empty args tuple, causing a ValueError.

🔧 Suggested fix
-        result = await channel.publish(messages=messages)
+        result = await channel.publish(messages)

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