@@ -174,6 +174,7 @@ def _head_bucket(self, bucket, refresh: bool = False) -> Optional[S3Object]:
174174 bucket = bucket ,
175175 key = None ,
176176 version_id = None ,
177+ delimiter = None ,
177178 )
178179 self .dircache [bucket ] = file
179180 else :
@@ -207,6 +208,7 @@ def _head_object(
207208 bucket = bucket ,
208209 key = key ,
209210 version_id = version_id ,
211+ delimiter = None ,
210212 )
211213 self .dircache [path ] = file
212214 else :
@@ -234,6 +236,7 @@ def _ls_buckets(self, refresh: bool = False) -> List[S3Object]:
234236 bucket = b ["Name" ],
235237 key = None ,
236238 version_id = None ,
239+ delimiter = None ,
237240 )
238241 for b in response ["Buckets" ]
239242 ]
@@ -254,58 +257,63 @@ def _ls_dirs(
254257 bucket , key , version_id = self .parse_path (path )
255258 if key :
256259 prefix = f"{ key } /{ prefix if prefix else '' } "
257- if path not in self .dircache or refresh :
258- files : List [S3Object ] = []
259- while True :
260- request : Dict [Any , Any ] = {
261- "Bucket" : bucket ,
262- "Prefix" : prefix ,
263- "Delimiter" : delimiter ,
264- }
265- if next_token :
266- request .update ({"ContinuationToken" : next_token })
267- if max_keys :
268- request .update ({"MaxKeys" : max_keys })
269- response = self ._call (
270- self ._client .list_objects_v2 ,
271- ** request ,
272- )
273- files .extend (
274- S3Object (
275- init = {
276- "ContentLength" : 0 ,
277- "ContentType" : None ,
278- "StorageClass" : S3StorageClass .S3_STORAGE_CLASS_DIRECTORY ,
279- "ETag" : None ,
280- "LastModified" : None ,
281- },
282- type = S3ObjectType .S3_OBJECT_TYPE_DIRECTORY ,
283- bucket = bucket ,
284- key = c ["Prefix" ][:- 1 ].rstrip ("/" ),
285- version_id = version_id ,
286- )
287- for c in response .get ("CommonPrefixes" , [])
288- )
289- files .extend (
290- S3Object (
291- init = c ,
292- type = S3ObjectType .S3_OBJECT_TYPE_FILE ,
293- bucket = bucket ,
294- key = c ["Key" ],
295- )
296- for c in response .get ("Contents" , [])
297- )
298- next_token = response .get ("NextContinuationToken" )
299- if not next_token :
300- break
301- if files :
302- self .dircache [path ] = files
303- else :
260+
261+ if path in self .dircache and not refresh :
304262 cache = self .dircache [path ]
305263 if not isinstance (cache , list ):
306- files = [cache ]
264+ caches = [cache ]
307265 else :
308- files = cache
266+ caches = cache
267+ if all ([f .delimiter == delimiter for f in caches ]):
268+ return caches
269+
270+ files : List [S3Object ] = []
271+ while True :
272+ request : Dict [Any , Any ] = {
273+ "Bucket" : bucket ,
274+ "Prefix" : prefix ,
275+ "Delimiter" : delimiter ,
276+ }
277+ if next_token :
278+ request .update ({"ContinuationToken" : next_token })
279+ if max_keys :
280+ request .update ({"MaxKeys" : max_keys })
281+ response = self ._call (
282+ self ._client .list_objects_v2 ,
283+ ** request ,
284+ )
285+ files .extend (
286+ S3Object (
287+ init = {
288+ "ContentLength" : 0 ,
289+ "ContentType" : None ,
290+ "StorageClass" : S3StorageClass .S3_STORAGE_CLASS_DIRECTORY ,
291+ "ETag" : None ,
292+ "LastModified" : None ,
293+ },
294+ type = S3ObjectType .S3_OBJECT_TYPE_DIRECTORY ,
295+ bucket = bucket ,
296+ key = c ["Prefix" ][:- 1 ].rstrip ("/" ),
297+ version_id = version_id ,
298+ delimiter = delimiter ,
299+ )
300+ for c in response .get ("CommonPrefixes" , [])
301+ )
302+ files .extend (
303+ S3Object (
304+ init = c ,
305+ type = S3ObjectType .S3_OBJECT_TYPE_FILE ,
306+ bucket = bucket ,
307+ key = c ["Key" ],
308+ delimiter = delimiter ,
309+ )
310+ for c in response .get ("Contents" , [])
311+ )
312+ next_token = response .get ("NextContinuationToken" )
313+ if not next_token :
314+ break
315+ if files :
316+ self .dircache [path ] = files
309317 return files
310318
311319 def ls (
@@ -340,6 +348,7 @@ def info(self, path: str, **kwargs) -> S3Object:
340348 bucket = bucket ,
341349 key = None ,
342350 version_id = None ,
351+ delimiter = None ,
343352 )
344353 if not refresh :
345354 caches : Union [List [S3Object ], S3Object ] = self ._ls_from_cache (path )
@@ -366,6 +375,7 @@ def info(self, path: str, **kwargs) -> S3Object:
366375 bucket = bucket ,
367376 key = key .rstrip ("/" ) if key else None ,
368377 version_id = version_id ,
378+ delimiter = None ,
369379 )
370380 if key :
371381 object_info = self ._head_object (path , refresh = refresh , version_id = version_id )
@@ -402,31 +412,50 @@ def info(self, path: str, **kwargs) -> S3Object:
402412 bucket = bucket ,
403413 key = key .rstrip ("/" ) if key else None ,
404414 version_id = version_id ,
415+ delimiter = None ,
405416 )
406417 else :
407418 raise FileNotFoundError (path )
408419
409- def find (
420+ def _find (
410421 self ,
411422 path : str ,
412423 maxdepth : Optional [int ] = None ,
413424 withdirs : Optional [bool ] = None ,
414- detail : bool = False ,
415425 ** kwargs ,
416- ) -> Union [Dict [str , S3Object ], List [str ]]:
417- # TODO: Support maxdepth and withdirs
426+ ) -> List [S3Object ]:
418427 path = self ._strip_protocol (path )
419428 if path in ["" , "/" ]:
420429 raise ValueError ("Cannot traverse all files in S3." )
421430 bucket , key , _ = self .parse_path (path )
422431 prefix = kwargs .pop ("prefix" , "" )
432+ if maxdepth :
433+ return super ().find (
434+ path = path ,
435+ maxdepth = maxdepth ,
436+ withdirs = withdirs ,
437+ detail = True ,
438+ ** kwargs
439+ ).values ()
423440
424441 files = self ._ls_dirs (path , prefix = prefix , delimiter = "" )
425442 if not files and key :
426443 try :
427444 files = [self .info (path )]
428445 except FileNotFoundError :
429446 files = []
447+ return files
448+
449+ def find (
450+ self ,
451+ path : str ,
452+ maxdepth : Optional [int ] = None ,
453+ withdirs : Optional [bool ] = None ,
454+ detail : bool = False ,
455+ ** kwargs ,
456+ ) -> Union [Dict [str , S3Object ], List [str ]]:
457+ # TODO: Support withdirs
458+ files = self ._find (path = path , maxdepth = maxdepth , withdirs = withdirs , ** kwargs )
430459 if detail :
431460 return {f .name : f for f in files }
432461 else :
0 commit comments