1- """Extension API for downstream SDKs building on ionq-core.
2-
3- Provides hooks that downstream libraries (qiskit-ionq, cirq-ionq, etc.)
4- use to customize client behavior without forking or monkey-patching.
5-
6- Typical usage::
7-
8- from ionq_core import IonQClient, ClientExtension
9-
10- ext = ClientExtension(
11- user_agent_token="qiskit-ionq/1.1.0",
12- default_headers={"X-Qiskit-Version": "1.3.0"},
13- )
14- client = IonQClient(api_key="...", extension=ext)
15- """
1+ """Extension API for downstream SDKs building on ionq-core."""
162
173import logging
184from collections .abc import Callable
@@ -40,7 +26,7 @@ async def on_request(self, request: httpx.Request) -> None: ...
4026 async def on_response (self , request : httpx .Request , response : httpx .Response ) -> None : ...
4127
4228
43- @attrs .define ( frozen = True )
29+ @attrs .frozen
4430class ClientExtension :
4531 """Declarative configuration bundle for downstream SDK integration."""
4632
@@ -84,19 +70,34 @@ async def _afire_hooks(hooks: tuple, method: str, *args, debug: bool = False) ->
8470
8571
8672class HookTransport (httpx .BaseTransport , httpx .AsyncBaseTransport ):
87- """Transport decorator that invokes EventHook instances."""
88-
89- def __init__ (self , transport , hooks : tuple , * , debug : bool = False ) -> None :
73+ """Transport decorator that invokes EventHook instances and optionally maps exceptions."""
74+
75+ def __init__ (
76+ self ,
77+ transport ,
78+ hooks : tuple = (),
79+ * ,
80+ debug : bool = False ,
81+ error_mapper : Callable [[Exception ], Exception ] | None = None ,
82+ ) -> None :
9083 self ._transport = transport
9184 self ._hooks = hooks
9285 self ._debug = debug
86+ self ._error_mapper = error_mapper
87+
88+ def _map_error (self , exc : Exception ) -> None :
89+ if self ._error_mapper is not None :
90+ mapped = self ._error_mapper (exc )
91+ if mapped is not exc :
92+ raise mapped from exc
9393
9494 def handle_request (self , request : httpx .Request ) -> httpx .Response :
9595 _fire_hooks (self ._hooks , "on_request" , request , debug = self ._debug )
9696 try :
9797 response = self ._transport .handle_request (request )
9898 except Exception as exc :
9999 _fire_hooks (self ._hooks , "on_error" , request , exc , debug = self ._debug )
100+ self ._map_error (exc )
100101 raise
101102 _fire_hooks (self ._hooks , "on_response" , request , response , debug = self ._debug )
102103 return response
@@ -107,6 +108,7 @@ async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
107108 response = await self ._transport .handle_async_request (request )
108109 except Exception as exc :
109110 await _afire_hooks (self ._hooks , "on_error" , request , exc , debug = self ._debug )
111+ self ._map_error (exc )
110112 raise
111113 await _afire_hooks (self ._hooks , "on_response" , request , response , debug = self ._debug )
112114 return response
@@ -116,35 +118,3 @@ def close(self) -> None:
116118
117119 async def aclose (self ) -> None :
118120 await self ._transport .aclose ()
119-
120-
121- class _ErrorMapperTransport (httpx .BaseTransport , httpx .AsyncBaseTransport ):
122- """Translates exceptions via an error_mapper callback for downstream SDKs."""
123-
124- def __init__ (self , transport , mapper : Callable [[Exception ], Exception ]) -> None :
125- self ._transport = transport
126- self ._mapper = mapper
127-
128- def handle_request (self , request : httpx .Request ) -> httpx .Response :
129- try :
130- return self ._transport .handle_request (request )
131- except Exception as exc :
132- mapped = self ._mapper (exc )
133- if mapped is not exc :
134- raise mapped from exc
135- raise
136-
137- async def handle_async_request (self , request : httpx .Request ) -> httpx .Response :
138- try :
139- return await self ._transport .handle_async_request (request )
140- except Exception as exc :
141- mapped = self ._mapper (exc )
142- if mapped is not exc :
143- raise mapped from exc
144- raise
145-
146- def close (self ) -> None :
147- self ._transport .close ()
148-
149- async def aclose (self ) -> None :
150- await self ._transport .aclose ()
0 commit comments