Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/document.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ jobs:

steps:
- name: Clone Repository
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Build
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ jobs:

steps:
- name: Clone Repository
uses: actions/checkout@v3
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ jobs:

steps:
- name: Clone Repository
uses: actions/checkout@v3
uses: actions/checkout@v6
- name: Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: ${{ matrix.os }} SSH
Expand Down
10 changes: 9 additions & 1 deletion docs/changes.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
1.2.1 (current, released 2026-2-11)
1.2.2 (current, released 2026-5-10)
-----------------------------------
* adding new test for _sftp_channel exception handling.
* adding curve25519-sha256@libssh.org to kex list.
* fix for UnboundLocalError on a certain exception in _sftp_channel.
* removing diffie-hellman-group-exchange-sha1 from kex list per paramiko.
* removing ssh-rsa from public key type list per paramiko.

1.2.1 (released 2026-2-11)
--------------------------
* adding boolean to rename for switch between posix and standard behavior.
* change in default path behavior where cwd is set when default path is not.
* update drivedrop to better handle all non-UNC Windows path possibilities.
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
# built documents.
#
# The short X.Y version.
version = '1.2.1'
version = '1.2.2'
# The full version, including alpha/beta/rc tags.
release = '1.2.1'
release = '1.2.2'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ keywords = [
name = 'sftpretty'
readme = 'README.rst'
requires-python = '>=3.6'
version = '1.2.1'
version = '1.2.2'

[project.scripts]
sftpretty = 'sftpretty:Connection'
Expand Down
29 changes: 16 additions & 13 deletions sftpretty/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@ def __init__(self, config=None, knownhosts=Path(
'hmac-sha1', 'hmac-md5')
self.disabled_algorithms = {}
self.hostkeys = hostkeys.HostKeys()
self.kex = ('ecdh-sha2-nistp521', 'ecdh-sha2-nistp384',
'ecdh-sha2-nistp256', 'diffie-hellman-group16-sha512',
self.kex = ('curve25519-sha256@libssh.org', 'ecdh-sha2-nistp521',
'ecdh-sha2-nistp384', 'ecdh-sha2-nistp256',
'diffie-hellman-group16-sha512',
'diffie-hellman-group-exchange-sha256',
'diffie-hellman-group-exchange-sha1')
'diffie-hellman-group14-sha256')
self.key_types = ('ssh-ed25519', 'ecdsa-sha2-nistp521',
'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp256',
'rsa-sha2-512', 'rsa-sha2-256', 'ssh-rsa')
'rsa-sha2-512', 'rsa-sha2-256')
self.log = False
self.log_level = 'info'
self.ssh_config = SSHConfig()
Expand Down Expand Up @@ -300,24 +301,26 @@ def _sftp_channel(self):
channel = None
fatal = False

self._cache.__dict__.setdefault('cwd', self._default_path)

try:
channel_name, data = next(
(key, value)
for key, value in self._channels.items()
if not value['busy']
if not value['busy'] and not value['meta'].closed
)

channel = data['channel']
meta = data['meta']
if not meta.closed:
channel = data['channel']
self._channels[channel_name]['busy'] = True
log.debug(f'Cached Channel: [{channel_name}]')
self._channels[channel_name]['busy'] = True
log.debug(f'Cached Channel: [{channel_name}]')
except StopIteration:
pass

try:
if channel is None:
channel = SFTPClient.from_transport(self._transport)
channel_name = uuid4().hex
channel = SFTPClient.from_transport(self._transport)
meta = channel.get_channel()
meta.set_name(channel_name)
log.debug(f'Channel Name: [{channel_name}]')
Expand All @@ -326,7 +329,6 @@ def _sftp_channel(self):
}

meta.settimeout(self._timeout)
self._cache.__dict__.setdefault('cwd', self._default_path)

if self._cache.cwd is None:
self._cache.cwd = drivedrop(channel.normalize('.'))
Expand All @@ -344,6 +346,7 @@ def _sftp_channel(self):
log.error(_message)
raise TimeoutError(_message)
except SFTPError as err:
code = err.args[0] if err.args else None
_message_map = {
SFTP_FAILURE: (
'A generic failure occurred on the SFTP server for path: '
Expand All @@ -361,9 +364,9 @@ def _sftp_channel(self):
),
}
_message = _message_map.get(
err.errno,
code,
('Unhandled SFTP error on directory change to '
f'[{self._cache.cwd}] (Code {err.errno}): {err}')
f'[{self._cache.cwd}] (Code {code}): {err}')
)
log.error(_message)
raise err
Expand Down
33 changes: 33 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest

from paramiko import SFTPError
from paramiko.ed25519key import Ed25519Key

from common import conn, LOCAL, VFS
Expand All @@ -10,6 +11,38 @@
HostKeysException, SSHException)


def test_channel_exception(sftpserver):
'''test except blocks in _sftp_channel don't raise secondary errors'''
with sftpserver.serve_content(VFS):
with Connection(**conn(sftpserver)) as sftp:
sftp.close()
with pytest.raises(AttributeError):
sftp.listdir()

with sftpserver.serve_content(VFS):
with Connection(**conn(sftpserver)) as sftp:
with pytest.raises(OSError):
sftp.chdir('/does/not/exist')

with sftpserver.serve_content(VFS):
with Connection(**conn(sftpserver)) as sftp:
with pytest.raises(SFTPError):
sftp.chdir('/home/test/read.me')

with sftpserver.serve_content(VFS):
sftp = Connection(**conn(sftpserver))
sftp._transport.close()
with pytest.raises(SSHException):
sftp.listdir()

with sftpserver.serve_content(VFS):
with Connection(**conn(sftpserver)) as sftp:
sftp.timeout = 0.0001
with pytest.raises(TimeoutError,
match='operation timed out after'):
sftp.listdir()


def test_cnopts_bad_knownhosts():
'''test setting knownhosts to a not understood file'''
with pytest.raises(HostKeysException):
Expand Down
Loading