@@ -908,38 +908,45 @@ def is_file(self, path: PathLike) -> bool:
908908 return False
909909 raise
910910
911- def _listdir (self , path : PathLike , * , recursive : bool = False ) -> List [str ]:
911+ def _listdir (self , path : PathLike , * , recursive : bool = False , return_meta : bool = False ) -> List [Union [ str , Dict [ str , Any ]] ]:
912912 """
913- Return the names of files in a directory.
913+ Return the names (or metadata) of files in a directory.
914914
915915 Parameters
916916 ----------
917917 path : Path or str
918918 Path to the folder
919919 recursive : bool, optional
920920 Should folders be listed recursively?
921-
921+ return_meta : bool, optional
922+ If True, return list of dicts with 'path' and 'type'. Otherwise just paths.
922923 """
923924 res = self ._manager ._get (
924925 f'files/fs/{ self ._location } /{ path } ' ,
925926 ).json ()
926927
927928 if recursive :
928- out = []
929- for item in res ['content' ] or []:
930- out .append (item ['path' ])
929+ out : List [Union [str , Dict [str , Any ]]] = []
930+ for item in res .get ('content' ) or []:
931+ if return_meta :
932+ out .append ({'path' : item ['path' ], 'type' : item ['type' ]})
933+ else :
934+ out .append (item ['path' ])
931935 if item ['type' ] == 'directory' :
932- out .extend (self ._listdir (item ['path' ], recursive = recursive ))
936+ out .extend (self ._listdir (item ['path' ], recursive = recursive , return_meta = return_meta ))
933937 return out
934938
935- return [x ['path' ] for x in res ['content' ] or []]
939+ if return_meta :
940+ return [{'path' : x ['path' ], 'type' : x ['type' ]} for x in (res .get ('content' ) or [])]
941+ return [x ['path' ] for x in (res .get ('content' ) or [])]
936942
937943 def listdir (
938944 self ,
939945 path : PathLike = '/' ,
940946 * ,
941947 recursive : bool = False ,
942- ) -> List [str ]:
948+ return_meta : bool = False ,
949+ ) -> List [Union [str , Dict [str , Any ]]]:
943950 """
944951 List the files / folders at the given path.
945952
@@ -948,21 +955,32 @@ def listdir(
948955 path : Path or str, optional
949956 Path to the file location
950957
958+ return_meta : bool, optional
959+ If True, return list of dicts with 'path' and 'type'. Otherwise just paths.
960+
951961 Returns
952962 -------
953- List[str]
963+ List[str] or List[dict]
954964
955965 """
956966 path = re .sub (r'^(\./|/)+' , r'' , str (path ))
957967 path = re .sub (r'/+$' , r'' , path ) + '/'
958968
959- if not self .is_dir (path ):
969+ # Single validation GET (info) rather than is_dir + info later
970+ info = self .info (path )
971+ if info .type != 'directory' :
960972 raise NotADirectoryError (f'path is not a directory: { path } ' )
961973
962- out = self ._listdir (path , recursive = recursive )
974+ out = self ._listdir (path , recursive = recursive , return_meta = return_meta )
963975 if path != '/' :
964976 path_n = len (path .split ('/' )) - 1
965- out = ['/' .join (x .split ('/' )[path_n :]) for x in out ]
977+ if return_meta :
978+ for i in range (len (out )):
979+ if isinstance (out [i ], dict ):
980+ rel = '/' .join (out [i ]['path' ].split ('/' )[path_n :])
981+ out [i ]['path' ] = rel
982+ else :
983+ out = ['/' .join (str (x ).split ('/' )[path_n :]) for x in out ]
966984 return out
967985
968986 def download_file (
@@ -1036,17 +1054,26 @@ def download_folder(
10361054 if local_path is not None and not overwrite and os .path .exists (local_path ):
10371055 raise OSError ('target path already exists; use overwrite=True to replace' )
10381056
1039- if not self .is_dir (path ):
1057+ # Validate directory with single info call
1058+ info = self .info (path )
1059+ if info .type != 'directory' :
10401060 raise NotADirectoryError (f'path is not a directory: { path } ' )
10411061
1042- files = self .listdir (path , recursive = True )
1043- for f in files :
1044- remote_path = os .path .join (path , f )
1045- if self .is_dir (remote_path ):
1062+ entries = self .listdir (path , recursive = True , return_meta = True )
1063+ for entry in entries :
1064+ # Each entry is a dict with path relative to root and type
1065+ if not isinstance (entry , dict ): # defensive: skip unexpected
1066+ continue
1067+ rel_path = entry ['path' ]
1068+ if entry ['type' ] == 'directory' :
1069+ # Ensure local directory exists; no remote call needed
1070+ target_dir = os .path .normpath (os .path .join (local_path , rel_path ))
1071+ os .makedirs (target_dir , exist_ok = True )
10461072 continue
1047- target = os .path .normpath (os .path .join (local_path , f ))
1048- os .makedirs (os .path .dirname (target ), exist_ok = True )
1049- self .download_file (remote_path , target , overwrite = overwrite )
1073+ remote_path = os .path .join (path , rel_path )
1074+ target_file = os .path .normpath (os .path .join (local_path , rel_path ))
1075+ os .makedirs (os .path .dirname (target_file ), exist_ok = True )
1076+ self .download_file (remote_path , target_file , overwrite = overwrite )
10501077
10511078 def remove (self , path : PathLike ) -> None :
10521079 """
0 commit comments