Skip to content

Commit 6b7dcc4

Browse files
committed
optimize management api calls
1 parent 4696f44 commit 6b7dcc4

File tree

1 file changed

+48
-21
lines changed

1 file changed

+48
-21
lines changed

singlestoredb/management/files.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)