Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trash endpoint #332

Merged
merged 26 commits into from
Oct 9, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions boxsdk/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..util.api_call_decorator import api_call
from ..object.search import Search
from ..object.events import Events
from ..object.trash import Trash
from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection
from ..pagination.marker_based_object_collection import MarkerBasedObjectCollection
from ..util.shared_link import get_shared_link_header
Expand Down Expand Up @@ -159,6 +160,17 @@ def collaboration(self, collab_id):
"""
return self.translator.translate('collaboration')(session=self._session, object_id=collab_id)

def trash(self):
"""
Initialize a :class:`Trash` object.

:return:
A :class:`Trash` object.
:rtype:
:class:`Trash`
"""
return Trash(self._session)
mattwiller marked this conversation as resolved.
Show resolved Hide resolved

def legal_hold_policy(self, policy_id):
"""
Initialize a :class:`LegalHoldPolicy` object, whose box id is policy_id.
Expand Down
1 change: 1 addition & 0 deletions boxsdk/object/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'retention_policy',
'retention_policy_assignment',
'search',
'trash',
'task',
'task_assignment',
'user',
Expand Down
129 changes: 129 additions & 0 deletions boxsdk/object/trash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# coding: utf-8
from __future__ import unicode_literals, absolute_import

import json

from .base_endpoint import BaseEndpoint
from ..pagination.limit_offset_based_object_collection import LimitOffsetBasedObjectCollection


class Trash(BaseEndpoint):
"""Box API endpoint for performing trash related actions in Box."""

def get_item(self, item, fields=None):
"""
Get item from trash.

:param item:
The :class:`Item` object to get info on.
:type item:
:class:`Item`
:param fields:
List of fields to request
:type fields:
`Iterable` of `unicode`
:returns:
Information for a trashed :class:`Item` object.
:rtype:
:class:`Item`
"""
url = item.get_url('trash')
params = {}
if fields:
params['fields'] = ','.join(fields)
box_response = self._session.get(url, params=params)
response = box_response.json()
return self.translator.translate(response['type'])(
session=self._session,
object_id=response['id'],
response_object=response,
)

def restore_item(self, item, name=None, parent_folder=None, fields=None):
"""
Restores an item from the trash. Could be files, folders, or weblinks.

:param item:
The :class:`Item` object to restore from trash.
:type item:
:class:`Item`.
:param name:
The new name for this item. Only used if the item can't be restored due to name conflict.
:type name:
`unicode` or None
:param parent_folder:
The new parent folder. Only used if the previous parent folder no longer exists.
:type parent_folder:
:class:`Item` or None
:param fields:
List of fields to request
:type fields:
`Iterable` of `unicode`
:returns:
A restored :class:`Item`.
:rtype:
:class:`Item`.
"""
url = item.get_url()
body = {}
if name is not None:
body['name'] = name
if parent_folder is not None:
body['parent'] = {'id': parent_folder.object_id}
params = {}
if fields:
params['fields'] = ','.join(fields)
box_response = self._session.post(url, data=json.dumps(body), params=params)
response = box_response.json()
return self.translator.translate(response['type'])(
session=self._session,
object_id=response['id'],
response_object=response,
)

def permanently_delete_item(self, item):
"""
Permanently delete an item that is in the trash. The item will no longer exist in Box.
mattwiller marked this conversation as resolved.
Show resolved Hide resolved

:param item:
The :class:`Item` to delete from trash.
:type item:
:class:`Item`
:returns:
Whether or not the delete was successful.
:rtype:
`bool`
"""
url = item.get_url('trash')
box_response = self._session.delete(url, expect_json_response=False)
return box_response.ok

def get_items(self, limit=None, offset=None, fields=None):
"""
Using limit-offset paging, get the files, folders and web links that are in the user's trash.

:param limit:
The maximum number of entries to return per page. If not specified, then will use the server-side default.
:type limit:
`int` or None
:param offset:
The offset of the item at which to begin the response.
:type offset:
`int` or None
:param fields:
List of fields to request.
:type fields:
`Iterable` of `unicode`
:returns:
An iterator of the entries in the trash
:rtype:
:class:`BoxObjectCollection`
"""
return LimitOffsetBasedObjectCollection(
session=self._session,
url=self._session.get_url('folders', 'trash', 'items'),
limit=limit,
offset=offset,
fields=fields,
return_full_pages=False,
)
97 changes: 97 additions & 0 deletions docs/trash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
Trash
=====

Under normal circumstances, when an item in Box is deleted, it is not actually erased immediately. Instead, it is
moved to the Trash. The Trash allows you to recover files and folders that have been deleted. By default, items in
the Trash will be purged after 30 days.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [List Trashed Items](#list-trashed-items)
- [Get Trashed Items](#get-trashed-items)
- [Restore Item from Trash](#restore-item-from-trash)
- [Permanently Delete Item](#permanently-delete-item)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

List Trashed Items
------------------

To retrieve a list of all trashed items, you can use `client.trash().get__items(limit=None, offset=None, fields=None)`

```python
trashed_items = client.trash().get_items()
for trashed_item in trashed_items:
# Do something
```

Get Trashed Items
-----------------

To retrieve a file, folder, weblink from the trash, use `client.trash.get_item(item, fields=None)`.

```python
file_info_to_retrieve = client.file('11111')
file_from_trash = client.trash().get_item(file_info_to_retrieve)
```

```python
folder = client.folder('22222')
folder_from_trash = client.trash().get_item(folder)
```

```python
web_link = client.web_link('33333')
web_link_from_trash = client.trash().get_item(web_link)
```

Restore Item from Trash
-----------------------

To retore a trashed item, effectively undeleting it, call `client.trash().restore_item(item, name=None, parent_folder=None, fields=None)`.

```python
file_to_restore = client.file('11111')
restored_file = client.trash().restore_item(file_to_restore)
```

```python
folder_to_restore = client.folder('22222')
restored_folder = client.trash().restore_item(folder_to_restore)
```

```python
web_link_to_restore = client.web_link('33333')
restored_web_link = client.trash().restore_item(web_link_to_restore)
```

In order to avoid conflicts, you can set a new name and new parent folder for the item you wish to restore.

```python
file_to_restore = client.file('11111')
new_name = 'New File Name'
new_parent_folder = client.folder('22222')
restored_file = client.trash().restore_item(file_to_restore, new_name, new_parent_folder)
```

Permanently Delete Item
-----------------------

To delete an item from trash, use `client.trash().permanently_delete_item(item)`.

```python
file_to_delete = client.file('11111')
client.trash().permanently_delete_item(file_to_delete)
```

```python
folder = client.folder('22222')
client.trash().permanently_delete_item(folder)
```

```python
web_link = client.web_link('33333')
client.trash().permanently_delete_item(web_link)
```
6 changes: 6 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.file import File
from boxsdk.object.group import Group
from boxsdk.object.user import User
from boxsdk.object.trash import Trash
from boxsdk.object.group_membership import GroupMembership
from boxsdk.object.retention_policy import RetentionPolicy
from boxsdk.object.file_version_retention import FileVersionRetention
Expand Down Expand Up @@ -495,6 +496,11 @@ def test_legal_hold_policies_return_the_correct_policy_objects(
assert policy._session == mock_box_session


def test_trash_initializer(mock_client):
trash = mock_client.trash()
assert isinstance(trash, Trash)


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()
Expand Down
9 changes: 9 additions & 0 deletions test/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,12 @@ def mock_folder_response(mock_object_id, make_mock_box_request):
response={'type': 'folder', 'id': mock_object_id},
)
return mock_box_response


@pytest.fixture(scope='function')
def mock_web_link_response(mock_object_id, make_mock_box_request):
# pylint:disable=redefined-outer-name
mock_box_response, _ = make_mock_box_request(
response={'type': 'web_link', 'id': mock_object_id},
)
return mock_box_response