diff --git a/api/files.py b/api/files.py index 0585b5993410695dccf95cd4e997ad3088fd8bf2..436b3a5e385dd1e09b6915b3ec0eb03b82380620 100644 --- a/api/files.py +++ b/api/files.py @@ -48,6 +48,16 @@ def hash_file_formatted(path, hash_alg=None, buffer_size=65536): return util.format_hash(hash_alg, hasher.hexdigest()) +<<<<<<< HEAD +======= + +class FileStoreException(Exception): + pass + +class FileFormException(Exception): + pass + +>>>>>>> Empty uploads 400 instead of 500 class HashingFile(file): def __init__(self, file_path, hash_alg): super(HashingFile, self).__init__(file_path, "wb") diff --git a/api/placer.py b/api/placer.py index 49ee2b746a8e32d7d4e2ffaaed9819b94c9ba836..3839175607894c9d5a800905f1742f30738b57cb 100644 --- a/api/placer.py +++ b/api/placer.py @@ -19,12 +19,13 @@ from .jobs.jobs import Job from .types import Origin from .web import encoder + class Placer(object): """ Interface for a placer, which knows how to process files and place them where they belong - on disk and database. """ - def __init__(self, container_type, container, id_, metadata, timestamp, origin, context): + def __init__(self, container_type, container, id_, metadata, timestamp, origin, context, file_fields): self.container_type = container_type self.container = container self.id_ = id_ @@ -42,6 +43,7 @@ class Placer(object): # A list of files that have been saved via save_file() usually returned by finalize() self.saved = [] + self.file_fields = file_fields def check(self): @@ -74,7 +76,15 @@ class Placer(object): Helper function that throws unless metadata was provided. """ if self.metadata == None: - raise Exception('Metadata required') + raise files.FileFormException('Metadata required') + + def requireFiles(self): + """ + Helper function that throws unless metadata was provided. + """ + if not self.file_fields or len(self.file_fields) < 1: + raise files.FileFormException('No files selected to upload') + def save_file(self, field=None, file_attrs=None): """ @@ -113,6 +123,16 @@ class TargetedPlacer(Placer): An exception is thrown in upload.process_upload() if you try. This could be fixed by making a better schema. """ + # TODO: Change schemas to enabled targeted uploads of more than one file. + # Ref docs from placer.TargetedPlacer for details. + def requireFiles(self): + """ + Helper function that throws unless metadata was provided. + """ + if not self.file_fields or len(self.file_fields) != 1: + raise files.FileFormException("Targeted uploads can only send one file") + + def check(self): self.requireTarget() validators.validate_data(self.metadata, 'file.json', 'input', 'POST', optional=True) @@ -137,14 +157,14 @@ class UIDPlacer(Placer): create_hierarchy = staticmethod(hierarchy.upsert_top_down_hierarchy) match_type = 'uid' - def __init__(self, container_type, container, id_, metadata, timestamp, origin, context): - super(UIDPlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context) + def __init__(self, container_type, container, id_, metadata, timestamp, origin, context, file_fields): + super(UIDPlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context, file_fields) self.metadata_for_file = {} self.session_id = None - def check(self): self.requireMetadata() + self.requireFiles() payload_schema_uri = validators.schema_uri('input', self.metadata_schema) metadata_validator = validators.from_schema_path(payload_schema_uri) @@ -315,8 +335,8 @@ class TokenPlacer(Placer): Intended for use with a token that tracks where the files will be stored. """ - def __init__(self, container_type, container, id_, metadata, timestamp, origin, context): - super(TokenPlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context) + def __init__(self, container_type, container, id_, metadata, timestamp, origin, context, file_fields): + super(TokenPlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context, file_fields) self.paths = [] self.folder = None @@ -354,8 +374,8 @@ class PackfilePlacer(Placer): A placer that can accept N files, save them into a zip archive, and place the result on an acquisition. """ - def __init__(self, container_type, container, id_, metadata, timestamp, origin, context): - super(PackfilePlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context) + def __init__(self, container_type, container, id_, metadata, timestamp, origin, context, file_fields): + super(PackfilePlacer, self).__init__(container_type, container, id_, metadata, timestamp, origin, context, file_fields) # This endpoint is an SSE endpoint self.sse = True diff --git a/api/web/base.py b/api/web/base.py index c9deb3886b22fb6c96f3c76782fdbbf7c418fbfc..7d46a39792810b9419a9049358ba664cfe737ee2 100644 --- a/api/web/base.py +++ b/api/web/base.py @@ -358,6 +358,8 @@ class RequestHandler(webapp2.RequestHandler): code = 400 elif isinstance(exception, errors.FileFormException): code = 400 + elif isinstance(exception, files.FileFormException): + code = 400 elif isinstance(exception, ElasticsearchException): code = 503 message = "Search is currently down. Try again later." diff --git a/tests/integration_tests/python/test_uploads.py b/tests/integration_tests/python/test_uploads.py index 5b5e366d68d43b0770391c49c3ad2d447988f42c..202a2a9a660e09ca2b19a20db28af15618f40bbd 100644 --- a/tests/integration_tests/python/test_uploads.py +++ b/tests/integration_tests/python/test_uploads.py @@ -55,7 +55,7 @@ def test_reaper_upload(data_builder, randstr, upload_file_form, as_admin): )) assert r.ok - # reaper-upload files to group_1/project_label_1 using session_uid + # reaper-upload files to group_1/project_label_1 using session_uid without any files file_form = upload_file_form( group={'_id': group_1}, project={'label': project_label_1, "files":[]}, @@ -67,7 +67,7 @@ def test_reaper_upload(data_builder, randstr, upload_file_form, as_admin): print file_form r = as_admin.post('/upload/reaper', files={"metadata": file_form.get("metadata")}) print r.json() - assert r.status_code == 500 + assert r.status_code == 400 # get session created by the upload project_1 = as_admin.get('/groups/' + group_1 + '/projects').json()[0]['_id'] @@ -204,7 +204,7 @@ def test_reaper_upload_unknown_group_project(data_builder, file_form, as_root, a } }).get("metadata")} ) - assert r.status_code == 500 + assert r.status_code == 400 # get session created by the upload @@ -271,7 +271,7 @@ def test_uid_upload(data_builder, file_form, as_admin, as_user, as_public): # try to uid-upload w/o metadata r = as_admin.post('/upload/uid', files=file_form('test.csv')) - assert r.status_code == 500 + assert r.status_code == 400 # NOTE unused.csv is testing code that discards files not referenced from meta uid_files = ('project.csv', 'subject.csv', 'session.csv', 'acquisition.csv', 'unused.csv') @@ -301,7 +301,7 @@ def test_uid_upload(data_builder, file_form, as_admin, as_user, as_public): # try to uid-upload no files r = as_admin.post('/upload/uid', files={"metadata": file_form(*uid_files, meta=uid_meta).get("metadata")}) - assert r.status_code == 500 + assert r.status_code == 400 # uid-upload files r = as_admin.post('/upload/uid', files=file_form(*uid_files, meta=uid_meta))