pysftp-ng 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pysftp_ng-0.3.0/.gitignore +21 -0
- pysftp_ng-0.3.0/CHANGELOG.md +63 -0
- pysftp_ng-0.3.0/CONTRIBUTING.md +70 -0
- pysftp_ng-0.3.0/LICENSE +28 -0
- pysftp_ng-0.3.0/PKG-INFO +144 -0
- pysftp_ng-0.3.0/README.md +111 -0
- pysftp_ng-0.3.0/SECURITY.md +26 -0
- pysftp_ng-0.3.0/pyproject.toml +100 -0
- pysftp_ng-0.3.0/src/pysftp/__init__.py +1372 -0
- pysftp_ng-0.3.0/src/pysftp/exceptions.py +40 -0
- pysftp_ng-0.3.0/src/pysftp/helpers.py +238 -0
- pysftp_ng-0.3.0/src/pysftp/py.typed +0 -0
- pysftp_ng-0.3.0/tests/__init__.py +0 -0
- pysftp_ng-0.3.0/tests/conftest.py +175 -0
- pysftp_ng-0.3.0/tests/sftp_server.py +305 -0
- pysftp_ng-0.3.0/tests/test_auth.py +96 -0
- pysftp_ng-0.3.0/tests/test_connect.py +226 -0
- pysftp_ng-0.3.0/tests/test_fileops.py +282 -0
- pysftp_ng-0.3.0/tests/test_helpers.py +84 -0
- pysftp_ng-0.3.0/tests/test_hostkeys.py +158 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.eggs/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.nox/
|
|
10
|
+
# A library: CI tests dependency ranges, so no lock file is kept
|
|
11
|
+
uv.lock
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
.ruff_cache/
|
|
14
|
+
.mypy_cache/
|
|
15
|
+
.coverage
|
|
16
|
+
.coverage.*
|
|
17
|
+
coverage.xml
|
|
18
|
+
htmlcov/
|
|
19
|
+
.DS_Store
|
|
20
|
+
.idea/
|
|
21
|
+
.vscode/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here. pysftp-ng follows [Semantic Versioning](https://semver.org/).
|
|
4
|
+
Until 1.0, minor releases may deprecate things but won't remove anything from the pysftp 0.2.9
|
|
5
|
+
API.
|
|
6
|
+
|
|
7
|
+
## 0.3.0 (unreleased)
|
|
8
|
+
|
|
9
|
+
First release of pysftp-ng, forked from pysftp 0.2.9 (released 2016-07-04).
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `import pysftp` no longer fails on paramiko 4.0 and later, which removed `DSSKey`.
|
|
14
|
+
- Ed25519 and ECDSA private key files now load, encrypted or not, in OpenSSH or PEM format.
|
|
15
|
+
Any `paramiko.PKey` object, not only `RSAKey` or `AgentKey`, can be passed as `private_key`.
|
|
16
|
+
- With no password or key given, the default keys `~/.ssh/id_rsa`, `id_ecdsa` and `id_ed25519`
|
|
17
|
+
(and `id_dsa` on paramiko < 4) are each tried, and the first one the server accepts is used.
|
|
18
|
+
- known_hosts entries in OpenSSH's `[host]:port` form, plain or hashed, are now found for servers
|
|
19
|
+
on non-standard ports. Bare hostname entries still work.
|
|
20
|
+
- Host key verification now negotiates only key types listed in known_hosts and accepts any
|
|
21
|
+
matching key, instead of pinning the first key found. This fixes "Incompatible ssh peer
|
|
22
|
+
(no acceptable host key)" when a retired key type or an old `ssh-dss` line came first.
|
|
23
|
+
- A connection that fails part-way no longer raises
|
|
24
|
+
`AttributeError: 'Connection' object has no attribute '_sftp_live'` from `__del__`, and its
|
|
25
|
+
transport is closed instead of left running.
|
|
26
|
+
- `close()` removes only the log handler the connection added and restores the logger's level.
|
|
27
|
+
It used to delete every handler on the `paramiko` logger.
|
|
28
|
+
- `put_r` and `put_d` join remote paths with `/` on every OS instead of `os.path.join`, which sent
|
|
29
|
+
backslashes from Windows. They no longer `chdir` the whole process.
|
|
30
|
+
- `get_r` builds local paths correctly on Windows, creates missing local directories, and
|
|
31
|
+
reports broken symlinks to the unknown-type callback instead of crashing.
|
|
32
|
+
- `walktree` uses one `listdir_attr` call per directory instead of a `stat` per entry.
|
|
33
|
+
- `get_r`, `get_d` and `walktree` skip, with a `RuntimeWarning`, file names from the server that
|
|
34
|
+
would escape the target directory (for example `../x`).
|
|
35
|
+
- `execute()` closes its channel.
|
|
36
|
+
- `makedirs` splits remote paths with `posixpath`.
|
|
37
|
+
- The missing-known_hosts warning reads "disable HostKey checking" (it said "disableHostKey").
|
|
38
|
+
- `path_advance` and `path_retreat` handle an empty path.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- `CnOpts.timeout`: limit, in seconds, for the TCP connection, SSH banner, key exchange and
|
|
43
|
+
authentication.
|
|
44
|
+
- `CnOpts.get_hostkey(host, port=22)` takes an optional port.
|
|
45
|
+
- `BadHostKeyException` and `PasswordRequiredException` are re-exported from `pysftp`.
|
|
46
|
+
- Type hints and a `py.typed` marker.
|
|
47
|
+
- An in-process SFTP server and a test suite covering the API across Python and paramiko
|
|
48
|
+
versions.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Requires Python 3.9+ and paramiko 3.4+. paramiko 3.4 is the first release with the fix for the
|
|
53
|
+
Terrapin attack (CVE-2023-48795).
|
|
54
|
+
- A server key that doesn't match known_hosts raises `paramiko.BadHostKeyException`. It is a
|
|
55
|
+
subclass of `SSHException`, so existing `except SSHException` blocks still catch it.
|
|
56
|
+
- Connection refused or unreachable still raises `SSHException("Unable to connect to ...")`, but
|
|
57
|
+
pysftp-ng now opens the socket itself, so a failed connect no longer leaks it.
|
|
58
|
+
- `str(ConnectionException)` is now the human-readable message. `.args` is unchanged.
|
|
59
|
+
- The deprecated `log` and `ciphers` arguments to `Connection` are no longer scheduled for removal
|
|
60
|
+
in 0.3.0. They will be removed no earlier than 1.0.
|
|
61
|
+
- If no username is given and `LOGNAME` isn't set, the current OS user is used (`getpass.getuser()`).
|
|
62
|
+
|
|
63
|
+
For changes before the fork, see the pysftp 0.2.9 changelog.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. pysftp-ng exists so that code written against pysftp 0.2.9 keeps working, so
|
|
4
|
+
compatibility comes first.
|
|
5
|
+
|
|
6
|
+
## Ground rules
|
|
7
|
+
|
|
8
|
+
- **Don't break the 0.2.9 API.** Keep public names, arguments, defaults and return values as they
|
|
9
|
+
are. New behaviour should be opt-in, normally through `CnOpts`.
|
|
10
|
+
- **Every bug fix comes with a test.** If the bug exists in pysftp 0.2.9, mark the test
|
|
11
|
+
`@pytest.mark.regression`.
|
|
12
|
+
- **Security-sensitive changes need extra care.** That covers host keys, authentication and path
|
|
13
|
+
handling. Explain the threat model in the pull request.
|
|
14
|
+
- **Small pull requests.** One fix or feature per pull request is much easier to review.
|
|
15
|
+
- **Understand what you submit.** Tools are fine, but you should be able to explain and defend
|
|
16
|
+
every line in review.
|
|
17
|
+
|
|
18
|
+
## Development setup
|
|
19
|
+
|
|
20
|
+
You need Python 3.9 or newer. With [uv](https://docs.astral.sh/uv/):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv venv
|
|
24
|
+
uv pip install -e . --group dev
|
|
25
|
+
uv run pytest
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or with pip:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
python -m venv .venv
|
|
32
|
+
source .venv/bin/activate # .venv\Scripts\activate on Windows
|
|
33
|
+
pip install -e . pytest pytest-cov ruff
|
|
34
|
+
pytest
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The tests start a small SFTP server inside the test process (`tests/sftp_server.py`), so you don't
|
|
38
|
+
need Docker or a real server.
|
|
39
|
+
|
|
40
|
+
To test against a specific paramiko version:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv run --with "paramiko==3.4.1" pytest
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Don't install the version with `uv pip install` first: `uv run` resets the environment to match
|
|
47
|
+
`uv.lock` before it starts, so the tests would run against the paramiko version in `uv.lock`
|
|
48
|
+
instead. The pytest header shows which paramiko version was used.
|
|
49
|
+
|
|
50
|
+
To run the full matrix (every supported Python with paramiko 3.4.1 and the newest release), plus
|
|
51
|
+
lint and a build check, use [nox](https://nox.thea.codes/). It downloads any Python you don't have:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
uvx nox # everything
|
|
55
|
+
uvx nox -s lint # one session; `uvx nox -l` lists them
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Style
|
|
59
|
+
|
|
60
|
+
[ruff](https://docs.astral.sh/ruff/) handles linting and formatting:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ruff check .
|
|
64
|
+
ruff format .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Releases
|
|
68
|
+
|
|
69
|
+
Maintainers cut releases from GitHub. The release workflow builds the package and publishes it to
|
|
70
|
+
PyPI through trusted publishing, so no API tokens are stored in the repository.
|
pysftp_ng-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014, Dundee Media & Technology, Inc
|
|
4
|
+
Copyright (c) 2026, Abdul Hakim Aremeyaw and pysftp-ng contributors
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
8
|
+
are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer.
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
* Neither the name of the Dundee Media & Technology, Inc nor the names of
|
|
16
|
+
its contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
20
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
21
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
23
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
24
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
25
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
26
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
27
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
28
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
pysftp_ng-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pysftp-ng
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A friendly face on SFTP. Maintained, drop-in fork of pysftp for paramiko 4/5 and current Python.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Vitalik-Hakim/pysftp-ng
|
|
6
|
+
Project-URL: Source, https://github.com/Vitalik-Hakim/pysftp-ng
|
|
7
|
+
Project-URL: Issues, https://github.com/Vitalik-Hakim/pysftp-ng/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Vitalik-Hakim/pysftp-ng/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Jeff Hinrichs, Abdul Hakim Aremeyaw
|
|
10
|
+
Maintainer: Abdul Hakim Aremeyaw
|
|
11
|
+
License-Expression: BSD-3-Clause
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: file transfer,ftp,paramiko,pysftp,sftp,ssh
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
26
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
27
|
+
Classifier: Topic :: Internet :: File Transfer Protocol (FTP)
|
|
28
|
+
Classifier: Topic :: System :: Networking
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.9
|
|
31
|
+
Requires-Dist: paramiko>=3.4
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# pysftp-ng
|
|
35
|
+
|
|
36
|
+
[](https://github.com/Vitalik-Hakim/pysftp-ng/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/pysftp-ng/)
|
|
38
|
+
[](https://pypi.org/project/pysftp-ng/)
|
|
39
|
+
|
|
40
|
+
A maintained, drop-in fork of [pysftp](https://pypi.org/project/pysftp/). Your code still says
|
|
41
|
+
`import pysftp` and uses the same API, but it now works with current paramiko (3.4 through 5.x)
|
|
42
|
+
and Python 3.9 through 3.14.
|
|
43
|
+
|
|
44
|
+
## Why this exists
|
|
45
|
+
|
|
46
|
+
pysftp 0.2.9 came out in July 2016 and is still installed around 11.8 million times a month.
|
|
47
|
+
It no longer works with current dependencies. paramiko 4.0 removed DSA keys, so a fresh
|
|
48
|
+
`pip install pysftp` now fails the moment you import it:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
ImportError: cannot import name 'DSSKey' from 'paramiko'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
There has been no upstream release since 2016. pysftp-ng fixes that break and a set of
|
|
55
|
+
long-standing bugs, and keeps the API stable so existing code keeps running.
|
|
56
|
+
|
|
57
|
+
## Switching over
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip uninstall -y pysftp
|
|
61
|
+
pip install pysftp-ng
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
No code changes are needed. In `requirements.txt` or `pyproject.toml`, replace `pysftp` with
|
|
65
|
+
`pysftp-ng`. Don't install both packages, because they provide the same `pysftp` module.
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import pysftp
|
|
71
|
+
|
|
72
|
+
with pysftp.Connection("sftp.example.com", username="me", private_key="~/.ssh/id_ed25519") as sftp:
|
|
73
|
+
sftp.put("report.csv", "/upload/report.csv")
|
|
74
|
+
with sftp.cd("/download"):
|
|
75
|
+
sftp.get_r(".", "local-copy")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Extra connection options go on a `CnOpts` object:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
cnopts = pysftp.CnOpts() # loads ~/.ssh/known_hosts
|
|
82
|
+
cnopts.timeout = 10 # new: limit for connecting, the handshake and logging in
|
|
83
|
+
|
|
84
|
+
with pysftp.Connection(
|
|
85
|
+
"sftp.example.com", port=2222, username="me", password="...", cnopts=cnopts
|
|
86
|
+
) as sftp:
|
|
87
|
+
print(sftp.listdir())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## What's fixed compared with pysftp 0.2.9
|
|
91
|
+
|
|
92
|
+
Each item below has a test in `tests/`. Tests marked `regression` reproduce bugs in pysftp 0.2.9.
|
|
93
|
+
|
|
94
|
+
- **paramiko 4 and 5.** Imports and runs without DSA. On paramiko < 4, DSA keys still work.
|
|
95
|
+
- **Modern keys.** Ed25519 and ECDSA key files work, encrypted or not, in OpenSSH or PEM format.
|
|
96
|
+
You can also pass any paramiko key object, such as `Ed25519Key` or `AgentKey`, as `private_key`.
|
|
97
|
+
- **Default keys.** With no password or key given, pysftp-ng tries `~/.ssh/id_rsa`, `id_ecdsa` and
|
|
98
|
+
`id_ed25519` in OpenSSH's order and uses the first one the server accepts. 0.2.9 only looked
|
|
99
|
+
for `id_rsa` and `id_dsa`.
|
|
100
|
+
- **known_hosts on non-standard ports.** OpenSSH stores these as `[host]:port`, including in hashed
|
|
101
|
+
form. pysftp-ng finds them; 0.2.9 failed with "No hostkey for host ... found".
|
|
102
|
+
- **Hosts with several known keys.** pysftp-ng only negotiates key types that are in known_hosts
|
|
103
|
+
and accepts any key that matches. 0.2.9 pinned whichever key was listed first. If the server had
|
|
104
|
+
retired that key type, or the first line was an old `ssh-dss` key, the connection failed with
|
|
105
|
+
"Incompatible ssh peer".
|
|
106
|
+
- **Cleaner failures.** No more `AttributeError: 'Connection' object has no attribute '_sftp_live'`
|
|
107
|
+
hiding the real error. A failed connection closes its transport.
|
|
108
|
+
- **Logging.** `close()` removes only the log handler it added. 0.2.9 removed every handler on the
|
|
109
|
+
`paramiko` logger, including your application's.
|
|
110
|
+
- **Windows paths.** `put_r` and `put_d` build remote paths with `/` on every OS (0.2.9 sent
|
|
111
|
+
backslashes from Windows), and they no longer change the process's working directory.
|
|
112
|
+
- **`get_r`.** Works on Windows clients, creates missing local directories, and no longer crashes
|
|
113
|
+
on a broken symlink.
|
|
114
|
+
- **Speed.** `walktree` sends one listing request per directory instead of one `stat` per entry.
|
|
115
|
+
- **Path traversal.** If a server returns a file name that would land outside the target
|
|
116
|
+
directory, such as `../../.bashrc`, `get_r`, `get_d` and `walktree` skip it with a warning.
|
|
117
|
+
- **Smaller fixes.** New `CnOpts.timeout` option. The username falls back to the OS user when
|
|
118
|
+
`LOGNAME` isn't set, as on Windows. `ConnectionException` has a readable message.
|
|
119
|
+
|
|
120
|
+
## Compatibility
|
|
121
|
+
|
|
122
|
+
- The public API of pysftp 0.2.9 is kept: same names, arguments and return values. The deprecated
|
|
123
|
+
`log` and `ciphers` arguments still work and emit a `DeprecationWarning`.
|
|
124
|
+
- Behaviour changes are limited to the fixes above. The [changelog](CHANGELOG.md) lists every one.
|
|
125
|
+
- The test suite runs against Python 3.9–3.14 and paramiko 3.4, 3.5, 4.0 and 5.1. It uses an
|
|
126
|
+
in-process SFTP server, so no external server is needed.
|
|
127
|
+
|
|
128
|
+
## Security
|
|
129
|
+
|
|
130
|
+
Host key checking is on by default and verifies the server before any password or key is sent.
|
|
131
|
+
Setting `cnopts.hostkeys = None` turns verification off and exposes you to man-in-the-middle
|
|
132
|
+
attacks, so don't do it outside a test environment.
|
|
133
|
+
|
|
134
|
+
To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
135
|
+
|
|
136
|
+
## Credits
|
|
137
|
+
|
|
138
|
+
pysftp was written by Jeff Hinrichs (Dundee Media & Technology), with contributions from Don
|
|
139
|
+
Reilly, James Kruth and Nick Robinson-Wall. pysftp-ng is forked from the 0.2.9 release and keeps
|
|
140
|
+
its BSD 3-Clause license.
|
|
141
|
+
|
|
142
|
+
## Contributing
|
|
143
|
+
|
|
144
|
+
Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# pysftp-ng
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Vitalik-Hakim/pysftp-ng/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/pysftp-ng/)
|
|
5
|
+
[](https://pypi.org/project/pysftp-ng/)
|
|
6
|
+
|
|
7
|
+
A maintained, drop-in fork of [pysftp](https://pypi.org/project/pysftp/). Your code still says
|
|
8
|
+
`import pysftp` and uses the same API, but it now works with current paramiko (3.4 through 5.x)
|
|
9
|
+
and Python 3.9 through 3.14.
|
|
10
|
+
|
|
11
|
+
## Why this exists
|
|
12
|
+
|
|
13
|
+
pysftp 0.2.9 came out in July 2016 and is still installed around 11.8 million times a month.
|
|
14
|
+
It no longer works with current dependencies. paramiko 4.0 removed DSA keys, so a fresh
|
|
15
|
+
`pip install pysftp` now fails the moment you import it:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
ImportError: cannot import name 'DSSKey' from 'paramiko'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
There has been no upstream release since 2016. pysftp-ng fixes that break and a set of
|
|
22
|
+
long-standing bugs, and keeps the API stable so existing code keeps running.
|
|
23
|
+
|
|
24
|
+
## Switching over
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip uninstall -y pysftp
|
|
28
|
+
pip install pysftp-ng
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
No code changes are needed. In `requirements.txt` or `pyproject.toml`, replace `pysftp` with
|
|
32
|
+
`pysftp-ng`. Don't install both packages, because they provide the same `pysftp` module.
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import pysftp
|
|
38
|
+
|
|
39
|
+
with pysftp.Connection("sftp.example.com", username="me", private_key="~/.ssh/id_ed25519") as sftp:
|
|
40
|
+
sftp.put("report.csv", "/upload/report.csv")
|
|
41
|
+
with sftp.cd("/download"):
|
|
42
|
+
sftp.get_r(".", "local-copy")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Extra connection options go on a `CnOpts` object:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
cnopts = pysftp.CnOpts() # loads ~/.ssh/known_hosts
|
|
49
|
+
cnopts.timeout = 10 # new: limit for connecting, the handshake and logging in
|
|
50
|
+
|
|
51
|
+
with pysftp.Connection(
|
|
52
|
+
"sftp.example.com", port=2222, username="me", password="...", cnopts=cnopts
|
|
53
|
+
) as sftp:
|
|
54
|
+
print(sftp.listdir())
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## What's fixed compared with pysftp 0.2.9
|
|
58
|
+
|
|
59
|
+
Each item below has a test in `tests/`. Tests marked `regression` reproduce bugs in pysftp 0.2.9.
|
|
60
|
+
|
|
61
|
+
- **paramiko 4 and 5.** Imports and runs without DSA. On paramiko < 4, DSA keys still work.
|
|
62
|
+
- **Modern keys.** Ed25519 and ECDSA key files work, encrypted or not, in OpenSSH or PEM format.
|
|
63
|
+
You can also pass any paramiko key object, such as `Ed25519Key` or `AgentKey`, as `private_key`.
|
|
64
|
+
- **Default keys.** With no password or key given, pysftp-ng tries `~/.ssh/id_rsa`, `id_ecdsa` and
|
|
65
|
+
`id_ed25519` in OpenSSH's order and uses the first one the server accepts. 0.2.9 only looked
|
|
66
|
+
for `id_rsa` and `id_dsa`.
|
|
67
|
+
- **known_hosts on non-standard ports.** OpenSSH stores these as `[host]:port`, including in hashed
|
|
68
|
+
form. pysftp-ng finds them; 0.2.9 failed with "No hostkey for host ... found".
|
|
69
|
+
- **Hosts with several known keys.** pysftp-ng only negotiates key types that are in known_hosts
|
|
70
|
+
and accepts any key that matches. 0.2.9 pinned whichever key was listed first. If the server had
|
|
71
|
+
retired that key type, or the first line was an old `ssh-dss` key, the connection failed with
|
|
72
|
+
"Incompatible ssh peer".
|
|
73
|
+
- **Cleaner failures.** No more `AttributeError: 'Connection' object has no attribute '_sftp_live'`
|
|
74
|
+
hiding the real error. A failed connection closes its transport.
|
|
75
|
+
- **Logging.** `close()` removes only the log handler it added. 0.2.9 removed every handler on the
|
|
76
|
+
`paramiko` logger, including your application's.
|
|
77
|
+
- **Windows paths.** `put_r` and `put_d` build remote paths with `/` on every OS (0.2.9 sent
|
|
78
|
+
backslashes from Windows), and they no longer change the process's working directory.
|
|
79
|
+
- **`get_r`.** Works on Windows clients, creates missing local directories, and no longer crashes
|
|
80
|
+
on a broken symlink.
|
|
81
|
+
- **Speed.** `walktree` sends one listing request per directory instead of one `stat` per entry.
|
|
82
|
+
- **Path traversal.** If a server returns a file name that would land outside the target
|
|
83
|
+
directory, such as `../../.bashrc`, `get_r`, `get_d` and `walktree` skip it with a warning.
|
|
84
|
+
- **Smaller fixes.** New `CnOpts.timeout` option. The username falls back to the OS user when
|
|
85
|
+
`LOGNAME` isn't set, as on Windows. `ConnectionException` has a readable message.
|
|
86
|
+
|
|
87
|
+
## Compatibility
|
|
88
|
+
|
|
89
|
+
- The public API of pysftp 0.2.9 is kept: same names, arguments and return values. The deprecated
|
|
90
|
+
`log` and `ciphers` arguments still work and emit a `DeprecationWarning`.
|
|
91
|
+
- Behaviour changes are limited to the fixes above. The [changelog](CHANGELOG.md) lists every one.
|
|
92
|
+
- The test suite runs against Python 3.9–3.14 and paramiko 3.4, 3.5, 4.0 and 5.1. It uses an
|
|
93
|
+
in-process SFTP server, so no external server is needed.
|
|
94
|
+
|
|
95
|
+
## Security
|
|
96
|
+
|
|
97
|
+
Host key checking is on by default and verifies the server before any password or key is sent.
|
|
98
|
+
Setting `cnopts.hostkeys = None` turns verification off and exposes you to man-in-the-middle
|
|
99
|
+
attacks, so don't do it outside a test environment.
|
|
100
|
+
|
|
101
|
+
To report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
102
|
+
|
|
103
|
+
## Credits
|
|
104
|
+
|
|
105
|
+
pysftp was written by Jeff Hinrichs (Dundee Media & Technology), with contributions from Don
|
|
106
|
+
Reilly, James Kruth and Nick Robinson-Wall. pysftp-ng is forked from the 0.2.9 release and keeps
|
|
107
|
+
its BSD 3-Clause license.
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Bug reports and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes go into the latest release. pysftp-ng is a drop-in replacement for pysftp 0.2.9,
|
|
6
|
+
which gets no further fixes, so upgrading to pysftp-ng is the fix for issues in the original.
|
|
7
|
+
|
|
8
|
+
## Reporting a vulnerability
|
|
9
|
+
|
|
10
|
+
Please don't open a public issue for security problems. Report them privately through GitHub:
|
|
11
|
+
open the repository's **Security** tab and choose **Report a vulnerability**.
|
|
12
|
+
|
|
13
|
+
Include what you found, how to reproduce it, and which versions of pysftp-ng, paramiko and
|
|
14
|
+
Python you used. You'll get an acknowledgement within 72 hours and a plan for a fix within a
|
|
15
|
+
week. You'll be credited in the release notes unless you'd rather not be.
|
|
16
|
+
|
|
17
|
+
## What counts
|
|
18
|
+
|
|
19
|
+
Anything that weakens what pysftp-ng promises is in scope. Examples:
|
|
20
|
+
|
|
21
|
+
- host key verification being skipped or bypassed
|
|
22
|
+
- credentials being sent before the server is verified
|
|
23
|
+
- files written outside the directory a transfer was aimed at
|
|
24
|
+
- private key material leaking into logs or exceptions
|
|
25
|
+
|
|
26
|
+
Bugs in paramiko itself should go to the [paramiko project](https://github.com/paramiko/paramiko).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pysftp-ng"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "A friendly face on SFTP. Maintained, drop-in fork of pysftp for paramiko 4/5 and current Python."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "BSD-3-Clause"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Jeff Hinrichs" },
|
|
15
|
+
{ name = "Abdul Hakim Aremeyaw" },
|
|
16
|
+
]
|
|
17
|
+
maintainers = [{ name = "Abdul Hakim Aremeyaw" }]
|
|
18
|
+
keywords = ["sftp", "ssh", "ftp", "paramiko", "pysftp", "file transfer"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: System Administrators",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Programming Language :: Python :: 3.13",
|
|
31
|
+
"Programming Language :: Python :: 3.14",
|
|
32
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
33
|
+
"Topic :: Internet :: File Transfer Protocol (FTP)",
|
|
34
|
+
"Topic :: System :: Networking",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
dependencies = ["paramiko>=3.4"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/Vitalik-Hakim/pysftp-ng"
|
|
41
|
+
Source = "https://github.com/Vitalik-Hakim/pysftp-ng"
|
|
42
|
+
Issues = "https://github.com/Vitalik-Hakim/pysftp-ng/issues"
|
|
43
|
+
Changelog = "https://github.com/Vitalik-Hakim/pysftp-ng/blob/main/CHANGELOG.md"
|
|
44
|
+
|
|
45
|
+
[dependency-groups]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8",
|
|
48
|
+
"pytest-cov>=5",
|
|
49
|
+
"ruff>=0.8",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.hatch.version]
|
|
53
|
+
path = "src/pysftp/__init__.py"
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/pysftp"]
|
|
57
|
+
|
|
58
|
+
[tool.hatch.build.targets.sdist]
|
|
59
|
+
include = [
|
|
60
|
+
"src/pysftp",
|
|
61
|
+
"tests",
|
|
62
|
+
"README.md",
|
|
63
|
+
"CHANGELOG.md",
|
|
64
|
+
"LICENSE",
|
|
65
|
+
"SECURITY.md",
|
|
66
|
+
"CONTRIBUTING.md",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.pytest.ini_options]
|
|
70
|
+
testpaths = ["tests"]
|
|
71
|
+
addopts = ["-ra", "--strict-markers"]
|
|
72
|
+
markers = [
|
|
73
|
+
"regression: reproduces a bug present in pysftp 0.2.9",
|
|
74
|
+
]
|
|
75
|
+
filterwarnings = [
|
|
76
|
+
"error",
|
|
77
|
+
# paramiko itself still imports ciphers that cryptography has deprecated
|
|
78
|
+
"ignore::DeprecationWarning:paramiko.*",
|
|
79
|
+
"ignore:.*TripleDES.*",
|
|
80
|
+
"ignore:.*Blowfish.*",
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
[tool.coverage.run]
|
|
84
|
+
source = ["pysftp"]
|
|
85
|
+
branch = true
|
|
86
|
+
|
|
87
|
+
[tool.ruff]
|
|
88
|
+
line-length = 100
|
|
89
|
+
target-version = "py39"
|
|
90
|
+
src = ["src", "tests"]
|
|
91
|
+
|
|
92
|
+
[tool.ruff.lint]
|
|
93
|
+
select = ["E", "F", "W", "I", "B", "UP", "S", "PT", "RUF"]
|
|
94
|
+
ignore = [
|
|
95
|
+
"UP007", # keep Union[...] spelling where it is evaluated at runtime on 3.9
|
|
96
|
+
"RUF022", # __all__ is grouped by hand
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.per-file-ignores]
|
|
100
|
+
"tests/**" = ["S101", "S105", "S106", "S107", "S108", "S505", "S603", "S607"]
|