From 6b526f6b539e744b7bfe5560e8821f151f21cf97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Wed, 22 Mar 2017 16:05:38 +0100 Subject: [PATCH] Fixed bug in local file storage URL generation. --- pillar/api/file_storage_backends/abstract.py | 3 +- pillar/api/file_storage_backends/local.py | 6 ++-- tests/test_api/test_file_storage_backends.py | 32 ++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 tests/test_api/test_file_storage_backends.py diff --git a/pillar/api/file_storage_backends/abstract.py b/pillar/api/file_storage_backends/abstract.py index 1195de71..59c6aa07 100644 --- a/pillar/api/file_storage_backends/abstract.py +++ b/pillar/api/file_storage_backends/abstract.py @@ -11,7 +11,8 @@ __all__ = ['Bucket', 'Blob', 'Path', 'FileType'] # Shorthand for the type of path we use. Path = pathlib.PurePosixPath -# This is a mess: typing.IO keeps mypy-0.501 happy, and io.FileIO keeps PyCharm-2017.1 happy. +# This is a mess: typing.IO keeps mypy-0.501 happy, but not in all cases, +# and io.FileIO keeps PyCharm-2017.1 happy. FileType = typing.Union[typing.IO, io.FileIO] diff --git a/pillar/api/file_storage_backends/local.py b/pillar/api/file_storage_backends/local.py index 3b2243de..b57a5cab 100644 --- a/pillar/api/file_storage_backends/local.py +++ b/pillar/api/file_storage_backends/local.py @@ -22,7 +22,8 @@ class LocalBucket(Bucket): # For local storage, the name is actually a partial path, relative # to the local storage root. self.root = pathlib.Path(current_app.config['STORAGE_DIR']) - self.abspath = self.root / name[:2] / name + self.bucket_path = pathlib.PurePosixPath(name[:2]) / name + self.abspath = self.root / self.bucket_path def blob(self, blob_name: str) -> 'LocalBlob': return LocalBlob(name=blob_name, bucket=self) @@ -71,7 +72,8 @@ class LocalBlob(Blob): def get_url(self, *, is_public: bool) -> str: from flask import url_for - url = url_for('file_storage.index', file_name=str(self.partial_path), _external=True, + path = self.bucket.bucket_path / self.partial_path + url = url_for('file_storage.index', file_name=str(path), _external=True, _scheme=current_app.config['SCHEME']) return url diff --git a/tests/test_api/test_file_storage_backends.py b/tests/test_api/test_file_storage_backends.py new file mode 100644 index 00000000..9e661ab7 --- /dev/null +++ b/tests/test_api/test_file_storage_backends.py @@ -0,0 +1,32 @@ +import typing + +from pillar.tests import AbstractPillarTest + + +class LocalStorageBackendTest(AbstractPillarTest): + + def test_upload_download(self): + import io + import secrets + + from pillar.api.file_storage_backends import Bucket + + file_contents = secrets.token_bytes(512) + test_file: typing.IO = io.BytesIO(file_contents) + + with self.app.test_request_context(): + bucket_class = Bucket.for_backend('local') + bucket = bucket_class('buckettest') + blob = bucket.blob('somefile.bin') + + # We should be able to upload the file, and then download it again + # from the URL given by its blob. + blob.create_from_file(test_file, file_size=512, content_type='application/octet-stream') + + url = blob.get_url(is_public=True) + + resp = self.get(url) + + self.assertEqual(200, resp.status_code) + self.assertEqual('512', resp.headers['Content-Length']) + self.assertEqual(file_contents, resp.data)