1919 Headers ,
2020 HTTPStatusError ,
2121 QueryParams ,
22- Request ,
2322 Response ,
2423)
24+ from httpx import (
25+ Request as HttpxRequest ,
26+ )
2527from pydantic import BaseModel , TypeAdapter
2628from typing_extensions import Concatenate , ParamSpec
2729from yarl import URL
3234
3335
3436@dataclass
35- class EndpointRequest :
36- method : HTTPRequestMethod
37+ class EmptyRequest :
3738 path : List [str ]
38- body : Optional [bytes ] = None
39- headers : Headers = field (default_factory = Headers )
40- query_params : QueryParams = field (default_factory = QueryParams )
39+ method : HTTPRequestMethod
40+ headers : Headers = field (default_factory = Headers , kw_only = True )
41+ query_params : QueryParams = field (default_factory = QueryParams , kw_only = True )
42+
43+ def to_request (self , base_url : URL ) -> HttpxRequest :
44+ return HttpxRequest (
45+ method = self .method ,
46+ url = str (base_url .joinpath (* self .path )),
47+ headers = self .headers ,
48+ params = self .query_params ,
49+ )
50+
51+
52+ @dataclass
53+ class BytesRequest (EmptyRequest ):
54+ body : bytes
4155
42- def bytes (self , bs : bytes ) -> "EndpointRequest" :
56+ def to_request (self , base_url : URL ) -> HttpxRequest :
4357 self .headers ["Content-Type" ] = "application/octet-stream"
44- self .body = bs
45- return self
58+ return HttpxRequest (
59+ method = self .method ,
60+ url = str (base_url .joinpath (* self .path )),
61+ headers = self .headers ,
62+ params = self .query_params ,
63+ content = self .body ,
64+ )
4665
47- def plain_text (self , text : str ) -> "EndpointRequest" :
48- self .headers ["Content-Type" ] = "text/plain; charset=utf-8"
49- self .body = text .encode ("utf-8" )
50- return self
5166
52- def model (self , model : BaseModel ) -> "EndpointRequest" :
67+ @dataclass
68+ class JSONRequest (EmptyRequest ):
69+ body : Union [JSON , BaseModel ]
70+ exclude_none : bool = True
71+
72+ def to_request (self , base_url : URL ) -> HttpxRequest :
73+ if isinstance (self .body , BaseModel ):
74+ content = self .body .__pydantic_serializer__ .to_json (
75+ self .body , exclude_none = self .exclude_none
76+ )
77+ else :
78+ content = JSONParser .dump_json (self .body )
5379 self .headers ["Content-Type" ] = "application/json"
54- self .body = model .__pydantic_serializer__ .to_json (model )
55- return self
80+ return HttpxRequest (
81+ method = self .method ,
82+ url = str (base_url .joinpath (* self .path )),
83+ headers = self .headers ,
84+ params = self .query_params ,
85+ content = content ,
86+ )
5687
57- def json (self , json : JSON ) -> "EndpointRequest" :
58- self .headers ["Content-Type" ] = "application/json"
59- self .body = JSONParser .dump_json (json )
60- return self
6188
62- def query_param ( self , key : str , value : str ) -> "EndpointRequest" :
63- self . query_params = self . query_params . set ( key , value )
64- return self
89+ @ dataclass
90+ class TextRequest ( EmptyRequest ):
91+ text : str
6592
66- def to_request (self , base_url : URL ) -> Request :
67- return Request (
93+ def to_request (self , base_url : URL ) -> HttpxRequest :
94+ self .headers ["Content-Type" ] = "text/plain; charset=utf-8"
95+ return HttpxRequest (
6896 method = self .method ,
6997 url = str (base_url .joinpath (* self .path )),
7098 headers = self .headers ,
7199 params = self .query_params ,
72- content = self .body ,
100+ content = self .text . encode ( "utf-8" ) ,
73101 )
74102
75103
76104T = TypeVar ("T" , covariant = True )
77105
78106
79- class FromHTTPResponse (Protocol [T ]):
107+ class FromHttpxResponse (Protocol [T ]):
80108 def __call__ (self , response : Response ) -> T : ...
81109
82110
@@ -86,7 +114,7 @@ def __call__(self, response: Response) -> T: ...
86114Model = TypeVar ("Model" , bound = BaseModel )
87115
88116
89- def validate_model (model : type [Model ]) -> FromHTTPResponse [Model ]:
117+ def validate_model (model : type [Model ]) -> FromHttpxResponse [Model ]:
90118 def from_response (response : Response ) -> Model :
91119 return model .model_validate_json (response .content )
92120
@@ -96,49 +124,53 @@ def from_response(response: Response) -> Model:
96124Inner = TypeVar ("Inner" )
97125
98126
99- def validate_adapter (adapter : TypeAdapter [Inner ]) -> FromHTTPResponse [Inner ]:
127+ def validate_adapter (adapter : TypeAdapter [Inner ]) -> FromHttpxResponse [Inner ]:
100128 def from_response (response : Response ) -> Inner :
101129 return adapter .validate_json (response .content )
102130
103131 return from_response
104132
105133
134+ class ToHttpxRequest (Protocol ):
135+ def to_request (self , base_url : URL ) -> HttpxRequest : ...
136+
137+
106138@dataclass
107- class ServerEndpoint (Generic [Success , Failure ]):
108- request : EndpointRequest
109- on_success : FromHTTPResponse [Success ]
110- on_failure : FromHTTPResponse [Failure ]
139+ class ResponseHandler (Generic [Success , Failure ]):
140+ request : ToHttpxRequest
141+ on_success : FromHttpxResponse [Success ]
142+ on_failure : FromHttpxResponse [Failure ]
111143
112144
113145class SyncExecutor :
114146 def __init__ (self , session : Client ) -> None :
115147 self .session = session
116148
117149 def communicate (
118- self , base_url : URL , endpoint : ServerEndpoint [Success , Failure ]
150+ self , base_url : URL , handler : ResponseHandler [Success , Failure ]
119151 ) -> Success :
120- response = self .session .send (endpoint .request .to_request (base_url ))
152+ response = self .session .send (handler .request .to_request (base_url ))
121153 try :
122154 response .raise_for_status ()
123- return endpoint .on_success (response )
155+ return handler .on_success (response )
124156 except HTTPStatusError :
125- raise endpoint .on_failure (response ) from None
157+ raise handler .on_failure (response ) from None
126158
127159
128160class AsyncExecutor :
129161 def __init__ (self , session : AsyncClient ) -> None :
130162 self .session = session
131163
132164 async def communicate (
133- self , base_url : URL , endpoint : ServerEndpoint [Success , Failure ]
165+ self , base_url : URL , handler : ResponseHandler [Success , Failure ]
134166 ) -> Success :
135- request = endpoint .request .to_request (base_url )
167+ request = handler .request .to_request (base_url )
136168 response = await self .session .send (request )
137169 try :
138170 response .raise_for_status ()
139- return endpoint .on_success (response )
171+ return handler .on_success (response )
140172 except HTTPStatusError :
141- raise endpoint .on_failure (response ) from None
173+ raise handler .on_failure (response ) from None
142174
143175
144176Params = ParamSpec ("Params" )
@@ -151,8 +183,8 @@ class HasExecutor(Protocol[Executor]):
151183
152184
153185@dataclass
154- class http_endpoint (Generic [Params , Success , Failure ]):
155- method : Callable [Concatenate [Any , Params ], ServerEndpoint [Success , Failure ]]
186+ class http_request (Generic [Params , Success , Failure ]):
187+ method : Callable [Concatenate [Any , Params ], ResponseHandler [Success , Failure ]]
156188
157189 @overload
158190 def __get__ (
0 commit comments