Resolve Client.Python-003, -005, -009 code-review findings
Client.Python-003: stream_events_raw and query_active_alarms passed `timeout` to the stub with no TypeError fallback, unlike _unary. Both now route through a shared _open_stream helper that strips `timeout` on TypeError. Client.Python-005: discover_hierarchy buffered the entire Galaxy hierarchy in memory. Added GalaxyRepositoryClient.iter_hierarchy, a lazy async generator yielding objects page-by-page; discover_hierarchy is now a thin wrapper that preserves its list contract. README documents iter_hierarchy. Client.Python-009: added regression coverage for previously untested paths — write2/add_item2 request shape, the MAX_BULK_ITEMS boundary, the None-argument TypeError guards, TLS ca_file reading, and the non-auth map_rpc_error fallthrough. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""Regression tests for Client.Python-003: stream timeout-kwarg fallback.
|
||||
|
||||
`stream_events_raw` and `query_active_alarms` must tolerate a fake/older stub
|
||||
that does not accept a ``timeout`` keyword argument, matching the fallback
|
||||
already present in `galaxy.watch_deploy_events` and the unary `_unary` helper.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from mxgateway import ClientOptions, GatewayClient
|
||||
from mxgateway.generated import mxaccess_gateway_pb2 as pb
|
||||
|
||||
|
||||
class _NoTimeoutStream:
|
||||
"""Sync-callable unary-stream fake that rejects a ``timeout`` kwarg."""
|
||||
|
||||
def __init__(self, replies: list[Any]) -> None:
|
||||
self._replies = list(replies)
|
||||
self.requests: list[Any] = []
|
||||
self.metadata: tuple[tuple[str, str], ...] | None = None
|
||||
self.cancelled = False
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
request: Any,
|
||||
*,
|
||||
metadata: tuple[tuple[str, str], ...],
|
||||
) -> "_NoTimeoutStream":
|
||||
self.requests.append(request)
|
||||
self.metadata = metadata
|
||||
return self
|
||||
|
||||
def __aiter__(self) -> "_NoTimeoutStream":
|
||||
return self
|
||||
|
||||
async def __anext__(self) -> Any:
|
||||
if not self._replies:
|
||||
raise StopAsyncIteration
|
||||
return self._replies.pop(0)
|
||||
|
||||
def cancel(self) -> None:
|
||||
self.cancelled = True
|
||||
|
||||
|
||||
class _NoTimeoutStubStreamEvents:
|
||||
def __init__(self, stream: _NoTimeoutStream) -> None:
|
||||
self.StreamEvents = stream
|
||||
|
||||
|
||||
class _NoTimeoutStubQueryAlarms:
|
||||
def __init__(self, stream: _NoTimeoutStream) -> None:
|
||||
self.QueryActiveAlarms = stream
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_events_raw_falls_back_when_stub_rejects_timeout() -> None:
|
||||
stream = _NoTimeoutStream(
|
||||
[pb.MxEvent(session_id="session-1", worker_sequence=1)],
|
||||
)
|
||||
client = await GatewayClient.connect(
|
||||
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0),
|
||||
stub=_NoTimeoutStubStreamEvents(stream),
|
||||
)
|
||||
|
||||
received = [
|
||||
event
|
||||
async for event in client.stream_events_raw(
|
||||
pb.StreamEventsRequest(session_id="session-1"),
|
||||
)
|
||||
]
|
||||
|
||||
assert len(received) == 1
|
||||
assert received[0].worker_sequence == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_active_alarms_falls_back_when_stub_rejects_timeout() -> None:
|
||||
stream = _NoTimeoutStream(
|
||||
[pb.ActiveAlarmSnapshot(alarm_full_reference="Tank01.Level.HiHi")],
|
||||
)
|
||||
client = await GatewayClient.connect(
|
||||
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=5.0),
|
||||
stub=_NoTimeoutStubQueryAlarms(stream),
|
||||
)
|
||||
|
||||
received = [
|
||||
snapshot
|
||||
async for snapshot in client.query_active_alarms(
|
||||
pb.QueryActiveAlarmsRequest(session_id="session-1"),
|
||||
)
|
||||
]
|
||||
|
||||
assert len(received) == 1
|
||||
assert received[0].alarm_full_reference == "Tank01.Level.HiHi"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_events_raw_still_passes_timeout_to_capable_stub() -> None:
|
||||
"""A stub that accepts ``timeout`` must still receive the configured value."""
|
||||
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
class _CapableStream(_NoTimeoutStream):
|
||||
def __call__( # type: ignore[override]
|
||||
self,
|
||||
request: Any,
|
||||
*,
|
||||
metadata: tuple[tuple[str, str], ...],
|
||||
timeout: float | None = None,
|
||||
) -> "_CapableStream":
|
||||
captured["timeout"] = timeout
|
||||
return super().__call__(request, metadata=metadata)
|
||||
|
||||
stream = _CapableStream([pb.MxEvent(session_id="session-1", worker_sequence=9)])
|
||||
client = await GatewayClient.connect(
|
||||
ClientOptions(endpoint="fake", plaintext=True, stream_timeout=7.5),
|
||||
stub=_NoTimeoutStubStreamEvents(stream),
|
||||
)
|
||||
|
||||
received = [
|
||||
event
|
||||
async for event in client.stream_events_raw(
|
||||
pb.StreamEventsRequest(session_id="session-1"),
|
||||
)
|
||||
]
|
||||
|
||||
assert len(received) == 1
|
||||
assert captured["timeout"] == 7.5
|
||||
Reference in New Issue
Block a user