From 395c0cb60f1d1a1cfa069e1cb99ddb474870c603 Mon Sep 17 00:00:00 2001 From: Alexander Leonov Date: Wed, 7 Jun 2023 22:16:19 -0400 Subject: [PATCH 1/4] feat: created functions to get multiple signed URLs. --- storage3/_async/file_api.py | 61 +++++++++++++++++++++++++++++------ storage3/_sync/file_api.py | 61 +++++++++++++++++++++++++++++------ storage3/types.py | 6 ++-- tests/_async/test_client.py | 62 +++++++++++++++++++++++++++++++++++ tests/_sync/test_client.py | 64 ++++++++++++++++++++++++++++++++++++- 5 files changed, 233 insertions(+), 21 deletions(-) diff --git a/storage3/_async/file_api.py b/storage3/_async/file_api.py index a3e8ca5a..84599078 100644 --- a/storage3/_async/file_api.py +++ b/storage3/_async/file_api.py @@ -9,14 +9,8 @@ from httpx import HTTPError, Response from ..constants import DEFAULT_FILE_OPTIONS, DEFAULT_SEARCH_OPTIONS -from ..types import ( - BaseBucket, - CreateSignedURLOptions, - FileOptions, - ListBucketFilesOptions, - RequestMethod, - TransformOptions, -) +from ..types import (BaseBucket, CreateSignedURLOptions, FileOptions, + ListBucketFilesOptions, RequestMethod, TransformOptions) from ..utils import AsyncClient, StorageException __all__ = ["AsyncBucket"] @@ -62,18 +56,67 @@ async def create_signed_url( file path to be downloaded, including the current file name. expires_in number of seconds until the signed URL expires. + options + options to be passed for downloading or transforming the file. """ + headers = {} + token = self._client.headers.get("Authorization") + if token: + headers["Authorization"] = token + + json = {"expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + if options.get("transform"): + json.update({"transform": options["transform"]}) + path = self._get_final_path(path) response = await self._request( "POST", f"/object/sign/{path}", - json={"expiresIn": str(expires_in)}, + headers=headers, + json=json, ) data = response.json() data[ "signedURL" ] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}" return data + + async def create_signed_urls( + self, paths: list[str], expires_in: int, options: CreateSignedURLOptions = {} + ) -> list[dict[str, str]]: + """ + Parameters + ---------- + path + file path to be downloaded, including the current file name. + expires_in + number of seconds until the signed URL expires. + options + options to be passed for downloading the file. + """ + headers = {} + token = self._client.headers.get("Authorization") + if token: + headers["Authorization"] = token + + json = {"paths": paths, "expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + + response = await self._request( + "POST", + f"/object/sign/{self.id}", + headers=headers, + json=json, + ) + data = response.json() + for item in data: + item[ + "signedURL" + ] = f"{self._client.base_url}{cast(str, item['signedURL']).lstrip('/')}" + return data async def get_public_url(self, path: str, options: TransformOptions = {}) -> str: """ diff --git a/storage3/_sync/file_api.py b/storage3/_sync/file_api.py index f8a2f086..2c1e767d 100644 --- a/storage3/_sync/file_api.py +++ b/storage3/_sync/file_api.py @@ -9,14 +9,8 @@ from httpx import HTTPError, Response from ..constants import DEFAULT_FILE_OPTIONS, DEFAULT_SEARCH_OPTIONS -from ..types import ( - BaseBucket, - CreateSignedURLOptions, - FileOptions, - ListBucketFilesOptions, - RequestMethod, - TransformOptions, -) +from ..types import (BaseBucket, CreateSignedURLOptions, FileOptions, + ListBucketFilesOptions, RequestMethod, TransformOptions) from ..utils import StorageException, SyncClient __all__ = ["SyncBucket"] @@ -62,12 +56,26 @@ def create_signed_url( file path to be downloaded, including the current file name. expires_in number of seconds until the signed URL expires. + options + options to be passed for downloading or transforming the file. """ + headers = {} + token = self._client.headers.get("Authorization") + if token: + headers["Authorization"] = token + + json = {"expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + if options.get("transform"): + json.update({"transform": options["transform"]}) + path = self._get_final_path(path) response = self._request( "POST", f"/object/sign/{path}", - json={"expiresIn": str(expires_in)}, + headers=headers, + json=json, ) data = response.json() data[ @@ -75,6 +83,41 @@ def create_signed_url( ] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}" return data + def create_signed_urls( + self, paths: list[str], expires_in: int, options: CreateSignedURLOptions = {} + ) -> list[dict[str, str]]: + """ + Parameters + ---------- + path + file path to be downloaded, including the current file name. + expires_in + number of seconds until the signed URL expires. + options + options to be passed for downloading the file. + """ + headers = {} + token = self._client.headers.get("Authorization") + if token: + headers["Authorization"] = token + + json = {"paths": paths, "expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + + response = self._request( + "POST", + f"/object/sign/{self.id}", + headers=headers, + json=json, + ) + data = response.json() + for item in data: + item[ + "signedURL" + ] = f"{self._client.base_url}{cast(str, item['signedURL']).lstrip('/')}" + return data + def get_public_url(self, path: str, options: TransformOptions = {}) -> str: """ Parameters diff --git a/storage3/types.py b/storage3/types.py index 9f905b63..018f65bd 100644 --- a/storage3/types.py +++ b/storage3/types.py @@ -41,9 +41,11 @@ class ListBucketFilesOptions(TypedDict): class TransformOptions(TypedDict): - height: Optional[float] - width: Optional[float] + height: Optional[int] + width: Optional[int] resize: Optional[Union[Literal["cover"], Literal["contain"], Literal["fill"]]] + format: Optional[Union[Literal["origin"], Literal["avif"]]] + quality: Optional[int] class CreateSignedURLOptions(TypedDict): diff --git a/tests/_async/test_client.py b/tests/_async/test_client.py index 7b84880c..0cd010b2 100644 --- a/tests/_async/test_client.py +++ b/tests/_async/test_client.py @@ -155,6 +155,49 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting: file_content=file_content, ) +@pytest.fixture +def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForTesting]: + """Creates multiple test files (same content, same bucket/folder path, different file names)""" + file_name_1 = "test_image_1.svg" + file_name_2 = "test_image_2.svg" + file_content = ( + b' ' + b' ' + b' ' + b' ' + b' ' + ) + bucket_folder = uuid_factory() + bucket_path_1 = f"{bucket_folder}/{file_name_1}" + bucket_path_2 = f"{bucket_folder}/{file_name_2}" + file_path_1 = tmp_path / file_name_1 + file_path_2 = tmp_path / file_name_2 + with open(file_path_1, "wb") as f: + f.write(file_content) + with open(file_path_2, "wb") as f: + f.write(file_content) + + return [FileForTesting( + name=file_name_1, + local_path=str(file_path_1), + bucket_folder=bucket_folder, + bucket_path=bucket_path_1, + mime_type="image/svg+xml", + file_content=file_content, + ), FileForTesting( + name=file_name_2, + local_path=str(file_path_2), + bucket_folder=bucket_folder, + bucket_path=bucket_path_2, + mime_type="image/svg+xml", + file_content=file_content, + )] # TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test @@ -193,6 +236,25 @@ async def test_client_create_signed_url( assert response.content == file.file_content +async def test_client_create_signed_urls( + storage_file_client: AsyncBucketProxy, multi_file: list[FileForTesting] +) -> None: + """Ensure we can create signed urls for files in a bucket""" + paths = [] + for file in multi_file: + paths.append(file.bucket_path) + await storage_file_client.upload( + file.bucket_path, file.local_path, {"content-type": file.mime_type} + ) + + signed_urls = await storage_file_client.create_signed_urls(paths, 10) + + async with HttpxClient() as client: + for url in signed_urls: + response = await client.get(url["signedURL"]) + response.raise_for_status() + assert response.content == multi_file[0].file_content + async def test_client_get_public_url( storage_file_client_public: AsyncBucketProxy, file: FileForTesting diff --git a/tests/_sync/test_client.py b/tests/_sync/test_client.py index d3622128..61a92be2 100644 --- a/tests/_sync/test_client.py +++ b/tests/_sync/test_client.py @@ -153,6 +153,50 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting: file_content=file_content, ) +@pytest.fixture +def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForTesting]: + """Creates multiple test files (same content, same bucket/folder path, different file names)""" + file_name_1 = "test_image_1.svg" + file_name_2 = "test_image_2.svg" + file_content = ( + b' ' + b' ' + b' ' + b' ' + b' ' + ) + bucket_folder = uuid_factory() + bucket_path_1 = f"{bucket_folder}/{file_name_1}" + bucket_path_2 = f"{bucket_folder}/{file_name_2}" + file_path_1 = tmp_path / file_name_1 + file_path_2 = tmp_path / file_name_2 + with open(file_path_1, "wb") as f: + f.write(file_content) + with open(file_path_2, "wb") as f: + f.write(file_content) + + return [FileForTesting( + name=file_name_1, + local_path=str(file_path_1), + bucket_folder=bucket_folder, + bucket_path=bucket_path_1, + mime_type="image/svg+xml", + file_content=file_content, + ), FileForTesting( + name=file_name_2, + local_path=str(file_path_2), + bucket_folder=bucket_folder, + bucket_path=bucket_path_2, + mime_type="image/svg+xml", + file_content=file_content, + )] + # TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test @@ -180,7 +224,7 @@ def test_client_create_signed_url( storage_file_client.upload( file.bucket_path, file.local_path, {"content-type": file.mime_type} ) - + signed_url = (storage_file_client.create_signed_url(file.bucket_path, 10))[ "signedURL" ] @@ -191,6 +235,24 @@ def test_client_create_signed_url( assert response.content == file.file_content +def test_client_create_signed_urls( + storage_file_client: SyncBucketProxy, multi_file: list[FileForTesting] +) -> None: + """Ensure we can create signed urls for files in a bucket""" + paths = [] + for file in multi_file: + paths.append(file.bucket_path) + storage_file_client.upload( + file.bucket_path, file.local_path, {"content-type": file.mime_type} + ) + + signed_urls = storage_file_client.create_signed_urls(paths, 10) + + with HttpxClient() as client: + for url in signed_urls: + response = client.get(url["signedURL"]) + response.raise_for_status() + assert response.content == multi_file[0].file_content def test_client_get_public_url( storage_file_client_public: SyncBucketProxy, file: FileForTesting From 36793e2951ff404c7bca0f8b1e33bb881d27a842 Mon Sep 17 00:00:00 2001 From: Alexander Leonov Date: Thu, 8 Jun 2023 10:44:00 -0400 Subject: [PATCH 2/4] feat: Fixed optional params. Handling auth token issue #73 in separate PR. --- storage3/_async/file_api.py | 27 +++++++++++---------------- storage3/_sync/file_api.py | 25 ++++++++++--------------- storage3/types.py | 22 +++++++++++++--------- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/storage3/_async/file_api.py b/storage3/_async/file_api.py index 84599078..701ba4c7 100644 --- a/storage3/_async/file_api.py +++ b/storage3/_async/file_api.py @@ -9,8 +9,15 @@ from httpx import HTTPError, Response from ..constants import DEFAULT_FILE_OPTIONS, DEFAULT_SEARCH_OPTIONS -from ..types import (BaseBucket, CreateSignedURLOptions, FileOptions, - ListBucketFilesOptions, RequestMethod, TransformOptions) +from ..types import ( + BaseBucket, + CreateSignedURLOptions, + CreateSignedURLsOptions, + FileOptions, + ListBucketFilesOptions, + RequestMethod, + TransformOptions, +) from ..utils import AsyncClient, StorageException __all__ = ["AsyncBucket"] @@ -59,11 +66,6 @@ async def create_signed_url( options options to be passed for downloading or transforming the file. """ - headers = {} - token = self._client.headers.get("Authorization") - if token: - headers["Authorization"] = token - json = {"expiresIn": str(expires_in)} if options.get("download"): json.update({"download": options["download"]}) @@ -74,7 +76,6 @@ async def create_signed_url( response = await self._request( "POST", f"/object/sign/{path}", - headers=headers, json=json, ) data = response.json() @@ -82,9 +83,9 @@ async def create_signed_url( "signedURL" ] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}" return data - + async def create_signed_urls( - self, paths: list[str], expires_in: int, options: CreateSignedURLOptions = {} + self, paths: list[str], expires_in: int, options: CreateSignedURLsOptions = {} ) -> list[dict[str, str]]: """ Parameters @@ -96,11 +97,6 @@ async def create_signed_urls( options options to be passed for downloading the file. """ - headers = {} - token = self._client.headers.get("Authorization") - if token: - headers["Authorization"] = token - json = {"paths": paths, "expiresIn": str(expires_in)} if options.get("download"): json.update({"download": options["download"]}) @@ -108,7 +104,6 @@ async def create_signed_urls( response = await self._request( "POST", f"/object/sign/{self.id}", - headers=headers, json=json, ) data = response.json() diff --git a/storage3/_sync/file_api.py b/storage3/_sync/file_api.py index 2c1e767d..09ec2e43 100644 --- a/storage3/_sync/file_api.py +++ b/storage3/_sync/file_api.py @@ -9,8 +9,15 @@ from httpx import HTTPError, Response from ..constants import DEFAULT_FILE_OPTIONS, DEFAULT_SEARCH_OPTIONS -from ..types import (BaseBucket, CreateSignedURLOptions, FileOptions, - ListBucketFilesOptions, RequestMethod, TransformOptions) +from ..types import ( + BaseBucket, + CreateSignedURLOptions, + CreateSignedURLsOptions, + FileOptions, + ListBucketFilesOptions, + RequestMethod, + TransformOptions, +) from ..utils import StorageException, SyncClient __all__ = ["SyncBucket"] @@ -59,11 +66,6 @@ def create_signed_url( options options to be passed for downloading or transforming the file. """ - headers = {} - token = self._client.headers.get("Authorization") - if token: - headers["Authorization"] = token - json = {"expiresIn": str(expires_in)} if options.get("download"): json.update({"download": options["download"]}) @@ -74,7 +76,6 @@ def create_signed_url( response = self._request( "POST", f"/object/sign/{path}", - headers=headers, json=json, ) data = response.json() @@ -84,7 +85,7 @@ def create_signed_url( return data def create_signed_urls( - self, paths: list[str], expires_in: int, options: CreateSignedURLOptions = {} + self, paths: list[str], expires_in: int, options: CreateSignedURLsOptions = {} ) -> list[dict[str, str]]: """ Parameters @@ -96,11 +97,6 @@ def create_signed_urls( options options to be passed for downloading the file. """ - headers = {} - token = self._client.headers.get("Authorization") - if token: - headers["Authorization"] = token - json = {"paths": paths, "expiresIn": str(expires_in)} if options.get("download"): json.update({"download": options["download"]}) @@ -108,7 +104,6 @@ def create_signed_urls( response = self._request( "POST", f"/object/sign/{self.id}", - headers=headers, json=json, ) data = response.json() diff --git a/storage3/types.py b/storage3/types.py index 018f65bd..d69d0e54 100644 --- a/storage3/types.py +++ b/storage3/types.py @@ -40,17 +40,21 @@ class ListBucketFilesOptions(TypedDict): sortBy: _sortByType -class TransformOptions(TypedDict): - height: Optional[int] - width: Optional[int] - resize: Optional[Union[Literal["cover"], Literal["contain"], Literal["fill"]]] - format: Optional[Union[Literal["origin"], Literal["avif"]]] - quality: Optional[int] +class TransformOptions(TypedDict, total=False): + height: int + width: int + resize: Literal["cover", "contain", "fill"] + format: Literal["origin", "avif"] + quality: int -class CreateSignedURLOptions(TypedDict): - download: Optional[Union[str, bool]] - transform: Optional[TransformOptions] +class CreateSignedURLOptions(TypedDict, total=False): + download: Union[str, bool] + transform: TransformOptions + + +class CreateSignedURLsOptions(TypedDict): + download: Union[str, bool] FileOptions = TypedDict( From b31744ad00ce9f7cabefc1ae16f7189095cab381 Mon Sep 17 00:00:00 2001 From: Alexander Leonov Date: Fri, 9 Jun 2023 10:23:06 -0400 Subject: [PATCH 3/4] feat: remove sync code as it will be generated by unasync. --- storage3/_sync/file_api.py | 40 +----------------------- tests/_sync/test_client.py | 64 +------------------------------------- 2 files changed, 2 insertions(+), 102 deletions(-) diff --git a/storage3/_sync/file_api.py b/storage3/_sync/file_api.py index 09ec2e43..f8a2f086 100644 --- a/storage3/_sync/file_api.py +++ b/storage3/_sync/file_api.py @@ -12,7 +12,6 @@ from ..types import ( BaseBucket, CreateSignedURLOptions, - CreateSignedURLsOptions, FileOptions, ListBucketFilesOptions, RequestMethod, @@ -63,20 +62,12 @@ def create_signed_url( file path to be downloaded, including the current file name. expires_in number of seconds until the signed URL expires. - options - options to be passed for downloading or transforming the file. """ - json = {"expiresIn": str(expires_in)} - if options.get("download"): - json.update({"download": options["download"]}) - if options.get("transform"): - json.update({"transform": options["transform"]}) - path = self._get_final_path(path) response = self._request( "POST", f"/object/sign/{path}", - json=json, + json={"expiresIn": str(expires_in)}, ) data = response.json() data[ @@ -84,35 +75,6 @@ def create_signed_url( ] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}" return data - def create_signed_urls( - self, paths: list[str], expires_in: int, options: CreateSignedURLsOptions = {} - ) -> list[dict[str, str]]: - """ - Parameters - ---------- - path - file path to be downloaded, including the current file name. - expires_in - number of seconds until the signed URL expires. - options - options to be passed for downloading the file. - """ - json = {"paths": paths, "expiresIn": str(expires_in)} - if options.get("download"): - json.update({"download": options["download"]}) - - response = self._request( - "POST", - f"/object/sign/{self.id}", - json=json, - ) - data = response.json() - for item in data: - item[ - "signedURL" - ] = f"{self._client.base_url}{cast(str, item['signedURL']).lstrip('/')}" - return data - def get_public_url(self, path: str, options: TransformOptions = {}) -> str: """ Parameters diff --git a/tests/_sync/test_client.py b/tests/_sync/test_client.py index 61a92be2..d3622128 100644 --- a/tests/_sync/test_client.py +++ b/tests/_sync/test_client.py @@ -153,50 +153,6 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting: file_content=file_content, ) -@pytest.fixture -def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForTesting]: - """Creates multiple test files (same content, same bucket/folder path, different file names)""" - file_name_1 = "test_image_1.svg" - file_name_2 = "test_image_2.svg" - file_content = ( - b' ' - b' ' - b' ' - b' ' - b' ' - ) - bucket_folder = uuid_factory() - bucket_path_1 = f"{bucket_folder}/{file_name_1}" - bucket_path_2 = f"{bucket_folder}/{file_name_2}" - file_path_1 = tmp_path / file_name_1 - file_path_2 = tmp_path / file_name_2 - with open(file_path_1, "wb") as f: - f.write(file_content) - with open(file_path_2, "wb") as f: - f.write(file_content) - - return [FileForTesting( - name=file_name_1, - local_path=str(file_path_1), - bucket_folder=bucket_folder, - bucket_path=bucket_path_1, - mime_type="image/svg+xml", - file_content=file_content, - ), FileForTesting( - name=file_name_2, - local_path=str(file_path_2), - bucket_folder=bucket_folder, - bucket_path=bucket_path_2, - mime_type="image/svg+xml", - file_content=file_content, - )] - # TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test @@ -224,7 +180,7 @@ def test_client_create_signed_url( storage_file_client.upload( file.bucket_path, file.local_path, {"content-type": file.mime_type} ) - + signed_url = (storage_file_client.create_signed_url(file.bucket_path, 10))[ "signedURL" ] @@ -235,24 +191,6 @@ def test_client_create_signed_url( assert response.content == file.file_content -def test_client_create_signed_urls( - storage_file_client: SyncBucketProxy, multi_file: list[FileForTesting] -) -> None: - """Ensure we can create signed urls for files in a bucket""" - paths = [] - for file in multi_file: - paths.append(file.bucket_path) - storage_file_client.upload( - file.bucket_path, file.local_path, {"content-type": file.mime_type} - ) - - signed_urls = storage_file_client.create_signed_urls(paths, 10) - - with HttpxClient() as client: - for url in signed_urls: - response = client.get(url["signedURL"]) - response.raise_for_status() - assert response.content == multi_file[0].file_content def test_client_get_public_url( storage_file_client_public: SyncBucketProxy, file: FileForTesting From 2a3d1297b24f7c5cb74cb593a43cc84787f18472 Mon Sep 17 00:00:00 2001 From: anand2312 <40204976+anand2312@users.noreply.github.com> Date: Sat, 10 Jun 2023 21:52:13 +0530 Subject: [PATCH 4/4] chore: generate sync client --- storage3/_sync/file_api.py | 40 +++++++++++++++++++++- tests/_async/test_client.py | 36 ++++++++++++-------- tests/_sync/test_client.py | 68 +++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 16 deletions(-) diff --git a/storage3/_sync/file_api.py b/storage3/_sync/file_api.py index f8a2f086..09ec2e43 100644 --- a/storage3/_sync/file_api.py +++ b/storage3/_sync/file_api.py @@ -12,6 +12,7 @@ from ..types import ( BaseBucket, CreateSignedURLOptions, + CreateSignedURLsOptions, FileOptions, ListBucketFilesOptions, RequestMethod, @@ -62,12 +63,20 @@ def create_signed_url( file path to be downloaded, including the current file name. expires_in number of seconds until the signed URL expires. + options + options to be passed for downloading or transforming the file. """ + json = {"expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + if options.get("transform"): + json.update({"transform": options["transform"]}) + path = self._get_final_path(path) response = self._request( "POST", f"/object/sign/{path}", - json={"expiresIn": str(expires_in)}, + json=json, ) data = response.json() data[ @@ -75,6 +84,35 @@ def create_signed_url( ] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}" return data + def create_signed_urls( + self, paths: list[str], expires_in: int, options: CreateSignedURLsOptions = {} + ) -> list[dict[str, str]]: + """ + Parameters + ---------- + path + file path to be downloaded, including the current file name. + expires_in + number of seconds until the signed URL expires. + options + options to be passed for downloading the file. + """ + json = {"paths": paths, "expiresIn": str(expires_in)} + if options.get("download"): + json.update({"download": options["download"]}) + + response = self._request( + "POST", + f"/object/sign/{self.id}", + json=json, + ) + data = response.json() + for item in data: + item[ + "signedURL" + ] = f"{self._client.base_url}{cast(str, item['signedURL']).lstrip('/')}" + return data + def get_public_url(self, path: str, options: TransformOptions = {}) -> str: """ Parameters diff --git a/tests/_async/test_client.py b/tests/_async/test_client.py index 0cd010b2..74925a13 100644 --- a/tests/_async/test_client.py +++ b/tests/_async/test_client.py @@ -155,6 +155,7 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting: file_content=file_content, ) + @pytest.fixture def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForTesting]: """Creates multiple test files (same content, same bucket/folder path, different file names)""" @@ -183,21 +184,25 @@ def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForT with open(file_path_2, "wb") as f: f.write(file_content) - return [FileForTesting( - name=file_name_1, - local_path=str(file_path_1), - bucket_folder=bucket_folder, - bucket_path=bucket_path_1, - mime_type="image/svg+xml", - file_content=file_content, - ), FileForTesting( - name=file_name_2, - local_path=str(file_path_2), - bucket_folder=bucket_folder, - bucket_path=bucket_path_2, - mime_type="image/svg+xml", - file_content=file_content, - )] + return [ + FileForTesting( + name=file_name_1, + local_path=str(file_path_1), + bucket_folder=bucket_folder, + bucket_path=bucket_path_1, + mime_type="image/svg+xml", + file_content=file_content, + ), + FileForTesting( + name=file_name_2, + local_path=str(file_path_2), + bucket_folder=bucket_folder, + bucket_path=bucket_path_2, + mime_type="image/svg+xml", + file_content=file_content, + ), + ] + # TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test @@ -236,6 +241,7 @@ async def test_client_create_signed_url( assert response.content == file.file_content + async def test_client_create_signed_urls( storage_file_client: AsyncBucketProxy, multi_file: list[FileForTesting] ) -> None: diff --git a/tests/_sync/test_client.py b/tests/_sync/test_client.py index d3622128..9f273460 100644 --- a/tests/_sync/test_client.py +++ b/tests/_sync/test_client.py @@ -154,6 +154,54 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting: ) +@pytest.fixture +def multi_file(tmp_path: Path, uuid_factory: Callable[[], str]) -> list[FileForTesting]: + """Creates multiple test files (same content, same bucket/folder path, different file names)""" + file_name_1 = "test_image_1.svg" + file_name_2 = "test_image_2.svg" + file_content = ( + b' ' + b' ' + b' ' + b' ' + b' ' + ) + bucket_folder = uuid_factory() + bucket_path_1 = f"{bucket_folder}/{file_name_1}" + bucket_path_2 = f"{bucket_folder}/{file_name_2}" + file_path_1 = tmp_path / file_name_1 + file_path_2 = tmp_path / file_name_2 + with open(file_path_1, "wb") as f: + f.write(file_content) + with open(file_path_2, "wb") as f: + f.write(file_content) + + return [ + FileForTesting( + name=file_name_1, + local_path=str(file_path_1), + bucket_folder=bucket_folder, + bucket_path=bucket_path_1, + mime_type="image/svg+xml", + file_content=file_content, + ), + FileForTesting( + name=file_name_2, + local_path=str(file_path_2), + bucket_folder=bucket_folder, + bucket_path=bucket_path_2, + mime_type="image/svg+xml", + file_content=file_content, + ), + ] + + # TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test @@ -192,6 +240,26 @@ def test_client_create_signed_url( assert response.content == file.file_content +def test_client_create_signed_urls( + storage_file_client: SyncBucketProxy, multi_file: list[FileForTesting] +) -> None: + """Ensure we can create signed urls for files in a bucket""" + paths = [] + for file in multi_file: + paths.append(file.bucket_path) + storage_file_client.upload( + file.bucket_path, file.local_path, {"content-type": file.mime_type} + ) + + signed_urls = storage_file_client.create_signed_urls(paths, 10) + + with HttpxClient() as client: + for url in signed_urls: + response = client.get(url["signedURL"]) + response.raise_for_status() + assert response.content == multi_file[0].file_content + + def test_client_get_public_url( storage_file_client_public: SyncBucketProxy, file: FileForTesting ) -> None: