Skip to content

Commit 53d63a3

Browse files
author
Pan
committed
Added sftp handle error exception. Updated sftp handle implementation.
Added tests for sftp open dir and file, readdir.
1 parent 7979d48 commit 53d63a3

File tree

5 files changed

+173
-10
lines changed

5 files changed

+173
-10
lines changed

ssh/exceptions.pyx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,7 @@ class UnknownChannelType(BaseSSHError):
153153

154154
class ResourceShortage(BaseSSHError):
155155
"""Raised on resource shortage errors"""
156+
157+
158+
class SFTPHandleError(BaseSSHError):
159+
"""Raised on errors SFTP handle errors"""

ssh/sftp.pyx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ from sftp_handles cimport SFTPFile, SFTPDir
2121
from sftp_attributes cimport SFTPAttributes
2222
from sftp_statvfs cimport SFTPStatVFS
2323
from utils cimport handle_ssh_error_codes, to_bytes
24+
from .exceptions import SFTPHandleError
2425

2526
cimport c_sftp
2627
from c_ssh cimport ssh_get_error_code, timeval
@@ -97,9 +98,12 @@ cdef class SFTP:
9798
with nogil:
9899
c_dir = c_sftp.sftp_opendir(self._sftp, c_path)
99100
if c_dir is NULL:
100-
return handle_ssh_error_codes(
101+
# May or may not be an 'ssh error' which only handles
102+
# three error types
103+
handle_ssh_error_codes(
101104
ssh_get_error_code(self.session._session),
102105
self.session._session)
106+
raise SFTPHandleError
103107
_dir = SFTPDir.from_ptr(c_dir, self)
104108
return _dir
105109

@@ -111,9 +115,10 @@ cdef class SFTP:
111115
with nogil:
112116
c_attrs = c_sftp.sftp_stat(self._sftp, c_path)
113117
if c_attrs is NULL:
114-
return handle_ssh_error_codes(
118+
handle_ssh_error_codes(
115119
ssh_get_error_code(self.session._session),
116120
self.session._session)
121+
raise SFTPHandleError
117122
_attrs = SFTPAttributes.from_ptr(c_attrs, self)
118123
return _attrs
119124

@@ -125,9 +130,10 @@ cdef class SFTP:
125130
with nogil:
126131
c_attrs = c_sftp.sftp_lstat(self._sftp, c_path)
127132
if c_attrs is NULL:
128-
return handle_ssh_error_codes(
133+
handle_ssh_error_codes(
129134
ssh_get_error_code(self.session._session),
130135
self.session._session)
136+
raise SFTPHandleError
131137
_attrs = SFTPAttributes.from_ptr(c_attrs, self)
132138
return _attrs
133139

@@ -139,9 +145,10 @@ cdef class SFTP:
139145
with nogil:
140146
c_file = c_sftp.sftp_open(self._sftp, c_path, accesstype, mode)
141147
if c_file is NULL:
142-
return handle_ssh_error_codes(
148+
handle_ssh_error_codes(
143149
ssh_get_error_code(self.session._session),
144150
self.session._session)
151+
raise SFTPHandleError
145152
_file = SFTPFile.from_ptr(c_file, self)
146153
return _file
147154

ssh/sftp_handles.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-130
1616

1717
from sftp cimport SFTP
18+
from sftp_attributes cimport SFTPAttributes
1819

1920
cimport c_sftp
2021

@@ -33,3 +34,5 @@ cdef class SFTPDir:
3334

3435
@staticmethod
3536
cdef SFTPDir from_ptr(c_sftp.sftp_dir _dir, SFTP sftp)
37+
38+
cpdef SFTPAttributes readdir(self)

ssh/sftp_handles.pyx

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
# License along with this library; if not, write to the Free Software
1515
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-130
1616

17+
from sftp_attributes cimport SFTPAttributes
18+
19+
from .exceptions import SFTPHandleError
20+
1721
from c_ssh cimport uint32_t, uint64_t
1822
cimport c_sftp
1923

@@ -29,6 +33,21 @@ cdef class SFTPFile:
2933
_fh._file = _file
3034
return _fh
3135

36+
def __enter__(self):
37+
return self
38+
39+
def __exit__(self, *args):
40+
self.close()
41+
42+
def __iter__(self):
43+
return self
44+
45+
def __next__(self):
46+
size, data = self.read()
47+
while size > 0:
48+
return size
49+
raise StopIteration
50+
3251
@property
3352
def sftp_session(self):
3453
return self.sftp
@@ -86,19 +105,50 @@ cdef class SFTPDir:
86105

87106
@staticmethod
88107
cdef SFTPDir from_ptr(c_sftp.sftp_dir _dir, SFTP sftp):
89-
cdef SFTPDir _fh = SFTPDir(SFTPDir, sftp)
108+
cdef SFTPDir _fh = SFTPDir.__new__(SFTPDir, sftp)
90109
_fh._dir = _dir
91110
return _fh
92111

112+
def __enter__(self):
113+
return self
114+
115+
def __exit__(self, *args):
116+
self.closedir()
117+
118+
def __iter__(self):
119+
return self
120+
121+
def __next__(self):
122+
cdef SFTPAttributes _attrs
123+
_attrs = self.readdir()
124+
while _attrs is not None:
125+
return _attrs
126+
raise StopIteration
127+
93128
@property
94129
def sftp_session(self):
95130
return self.sftp
96131

97132
def eof(self):
98-
pass
133+
cdef bint rc
134+
with nogil:
135+
rc = c_sftp.sftp_dir_eof(self._dir)
136+
return bool(rc)
99137

100138
def closedir(self):
101-
pass
102-
103-
def readdir(self):
104-
pass
139+
cdef int rc
140+
with nogil:
141+
rc = c_sftp.sftp_closedir(self._dir)
142+
return rc
143+
144+
cpdef SFTPAttributes readdir(self):
145+
cdef SFTPAttributes _attrs
146+
cdef c_sftp.sftp_attributes c_attrs
147+
with nogil:
148+
c_attrs = c_sftp.sftp_readdir(self.sftp._sftp, self._dir)
149+
if c_sftp.sftp_dir_eof(self._dir) == 1:
150+
return
151+
elif c_attrs is NULL:
152+
raise SFTPHandleError
153+
_attrs = SFTPAttributes.from_ptr(c_attrs, self.sftp)
154+
return _attrs

tests/test_sftp.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# This file is part of ssh-python.
2+
# Copyright (C) 2018 Panos Kittenis
3+
#
4+
# This library is free software; you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation, version 2.1.
7+
#
8+
# This library is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11+
# Lesser General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU Lesser General Public
14+
# License along with this library; if not, write to the Free Software
15+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-130
16+
17+
import unittest
18+
import os
19+
import platform
20+
21+
from ssh.sftp import SFTP
22+
from ssh.sftp_handles import SFTPDir, SFTPFile
23+
from ssh.sftp_attributes import SFTPAttributes
24+
from ssh.exceptions import InvalidAPIUse, SFTPHandleError
25+
26+
from .base_test import SSHTestCase
27+
28+
29+
class SFTPTest(SSHTestCase):
30+
31+
def test_sftp_init(self):
32+
self._auth()
33+
sftp = self.session.sftp_new()
34+
self.assertIsInstance(sftp, SFTP)
35+
self.assertEqual(sftp.init(), 0)
36+
37+
def test_sftp_fail(self):
38+
self.assertRaises(InvalidAPIUse, self.session.sftp_new)
39+
self._auth()
40+
sftp = self.session.sftp_new()
41+
self.assertRaises(SFTPHandleError, sftp.opendir, '.')
42+
43+
def test_sftp_dir(self):
44+
self._auth()
45+
sftp = self.session.sftp_new()
46+
sftp.init()
47+
_dir = sftp.opendir('.')
48+
self.assertIsInstance(_dir, SFTPDir)
49+
self.assertFalse(_dir.eof())
50+
self.assertEqual(_dir.closedir(), 0)
51+
# dir handle from context manager
52+
with sftp.opendir('.') as _dir:
53+
for attr in _dir:
54+
self.assertIsInstance(attr, SFTPAttributes)
55+
self.assertIsNotNone(attr.name)
56+
self.assertTrue(_dir.eof())
57+
58+
def test_sftp_readdir(self):
59+
self._auth()
60+
sftp = self.session.sftp_new()
61+
sftp.init()
62+
with sftp.opendir('.') as _dir:
63+
attrs = _dir.readdir()
64+
self.assertIsInstance(attrs, SFTPAttributes)
65+
self.assertIsNotNone(attrs.uid)
66+
self.assertIsNotNone(attrs.gid)
67+
self.assertIsNotNone(attrs.owner)
68+
self.assertIsNotNone(attrs.group)
69+
self.assertTrue(attrs.size > 0)
70+
self.assertEqual(attrs.name, b'.')
71+
self.assertTrue(len(attrs.longname) > 1)
72+
73+
def test_sftp_file(self):
74+
self._auth()
75+
sftp = self.session.sftp_new()
76+
sftp.init()
77+
if int(platform.python_version_tuple()[0]) >= 3:
78+
test_file_data = b'test' + bytes(os.linesep, 'utf-8')
79+
else:
80+
test_file_data = b'test' + os.linesep
81+
remote_filename = os.sep.join([os.path.dirname(__file__),
82+
'remote_test_file'])
83+
with open(remote_filename, 'wb') as test_fh:
84+
test_fh.write(test_file_data)
85+
try:
86+
remote_fh = sftp.open(remote_filename, os.O_RDONLY, 0)
87+
self.assertIsInstance(remote_fh, SFTPFile)
88+
finally:
89+
os.unlink(remote_filename)
90+
# with sftp.open(remote_filename, 0, 0) as remote_fh:
91+
# try:
92+
# self.assertTrue(remote_fh is not None)
93+
# remote_data = b""
94+
# for rc, data in remote_fh:
95+
# remote_data += data
96+
# self.assertEqual(remote_fh.close(), 0)
97+
# self.assertEqual(remote_data, test_file_data)
98+
# finally:
99+
# os.unlink(remote_filename)

0 commit comments

Comments
 (0)