Source code for pacifica.archiveinterface.rest_generator

#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Class for the archive interface.

Allows API to file interactions for passed in archive backends.
"""
from __future__ import print_function
import shutil
from json import dumps
import cherrypy
from .archive_utils import get_http_modified_time, file_status, bytes_type
from .exception import ArchiveInterfaceError

BLOCK_SIZE = 1 << 20


[docs]def error_page_default(**kwargs): """The default error page should always enforce json.""" cherrypy.response.headers["Content-Type"] = "application/json" return dumps( { "status": kwargs["status"], "message": kwargs["message"], "traceback": kwargs["traceback"], "version": kwargs["version"], } )
[docs]class ArchiveInterfaceGenerator: """Archive Interface Generator. Defines the methods that can be used on files for request types. """ exposed = True
[docs] def __init__(self, archive): """Create an archive interface generator.""" self._archive = archive self._response = None print("Pacifica Archive Interface Up and Running")
[docs] @staticmethod def _byte_range_get(archivefile, byte_range): """Perform a byte range get and return generator.""" try: start, finish = byte_range.split("-") start = int(start) finish = int(finish) except ValueError: archivefile.close() raise ArchiveInterfaceError( "Invalid byte range format (int-int) given {}".format(byte_range) ) archivefile.seek(start) def read(): """Read the data from the file.""" total = finish - start while total > 0: if total < BLOCK_SIZE: buf = archivefile.read(total) total = 0 else: buf = archivefile.read(BLOCK_SIZE) total -= BLOCK_SIZE yield buf archivefile.close() return read()
# pylint: disable=invalid-name
[docs] def GET(self, *args, **kwargs): """Get a file from WSGI request. Gets a file specified in the request and writes back the data. """ # if asking for / then return a message that the archive is working if not args: cherrypy.response.headers["Content-Type"] = "application/json" return bytes_type( dumps({"message": "Pacifica Archive Interface Up and Running"}) ) archivefile = self._archive.open(args[0], "r") cherrypy.response.headers["Content-Type"] = "application/octet-stream" if kwargs.get("byte_range", None): return self._byte_range_get(archivefile, kwargs.get("byte_range")) def read(): """Read the data from the file.""" buf = archivefile.read(BLOCK_SIZE) while buf: yield buf buf = archivefile.read(BLOCK_SIZE) archivefile.close() return read()
# as documented in cherrypy wiki # pylint: disable=protected-access GET._cp_config = {"response.stream": True} # pylint: enable=protected-access # pylint: enable=invalid-name
[docs] @cherrypy.tools.json_out() # pylint: disable=invalid-name def PUT(self, filepath): """Write a file from WSGI requests. Writes a file passed in the request to the archive. """ mod_time = get_http_modified_time(cherrypy.request.headers) archivefile = self._archive.open(filepath, "w") try: content_length = int(cherrypy.request.headers.get("Content-Length")) except Exception as ex: # pragma: no cover it's hard to get a client to not do this raise ArchiveInterfaceError( "Can't get file content length with error: {}".format(str(ex)) ) shutil.copyfileobj(cherrypy.request.body, archivefile) archivefile.close() archivefile.set_mod_time(mod_time) archivefile.set_file_permissions() cherrypy.response.status = "201 Created" return {"message": "File added to archive", "total_bytes": content_length}
# pylint: enable=invalid-name # pylint: disable=invalid-name
[docs] def HEAD(self, filepath): """Get the file status from WSGI request. Gets the status of a file specified in the request. """ archivefile = self._archive.open(filepath, "r") status = archivefile.status() file_status(status, cherrypy.response) archivefile.close()
# pylint: enable=invalid-name
[docs] @cherrypy.tools.json_out() # pylint: disable=invalid-name def POST(self, filepath): """Stage a file from WSGI request. Stage the file specified in the request to disk. """ archivefile = self._archive.open(filepath, "r") archivefile.stage() archivefile.close() cherrypy.response.status = "200 OK" return {"message": "File was staged", "file": filepath}
# pylint: enable=invalid-name
[docs] @cherrypy.tools.json_in() @cherrypy.tools.json_out() # pylint: disable=invalid-name def PATCH(self, filepath): """Move a file from the original path to the new one specified.""" file_path = cherrypy.request.json["path"] file_id = filepath self._archive.patch(file_id, file_path) cherrypy.response.status = "200 OK" return {"message": "File Moved Successfully"}
# pylint: enable=invalid-name # pylint: disable=invalid-name
[docs] def DELETE(self, filepath): """Delete a file from WSGI request. Delete the file specified in the request to disk. """ archivefile = self._archive.open(filepath, "r") archivefile.close() archivefile.remove() cherrypy.response.status = "200 OK"
# pylint: enable=invalid-name