Skip to content

Commit 391cdeb

Browse files
committed
improve python coding style
1 parent 05682b5 commit 391cdeb

1 file changed

Lines changed: 106 additions & 97 deletions

File tree

serpapi/serpapi.py

Lines changed: 106 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,125 @@
44
from .error import SerpApiException
55
from .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

Comments
 (0)