22import os
33from collections .abc import AsyncIterator
44
5- from fastapi import HTTPException , Response
5+ from fastapi import HTTPException , Response , status
66from fastapi .responses import StreamingResponse
7+ from opentelemetry import trace
78
89from boxdrive .schemas import BaseListObjectsInfo
910
1011from . import constants , exceptions
1112from .schemas import BucketName , ContentType , Key , MaxKeys , xml
1213from .store import ObjectStore
1314
15+ tracer = trace .get_tracer (__name__ )
1416logger = logging .getLogger (__name__ )
1517
1618
1719class S3 :
1820 def __init__ (self , store : ObjectStore ):
1921 self .store = store
2022
23+ @tracer .start_as_current_span ("list_buckets" )
2124 async def list_buckets (self ) -> xml .ListAllMyBucketsResult :
2225 buckets = await self .store .list_buckets ()
2326 buckets_xml = [
@@ -27,57 +30,66 @@ async def list_buckets(self) -> xml.ListAllMyBucketsResult:
2730 buckets_model = xml .Buckets (buckets = buckets_xml )
2831 return xml .ListAllMyBucketsResult (owner = owner , buckets = buckets_model )
2932
30- async def list_objects_v2 (
33+ @tracer .start_as_current_span ("list_objects" )
34+ async def list_objects (
3135 self ,
3236 bucket : BucketName ,
33- prefix : Key | None = None ,
37+ prefix : str | None = None ,
3438 delimiter : str | None = None ,
3539 max_keys : MaxKeys = constants .MAX_KEYS ,
36- continuation_token : Key | None = None ,
37- start_after : Key | None = None ,
40+ marker : Key | None = None ,
41+ encoding_type : str | None = None ,
3842 ) -> xml .ListBucketResult :
39- objects_info = await self .store .list_objects_v2 (
40- bucket ,
41- prefix = prefix ,
42- delimiter = delimiter ,
43- max_keys = max_keys ,
44- continuation_token = continuation_token ,
45- start_after = start_after ,
43+ objects_info = await self .store .list_objects (
44+ bucket , prefix = prefix , delimiter = delimiter , max_keys = max_keys , marker = marker , encoding_type = encoding_type
4645 )
4746 return self ._build_list_bucket_result (
4847 bucket ,
49- objects_info ,
48+ next_marker = objects_info .next_marker ,
49+ objects_info = objects_info ,
5050 prefix = prefix ,
5151 delimiter = delimiter ,
5252 max_keys = max_keys ,
5353 )
5454
55- async def list_objects (
55+ @tracer .start_as_current_span ("list_objects_v2" )
56+ async def list_objects_v2 (
5657 self ,
5758 bucket : BucketName ,
58- prefix : Key | None = None ,
59+ prefix : str | None = None ,
5960 delimiter : str | None = None ,
6061 max_keys : MaxKeys = constants .MAX_KEYS ,
61- marker : Key | None = None ,
62+ continuation_token : Key | None = None ,
63+ start_after : Key | None = None ,
64+ encoding_type : str | None = None ,
6265 ) -> xml .ListBucketResult :
63- objects_info = await self .store .list_objects (
64- bucket , prefix = prefix , delimiter = delimiter , max_keys = max_keys , marker = marker
66+ objects_info = await self .store .list_objects_v2 (
67+ bucket ,
68+ prefix = prefix ,
69+ delimiter = delimiter ,
70+ max_keys = max_keys ,
71+ continuation_token = continuation_token ,
72+ start_after = start_after ,
73+ encoding_type = encoding_type ,
6574 )
6675 return self ._build_list_bucket_result (
6776 bucket ,
68- objects_info ,
77+ objects_info = objects_info ,
6978 prefix = prefix ,
7079 delimiter = delimiter ,
7180 max_keys = max_keys ,
7281 )
7382
83+ # TODO: exclude None NextMarker from response
7484 def _build_list_bucket_result (
7585 self ,
7686 bucket : BucketName ,
87+ * ,
7788 objects_info : BaseListObjectsInfo ,
78- prefix : Key | None = None ,
89+ prefix : str | None = None ,
7990 delimiter : str | None = None ,
8091 max_keys : MaxKeys = constants .MAX_KEYS ,
92+ next_marker : str = "" ,
8193 ) -> xml .ListBucketResult :
8294 objects : list [xml .Content ] = []
8395 for obj in objects_info .objects :
@@ -99,10 +111,12 @@ def _build_list_bucket_result(
99111 key_count = len (objects ) + len (objects_info .common_prefixes ),
100112 is_truncated = objects_info .is_truncated ,
101113 delimiter = delimiter ,
114+ next_marker = next_marker or None ,
102115 contents = objects ,
103116 common_prefixes = [xml .CommonPrefix (prefix = prefix ) for prefix in objects_info .common_prefixes ],
104117 )
105118
119+ @tracer .start_as_current_span ("get_object" )
106120 async def get_object (
107121 self ,
108122 bucket : BucketName ,
@@ -153,10 +167,11 @@ async def generate() -> AsyncIterator[bytes]:
153167 status_code = status_code ,
154168 )
155169
170+ @tracer .start_as_current_span ("head_object" )
156171 async def head_object (self , bucket : BucketName , key : Key ) -> Response :
157172 metadata = await self .store .head_object (bucket , key )
158173 return Response (
159- status_code = 200 ,
174+ status_code = status . HTTP_200_OK ,
160175 headers = {
161176 "Content-Length" : str (metadata .size ),
162177 "ETag" : f'"{ metadata .etag } "' ,
@@ -166,6 +181,7 @@ async def head_object(self, bucket: BucketName, key: Key) -> Response:
166181 },
167182 )
168183
184+ @tracer .start_as_current_span ("put_object" )
169185 async def put_object (
170186 self ,
171187 bucket : BucketName ,
@@ -175,27 +191,32 @@ async def put_object(
175191 ) -> Response :
176192 final_content_type = content_type or constants .DEFAULT_CONTENT_TYPE
177193 result_etag = await self .store .put_object (bucket , key , content , final_content_type )
178- return Response (status_code = 200 , headers = {"ETag" : f'"{ result_etag } "' , "Content-Length" : "0" })
194+ return Response (status_code = status . HTTP_200_OK , headers = {"ETag" : f'"{ result_etag } "' })
179195
180- async def delete_object (self , bucket : BucketName , key : Key ) -> None :
196+ @tracer .start_as_current_span ("delete_object" )
197+ async def delete_object (self , bucket : BucketName , key : Key ) -> Response :
181198 try :
182199 await self .store .delete_object (bucket , key )
183200 except exceptions .NoSuchBucket :
184201 logger .info ("Bucket %s not found" , bucket )
185202 except exceptions .NoSuchKey :
186203 logger .info ("Object %s not found in bucket %s" , key , bucket )
187- return None
204+ return Response (
205+ status_code = status .HTTP_204_NO_CONTENT ,
206+ headers = {
207+ "content-length" : "0" ,
208+ },
209+ )
188210
211+ @tracer .start_as_current_span ("create_bucket" )
189212 async def create_bucket (self , bucket : BucketName ) -> Response :
190- try :
191- await self .store .create_bucket (bucket )
192- except exceptions .BucketAlreadyExists :
193- raise HTTPException (status_code = 409 , detail = "Bucket already exists" )
194- return Response (status_code = 200 , headers = {"Location" : f"/{ bucket } " })
213+ await self .store .create_bucket (bucket )
214+ return Response (status_code = status .HTTP_200_OK , headers = {"Location" : f"/{ bucket } " })
195215
216+ @tracer .start_as_current_span ("delete_bucket" )
196217 async def delete_bucket (self , bucket : BucketName ) -> Response :
197218 try :
198219 await self .store .delete_bucket (bucket )
199220 except exceptions .NoSuchBucket :
200221 logger .info ("Bucket %s not found" , bucket )
201- return Response (status_code = 204 )
222+ return Response (status_code = status . HTTP_204_NO_CONTENT )
0 commit comments