The Metadata API enables applications to:

  • CRUD Metadata on a file
  • Search by Metadata value

Terminology:

  • Metadata properties are grouped into Templates (e.g. Contracts, Invoices)
  • Templates are identified by template key and scope
  • A metadata instance is attached to a file, and conforms to a template’s schema. A file can have multiple instances, but only one from each template.

Setting up our client, we want to subclass the DefaultNetwork to add illustrative logging to our output:

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

class LoggingNetwork(DefaultNetwork):
    def request(self, method, url, access_token, **kwargs):
        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

Fill in your client_id, client_secret, and developer token that you obtained from the developers site:

In [5]:
from boxsdk import OAuth2
from boxsdk import Client
oauth = OAuth2(client_id='YOUR_CLIENT_ID', client_secret='YOUR_CLIENT_SECRET', access_token='YOUR_DEVELOPER_TOKEN')
client = Client(oauth, LoggingNetwork())

I'll demonstrate the CRUD abilities of the Metadata API. Using the Python SDK makes the API dead simple to use.

First, let's assume our account already has a file with ID 28792944858 and a template named "mortgageContarct". We'll create a metadata instance of that type:

In [12]:
metadata = client.file(28792944858).metadata('enterprise', 'mortgageApplication')
metadata.create({'borrowerName':'Alice', 'amount':8888888, 'status':'Pending Approval'})
POST https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'data': '{"status": "Pending Approval", "amount": 8888888, "borrowerName": "Alice"}',
 'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW',
             'Content-Type': 'application/json'}}
{
    "status": "Pending Approval",
    "amount": 8888888,
    "borrowerName": "Alice",
    "$type": "mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54",
    "$parent": "file_28792944858",
    "$id": "657378d4-0617-4a04-9067-7e625076a0c2"
}
Out[12]:
{u'$id': u'657378d4-0617-4a04-9067-7e625076a0c2',
 u'$parent': u'file_28792944858',
 u'$type': u'mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54',
 u'amount': 8888888,
 u'borrowerName': u'Alice',
 u'status': u'Pending Approval'}

The metadata instance has been created and returned to you.

Next, we'll demonstrate the Metadata Search API. The Metadata Search API allows clients to search for files that match certain metadata values.

For example, our user's enterprise has a Mortgage Application metadata template, and we've attached an instance of the Mortgage Application template to a file in the user's account. We can search for this file by a specific field, status. In this example, the template is identified by template_key and scope, and the value itself is identified by the field_key.

In [14]:
from boxsdk.object.search import Search
metadata_filters = Search.start_metadata_filters()
metadata_filter = Search.make_single_metadata_filter(template_key='mortgageApplication', scope='enterprise')
metadata_filter.add_value_based_filter(field_key='status', value='Pending Approval')
metadata_filters.add_filter(metadata_filter)
client.search('pdf', limit=1, offset=0, metadata_filters=metadata_filters)
GET https://api.box.com/2.0/search {'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW'},
 'params': {u'limit': 1,
            u'mdfilters': '[{"templateKey": "mortgageApplication", "scope": "enterprise", "filters": {"status": "Pending Approval"}}]',
            u'offset': 0,
            u'query': 'pdf'}}
{
    "total_count": 1,
    "entries": [ ... ],
    "limit": 1,
    "offset": 0
}
Out[14]:
[<boxsdk.object.file.File at 0x10e2e88d0>]

Next, we'll update the metadata instance, testing that the value is still what we expect it to be:

In [15]:
from boxsdk.object.metadata import Metadata
update = Metadata.start_update()
update.update('/status', 'Approved', 'Pending Approval')
metadata.update(update)
PUT https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'data': '[{"path": "/status", "value": "Pending Approval", "op": "test"}, {"path": "/status", "value": "Approved", "op": "replace"}]',
 'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW',
             'Content-Type': 'application/json-patch+json'}}
{
    "status": "Approved",
    "amount": 8888888,
    "borrowerName": "Alice",
    "$type": "mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54",
    "$parent": "file_28792944858",
    "$id": "657378d4-0617-4a04-9067-7e625076a0c2"
}
Out[15]:
{u'$id': u'657378d4-0617-4a04-9067-7e625076a0c2',
 u'$parent': u'file_28792944858',
 u'$type': u'mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54',
 u'amount': 8888888,
 u'borrowerName': u'Alice',
 u'status': u'Approved'}

Notice the PUT body is an array of 2 operations: test and replace. The PUT body conforms to the JSON Patch specification for updating JSON objects.

If we attempt to issue the same patch again, the operation will fail because the value is not what we expected it to be:

In [16]:
update = Metadata.start_update()
update.update('/status', 'Approved', 'Pending Approval')
metadata.update(update)
PUT https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'data': '[{"path": "/status", "value": "Pending Approval", "op": "test"}, {"path": "/status", "value": "Approved", "op": "replace"}]',
 'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW',
             'Content-Type': 'application/json-patch+json'}}
409
{'content-length': '123', 'age': '1', 'server': 'ATS', 'connection': 'keep-alive', 'date': 'Wed, 22 Apr 2015 16:28:46 GMT', 'content-type': 'application/json'}
'{"message":"value differs from expectations","code":"failed_json_patch_application","request_id":"11962628355537cc3dd9baa"}'
---------------------------------------------------------------------------
BoxAPIException                           Traceback (most recent call last)
<ipython-input-16-0e3a050a8302> in <module>()
1
update = Metadata.start_update()
2
update.update('/status', 'Approved', 'Pending Approval') ----> 3 metadata.update(update) /Users/hnguyen/Code/box-python-sdk-hieu/boxsdk/object/metadata.pyc in update(self, metadata_update)
155
self.get_url(),
156
data=json.dumps(metadata_update.ops), --> 157 headers={b'Content-Type': b'application/json-patch+json'},
158
).json()
159
/Users/hnguyen/Code/box-python-sdk-hieu/boxsdk/session/box_session.pyc in put(self, url, **kwargs)
347
`unicode`
348
""" --> 349 response = self._prepare_and_send_request('PUT', url, **kwargs)
350
return BoxResponse(response)
351
/Users/hnguyen/Code/box-python-sdk-hieu/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/hnguyen/Code/box-python-sdk-hieu/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/hnguyen/Code/box-python-sdk-hieu/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: value differs from expectations Status: 409 Code: failed_json_patch_application Request id: 11962628355537cc3dd9baa Headers: {'content-length': '123', 'age': '1', 'server': 'ATS', 'connection': 'keep-alive', 'date': 'Wed, 22 Apr 2015 16:28:46 GMT', 'content-type': 'application/json'} URL: https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication Method: PUT

Now, we'll retrieve the metadata again to demonstrate the GET operation:

In [17]:
metadata.get()
GET https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW'}}
{
    "status": "Approved",
    "amount": 8888888,
    "borrowerName": "Alice",
    "$type": "mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54",
    "$parent": "file_28792944858",
    "$id": "657378d4-0617-4a04-9067-7e625076a0c2"
}
Out[17]:
{u'$id': u'657378d4-0617-4a04-9067-7e625076a0c2',
 u'$parent': u'file_28792944858',
 u'$type': u'mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54',
 u'amount': 8888888,
 u'borrowerName': u'Alice',
 u'status': u'Approved'}

Finally, we'll delete the metadata on the file:

In [18]:
metadata.delete()
DELETE https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW'}}

Out[18]:
True

If we attempt to retrieve the metadata again, then the metadata will not be found:

In [19]:
metadata.get()
GET https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication {'headers': {u'Authorization': u'Bearer bljFP0m8vTdsKK3gWY0pnI2ETkluAAhW'}}
404
{'content-length': '180', 'content-encoding': 'gzip', 'age': '0', 'vary': 'Accept-Encoding', 'server': 'ATS', 'connection': 'keep-alive', 'date': 'Wed, 22 Apr 2015 16:28:56 GMT', 'content-type': 'application/json'}
'{"message":"Instance not found for \'\\"parented\\", \\"file_28792944858\\", \\"mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54\\"\'","code":"tuple_not_found","request_id":"19099247955537cc48197fe"}'
---------------------------------------------------------------------------
BoxAPIException                           Traceback (most recent call last)
<ipython-input-19-5912b71dad7f> in <module>()
----> 1 metadata.get()

/Users/hnguyen/Code/box-python-sdk-hieu/boxsdk/object/metadata.pyc in get(self)
167
:class:`Metadata`
168
""" --> 169 return self._session.get(self.get_url()).json()
170
171
def delete(self): /Users/hnguyen/Code/box-python-sdk-hieu/boxsdk/session/box_session.pyc in get(self, url, **kwargs)
325
`unicode`
326
""" --> 327 response = self._prepare_and_send_request('GET', url, **kwargs)
328
return BoxResponse(response)
329
/Users/hnguyen/Code/box-python-sdk-hieu/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/hnguyen/Code/box-python-sdk-hieu/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/hnguyen/Code/box-python-sdk-hieu/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: Instance not found for '"parented", "file_28792944858", "mortgageApplication-a2387be8-1279-4249-853b-f4a7cb6e0a54"' Status: 404 Code: tuple_not_found Request id: 19099247955537cc48197fe Headers: {'content-length': '180', 'content-encoding': 'gzip', 'age': '0', 'vary': 'Accept-Encoding', 'server': 'ATS', 'connection': 'keep-alive', 'date': 'Wed, 22 Apr 2015 16:28:56 GMT', 'content-type': 'application/json'} URL: https://api.box.com/2.0/files/28792944858/metadata/enterprise/mortgageApplication Method: GET

That's it! Now you'll be able to easily CRUD metadata and search for files with metadata using the Python SDK.

This notebook is available for download: http://opensource.box.com/box-python-sdk/tutorials/metadata.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/metadata.ipynb
pip install boxsdk ipython[notebook]
ipython notebook metadata.ipynb