mfatsftp 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.
Files changed (40) hide show
  1. mfatsftp-0.3.0/.gitignore +17 -0
  2. mfatsftp-0.3.0/CHANGELOG.md +138 -0
  3. mfatsftp-0.3.0/CONTRIBUTING.md +75 -0
  4. mfatsftp-0.3.0/LICENSE +675 -0
  5. mfatsftp-0.3.0/PKG-INFO +420 -0
  6. mfatsftp-0.3.0/README.md +382 -0
  7. mfatsftp-0.3.0/SECURITY.md +39 -0
  8. mfatsftp-0.3.0/docs/api.md +379 -0
  9. mfatsftp-0.3.0/docs/changelog.md +2 -0
  10. mfatsftp-0.3.0/docs/conf.py +38 -0
  11. mfatsftp-0.3.0/docs/contributing.md +3 -0
  12. mfatsftp-0.3.0/docs/guide.md +4 -0
  13. mfatsftp-0.3.0/docs/index.md +14 -0
  14. mfatsftp-0.3.0/docs/reference.rst +45 -0
  15. mfatsftp-0.3.0/docs/requirements.txt +3 -0
  16. mfatsftp-0.3.0/docs/security.md +2 -0
  17. mfatsftp-0.3.0/examples/list_remote.py +29 -0
  18. mfatsftp-0.3.0/examples/transfer.py +54 -0
  19. mfatsftp-0.3.0/pyproject.toml +70 -0
  20. mfatsftp-0.3.0/src/mfatsftp/__init__.py +54 -0
  21. mfatsftp-0.3.0/src/mfatsftp/client.py +2614 -0
  22. mfatsftp-0.3.0/src/mfatsftp/protocol.py +473 -0
  23. mfatsftp-0.3.0/src/mfatsftp/py.typed +0 -0
  24. mfatsftp-0.3.0/src/mfatsftp/transport.py +270 -0
  25. mfatsftp-0.3.0/tests/__init__.py +0 -0
  26. mfatsftp-0.3.0/tests/conftest.py +15 -0
  27. mfatsftp-0.3.0/tests/fake_server.py +222 -0
  28. mfatsftp-0.3.0/tests/real_server.py +125 -0
  29. mfatsftp-0.3.0/tests/sftp_stdio_server.py +19 -0
  30. mfatsftp-0.3.0/tests/test_client.py +115 -0
  31. mfatsftp-0.3.0/tests/test_docs.py +78 -0
  32. mfatsftp-0.3.0/tests/test_highlevel.py +411 -0
  33. mfatsftp-0.3.0/tests/test_large_files.py +620 -0
  34. mfatsftp-0.3.0/tests/test_live_ssh.py +47 -0
  35. mfatsftp-0.3.0/tests/test_open_and_rename_requests.py +197 -0
  36. mfatsftp-0.3.0/tests/test_paramiko_parity.py +331 -0
  37. mfatsftp-0.3.0/tests/test_protocol.py +165 -0
  38. mfatsftp-0.3.0/tests/test_symlink.py +131 -0
  39. mfatsftp-0.3.0/tests/test_transport.py +50 -0
  40. mfatsftp-0.3.0/tests/test_transport_subprocess.py +64 -0
@@ -0,0 +1,17 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # Tooling
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+ docs/_build/
@@ -0,0 +1,138 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2026-10-18
10
+
11
+ paramiko parity for the SFTP client, a documentation site, strict typing, and
12
+ a security fix.
13
+
14
+ ### Added
15
+ - Reply timeouts: `SFTPClient.settimeout()`/`gettimeout()` (also on
16
+ `SFTPFile`, with `setblocking`), raising the new `SFTPTimeoutError`
17
+ (a `TimeoutError`).
18
+ - Line I/O on `SFTPFile`: `readline`, `readlines`, iteration, `writelines`.
19
+ - Write buffering via `open(..., bufsize=N)` (`1` = line-buffered) and
20
+ `SFTPFile.set_pipelined()`; `flush()` now sends and confirms buffered data.
21
+ - `chdir`/`getcwd` (an emulated working directory, as in paramiko).
22
+ - `listdir_iter` (streaming directory listing).
23
+ - `readv` on `SFTPClient` and `SFTPFile` (many ranges, one pipelined batch).
24
+ - `SFTPClient.max_concurrent_requests` and per-call
25
+ `max_concurrent_prefetch_requests` on `get`/`getfo` to cap requests in flight.
26
+ - paramiko compatibility: `prefetch=` on `get`/`getfo`, `SFTPFile.prefetch()`
27
+ (a no-op), `SFTPFile.chown()`, `SFTPAttributes.attr` (extended attributes,
28
+ previously discarded) and `SFTPAttributes.from_stat()`.
29
+ - A garbage-collected `SFTPFile` sends its buffered writes and closes its
30
+ handle without waiting for replies.
31
+ - Documentation site (Sphinx, with a reference generated from the
32
+ docstrings), ready for Read the Docs; `CONTRIBUTING.md` and `SECURITY.md`.
33
+ - Tests that fail when a public member lacks a docstring or an entry in
34
+ `docs/api.md`, or when a code sample in the docs does not compile.
35
+ - A release workflow that publishes to PyPI when a `v*` tag is pushed.
36
+
37
+ ### Changed
38
+ - Type hints pass `mypy --strict`; CI checks them, along with the docs build.
39
+ Stream arguments are typed `IO[bytes]` and `env` as `Dict[str, str]`.
40
+
41
+ ### Fixed
42
+ - `put_dir` now gives small files exactly their local permission bits. The
43
+ server's umask could strip bits from them before, for example group write.
44
+ - The docs gave OpenSSH 9.6 as the first version with
45
+ `users-groups-by-id@openssh.com`; it is 9.1.
46
+
47
+ ### Security
48
+ - `connect()` / `build_ssh_argv()` refuse a host that is empty or starts with
49
+ `-`. Before, a host such as `-oProxyCommand=…` from untrusted input was read
50
+ by ssh as an option and could run a local command.
51
+
52
+ ## [0.2.0] - 2026-10-18
53
+
54
+ Brings the client up to date with the SFTP code in sshPilot, adds the
55
+ convenience API a standalone library needs, and documents all of it.
56
+
57
+ ### Added
58
+ - Pipelined transfers: reads and writes keep a window of requests in flight
59
+ that grows from the measured round-trip time. Chunk sizes come from the
60
+ server's `limits@openssh.com`. A server that returns less than it
61
+ advertised gets the missing pieces fetched together.
62
+ - Batch operations that cost about one round trip per 100 paths:
63
+ `stat_many`, `listdir_many`, `mkdir_many`, `remove_many`, `rmdir_many`,
64
+ `read_small_files`, `atomic_upload_many` (with `AtomicUploadItem`).
65
+ Directory listings keep several READDIRs in flight.
66
+ - `rmtree` (like `rm -r`, never follows symlinks) and `walk` (like `os.walk`).
67
+ - `getfo` / `putfo`, a `confirm` option on `put`/`putfo`, and `copy`
68
+ (server-side with `copy-data`, read + write otherwise).
69
+ - Metadata: `fstat`, `setstat`, `fsetstat`, `chown`, `utime`, `truncate`,
70
+ `symlink`, `readlink`.
71
+ - OpenSSH extensions: `atomic_rename` (falls back when `posix-rename` is
72
+ missing or rejected), `hardlink`, `copy_data`, `statvfs` (`SFTPStatVFS`),
73
+ `users_groups_by_id`, and `supports_*()` checks for each.
74
+ - `SFTPFile`: `seek`, `tell`, `truncate`, `stat`, `chmod`, `utime`,
75
+ `readinto` (so `io.BufferedReader`/`io.TextIOWrapper` can wrap it),
76
+ `readable`/`writable`/`seekable`, `closed`, `handle`.
77
+ - `open(..., create_mode=0o600)` sets a new file's permissions in the open
78
+ request.
79
+ - `SFTPClient.closed`, `SFTPClient.extensions`, `max_read_length`,
80
+ `max_write_length`, `max_open_handles`.
81
+ - `SFTPAttributes.is_file()` and an `ls -l`-style `str()`.
82
+ - Exception subclasses that match Python's built-ins: `SFTPNoSuchFileError`
83
+ (`FileNotFoundError`), `SFTPPermissionError` (`PermissionError`),
84
+ `SFTPConnectionError` (`ConnectionError`).
85
+ - `PipelinedWriter` (via `pipelined_writer`) with an `on_ack` progress hook.
86
+ - Full API reference (`docs/api.md`), an expanded README, and
87
+ `examples/transfer.py`.
88
+ - Tests against the real OpenSSH `sftp-server` binary (including simulated
89
+ high-latency links), and an opt-in end-to-end test through `ssh`
90
+ (`MFATSFTP_TEST_HOST`).
91
+
92
+ ### Changed
93
+ - `get`/`put`/`get_dir`/`put_dir` use the pipelined and batched paths. A
94
+ tree of many small files now costs a few round trips per directory level
95
+ instead of several per file.
96
+ - `put_dir` keeps local permission bits and modification times, and writes
97
+ small files through a temporary name and a rename.
98
+ - `makedirs` now raises if something other than a directory is in the way,
99
+ instead of silently ignoring every error.
100
+ - `open()` modes now follow Python: `r+` no longer creates a missing file,
101
+ `w+` truncates, `x` fails if the file exists, `a` starts at the end, and an
102
+ unknown mode raises `ValueError`.
103
+ - `SFTPFile.write` returns the number of bytes written.
104
+ - `from_command`/`connect`: `timeout` now limits login plus handshake and
105
+ raises `TimeoutError`. The default is `None` (no limit). Before, the
106
+ handshake could hang indefinitely whatever the value.
107
+ - Packaging: SPDX license expression (`GPL-3.0-or-later`) per PEP 639,
108
+ Python 3.14 classifier, ruff configuration. CI lints, builds and checks
109
+ the package, and runs the real-server tests.
110
+ - License changed from MIT to GPL-3.0-or-later.
111
+
112
+ ### Fixed
113
+ - Reads no longer stop short on large files, and writes larger than one
114
+ packet no longer drop the session. OpenSSH caps a READ at about 255 KiB and
115
+ closes the connection on a message over 256 KiB.
116
+ - Filenames that are not valid UTF-8 are kept intact (`surrogateescape`)
117
+ instead of being replaced, so such files can be opened, renamed and
118
+ deleted.
119
+ - `get_dir` refuses server-listed names containing a path separator or equal
120
+ to `.`/`..`, so a hostile server cannot make it write outside the target
121
+ directory.
122
+ - A failed connection attempt now closes the ssh process's pipes.
123
+
124
+ ## [0.1.0] - initial extraction
125
+ ### Added
126
+ - Pure-Python SFTP v3 wire-protocol codec (`mfatsftp.protocol`).
127
+ - `SFTPClient` / `SFTPFile` over a stream pair, with a paramiko-compatible
128
+ surface (`open`/`file`, `stat`/`lstat`, `listdir`/`listdir_attr`,
129
+ `mkdir`/`makedirs`/`rmdir`, `remove`/`unlink`, `rename`/`posix_rename`,
130
+ `chmod`, `realpath`/`normalize`, `get`/`put`/`get_dir`/`put_dir`).
131
+ - `connect()` / `from_command()` / `build_ssh_argv()` transport over the system
132
+ OpenSSH (`ssh -s <host> sftp`), with clean subprocess teardown.
133
+ - In-memory SFTP server test fixture; protocol, client and transport test suites.
134
+
135
+ [Unreleased]: https://github.com/mfat/mfatsftp/compare/v0.3.0...HEAD
136
+ [0.3.0]: https://github.com/mfat/mfatsftp/releases/tag/v0.3.0
137
+ [0.2.0]: https://github.com/mfat/mfatsftp/commit/09978e8
138
+ [0.1.0]: https://github.com/mfat/mfatsftp/commit/aa26356
@@ -0,0 +1,75 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve mfatsftp. Bug reports, fixes and documentation
4
+ improvements are all welcome.
5
+
6
+ ## Setting up
7
+
8
+ ```sh
9
+ git clone https://github.com/mfat/mfatsftp
10
+ cd mfatsftp
11
+ python -m venv .venv && . .venv/bin/activate
12
+ pip install -e ".[dev]"
13
+ ```
14
+
15
+ Most tests drive the real OpenSSH `sftp-server` binary over pipes. Install it
16
+ if it's missing:
17
+
18
+ - Debian/Ubuntu: `sudo apt install openssh-sftp-server`
19
+ - Fedora: `sudo dnf install openssh-server`
20
+ - macOS: already present at `/usr/libexec/sftp-server`
21
+
22
+ Tests that need it are skipped, not failed, when it isn't there.
23
+
24
+ ## Checks
25
+
26
+ These are the checks CI runs. Please run them before opening a pull request:
27
+
28
+ ```sh
29
+ pytest # tests
30
+ ruff check src tests examples # lint
31
+ mypy --strict src/mfatsftp # types (the package ships py.typed)
32
+ sphinx-build -W -b html docs docs/_build/html # docs (pip install -r docs/requirements.txt)
33
+ ```
34
+
35
+ To test a real ssh login as well, point `MFATSFTP_TEST_HOST` at a host you
36
+ can reach without a password prompt (for example `localhost` with your key in
37
+ `~/.ssh/authorized_keys`):
38
+
39
+ ```sh
40
+ MFATSFTP_TEST_HOST=localhost pytest tests/test_live_ssh.py
41
+ ```
42
+
43
+ ## Where things are
44
+
45
+ | Path | What |
46
+ |---|---|
47
+ | `src/mfatsftp/protocol.py` | SFTP v3 wire codec: pure functions, no I/O |
48
+ | `src/mfatsftp/client.py` | `SFTPClient`, `SFTPFile`, `PipelinedWriter`: requests, pipelining, transfers |
49
+ | `src/mfatsftp/transport.py` | `connect` / `from_command`: running `ssh -s host sftp` |
50
+ | `tests/real_server.py` | helpers for tests against `sftp-server`, including a simulated slow link |
51
+ | `tests/fake_server.py` | in-memory SFTP server for protocol-level tests |
52
+ | `docs/` | Sphinx site; `docs/api.md` is the hand-written reference |
53
+
54
+ ## Guidelines
55
+
56
+ - **Tests:** add a test for every fix or feature. Prefer the real-server
57
+ fixture (`client` in `tests/conftest.py`) over the fake server.
58
+ - **Speed:** if you change how requests are sent, check the round trips with
59
+ the simulated slow link (`start_client(delay)`), as in
60
+ `tests/test_large_files.py`.
61
+ - **Docs:** public methods need a docstring and an entry in `docs/api.md`.
62
+ `tests/test_docs.py` fails otherwise. User-visible changes go under
63
+ "Unreleased" in `CHANGELOG.md`.
64
+ - **Dependencies:** the package has no runtime dependencies beyond the
65
+ standard library, and it should stay that way.
66
+ - **Compatibility:** Python 3.9+ on Linux and macOS.
67
+
68
+ ## Reporting bugs
69
+
70
+ Open an issue with your Python version, OS, `ssh -V` output, the server
71
+ software if known, and a minimal script that shows the problem. For security
72
+ problems, see [SECURITY.md](https://github.com/mfat/mfatsftp/blob/main/SECURITY.md) instead.
73
+
74
+ By contributing you agree that your contributions are licensed under the
75
+ project's license (GPL-3.0-or-later).