Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit bd2e09c

Browse files
committed
fix: signed_url
1 parent ca95502 commit bd2e09c

File tree

5 files changed

+226
-53
lines changed

5 files changed

+226
-53
lines changed

storage3/_async/file_api.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass, field
44
from pathlib import Path
5-
from typing import Any, Optional, Union
5+
from typing import Any, Optional, Union, cast
66

77
from httpx import HTTPError, Response
88

@@ -59,10 +59,12 @@ async def create_signed_url(self, path: str, expires_in: int) -> dict[str, str]:
5959
json={"expiresIn": str(expires_in)},
6060
)
6161
data = response.json()
62-
data["signedURL"] = f"{self._client.base_url}{data['signedURL']}"
62+
data[
63+
"signedURL"
64+
] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}"
6365
return data
6466

65-
def get_public_url(self, path: str) -> str:
67+
async def get_public_url(self, path: str) -> str:
6668
"""
6769
Parameters
6870
----------

storage3/_sync/file_api.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from dataclasses import dataclass, field
44
from pathlib import Path
5-
from typing import Any, Optional, Union
5+
from typing import Any, Optional, Union, cast
66

77
from httpx import HTTPError, Response
88

@@ -59,7 +59,9 @@ def create_signed_url(self, path: str, expires_in: int) -> dict[str, str]:
5959
json={"expiresIn": str(expires_in)},
6060
)
6161
data = response.json()
62-
data["signedURL"] = f"{self._client.base_url}{data['signedURL']}"
62+
data[
63+
"signedURL"
64+
] = f"{self._client.base_url}{cast(str, data['signedURL']).lstrip('/')}"
6365
return data
6466

6567
def get_public_url(self, path: str) -> str:

tests/_async/test_client.py

Lines changed: 104 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from dataclasses import dataclass
34
from time import sleep
45
from typing import TYPE_CHECKING
56
from uuid import uuid4
@@ -10,6 +11,7 @@
1011
from storage3.utils import StorageException
1112

1213
from .. import AsyncBucketProxy
14+
from ..utils import AsyncClient as HttpxClient
1315
from ..utils import AsyncFinalizerFactory
1416

1517
if TYPE_CHECKING:
@@ -52,6 +54,12 @@ async def afinalizer():
5254
request.addfinalizer(AsyncFinalizerFactory(afinalizer).finalizer)
5355

5456

57+
async def bucket_factory(
58+
storage: AsyncStorageClient, uuid_factory: Callable[[], str], public: bool
59+
) -> str:
60+
"""Creates a test bucket which will be used in the whole storage tests run and deleted at the end"""
61+
62+
5563
@pytest.fixture(scope="module")
5664
async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) -> str:
5765
"""Creates a test bucket which will be used in the whole storage tests run and deleted at the end"""
@@ -71,14 +79,53 @@ async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) -
7179
temp_test_buckets_ids.remove(bucket_id)
7280

7381

82+
@pytest.fixture(scope="module")
83+
async def public_bucket(
84+
storage: AsyncStorageClient, uuid_factory: Callable[[], str]
85+
) -> str:
86+
"""Creates a test public bucket which will be used in the whole storage tests run and deleted at the end"""
87+
bucket_id = uuid_factory()
88+
89+
# Store bucket_id in global list
90+
global temp_test_buckets_ids
91+
temp_test_buckets_ids.append(bucket_id)
92+
93+
await storage.create_bucket(id=bucket_id, public=True)
94+
95+
yield bucket_id
96+
97+
await storage.empty_bucket(bucket_id)
98+
await storage.delete_bucket(bucket_id)
99+
100+
temp_test_buckets_ids.remove(bucket_id)
101+
102+
74103
@pytest.fixture(scope="module")
75104
def storage_file_client(storage: AsyncStorageClient, bucket: str) -> AsyncBucketProxy:
76105
"""Creates the storage file client for the whole storage tests run"""
77106
yield storage.from_(bucket)
78107

79108

109+
@pytest.fixture(scope="module")
110+
def storage_file_client_public(
111+
storage: AsyncStorageClient, public_bucket: str
112+
) -> AsyncBucketProxy:
113+
"""Creates the storage file client for the whole storage tests run"""
114+
yield storage.from_(public_bucket)
115+
116+
117+
@dataclass
118+
class FileForTesting:
119+
name: str
120+
local_path: str
121+
bucket_folder: str
122+
bucket_path: str
123+
mime_type: str
124+
file_content: bytes
125+
126+
80127
@pytest.fixture
81-
def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> dict[str, str]:
128+
def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting:
82129
"""Creates a different test file (same content but different path) for each test"""
83130
file_name = "test_image.svg"
84131
file_content = (
@@ -100,39 +147,72 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> dict[str, str]:
100147
with open(file_path, "wb") as f:
101148
f.write(file_content)
102149

103-
return {
104-
"name": file_name,
105-
"local_path": str(file_path),
106-
"bucket_folder": bucket_folder,
107-
"bucket_path": bucket_path,
108-
"mime_type": "image/svg+xml",
109-
"file_content": file_content,
110-
}
150+
return FileForTesting(
151+
name=file_name,
152+
local_path=str(file_path),
153+
bucket_folder=bucket_folder,
154+
bucket_path=bucket_path,
155+
mime_type="image/svg+xml",
156+
file_content=file_content,
157+
)
111158

112159

113160
# TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test
114161

115162

116-
async def test_client_upload_file(
117-
storage_file_client: AsyncBucketProxy, file: dict[str, str]
163+
async def test_client_upload(
164+
storage_file_client: AsyncBucketProxy, file: FileForTesting
118165
) -> None:
119166
"""Ensure we can upload files to a bucket"""
167+
await storage_file_client.upload(
168+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
169+
)
120170

121-
file_name = file["name"]
122-
file_path = file["local_path"]
123-
mime_type = file["mime_type"]
124-
file_content = file["file_content"]
125-
bucket_file_path = file["bucket_path"]
126-
bucket_folder = file["bucket_folder"]
127-
options = {"content-type": mime_type}
171+
sleep(3)
128172

129-
await storage_file_client.upload(bucket_file_path, file_path, options)
173+
image = await storage_file_client.download(file.bucket_path)
174+
files = await storage_file_client.list(file.bucket_folder)
175+
image_info = next((f for f in files if f.get("name") == file.name), None)
176+
177+
assert image == file.file_content
178+
assert image_info.get("metadata", {}).get("mimetype") == file.mime_type
179+
180+
181+
async def test_client_create_signed_url(
182+
storage_file_client: AsyncBucketProxy, file: FileForTesting
183+
) -> None:
184+
"""Ensure we can create a signed url for a file in a bucket"""
185+
await storage_file_client.upload(
186+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
187+
)
130188

131189
sleep(3)
132190

133-
image = await storage_file_client.download(bucket_file_path)
134-
files = await storage_file_client.list(bucket_folder)
135-
image_info = next((f for f in files if f.get("name") == file_name), None)
191+
signed_url = (await storage_file_client.create_signed_url(file.bucket_path, 10))[
192+
"signedURL"
193+
]
194+
195+
async with HttpxClient() as client:
196+
response = await client.get(signed_url)
197+
response.raise_for_status()
198+
199+
assert response.content == file.file_content
200+
201+
202+
async def test_client_get_public_url(
203+
storage_file_client_public: AsyncBucketProxy, file: FileForTesting
204+
) -> None:
205+
"""Ensure we can get the public url of a file in a bucket"""
206+
await storage_file_client_public.upload(
207+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
208+
)
209+
210+
sleep(3)
211+
212+
public_url = await storage_file_client_public.get_public_url(file.bucket_path)
213+
214+
async with HttpxClient() as client:
215+
response = await client.get(public_url)
216+
response.raise_for_status()
136217

137-
assert image == file_content
138-
assert image_info.get("metadata", {}).get("mimetype") == mime_type
218+
assert response.content == file.file_content

0 commit comments

Comments
 (0)