Skip to content

Commit e66c9d4

Browse files
committed
[Client] Fix object creation single object + hit resource with url params
1 parent c577c88 commit e66c9d4

File tree

8 files changed

+146
-18
lines changed

8 files changed

+146
-18
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to this project will be documented here.
44

5+
## [0.2.1] - 2019-07-08
6+
### Fixed
7+
- Object creation when requesting a single object.
8+
- Hit an endpoint with a resource_id and url parameters.
9+
510
## [0.2.0] - 2019-07-01
611
### Added
712
- Pickle compatible objects

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,13 @@ User(id=12391,
136136
> conversation_id = 10
137137
> hs.conversations.delete(resource_id=conversation_id)
138138
```
139+
140+
### Requesting a pre-made report
141+
142+
```python
143+
> from helpscout.client import HelpScout
144+
> hs = HelpScout(app_id='asdon123', app_secret='asdoin1')
145+
> report_url = 'reports/happiness?start=2019-06-01T00:00:00Z&end=2019-06-15:00:00Z'
146+
> next(hs.hit(report_url, 'get'))
147+
...
148+
```

helpscout/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from helpscout.client import HelpScout # noqa
22

33

4-
__version__ = '0.2.0'
4+
__version__ = '0.2.1'

helpscout/client.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,14 @@ def get_objects(self, endpoint, resource_id=None, params=None):
9595
[HelpScoutObject]
9696
A list of objects returned by the api.
9797
"""
98-
if params:
99-
if isinstance(params, dict):
100-
params = '&'.join('%s=%s' % (k, v) for k, v in params.items())
101-
url = '%s?%s' % (endpoint, params)
102-
else:
103-
url = endpoint
10498
cls = HelpScoutObject.cls(endpoint, endpoint)
105-
return cls.from_results(self.hit(url, 'get', resource_id))
99+
results = cls.from_results(
100+
self.hit(endpoint, 'get', resource_id, params=params))
101+
if resource_id is not None:
102+
return results[0]
103+
return results
106104

107-
def hit(self, endpoint, method, resource_id=None, data=None):
105+
def hit(self, endpoint, method, resource_id=None, data=None, params=None):
108106
"""Hits the api and yields the data.
109107
110108
Parameters
@@ -114,12 +112,15 @@ def hit(self, endpoint, method, resource_id=None, data=None):
114112
method: str
115113
The http method to hit the endpoint with.
116114
One of {'get', 'post', 'put', 'patch', 'delete', 'head', 'options'}
117-
data: dict or None
118-
A dictionary with the data to send to the API.
119115
resource_id: int or str or None
120116
The id of the resource in the endpoint to query.
121117
E.g.: in "GET /v2/conversations/123 HTTP/1.1" the id would be 123.
122118
If None is provided, nothing will be done
119+
data: dict or None
120+
A dictionary with the data to send to the API as json.
121+
params: dict or str or None
122+
Dictionary with the parameters to send to the url.
123+
Or the parameters already un url format.
123124
124125
Yields
125126
-------
@@ -131,6 +132,10 @@ def hit(self, endpoint, method, resource_id=None, data=None):
131132
url = urljoin(self.base_url, endpoint)
132133
if resource_id is not None:
133134
url = urljoin(url + '/', str(resource_id))
135+
if params:
136+
if isinstance(params, dict):
137+
params = '&'.join('%s=%s' % (k, v) for k, v in params.items())
138+
url = '%s?%s' % (url, params)
134139
headers = self._authentication_headers()
135140
r = getattr(requests, method)(url, headers=headers, data=data)
136141
logger.debug('%s %s' % (method, url))

helpscout/model.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def from_results(cls, api_results):
2929
3030
Parameters
3131
----------
32-
api_results: generator({cls.key: [dict]})
32+
api_results: generator({cls.key: [dict]}) or generator(dict)
3333
A generator returning API responses that cointain a list of
3434
objects each under the class key.
3535
@@ -39,8 +39,9 @@ def from_results(cls, api_results):
3939
"""
4040
results = []
4141
for api_result in api_results:
42-
for object_data in api_result.get(cls.key, []):
43-
results.append(cls(object_data))
42+
for object_data in api_result.get(cls.key, [api_result]):
43+
if len(object_data) > 0:
44+
results.append(cls(object_data))
4445
return results
4546

4647
@classmethod

pypi_upload.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
3+
rm -rf build dist
4+
python setup.py sdist bdist_wheel
5+
twine upload -r pypi dist/python*

tests/helpscout/test_client.py

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test_get_objects_dict_params(self):
4040
hit.return_value = hit_return = 9
4141
hs.get_objects(endpoint, params=params)
4242
HelpScoutObject.cls.assert_called_with(endpoint, endpoint)
43-
hit.assert_called_with(endpoint + '?id=10&name=Mike', 'get', None)
43+
hit.assert_called_with(endpoint, 'get', None, params=params)
4444
cls.from_results.assert_called_with(hit_return)
4545

4646
def test_get_objects_str_params(self):
@@ -52,7 +52,7 @@ def test_get_objects_str_params(self):
5252
hit.return_value = hit_return = 9
5353
hs.get_objects(endpoint, params=params)
5454
HelpScoutObject.cls.assert_called_with(endpoint, endpoint)
55-
hit.assert_called_with(endpoint + '?id=10&name=Mike', 'get', None)
55+
hit.assert_called_with(endpoint, 'get', None, params=params)
5656
cls.from_results.assert_called_with(hit_return)
5757

5858
def test_get_objects_no_params(self):
@@ -64,9 +64,24 @@ def test_get_objects_no_params(self):
6464
hit.return_value = hit_return = 9
6565
hs.get_objects(endpoint, params)
6666
HelpScoutObject.cls.assert_called_with(endpoint, endpoint)
67-
hit.assert_called_with(endpoint, 'get', None)
67+
hit.assert_called_with(endpoint, 'get', None, params=params)
6868
cls.from_results.assert_called_with(hit_return)
6969

70+
def test_get_objects_resource_id(self):
71+
user = {'id': '10', 'name': 'Mike'}
72+
endpoint, resource_id = 'users', 10
73+
hs = self._get_client()
74+
with patch('helpscout.client.HelpScoutObject') as HelpScoutObject, \
75+
patch('helpscout.client.HelpScout.hit') as hit:
76+
HelpScoutObject.cls.return_value = cls = MagicMock()
77+
cls.from_results.return_value = [user]
78+
hit.return_value = hit_return = user
79+
data = hs.get_objects(endpoint, resource_id=resource_id)
80+
HelpScoutObject.cls.assert_called_with(endpoint, endpoint)
81+
hit.assert_called_with(endpoint, 'get', 10, params=None)
82+
cls.from_results.assert_called_with(hit_return)
83+
self.assertEqual(data, user)
84+
7085
def test_hit_no_access_token_ok(self):
7186
endpoint, method = 'users', 'get'
7287
full_url = self.url + endpoint
@@ -142,6 +157,85 @@ def test_hit_resource_id_ok(self):
142157
response.json.assert_called_once()
143158
pages.assert_called_once_with(json_response, method)
144159

160+
def test_hit_params_dict_ok(self):
161+
params, params_str = {'embed': 'threads'}, '?embed=threads'
162+
endpoint, method = 'users', 'get'
163+
full_url = self.url + endpoint + params_str
164+
hs_path = 'helpscout.client.HelpScout.'
165+
hs = self._get_client(token='abc')
166+
with patch('helpscout.client.requests') as requests, \
167+
patch('helpscout.client.logger') as logger, \
168+
patch(hs_path + '_authenticate') as auth, \
169+
patch(hs_path + '_authentication_headers') as auth_headers, \
170+
patch(hs_path + '_results_with_pagination') as pages:
171+
# Setup
172+
auth_headers.return_value = headers = {'token': 'abc'}
173+
response = requests.get.return_value = MagicMock()
174+
response.ok = True
175+
response.json.return_value = json_response = {'a': 'b'}
176+
list(hs.hit(endpoint, method, None, params=params))
177+
# Asserts
178+
auth.assert_not_called()
179+
auth_headers.assert_called_once()
180+
logger.debug.assert_called_once_with(method + ' ' + full_url)
181+
requests.get.assert_called_once_with(
182+
full_url, headers=headers, data=None)
183+
response.json.assert_called_once()
184+
pages.assert_called_once_with(json_response, method)
185+
186+
def test_hit_resource_id_with_params_dict_ok(self):
187+
params, params_str = {'embed': 'threads'}, '?embed=threads'
188+
endpoint, method, resource_id = 'users', 'get', 4
189+
full_url = self.url + endpoint + '/' + str(resource_id) + params_str
190+
hs_path = 'helpscout.client.HelpScout.'
191+
hs = self._get_client(token='abc')
192+
with patch('helpscout.client.requests') as requests, \
193+
patch('helpscout.client.logger') as logger, \
194+
patch(hs_path + '_authenticate') as auth, \
195+
patch(hs_path + '_authentication_headers') as auth_headers, \
196+
patch(hs_path + '_results_with_pagination') as pages:
197+
# Setup
198+
auth_headers.return_value = headers = {'token': 'abc'}
199+
response = requests.get.return_value = MagicMock()
200+
response.ok = True
201+
response.json.return_value = json_response = {'a': 'b'}
202+
list(hs.hit(endpoint, method, resource_id, params=params))
203+
# Asserts
204+
auth.assert_not_called()
205+
auth_headers.assert_called_once()
206+
logger.debug.assert_called_once_with(method + ' ' + full_url)
207+
requests.get.assert_called_once_with(
208+
full_url, headers=headers, data=None)
209+
response.json.assert_called_once()
210+
pages.assert_called_once_with(json_response, method)
211+
212+
def test_hit_resource_id_with_params_str_ok(self):
213+
params_str = 'embed=threads'
214+
endpoint, method, resource_id = 'users', 'get', 4
215+
full_url = (self.url + endpoint + '/' + str(resource_id) + '?' +
216+
params_str)
217+
hs_path = 'helpscout.client.HelpScout.'
218+
hs = self._get_client(token='abc')
219+
with patch('helpscout.client.requests') as requests, \
220+
patch('helpscout.client.logger') as logger, \
221+
patch(hs_path + '_authenticate') as auth, \
222+
patch(hs_path + '_authentication_headers') as auth_headers, \
223+
patch(hs_path + '_results_with_pagination') as pages:
224+
# Setup
225+
auth_headers.return_value = headers = {'token': 'abc'}
226+
response = requests.get.return_value = MagicMock()
227+
response.ok = True
228+
response.json.return_value = json_response = {'a': 'b'}
229+
list(hs.hit(endpoint, method, resource_id, params=params_str))
230+
# Asserts
231+
auth.assert_not_called()
232+
auth_headers.assert_called_once()
233+
logger.debug.assert_called_once_with(method + ' ' + full_url)
234+
requests.get.assert_called_once_with(
235+
full_url, headers=headers, data=None)
236+
response.json.assert_called_once()
237+
pages.assert_called_once_with(json_response, method)
238+
145239
def test_hit_post_ok(self):
146240
endpoint, method = 'users', 'post'
147241
full_url = self.url + endpoint
@@ -660,7 +754,7 @@ def test_getattr_requester_get(self):
660754
hit.return_value = hit_return = 9
661755
getattr(hs, endpoint).get(params=params)
662756
HelpScoutObject.cls.assert_called_with(endpoint, endpoint)
663-
hit.assert_called_with(endpoint + '?id=10&name=Mike', 'get', None)
757+
hit.assert_called_with(endpoint, 'get', None, params=params)
664758
cls.from_results.assert_called_with(hit_return)
665759

666760
def test_getattr_requester_delete_resource_id(self):

tests/helpscout/test_model.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ def test_from_results_empty(self):
3636
self.assertTrue(isinstance(users, list))
3737
self.assertEqual(len(users), 0)
3838

39+
def test_from_results_single(self):
40+
data = {'id': 9}
41+
data_generator = (data for _ in range(1))
42+
cls = HelpScoutObject.cls('users', 'users')
43+
users = cls.from_results(data_generator)
44+
self.assertTrue(isinstance(users, list))
45+
self.assertEqual(len(users), 1)
46+
3947
def test_entity_class_name(self):
4048
cls = HelpScoutObject.cls('users', 'users')
4149
self.assertEqual(cls.__name__, 'User')

0 commit comments

Comments
 (0)