Skip to content

Commit cd88ad3

Browse files
Merge pull request #9 from sendgrid/fully-fluent
Fully fluent
2 parents d64602f + ce4bb9a commit cd88ad3

File tree

8 files changed

+136
-171
lines changed

8 files changed

+136
-171
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [1.2.3] - 2016-3-01
6+
### Added
7+
- Can now reuse part of the chaining construction for multiple urls/requests
8+
- Thanks to [Kevin Gillette](https://github.com/extemporalgenome)!
9+
- Update of request headers simplified
10+
- Thanks to [Matt Bernier](https://github.com/mbernier)
11+
512
## [1.1.3] - 2016-02-29
613
### Fixed
714
- Various standardizations for commenting, syntax, pylint

examples/example.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,62 @@
1010
local_path = '{0}/..'.format(os.path.abspath(os.path.dirname(__file__)))
1111
Config(local_path)
1212
api_key = os.environ.get('SENDGRID_API_KEY')
13-
request_headers = {
13+
headers = {
1414
"Authorization": 'Bearer {0}'.format(api_key),
1515
"Content-Type": "application/json"
1616
}
1717
client = Client(host=os.environ.get('MOCK_HOST'),
18-
request_headers=request_headers,
18+
request_headers=headers,
1919
version=3)
2020

2121
response = client.version(3).api_keys.get()
2222
print(response.response_headers)
2323
print(response.status_code)
2424
print(response.response_body)
2525

26-
request_headers = {
26+
headers = {
2727
'X-Mock': 200
2828
}
29-
query_params = {'limit': 100}
30-
response = client.api_keys.get(query_params=query_params,
31-
request_headers=request_headers)
29+
params = {'limit': 100}
30+
response = client.api_keys.get(query_params=params,
31+
request_headers=headers)
3232
print('\nGET Mocked Example')
3333
print(response.response_headers)
3434
print(response.status_code)
3535
print(response.response_body)
3636

3737
data = {'sample': 'data'}
38-
request_headers = {'X-Mock': 201}
38+
headers = {'X-Mock': 201}
3939
response = client.api_keys.post(request_body=data,
40-
request_headers=request_headers)
40+
request_headers=headers)
4141
print('\nPOST Mocked Example')
4242
print(response.response_headers)
4343
print(response.status_code)
4444
print(response.response_body)
4545

4646
data = {'sample': 'data'}
47-
request_headers = {'X-Mock': 200}
47+
headers = {'X-Mock': 200}
4848
api_key_id = 'test_url_param'
4949
response = client.api_keys._(api_key_id).put(request_body=data,
50-
request_headers=request_headers)
50+
request_headers=headers)
5151
print('\nPUT Mocked Example')
5252
print(response.response_headers)
5353
print(response.status_code)
5454
print(response.response_body)
5555

5656
data = {'sample': 'data'}
57-
request_headers = {'X-Mock': 200}
57+
headers = {'X-Mock': 200}
5858
api_key_id = 'test_url_param'
5959
response = client.api_keys._(api_key_id).patch(request_body=data,
60-
request_headers=request_headers)
60+
request_headers=headers)
6161
print('\nPATCH Mocked Example')
6262
print(response.response_headers)
6363
print(response.status_code)
6464
print(response.response_body)
6565

66-
request_headers = {'X-Mock': 204}
66+
headers = {'X-Mock': 204}
6767
api_key_id = 'test_url_param'
68-
response = client.api_keys._(api_key_id).delete(request_headers=request_headers)
68+
response = client.api_keys._(api_key_id).delete(request_headers=headers)
6969
print('\nDELETE Mocked Example')
7070
print(response.response_headers)
7171
print(response.status_code)

examples/live_sendgrid_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
host = os.environ.get('HOST') # http://api.sendgrid.com
88
api_key = os.environ.get('SENDGRID_API_KEY')
99
request_headers = {
10-
"Authorization": 'Bearer {0}'.format(api_key),
10+
"Authorization": 'Bearer {0}'.format(api_key),
1111
"Content-Type": "application/json"
1212
}
1313
version = 3 # we could also use client.version(3)

python_http_client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from .client import Client
2-
from .config import Config
2+
from .config import Config

python_http_client/client.py

Lines changed: 70 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,47 @@
1212
from urllib import urlencode
1313

1414

15+
class Response(object):
16+
"""Holds the response from an API call."""
17+
def __init__(self, response):
18+
"""
19+
:param response: The return value from a open call
20+
on a urllib.build_opener()
21+
:type response: urllib response object
22+
"""
23+
self._status_code = response.getcode()
24+
self._response_body = response.read()
25+
self._response_headers = response.info()
26+
27+
@property
28+
def status_code(self):
29+
"""
30+
:return: integer, status code of API call
31+
"""
32+
return self._status_code
33+
34+
@property
35+
def response_body(self):
36+
"""
37+
:return: response from the API
38+
"""
39+
return self._response_body
40+
41+
@property
42+
def response_headers(self):
43+
"""
44+
:return: dict of response headers
45+
"""
46+
return self._response_headers
47+
48+
1549
class Client(object):
1650
"""Quickly and easily access any REST or REST-like API."""
1751
def __init__(self,
1852
host,
1953
request_headers=None,
20-
version=None):
54+
version=None,
55+
url_path=None):
2156
"""
2257
:param host: Base URL for the api. (e.g. https://api.sendgrid.com)
2358
:type host: string
@@ -28,43 +63,25 @@ def __init__(self,
2863
Subclass _build_versioned_url for custom behavior.
2964
Or just pass the version as part of the URL
3065
(e.g. client._("/v3"))
31-
:type integer:
66+
:type version: integer
67+
:param url_path: A list of the url path segments
68+
:type url_path: list of strings
3269
"""
3370
self.host = host
34-
self.request_headers = request_headers
71+
self.request_headers = request_headers or {}
72+
self._version = version
73+
# _url_path keeps track of the dynamically built url
74+
self._url_path = url_path or []
3575
# These are the supported HTTP verbs
3676
self.methods = ['delete', 'get', 'patch', 'post', 'put']
37-
self._version = version
38-
# _count and _url_path keep track of the dynamically built url
39-
self._count = 0
40-
self._url_path = {}
41-
self._status_code = None
42-
self._response_body = None
43-
self._response_headers = None
44-
self._response = None
45-
46-
def _reset(self):
47-
"""Resets the URL builder, so you can make a fresh new dynamic call."""
48-
self._count = 0
49-
self._url_path = {}
50-
self._response = None
51-
52-
def _add_to_url_path(self, name):
53-
"""Takes the method chained call and adds to the url path.
54-
55-
:param name: The name of the method call
56-
:type name: string
57-
"""
58-
self._url_path[self._count] = name
59-
self._count += 1
6077

6178
def _build_versioned_url(self, url):
6279
"""Subclass this function for your own needs.
6380
Or just pass the version as part of the URL
6481
(e.g. client._('/v3'))
6582
:param url: URI portion of the full URL being requested
6683
:type url: string
67-
:return:
84+
:return: string
6885
"""
6986
return '{0}/v{1}{2}'.format(self.host, str(self._version), url)
7087

@@ -73,7 +90,7 @@ def _build_url(self, query_params):
7390
7491
:param query_params: A dictionary of all the query parameters
7592
:type query_params: dictionary
76-
:return:
93+
:return: string
7794
"""
7895
url = ''
7996
count = 0
@@ -83,33 +100,30 @@ def _build_url(self, query_params):
83100
if query_params:
84101
url_values = urlencode(sorted(query_params.items()))
85102
url = '{0}?{1}'.format(url, url_values)
86-
if self._version:
87-
url = self._build_versioned_url(url)
88-
else:
89-
url = self.host + url
103+
url = self._build_versioned_url(url) if self._version else self.host + url
90104
return url
91105

92-
def _set_response(self, response):
93-
"""Build the API call's response
106+
def _update_headers(self, request_headers):
107+
"""Update the headers for the request
94108
95-
:param response: The response object from the API call from urllib
96-
:type response: urllib.Request object
109+
:param request_headers: headers to set for the API call
110+
:type response: dictionary
111+
:return: dictionary
97112
"""
98-
self._status_code = response.getcode()
99-
self._response_body = response.read()
100-
self._response_headers = response.info()
113+
self.request_headers.update(request_headers)
101114

102-
def _set_headers(self, request_headers):
103-
"""Build the headers for the request
115+
def _build_client(self, name=None):
116+
"""Make a new Client object
104117
105-
:param request_headers: headers to set for the API call
106-
:type response: dict
107-
:return:
118+
:param name: Name of the url segment
119+
:type name: string
120+
:return: A Client object
108121
"""
109-
if self.request_headers:
110-
self.request_headers.update(request_headers)
111-
else:
112-
self.request_headers = request_headers
122+
url_path = self._url_path+[name] if name else self._url_path
123+
return Client(host=self.host,
124+
version=self._version,
125+
request_headers=self.request_headers,
126+
url_path=url_path)
113127

114128
def _make_request(self, opener, request):
115129
"""Make the API call and return the response. This is separated into
@@ -119,7 +133,7 @@ def _make_request(self, opener, request):
119133
:type opener:
120134
:param request: url payload to request
121135
:type request: urllib.Request object
122-
:return:
136+
:return: urllib response
123137
"""
124138
return opener.open(request)
125139

@@ -131,10 +145,9 @@ def _(self, name):
131145
132146
:param name: Name of the url segment
133147
:type name: string
134-
:return:
148+
:return: Client object
135149
"""
136-
self._add_to_url_path(name)
137-
return self
150+
return self._build_client(name)
138151

139152
def __getattr__(self, name):
140153
"""Dynamically add method calls to the url, then call a method.
@@ -153,7 +166,7 @@ def get_version(*args, **kwargs):
153166
:return: string, version
154167
"""
155168
self._version = args[0]
156-
return self
169+
return self._build_client()
157170
return get_version
158171

159172
# We have reached the end of the method chain, make the API call
@@ -167,7 +180,7 @@ def http_request(*args, **kwargs):
167180
:return: Client object
168181
"""
169182
if 'request_headers' in kwargs:
170-
self._set_headers(kwargs['request_headers'])
183+
self._update_headers(kwargs['request_headers'])
171184
data = json.dumps(kwargs['request_body']).encode('utf-8')\
172185
if 'request_body' in kwargs else None
173186
params = kwargs['query_params']\
@@ -178,32 +191,8 @@ def http_request(*args, **kwargs):
178191
for key, value in self.request_headers.items():
179192
request.add_header(key, value)
180193
request.get_method = lambda: method
181-
self._response = self._make_request(opener, request)
182-
self._set_response(self._response)
183-
self._reset()
184-
return self
194+
return Response(self._make_request(opener, request))
185195
return http_request
186196
else:
187-
self._add_to_url_path(name)
188-
return self
189-
190-
@property
191-
def status_code(self):
192-
"""
193-
:return: integer, status code of API call
194-
"""
195-
return self._status_code
196-
197-
@property
198-
def response_body(self):
199-
"""
200-
:return: response from the API
201-
"""
202-
return self._response_body
203-
204-
@property
205-
def response_headers(self):
206-
"""
207-
:return: dict of response headers
208-
"""
209-
return self._response_headers
197+
# Add a segment to the URL
198+
return self._(name)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def getRequires():
99
return deps
1010

1111
base_url = 'https://github.com/sendgrid/'
12-
version = '1.1.3'
12+
version = '1.2.3'
1313
setup(
1414
name='python_http_client',
1515
version=version,

tests/profile.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,21 +45,24 @@ def get(self,
4545
request_body=None,
4646
query_params=None,
4747
request_headers=None):
48-
self.make_request('get', request_body, query_params, request_headers)
48+
self.make_request('get', request_body, query_params,
49+
request_headers)
4950
return self
5051

5152
def post(self,
5253
request_body=None,
5354
query_params=None,
5455
request_headers=None):
55-
self.make_request('post', request_body, query_params, request_headers)
56+
self.make_request('post', request_body, query_params,
57+
request_headers)
5658
return self
5759

5860
def put(self,
5961
request_body=None,
6062
query_params=None,
6163
request_headers=None):
62-
self.make_request('put', request_body, query_params, request_headers)
64+
self.make_request('put', request_body, query_params,
65+
request_headers)
6366
return self
6467

6568
def patch(self,
@@ -74,7 +77,8 @@ def delete(self,
7477
request_body=None,
7578
query_params=None,
7679
request_headers=None):
77-
self.make_request('delete', request_body, query_params, request_headers)
80+
self.make_request('delete', request_body, query_params,
81+
request_headers)
7882
return self
7983

8084

0 commit comments

Comments
 (0)