1
1
from __future__ import annotations
2
2
3
3
from collections .abc import Awaitable
4
- from typing import Any , Literal , Optional , Union
4
+ from dataclasses import dataclass
5
+ from datetime import datetime
6
+ from typing import Any , Literal , Optional , Type , Union
5
7
6
8
from httpx import AsyncClient , Client
7
9
8
- RequestMethod = Literal ["GET" , "POST" , "PUT" , "DELETE" , "PATCH" ]
9
- # dict is returned when the request was synchronous, awaitable when async, None if there was an error
10
- _SyncOrAsyncResponse = Union [dict [str , Any ], Awaitable , None ]
10
+ __all__ = ["Bucket" , "StorageBucketAPI" ]
11
+
12
+ _RequestMethod = Literal ["GET" , "POST" , "PUT" , "DELETE" , "PATCH" ]
13
+
14
+
15
+ @dataclass
16
+ class Bucket :
17
+ id : str
18
+ name : str
19
+ owner : str
20
+ public : bool
21
+ created_at : datetime
22
+ updated_at : datetime
23
+
24
+ def __post_init__ (self ) -> None :
25
+ # created_at and updated_at are returned by the API as ISO timestamps
26
+ # so we convert them to datetime objects
27
+ self .created_at = datetime .fromisoformat (self .created_at ) # type: ignore
28
+ self .updated_at = datetime .fromisoformat (self .updated_at ) # type: ignore
29
+
30
+
31
+ ResponseType = Union [
32
+ dict [
33
+ str , str
34
+ ], # response from an endpoint without a custom response_class, example: create_bucket
35
+ list [
36
+ Bucket
37
+ ], # response from an endpoint which returns a list of objects, example: list_buckets
38
+ Bucket , # response from an endpoint which returns a single object, example: get_bucket
39
+ None ,
40
+ ]
11
41
12
42
13
43
class StorageBucketAPI :
@@ -27,50 +57,82 @@ def __init__(
27
57
self ._client = Client (headers = self .headers )
28
58
29
59
def _request (
30
- self , method : RequestMethod , url : str , json : Optional [dict [Any , Any ]] = None
31
- ) -> Union [dict [Any , Any ], Awaitable , None ]:
60
+ self ,
61
+ method : _RequestMethod ,
62
+ url : str ,
63
+ json : Optional [dict [Any , Any ]] = None ,
64
+ response_class : Optional [Type ] = None ,
65
+ ) -> Any :
32
66
if self ._is_async :
33
- return self ._async_request (method , url , json )
67
+ return self ._async_request (method , url , json , response_class )
34
68
else :
35
- return self ._sync_request (method , url , json )
69
+ return self ._sync_request (method , url , json , response_class )
36
70
37
71
def _sync_request (
38
- self , method : RequestMethod , url : str , json : Optional [dict [Any , Any ]] = None
39
- ) -> Optional [dict [Any , Any ]]:
72
+ self ,
73
+ method : _RequestMethod ,
74
+ url : str ,
75
+ json : Optional [dict [Any , Any ]] = None ,
76
+ response_class : Optional [Type ] = None ,
77
+ ) -> ResponseType :
40
78
if isinstance (self ._client , AsyncClient ): # only to appease the type checker
41
79
return
42
80
43
81
response = self ._client .request (method , url , json = json )
44
82
response .raise_for_status ()
45
- return response .json ()
83
+
84
+ response_data = response .json ()
85
+
86
+ if not response_class :
87
+ # if no response_class is specified, return the raw response
88
+ return response_data
89
+
90
+ if isinstance (response_data , list ):
91
+ # if a list of objects are returned, convert each member to response_class
92
+ return [response_class (** item ) for item in response_data ]
93
+ else :
94
+ return response_class (** response_data )
46
95
47
96
async def _async_request (
48
- self , method : RequestMethod , url : str , json : Optional [dict [Any , Any ]] = None
49
- ) -> Optional [dict [Any , Any ]]:
97
+ self ,
98
+ method : _RequestMethod ,
99
+ url : str ,
100
+ json : Optional [dict [Any , Any ]] = None ,
101
+ response_class : Optional [Type ] = None ,
102
+ ) -> ResponseType :
50
103
if isinstance (self ._client , Client ): # only to appease the type checker
51
104
return
52
105
53
106
response = await self ._client .request (method , url , json = json )
54
107
response .raise_for_status ()
55
- return response .json ()
56
108
57
- def list_buckets (self ) -> _SyncOrAsyncResponse :
109
+ response_data = response .json ()
110
+
111
+ if not response_class :
112
+ return response_data
113
+
114
+ if isinstance (response_data , list ):
115
+ return [response_class (** item ) for item in response_data ]
116
+ else :
117
+ return response_class (** response_data )
118
+
119
+ def list_buckets (self ) -> Union [list [Bucket ], Awaitable [list [Bucket ]], None ]:
58
120
"""Retrieves the details of all storage buckets within an existing product."""
59
- return self ._request ("GET" , f"{ self .url } /bucket" )
121
+ return self ._request ("GET" , f"{ self .url } /bucket" , response_class = Bucket )
60
122
61
- def get_bucket (self , id : str ) -> _SyncOrAsyncResponse :
123
+ def get_bucket (self , id : str ) -> Union [ Bucket , Awaitable [ Bucket ], None ] :
62
124
"""Retrieves the details of an existing storage bucket.
63
125
64
126
Parameters
65
127
----------
66
128
id
67
129
The unique identifier of the bucket you would like to retrieve.
68
130
"""
69
- return self ._request ("GET" , f"{ self .url } /bucket/{ id } " )
131
+ return self ._request ("GET" , f"{ self .url } /bucket/{ id } " , response_class = Bucket )
70
132
71
133
def create_bucket (
72
134
self , id : str , name : str = None , public : bool = False
73
- ) -> _SyncOrAsyncResponse :
135
+ ) -> Union [ dict [ str , str ], Awaitable [ dict [ str , str ]]] :
74
136
"""Creates a new storage bucket.
75
137
76
138
Parameters
@@ -88,7 +150,7 @@ def create_bucket(
88
150
json = {"id" : id , "name" : name , "public" : public },
89
151
)
90
152
91
- def empty_bucket (self , id : str ) -> _SyncOrAsyncResponse :
153
+ def empty_bucket (self , id : str ) -> Union [ dict [ str , str ], Awaitable [ dict [ str , str ]]] :
92
154
"""Removes all objects inside a single bucket.
93
155
94
156
Parameters
@@ -98,7 +160,9 @@ def empty_bucket(self, id: str) -> _SyncOrAsyncResponse:
98
160
"""
99
161
return self ._request ("POST" , f"{ self .url } /bucket/{ id } /empty" , json = {})
100
162
101
- def delete_bucket (self , id : str ) -> _SyncOrAsyncResponse :
163
+ def delete_bucket (
164
+ self , id : str
165
+ ) -> Union [dict [str , str ], Awaitable [dict [str , str ]]]:
102
166
"""Deletes an existing bucket. Note that you cannot delete buckets with existing objects inside. You must first
103
167
`empty()` the bucket.
104
168
0 commit comments