Repository: box/box-python-sdk
Pull request #25 (2015-03-05 01:33:27)

Add preflight
Add preflight interface to `File` class.

I will add preflight to file update and file upload in later PRs.


Patch:
diff --git a/HISTORY.rst b/HISTORY.rst
index 0e8b85c..5b2d910 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,12 +3,6 @@
 Release History
 ---------------
 
-1.1.1 (2015-03-11)
-++++++++++++++++++
-
-- A minor change to namespacing. The ``OAuth2`` class can now be imported directly from ``boxsdk``.
-  Demo code has been updated to reflect the change.
-
 1.1.0 (2015-03-02)
 ++++++++++++++++++
 
diff --git a/README.rst b/README.rst
index 532f48a..55e3c7f 100644
--- a/README.rst
+++ b/README.rst
@@ -37,7 +37,7 @@ Get the authorization url
 
 .. code-block:: python
 
-    from boxsdk import OAuth2
+    from boxsdk.auth.oauth2 import OAuth2
 
     oauth = OAuth2(
         client_id='YOUR_CLIENT_ID',
@@ -81,7 +81,7 @@ Create an authenticated client
 
 .. code-block:: python
 
-    from boxsdk import Client
+    from boxsdk.client import Client
 
     client = Client(oauth)
 
diff --git a/boxsdk/__init__.py b/boxsdk/__init__.py
index 83126e9..d8fea93 100644
--- a/boxsdk/__init__.py
+++ b/boxsdk/__init__.py
@@ -2,6 +2,5 @@
 
 from __future__ import unicode_literals
 
-from .auth.oauth2 import OAuth2
 from .client import Client
 from .object import *  # pylint:disable=wildcard-import,redefined-builtin
diff --git a/boxsdk/object/collaboration.py b/boxsdk/object/collaboration.py
index 07b8eed..b7c3d16 100644
--- a/boxsdk/object/collaboration.py
+++ b/boxsdk/object/collaboration.py
@@ -8,16 +8,16 @@
 
 class CollaborationRole(TextEnum):
     """An enum of possible collaboration roles"""
-    EDITOR = 'editor'
-    VIEWER = 'viewer'
+    EDITOR = 'Editor'
+    VIEWER = 'Viewer'
 
     # Available to enterprise accounts:
-    PREVIEWER = 'previewer'
-    UPLOADER = 'uploader'
-    PREVIEWER_UPLOADER = 'previewer uploader'
-    VIEWER_UPLOADER = 'viewer uploader'
-    CO_OWNER = 'co-owner'
-    OWNER = 'owner'
+    PREVIEWER = 'Previewer'
+    UPLOADER = 'Uploader'
+    PREVIEWER_UPLOADER = 'Previewer-Uploader'
+    VIEWER_UPLOADER = 'Viewer-Uploader'
+    CO_OWNER = 'Co-Owner'
+    OWNER = 'Owner'
 
 
 class CollaborationStatus(TextEnum):
diff --git a/boxsdk/object/file.py b/boxsdk/object/file.py
index c705b86..401be2c 100644
--- a/boxsdk/object/file.py
+++ b/boxsdk/object/file.py
@@ -1,6 +1,7 @@
 # coding: utf-8
 
 from __future__ import unicode_literals
+
 from boxsdk.config import API
 from .item import Item
 from .metadata import Metadata
@@ -11,6 +12,27 @@ class File(Item):
 
     _item_type = 'file'
 
+    def preflight_check(self, size, name=None):
+        """
+        Make an API call to check if the file can be updated with the new name and size of the file.
+
+        :param size:
+            The size of the file in bytes. Specify 0 for unknown file-sizes.
+        :type size:
+            `int`
+        :param name:
+            The name of the file to be updated. It's optional, if the name is not being changed.
+        :type name:
+            `unicode`
+        :raises:
+            :class:`BoxAPIException` when preflight check fails.
+        """
+        self._preflight_check(
+            size=size,
+            name=name,
+            file_id=self._object_id,
+        )
+
     def content(self):
         """
         Get the content of a file on Box.
diff --git a/boxsdk/object/folder.py b/boxsdk/object/folder.py
index b0bb835..8892bcc 100644
--- a/boxsdk/object/folder.py
+++ b/boxsdk/object/folder.py
@@ -91,6 +91,27 @@ class Folder(Item):
 
     _item_type = 'folder'
 
+    def preflight_check(self, size, name):
+        """
+        Make an API call to check if a new file with given name and size can be uploaded to this folder.
+
+        :param size:
+            The size of the file in bytes. Specify 0 for unknown file-sizes.
+        :type size:
+            `int`
+        :param name:
+            The name of the file to be uploaded.
+        :type name:
+            `unicode`
+        :raises:
+            :class:`BoxAPIException` when preflight check fails.
+        """
+        self._preflight_check(
+            size=size,
+            name=name,
+            parent_id=self._object_id,
+        )
+
     def get_items(self, limit, offset=0, fields=None):
         """Get the items in a folder.
 
diff --git a/boxsdk/object/item.py b/boxsdk/object/item.py
index 1bc446a..8e5e483 100644
--- a/boxsdk/object/item.py
+++ b/boxsdk/object/item.py
@@ -5,11 +5,51 @@
 import json
 
 from .base_object import BaseObject
+from boxsdk.config import API
 
 
 class Item(BaseObject):
     """Box API endpoint for interacting with files and folders."""
 
+    def _preflight_check(self, size, name=None, file_id=None, parent_id=None):
+        """
+        Make an API call to check if certain file can be uploaded to Box or not.
+        (https://developers.box.com/docs/#files-preflight-check)
+
+        :param size:
+            The size of the file to be uploaded in bytes. Specify 0 for unknown file sizes.
+        :type size:
+            `int`
+        :param name:
+            The name of the file to be uploaded. This is optional if `file_id` is specified,
+            but required for new file uploads.
+        :type name:
+            `unicode`
+        :param file_id:
+            Box id of the file to be uploaded. Not required for new file uploads.
+        :type file_id:
+            `unicode`
+        :param parent_id:
+            The ID of the parent folder. Required only for new file uploads.
+        :type parent_id:
+            `unicode`
+        :raises:
+            :class:`BoxAPIException` when preflight check fails.
+        """
+        endpoint = '{0}/content'.format(file_id) if file_id else 'content'
+        url = '{0}/files/{1}'.format(API.BASE_API_URL, endpoint)
+        data = {'size': size}
+        if name:
+            data['name'] = name
+        if parent_id:
+            data['parent'] = {'id': parent_id}
+
+        self._session.options(
+            url=url,
+            expect_json_response=False,
+            data=json.dumps(data),
+        )
+
     def update_info(self, data, etag=None):
         """Baseclass override.
 
diff --git a/demo/auth.py b/demo/auth.py
index 994addb..6b14542 100644
--- a/demo/auth.py
+++ b/demo/auth.py
@@ -8,7 +8,7 @@
 import webbrowser
 from wsgiref.simple_server import WSGIServer, WSGIRequestHandler, make_server
 
-from boxsdk import OAuth2
+from boxsdk.auth.oauth2 import OAuth2
 
 
 CLIENT_ID = ''  # Insert Box client ID here
diff --git a/demo/example.py b/demo/example.py
index 0f4eca7..c2e7bb7 100644
--- a/demo/example.py
+++ b/demo/example.py
@@ -2,9 +2,9 @@
 
 from __future__ import print_function, unicode_literals
 import os
-from boxsdk import Client
-from boxsdk.exception import BoxAPIException
+from boxsdk.client import Client
 from boxsdk.object.collaboration import CollaborationRole
+from boxsdk.exception import BoxAPIException
 from auth import authenticate
 
 
diff --git a/setup.py b/setup.py
index 1af9167..cd13558 100644
--- a/setup.py
+++ b/setup.py
@@ -57,7 +57,7 @@ def main():
         install_requires.append('ordereddict>=1.1')
     setup(
         name='boxsdk',
-        version='1.1.1',
+        version='1.1.0',
         description='Official Box Python SDK',
         long_description=open(join(base_dir, 'README.rst')).read(),
         author='Box',


Test patch:
diff --git a/test/unit/object/test_file.py b/test/unit/object/test_file.py
index d960416..f729204 100644
--- a/test/unit/object/test_file.py
+++ b/test/unit/object/test_file.py
@@ -99,3 +99,43 @@ def test_unlock(test_file, mock_box_session, mock_file_response):
         params=None,
         headers=None,
     )
+
+
+@pytest.mark.parametrize(
+    'size, name, expected_data',
+    [
+        # Test case for specifying the name of the file for preflight
+        (
+            100,
+            'foo.txt',
+            json.dumps({'size': 100, 'name': 'foo.txt'}),
+        ),
+
+        # Test case for omitting the name of the file for preflight
+        (
+            200,
+            None,
+            json.dumps({'size': 200})
+        ),
+    ]
+)
+def test_preflight_check(
+        test_file,
+        mock_object_id,
+        mock_box_session,
+        size,
+        name,
+        expected_data,
+):
+    kwargs = {'size': size}
+    if name:
+        kwargs['name'] = name
+    test_file.preflight_check(**kwargs)
+    mock_box_session.options.assert_called_once_with(
+        url='{0}/files/{1}/content'.format(
+            API.BASE_API_URL,
+            mock_object_id,
+        ),
+        expect_json_response=False,
+        data=expected_data,
+    )
diff --git a/test/unit/object/test_folder.py b/test/unit/object/test_folder.py
index 482542a..6bf0934 100644
--- a/test/unit/object/test_folder.py
+++ b/test/unit/object/test_folder.py
@@ -167,3 +167,19 @@ def test_update_sync_state(test_folder, mock_folder_response, mock_box_session,
     mock_box_session.put.assert_called_once_with(expected_url, data=json.dumps(data), params=None, headers=None)
     assert isinstance(update_response, Folder)
     assert update_response.object_id == test_folder.object_id
+
+
+def test_preflight(test_folder, mock_object_id, mock_box_session):
+    new_file_size, new_file_name = 100, 'foo.txt'
+    test_folder.preflight_check(size=new_file_size, name=new_file_name)
+    mock_box_session.options.assert_called_once_with(
+        url='{0}/files/content'.format(API.BASE_API_URL),
+        expect_json_response=False,
+        data=json.dumps(
+            {
+                'size': new_file_size,
+                'name': new_file_name,
+                'parent': {'id': mock_object_id},
+            }
+        ),
+    )