Skip to content

feat: python sdk batch operations & smart batching#3

Merged
vieiralucas merged 1 commit intomainfrom
feat/26.3-batch-smart-batching
Mar 24, 2026
Merged

feat: python sdk batch operations & smart batching#3
vieiralucas merged 1 commit intomainfrom
feat/26.3-batch-smart-batching

Conversation

@vieiralucas
Copy link
Copy Markdown
Member

@vieiralucas vieiralucas commented Mar 24, 2026

Summary

  • Add batch_enqueue() method for explicit multi-message RPCs on both sync and async clients
  • Add smart batching via BatchMode enum (AUTO/DISABLED) and Linger dataclass, routing enqueue() through a background batcher thread by default
  • Add delivery batching: consume stream transparently unpacks ConsumeResponse.messages repeated field with backward-compatible fallback to singular message field
  • Single-item optimization: when flushing 1 message, use singular Enqueue RPC to preserve error types (QueueNotFoundError vs BatchEnqueueError)
  • close() drains pending batched messages before disconnecting
  • Update proto to include BatchEnqueue RPC and ConsumeResponse.messages repeated field
  • 16 new unit tests (batcher mechanics, flush functions, batch mode types) + 9 integration tests (batch enqueue, smart batching modes)

Test plan

  • Unit tests for _flush_single and _flush_batch with mock stubs
  • Unit tests for AutoBatcher (single message, concurrent batching, close drains, stub update)
  • Unit tests for LingerBatcher (batch size trigger, linger timeout, close drains)
  • Unit tests for BatchMode, Linger, BatchEnqueueResult types
  • Integration tests for explicit batch_enqueue() (multiple messages, single message, consume verification)
  • Integration tests for async batch_enqueue()
  • Integration tests for smart batching modes (AUTO, DISABLED, LINGER, default-is-AUTO)
  • Existing integration tests still pass (enqueue/consume/ack lifecycle, nack redelivery, API key auth)
  • ruff lint clean, mypy clean (no new errors)

Generated with Claude Code


Summary by cubic

Add explicit batch_enqueue() to the Python SDK and enable smart batching by default (AUTO) for enqueue(). Also add batched delivery in consume() and update the proto with BatchEnqueue and ConsumeResponse.messages.

  • New Features

    • Client.batch_enqueue() and AsyncClient.batch_enqueue() send many messages in one RPC; return per-message BatchEnqueueResult.
    • Smart batching via BatchMode (AUTO default, DISABLED) and Linger; background batcher with single-item optimization; close() drains pending; reconnect updates batcher stub.
    • Delivery batching: consume() transparently iterates ConsumeResponse.messages with fallback to singular message.
    • Proto changes: add BatchEnqueue RPC and ConsumeResponse.messages; new BatchEnqueueError; public exports updated.
  • Migration

    • enqueue() now routes through a background batcher by default; disable with batch_mode=BatchMode.DISABLED, or use Linger(linger_ms, batch_size) for timer-based batching.
    • Call close() to flush any pending batched messages before shutdown.
    • No changes needed for consumers; batched deliveries are unpacked automatically.

Written for commit dcac3da. Summary will update on new commits.

add batch_enqueue() for explicit multi-message RPCs, smart batching via
BatchMode (AUTO/DISABLED/Linger) that routes enqueue() through a background
batcher thread, and delivery batching that unpacks ConsumeResponse.messages
repeated field. update proto to include BatchEnqueue RPC and ConsumeResponse
batched messages field. single-item optimization uses singular Enqueue RPC
to preserve error types. close() drains pending messages before disconnecting.
@vieiralucas vieiralucas merged commit c9a83fc into main Mar 24, 2026
2 of 3 checks passed
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 13 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="fila/errors.py">

<violation number="1" location="fila/errors.py:75">
P2: `_map_batch_enqueue_error()` does not return `BatchEnqueueError` for RPC-level batch failures, despite the new exception being documented for exactly that case.</violation>
</file>

<file name="fila/v1/service_pb2_grpc.py">

<violation number="1" location="fila/v1/service_pb2_grpc.py:8">
P2: This generated version pin raises the SDK’s runtime minimum to grpcio 1.78.1, which can break users on the declared supported range (>=1.60.0) at import time.</violation>
</file>

<file name="fila/v1/messages_pb2_grpc.py">

<violation number="1" location="fila/v1/messages_pb2_grpc.py:7">
P2: This change raises the minimum runtime grpcio version to 1.78.1 and can break imports for users on 1.78.0 due to the generated runtime version guard.</violation>
</file>

<file name="fila/batcher.py">

<violation number="1" location="fila/batcher.py:102">
P1: Futures left permanently unresolved when server returns fewer results than batch size. If `len(resp.results) < len(batch)`, the remaining futures never get a result or exception, causing callers to block forever on `.result()`. Add a fallback after the loop to fail any unmatched futures.</violation>
</file>

<file name="tests/test_batcher.py">

<violation number="1" location="tests/test_batcher.py:243">
P2: This assertion is too weak for a batching behavior test; it can pass even when messages are not batched.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread fila/batcher.py
return

# Pair each result with its request future.
for i, result in enumerate(resp.results):
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

P1: Futures left permanently unresolved when server returns fewer results than batch size. If len(resp.results) < len(batch), the remaining futures never get a result or exception, causing callers to block forever on .result(). Add a fallback after the loop to fail any unmatched futures.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At fila/batcher.py, line 102:

<comment>Futures left permanently unresolved when server returns fewer results than batch size. If `len(resp.results) < len(batch)`, the remaining futures never get a result or exception, causing callers to block forever on `.result()`. Add a fallback after the loop to fail any unmatched futures.</comment>

<file context>
@@ -0,0 +1,267 @@
+        return
+
+    # Pair each result with its request future.
+    for i, result in enumerate(resp.results):
+        if i >= len(batch):
+            break
</file context>
Fix with Cubic

Comment thread fila/errors.py
code = err.code()
if code == grpc.StatusCode.NOT_FOUND:
return QueueNotFoundError(f"batch_enqueue: {err.details()}")
return RPCError(code, err.details() or "")
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

P2: _map_batch_enqueue_error() does not return BatchEnqueueError for RPC-level batch failures, despite the new exception being documented for exactly that case.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At fila/errors.py, line 75:

<comment>`_map_batch_enqueue_error()` does not return `BatchEnqueueError` for RPC-level batch failures, despite the new exception being documented for exactly that case.</comment>

<file context>
@@ -56,3 +65,11 @@ def _map_nack_error(err: grpc.RpcError) -> FilaError:
+    code = err.code()
+    if code == grpc.StatusCode.NOT_FOUND:
+        return QueueNotFoundError(f"batch_enqueue: {err.details()}")
+    return RPCError(code, err.details() or "")
</file context>
Fix with Cubic

from fila.v1 import service_pb2 as fila_dot_v1_dot_service__pb2

GRPC_GENERATED_VERSION = '1.78.0'
GRPC_GENERATED_VERSION = '1.78.1'
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

P2: This generated version pin raises the SDK’s runtime minimum to grpcio 1.78.1, which can break users on the declared supported range (>=1.60.0) at import time.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At fila/v1/service_pb2_grpc.py, line 8:

<comment>This generated version pin raises the SDK’s runtime minimum to grpcio 1.78.1, which can break users on the declared supported range (>=1.60.0) at import time.</comment>

<file context>
@@ -5,7 +5,7 @@
 from fila.v1 import service_pb2 as fila_dot_v1_dot_service__pb2
 
-GRPC_GENERATED_VERSION = '1.78.0'
+GRPC_GENERATED_VERSION = '1.78.1'
 GRPC_VERSION = grpc.__version__
 _version_not_supported = False
</file context>
Fix with Cubic



GRPC_GENERATED_VERSION = '1.78.0'
GRPC_GENERATED_VERSION = '1.78.1'
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

P2: This change raises the minimum runtime grpcio version to 1.78.1 and can break imports for users on 1.78.0 due to the generated runtime version guard.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At fila/v1/messages_pb2_grpc.py, line 7:

<comment>This change raises the minimum runtime grpcio version to 1.78.1 and can break imports for users on 1.78.0 due to the generated runtime version guard.</comment>

<file context>
@@ -4,7 +4,7 @@
 
 
-GRPC_GENERATED_VERSION = '1.78.0'
+GRPC_GENERATED_VERSION = '1.78.1'
 GRPC_VERSION = grpc.__version__
 _version_not_supported = False
</file context>
Fix with Cubic

Comment thread tests/test_batcher.py
# Either BatchEnqueue or multiple Enqueue calls will resolve things.
for _i, f in enumerate(futures):
result = f.result(timeout=5.0)
assert result is not None
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Choose a reason for hiding this comment

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

P2: This assertion is too weak for a batching behavior test; it can pass even when messages are not batched.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/test_batcher.py, line 243:

<comment>This assertion is too weak for a batching behavior test; it can pass even when messages are not batched.</comment>

<file context>
@@ -0,0 +1,333 @@
+        # Either BatchEnqueue or multiple Enqueue calls will resolve things.
+        for _i, f in enumerate(futures):
+            result = f.result(timeout=5.0)
+            assert result is not None
+
+        batcher.close()
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant