Skip to content

Commit

Permalink
[1.0.0] Hit nexted, hit_ generator, resources as dictionary, subendpo…
Browse files Browse the repository at this point in the history
…ints as attributes
  • Loading branch information
santiher committed Sep 18, 2019
1 parent d8baac2 commit f5e1de2
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 31 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to this project will be documented here.

## [1.0.0] - 2019-09-18
### Changed
- HelpScoutEndpointRequester now returns subentities for methods named other
than http methods like get / post / put / delete / etc.
- HelpScoutEndpointRequester can now be accessed as a dictionary to request
specific resources or specific resources sub endpoints (like a conversation's
tags).
- The client's *hit* method has been renamed to *hit_* and *hit* nows nexts the
generator.

## [0.2.3] - 2019-07-16
### Fixed
- Setting attributes to HelpScout objects adds them to the attributes list.
Expand Down
102 changes: 99 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The client contains as little internal knowledge of the API as possible, mostly
authentication, pagination and how are objects returned.

In order to handle pagination calls to API are done inside a generator.
As a consequence, even post and deletes have to be "nexted" if using the *hit*
As a consequence, even post and deletes have to be "nexted" if using the *hit_*
method.

## Installation
Expand All @@ -30,7 +30,7 @@ More about credentials can be found in

## General use

The general use is by instantiating a client and then hitting the API by
The general use is by instantiating a client and then hitting the API by
doing `client.<endpoint>.<method>(<resource_id>, <params>)`. Where:

* *Endpoint* is one of the endpoints defined in the API's documentation.
Expand All @@ -40,6 +40,12 @@ doing `client.<endpoint>.<method>(<resource_id>, <params>)`. Where:
* *Params* can be None, a string or a dictionary with the parameters to access
in the get method or the data to send otherwise.

To access attributes of specific resources, like the tags of a conversation,
you can do:
`client.<endpoint>[<resource_id>].<attribute>.<method>(<resource_id>, <params>)`.

Example: `client.conversations[212109].threads.get()`

## Examples

### Listing all users
Expand Down Expand Up @@ -89,6 +95,30 @@ User(id=12391,
}
```

### Hitting the API directly to get all mailboxes but handling requests with pagination as iteration goes on

```python
> from helpscout.client import HelpScout
> hs = HelpScout(app_id='laknsdo', app_secret='12haosd9')
> for mailbox in hs.hit_('mailboxes', 'get'):
> print(mailbox)
{'mailboxes': [
{'id': 1930,
'name': 'Fake Support',
'slug': '0912301u',
'email': '[email protected]',
'createdAt': '2018-12-20T20:00:00Z',
'updatedAt': '2019-05-01T16:00:00Z',
'_links': {
'fields': {'href': 'https://api.helpscout.net/v2/mailboxes/1930/fields/'},
'folders': {'href': 'https://api.helpscout.net/v2/mailboxes/1930/folders/'},
'self': {'href': 'https://api.helpscout.net/v2/mailboxes/1930'}
}
}
]
}
```

### Hitting the API directly to get a specific mailbox

```python
Expand All @@ -110,6 +140,30 @@ User(id=12391,
}
```

### Hitting the API directly to get a specific mailbox dictionary style

In this case, you will have to select the first element of the list yourself,
as it is not quite clear if one or more elements should be expected from the
api depending on the endpoint.

```python
> from helpscout.client import HelpScout
> hs = HelpScout(app_id='laknsdo', app_secret='12haosd9')
> print(hs.mailboxes[1930].get())
[{'id': 1930,
'name': 'Fake Support',
'slug': '0912301u',
'email': '[email protected]',
'createdAt': '2018-12-20T20:00:00Z',
'updatedAt': '2019-05-01T16:00:00Z',
'_links': {
'fields': {'href': 'https://api.helpscout.net/v2/mailboxes/1930/fields/'},
'folders': {'href': 'https://api.helpscout.net/v2/mailboxes/1930/folders/'},
'self': {'href': 'https://api.helpscout.net/v2/mailboxes/1930'}
}
}]
```

### Listing conversations using a dictionary parameters

```python
Expand Down Expand Up @@ -143,6 +197,48 @@ User(id=12391,
> from helpscout.client import HelpScout
> hs = HelpScout(app_id='asdon123', app_secret='asdoin1')
> report_url = 'reports/happiness?start=2019-06-01T00:00:00Z&end=2019-06-15:00:00Z'
> next(hs.hit(report_url, 'get'))
> next(hs.hit_(report_url, 'get'))
...
```

or

```python
> from helpscout.client import HelpScout
> hs = HelpScout(app_id='asdon123', app_secret='asdoin1')
> report_url = 'reports/happiness?start=2019-06-01T00:00:00Z&end=2019-06-15:00:00Z'
> hs.hit(report_url, 'get')
...
```

### Adding tags to a conversation

```python
> from helpscout import HelpScout
> helpscout_client = HelpScout(app_id='ax0912n', app_secret='axon129')
> conversation_id = 999
> endpoint = 'conversations/%s/tags' % conversation_id
> data = {'tags': conversation_tags}
> helpscout_client.hit(endpoint, 'put', data=data)
```

or

```python
> from helpscout import HelpScout
> helpscout_client = HelpScout(app_id='ax0912n', app_secret='axon129')
> conversation_id = 999
> endpoint = 'conversations/%s/tags' % conversation_id
> data = {'tags': conversation_tags}
> next(helpscout_client.hit_(endpoint, 'put', data=data))
```

or

```python
> from helpscout import HelpScout
> helpscout_client = HelpScout(app_id='ax0912n', app_secret='axon129')
> conversation_id = 999
> data = {'tags': conversation_tags}
> helpscout_client.conversations[999].tags.put(data=data)
```
2 changes: 1 addition & 1 deletion helpscout/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from helpscout.client import HelpScout # noqa


__version__ = '0.2.3'
__version__ = '1.0.0'
81 changes: 73 additions & 8 deletions helpscout/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,43 @@ def get_objects(self, endpoint, resource_id=None, params=None):
"""
cls = HelpScoutObject.cls(endpoint, endpoint)
results = cls.from_results(
self.hit(endpoint, 'get', resource_id, params=params))
self.hit_(endpoint, 'get', resource_id, params=params))
if resource_id is not None:
return results[0]
return results

def hit(self, endpoint, method, resource_id=None, data=None, params=None):
"""Hits the api and returns all the data.
If several calls are needed due to pagination, control won't be
returned to the caller until all is retrieved.
Parameters
----------
endpoint: str
The API endpoint.
method: str
The http method to hit the endpoint with.
One of {'get', 'post', 'put', 'patch', 'delete', 'head', 'options'}
resource_id: int or str or None
The id of the resource in the endpoint to query.
E.g.: in "GET /v2/conversations/123 HTTP/1.1" the id would be 123.
If None is provided, nothing will be done
data: dict or None
A dictionary with the data to send to the API as json.
params: dict or str or None
Dictionary with the parameters to send to the url.
Or the parameters already un url format.
Returns
-------
[dict] or [None]
dict: when several objects are received from the API, a list of
dictionaries with HelpScout's _embedded data will be returned
None if http 201 created or 204 no content are received.
"""
return list(self.hit_(endpoint, method, resource_id, data, params))

def hit_(self, endpoint, method, resource_id=None, data=None, params=None):
"""Hits the api and yields the data.
Parameters
Expand All @@ -123,9 +154,10 @@ def hit(self, endpoint, method, resource_id=None, data=None, params=None):
Or the parameters already un url format.
Yields
-------
dict
------
dict or None
Dictionary with HelpScout's _embedded data.
None if http 201 created or 204 no content are received.
"""
if self.access_token is None:
self._authenticate()
Expand All @@ -148,11 +180,11 @@ def hit(self, endpoint, method, resource_id=None, data=None, params=None):
yield item
elif status_code == 401:
self._authenticate()
for item in self.hit(endpoint, method, resource_id, data):
for item in self.hit_(endpoint, method, resource_id, data):
yield item
elif status_code == 429:
self._handle_rate_limit_exceeded()
for item in self.hit(endpoint, method, resource_id, data):
for item in self.hit_(endpoint, method, resource_id, data):
yield item
else:
raise HelpScoutException(r.text)
Expand Down Expand Up @@ -282,6 +314,9 @@ def __init__(self, client, endpoint):

def __getattr__(self, method):
"""Catches http methods like get, post, patch, put and delete.
Returns a subrequester when methods not named after http methods are
requested, as this are considered attributes of the main object, like
tags of a conversation.
Parameters
----------
Expand All @@ -291,11 +326,41 @@ def __getattr__(self, method):
Returns
-------
client.get_objects return value for the *get* method.
client.hit return value for other methods.
client.hit return value for other http named methods.
HelpScoutEndpointRequester when other attributes are accessed, this is
expected to be used mainly for subattributes of an endpoint or
subendpoints of specific resources, like tags from a conversation.
"""
if method == 'get':
return partial(self.client.get_objects, self.endpoint)
return partial(self._yielded_function, method)
elif method in ('head', 'post', 'put', 'delete', 'patch', 'connect',
'options', 'trace'):
return partial(self._yielded_function, method)
else:
return HelpScoutEndpointRequester(
self.client, urljoin(self.endpoint + '/', str(method)))

def __getitem__(self, resource_id):
"""Returns a second endpoint requester extending the endpoint to a
specific resource_id or resource_name.
This is intented to access things such as conversations tags or notes,
whose url are like 'conversations/%s/tags' % conversation_id
Parameters
----------
resource_id: int or str
The resource id or attribute available in the API through a
specific call.
Returns
-------
HelpScoutEndpointRequester
A second endpoint requester for the specific resource id of the
main requester's endpoint.
"""
return HelpScoutEndpointRequester(
self.client, urljoin(self.endpoint + '/', str(resource_id)))

def _yielded_function(self, method, *args, **kwargs):
"""Calls a generator function and calls next.
Expand All @@ -313,7 +378,7 @@ def _yielded_function(self, method, *args, **kwargs):
-------
client.hit yielded value.
"""
return next(self.client.hit(self.endpoint, method, *args, **kwargs))
return next(self.client.hit_(self.endpoint, method, *args, **kwargs))

def __eq__(self, other):
"""Equality comparison."""
Expand Down
Loading

0 comments on commit f5e1de2

Please sign in to comment.