1+ import json
2+ from typing import Any , Dict , Generic , List , Optional , Union , cast
3+
4+ from typing_extensions import Literal , TypeVar
5+
6+ import resend
7+ from resend .exceptions import (NoContentError , ResendError ,
8+ raise_for_code_and_type )
9+ from resend .version import get_version
10+
11+ RequestVerb = Literal ["get" , "post" , "put" , "patch" , "delete" ]
12+ T = TypeVar ("T" )
13+
14+ ParamsType = Union [Dict [str , Any ], List [Dict [str , Any ]]]
15+ HeadersType = Dict [str , str ]
16+
17+
18+ class AsyncRequest (Generic [T ]):
19+ def __init__ (
20+ self ,
21+ path : str ,
22+ params : ParamsType ,
23+ verb : RequestVerb ,
24+ options : Optional [Dict [str , Any ]] = None ,
25+ ):
26+ self .path = path
27+ self .params = params
28+ self .verb = verb
29+ self .options = options
30+
31+ async def perform (self ) -> Union [T , None ]:
32+ data = await self .make_request (url = f"{ resend .api_url } { self .path } " )
33+
34+ if isinstance (data , dict ) and data .get ("statusCode" ) not in (None , 200 ):
35+ raise_for_code_and_type (
36+ code = data .get ("statusCode" ) or 500 ,
37+ message = data .get ("message" , "Unknown error" ),
38+ error_type = data .get ("name" , "InternalServerError" ),
39+ )
40+
41+ return cast (T , data )
42+
43+ async def perform_with_content (self ) -> T :
44+ resp = await self .perform ()
45+ if resp is None :
46+ raise NoContentError ()
47+ return resp
48+
49+ def __get_headers (self ) -> HeadersType :
50+ headers : HeadersType = {
51+ "Accept" : "application/json" ,
52+ "Authorization" : f"Bearer { resend .api_key } " ,
53+ "User-Agent" : f"resend-python:{ get_version ()} " ,
54+ }
55+
56+ if self .verb == "post" and self .options and "idempotency_key" in self .options :
57+ headers ["Idempotency-Key" ] = str (self .options ["idempotency_key" ])
58+
59+ return headers
60+
61+ async def make_request (self , url : str ) -> Union [Dict [str , Any ], List [Any ]]:
62+ headers = self .__get_headers ()
63+
64+ if isinstance (self .params , dict ):
65+ json_params : Optional [Union [Dict [str , Any ], List [Any ]]] = {
66+ str (k ): v for k , v in self .params .items ()
67+ }
68+ elif isinstance (self .params , list ):
69+ json_params = [dict (item ) for item in self .params ]
70+ else :
71+ json_params = None
72+
73+ try :
74+ content , _status_code , resp_headers = await resend .default_http_client .request (
75+ method = self .verb ,
76+ url = url ,
77+ headers = headers ,
78+ json = json_params ,
79+ )
80+
81+ # Safety net around the HTTP Client
82+ except Exception as e :
83+ raise ResendError (
84+ code = 500 ,
85+ message = str (e ),
86+ error_type = "HttpClientError" ,
87+ suggested_action = "Request failed, please try again." ,
88+ )
89+
90+ content_type = {k .lower (): v for k , v in resp_headers .items ()}.get (
91+ "content-type" , ""
92+ )
93+
94+ if "application/json" not in content_type :
95+ raise_for_code_and_type (
96+ code = 500 ,
97+ message = f"Expected JSON response but got: { content_type } " ,
98+ error_type = "InternalServerError" ,
99+ )
100+
101+ try :
102+ return cast (Union [Dict [str , Any ], List [Any ]], json .loads (content ))
103+ except json .JSONDecodeError :
104+ raise_for_code_and_type (
105+ code = 500 ,
106+ message = "Failed to decode JSON response" ,
107+ error_type = "InternalServerError" ,
108+ )
0 commit comments