Files
pillar-python-sdk/pillarsdk/files.py
Sybren A. Stüvel 2e4c5b2663 Split Node.create_asset_from_file and added direct uploading from file obj
Node.create_asset_from_file() is split such that the actual file upload is
handled by File.upload_to_project(). This now also supports directly
uploading from a file object, which could be an io.BytesIO() object to
upload from RAM.
2016-07-07 11:08:30 +02:00

155 lines
5.3 KiB
Python
Executable File

import os.path
import logging
from .resource import List
from .resource import Find
from .resource import Create
from .resource import Post
from .resource import Update
from .resource import Delete
from .resource import Replace
from . import utils
THUMBNAIL_SIZES = 'sbtmlh'
log = logging.getLogger(__name__)
class File(List, Find, Create, Post, Update, Delete, Replace):
"""Node class wrapping the REST nodes endpoint
"""
path = "files"
file_server_path = "file_storage/file"
build_previews_server_path = "file_storage/build_previews"
ensure_query_projections = {'backend': 1, 'file_path': 1, 'project': 1, 'content_type': 1,
'link': 1, 'link_expires': 1}
def post_file(self, file_path, name=None, api=None):
"""Stores a file on the database or static folder.
:param file: A file object
"""
api = api or self.api
url = utils.join_url(self.file_server_path)
file_ = open(file_path, 'rb')
files = {'data': file_}
api.post(url, {"name": name}, {}, files)
file_.close()
# self.error = None
# self.merge(new_attributes)
return self.success()
def build_previews(self, path, api=None):
"""Stores a file on the database or static folder.
:param path: A file path
"""
api = api or self.api
url = utils.join_url(self.build_previews_server_path, path)
api.get(url)
return self.success()
# def children(self, api=None):
# """Collect children (variations) of the current file. Used to connect
# different resolutions of the same picture, or multiple versions of the
# same video in different formats/containers.
# TODO: add params to support pagination.
# """
# api = api or self.api
# files = self.all({'where': '{"parent": "%s"}' % self._id}, api=api)
# if not files._items:
# return None
# return files
def thumbnail(self, size, api=None):
"""Utility to replace a component of an image link so that it points to
a thumbnail.
"""
if size not in THUMBNAIL_SIZES:
raise ValueError("Size should be in ({}), not {}"
.format(', '.join(THUMBNAIL_SIZES), size))
if self.variations:
thumbnail = next((item for item in self['variations']
if item['size'] == size), None)
if thumbnail:
try:
return thumbnail['link']
except KeyError:
return None
if self.link:
root, ext = os.path.splitext(self.link)
return "{0}-{1}.jpg".format(root, size)
thumbnail = self.find_first({'where': {'parent': self._id, 'size': size}}, api=api)
if thumbnail is not None:
return thumbnail.link
return ''
def stream_thumb_to_file(self, directory, desired_size, api=None):
"""Streams a thumbnail to a file.
@param directory: the directory to save the file to.
@param desired_size: thumbnail size
@return: the absolute path of the downloaded file.
"""
api = api or self.api
thumb_link = self.thumbnail(desired_size, api=api)
if thumb_link is None:
raise ValueError("File {} has no thumbnail of size {}"
.format(self._id, desired_size))
root, ext = os.path.splitext(self.file_path)
thumb_fname = "{0}-{1}.jpg".format(root, desired_size)
# thumb is now a dict like:
# {'content_type': 'image/jpeg', 'height': 160, 'length': 5846,
# 'link': 'https://storage.googleapis.com/asdlajsdhaukihuwefiuh',
# 'width': 160, 'size': 'b', 'file_path': '65b526639295c0dd9dc99cf54a0a606cd4924f1d-b.jpg',
# 'md5': '--', 'format': 'jpg'},
thumb_path = os.path.abspath(os.path.join(directory, thumb_fname))
utils.download_to_file(thumb_link, thumb_path)
return thumb_path
@classmethod
def upload_to_project(cls, project_id,
mimetype,
filename,
fileobj=None,
api=None):
"""Uploads a file to the project storage space.
:param project_id: the project ID
:param mimetype: MIME type of the file, such as "image/jpeg"
:param filename: path of the file to upload. Must be readable when fileobj
is not given.
:param fileobj: file object to read the file from. If None, it is read
by opening 'filename'. The file object will not be closed after uploading.
:returns: the upload response as a dict {'status': 'ok', 'file_id': 'some-id'}
:rtype: dict
"""
# Select which file object to upload.
if fileobj is None:
infile = open(filename, mode='rb')
else:
infile = fileobj
log.debug('Uploading directly from file object %r', fileobj)
assert infile is not None
# Perform the upload.
try:
return api.post('storage/stream/%s' % project_id,
files={'file': (os.path.basename(filename), infile, mimetype)})
finally:
if fileobj is None:
infile.close()