Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions boxsdk/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ..util.api_call_decorator import api_call
from ..object.search import Search
from ..object.events import Events
from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
from ..util.shared_link import get_shared_link_header


Expand Down Expand Up @@ -316,6 +317,43 @@ def create_group(self, name):
response_object=response,
)

@api_call
def get_recent_items(self, limit=None, marker=None, fields=None, **collection_kwargs):
"""
Get the user's recently accessed items.

:param: limit
The maximum number of items to return. If limit is set to None, then the default
limit (returned by Box in the response) is used. See https://developer.box.com/reference#get-recent-items
for default.
:type: limit
`int` or None
:param marker:
The index at which to start returning items.
:type marker:
`str` or None
:param fields:
List of fields to request on the file or folder which the `RecentItem` references.
:type fields:
`Iterable` of `unicode`
:param **collection_kwargs:
Keyword arguments passed to `MarkerBasedObjectCollection`.
:type **collection_args:
`dict`
:returns:
An iterator on the user's recent items
:rtype:
:class:`MarkerBasedObjectCollection`
"""
return MarkerBasedObjectCollection(
self.session,
self.get_url('recent_items'),
limit=limit,
fields=fields,
marker=marker,
**collection_kwargs
)

@api_call
def get_shared_item(self, shared_link, password=None):
"""
Expand Down
2 changes: 1 addition & 1 deletion boxsdk/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
from six.moves import map # pylint:disable=redefined-builtin


__all__ = list(map(str, ['collaboration', 'events', 'event', 'file', 'folder', 'group', 'group_membership', 'search', 'user']))
__all__ = list(map(str, ['collaboration', 'events', 'event', 'file', 'folder', 'group', 'group_membership', 'recent_item', 'search', 'user']))
27 changes: 27 additions & 0 deletions boxsdk/object/recent_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# coding: utf-8

from __future__ import unicode_literals, absolute_import

from .base_endpoint import BaseEndpoint
from .base_api_json_object import BaseAPIJSONObject


class RecentItem(BaseEndpoint, BaseAPIJSONObject):
"""Represents a single recent item accessed by a Box user."""

_item_type = 'recent_item'

@property
def item(self):
"""
Returns the Box Item which this recent item references.
:rtype:
:class:`Item`
"""
item = self._response_object['item']
return self.translator.translate(item['type'])(
session=self._session,
object_id=item['id'],
response_object=item,
)
15 changes: 10 additions & 5 deletions boxsdk/pagination/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
from collections import Sequence
import copy

from boxsdk.object.base_object import BaseObject
from boxsdk.object.base_endpoint import BaseEndpoint


class Page(Sequence, object):
"""
Expand Down Expand Up @@ -59,11 +62,13 @@ def __getitem__(self, key):
"""
item_json = self._response_object[self._item_entries_key_name][key]
item_class = self._translator.translate(item_json['type'])
item = item_class(
session=self._session,
object_id=item_json['id'],
response_object=item_json,
)
kwargs = {}
if issubclass(item_class, BaseObject):
kwargs['object_id'] = item_json['id']
if issubclass(item_class, BaseEndpoint):
kwargs['session'] = self._session

item = item_class(response_object=item_json, **kwargs)
return item

def __len__(self):
Expand Down
44 changes: 44 additions & 0 deletions test/unit/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from boxsdk.object.group import Group
from boxsdk.object.user import User
from boxsdk.object.group_membership import GroupMembership
from boxsdk.pagination.marker_based_object_collection import MarkerBasedObjectCollection


@pytest.fixture
Expand Down Expand Up @@ -60,6 +61,11 @@ def folder_id():
return '1022'


@pytest.fixture(scope='module')
def marker_id():
return 'marker_1'


@pytest.fixture(scope='module')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this break if it is a module fixture? Because the Mock will retain its call spec between tests if we do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This user_response object was pre-existing, git just reports me as adding it. I added the lines above, with the @pytext.fixture from there. So for the comment on scope below, I followed suit from this line you're commenting on here. Additionally, I believe most of these fixtures are used in a read-only sense, so reuse of them in the tests is inconsequential.

def users_response(user_id_1, user_id_2):
# pylint:disable=redefined-outer-name
Expand Down Expand Up @@ -145,6 +151,19 @@ def search_response(file_id, folder_id):
return mock_network_response


@pytest.fixture(scope='module')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, this doesn't seem like something that should be module scoped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

def recent_items_response(file_id):
mock_network_response = Mock(DefaultNetworkResponse)
mock_network_response.json.return_value = {
'entries': [
{'type': 'recent_item', 'item': {'type': 'file', 'id': file_id}}
],
'next_marker': None,
'limit': 100,
}
return mock_network_response


@pytest.mark.parametrize('test_class, factory_method_name', [
(Folder, 'folder'),
(File, 'file'),
Expand Down Expand Up @@ -262,6 +281,31 @@ def test_create_group_returns_the_correct_group_object(mock_client, mock_box_ses
assert new_group.name == test_group_name


def test_get_recent_items_returns_the_correct_items(mock_client, mock_box_session, recent_items_response, file_id):
mock_box_session.get.return_value = recent_items_response
recent_items = mock_client.get_recent_items()
assert isinstance(recent_items, MarkerBasedObjectCollection)
recent_item = recent_items.next()
assert recent_item.item.object_id == file_id
next_pointer = recent_items.next_pointer()
assert next_pointer is None


def test_get_recent_items_sends_get_with_correct_params(mock_client, mock_box_session, recent_items_response, marker_id):
limit = 50
marker = marker_id
fields = ['modified_at', 'name']
expected_params = {
'limit': limit,
'marker': marker_id,
'fields': ','.join(fields),
}
mock_box_session.get.return_value = recent_items_response
object_collection = mock_client.get_recent_items(limit=limit, marker=marker, fields=fields)
object_collection.next()
mock_box_session.get.assert_called_once_with('{0}/recent_items'.format(API.BASE_API_URL), params=expected_params)


@pytest.mark.parametrize('password', (None, 'p4ssw0rd'))
def test_get_shared_item_returns_the_correct_item(mock_client, mock_box_session, shared_item_response, password):
# pylint:disable=redefined-outer-name
Expand Down
17 changes: 17 additions & 0 deletions test/unit/object/test_recent_item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# coding: utf-8

from __future__ import unicode_literals

from boxsdk.object.recent_item import RecentItem


def test_init_recent_item(mock_box_session, mock_object_id):
recent_item = RecentItem(
session=mock_box_session,
response_object={
"type": "recent_item",
"item": {"type": "file", "id": mock_object_id}
})
assert recent_item['type'] == 'recent_item'
assert recent_item.item.object_id == mock_object_id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also assert recent_item.item.session is mock_box_session?

assert recent_item.item.session is mock_box_session