def test_non_idempotent_uses_POST(self): # If a function is declared as not idempotent the export signature # includes the HTTP POST method. def func(): pass self.assertEqual(("POST", func.__name__), operation(idempotent=False)(func).export)
def test_idempotent_uses_GET(self): # If a function is declared as idempotent the export signature # includes the HTTP GET method. def func(): pass self.assertEqual(("GET", func.__name__), operation(idempotent=True)(func).export)
def testexported_as_is_optional(self): # If exported_as is not passed then we expect the function to be # exported in the API using the actual function name itself. def exported_function(): pass decorate = operation(idempotent=True) decorated = decorate(exported_function) self.assertEqual("exported_function", decorated.export[1])
class AnonFilesHandler(AnonymousOperationsHandler): """Anonymous file operations. This is needed for Juju. The story goes something like this: - The Juju provider will upload a file using an "unguessable" name. - The name of this file (or its URL) will be shared with all the agents in the environment. They cannot modify the file, but they can access it without credentials. """ create = read = update = delete = None get_by_key = operation( idempotent=True, exported_as='get_by_key')(get_file_by_key) @classmethod def resource_uri(cls, *args, **kwargs): return ('files_handler', [])
class FilesHandler(OperationsHandler): """Manage the collection of all the files in this MAAS.""" api_doc_section_name = "Files" update = None anonymous = AnonFilesHandler get_by_name = operation( idempotent=True, exported_as='get')(get_file_by_name) get_by_key = operation( idempotent=True, exported_as='get_by_key')(get_file_by_key) def create(self, request): """Add a new file to the file storage. :param filename: The file name to use in the storage. :type filename: string :param file: Actual file data with content type application/octet-stream Returns 400 if any of these conditions apply: - The filename is missing from the parameters - The file data is missing - More than one file is supplied """ filename = request.data.get("filename", None) if not filename: raise MAASAPIBadRequest("Filename not supplied") files = request.FILES if not files: raise MAASAPIBadRequest("File not supplied") if len(files) != 1: raise MAASAPIBadRequest("Exactly one file must be supplied") uploaded_file = files['file'] # As per the comment in FileStorage, this ought to deal in # chunks instead of reading the file into memory, but large # files are not expected. FileStorage.objects.save_file(filename, uploaded_file, request.user) return HttpResponse('', status=int(http.client.CREATED)) def read(self, request): """List the files from the file storage. The returned files are ordered by file name and the content is excluded. :param prefix: Optional prefix used to filter out the returned files. :type prefix: string """ prefix = request.GET.get("prefix", None) files = FileStorage.objects.filter(owner=request.user) if prefix is not None: files = files.filter(filename__startswith=prefix) files = files.order_by('filename') return files def delete(self, request, **kwargs): """Delete a FileStorage object. :param filename: The filename of the object to be deleted. :type filename: unicode """ # It is valid for a path in a POST, PUT, or DELETE (or any other HTTP # method) to contain a query string. However, Django only makes # parameters from the query string available in the badly named # request.GET object. filename = get_mandatory_param(request.GET, 'filename') stored_file = get_object_or_404( FileStorage, filename=filename, owner=request.user) stored_file.delete() return rc.DELETED @classmethod def resource_uri(cls, *args, **kwargs): return ('files_handler', [])
def test_can_passexported_as(self): # Test that passing the optional "exported_as" works as expected. randomexported_name = factory.make_name("exportedas", sep="") decorate = operation(idempotent=False, exported_as=randomexported_name) decorated = decorate(lambda: None) self.assertEqual(randomexported_name, decorated.export[1])
def test_valid_decoration(self): value = "value" + factory.make_string() decorate = operation(idempotent=False) decorated = decorate(lambda: value) self.assertEqual(value, decorated())
class FilesHandler(OperationsHandler): """Manage the collection of all the files in this MAAS.""" api_doc_section_name = "Files" update = None anonymous = AnonFilesHandler get_by_name = operation(idempotent=True, exported_as="get")(get_file_by_name) get_by_key = operation(idempotent=True, exported_as="get_by_key")(get_file_by_key) def create(self, request): """@description-title Add a new file @description Add a new file to the file storage. @param (string) "filename" [required=true] The file name to use in storage. @param (string) "file" [required=true] File data. Content type must be ``application/octet-stream``. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the new file. @success-example "success-json" [exkey=files-placeholder] placeholder text @error (http-status-code) "400" 400 @error (content) "arg-prob" The filename is missing, the file data is missing or more than one file is supplied. """ filename = request.data.get("filename", None) if not filename: raise MAASAPIBadRequest("Filename not supplied") files = request.FILES if not files: raise MAASAPIBadRequest("File not supplied") if len(files) != 1: raise MAASAPIBadRequest("Exactly one file must be supplied") uploaded_file = files["file"] # As per the comment in FileStorage, this ought to deal in # chunks instead of reading the file into memory, but large # files are not expected. FileStorage.objects.save_file(filename, uploaded_file, request.user) return HttpResponse("", status=int(http.client.CREATED)) def read(self, request): """@description-title List files @description List the files from the file storage. The returned files are ordered by file name and the content is excluded. @param (string) "prefix" [required=false] Prefix used to filter returned files. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing a list of the reqeusted file names. @success-example "success-json" [exkey=files-placeholder] placeholder text """ prefix = request.GET.get("prefix", None) files = FileStorage.objects.filter(owner=request.user) if prefix is not None: files = files.filter(filename__startswith=prefix) files = files.order_by("filename") return files def delete(self, request, **kwargs): """@description-title Delete a file @description Delete a stored file. @param (string) "filename" [required=true] The filename of the object to be deleted. @success (http-status-code) "server-success" 204 @error (http-status-code) "404" 404 @error (content) "not-found" The requested file is not found. @error-example "not-found" Not Found """ # It is valid for a path in a POST, PUT, or DELETE (or any other HTTP # method) to contain a query string. However, Django only makes # parameters from the query string available in the badly named # request.GET object. filename = get_mandatory_param(request.GET, "filename") stored_file = get_object_or_404(FileStorage, filename=filename, owner=request.user) stored_file.delete() return rc.DELETED @classmethod def resource_uri(cls, *args, **kwargs): return ("files_handler", [])