Skip to content

Commit ce4381a

Browse files
silentworkso-santi
andauthored
fix(storage): add upsert option for signed bucket (#1283)
Co-authored-by: Leonardo Santiago <[email protected]>
1 parent 459f1e8 commit ce4381a

File tree

3 files changed

+55
-19
lines changed

3 files changed

+55
-19
lines changed

src/storage/src/storage3/_async/file_api.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ..exceptions import StorageApiError
1616
from ..types import (
1717
BaseBucket,
18+
CreateSignedUploadUrlOptions,
1819
CreateSignedUrlResponse,
1920
CreateSignedURLsOptions,
2021
DownloadOptions,
@@ -28,6 +29,7 @@
2829
TransformOptions,
2930
UploadData,
3031
UploadResponse,
32+
UploadSignedUrlFileOptions,
3133
URLOptions,
3234
transform_to_dict,
3335
)
@@ -84,18 +86,28 @@ async def _request(
8486

8587
return response
8688

87-
async def create_signed_upload_url(self, path: str) -> SignedUploadURL:
89+
async def create_signed_upload_url(
90+
self,
91+
path: str,
92+
options: Optional[CreateSignedUploadUrlOptions] = None,
93+
) -> SignedUploadURL:
8894
"""
8995
Creates a signed upload URL.
9096
9197
Parameters
9298
----------
9399
path
94100
The file path, including the file name. For example `folder/image.png`.
101+
options
102+
Additional options for the upload url creation.
95103
"""
104+
headers: dict[str, str] = dict()
105+
if options is not None and options.upsert:
106+
headers.update({"x-upsert": options.upsert})
107+
96108
path_parts = relative_path_to_parts(path)
97109
response = await self._request(
98-
"POST", ["object", "upload", "sign", self.id, *path_parts]
110+
"POST", ["object", "upload", "sign", self.id, *path_parts], headers=headers
99111
)
100112
data = response.json()
101113
full_url: urllib.parse.ParseResult = urllib.parse.urlparse(
@@ -116,7 +128,7 @@ async def upload_to_signed_url(
116128
path: str,
117129
token: str,
118130
file: Union[BufferedReader, bytes, FileIO, str, Path],
119-
file_options: Optional[FileOptions] = None,
131+
file_options: Optional[UploadSignedUrlFileOptions] = None,
120132
) -> UploadResponse:
121133
"""
122134
Upload a file with a token generated from :meth:`.create_signed_url`
@@ -137,20 +149,18 @@ async def upload_to_signed_url(
137149

138150
final_url = ["object", "upload", "sign", self.id, *path_parts]
139151

140-
if file_options is None:
141-
file_options = {}
142-
143-
cache_control = file_options.get("cache-control")
152+
options: UploadSignedUrlFileOptions = file_options or {}
153+
cache_control = options.get("cache-control")
144154
# cacheControl is also passed as form data
145155
# https://github.com/supabase/storage-js/blob/fa44be8156295ba6320ffeff96bdf91016536a46/src/packages/StorageFileApi.ts#L89
146156
_data = {}
147157
if cache_control:
148-
file_options["cache-control"] = f"max-age={cache_control}"
158+
options["cache-control"] = f"max-age={cache_control}"
149159
_data = {"cacheControl": cache_control}
150160
headers = {
151161
**self._client.headers,
152162
**DEFAULT_FILE_OPTIONS,
153-
**file_options,
163+
**options,
154164
}
155165
filename = path_parts[-1]
156166

src/storage/src/storage3/_sync/file_api.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ..exceptions import StorageApiError
1616
from ..types import (
1717
BaseBucket,
18+
CreateSignedUploadUrlOptions,
1819
CreateSignedUrlResponse,
1920
CreateSignedURLsOptions,
2021
DownloadOptions,
@@ -28,6 +29,7 @@
2829
TransformOptions,
2930
UploadData,
3031
UploadResponse,
32+
UploadSignedUrlFileOptions,
3133
URLOptions,
3234
transform_to_dict,
3335
)
@@ -84,18 +86,28 @@ def _request(
8486

8587
return response
8688

87-
def create_signed_upload_url(self, path: str) -> SignedUploadURL:
89+
def create_signed_upload_url(
90+
self,
91+
path: str,
92+
options: Optional[CreateSignedUploadUrlOptions] = None,
93+
) -> SignedUploadURL:
8894
"""
8995
Creates a signed upload URL.
9096
9197
Parameters
9298
----------
9399
path
94100
The file path, including the file name. For example `folder/image.png`.
101+
options
102+
Additional options for the upload url creation.
95103
"""
104+
headers: dict[str, str] = dict()
105+
if options is not None and options.upsert:
106+
headers.update({"x-upsert": options.upsert})
107+
96108
path_parts = relative_path_to_parts(path)
97109
response = self._request(
98-
"POST", ["object", "upload", "sign", self.id, *path_parts]
110+
"POST", ["object", "upload", "sign", self.id, *path_parts], headers=headers
99111
)
100112
data = response.json()
101113
full_url: urllib.parse.ParseResult = urllib.parse.urlparse(
@@ -116,7 +128,7 @@ def upload_to_signed_url(
116128
path: str,
117129
token: str,
118130
file: Union[BufferedReader, bytes, FileIO, str, Path],
119-
file_options: Optional[FileOptions] = None,
131+
file_options: Optional[UploadSignedUrlFileOptions] = None,
120132
) -> UploadResponse:
121133
"""
122134
Upload a file with a token generated from :meth:`.create_signed_url`
@@ -137,20 +149,18 @@ def upload_to_signed_url(
137149

138150
final_url = ["object", "upload", "sign", self.id, *path_parts]
139151

140-
if file_options is None:
141-
file_options = {}
142-
143-
cache_control = file_options.get("cache-control")
152+
options: UploadSignedUrlFileOptions = file_options or {}
153+
cache_control = options.get("cache-control")
144154
# cacheControl is also passed as form data
145155
# https://github.com/supabase/storage-js/blob/fa44be8156295ba6320ffeff96bdf91016536a46/src/packages/StorageFileApi.ts#L89
146156
_data = {}
147157
if cache_control:
148-
file_options["cache-control"] = f"max-age={cache_control}"
158+
options["cache-control"] = f"max-age={cache_control}"
149159
_data = {"cacheControl": cache_control}
150160
headers = {
151161
**self._client.headers,
152162
**DEFAULT_FILE_OPTIONS,
153-
**file_options,
163+
**options,
154164
}
155165
filename = path_parts[-1]
156166

src/storage/src/storage3/types.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from datetime import datetime
55
from typing import Any, Dict, Literal, Optional, TypedDict, Union
66

7-
from pydantic import BaseModel, ConfigDict, TypeAdapter
7+
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
88
from typing_extensions import ReadOnly
99

1010
RequestMethod = Literal["GET", "POST", "DELETE", "PUT", "HEAD"]
@@ -135,3 +135,19 @@ class SignedUrlsJsonItem(BaseModel):
135135

136136

137137
SignedUrlsJsonResponse = TypeAdapter(list[SignedUrlsJsonItem])
138+
139+
140+
class CreateSignedUploadUrlOptions(BaseModel):
141+
upsert: str
142+
143+
144+
UploadSignedUrlFileOptions = TypedDict(
145+
"UploadSignedUrlFileOptions",
146+
{
147+
"cache-control": str,
148+
"content-type": str,
149+
"metadata": Dict[str, Any],
150+
"headers": Dict[str, str],
151+
},
152+
total=False,
153+
)

0 commit comments

Comments
 (0)