The Box API provides a great way to securely connect your application to the Box platform.

The Box Python SDK makes consuming the API an easy task. Let's see how easy it is to get started.

To begin, we need 3 things:

  • A Box client ID for a Box application
  • The corresponding Box client secret
  • A valid developer token for that application

You can get all 3 at http://developers.box.com - if you don't already have a Box application, you can sign up for one there.

For this demo, I wrote these 3 things to a text file called app.cfg, one per line.

Finally, we need to install the SDK:

pip install boxsdk

Now we can start writing some code.

In [1]:
# Import two classes from the boxsdk module - Client and OAuth2
from boxsdk import Client, OAuth2

# Define client ID, client secret, and developer token.
CLIENT_ID = None
CLIENT_SECRET = None
ACCESS_TOKEN = None

# Read app info from text file
with open('app.cfg', 'r') as app_cfg:
    CLIENT_ID = app_cfg.readline()
    CLIENT_SECRET = app_cfg.readline()
    ACCESS_TOKEN = app_cfg.readline()

The Python SDK is organized into layers:

  • The network layer, responsible for making network calls.
  • The session layer, responsible for auth and retrying calls.
  • The client layer, which is the interface to your application.

For demonstration purposes, I'm subclassing the default network layer to log network requests and responses.

In [2]:
from boxsdk.network.default_network import DefaultNetwork
from pprint import pformat

class LoggingNetwork(DefaultNetwork):
    def request(self, method, url, access_token, **kwargs):
        """ Base class override. Pretty-prints outgoing requests and incoming responses. """
        print '\x1b[36m{} {} {}\x1b[0m'.format(method, url, pformat(kwargs))
        response = super(LoggingNetwork, self).request(
            method, url, access_token, **kwargs
        )
        if response.ok:
            print '\x1b[32m{}\x1b[0m'.format(response.content)
        else:
            print '\x1b[31m{}\n{}\n{}\x1b[0m'.format(
                response.status_code,
                response.headers,
                pformat(response.content),
            )
        return response
In [3]:
# Create OAuth2 object. It's already authenticated, thanks to the developer token.
oauth2 = OAuth2(CLIENT_ID, CLIENT_SECRET, access_token=ACCESS_TOKEN)

# Create the authenticated client
client = Client(oauth2, LoggingNetwork())

We now have a fully authenticated SDK client!

Let's try it out.

The SDK's Client object contains a collection of methods that allow you to instantiate Box objects and endpoints.

  • client.folder(folder_id)
  • client.file(file_id)
  • client.events()
  • client.search()
  • client.user(user_id)

Once you have a Box object, you can get() information about it, or do various operations (delete(), or upload/download).

Let's look at the User object.

In [4]:
# Get information about the logged in user (that's whoever owns the developer token)
my = client.user(user_id='me').get()
print my.name
print my.login
print my.avatar_url
GET https://api.box.com/2.0/users/me {'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'},
 'params': None}
{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com","created_at":"2015-03-22T16:47:53-07:00","modified_at":"2015-04-06T14:08:25-07:00","language":"en","timezone":"America\/Los_Angeles","space_amount":10737418240,"space_used":0,"max_upload_size":262144000,"status":"active","job_title":"","phone":"4126068527","address":"","avatar_url":"https:\/\/app.box.com\/api\/avatar\/large\/234630582"}
Jeff Boxdev
jrmeadows2+boxdev2015@gmail.com
https://app.box.com/api/avatar/large/234630582

Now let's look at some different objects, like File and Folder.

client.folder('0') is a reference to the root folder ("All Files").

In [5]:
root_folder = client.folder('0')
root_folder_with_info = root_folder.get()
GET https://api.box.com/2.0/folders/0 {'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'},
 'params': None}
{"type":"folder","id":"0","sequence_id":null,"etag":null,"name":"All Files","created_at":null,"modified_at":null,"description":"","size":0,"path_collection":{"total_count":0,"entries":[]},"created_by":{"type":"user","id":"","name":"","login":""},"modified_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"trashed_at":null,"purged_at":null,"content_created_at":null,"content_modified_at":null,"owned_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"shared_link":null,"folder_upload_email":null,"parent":null,"item_status":"active","item_collection":{"total_count":0,"entries":[],"offset":0,"limit":100,"order":[{"by":"type","direction":"ASC"},{"by":"name","direction":"ASC"}]}}
In [6]:
# Save time and bandwidth by only asking for the folder owner
root_folder_with_limited_info = root_folder.get(fields=['owned_by'])
GET https://api.box.com/2.0/folders/0 {'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'},
 'params': {u'fields': u'owned_by'}}
{"type":"folder","id":"0","etag":null,"owned_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"}}

The SDK makes it very easy to download and upload files.

Folder.upload(file_path ,filename) uploads a file to that folder, and Folder.upload_stream(stream, filename) uploads the contents of a stream as a file to the folder. Both methods return a File object.

In [7]:
# Upload a file to Box!
from StringIO import StringIO

stream = StringIO()
stream.write('Box Python SDK test!')
stream.seek(0)
box_file = client.folder('0').upload_stream(stream, 'box-python-sdk-test.txt')
print box_file.name
POST https://upload.box.com/api/2.0/files/content {'data': {u'attributes': '{"name": "box-python-sdk-test.txt", "parent": {"id": "0"}}'},
 'files': {u'file': (u'unused', <StringIO.StringIO instance at 0x106b30b00>)},
 'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'}}
{"total_count":1,"entries":[{"type":"file","id":"28430517200","file_version":{"type":"file_version","id":"27041592694","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98"},"sequence_id":"0","etag":"0","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98","name":"box-python-sdk-test.txt","description":"","size":20,"path_collection":{"total_count":1,"entries":[{"type":"folder","id":"0","sequence_id":null,"etag":null,"name":"All Files"}]},"created_at":"2015-04-06T14:09:26-07:00","modified_at":"2015-04-06T14:09:26-07:00","trashed_at":null,"purged_at":null,"content_created_at":"2015-04-06T14:09:26-07:00","content_modified_at":"2015-04-06T14:09:26-07:00","created_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"modified_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"owned_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"shared_link":null,"parent":{"type":"folder","id":"0","sequence_id":null,"etag":null,"name":"All Files"},"item_status":"active"}]}
box-python-sdk-test.txt
In [8]:
# Download the file's contents from Box
print box_file.content()
print box_file.id
GET https://api.box.com/2.0/files/28430517200/content {'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'}}
Box Python SDK test!
Box Python SDK test!
28430517200

The SDK provides information when an API call couldn't be completed.

Let's try to upload the same file again - this time, the upload will fail! That's because the name is already in use.

In [9]:
stream.seek(0)
box_file = client.folder('0').upload_stream(stream, 'box-python-sdk-test.txt')
POST https://upload.box.com/api/2.0/files/content {'data': {u'attributes': '{"name": "box-python-sdk-test.txt", "parent": {"id": "0"}}'},
 'files': {u'file': (u'unused', <StringIO.StringIO instance at 0x106b30b00>)},
 'headers': {u'Authorization': u'Bearer vMPUC6ZGTD3AlW1HFD5ZAL5RZMdMkrEi\n'}}
409
{'content-length': '484', 'age': '0', 'server': 'ATS', 'connection': 'keep-alive', 'cache-control': 'no-cache, no-store', 'date': 'Mon, 06 Apr 2015 21:09:32 GMT', 'content-type': 'application/json'}
'{"type":"error","status":409,"code":"item_name_in_use","context_info":{"conflicts":{"type":"file","id":"28430517200","file_version":{"type":"file_version","id":"27041592694","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98"},"sequence_id":"0","etag":"0","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98","name":"box-python-sdk-test.txt"}},"help_url":"http:\\/\\/developers.box.com\\/docs\\/#errors","message":"Item with the same name already exists","request_id":"8098479195522f60c3ff0d"}'
---------------------------------------------------------------------------
BoxAPIException                           Traceback (most recent call last)
<ipython-input-9-10f6336d1b3d> in <module>()
      1 stream.seek(0)
----> 2 box_file = client.folder('0').upload_stream(stream, 'box-python-sdk-test.txt')

/Users/jmeadows/.virtualenvs/boxdev/lib/python2.7/site-packages/boxsdk/object/folder.pyc in upload_stream(self, file_stream, file_name, preflight_check, preflight_expected_size)
    182             'file': ('unused', file_stream),
    183         }
--> 184         box_response = self._session.post(url, data=data, files=files, expect_json_response=False)
    185         file_response = box_response.json()['entries'][0]
    186         file_id = file_response['id']

/Users/jmeadows/.virtualenvs/boxdev/lib/python2.7/site-packages/boxsdk/session/box_session.pyc in post(self, url, **kwargs)
    336             `unicode`
    337         """
--> 338         response = self._prepare_and_send_request('POST', url, **kwargs)
    339         return BoxResponse(response)
    340 

/Users/jmeadows/.virtualenvs/boxdev/lib/python2.7/site-packages/boxsdk/session/box_session.pyc in _prepare_and_send_request(self, method, url, headers, auto_session_renewal, expect_json_response, attempt_number, **kwargs)
    236             attempt_number,
    237             file_stream_positions=file_stream_positions,
--> 238             **kwargs
    239         )
    240 

/Users/jmeadows/.virtualenvs/boxdev/lib/python2.7/site-packages/boxsdk/session/box_session.pyc in _make_request(self, method, url, headers, auto_session_renewal, expect_json_response, attempt_number, **kwargs)
    313         )
    314 
--> 315         self._raise_on_unsuccessful_request(network_response, expect_json_response, method, url)
    316 
    317         return network_response

/Users/jmeadows/.virtualenvs/boxdev/lib/python2.7/site-packages/boxsdk/session/box_session.pyc in _raise_on_unsuccessful_request(self, network_response, expect_json_response, method, url)
    175                 request_id=response_json.get('request_id', None),
    176                 url=url,
--> 177                 method=method,
    178             )
    179         if expect_json_response and not self._is_json_response(network_response):

BoxAPIException: 
Message: Item with the same name already exists
Status: 409
Code: item_name_in_use
Request id: 8098479195522f60c3ff0d
Headers: {'content-length': '484', 'age': '0', 'server': 'ATS', 'connection': 'keep-alive', 'cache-control': 'no-cache, no-store', 'date': 'Mon, 06 Apr 2015 21:09:32 GMT', 'content-type': 'application/json'}
URL: https://upload.box.com/api/2.0/files/content
Method: POST

The Pre-flight check API will verify that a file will be accepted by Box before you send all the bytes over the wire. It can be used for both first-time uploads, and uploading new versions of an existing File (on /files/[id]/content). If the call returns a 200, then you can proceed with a standard upload call. Preflight checks verify all permissions as if the file was actually uploaded including:

  • Folder upload permission
  • File name collisions
  • file size caps
  • folder and file name restrictions*
  • folder and account storage quota
In [12]:
stream.seek(0)
from boxsdk.exception import BoxAPIException
try:
    box_file = client.folder('0').upload_stream(stream, 'box-python-sdk-test.txt', preflight_check=True)
except BoxAPIException:
    pass
OPTIONS https://api.box.com/2.0/files/content {'data': '{"name": "box-python-sdk-test.txt", "parent": {"id": "0"}, "size": 0}',
 'headers': {u'Authorization': u'Bearer wZrNb8pHQmDpybvrZzYSl243KtOVpUMX'}}
409
{'content-length': '485', 'age': '0', 'server': 'ATS', 'connection': 'keep-alive', 'cache-control': 'no-cache, no-store', 'date': 'Mon, 06 Apr 2015 21:10:33 GMT', 'content-type': 'application/json'}
'{"type":"error","status":409,"code":"item_name_in_use","context_info":{"conflicts":{"type":"file","id":"28430517200","file_version":{"type":"file_version","id":"27041592694","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98"},"sequence_id":"0","etag":"0","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98","name":"box-python-sdk-test.txt"}},"help_url":"http:\\/\\/developers.box.com\\/docs\\/#errors","message":"Item with the same name already exists","request_id":"13995869325522f6491dd3a"}'
In [13]:
# See if we can find the file on Box using search (may need to wait for Box to index the file)
results = client.search('Box Python SDK test', 2, 0)
matching_results = (r for r in results if r.id == box_file.id)
for m in matching_results:
    print m.name
    print m.created_at
    break
else:
    print 'No match found'
GET https://api.box.com/2.0/search {'headers': {u'Authorization': u'Bearer wZrNb8pHQmDpybvrZzYSl243KtOVpUMX'},
 'params': {u'limit': 2, u'offset': 0, u'query': 'Box Python SDK test'}}
{"total_count":1,"entries":[{"type":"file","id":"28430517200","file_version":{"type":"file_version","id":"27041592694","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98"},"sequence_id":"0","etag":"0","sha1":"30e6d6373e1d275073b5e4b0244368cc40566f98","name":"box-python-sdk-test.txt","description":"","size":20,"path_collection":{"total_count":1,"entries":[{"type":"folder","id":"0","sequence_id":null,"etag":null,"name":"All Files"}]},"created_at":"2015-04-06T14:09:26-07:00","modified_at":"2015-04-06T14:09:26-07:00","trashed_at":null,"purged_at":null,"content_created_at":"2015-04-06T14:09:26-07:00","content_modified_at":"2015-04-06T14:09:26-07:00","created_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"modified_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"owned_by":{"type":"user","id":"234630582","name":"Jeff Boxdev","login":"jrmeadows2+boxdev2015@gmail.com"},"shared_link":null,"parent":{"type":"folder","id":"0","sequence_id":null,"etag":null,"name":"All Files"},"item_status":"active"}],"limit":2,"offset":0}
box-python-sdk-test.txt
2015-04-06T14:09:26-07:00

This notebook is available for download: http://opensource.box.com/box-python-sdk/tutorials/intro.ipynb

Its content is licensed under the Apache License 2.0.

To reproduce the presentation, execute the following commands:

curl -O http://opensource.box.com/box-python-sdk/tutorials/intro.ipynb
pip install boxsdk ipython[notebook]
ipython notebook intro.ipynb