Skip to content

Add helper method to support file uploads #49

@paul121

Description

@paul121

Some context on this:

An overview:

JSON:API now:

Supports POSTing a file's contents
Supports sending file data as a binary data stream via the Content-Type: application/octet-stream request header.
This allows uploads of an arbitrary size, including uploads larger than the PHP memory limit.
Ensures that settings/validation constraints of the field to which the file is being uploaded will be respected.
Supports naming the file using the Content-Disposition: file; filename="filename.jpg" header

To use JSON:API's file uploads, you must choose to implement one or both of two different "flows":

Flow 1 requires only a single HTTP request, but will only work when the host entity already exists.
Flow 2 requires two HTTP requests but will work under all circumstances.

TL;DR

I tested uploading files using the requests session from the farmOS.py client and it works great:

client.session.post('http://localhost/api/log/input/file', data=open('data.csv', 'rb').read(), headers={'Content-Type': 'application/octet-stream', 'Content-Disposition': 'file; filename="data.csv"'})

We can make this even easier in the farmOS.py client by adding a helper method. This can't be done with the existing methods because file uploads must always be a POST with special headers to unique {entity_type}/{bundle}/{file field} endpoints. This ensures proper files are uploaded to the correct location (the location can vary depending on each bundle field) and all proper validation is performed.

Solution:

# Method to be namespaced as client.file.create()
def create(entity_type, bundle, field, data, filename, id = None):
    if id is not None:
        path = "{entity_type}/{bundle}/{id}/{field}".format(entity_type, bundle, id, field)
    else:
        path = "{entity_type}/{bundle}/{field}".format(entity_type, bundle, field)
    headers = {'Content-Type': 'application/octet-stream', 'Content-Disposition': "file; filename=\"{filename}\"".format(filename)} 
    return self.session.post(path, data, headers)
    
# Usage

# Flow 1: update existing log
data = open('data.csv', 'rb').read()
response = client.file.create('log', 'observation', 'file', data, 'observation_data.csv', '{log_id}')

# Flow 2: upload file then include with a new log.
data = open('data.csv', 'rb').read()
response = client.file.create('log', 'observation', 'file', data, 'observation_data.csv')
file_id = response['data']['id']
log_data = {
  'attributes': {'name': 'observation log'},
  'relationships': { 'file': {'data': [{'type': 'file--file', 'id': file_id}]}},
}
new_log = client.log.send('observation', log_data)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions