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

feat: Support file request APIs #747

Merged
merged 5 commits into from
Jul 14, 2022
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
12 changes: 12 additions & 0 deletions boxsdk/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from boxsdk.util.translator import Translator
from boxsdk.object.folder import Folder
from boxsdk.object.file import File
from boxsdk.object.file_request import FileRequest
from boxsdk.object.file_version import FileVersion
from boxsdk.object.upload_session import UploadSession
from boxsdk.object.comment import Comment
Expand Down Expand Up @@ -127,6 +128,17 @@ def file(self, file_id: str) -> 'File':
"""
return self.translator.get('file')(session=self._session, object_id=file_id)

def file_request(self, request_id: str) -> 'FileRequest':
"""
Initialize a :class:`FileRequest` object, whose box id is request_id.

:param request_id:
The box id of the :class:`FileRequest` object.
:return:
A :class:`FileRequest` object with the given file request id.
"""
return self.translator.get('file_request')(session=self._session, object_id=request_id)

def file_version(self, version_id: str) -> 'FileVersion':
"""
Initialize a :class:`FileVersion` object, whose box id is version_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 @@ -10,6 +10,7 @@
'events',
'event',
'file',
'file_request',
'file_version',
'file_version_retention',
'folder',
Expand Down
78 changes: 78 additions & 0 deletions boxsdk/object/file_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import json
from datetime import datetime
from typing import TYPE_CHECKING, Optional, Union

from boxsdk.util.datetime_formatter import normalize_date_to_rfc3339_format
from boxsdk.util.text_enum import TextEnum

from ..util.api_call_decorator import api_call
from .base_object import BaseObject

if TYPE_CHECKING:
from boxsdk.object.folder import Folder


class StatusState(TextEnum):
"""An enum of possible status states"""
ACTIVE = 'active'
INACTIVE = 'inactive'


class FileRequest(BaseObject):
"""Represents the file request."""
_item_type = 'file_request'

@api_call
def copy(
self,
*,
folder: 'Folder',
description: Optional[str] = None,
expires_at: Union[datetime, str] = None,
require_description: Optional[bool] = None,
require_email: Optional[bool] = None,
status: Optional[str] = None,
title: Optional[str] = None,
**_kwargs
) -> 'FileRequest':
"""Copy an existing file request already present on one folder, and applies it to another folder.

:param description:
A new description for the file request.
:param title:
A new title for the file request.
:param expires_at:
A expiration time for file request which no longer accepts new files.
:param folder:
The folder to which the file request will be saved.
:param require_description:
A flag indicating whether the file submitted must have a description.
:param require_email:
A flag indicating whether the file submitted must have sender email.
:param status:
The status of the file request.
:returns:
The copy of the file request
"""
url = self.get_url('copy')
data = {
'folder': {'id': folder.object_id, 'type': folder.object_type},
}
if description is not None:
data['description'] = description
if title is not None:
data['title'] = title
if expires_at is not None:
data['expires_at'] = normalize_date_to_rfc3339_format(expires_at)
if require_description is not None:
data['is_description_required'] = require_description
if require_email is not None:
data['is_email_required'] = require_email
if status is not None:
data['status'] = status
box_response = self._session.post(url, data=json.dumps(data))
response = box_response.json()
return self.translator.translate(
session=self._session,
response_object=response,
)
10 changes: 10 additions & 0 deletions test/unit/client/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from boxsdk.object.device_pinner import DevicePinner
from boxsdk.object.enterprise import Enterprise
from boxsdk.object.events import Events
from boxsdk.object.file_request import FileRequest
from boxsdk.object.folder import Folder
from boxsdk.object.file import File
from boxsdk.object.file_version import FileVersion
Expand Down Expand Up @@ -1706,3 +1707,12 @@ def test_create_sign_request(mock_client, mock_box_session, mock_sign_request_re
assert new_sign_request['source_files'][0]['id'] == source_file['id']
assert new_sign_request['signers'][0]['email'] == signer['email']
assert new_sign_request['parent_folder']['id'] == parent_folder_id


def test_file_request(mock_client):
# pylint:disable=redefined-outer-name
file_request_id = '12345'
file_request = mock_client.file_request(file_request_id)

assert isinstance(file_request, FileRequest)
assert file_request.object_id == file_request_id
6 changes: 6 additions & 0 deletions test/unit/object/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from boxsdk.object.comment import Comment
from boxsdk.object.device_pinner import DevicePinner
from boxsdk.object.file import File
from boxsdk.object.file_request import FileRequest
from boxsdk.object.file_version import FileVersion
from boxsdk.object.file_version_retention import FileVersionRetention
from boxsdk.object.legal_hold import LegalHold
Expand Down Expand Up @@ -414,3 +415,8 @@ def test_folder_lock(mock_box_session, mock_object_id):
@pytest.fixture()
def test_sign_request(mock_box_session, mock_object_id):
return SignRequest(mock_box_session, mock_object_id)


@pytest.fixture()
def test_file_request(mock_box_session, mock_object_id):
return FileRequest(mock_box_session, mock_object_id)
90 changes: 90 additions & 0 deletions test/unit/object/test_file_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import json
from datetime import datetime
import pytest
import pytz

from boxsdk.config import API
from boxsdk.object.folder import Folder
from boxsdk.object.file_request import FileRequest
from boxsdk.object.file_request import StatusState


def test_get(test_file_request, mock_box_session):
expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}'
mock_box_session.get.return_value.json.return_value = {
'type': test_file_request.object_type,
'id': test_file_request.object_id,
'title': 'File Request'
}
file_request = test_file_request.get()
mock_box_session.get.assert_called_once_with(expected_url, headers=None, params=None)
assert isinstance(file_request, FileRequest)
assert file_request['type'] == file_request.object_type
assert file_request['id'] == file_request.object_id
assert file_request['title'] == 'File Request'


def test_update(test_file_request, mock_box_session):
new_title = 'New File Request Title'
new_status = StatusState.INACTIVE
expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}'
mock_box_session.put.return_value.json.return_value = {
'type': test_file_request.object_type,
'id': test_file_request.object_id,
'title': new_title,
'status': new_status,
}
data = {
'title': new_title,
'status': StatusState.INACTIVE,
}
file_request = test_file_request.update_info(data=data)
mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), headers=None, params=None)
assert isinstance(file_request, FileRequest)
assert file_request['type'] == test_file_request.object_type
assert file_request['id'] == test_file_request.object_id
assert file_request['title'] == new_title
assert file_request['status'] == StatusState.INACTIVE


@pytest.mark.parametrize('expires_at', [
'2019-07-01T22:02:24+14:00',
datetime(2019, 7, 1, 22, 2, 24, tzinfo=pytz.timezone('US/Alaska'))
])
def test_copy(test_file_request, mock_box_session, expires_at):
new_folder_id = '100'
expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}/copy'
expected_expires_at = '2019-07-01T22:02:24+14:00'
mock_box_session.post.return_value.json.return_value = {
'type': test_file_request.object_type,
'id': test_file_request.object_id,
'title': 'File Request Copied',
'folder': {
'type': 'folder',
'id': new_folder_id,
},
'expires_at': expected_expires_at,
}
new_title = 'File Request Copied'
new_folder = Folder(mock_box_session, object_id=new_folder_id)
file_request = test_file_request.copy(title=new_title, folder=new_folder, expires_at=expires_at)
data = {
'folder': {
'id': new_folder_id,
'type': 'folder',
},
'title': new_title,
'expires_at': expected_expires_at,
}
mock_box_session.post.assert_called_once_with(expected_url, data=json.dumps(data))
assert isinstance(file_request, FileRequest)
assert file_request['type'] == test_file_request.object_type
assert file_request['title'] == 'File Request Copied'
assert file_request['folder']['id'] == '100'
assert file_request['folder']['type'] == 'folder'


def test_delete(test_file_request, mock_box_session):
expected_url = f'{API.BASE_API_URL}/file_requests/{test_file_request.object_id}'
test_file_request.delete()
mock_box_session.delete.assert_called_once_with(expected_url, expect_json_response=False, headers=None, params={})