Skip to content

Commit de52a20

Browse files
committed
Properly work with isfile/isdir/islink
* add islink * add symlink (`os.symlink` mimic) * add chmod (`os.chmod` mimic) Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com> (cherry picked from commit 5e9bea3) Signed-off-by: Aleksei Stepanov <penguinolog@gmail.com>
1 parent 7903f23 commit de52a20

File tree

4 files changed

+115
-6
lines changed

4 files changed

+115
-6
lines changed

doc/source/SSHClient.rst

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,31 +337,39 @@ API: SSHClient and SSHAuth.
337337
338338
Open file on remote using SFTP session.
339339

340+
:param path: filesystem object path
340341
:type path: ``str``
342+
:param mode: open file mode ('t' is not supported)
341343
:type mode: ``str``
344+
:return: file.open() stream
345+
:rtype: ``paramiko.SFTPFile``
342346

343347
.. py:method:: exists(path)
344348
345349
Check for file existence using SFTP session.
346350

351+
:param path: filesystem object path
347352
:type path: ``str``
353+
:return: path is valid (object exists)
348354
:rtype: ``bool``
349355

350356
.. py:method:: stat(path)
351357
352358
Get stat info for path with following symlinks.
353359

360+
:param path: filesystem object path
354361
:type path: ``str``
362+
:return: stat like information for remote path
355363
:rtype: ``paramiko.sftp_attr.SFTPAttributes``
356364

357365
.. py:method:: utime(path, times=None):
358366
359367
Set atime, mtime.
360368

361369
:param path: filesystem object path
362-
:type path: str
370+
:type path: ``str``
363371
:param times: (atime, mtime)
364-
:type times: typing.Optional[typing.Tuple[int, int]]
372+
:type times: ``typing.Optional[typing.Tuple[int, int]]``
365373
:rtype: None
366374

367375
.. versionadded:: 1.0.0
@@ -370,16 +378,47 @@ API: SSHClient and SSHAuth.
370378
371379
Check, that path is file using SFTP session.
372380

381+
:param path: remote path to validate
373382
:type path: ``str``
383+
:return: path is file
374384
:rtype: ``bool``
375385

376386
.. py:method:: isdir(path)
377387
378388
Check, that path is directory using SFTP session.
379389

390+
:param path: remote path to validate
391+
:type path: ``str``
392+
:return: path is directory
393+
:rtype: ``bool``
394+
395+
.. py:method:: islink(path)
396+
397+
Check, that path is symlink using SFTP session.
398+
399+
:param path: remote path to validate
380400
:type path: ``str``
401+
:return: path is symlink
381402
:rtype: ``bool``
382403

404+
.. py:method:: symlink(source, dest)
405+
406+
Produce symbolic link like `os.symlink`.
407+
408+
:param source: source path
409+
:type source: ``str``
410+
:param dest: source path
411+
:type dest: ``str``
412+
413+
.. py:method:: chmod(path, mode)
414+
415+
Change the mode (permissions) of a file like `os.chmod`.
416+
417+
:param path: filesystem object path
418+
:type path: ``str``
419+
:param mode: new permissions
420+
:type mode: ``int``
421+
383422
**Non standard methods:**
384423

385424
.. py:method:: mkdir(path)

exec_helpers/_ssh_client_base.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -996,7 +996,7 @@ def isfile(self, path: str) -> bool:
996996
"""
997997
try:
998998
attrs = self._sftp.lstat(path)
999-
return attrs.st_mode & stat.S_IFREG != 0 # type: ignore
999+
return stat.S_ISREG(attrs.st_mode)
10001000
except IOError:
10011001
return False
10021002

@@ -1010,6 +1010,40 @@ def isdir(self, path: str) -> bool:
10101010
"""
10111011
try:
10121012
attrs = self._sftp.lstat(path)
1013-
return attrs.st_mode & stat.S_IFDIR != 0 # type: ignore
1013+
return stat.S_ISDIR(attrs.st_mode)
10141014
except IOError:
10151015
return False
1016+
1017+
def islink(self, path: str) -> bool:
1018+
"""Check, that path is symlink using SFTP session.
1019+
1020+
:param path: remote path to validate
1021+
:type path: str
1022+
:return: path is symlink
1023+
:rtype: bool
1024+
"""
1025+
try:
1026+
attrs = self._sftp.lstat(path)
1027+
return stat.S_ISLNK(attrs.st_mode)
1028+
except IOError:
1029+
return False
1030+
1031+
def symlink(self, source: str, dest: str) -> None:
1032+
"""Produce symbolic link like `os.symlink`.
1033+
1034+
:param source: source path
1035+
:type source: str
1036+
:param dest: source path
1037+
:type dest: str
1038+
"""
1039+
self._sftp.symlink(source, dest) # pragma: no cover
1040+
1041+
def chmod(self, path: str, mode: int) -> None:
1042+
"""Change the mode (permissions) of a file like `os.chmod`.
1043+
1044+
:param path: filesystem object path
1045+
:type path: str
1046+
:param mode: new permissions
1047+
:type mode: int
1048+
"""
1049+
self._sftp.chmod(path, mode) # pragma: no cover

setup.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def get_simple_vars_from_src(src):
155155
str, bytes,
156156
int, float, complex,
157157
list, set, dict, tuple,
158-
None,
158+
None, bool, Ellipsis
159159
]
160160
]
161161
@@ -189,7 +189,10 @@ def get_simple_vars_from_src(src):
189189
>>> get_simple_vars_from_src(multiple_assign)
190190
OrderedDict([('e', 1), ('f', 1), ('g', 1)])
191191
"""
192-
ast_data = (ast.Str, ast.Num, ast.List, ast.Set, ast.Dict, ast.Tuple, ast.Bytes, ast.NameConstant)
192+
if sys.version_info[:2] < (3, 8):
193+
ast_data = (ast.Str, ast.Num, ast.List, ast.Set, ast.Dict, ast.Tuple, ast.Bytes, ast.NameConstant, ast.Ellipsis)
194+
else:
195+
ast_data = ast.Constant
193196

194197
tree = ast.parse(src)
195198

@@ -222,6 +225,7 @@ def get_simple_vars_from_src(src):
222225
"Programming Language :: Python :: 3.5",
223226
"Programming Language :: Python :: 3.6",
224227
"Programming Language :: Python :: 3.7",
228+
"Programming Language :: Python :: 3.8",
225229
"Programming Language :: Python :: Implementation :: CPython",
226230
"Programming Language :: Python :: Implementation :: PyPy",
227231
]

test/test_sftp.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,38 @@ def __init__(self, mode):
158158
self.assertFalse(result)
159159
lstat.assert_called_once_with(dst)
160160

161+
def test_islink(self, client, *args):
162+
class Attrs:
163+
def __init__(self, mode):
164+
self.st_mode = mode
165+
166+
ssh, _sftp = self.prepare_sftp_file_tests(client)
167+
lstat = mock.Mock()
168+
_sftp.attach_mock(lstat, "lstat")
169+
lstat.return_value = Attrs(stat.S_IFLNK)
170+
dst = "/etc/passwd"
171+
172+
# noinspection PyTypeChecker
173+
result = ssh.islink(dst)
174+
self.assertTrue(result)
175+
lstat.assert_called_once_with(dst)
176+
177+
# Negative scenario
178+
lstat.reset_mock()
179+
lstat.return_value = Attrs(stat.S_IFREG)
180+
181+
# noinspection PyTypeChecker
182+
result = ssh.islink(dst)
183+
self.assertFalse(result)
184+
lstat.assert_called_once_with(dst)
185+
186+
lstat.reset_mock()
187+
lstat.side_effect = IOError
188+
# noinspection PyTypeChecker
189+
result = ssh.islink(dst)
190+
self.assertFalse(result)
191+
lstat.assert_called_once_with(dst)
192+
161193
@mock.patch("exec_helpers.ssh_client.SSHClient.exists")
162194
@mock.patch("exec_helpers.ssh_client.SSHClient.execute")
163195
def test_mkdir(self, execute, exists, *args):

0 commit comments

Comments
 (0)