Skip to content

Commit da3e425

Browse files
authored
Transport: unify the behavior of core.ssh_async::glob with core.ssh::glob (#6950)
This commit suggest two changes in core.ssh_async interface: - Catch and raise a better error message if the connection fails to open - Globing of a non-existing path or an unmatched pattern will not raise anymore, instead it will just returns an empty list. This is to respect the same convention of core.ssh behavior
1 parent 81dd4df commit da3e425

File tree

3 files changed

+26
-8
lines changed

3 files changed

+26
-8
lines changed

src/aiida/transports/plugins/async_backend.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,13 +164,16 @@ async def symlink(self, source: str, destination: str):
164164
"""
165165

166166
@abc.abstractmethod
167-
async def glob(self, path: str):
167+
async def glob(self, path: str, ignore_nonexisting: bool = True):
168168
"""Return a list of files and directories matching the glob pattern.
169169
:param path: A path potentially containing the glob pattern
170+
:param ignore_nonexisting: If `True` (default),
171+
return an empty list when path does not exist or no matching files/folders are found.
170172
171173
:return: A list of matching files and directories
172174
173-
:raises OSError: If the path does not exist or no matching files/folders are found.
175+
:raises OSError: If the path does not exist or no matching files/folders are found,
176+
and only if `ignore_nonexisting` is `False`.
174177
"""
175178

176179
@abc.abstractmethod
@@ -326,10 +329,13 @@ async def symlink(self, source: str, destination: str):
326329
"""
327330
await self._sftp.symlink(source, destination)
328331

329-
async def glob(self, path: str):
332+
async def glob(self, path: str, ignore_nonexisting: bool = True):
330333
try:
331334
return await self._sftp.glob(path)
332335
except asyncssh.sftp.SFTPNoSuchFile:
336+
if ignore_nonexisting:
337+
self.logger.debug(f'Glob pattern {path} did not match any files or directories. Ignoring.')
338+
return []
333339
raise OSError(f'Either the remote path {path} does not exist, or a matching file/folder not found.')
334340

335341
async def chmod(self, path: str, mode: int, follow_symlinks: bool = True):
@@ -518,11 +524,14 @@ async def chmod(self, path: str, mode: int, follow_symlinks: bool = True):
518524
if returncode != 0:
519525
raise OSError(f'Failed to change permissions: {path}')
520526

521-
async def glob(self, path: str):
527+
async def glob(self, path: str, ignore_nonexisting: bool = True):
522528
commands = self.ssh_command_generator(f'find {path} -maxdepth 0')
523529
returncode, stdout, stderr = await self.openssh_execute(commands)
524530

525531
if returncode != 0:
532+
if ignore_nonexisting:
533+
self.logger.debug(f'Glob pattern {path} did not match any files or directories. Ignoring.')
534+
return []
526535
raise OSError(f'Either the path {path} does not exist, or a matching file/folder not found.')
527536

528537
return list(stdout.strip().split())

src/aiida/transports/plugins/ssh_async.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ async def open_async(self):
172172
if self.script_before != 'None':
173173
os.system(f'{self.script_before}')
174174

175-
await self.async_backend.open()
175+
try:
176+
await self.async_backend.open()
177+
except OSError as exc:
178+
raise OSError(f'Error while opening the transport: {exc}')
179+
176180
self._is_open = True
177181

178182
return self
@@ -755,7 +759,7 @@ async def compress_async(
755759

756760
for source in remotesources:
757761
if has_magic(source):
758-
copy_list += await self.glob_async(source)
762+
copy_list += await self.glob_async(source, ignore_nonexisting=False)
759763
else:
760764
if not await self.path_exists_async(source):
761765
raise OSError(f'The remote path {source} does not exist')
@@ -1161,20 +1165,21 @@ async def symlink_async(self, remotesource: TransportPath, remotedestination: Tr
11611165
else:
11621166
await self.async_backend.symlink(remotesource, remotedestination)
11631167

1164-
async def glob_async(self, pathname: TransportPath):
1168+
async def glob_async(self, pathname: TransportPath, ignore_nonexisting: bool = True):
11651169
"""Return a list of paths matching a pathname pattern.
11661170
11671171
The pattern may contain simple shell-style wildcards a la fnmatch.
11681172
11691173
:param pathname: the pathname pattern to match.
11701174
It should only be absolute path.
1175+
:param ignore_nonexisting: if True, it will not raise and returns an empty list.
11711176
11721177
:type pathname: :class:`Path <pathlib.Path>`, :class:`PurePosixPath <pathlib.PurePosixPath>`, or `str`
11731178
11741179
:return: a list of paths matching the pattern.
11751180
"""
11761181
pathname = str(pathname)
1177-
return await self.async_backend.glob(pathname)
1182+
return await self.async_backend.glob(pathname, ignore_nonexisting=ignore_nonexisting)
11781183

11791184
async def chmod_async(self, path: TransportPath, mode: int, follow_symlinks: bool = True):
11801185
"""Change the permissions of a file.

tests/transports/test_all_plugins.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,10 @@ def test_glob(custom_transport, tmp_path_local):
15011501
tmp_path_local.joinpath(subpath).write_text('touch')
15021502

15031503
with custom_transport as transport:
1504+
# do not raise an error if the path does not exist
1505+
g_list = transport.glob(str(tmp_path_local) + '/non_existing/*.txt')
1506+
assert g_list == []
1507+
15041508
g_list = transport.glob(str(tmp_path_local) + '/*.txt')
15051509
paths = [str(tmp_path_local.joinpath(item)) for item in ['i.txt', 'j.txt']]
15061510
assert sorted(paths) == sorted(g_list)

0 commit comments

Comments
 (0)