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

Commit c8cdf44

Browse files
authored
fix: signed_url
fix: signed_url
2 parents ca95502 + 68026be commit c8cdf44

File tree

7 files changed

+216
-55
lines changed

7 files changed

+216
-55
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/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ async def storage() -> AsyncStorageClient:
3232
"Authorization": f"Bearer {key}",
3333
},
3434
) as client:
35+
client.session.timeout = None
3536
yield client

tests/_async/test_client.py

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

3-
from time import sleep
3+
from dataclasses import dataclass
44
from typing import TYPE_CHECKING
55
from uuid import uuid4
66

@@ -10,6 +10,7 @@
1010
from storage3.utils import StorageException
1111

1212
from .. import AsyncBucketProxy
13+
from ..utils import AsyncClient as HttpxClient
1314
from ..utils import AsyncFinalizerFactory
1415

1516
if TYPE_CHECKING:
@@ -52,6 +53,12 @@ async def afinalizer():
5253
request.addfinalizer(AsyncFinalizerFactory(afinalizer).finalizer)
5354

5455

56+
async def bucket_factory(
57+
storage: AsyncStorageClient, uuid_factory: Callable[[], str], public: bool
58+
) -> str:
59+
"""Creates a test bucket which will be used in the whole storage tests run and deleted at the end"""
60+
61+
5562
@pytest.fixture(scope="module")
5663
async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) -> str:
5764
"""Creates a test bucket which will be used in the whole storage tests run and deleted at the end"""
@@ -71,14 +78,53 @@ async def bucket(storage: AsyncStorageClient, uuid_factory: Callable[[], str]) -
7178
temp_test_buckets_ids.remove(bucket_id)
7279

7380

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

79107

108+
@pytest.fixture(scope="module")
109+
def storage_file_client_public(
110+
storage: AsyncStorageClient, public_bucket: str
111+
) -> AsyncBucketProxy:
112+
"""Creates the storage file client for the whole storage tests run"""
113+
yield storage.from_(public_bucket)
114+
115+
116+
@dataclass
117+
class FileForTesting:
118+
name: str
119+
local_path: str
120+
bucket_folder: str
121+
bucket_path: str
122+
mime_type: str
123+
file_content: bytes
124+
125+
80126
@pytest.fixture
81-
def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> dict[str, str]:
127+
def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> FileForTesting:
82128
"""Creates a different test file (same content but different path) for each test"""
83129
file_name = "test_image.svg"
84130
file_content = (
@@ -100,39 +146,66 @@ def file(tmp_path: Path, uuid_factory: Callable[[], str]) -> dict[str, str]:
100146
with open(file_path, "wb") as f:
101147
f.write(file_content)
102148

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-
}
149+
return FileForTesting(
150+
name=file_name,
151+
local_path=str(file_path),
152+
bucket_folder=bucket_folder,
153+
bucket_path=bucket_path,
154+
mime_type="image/svg+xml",
155+
file_content=file_content,
156+
)
111157

112158

113159
# TODO: Test create_bucket, delete_bucket, empty_bucket, list_buckets, fileAPI.list before upload test
114160

115161

116-
async def test_client_upload_file(
117-
storage_file_client: AsyncBucketProxy, file: dict[str, str]
162+
async def test_client_upload(
163+
storage_file_client: AsyncBucketProxy, file: FileForTesting
118164
) -> None:
119165
"""Ensure we can upload files to a bucket"""
166+
await storage_file_client.upload(
167+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
168+
)
169+
170+
image = await storage_file_client.download(file.bucket_path)
171+
files = await storage_file_client.list(file.bucket_folder)
172+
image_info = next((f for f in files if f.get("name") == file.name), None)
173+
174+
assert image == file.file_content
175+
assert image_info.get("metadata", {}).get("mimetype") == file.mime_type
120176

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}
128177

129-
await storage_file_client.upload(bucket_file_path, file_path, options)
178+
async def test_client_create_signed_url(
179+
storage_file_client: AsyncBucketProxy, file: FileForTesting
180+
) -> None:
181+
"""Ensure we can create a signed url for a file in a bucket"""
182+
await storage_file_client.upload(
183+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
184+
)
185+
186+
signed_url = (await storage_file_client.create_signed_url(file.bucket_path, 10))[
187+
"signedURL"
188+
]
189+
190+
async with HttpxClient() as client:
191+
response = await client.get(signed_url)
192+
response.raise_for_status()
193+
194+
assert response.content == file.file_content
195+
196+
197+
async def test_client_get_public_url(
198+
storage_file_client_public: AsyncBucketProxy, file: FileForTesting
199+
) -> None:
200+
"""Ensure we can get the public url of a file in a bucket"""
201+
await storage_file_client_public.upload(
202+
file.bucket_path, file.local_path, {"content-type": file.mime_type}
203+
)
130204

131-
sleep(3)
205+
public_url = await storage_file_client_public.get_public_url(file.bucket_path)
132206

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)
207+
async with HttpxClient(timeout=None) as client:
208+
response = await client.get(public_url)
209+
response.raise_for_status()
136210

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

tests/_sync/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ def storage() -> SyncStorageClient:
3232
"Authorization": f"Bearer {key}",
3333
},
3434
) as client:
35+
client.session.timeout = None
3536
yield client

0 commit comments

Comments
 (0)