11import json
22from dataclasses import dataclass
3- from typing import Any , Protocol
3+ from typing import Any , Generic , Protocol , TypeVar , cast , overload
44
55import httpx
66
77from deepset_mcp .api .exceptions import BadRequestError , ResourceNotFoundError , UnexpectedAPIError
88
9+ T = TypeVar ("T" )
10+
911
1012@dataclass
11- class TransportResponse :
12- """Response envelope for HTTP transport."""
13+ class TransportResponse ( Generic [ T ]) :
14+ """Reponse envelope for HTTP transport."""
1315
1416 text : str
1517 status_code : int
16- json : dict [ str , Any ] | None = None
18+ json : T | None = None
1719
1820 @property
1921 def success (self ) -> bool :
20- """Returns True if the response status code indicates success ( < 400)."""
22+ """Check if the response was successful ( status code < 400)."""
2123 return self .status_code < 400
2224
2325
24- def raise_for_status (response : TransportResponse ) -> None :
26+ def raise_for_status (response : TransportResponse [ Any ] ) -> None :
2527 """Raises the appropriate exception based on the response status code."""
2628 if response .success :
2729 return
@@ -32,9 +34,12 @@ def raise_for_status(response: TransportResponse) -> None:
3234 404 : ResourceNotFoundError ,
3335 }
3436
35- # Extract error details from response if available
36- detail = response .json .get ("details" ) if response .json else None
37- message = response .json .get ("message" ) if response .json else response .text
37+ if isinstance (response .json , dict ):
38+ detail = response .json .get ("details" ) if response .json else None
39+ message = response .json .get ("message" ) if response .json else response .text
40+ else :
41+ detail = json .dumps (response .json ) if response .json else None
42+ message = response .text
3843
3944 # Get exception class
4045 exception_class = exception_map .get (response .status_code )
@@ -52,8 +57,20 @@ def raise_for_status(response: TransportResponse) -> None:
5257class TransportProtocol (Protocol ):
5358 """Protocol for HTTP transport."""
5459
55- async def request (self , method : str , url : str , ** kwargs : Any ) -> TransportResponse :
56- """Send an HTTP request and return the parsed JSON response."""
60+ @overload
61+ async def request (
62+ self , method : str , url : str , * , response_type : type [T ], ** kwargs : Any
63+ ) -> TransportResponse [T ]: ...
64+
65+ @overload
66+ async def request (
67+ self , method : str , url : str , * , response_type : None = None , ** kwargs : Any
68+ ) -> TransportResponse [Any ]: ...
69+
70+ async def request (
71+ self , method : str , url : str , * , response_type : type [T ] | None = None , ** kwargs : Any
72+ ) -> TransportResponse [Any ]:
73+ """Send an HTTP request and return the response."""
5774 ...
5875
5976 async def close (self ) -> None :
@@ -94,18 +111,34 @@ def __init__(
94111 }
95112 self ._client = httpx .AsyncClient (** client_kwargs )
96113
97- async def request (self , method : str , url : str , ** kwargs : Any ) -> TransportResponse :
114+ @overload
115+ async def request (
116+ self , method : str , url : str , * , response_type : type [T ], ** kwargs : Any
117+ ) -> TransportResponse [T ]: ...
118+
119+ @overload
120+ async def request (
121+ self , method : str , url : str , * , response_type : None = None , ** kwargs : Any
122+ ) -> TransportResponse [Any ]: ...
123+
124+ async def request (
125+ self , method : str , url : str , * , response_type : type [T ] | None = None , ** kwargs : Any
126+ ) -> TransportResponse [Any ]:
98127 """Send an HTTP request and return the response."""
99128 response = await self ._client .request (method , url , ** kwargs )
100129
101- transport_response = TransportResponse (text = response .text , status_code = response .status_code )
130+ if response_type is not None :
131+ raw = response .json ()
132+ payload : T = cast (T , raw )
133+ return TransportResponse (text = response .text , status_code = response .status_code , json = payload )
102134
103135 try :
104- transport_response . json = response .json ()
136+ untyped_response = response .json ()
105137 except json .decoder .JSONDecodeError :
138+ untyped_response = None
106139 pass
107140
108- return transport_response
141+ return TransportResponse ( text = response . text , status_code = response . status_code , json = untyped_response )
109142
110143 async def close (self ) -> None :
111144 """Clean up any resources (e.g., close connections)."""
0 commit comments