Skip to content

Commit ea32ba5

Browse files
autoreleasefooljmoldow
authored andcommitted
Add new Recents API to client (#210)
See: https://developer.box.com/v2.0/reference#recent-item-object Use the Client to retrieve a user's recently accessed items on Box. `RecentItem` class allows the translator to correctly instantiate the item returned by the API, which has the field `type='recent_item'`. This provides some additional fields, and access to the underlying File/Folder as the property `RecentItem.item`
1 parent 1d8ee60 commit ea32ba5

File tree

6 files changed

+137
-6
lines changed

6 files changed

+137
-6
lines changed

boxsdk/client/client.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ..util.api_call_decorator import api_call
1111
from ..object.search import Search
1212
from ..object.events import Events
13+
from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
1314
from ..util.shared_link import get_shared_link_header
1415

1516

@@ -316,6 +317,43 @@ def create_group(self, name):
316317
response_object=response,
317318
)
318319

320+
@api_call
321+
def get_recent_items(self, limit=None, marker=None, fields=None, **collection_kwargs):
322+
"""
323+
Get the user's recently accessed items.
324+
325+
:param: limit
326+
The maximum number of items to return. If limit is set to None, then the default
327+
limit (returned by Box in the response) is used. See https://developer.box.com/reference#get-recent-items
328+
for default.
329+
:type: limit
330+
`int` or None
331+
:param marker:
332+
The index at which to start returning items.
333+
:type marker:
334+
`str` or None
335+
:param fields:
336+
List of fields to request on the file or folder which the `RecentItem` references.
337+
:type fields:
338+
`Iterable` of `unicode`
339+
:param **collection_kwargs:
340+
Keyword arguments passed to `MarkerBasedObjectCollection`.
341+
:type **collection_args:
342+
`dict`
343+
:returns:
344+
An iterator on the user's recent items
345+
:rtype:
346+
:class:`MarkerBasedObjectCollection`
347+
"""
348+
return MarkerBasedObjectCollection(
349+
self.session,
350+
self.get_url('recent_items'),
351+
limit=limit,
352+
fields=fields,
353+
marker=marker,
354+
**collection_kwargs
355+
)
356+
319357
@api_call
320358
def get_shared_item(self, shared_link, password=None):
321359
"""

boxsdk/object/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
from six.moves import map # pylint:disable=redefined-builtin
66

77

8-
__all__ = list(map(str, ['collaboration', 'events', 'event', 'file', 'folder', 'group', 'group_membership', 'search', 'user']))
8+
__all__ = list(map(str, ['collaboration', 'events', 'event', 'file', 'folder', 'group', 'group_membership', 'recent_item', 'search', 'user']))

boxsdk/object/recent_item.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# coding: utf-8
2+
3+
from __future__ import unicode_literals, absolute_import
4+
5+
from .base_endpoint import BaseEndpoint
6+
from .base_api_json_object import BaseAPIJSONObject
7+
8+
9+
class RecentItem(BaseEndpoint, BaseAPIJSONObject):
10+
"""Represents a single recent item accessed by a Box user."""
11+
12+
_item_type = 'recent_item'
13+
14+
@property
15+
def item(self):
16+
"""
17+
Returns the Box Item which this recent item references.
18+
19+
:rtype:
20+
:class:`Item`
21+
"""
22+
item = self._response_object['item']
23+
return self.translator.translate(item['type'])(
24+
session=self._session,
25+
object_id=item['id'],
26+
response_object=item,
27+
)

boxsdk/pagination/page.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
from collections import Sequence
66
import copy
77

8+
from boxsdk.object.base_object import BaseObject
9+
from boxsdk.object.base_endpoint import BaseEndpoint
10+
811

912
class Page(Sequence, object):
1013
"""
@@ -59,11 +62,13 @@ def __getitem__(self, key):
5962
"""
6063
item_json = self._response_object[self._item_entries_key_name][key]
6164
item_class = self._translator.translate(item_json['type'])
62-
item = item_class(
63-
session=self._session,
64-
object_id=item_json['id'],
65-
response_object=item_json,
66-
)
65+
kwargs = {}
66+
if issubclass(item_class, BaseObject):
67+
kwargs['object_id'] = item_json['id']
68+
if issubclass(item_class, BaseEndpoint):
69+
kwargs['session'] = self._session
70+
71+
item = item_class(response_object=item_json, **kwargs)
6772
return item
6873

6974
def __len__(self):

test/unit/client/test_client.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from boxsdk.object.group import Group
2424
from boxsdk.object.user import User
2525
from boxsdk.object.group_membership import GroupMembership
26+
from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection
2627

2728

2829
@pytest.fixture
@@ -60,6 +61,11 @@ def folder_id():
6061
return '1022'
6162

6263

64+
@pytest.fixture(scope='module')
65+
def marker_id():
66+
return 'marker_1'
67+
68+
6369
@pytest.fixture(scope='module')
6470
def users_response(user_id_1, user_id_2):
6571
# pylint:disable=redefined-outer-name
@@ -145,6 +151,19 @@ def search_response(file_id, folder_id):
145151
return mock_network_response
146152

147153

154+
@pytest.fixture(scope='module')
155+
def recent_items_response(file_id):
156+
mock_network_response = Mock(DefaultNetworkResponse)
157+
mock_network_response.json.return_value = {
158+
'entries': [
159+
{'type': 'recent_item', 'item': {'type': 'file', 'id': file_id}}
160+
],
161+
'next_marker': None,
162+
'limit': 100,
163+
}
164+
return mock_network_response
165+
166+
148167
@pytest.mark.parametrize('test_class, factory_method_name', [
149168
(Folder, 'folder'),
150169
(File, 'file'),
@@ -262,6 +281,31 @@ def test_create_group_returns_the_correct_group_object(mock_client, mock_box_ses
262281
assert new_group.name == test_group_name
263282

264283

284+
def test_get_recent_items_returns_the_correct_items(mock_client, mock_box_session, recent_items_response, file_id):
285+
mock_box_session.get.return_value = recent_items_response
286+
recent_items = mock_client.get_recent_items()
287+
assert isinstance(recent_items, MarkerBasedObjectCollection)
288+
recent_item = recent_items.next()
289+
assert recent_item.item.object_id == file_id
290+
next_pointer = recent_items.next_pointer()
291+
assert next_pointer is None
292+
293+
294+
def test_get_recent_items_sends_get_with_correct_params(mock_client, mock_box_session, recent_items_response, marker_id):
295+
limit = 50
296+
marker = marker_id
297+
fields = ['modified_at', 'name']
298+
expected_params = {
299+
'limit': limit,
300+
'marker': marker_id,
301+
'fields': ','.join(fields),
302+
}
303+
mock_box_session.get.return_value = recent_items_response
304+
object_collection = mock_client.get_recent_items(limit=limit, marker=marker, fields=fields)
305+
object_collection.next()
306+
mock_box_session.get.assert_called_once_with('{0}/recent_items'.format(API.BASE_API_URL), params=expected_params)
307+
308+
265309
@pytest.mark.parametrize('password', (None, 'p4ssw0rd'))
266310
def test_get_shared_item_returns_the_correct_item(mock_client, mock_box_session, shared_item_response, password):
267311
# pylint:disable=redefined-outer-name
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# coding: utf-8
2+
3+
from __future__ import unicode_literals
4+
5+
from boxsdk.object.recent_item import RecentItem
6+
7+
8+
def test_init_recent_item(mock_box_session, mock_object_id):
9+
recent_item = RecentItem(
10+
session=mock_box_session,
11+
response_object={
12+
"type": "recent_item",
13+
"item": {"type": "file", "id": mock_object_id}
14+
})
15+
assert recent_item['type'] == 'recent_item'
16+
assert recent_item.item.object_id == mock_object_id
17+
assert recent_item.item.session is mock_box_session

0 commit comments

Comments
 (0)