Skip to content

Map JetStream errors in callers, not the API client - #984

Draft
caspervonb wants to merge 2 commits into
mainfrom
jetstream-error-conversion-in-callers
Draft

Map JetStream errors in callers, not the API client#984
caspervonb wants to merge 2 commits into
mainfrom
jetstream-error-conversion-in-callers

Conversation

@caspervonb

Copy link
Copy Markdown
Collaborator

The api/client layer is meant to be raw endpoint dispatch, but each method wrapped request_json in a try/except that re-raised a specific type (StreamNotFoundError, ConsumerNotFoundError, …) keyed on error_code.

Move that mapping to the callers — JetStream (__init__.py) and Stream (stream.py) — so the call site documents which errors each operation produces (inline per caller). request_json still raises the generic JetStreamError, so errors stay exceptions and can't be silently dropped; api/client now just builds the request and returns the response.

The 14 existing tests that assert specific typed errors (StreamNotFoundError, ConsumerNotFoundError, MessageNotFoundError, StreamNameAlreadyInUseError, ConsumerInvalidResetError, JetStreamNotEnabledError) all still pass; full suite is 308 green.

The api/client layer is meant to be raw endpoint dispatch. Each method
wrapped request_json in a try/except that re-raised a specific error type
(StreamNotFoundError, ConsumerNotFoundError, ...) keyed on error_code.
Move that mapping to the callers (JetStream in __init__.py, Stream in
stream.py), where the call site documents which errors each operation
produces. request_json still raises the generic JetStreamError, so errors
stay exceptions; api/client now just builds the request and returns the
response.
@synadia-claude-reviewer

synadia-claude-reviewer Bot commented Jun 22, 2026

Copy link
Copy Markdown

Claude finished @caspervonb's task in 4m 4s —— View job


Review

  • Load tools
  • Read PR diff and changed files
  • Analyze for correctness, asyncio patterns, type accuracy
  • Post inline comments and summary

The refactoring is correct. I traced all 11 error mappings that were removed from api/client.py and verified each one has been reproduced in the appropriate caller:

API method Old location New location
account_info (NoResponders → JetStreamNotEnabled) api/client.py __init__.py JetStream.account_info
account_info (JetStreamNotEnabled*) api/client.py __init__.py JetStream.account_info
consumer_create (StreamNotFound, MaxConsumers) api/client.py stream.py Stream._upsert_consumer
consumer_delete (ConsumerNotFound) api/client.py stream.py Stream.delete_consumer
consumer_info (ConsumerNotFound) api/client.py __init__.py + stream.py
consumer_pause (ConsumerNotFound) api/client.py stream.py pause_consumer + resume_consumer
consumer_reset (ConsumerNotFound, InvalidReset) api/client.py stream.py Stream.reset_consumer
stream_create (StreamNameInUse) api/client.py __init__.py JetStream.create_stream
stream_delete/info/update (StreamNotFound) api/client.py __init__.py
stream_msg_get (MessageNotFound) api/client.py __init__.py (×2) + stream.py

NoRespondersError propagation is correct: request_json doesn't catch it, so it flows through the now-thin api.account_info() and is caught by JetStream.account_info() exactly as before.

One minor issue: the from nats.client.errors import NoRespondersError import is placed inside the account_info() method body (see inline comment). nats.client is already a top-level dependency of __init__.py, so there's no circular import justification for making it lazy. Moving it to the module-level import block keeps the dependency surface visible.

No asyncio issues, no type accuracy regressions, no security concerns.

@@ -757,7 +792,25 @@ async def account_info(self) -> AccountInfo:
JetStreamNotEnabledForAccountError: If JetStream is not enabled for this account
JetStreamError: For other JetStream API errors
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The deferred import of NoRespondersError inside the method body is non-idiomatic. Since nats.client is already a module-level dependency (e.g. from nats.client.message import Headers at the top of the file), there is no circular import risk here. This should sit with the other nats.client imports at the top of the file.

@caspervonb

Copy link
Copy Markdown
Collaborator Author

Review comments addressed, PTAL @claude

@synadia-claude-reviewer

synadia-claude-reviewer Bot commented Jul 13, 2026

Copy link
Copy Markdown

Claude finished @caspervonb's task in 31s —— View job


Review

  • Load tools
  • Check current state of __init__.py for the import fix
  • Post updated review

The review comment has been addressed. NoRespondersError is now imported at the module level (line 11 of __init__.py), grouped with the other nats.client imports:

from nats.client.errors import NoRespondersError
from nats.client.message import Headers
from nats.client.protocol.message import parse_headers

No other issues. LGTM.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant