pathlib-next 0.4.1__tar.gz → 0.5.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.
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/.gitignore +3 -0
- pathlib_next-0.5.0/CHANGELOG.md +215 -0
- pathlib_next-0.5.0/PKG-INFO +203 -0
- pathlib_next-0.5.0/README.md +168 -0
- pathlib_next-0.5.0/docs/divergences.md +61 -0
- pathlib_next-0.5.0/docs/guides/extending.md +121 -0
- pathlib_next-0.5.0/docs/guides/schemes.md +39 -0
- pathlib_next-0.5.0/docs/index.md +85 -0
- pathlib_next-0.5.0/examples/http_listing.py +38 -0
- pathlib_next-0.5.0/examples/local_and_mem.py +63 -0
- pathlib_next-0.5.0/examples/sftp_sync.py +57 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/mkdocs.yml +4 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/pyproject.toml +2 -2
- pathlib_next-0.5.0/src/pathlib_next/fspath.py +135 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/mempath.py +67 -17
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/path.py +148 -25
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/protocols/fs.py +10 -5
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/protocols/io.py +13 -4
- pathlib_next-0.5.0/src/pathlib_next/testing.py +132 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/__init__.py +64 -13
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/query.py +41 -2
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/schemes/file.py +7 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/schemes/http.py +34 -6
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/schemes/sftp.py +26 -4
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/source.py +11 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/utils/__init__.py +13 -5
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/utils/glob.py +42 -4
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/utils/stat.py +5 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/utils/sync.py +15 -0
- pathlib_next-0.5.0/tests/conftest.py +63 -0
- pathlib_next-0.5.0/tests/test_contract.py +29 -0
- pathlib_next-0.5.0/tests/test_glob.py +91 -0
- pathlib_next-0.5.0/tests/test_http.py +70 -0
- pathlib_next-0.5.0/tests/test_mempath.py +155 -0
- pathlib_next-0.5.0/tests/test_parity_io.py +159 -0
- pathlib_next-0.5.0/tests/test_parity_pure.py +102 -0
- pathlib_next-0.5.0/tests/test_pathname.py +136 -0
- pathlib_next-0.5.0/tests/test_query.py +45 -0
- pathlib_next-0.5.0/tests/test_sftp.py +158 -0
- pathlib_next-0.5.0/tests/test_smoke.py +95 -0
- pathlib_next-0.5.0/tests/test_source.py +62 -0
- pathlib_next-0.5.0/tests/test_sync.py +122 -0
- pathlib_next-0.4.1/tests/test_uri.py → pathlib_next-0.5.0/tests/test_uri_parse.py +47 -0
- pathlib_next-0.5.0/tests/test_uri_path.py +100 -0
- pathlib_next-0.5.0/tests/test_utils.py +68 -0
- pathlib_next-0.4.1/CHANGELOG.md +0 -39
- pathlib_next-0.4.1/PKG-INFO +0 -113
- pathlib_next-0.4.1/README.md +0 -79
- pathlib_next-0.4.1/docs/index.md +0 -29
- pathlib_next-0.4.1/examples/example.py +0 -70
- pathlib_next-0.4.1/src/pathlib_next/fspath.py +0 -85
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/LICENSE +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/docs/api/reference.md +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/docs/changelog.md +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/__init__.py +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/protocols/__init__.py +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/py.typed +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/src/pathlib_next/uri/schemes/__init__.py +0 -0
- {pathlib_next-0.4.1 → pathlib_next-0.5.0}/tests/test_local.py +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.5.0] - 2026-07-11
|
|
11
|
+
|
|
12
|
+
### Fixed (critical -- found while writing Phase 6 examples)
|
|
13
|
+
- `Path("...")` -- the top-level dispatcher documented in this project's
|
|
14
|
+
own README quick start and used throughout -- silently dropped its
|
|
15
|
+
constructor arguments on Python <3.12, leaving a blank instance that
|
|
16
|
+
crashed with `AttributeError: _drv` the moment anything touched it (e.g.
|
|
17
|
+
the `/` operator). Masked on 3.12+, where the real parsing happens in
|
|
18
|
+
`__init__` (called separately, with the original args, regardless of what
|
|
19
|
+
`__new__` did) rather than `__new__` itself. Every one of Phase 5's 300
|
|
20
|
+
tests constructed via `LocalPath(...)` directly instead, so this went
|
|
21
|
+
undetected until `examples/local_and_mem.py` exercised the documented
|
|
22
|
+
`Path(...)` entry point end to end.
|
|
23
|
+
|
|
24
|
+
### Fixed (found by the new Phase 5 test suite, not in the original bug list)
|
|
25
|
+
- `LocalPath.stat()`/`chmod()` inherit directly from `pathlib.Path` via MRO
|
|
26
|
+
and crashed with `TypeError` on Python 3.9 the moment anything passed
|
|
27
|
+
`follow_symlinks=` (e.g. `Path.walk()`'s default `follow_symlinks=False`)
|
|
28
|
+
-- now shimmed with `lstat()`/`lchmod()` on <3.10, same as the existing
|
|
29
|
+
`FileUri` shim (which now just delegates to `LocalPath`).
|
|
30
|
+
- `MemPath.__init__` decided whether to propagate a parent's backend with
|
|
31
|
+
`if _backend and backend is None:` -- an empty (but valid) backend dict is
|
|
32
|
+
falsy, so joining off a freshly-created, empty `MemPath` silently gave the
|
|
33
|
+
child a disconnected new backend instead of sharing the parent's.
|
|
34
|
+
- `MemPath.stat()` never set `st_size` for files (always defaulted to `0`),
|
|
35
|
+
breaking any size-based checksum comparison (notably `PathSyncer`'s
|
|
36
|
+
typical usage).
|
|
37
|
+
- `glob()`'s core algorithm decided whether to recurse into the *parent*
|
|
38
|
+
directory using whether the *leaf* segment is a wildcard, instead of
|
|
39
|
+
whether the *parent path itself* contains one. Since a wildcarded leaf
|
|
40
|
+
with a literal parent directory is the overwhelmingly common case
|
|
41
|
+
(`glob("*.py")`), this always took the "recurse into parent" branch,
|
|
42
|
+
which only degenerated back to the correct single directory when the
|
|
43
|
+
parent has a non-empty literal name to re-match against -- true for
|
|
44
|
+
essentially every real filesystem path except an OS root. It silently
|
|
45
|
+
returned the wrong result on `MemPath`'s virtual root (empty name).
|
|
46
|
+
- `HttpPath.iterdir()` gave every subdirectory entry an empty `.name`:
|
|
47
|
+
directory-listing entries for subdirectories carry a trailing `/`
|
|
48
|
+
(`htmllistparse`'s convention), which wasn't stripped before building the
|
|
49
|
+
child's path, and `Pathname.name` derives from the last path segment --
|
|
50
|
+
empty for a trailing-slash path.
|
|
51
|
+
- `SftpPath.rename()` resolved a plain string target relative to `self`
|
|
52
|
+
(joining it as a child, e.g. `"/a.txt".rename("b.txt")` produced
|
|
53
|
+
`"/a.txt/b.txt"`) instead of `self`'s parent (sibling rename).
|
|
54
|
+
|
|
55
|
+
### Added (test suite)
|
|
56
|
+
- Full pytest suite (`tests/`): pure-path parity against `pathlib.PurePosixPath`
|
|
57
|
+
(`test_parity_pure.py`), local I/O parity against `pathlib.Path`/`os.walk`
|
|
58
|
+
(`test_parity_io.py`), a reusable filesystem-contract mixin run against
|
|
59
|
+
`LocalPath`/`MemPath`/`FileUri` and exported as `pathlib_next.testing.
|
|
60
|
+
PathContract` for third-party `Path`/`UriPath` implementers
|
|
61
|
+
(`test_contract.py`), glob vs. stdlib ground truth (`test_glob.py`), URI
|
|
62
|
+
parsing/scheme-dispatch/query/source coverage, MemPath- and SFTP-specific
|
|
63
|
+
unit tests (SFTP mocked, no real server), HTTP tests against a real stdlib
|
|
64
|
+
`ThreadingHTTPServer`, and `PathSyncer` coverage. 300 tests, ~85% line
|
|
65
|
+
coverage, green on both Python 3.9 and 3.13.
|
|
66
|
+
|
|
67
|
+
### Added (docs)
|
|
68
|
+
- `docs/guides/schemes.md` (capability matrix per scheme) and
|
|
69
|
+
`docs/guides/extending.md` (both extension tracks, with worked examples
|
|
70
|
+
and `pathlib_next.testing.PathContract` usage). Rewrote `docs/index.md`
|
|
71
|
+
and the README with a 30-second example per scheme and a capability
|
|
72
|
+
matrix. Class-level docstrings added across the package for the rendered
|
|
73
|
+
API reference.
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- `examples/example.py` (an unstructured scratch script) split into three
|
|
77
|
+
focused, runnable examples: `examples/local_and_mem.py` (self-contained,
|
|
78
|
+
no network), `examples/http_listing.py` and `examples/sftp_sync.py`
|
|
79
|
+
(network-touching, guarded under `if __name__ == "__main__"`,
|
|
80
|
+
configurable via env vars, fail soft when unreachable/unconfigured).
|
|
81
|
+
|
|
82
|
+
### Added
|
|
83
|
+
- `Pathname.joinpath()`, `Pathname.full_match()` (3.13 parity, supports `**`
|
|
84
|
+
matching any number of segments), `Pathname.anchor`/`drive`/`root`
|
|
85
|
+
(generic derivation for non-local paths), `Path.rglob()`,
|
|
86
|
+
`read_text(..., newline=)` (3.13 parity), `Path.samefile()` (default
|
|
87
|
+
`st_dev`/`st_ino` comparison when the backend's `stat()` provides them,
|
|
88
|
+
`NotImplementedError` otherwise).
|
|
89
|
+
- `Path.glob()`/`LocalPath.glob()`: `recursive=` now auto-detects (`True` if
|
|
90
|
+
the pattern has a `"**"` component) instead of defaulting to `False`;
|
|
91
|
+
explicit `recursive=True`/`False` still overrides.
|
|
92
|
+
- `Path.copy()`: raises `IsADirectoryError` when the target is an existing
|
|
93
|
+
directory (previously misbehaved); gained `follow_symlinks=`/
|
|
94
|
+
`preserve_metadata=` kwargs, named to match CPython 3.14's `Path.copy()`.
|
|
95
|
+
- `docs/divergences.md`: registry of every deliberate behavioral divergence
|
|
96
|
+
from `pathlib`, with rationale. Linked from the docs nav.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
- `Path.mkdir(parents=True)` created intermediate parents with `exist_ok=False`
|
|
100
|
+
(racy, and wrong when a parent already existed) and dropped the caller's
|
|
101
|
+
`exist_ok` on the final retry.
|
|
102
|
+
- `Path.touch(exist_ok=False)` silently truncated an existing file instead of
|
|
103
|
+
raising `FileExistsError` (pathlib parity).
|
|
104
|
+
- `LocalPath.glob()`'s `dironly` parameter defaulted to `False`, which made the
|
|
105
|
+
`is None` check for trailing-slash directory-only detection dead code.
|
|
106
|
+
- `Stat._st_mode()` only caught `FileNotFoundError`, letting `PermissionError`
|
|
107
|
+
and other `OSError`s propagate out of `exists()`/`is_dir()`/etc. where pathlib
|
|
108
|
+
returns `False`. Also fixed: `follow_symlinks` was accepted but never
|
|
109
|
+
forwarded to the underlying `stat()` call, so `is_symlink()` never actually
|
|
110
|
+
inspected the symlink itself.
|
|
111
|
+
- `MemPath._open()` treated any mode other than `"w"` as a read, so `"a"`/`"x"`
|
|
112
|
+
silently misbehaved; now dispatches `r`/`w`/`x`/`a` correctly and raises
|
|
113
|
+
`NotImplementedError` for anything else. `MemBytesIO.close()` used
|
|
114
|
+
`seek(0);read()` instead of `getvalue()`, losing content if the caller's
|
|
115
|
+
cursor wasn't already at position 0 when closing.
|
|
116
|
+
- `MemPath.normalized` mangled `".."`-escaping paths (e.g. `".."`) into `"."`;
|
|
117
|
+
now normalizes against a virtual root so they clamp at the root instead.
|
|
118
|
+
- `PathAndStat.__getattr__()` returned `None` for any unrecognized attribute
|
|
119
|
+
instead of raising `AttributeError`, breaking `hasattr()`-based logic.
|
|
120
|
+
- `parsedate(None)` / an unparseable date string returned "now" instead of
|
|
121
|
+
epoch 0, which could poison `PathSyncer`'s checksum/freshness comparisons for
|
|
122
|
+
HTTP sources with no `Last-Modified` header.
|
|
123
|
+
- `HttpPath.stat()` used a bare `except:`; cached `_isdir` from a response that
|
|
124
|
+
hadn't been confirmed successful yet (including 404s); and didn't fall back
|
|
125
|
+
to GET when a server rejected `HEAD` with 405.
|
|
126
|
+
- `uri.Query` no longer depends on `uritools`' private `_querydict`/`_querylist`
|
|
127
|
+
helpers (reimplemented locally against the public `uriencode()`).
|
|
128
|
+
- `Uri` join (`_load_parts`): `query`/`fragment` are now resolved with the same
|
|
129
|
+
"last segment that actually sets one wins" rule already used for `source`
|
|
130
|
+
(previously any segment, even one with no query/fragment, would blank out an
|
|
131
|
+
earlier segment's). Join semantics are now documented explicitly:
|
|
132
|
+
pathlib-`joinpath`-like, not RFC 3986 reference resolution, `..` is never
|
|
133
|
+
resolved during join.
|
|
134
|
+
- `Source.is_local()` (DNS lookup) and `get_machine_ips()` are now
|
|
135
|
+
`functools.lru_cache`d -- previously ran on every call.
|
|
136
|
+
|
|
137
|
+
### Fixed (crash-level bugs)
|
|
138
|
+
- `MemPath.stat()`/`MemPath._open()` returned a `FileNotFoundError` instance instead
|
|
139
|
+
of raising it for a missing path, causing an unrelated `AttributeError` downstream.
|
|
140
|
+
- `LRU.invalidate()` called `self.lock()` instead of using `self.lock` as a context
|
|
141
|
+
manager (`RLock` isn't callable) -- broke the SFTP client reconnect path.
|
|
142
|
+
- `Pathname.match()` had reversed `isinstance()` arguments and compared against
|
|
143
|
+
`str(self)` (which includes scheme/host for `Uri`) instead of `as_posix()`.
|
|
144
|
+
- Glob wildcard detection (`WILCARD_PATTERN`, renamed `WILDCARD_PATTERN`, old name
|
|
145
|
+
kept as an alias) used `.match()` (anchored) instead of `.search()`, so patterns
|
|
146
|
+
like `"foo*"` weren't recognized as wildcards.
|
|
147
|
+
- `Uri` was unhashable (defined `__eq__` without `__hash__`); `__eq__` now also
|
|
148
|
+
returns `NotImplemented` for non-`Pathname`/`str` operands instead of raising.
|
|
149
|
+
- `Uri.is_relative_to()` used `str.startswith()` on normalized path strings, so
|
|
150
|
+
`/foo/bar2` was incorrectly reported as relative to `/foo/bar`; now compares
|
|
151
|
+
path segments.
|
|
152
|
+
- `Uri.relative_to(walk_up=True)` was dead code -- an early guard raised
|
|
153
|
+
`ValueError` before the walk-up loop ever ran.
|
|
154
|
+
- `HttpPath.is_dir()`/`is_file()` tested truthiness of bound methods
|
|
155
|
+
(`self._is_dir`, `self.is_dir`) instead of calling/checking the right attribute,
|
|
156
|
+
so both always returned truthy nonsense.
|
|
157
|
+
- `SftpPath.chmod()` didn't accept `follow_symlinks=`, so the inherited `lchmod()`
|
|
158
|
+
crashed with `TypeError`; now raises `NotImplementedError` for
|
|
159
|
+
`follow_symlinks=False` (paramiko has no `lchmod`).
|
|
160
|
+
- `SftpPath` defined `_rename()`, which nothing ever called -- renamed to
|
|
161
|
+
`rename()` so `move()`/`rename()` actually use SFTP's native rename instead of
|
|
162
|
+
silently falling back to copy+unlink for every move.
|
|
163
|
+
- `Uri.__init__()` used a bare `except:` around `Path.as_uri()` (now
|
|
164
|
+
`except ValueError:`, matching what `as_uri()` actually raises for relative
|
|
165
|
+
paths) and crashed with `AttributeError` when constructing from an
|
|
166
|
+
`os.PathLike` that only implements `__fspath__` (no `as_posix()`).
|
|
167
|
+
- `Path.rm(ignore_error=callable)` never actually called the callable -- both
|
|
168
|
+
branches of its error handler returned the callable object itself.
|
|
169
|
+
|
|
170
|
+
### Fixed (Python 3.9/3.10 compatibility)
|
|
171
|
+
- Actual Python 3.9/3.10 runtime compatibility (CI previously only tested 3.11/3.13
|
|
172
|
+
and missed these): `LocalPath`/`Uri` case-sensitivity and path-separator detection
|
|
173
|
+
crashed on 3.9-3.11 (`_flavour` object has no `normcase`); `open(mode="r")` crashed
|
|
174
|
+
on <3.10 (`io.text_encoding` is 3.10+); glob pattern compilation crashed on <3.11
|
|
175
|
+
(`re.NOFLAG` is 3.11+); `FileUri.stat()`/`chmod()` crashed on 3.9
|
|
176
|
+
(`pathlib.Path.stat/chmod` gained `follow_symlinks=` in 3.10; raises
|
|
177
|
+
`NotImplementedError` there for `follow_symlinks=False`).
|
|
178
|
+
- `LocalPath._path_separators` returned the env-var list separator (`;`/`:`) instead
|
|
179
|
+
of the path separator, and could include a `None` altsep on POSIX.
|
|
180
|
+
|
|
181
|
+
### Added
|
|
182
|
+
- `tests/test_smoke.py`: regression coverage for README/example snippets across
|
|
183
|
+
supported Python versions.
|
|
184
|
+
|
|
185
|
+
## [0.4.1] - 2026-07-11
|
|
186
|
+
|
|
187
|
+
### Fixed
|
|
188
|
+
- Removed explicit `[tool.hatch.build.targets.wheel]` packages config that caused hatchling to fail resolving `README.md` during editable installs on CI.
|
|
189
|
+
- Converted `README.md` from a symlink (mode `120000`) to a regular file, fixing `git checkout` failures on macOS and Windows runners.
|
|
190
|
+
- Removed agent-tooling references (`AGENTS.md`, `PYTHON.md`) from committed files.
|
|
191
|
+
|
|
192
|
+
## [0.4.0] - 2026-07-11
|
|
193
|
+
|
|
194
|
+
### Added
|
|
195
|
+
- Standardized repository layout and relocated examples to `examples/` directory.
|
|
196
|
+
- Configured MkDocs documentation site with dynamic API reference using `mkdocstrings`.
|
|
197
|
+
- Added GitHub Actions workflows for matrix testing (`test.yml`) and release pipelines (`release.yml`).
|
|
198
|
+
- Added typing marker `py.typed` for PEP 561 compliance.
|
|
199
|
+
|
|
200
|
+
### Changed
|
|
201
|
+
- Added backward compatibility support for Python 3.9 and 3.10: added `from __future__ import annotations` across the codebase, refactored runtime-evaluated union types to use `typing.Union`, and provided fallbacks for `TypeAlias` and `ParamSpec`.
|
|
202
|
+
- Updated package requirement to `requires-python = ">=3.9"`.
|
|
203
|
+
|
|
204
|
+
## [0.3.5] - 2026-07-11
|
|
205
|
+
|
|
206
|
+
### Added
|
|
207
|
+
- Split path into protocols that can be standalone.
|
|
208
|
+
- Sync error handling.
|
|
209
|
+
- Generic Path Protocol based pathlib implementation for URI paths with file access support for sftp, http, file schemes.
|
|
210
|
+
|
|
211
|
+
[Unreleased]: https://github.com/jose-pr/pathlib_next/compare/v0.5.0...HEAD
|
|
212
|
+
[0.5.0]: https://github.com/jose-pr/pathlib_next/compare/v0.4.1...v0.5.0
|
|
213
|
+
[0.4.1]: https://github.com/jose-pr/pathlib_next/compare/v0.4.0...v0.4.1
|
|
214
|
+
[0.4.0]: https://github.com/jose-pr/pathlib_next/releases/tag/v0.4.0
|
|
215
|
+
[0.3.5]: https://github.com/jose-pr/pathlib_next/releases/tag/v0.3.5
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pathlib_next
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Generic Path Protocol based pathlib
|
|
5
|
+
Project-URL: Homepage, https://github.com/jose-pr/pathlib_next/
|
|
6
|
+
Project-URL: Documentation, https://jose-pr.github.io/pathlib_next/
|
|
7
|
+
Project-URL: Issues, https://github.com/jose-pr/pathlib_next/issues
|
|
8
|
+
Author: Jose A
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: build; extra == 'dev'
|
|
16
|
+
Requires-Dist: hatchling; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
18
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
19
|
+
Requires-Dist: twine; extra == 'dev'
|
|
20
|
+
Provides-Extra: docs
|
|
21
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
22
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
23
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
24
|
+
Provides-Extra: http
|
|
25
|
+
Requires-Dist: bs4; extra == 'http'
|
|
26
|
+
Requires-Dist: htmllistparse; extra == 'http'
|
|
27
|
+
Requires-Dist: requests; extra == 'http'
|
|
28
|
+
Requires-Dist: uritools; extra == 'http'
|
|
29
|
+
Provides-Extra: sftp
|
|
30
|
+
Requires-Dist: paramiko; extra == 'sftp'
|
|
31
|
+
Requires-Dist: uritools; extra == 'sftp'
|
|
32
|
+
Provides-Extra: uri
|
|
33
|
+
Requires-Dist: uritools; extra == 'uri'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# pathlib_next
|
|
37
|
+
|
|
38
|
+
[](https://pypi.org/project/pathlib_next/)
|
|
39
|
+
[](https://pypi.org/project/pathlib_next/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[](https://jose-pr.github.io/pathlib_next/)
|
|
42
|
+
[](https://github.com/jose-pr/pathlib_next/actions/workflows/test.yml)
|
|
43
|
+
|
|
44
|
+
A **robust, extensible pathlib-like base** for any resource addressable as a
|
|
45
|
+
path or URI. Same method names, signatures, semantics, and exception types as
|
|
46
|
+
`pathlib.Path` wherever a `pathlib.Path` equivalent exists -- write code once
|
|
47
|
+
against `Path`/`UriPath` and it works against your local disk, an in-memory
|
|
48
|
+
tree, an HTTP index, or an SFTP server. Every intentional divergence from
|
|
49
|
+
`pathlib`'s behavior is documented, not silent -- see
|
|
50
|
+
[`docs/divergences.md`](https://jose-pr.github.io/pathlib_next/divergences/).
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
| Capability | `LocalPath` | `file:` | `mem:` (`MemPath`) | `http(s):` | `sftp:` |
|
|
55
|
+
| --- | --- | --- | --- | --- | --- |
|
|
56
|
+
| Read | Yes | Yes | Yes | Yes | Yes |
|
|
57
|
+
| Write | Yes | Yes | Yes | No | Yes |
|
|
58
|
+
| List (`iterdir`) | Yes | Yes | Yes | Yes (HTML index) | Yes |
|
|
59
|
+
| Stat / exists / is_dir / is_file | Yes | Yes | Yes | Yes | Yes |
|
|
60
|
+
| `mkdir` | Yes | Yes | Yes | No | Yes |
|
|
61
|
+
| Delete | Yes | Yes | Yes | No | Yes |
|
|
62
|
+
| `rename` | Yes | Yes | No (copy+unlink fallback) | No | Yes |
|
|
63
|
+
| Extra required | none | none | none | `http` | `sftp` |
|
|
64
|
+
|
|
65
|
+
Every scheme shares the same `glob()`, `walk()`, `copy()`/`move()`, `rm()`
|
|
66
|
+
implementations -- see the full matrix and notes in
|
|
67
|
+
[Schemes](https://jose-pr.github.io/pathlib_next/guides/schemes/).
|
|
68
|
+
|
|
69
|
+
- **Unified path interface** across local files, in-memory paths, and
|
|
70
|
+
`sftp`/`http`/`file` URIs.
|
|
71
|
+
- **`MemPath`** -- a lightweight virtual filesystem for mocks, tests, or
|
|
72
|
+
transient storage.
|
|
73
|
+
- **`PathSyncer`** -- one-way checksum-driven tree sync between any two
|
|
74
|
+
`Path` implementations, with dry-run and event hooks.
|
|
75
|
+
- **`Query`/`Source`** -- parse and serialize URL query strings and URI
|
|
76
|
+
authority components.
|
|
77
|
+
- **Extensible two ways**: subclass `Path` directly for a custom
|
|
78
|
+
non-URI resource, or subclass `UriPath` for a new URI scheme -- see
|
|
79
|
+
[Extending](https://jose-pr.github.io/pathlib_next/guides/extending/).
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install pathlib_next
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Optional features/extras:
|
|
88
|
+
|
|
89
|
+
| Extra/flag | Adds | Needed for |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `uri` | `uritools` | URI parsing capabilities |
|
|
92
|
+
| `http` | `requests`, `bs4`, `htmllistparse` | Read and list files over HTTP/HTTPS |
|
|
93
|
+
| `sftp` | `paramiko` | SFTP path operations and transfers |
|
|
94
|
+
|
|
95
|
+
`import pathlib_next` and `LocalPath`/`MemPath` work with no extras
|
|
96
|
+
installed.
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
|
|
100
|
+
**Local filesystem** -- drop-in `pathlib.Path`:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from pathlib_next import Path
|
|
104
|
+
|
|
105
|
+
p = Path("./data") / "report.txt"
|
|
106
|
+
p.write_text("hello")
|
|
107
|
+
print(p.read_text())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**In-memory** (`mem:`) -- a virtual filesystem, no disk I/O:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from pathlib_next.mempath import MemPath
|
|
114
|
+
|
|
115
|
+
p = MemPath("/config/settings.json")
|
|
116
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
p.write_text('{"debug": true}')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**`file:`** -- the same local filesystem, addressed as a URI:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pathlib_next.uri import UriPath
|
|
124
|
+
|
|
125
|
+
p = UriPath("file:./data/report.txt")
|
|
126
|
+
print(p.read_text())
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**`http(s):`** -- read files and list Apache/nginx-style directory indexes:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from pathlib_next.uri import UriPath
|
|
133
|
+
|
|
134
|
+
p = UriPath("http://example.com/data/")
|
|
135
|
+
for child in p.iterdir():
|
|
136
|
+
if child.is_file():
|
|
137
|
+
print(child.name, child.stat().st_size)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**`sftp:`** -- same interface, over SSH:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from pathlib_next.uri import UriPath
|
|
144
|
+
|
|
145
|
+
p = UriPath("sftp://user@host/var/log/app.log")
|
|
146
|
+
print(p.read_text())
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Extending
|
|
150
|
+
|
|
151
|
+
Two first-class ways to add a new path-addressable resource -- both covered
|
|
152
|
+
in depth, with worked examples, in
|
|
153
|
+
[Extending](https://jose-pr.github.io/pathlib_next/guides/extending/):
|
|
154
|
+
|
|
155
|
+
- Subclass `Path` directly for a custom, non-URI resource (`MemPath` is the
|
|
156
|
+
reference exemplar).
|
|
157
|
+
- Subclass `UriPath` and set `__SCHEMES` for a new URI scheme (`FileUri`/
|
|
158
|
+
`HttpPath`/`SftpPath` are the built-in examples).
|
|
159
|
+
|
|
160
|
+
`pathlib_next.testing.PathContract` is a reusable pytest mixin covering the
|
|
161
|
+
baseline contract every implementation must satisfy -- subclass it with a
|
|
162
|
+
`root` fixture to verify your own.
|
|
163
|
+
|
|
164
|
+
## API overview
|
|
165
|
+
|
|
166
|
+
| Module/Package | Purpose |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `pathlib_next.path` | Base Path implementation and protocols |
|
|
169
|
+
| `pathlib_next.uri` | URI/URL specific path support and Query utils |
|
|
170
|
+
| `pathlib_next.mempath` | In-memory transient path structure |
|
|
171
|
+
| `pathlib_next.utils.sync` | Synchronization functions and PathSyncer class |
|
|
172
|
+
| `pathlib_next.testing` | `PathContract`, a pytest mixin for verifying custom implementations |
|
|
173
|
+
|
|
174
|
+
## Supported Python versions
|
|
175
|
+
|
|
176
|
+
Python >= 3.9, tested on 3.9 and 3.13 in CI (see
|
|
177
|
+
[`.github/workflows/test.yml`](.github/workflows/test.yml)).
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
pip install -e ".[dev,uri,http,sftp]"
|
|
183
|
+
pytest -q
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
If you maintain separate virtual environments per Python version locally
|
|
187
|
+
(e.g. `.venv/3.9/`, `.venv/3.13/`), run the same `pytest -q` in each --
|
|
188
|
+
CI does the equivalent across Python 3.9/3.13 on Linux, macOS, and Windows.
|
|
189
|
+
|
|
190
|
+
### Releasing
|
|
191
|
+
|
|
192
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
193
|
+
[`CHANGELOG.md`](CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
194
|
+
workflow: test gate → build → publish → docs deploy.
|
|
195
|
+
|
|
196
|
+
### Documentation site
|
|
197
|
+
|
|
198
|
+
MkDocs builds the API reference from `docs/`, published on every
|
|
199
|
+
release. To preview locally: `mkdocs serve`.
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# pathlib_next
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pathlib_next/)
|
|
4
|
+
[](https://pypi.org/project/pathlib_next/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://jose-pr.github.io/pathlib_next/)
|
|
7
|
+
[](https://github.com/jose-pr/pathlib_next/actions/workflows/test.yml)
|
|
8
|
+
|
|
9
|
+
A **robust, extensible pathlib-like base** for any resource addressable as a
|
|
10
|
+
path or URI. Same method names, signatures, semantics, and exception types as
|
|
11
|
+
`pathlib.Path` wherever a `pathlib.Path` equivalent exists -- write code once
|
|
12
|
+
against `Path`/`UriPath` and it works against your local disk, an in-memory
|
|
13
|
+
tree, an HTTP index, or an SFTP server. Every intentional divergence from
|
|
14
|
+
`pathlib`'s behavior is documented, not silent -- see
|
|
15
|
+
[`docs/divergences.md`](https://jose-pr.github.io/pathlib_next/divergences/).
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
| Capability | `LocalPath` | `file:` | `mem:` (`MemPath`) | `http(s):` | `sftp:` |
|
|
20
|
+
| --- | --- | --- | --- | --- | --- |
|
|
21
|
+
| Read | Yes | Yes | Yes | Yes | Yes |
|
|
22
|
+
| Write | Yes | Yes | Yes | No | Yes |
|
|
23
|
+
| List (`iterdir`) | Yes | Yes | Yes | Yes (HTML index) | Yes |
|
|
24
|
+
| Stat / exists / is_dir / is_file | Yes | Yes | Yes | Yes | Yes |
|
|
25
|
+
| `mkdir` | Yes | Yes | Yes | No | Yes |
|
|
26
|
+
| Delete | Yes | Yes | Yes | No | Yes |
|
|
27
|
+
| `rename` | Yes | Yes | No (copy+unlink fallback) | No | Yes |
|
|
28
|
+
| Extra required | none | none | none | `http` | `sftp` |
|
|
29
|
+
|
|
30
|
+
Every scheme shares the same `glob()`, `walk()`, `copy()`/`move()`, `rm()`
|
|
31
|
+
implementations -- see the full matrix and notes in
|
|
32
|
+
[Schemes](https://jose-pr.github.io/pathlib_next/guides/schemes/).
|
|
33
|
+
|
|
34
|
+
- **Unified path interface** across local files, in-memory paths, and
|
|
35
|
+
`sftp`/`http`/`file` URIs.
|
|
36
|
+
- **`MemPath`** -- a lightweight virtual filesystem for mocks, tests, or
|
|
37
|
+
transient storage.
|
|
38
|
+
- **`PathSyncer`** -- one-way checksum-driven tree sync between any two
|
|
39
|
+
`Path` implementations, with dry-run and event hooks.
|
|
40
|
+
- **`Query`/`Source`** -- parse and serialize URL query strings and URI
|
|
41
|
+
authority components.
|
|
42
|
+
- **Extensible two ways**: subclass `Path` directly for a custom
|
|
43
|
+
non-URI resource, or subclass `UriPath` for a new URI scheme -- see
|
|
44
|
+
[Extending](https://jose-pr.github.io/pathlib_next/guides/extending/).
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install pathlib_next
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Optional features/extras:
|
|
53
|
+
|
|
54
|
+
| Extra/flag | Adds | Needed for |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| `uri` | `uritools` | URI parsing capabilities |
|
|
57
|
+
| `http` | `requests`, `bs4`, `htmllistparse` | Read and list files over HTTP/HTTPS |
|
|
58
|
+
| `sftp` | `paramiko` | SFTP path operations and transfers |
|
|
59
|
+
|
|
60
|
+
`import pathlib_next` and `LocalPath`/`MemPath` work with no extras
|
|
61
|
+
installed.
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
**Local filesystem** -- drop-in `pathlib.Path`:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from pathlib_next import Path
|
|
69
|
+
|
|
70
|
+
p = Path("./data") / "report.txt"
|
|
71
|
+
p.write_text("hello")
|
|
72
|
+
print(p.read_text())
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**In-memory** (`mem:`) -- a virtual filesystem, no disk I/O:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from pathlib_next.mempath import MemPath
|
|
79
|
+
|
|
80
|
+
p = MemPath("/config/settings.json")
|
|
81
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
p.write_text('{"debug": true}')
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**`file:`** -- the same local filesystem, addressed as a URI:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pathlib_next.uri import UriPath
|
|
89
|
+
|
|
90
|
+
p = UriPath("file:./data/report.txt")
|
|
91
|
+
print(p.read_text())
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**`http(s):`** -- read files and list Apache/nginx-style directory indexes:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from pathlib_next.uri import UriPath
|
|
98
|
+
|
|
99
|
+
p = UriPath("http://example.com/data/")
|
|
100
|
+
for child in p.iterdir():
|
|
101
|
+
if child.is_file():
|
|
102
|
+
print(child.name, child.stat().st_size)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**`sftp:`** -- same interface, over SSH:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from pathlib_next.uri import UriPath
|
|
109
|
+
|
|
110
|
+
p = UriPath("sftp://user@host/var/log/app.log")
|
|
111
|
+
print(p.read_text())
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Extending
|
|
115
|
+
|
|
116
|
+
Two first-class ways to add a new path-addressable resource -- both covered
|
|
117
|
+
in depth, with worked examples, in
|
|
118
|
+
[Extending](https://jose-pr.github.io/pathlib_next/guides/extending/):
|
|
119
|
+
|
|
120
|
+
- Subclass `Path` directly for a custom, non-URI resource (`MemPath` is the
|
|
121
|
+
reference exemplar).
|
|
122
|
+
- Subclass `UriPath` and set `__SCHEMES` for a new URI scheme (`FileUri`/
|
|
123
|
+
`HttpPath`/`SftpPath` are the built-in examples).
|
|
124
|
+
|
|
125
|
+
`pathlib_next.testing.PathContract` is a reusable pytest mixin covering the
|
|
126
|
+
baseline contract every implementation must satisfy -- subclass it with a
|
|
127
|
+
`root` fixture to verify your own.
|
|
128
|
+
|
|
129
|
+
## API overview
|
|
130
|
+
|
|
131
|
+
| Module/Package | Purpose |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `pathlib_next.path` | Base Path implementation and protocols |
|
|
134
|
+
| `pathlib_next.uri` | URI/URL specific path support and Query utils |
|
|
135
|
+
| `pathlib_next.mempath` | In-memory transient path structure |
|
|
136
|
+
| `pathlib_next.utils.sync` | Synchronization functions and PathSyncer class |
|
|
137
|
+
| `pathlib_next.testing` | `PathContract`, a pytest mixin for verifying custom implementations |
|
|
138
|
+
|
|
139
|
+
## Supported Python versions
|
|
140
|
+
|
|
141
|
+
Python >= 3.9, tested on 3.9 and 3.13 in CI (see
|
|
142
|
+
[`.github/workflows/test.yml`](.github/workflows/test.yml)).
|
|
143
|
+
|
|
144
|
+
## Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
pip install -e ".[dev,uri,http,sftp]"
|
|
148
|
+
pytest -q
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
If you maintain separate virtual environments per Python version locally
|
|
152
|
+
(e.g. `.venv/3.9/`, `.venv/3.13/`), run the same `pytest -q` in each --
|
|
153
|
+
CI does the equivalent across Python 3.9/3.13 on Linux, macOS, and Windows.
|
|
154
|
+
|
|
155
|
+
### Releasing
|
|
156
|
+
|
|
157
|
+
This project follows [Semantic Versioning](https://semver.org/) and keeps a
|
|
158
|
+
[`CHANGELOG.md`](CHANGELOG.md). Pushing a tag matching `v*` triggers the release
|
|
159
|
+
workflow: test gate → build → publish → docs deploy.
|
|
160
|
+
|
|
161
|
+
### Documentation site
|
|
162
|
+
|
|
163
|
+
MkDocs builds the API reference from `docs/`, published on every
|
|
164
|
+
release. To preview locally: `mkdocs serve`.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Divergences from `pathlib`
|
|
2
|
+
|
|
3
|
+
`pathlib_next` targets `pathlib.Path` parity: same method names, signatures,
|
|
4
|
+
semantics and exception types wherever a `pathlib.Path` equivalent exists.
|
|
5
|
+
Extensions (extra optional kwargs, new methods like `rm()`/`sync`) are
|
|
6
|
+
allowed. Any *behavioral* divergence from `pathlib` on a method that exists
|
|
7
|
+
in both must be listed here -- no silent divergence.
|
|
8
|
+
|
|
9
|
+
`LocalPath` is `pathlib.WindowsPath`/`pathlib.PosixPath` with our `Path` mixed
|
|
10
|
+
in via MRO, so unless noted otherwise it behaves exactly like `pathlib.Path`
|
|
11
|
+
(it inherits the real implementation for anything not explicitly overridden).
|
|
12
|
+
The divergences below apply to `Uri`/`UriPath` and `MemPath`.
|
|
13
|
+
|
|
14
|
+
| Method | pathlib behavior | Our behavior | Why |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| `Uri("a").parent` | `PurePosixPath("a").parent == PurePosixPath(".")` | `Uri("a").parent` has path `""` (`Uri("")`, which round-trips) | `Uri` has no cwd-relative concept of `"."` -- an empty path is the URI-natural "no path" representation. Changing this would make `Uri("")` non-idempotent under `.parent`. |
|
|
17
|
+
| `with_name()` / `with_suffix()` / `with_stem()` on `Uri`/`UriPath` | N/A (pathlib has no query/fragment) | Preserve the URI's query and fragment (implemented via `with_path`, which carries them over) | Deliberate extension: `UriPath("http://h/a?x=1").with_suffix(".txt")` keeping `?x=1` matches how most callers actually want to retarget just the path component of a URL. **User decision, 2026-07-11.** |
|
|
18
|
+
| `Path.__iter__` | `pathlib.Path` is not iterable (no `__iter__`) | `iter(path)` is `path.iterdir()` | Deliberate extension for ergonomic `for child in path:` loops. **Caution:** on remote schemes (http/sftp) this is a network call. **User decision, 2026-07-11.** |
|
|
19
|
+
| `Path.copy(target, ...)` | CPython 3.14 `Path.copy(target, *, follow_symlinks=True, dirs_exist_ok=False, preserve_metadata=False)`; always raises if `target` exists | Ours predates 3.14. Signature: `copy(target, *, overwrite=False, follow_symlinks=True, preserve_metadata=True)`. `overwrite=True` unlinks an existing non-directory target first; `preserve_metadata` defaults to **True** (opposite of 3.14) and only propagates `st_mode`, not timestamps/xattrs | Argument names aligned with 3.14 where cheap; `preserve_metadata=True` default kept for backward compat with this method's pre-existing (pre-3.14-alignment) behavior of always copying the mode bits. Full metadata preservation (timestamps, xattrs) is not implemented. |
|
|
20
|
+
| `Path.move(target, ...)` | Not in `pathlib` at all | Our own extension: tries `rename()`, falls back to copy+unlink | N/A -- pure extension, no pathlib method to diverge from. |
|
|
21
|
+
| `Path.rm(recursive=, missing_ok=, ignore_error=)` | Not in `pathlib` (closest: `shutil.rmtree`) | Our own extension | N/A -- pure extension. |
|
|
22
|
+
| `PathSyncer` / `Query` / `Source` | N/A | Our own extensions | N/A -- pure extensions, no pathlib equivalent. |
|
|
23
|
+
|
|
24
|
+
## Explicitly out of scope (not implemented on `Pathname`/`Path`)
|
|
25
|
+
|
|
26
|
+
These `pathlib.Path` methods are **not** part of the generic `Pathname`/`Path`
|
|
27
|
+
contract because they don't have a portable meaning across arbitrary
|
|
28
|
+
URI/virtual backends (a `MemPath` or `http://` URL has no filesystem-relative
|
|
29
|
+
cwd, no symlinks, no OS-level owner/group). `LocalPath` gets every one of
|
|
30
|
+
these for free from `pathlib.Path` via MRO -- this list only describes what
|
|
31
|
+
`Uri`/`UriPath`/`MemPath` (and custom `Path` subclasses in general) don't get:
|
|
32
|
+
|
|
33
|
+
- `resolve()`, `absolute()` -- no portable notion of "the current working
|
|
34
|
+
directory" or canonicalizing `..`/symlinks for an arbitrary backend.
|
|
35
|
+
- `readlink()`, `symlink_to()`, `hardlink_to()` -- no portable symlink/hardlink
|
|
36
|
+
concept (sftp *could* support these via paramiko, but it's not implemented).
|
|
37
|
+
- `owner()`, `group()` -- no portable uid/gid-to-name mapping.
|
|
38
|
+
- `expanduser()`, `Path.cwd()`, `Path.home()` -- inherently tied to the local
|
|
39
|
+
OS/filesystem, meaningless for a URI or in-memory path.
|
|
40
|
+
- `walk(..., follow_symlinks=True)` symlink-cycle protection -- `walk()`
|
|
41
|
+
itself is implemented (see `Path.walk`), but cycle detection when following
|
|
42
|
+
symlinks is not; only `LocalPath` (via pathlib) protects against symlink
|
|
43
|
+
loops during a followed walk.
|
|
44
|
+
|
|
45
|
+
## Deliberate extensions (new methods/kwargs, not divergences)
|
|
46
|
+
|
|
47
|
+
These don't diverge from any existing pathlib behavior (pathlib has no
|
|
48
|
+
equivalent, or the kwarg is new/optional) -- listed for completeness, not
|
|
49
|
+
because a behavioral decision needed documenting:
|
|
50
|
+
|
|
51
|
+
- `joinpath(*args)`, `rglob(pattern)`, `full_match(pattern)` (3.13 parity),
|
|
52
|
+
`anchor`/`drive`/`root` on `Pathname` (generic derivation: `root` is `"/"`
|
|
53
|
+
when the first segment is empty, else `""`; `drive` is always `""`),
|
|
54
|
+
`read_text(..., newline=)` (3.13 parity) -- all additive, no divergence.
|
|
55
|
+
- `Path.glob()`/`LocalPath.glob()`: `recursive=` defaults to auto-detect
|
|
56
|
+
(`True` if the pattern contains a `"**"` component, else `False`) instead
|
|
57
|
+
of pathlib's implicit-always-recursive-on-`**` with no override. Passing
|
|
58
|
+
`recursive=False`/`True` explicitly always wins over the auto-detect.
|
|
59
|
+
**User decision, 2026-07-11.** `include_hidden=`/`dironly=` are documented
|
|
60
|
+
extensions beyond pathlib's `glob()` signature. **Caution:** on remote
|
|
61
|
+
schemes (http/sftp), a recursive glob walks the whole remote subtree.
|