44from .error import SerpApiException
55from .object_decoder import ObjectDecoder
66
7- class Client (ObjectDecoder ):
7+ class HttpClient :
8+ """Simple HTTP client wrapper around urllib3"""
9+
10+ def __init__ (self , parameter : dict = {}):
11+ # initialize the http client
12+ self .http = urllib3 .PoolManager ()
13+
14+ # urllib3 configurations
15+ # HTTP connect timeout
16+ if 'timeout' in parameter :
17+ self .timeout = parameter ['timeout' ]
18+ else :
19+ # 60s default
20+ self .timeout = 60.0
21+
22+ # no HTTP retry
23+ if 'retries' in parameter :
24+ self .retries = parameter ['retries' ]
25+ else :
26+ self .retries = False
27+
28+ def start (self , path : str , parameter : dict = None , decoder : str = 'json' ):
29+ """
30+ start HTTP request and decode response using urllib3
31+ the response is decoded using the selected decoder:
32+ - html: raw HTML response
33+ - json: deep dict contains search results
34+ - object: containing search results as a dynamic object
35+
36+ Parameters:
37+ ---
38+ path: str
39+ HTTP endpoint path under serpapi.com/<path>
40+ decoder: str
41+ define how to post process the HTTP response.
42+ for example: json -> convert response to a dict
43+ using the default JSON parser from Python
44+ parameter: dict
45+ search query
46+
47+ Returns:
48+ ---
49+ dict|str|object
50+ decoded HTTP response
51+ """
52+ # set client language
53+ self .parameter ['source' ] = 'python'
54+
55+ # set output type
56+ if decoder == 'object' :
57+ self .parameter ['output' ] = 'json'
58+ else :
59+ self .parameter ['output' ] = decoder
60+
61+ # merge parameter defaults and overrides
62+ fields = self .parameter .copy ()
63+ fields .update (parameter )
64+
65+ # execute HTTP get request
66+ response = self .http .request ('GET' ,
67+ self .BACKEND + path ,
68+ fields = fields ,
69+ timeout = self .timeout ,
70+ retries = self .retries )
71+ # decode response
72+ return self .decode (response , decoder )
73+
74+ def decode (self , response : any , decoder : str ):
75+ """Decode HTTP response using a given decoder"""
76+ # handle HTTP error
77+ if response .status != 200 :
78+ try :
79+ raw = response .data .decode ('utf-8' )
80+ payload = json .loads (raw )
81+ raise SerpApiException (payload ['error' ])
82+ except Exception as ex :
83+ raise SerpApiException (raw ) from ex
84+
85+ # HTTP success 200
86+ payload = response .data .decode ('utf-8' )
87+
88+ # successful response decoding
89+ if decoder == 'json' :
90+ return json .loads (payload )
91+
92+ if decoder == 'html' :
93+ return payload
94+
95+ if decoder == 'object' :
96+ data = json .loads (payload )
97+ return self .dict2object (data )
98+
99+ raise SerpApiException ("Invalid decoder: " +
100+ decoder + ", available: json, html, object" )
101+
102+ class Client (ObjectDecoder , HttpClient ):
8103 """
9- Client performend http query to serpApi.com
10- using urllib3 under the hood.
104+ Client performend http query to serpApi.com using urllib3 under the hood.
11105
12106 The HTTP connection be tuned to allow
13107 - retries : attempt to reconnect if the connection fail by default: False
14108 - timeout : connection timeout by default 60s
15109 for more details: https://urllib3.readthedocs.io/en/stable/user-guide.html
110+
16111 """
17112
18113 BACKEND = 'https://serpapi.com'
19114 SUPPORTED_DECODER = ['json' , 'html' , 'object' ]
20115
21- def __init__ (self , parameter = None ):
22- # urllib3 configuration
23- # 60s default
24- self .timeout = 60.0
25- # no HTTP retry
26- self .retries = False
27- # initialize the http client
28- self .http = urllib3 .PoolManager ()
29-
116+ def __init__ (self , parameter : dict = None ):
30117 # define default parameter
31118 if parameter is None :
32119 self .parameter = {}
33120 else :
34121 # assign user parameter
35122 self .parameter = parameter
36- # override default
37- if 'timeout' in parameter :
38- self .timeout = parameter ['timeout' ]
39- if 'retries' in parameter :
40- self .retries = parameter ['retries' ]
123+ HttpClient .__init__ (self , self .parameter )
41124
42- def search (self , parameter = None , decoder = 'json' ):
125+ def search (self , parameter : dict = None , decoder : str = 'json' ):
43126 """
44127 make search then decode the output
45128 decoder supported 'json', 'html', 'object'
@@ -61,7 +144,7 @@ def search(self, parameter=None, decoder='json'):
61144 """
62145 return self .start (path = '/search' , parameter = parameter , decoder = decoder )
63146
64- def html (self , parameter = None ):
147+ def html (self , parameter : dict = None ):
65148 """
66149 html search
67150
@@ -77,7 +160,7 @@ def html(self, parameter=None):
77160 """
78161 return self .start ('/search' , parameter , 'html' )
79162
80- def location (self , parameter = None ):
163+ def location (self , parameter : dict = None ):
81164 """
82165 Get location using Location API
83166
@@ -94,7 +177,7 @@ def location(self, parameter=None):
94177 """
95178 return self .start ('/locations.json' , parameter , 'json' )
96179
97- def search_archive (self , search_id , decoder = 'json' ):
180+ def search_archive (self , search_id : str , decoder : str = 'json' ):
98181 """
99182 Retrieve search results from the Search Archive API
100183
@@ -111,15 +194,15 @@ def search_archive(self, search_id, decoder='json'):
111194 raise SerpApiException ('Decoder must be json or html or object' )
112195 return self .start (path , {}, decoder )
113196
114- def account (self , api_key = None ):
197+ def account (self , api_key : str = None ):
115198 """
116199 Get account information using Account API
117200
118201 Parameters
119202 ---
120203 api_key: str
121204 secret user key provided by serpapi.com
122-
205+
123206 Returns
124207 ---
125208 dict
@@ -128,77 +211,3 @@ def account(self, api_key=None):
128211 if api_key is not None :
129212 self .parameter ['api_key' ] = api_key
130213 return self .start ('/account' , self .parameter , 'json' )
131-
132- def start (self , path , parameter = None , decoder = 'json' ):
133- """
134- start HTTP request and decode response using urllib3
135- the response is decoded using the selected decoder:
136- - html: raw HTML response
137- - json: deep dict contains search results
138- - object: containing search results as a dynamic object
139-
140- Parameters:
141- ---
142- path: str
143- HTTP endpoint path under serpapi.com/<path>
144- decoder: str
145- define how to post process the HTTP response.
146- for example: json -> convert response to a dict
147- using the default JSON parser from Python
148- parameter: dict
149- search query
150-
151- Returns:
152- ---
153- dict|str|object
154- decoded HTTP response
155- """
156- # set client language
157- self .parameter ['source' ] = 'python'
158-
159- # set output type
160- if decoder == 'object' :
161- self .parameter ['output' ] = 'json'
162- else :
163- self .parameter ['output' ] = decoder
164-
165- # merge parameter defaults and overrides
166- fields = self .parameter .copy ()
167- fields .update (parameter )
168-
169- # execute HTTP get request
170- response = self .http .request ('GET' ,
171- self .BACKEND + path ,
172- fields = fields ,
173- timeout = self .timeout ,
174- retries = self .retries )
175- # decode response
176- return self .decode (response , decoder )
177-
178- def decode (self , response , decoder ):
179- """Decode HTTP response using a given decoder"""
180- # handle HTTP error
181- if response .status != 200 :
182- try :
183- raw = response .data .decode ('utf-8' )
184- payload = json .loads (raw )
185- raise SerpApiException (payload ['error' ])
186- except Exception as ex :
187- raise SerpApiException (raw ) from ex
188-
189- # HTTP success 200
190- payload = response .data .decode ('utf-8' )
191-
192- # successful response decoding
193- if decoder == 'json' :
194- return json .loads (payload )
195-
196- if decoder == 'html' :
197- return payload
198-
199- if decoder == 'object' :
200- data = json .loads (payload )
201- return self .dict2object (data )
202-
203- raise SerpApiException ("Invalid decoder: " +
204- decoder + ", available: json, html, object" )
0 commit comments