diff options
| author | Jan Friedli | 2020-08-21 09:09:19 +0200 |
|---|---|---|
| committer | Jan Friedli | 2020-08-21 09:09:19 +0200 |
| commit | 668451751411b0d43ecc0791c6524e7e9a0cd21e (patch) | |
| tree | f1010f427b258121998bf46477bf6bbbba602df6 | |
| parent | 5b9bd99eb7e90b29022a2c0466ee6b6dbaf0b9ac (diff) | |
added endpoint to clean file and return it directly for automated clients
| -rw-r--r-- | matweb/oas/remove_metadata.yml | 41 | ||||
| -rw-r--r-- | matweb/rest_api.py | 36 | ||||
| -rw-r--r-- | test/test_api.py | 63 |
3 files changed, 137 insertions, 3 deletions
diff --git a/matweb/oas/remove_metadata.yml b/matweb/oas/remove_metadata.yml new file mode 100644 index 0000000..ad5a7da --- /dev/null +++ b/matweb/oas/remove_metadata.yml | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | --- | ||
| 2 | tags: | ||
| 3 | - "Metadata removal cleaning in one request (automated clients)" | ||
| 4 | summary: 'Upload a single file which will be cleaned from metadata and returned directly' | ||
| 5 | requestBody: | ||
| 6 | description: "The file that will be cleaned from metadata and the cleaned file is returned directly" | ||
| 7 | required: true | ||
| 8 | content: | ||
| 9 | multipart/form-data: | ||
| 10 | schema: | ||
| 11 | type: object | ||
| 12 | properties: | ||
| 13 | file: | ||
| 14 | type: string | ||
| 15 | format: binary | ||
| 16 | responses: | ||
| 17 | '200': | ||
| 18 | description: "The cleaned file" | ||
| 19 | content: | ||
| 20 | "*/*": | ||
| 21 | schema: | ||
| 22 | type: string | ||
| 23 | format: binary | ||
| 24 | 400: | ||
| 25 | description: "Invalid input" | ||
| 26 | content: | ||
| 27 | application/json: | ||
| 28 | schema: | ||
| 29 | $ref: '#/components/schemas/ErrorResponse' | ||
| 30 | 415: | ||
| 31 | description: "Unsupported file type" | ||
| 32 | content: | ||
| 33 | application/json: | ||
| 34 | schema: | ||
| 35 | $ref: '#/components/schemas/ErrorResponse' | ||
| 36 | 500: | ||
| 37 | description: "Unable to clean the file" | ||
| 38 | content: | ||
| 39 | application/json: | ||
| 40 | schema: | ||
| 41 | $ref: '#/components/schemas/ErrorResponse' \ No newline at end of file | ||
diff --git a/matweb/rest_api.py b/matweb/rest_api.py index 2909cf6..50b7c37 100644 --- a/matweb/rest_api.py +++ b/matweb/rest_api.py | |||
| @@ -84,6 +84,38 @@ class APIDownload(Resource): | |||
| 84 | return send_from_directory(current_app.config['UPLOAD_FOLDER'], filepath, as_attachment=True) | 84 | return send_from_directory(current_app.config['UPLOAD_FOLDER'], filepath, as_attachment=True) |
| 85 | 85 | ||
| 86 | 86 | ||
| 87 | class APIClean(Resource): | ||
| 88 | @swag_from('./oas/remove_metadata.yml') | ||
| 89 | def post(self): | ||
| 90 | if 'file' not in request.files: | ||
| 91 | abort(400, message='No file part') | ||
| 92 | |||
| 93 | uploaded_file = request.files['file'] | ||
| 94 | if not uploaded_file.filename: | ||
| 95 | abort(400, message='No selected `file`') | ||
| 96 | try: | ||
| 97 | filename, filepath = utils.save_file(uploaded_file, current_app.config['UPLOAD_FOLDER']) | ||
| 98 | except ValueError: | ||
| 99 | abort(400, message='Invalid Filename') | ||
| 100 | |||
| 101 | parser, mime = utils.get_file_parser(filepath) | ||
| 102 | |||
| 103 | if parser is None: | ||
| 104 | abort(415, message='The type %s is not supported' % mime) | ||
| 105 | |||
| 106 | if parser.remove_all() is not True: | ||
| 107 | abort(500, message='Unable to clean %s' % mime) | ||
| 108 | |||
| 109 | _, _, _, output_filename = utils.cleanup(parser, filepath, current_app.config['UPLOAD_FOLDER']) | ||
| 110 | |||
| 111 | @after_this_request | ||
| 112 | def remove_file(response): | ||
| 113 | os.remove(os.path.join(current_app.config['UPLOAD_FOLDER'], output_filename)) | ||
| 114 | return response | ||
| 115 | |||
| 116 | return send_from_directory(current_app.config['UPLOAD_FOLDER'], output_filename, as_attachment=True) | ||
| 117 | |||
| 118 | |||
| 87 | class APIBulkDownloadCreator(Resource): | 119 | class APIBulkDownloadCreator(Resource): |
| 88 | schema = { | 120 | schema = { |
| 89 | 'download_list': { | 121 | 'download_list': { |
| @@ -169,6 +201,10 @@ api.add_resource( | |||
| 169 | '/download/<string:key>/<string:secret>/<string:filename>' | 201 | '/download/<string:key>/<string:secret>/<string:filename>' |
| 170 | ) | 202 | ) |
| 171 | api.add_resource( | 203 | api.add_resource( |
| 204 | APIClean, | ||
| 205 | '/remove_metadata' | ||
| 206 | ) | ||
| 207 | api.add_resource( | ||
| 172 | APIBulkDownloadCreator, | 208 | APIBulkDownloadCreator, |
| 173 | '/download/bulk' | 209 | '/download/bulk' |
| 174 | ) | 210 | ) |
diff --git a/test/test_api.py b/test/test_api.py index 6961197..6d48a35 100644 --- a/test/test_api.py +++ b/test/test_api.py | |||
| @@ -1,13 +1,15 @@ | |||
| 1 | import unittest | ||
| 2 | import tempfile | ||
| 3 | import json | 1 | import json |
| 4 | import os | 2 | import os |
| 5 | import shutil | 3 | import shutil |
| 4 | import tempfile | ||
| 5 | import unittest | ||
| 6 | import zipfile | 6 | import zipfile |
| 7 | from six import BytesIO | 7 | import io |
| 8 | 8 | ||
| 9 | from unittest.mock import patch | 9 | from unittest.mock import patch |
| 10 | |||
| 10 | from openapi_spec_validator import validate_spec | 11 | from openapi_spec_validator import validate_spec |
| 12 | from six import BytesIO | ||
| 11 | 13 | ||
| 12 | import main | 14 | import main |
| 13 | 15 | ||
| @@ -433,5 +435,60 @@ class Mat2APITestCase(unittest.TestCase): | |||
| 433 | spec = self.app.get('apispec_1.json').get_json() | 435 | spec = self.app.get('apispec_1.json').get_json() |
| 434 | validate_spec(spec) | 436 | validate_spec(spec) |
| 435 | 437 | ||
| 438 | def test_remove_metadata(self): | ||
| 439 | r = self.app.post( | ||
| 440 | '/api/remove_metadata', | ||
| 441 | data=dict( | ||
| 442 | file=(io.BytesIO(b""), 'test.txt'), | ||
| 443 | ), | ||
| 444 | follow_redirects=False | ||
| 445 | ) | ||
| 446 | self.assertEqual(r.status_code, 200) | ||
| 447 | self.assertEqual(r.headers['Content-Disposition'], 'attachment; filename=test.cleaned.txt') | ||
| 448 | self.assertEqual(r.headers['Content-Type'], 'text/plain; charset=utf-8') | ||
| 449 | self.assertEqual(r.data, b'') | ||
| 450 | |||
| 451 | def test_remove_metdata_validation(self): | ||
| 452 | r = self.app.post( | ||
| 453 | '/api/remove_metadata', | ||
| 454 | data=dict( | ||
| 455 | fileNotExisting=(io.BytesIO(b""), 'test.random'), | ||
| 456 | ), | ||
| 457 | follow_redirects=False | ||
| 458 | ) | ||
| 459 | self.assertEqual(r.get_json()['message'], 'No file part') | ||
| 460 | self.assertEqual(r.status_code, 400) | ||
| 461 | |||
| 462 | r = self.app.post( | ||
| 463 | '/api/remove_metadata', | ||
| 464 | data=dict( | ||
| 465 | file=(io.BytesIO(b""), ''), | ||
| 466 | ), | ||
| 467 | follow_redirects=False | ||
| 468 | ) | ||
| 469 | self.assertEqual(r.get_json()['message'], 'No selected `file`') | ||
| 470 | self.assertEqual(r.status_code, 400) | ||
| 471 | |||
| 472 | r = self.app.post( | ||
| 473 | '/api/remove_metadata', | ||
| 474 | data=dict( | ||
| 475 | file=(io.BytesIO(b""), '../../'), | ||
| 476 | ), | ||
| 477 | follow_redirects=False | ||
| 478 | ) | ||
| 479 | self.assertEqual(r.get_json()['message'], 'Invalid Filename') | ||
| 480 | self.assertEqual(r.status_code, 400) | ||
| 481 | |||
| 482 | r = self.app.post( | ||
| 483 | '/api/remove_metadata', | ||
| 484 | data=dict( | ||
| 485 | file=(io.BytesIO(b""), 'test.random'), | ||
| 486 | ), | ||
| 487 | follow_redirects=False | ||
| 488 | ) | ||
| 489 | self.assertEqual(r.get_json()['message'], 'The type None is not supported') | ||
| 490 | self.assertEqual(r.status_code, 415) | ||
| 491 | |||
| 492 | |||
| 436 | if __name__ == '__main__': | 493 | if __name__ == '__main__': |
| 437 | unittest.main() | 494 | unittest.main() |
