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:
@@ -133,7 +133,7 @@ class GatewayClient:
|
||||
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
|
||||
if self.options.stream_timeout is not None:
|
||||
kwargs["timeout"] = self.options.stream_timeout
|
||||
call = self.raw_stub.StreamEvents(request, **kwargs)
|
||||
call = _open_stream(self.raw_stub.StreamEvents, request, kwargs)
|
||||
return _canceling_iterator(call)
|
||||
|
||||
async def acknowledge_alarm(
|
||||
@@ -169,7 +169,7 @@ class GatewayClient:
|
||||
kwargs: dict[str, Any] = {"metadata": merge_metadata(self.options.api_key, metadata)}
|
||||
if self.options.stream_timeout is not None:
|
||||
kwargs["timeout"] = self.options.stream_timeout
|
||||
call = self.raw_stub.QueryActiveAlarms(request, **kwargs)
|
||||
call = _open_stream(self.raw_stub.QueryActiveAlarms, request, kwargs)
|
||||
return _canceling_active_alarms_iterator(call)
|
||||
|
||||
async def _unary(
|
||||
@@ -201,6 +201,23 @@ class GatewayClient:
|
||||
raise map_rpc_error(operation, error) from error
|
||||
|
||||
|
||||
def _open_stream(method: Any, request: Any, kwargs: dict[str, Any]) -> Any:
|
||||
"""Open a server-streaming call, dropping ``timeout`` if the stub rejects it.
|
||||
|
||||
Mirrors the fallback in ``_unary`` so an older or fake stub that does not
|
||||
accept a ``timeout`` keyword argument does not crash when ``stream_timeout``
|
||||
is configured.
|
||||
"""
|
||||
|
||||
try:
|
||||
return method(request, **kwargs)
|
||||
except TypeError as error:
|
||||
if "timeout" not in kwargs or "unexpected keyword argument 'timeout'" not in str(error):
|
||||
raise
|
||||
kwargs.pop("timeout")
|
||||
return method(request, **kwargs)
|
||||
|
||||
|
||||
async def _canceling_iterator(call: Any) -> AsyncIterator[pb.MxEvent]:
|
||||
try:
|
||||
async for event in call:
|
||||
|
||||
@@ -114,10 +114,17 @@ class GalaxyRepositoryClient:
|
||||
return None
|
||||
return reply.time_of_last_deploy.ToDatetime()
|
||||
|
||||
async def discover_hierarchy(self) -> list[galaxy_pb.GalaxyObject]:
|
||||
"""Return the deployed Galaxy object hierarchy as raw proto messages."""
|
||||
async def iter_hierarchy(self) -> AsyncIterator[galaxy_pb.GalaxyObject]:
|
||||
"""Yield the deployed Galaxy object hierarchy one object at a time.
|
||||
|
||||
Pages are fetched lazily: a page is only requested once the caller has
|
||||
consumed every object from the previous page. This keeps peak memory
|
||||
bounded by a single page (``_DISCOVER_HIERARCHY_PAGE_SIZE`` objects)
|
||||
rather than the whole Galaxy. Use this for large Galaxies; use
|
||||
:meth:`discover_hierarchy` when a fully buffered ``list`` is convenient
|
||||
and the Galaxy is known to be small.
|
||||
"""
|
||||
|
||||
objects: list[galaxy_pb.GalaxyObject] = []
|
||||
seen_page_tokens: set[str] = set()
|
||||
page_token = ""
|
||||
while True:
|
||||
@@ -129,16 +136,27 @@ class GalaxyRepositoryClient:
|
||||
page_token=page_token,
|
||||
),
|
||||
)
|
||||
objects.extend(reply.objects)
|
||||
for obj in reply.objects:
|
||||
yield obj
|
||||
page_token = reply.next_page_token
|
||||
if not page_token:
|
||||
return objects
|
||||
return
|
||||
if page_token in seen_page_tokens:
|
||||
raise MxGatewayError(
|
||||
f"galaxy discover hierarchy returned repeated page token {page_token!r}"
|
||||
)
|
||||
seen_page_tokens.add(page_token)
|
||||
|
||||
async def discover_hierarchy(self) -> list[galaxy_pb.GalaxyObject]:
|
||||
"""Return the deployed Galaxy object hierarchy as raw proto messages.
|
||||
|
||||
This buffers every object (and its full attribute list) into a single
|
||||
in-memory ``list``. For a large Galaxy prefer :meth:`iter_hierarchy`,
|
||||
which streams objects page by page without holding the whole hierarchy.
|
||||
"""
|
||||
|
||||
return [obj async for obj in self.iter_hierarchy()]
|
||||
|
||||
def watch_deploy_events(
|
||||
self,
|
||||
last_seen_deploy_time: datetime | None = None,
|
||||
|
||||
Reference in New Issue
Block a user