pathlib-next 0.8.4__tar.gz → 0.8.5__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.8.4 → pathlib_next-0.8.5}/.gitignore +2 -2
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/CHANGELOG.md +21 -1
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/PKG-INFO +1 -1
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/divergences.md +20 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/pyproject.toml +10 -1
- pathlib_next-0.8.5/src/pathlib_next/AGENTS.md +270 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/fspath.py +31 -3
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/path.py +6 -2
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_parity_io.py +11 -2
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_pathname.py +13 -6
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/LICENSE +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/README.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/api/mempath.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/api/path.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/api/testing.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/api/uri.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/api/utils.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/benchmarks.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/changelog.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/guides/cli.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/guides/extending.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/guides/schemes.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/docs/index.md +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/az_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/data_and_archive.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/ftp_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/github_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/gitlab_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/gs_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/http_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/local_and_mem.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/s3_listing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/sftp_sync.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/examples/webdav_roundtrip.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/mkdocs.yml +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/mempath.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/protocols/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/protocols/fs.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/protocols/io.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/py.typed +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/testing.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/tools/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/tools/uripath.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/query.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/_gitrepo.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/archive/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/archive/_base.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/archive/tar.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/archive/zip.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/az.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/data.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/dav.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/file.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/ftp.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/git/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/git/_base.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/git/github.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/git/gitlab.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/github.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/gitlab.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/gs.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/http.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/s3.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/sftp/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/sftp/_asyncssh.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/sftp/_paramiko.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/schemes/sftp/_sshconfig.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/uri/source.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/__init__.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/archive.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/checksum.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/glob.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/stat.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/src/pathlib_next/utils/sync.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/conftest.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_archive_uri.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_az.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_az_fake.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_contract.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_data_uri.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_dav.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_ftp.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_gitrepo.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_glob.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_gs.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_gs_fake.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_http.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_http_live.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_http_parser.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_local.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_mempath.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_parity_pure.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_path_gaps.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_plugins.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_properties.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_query.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_s3.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_sftp.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_sftp_asyncssh.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_smoke.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_source.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_sync.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_uri_parse.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_uri_path.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_uripath_tool.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_utils.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_walk.py +0 -0
- {pathlib_next-0.8.4 → pathlib_next-0.8.5}/tests/test_webdav.py +0 -0
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.5] - 2026-07-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`LocalPath.copy()` and `LocalPath.move()` resolved to the incompatible
|
|
14
|
+
stdlib implementations on Python 3.14.** Python 3.14 added methods with
|
|
15
|
+
those names ahead of `pathlib_next.Path` in `LocalPath`'s MRO, so calls using
|
|
16
|
+
pathlib_next extensions such as `overwrite=` or `recursive=` failed with
|
|
17
|
+
`TypeError`. `LocalPath` now routes both methods explicitly through the
|
|
18
|
+
pathlib_next implementations on every supported Python version.
|
|
19
|
+
- Generic paths now follow Python 3.14's updated `PurePath.with_suffix(".")`
|
|
20
|
+
behavior while retaining the earlier `ValueError` behavior on older Python
|
|
21
|
+
versions.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Documented that stdlib inheritance is deliberately local-only:
|
|
25
|
+
`LocalPath` is a real `pathlib.Path`, while URI, in-memory, and other virtual
|
|
26
|
+
implementations inherit the generic pathlib_next contracts without claiming
|
|
27
|
+
local-filesystem semantics.
|
|
28
|
+
|
|
10
29
|
## [0.8.4] - 2026-07-18
|
|
11
30
|
|
|
12
31
|
### Changed
|
|
@@ -516,7 +535,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
516
535
|
- Sync error handling.
|
|
517
536
|
- Generic Path Protocol based pathlib implementation for URI paths with file access support for sftp, http, file schemes.
|
|
518
537
|
|
|
519
|
-
[Unreleased]: https://github.com/jose-pr/pathlib_next/compare/v0.8.
|
|
538
|
+
[Unreleased]: https://github.com/jose-pr/pathlib_next/compare/v0.8.5...HEAD
|
|
539
|
+
[0.8.5]: https://github.com/jose-pr/pathlib_next/compare/v0.8.4...v0.8.5
|
|
520
540
|
[0.8.4]: https://github.com/jose-pr/pathlib_next/compare/v0.8.3...v0.8.4
|
|
521
541
|
[0.8.3]: https://github.com/jose-pr/pathlib_next/compare/v0.8.2...v0.8.3
|
|
522
542
|
[0.8.2]: https://github.com/jose-pr/pathlib_next/compare/v0.8.1...v0.8.2
|
|
@@ -11,6 +11,26 @@ in via MRO, so unless noted otherwise it behaves exactly like `pathlib.Path`
|
|
|
11
11
|
(it inherits the real implementation for anything not explicitly overridden).
|
|
12
12
|
The divergences below apply to `Uri`/`UriPath` and `MemPath`.
|
|
13
13
|
|
|
14
|
+
## Type relationships
|
|
15
|
+
|
|
16
|
+
Stdlib inheritance is deliberately limited to local filesystem paths:
|
|
17
|
+
|
|
18
|
+
- `LocalPath` subclasses both `pathlib.Path` and `pathlib_next.Path`.
|
|
19
|
+
- `PosixPathname` and `WindowsPathname` subclass the matching stdlib
|
|
20
|
+
`PurePath` classes and `pathlib_next.Pathname`.
|
|
21
|
+
- `MemPath`, `Uri`, `UriPath`, and custom virtual or remote implementations
|
|
22
|
+
subclass the generic pathlib_next bases, not `pathlib.Path`/`PurePath`.
|
|
23
|
+
- A plain stdlib `pathlib.Path` is not a `pathlib_next.Path`.
|
|
24
|
+
|
|
25
|
+
The generic classes cannot safely inherit the stdlib classes: pathlib parses
|
|
26
|
+
OS-specific path syntax and supplies operations whose semantics assume a local
|
|
27
|
+
filesystem, neither of which applies to a URI, archive member, object-store key,
|
|
28
|
+
or in-memory path. Registering stdlib paths as virtual `pathlib_next.Path`
|
|
29
|
+
subclasses would likewise promise pathlib_next's extended operation contract on
|
|
30
|
+
Python versions where stdlib paths do not implement it. Code accepting every
|
|
31
|
+
implementation should type against `pathlib_next.Path` or its documented
|
|
32
|
+
protocols; code requiring an OS path should type against `pathlib.Path`.
|
|
33
|
+
|
|
14
34
|
| Method | pathlib behavior | Our behavior | Why |
|
|
15
35
|
| --- | --- | --- | --- |
|
|
16
36
|
| `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`. |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pathlib_next"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.5"
|
|
8
8
|
authors = [{ name = "Jose A" }]
|
|
9
9
|
description = "Generic Path Protocol based pathlib"
|
|
10
10
|
readme = "README.md"
|
|
@@ -58,6 +58,15 @@ Homepage = "https://github.com/jose-pr/pathlib_next/"
|
|
|
58
58
|
Documentation = "https://jose-pr.github.io/pathlib_next/"
|
|
59
59
|
Issues = "https://github.com/jose-pr/pathlib_next/issues"
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
# Ship the consumer-facing docs inside the installed package, so they are
|
|
63
|
+
# readable from site-packages via importlib.resources without the repo.
|
|
64
|
+
# src/pathlib_next/AGENTS.md is included automatically by living in the package
|
|
65
|
+
# dir; the repo-root AGENTS.md is development-only and deliberately not
|
|
66
|
+
# shipped.
|
|
67
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
68
|
+
"README.md" = "pathlib_next/README.md"
|
|
69
|
+
|
|
61
70
|
[tool.hatch.build.targets.sdist]
|
|
62
71
|
exclude = ["/.*", "/benchmarks"]
|
|
63
72
|
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# `pathlib_next` — public API header
|
|
2
|
+
|
|
3
|
+
Header-file-style reference for the `pathlib_next` package: every public
|
|
4
|
+
export with its signature, arguments, contract, and gotchas, so this module
|
|
5
|
+
can be consumed without reading its source. Kept current with the public
|
|
6
|
+
API. For the project overview, install extras, and code layout, see the
|
|
7
|
+
<https://github.com/jose-pr/pathlib_next>. Any behavioral divergence from `pathlib.Path` is
|
|
8
|
+
recorded in `docs/divergences.md` — this file documents the *contract*, not
|
|
9
|
+
every internal deviation.
|
|
10
|
+
|
|
11
|
+
`import pathlib_next` re-exports `path`, `fspath`, `utils.glob`,
|
|
12
|
+
`utils.sync`, and (if `uritools` is importable) `uri.Uri`/`uri.UriPath`; a
|
|
13
|
+
missing `uritools` degrades that last import silently (`try`/`except
|
|
14
|
+
ImportError: pass`), so `pathlib_next.uri` may need an explicit
|
|
15
|
+
`from pathlib_next.uri import UriPath` even after a plain `import
|
|
16
|
+
pathlib_next`.
|
|
17
|
+
|
|
18
|
+
## Pure-path / I/O base (`pathlib_next.path`)
|
|
19
|
+
|
|
20
|
+
- **`Pathname`** — ABC for a pure (no I/O) path: `name`, `suffix`,
|
|
21
|
+
`suffixes`, `stem`, `segments` (abstract), `parts` (abstract),
|
|
22
|
+
`with_segments(*segments)` (abstract), `with_name`/`with_stem`/
|
|
23
|
+
`with_suffix`, `relative_to(other)`, `is_relative_to(other)`,
|
|
24
|
+
`__truediv__`/`joinpath`, `root`/`drive`/`anchor` (all `""` unless
|
|
25
|
+
overridden), `parent`/`parents` (abstract `parent`), `is_absolute()`
|
|
26
|
+
(abstract), `match(pattern, *, case_sensitive=None)`,
|
|
27
|
+
`full_match(pattern, *, case_sensitive=None)`, `as_posix()`,
|
|
28
|
+
`has_glob_pattern()`. `as_uri()` is abstract on `Pathname` itself.
|
|
29
|
+
- **`Path(Pathname, Chmod, Stat, BinaryOpen)`** — base class for I/O paths.
|
|
30
|
+
`Path(*args)` (the bare class, not a subclass) always constructs a
|
|
31
|
+
`LocalPath` (`fspath.py`) — the real local filesystem. Adds:
|
|
32
|
+
- `is_hidden()` — name starts with `"."`.
|
|
33
|
+
- `samefile(other_path)` — compares `(st_dev, st_ino)` from `stat()`;
|
|
34
|
+
raises `NotImplementedError` if either isn't available (`LocalPath` gets
|
|
35
|
+
a real implementation from `pathlib.Path` via MRO instead).
|
|
36
|
+
- `iterdir() -> Iterator[Self]` — **not implemented** by default (raises
|
|
37
|
+
`NotImplementedError`); every concrete `Path` overrides it.
|
|
38
|
+
- `_scandir() -> Iterator[tuple[str, FileStat | None]]` — default falls
|
|
39
|
+
back to `iterdir()` + one `stat()` per child; override directly when the
|
|
40
|
+
listing call already returns metadata (used by `walk()`/`glob()` so
|
|
41
|
+
remote schemes avoid a stat round trip per entry).
|
|
42
|
+
- `glob(pattern, *, case_sensitive=None, include_hidden=False,
|
|
43
|
+
recursive=None, dironly=None)` — a `"**"` pattern component
|
|
44
|
+
auto-enables recursion (pathlib parity); pass `recursive=False`
|
|
45
|
+
explicitly to disable it even with `"**"` present, or `True` to force it
|
|
46
|
+
without `"**"`. A recursive glob on a remote scheme walks the whole
|
|
47
|
+
subtree, one round trip per directory.
|
|
48
|
+
- `rglob(pattern, ...)` — `glob(f"**/{pattern}", recursive=True)`.
|
|
49
|
+
- `walk(top_down=True, on_error=None, follow_symlinks=False)` — drives
|
|
50
|
+
`_scandir()`, not `iterdir()`; the pre-seeded stat from `_scandir()` is
|
|
51
|
+
trusted only when `follow_symlinks=False` (its own default) — an
|
|
52
|
+
explicit `follow_symlinks=True` always re-`stat()`s each entry.
|
|
53
|
+
- `touch(mode=0o666, exist_ok=True)` — raises `FileExistsError` (not a
|
|
54
|
+
silent truncate) when `exist_ok=False` and the file exists.
|
|
55
|
+
- `_mkdir(mode)` (not implemented by default) / `mkdir(mode=0o777,
|
|
56
|
+
parents=False, exist_ok=False)` — `mkdir()` retries through
|
|
57
|
+
`_mkdir()`, creating parents on `FileNotFoundError` when `parents=True`.
|
|
58
|
+
- `unlink(missing_ok=False)` / `rmdir()` — not implemented by default;
|
|
59
|
+
every concrete `Path` overrides them.
|
|
60
|
+
- `rm(recursive=False, missing_ok=False, ignore_error=False |
|
|
61
|
+
Callable[[Exception, Self], bool])` — extension, no direct pathlib
|
|
62
|
+
equivalent. Removes a file or (with `recursive=True`) a directory tree;
|
|
63
|
+
`ignore_error` (bool or predicate) controls whether an error during the
|
|
64
|
+
walk is swallowed (predicate return `True`) or re-raised.
|
|
65
|
+
- `rename(target)` — not implemented by default.
|
|
66
|
+
- `copy(target, *, overwrite=False, follow_symlinks=True,
|
|
67
|
+
preserve_metadata=True, recursive=False, ignore_error=None)` —
|
|
68
|
+
`follow_symlinks`/`preserve_metadata` names match CPython 3.14's
|
|
69
|
+
`Path.copy()`; `overwrite` is this library's own extension (3.14 always
|
|
70
|
+
raises if the destination exists). `preserve_metadata` defaults `True`
|
|
71
|
+
here (3.14 defaults `False`) and only preserves `st_mode`, not
|
|
72
|
+
timestamps/xattrs. `ignore_error`, when given, receives exceptions
|
|
73
|
+
instead of raising (same contract as `rm()`'s callable form); `None`
|
|
74
|
+
(default) fails on the first error.
|
|
75
|
+
- `move(target, *, overwrite=False)` — tries `rename()` first, falls back
|
|
76
|
+
to `copy(recursive=True)` + `rm(recursive=True)`/`unlink()` when
|
|
77
|
+
`rename()` raises `NotImplementedError`.
|
|
78
|
+
- **`PathLike`** — `Union[str, Path]`. **`PurePathLike`** — `Union[str,
|
|
79
|
+
Pathname]`. **`FsPathLike`** — `Protocol` requiring `__fspath__() -> str`.
|
|
80
|
+
|
|
81
|
+
## Local filesystem (`pathlib_next.fspath`)
|
|
82
|
+
|
|
83
|
+
- **`LocalPath`** — `pathlib.WindowsPath`/`PosixPath` (by `os.name`) with
|
|
84
|
+
this library's `Path` mixed in via MRO. Behaves exactly like
|
|
85
|
+
`pathlib.Path` for anything not explicitly overridden (see
|
|
86
|
+
`docs/divergences.md`); overrides `_scandir()`, `walk()`, `copy()`,
|
|
87
|
+
`move()`, `stat()`, `chmod()`, and `glob()` to keep this project's
|
|
88
|
+
contracts (tuple-yielding `_scandir`, extended copy/move kwargs,
|
|
89
|
+
`follow_symlinks=` support pre-3.10) regardless of what a given Python
|
|
90
|
+
version's own `pathlib.Path` does at the same MRO position.
|
|
91
|
+
Stdlib inheritance is intentionally local-only: `MemPath`, `Uri`, and
|
|
92
|
+
`UriPath` implement the pathlib_next bases but are not stdlib
|
|
93
|
+
`PurePath`/`Path` instances because stdlib construction and operations
|
|
94
|
+
assume OS path syntax and a local filesystem. Conversely, a plain stdlib
|
|
95
|
+
`pathlib.Path` is not a `pathlib_next.Path`.
|
|
96
|
+
- **`PosixPathname`** / **`WindowsPathname`** — pure (no I/O) path classes
|
|
97
|
+
implementing `Pathname` on top of `pathlib.PurePosixPath`/
|
|
98
|
+
`PureWindowsPath`.
|
|
99
|
+
|
|
100
|
+
## In-memory filesystem (`pathlib_next.mempath`)
|
|
101
|
+
|
|
102
|
+
- **`MemPath(Path)`** — `MemPath(*segments, backend=None, **kwargs)`.
|
|
103
|
+
In-memory path over nested dicts; a `dict` value is a directory, a
|
|
104
|
+
`bytearray` value is a file's content. Reference exemplar for subclassing
|
|
105
|
+
`Path` directly. `relative_to()` is not implemented. `as_uri()` returns
|
|
106
|
+
`mempath:<url-quoted posix path>`. Supports `_open()` modes `"r"`, `"w"`,
|
|
107
|
+
`"x"`, `"a"` (the `"a"` extension isn't part of the base `BinaryOpen`
|
|
108
|
+
contract). `rename()` is not implemented (see the scheme feature matrix in
|
|
109
|
+
the README).
|
|
110
|
+
- **`MemPathBackend(dict)`** — the nested-dict storage. Share one instance
|
|
111
|
+
across `MemPath`s via `backend=` to give them the same virtual filesystem;
|
|
112
|
+
omitted, each root `MemPath()` gets its own.
|
|
113
|
+
|
|
114
|
+
## Protocols (`pathlib_next.protocols`)
|
|
115
|
+
|
|
116
|
+
- **`fs.FileStatLike`** — `Protocol`: `st_mode`, `st_size`, `st_mtime`
|
|
117
|
+
(all abstract properties).
|
|
118
|
+
- **`fs.Stat`** — `Protocol`. `stat(*, follow_symlinks=True) ->
|
|
119
|
+
FileStatLike` (not implemented by default). Derives `lstat()`,
|
|
120
|
+
`exists()`, `is_dir()`, `is_file()`, `is_symlink()`, `is_block_device()`,
|
|
121
|
+
`is_char_device()`, `is_fifo()`, `is_socket()` — all methods, not
|
|
122
|
+
properties. `exists()`/the `is_*` methods swallow `OSError`/`ValueError`
|
|
123
|
+
from `stat()` and report `False` rather than propagating (pathlib parity).
|
|
124
|
+
- **`fs.Chmod`** — `Protocol`. `chmod(mode, *, follow_symlinks=True)` (not
|
|
125
|
+
implemented by default); derives `lchmod(mode)`.
|
|
126
|
+
- **`io.BinaryOpen`** — `Protocol`. `_open(mode="r", buffering=-1) ->
|
|
127
|
+
io.IOBase` (not implemented by default; must yield a **binary** stream).
|
|
128
|
+
Derives `open(mode="r", buffering=-1, encoding=None, errors=None,
|
|
129
|
+
newline=None)`, `read_bytes()`, `read_text(encoding=None, errors=None,
|
|
130
|
+
newline=None)`, `write_bytes(data)`, `write_text(data, encoding=None,
|
|
131
|
+
errors=None, newline=None)`, `copy(target)` (streams this object's binary
|
|
132
|
+
content into another `BinaryOpen`).
|
|
133
|
+
|
|
134
|
+
## URIs (`pathlib_next.uri`)
|
|
135
|
+
|
|
136
|
+
Only importable if `uritools` is installed (the `uri` extra or any scheme
|
|
137
|
+
extra that depends on it).
|
|
138
|
+
|
|
139
|
+
- **`Uri(Pathname)`** — a pure (no I/O), RFC 3986 URI, lazily parsed into
|
|
140
|
+
`source`/`path`/`query`/`fragment` on first access. `Uri(*uris,
|
|
141
|
+
**options)` — multiple constructor args are joined pathlib-`joinpath`-style
|
|
142
|
+
(right to left, stopping at the first absolute segment) — this is **not**
|
|
143
|
+
RFC 3986 reference resolution, and `..` is never resolved during join (see
|
|
144
|
+
`docs/divergences.md`). Properties: `source -> Source`, `path -> str`,
|
|
145
|
+
`query -> str`, `fragment -> str`, `parts -> (source, path, query,
|
|
146
|
+
fragment)`, `normalized_path` (posixpath-normalized `path`), `segments`,
|
|
147
|
+
`suffix`, `stem`, `parent`. Methods: `as_uri(sanitize=False)` (sanitize
|
|
148
|
+
strips password from userinfo before formatting), `with_source(source)`,
|
|
149
|
+
`with_segments(*segments)`, `with_path(path)`, `with_query(query)`,
|
|
150
|
+
`with_fragment(fragment)`, `is_absolute()`, `is_relative_to(other)`,
|
|
151
|
+
`relative_to(other, *, walk_up=False)`, `is_local()` (delegates to
|
|
152
|
+
`Source.is_local()` — does a DNS lookup, cached per `Source`),
|
|
153
|
+
`as_posix()` (`user@host:path` / `host:path` form when a source is
|
|
154
|
+
present). `__fspath__()` only succeeds for a `file:`-scheme URI pointing
|
|
155
|
+
at this machine; otherwise raises `NotImplementedError`.
|
|
156
|
+
- **`UriPath(Uri, Path)`** — `Uri` + `Path` (I/O) + scheme dispatch.
|
|
157
|
+
`UriPath(*uris, **options)` (the bare class) parses the URI and returns an
|
|
158
|
+
instance of the concrete subclass registered for its scheme via
|
|
159
|
+
`__SCHEMES` (name-mangled per class — declare `__SCHEMES = ("http",
|
|
160
|
+
"https")` in the subclass body, not as a module-level or dynamically
|
|
161
|
+
assigned attribute, and never give a `__SCHEMES`-registered class a
|
|
162
|
+
leading underscore in its name, or the name-mangled lookup silently
|
|
163
|
+
misses). If the scheme isn't loaded yet, resolution tries a
|
|
164
|
+
`pathlib_next.schemes` entry point first, then imports the matching
|
|
165
|
+
builtin `uri/schemes/*` module — importing any module that defines a
|
|
166
|
+
`UriPath` subclass registers it. `backend` property — per-instance
|
|
167
|
+
connection/session state, lazily created via `_initbackend()` (override
|
|
168
|
+
in a scheme subclass; base returns `None`); `with_backend(backend)`
|
|
169
|
+
returns a new instance sharing the given backend. `_listdir() ->
|
|
170
|
+
Iterator[str]` (not implemented by default) / `_scandir()` (derives from
|
|
171
|
+
`_listdir()` + one `stat()` per child unless overridden directly — prefer
|
|
172
|
+
overriding `_scandir()` when the listing call already returns
|
|
173
|
+
type/size/mtime metadata, e.g. WebDAV PROPFIND, FTP MLSD, SFTP
|
|
174
|
+
`listdir_attr`, an S3 list page). `iterdir()` is provided (drives
|
|
175
|
+
`_scandir()`); implement `_listdir()` or `_scandir()`, not `iterdir()`
|
|
176
|
+
itself.
|
|
177
|
+
- **`Source`** (`uri.source`, re-exported at `uri.Source` via `uri/__init__`
|
|
178
|
+
imports) — `NamedTuple(scheme, userinfo, host, port)`; falsy when every
|
|
179
|
+
field is empty/`None`. `Source.from_str(source, strict=True) -> Source`
|
|
180
|
+
(`strict=True` raises `ValueError` if `source` carries a path/query/
|
|
181
|
+
fragment). `parsed_userinfo() -> (user, password)`. `get_scheme_cls(
|
|
182
|
+
schemesmap=None) -> type[UriPath]` — resolves (and lazily loads) the
|
|
183
|
+
scheme class. `is_local()` — DNS lookup, `lru_cache(maxsize=256)`d per
|
|
184
|
+
`Source` value; never call on a hot path uncached.
|
|
185
|
+
- **`Query(str)`** (`uri.query`) — a URI query string, buildable from a
|
|
186
|
+
`str`, a sequence of `(key, value)` pairs, or a mapping (`value` may be a
|
|
187
|
+
sequence to repeat the key). `Query(query, *, encoding="utf-8",
|
|
188
|
+
separator="&")`. `decode() -> list[tuple[str, str | None]]`,
|
|
189
|
+
`__iter__()` (iterates decoded pairs), `to_dict(*, single=False) ->
|
|
190
|
+
dict[str, list[str | None]]` (or `dict[str, str | None]` when
|
|
191
|
+
`single=True`, last value wins).
|
|
192
|
+
|
|
193
|
+
Built-in scheme modules live under `uri/schemes/` — see the table in the
|
|
194
|
+
<https://github.com/jose-pr/pathlib_next>. `PATHLIB_NEXT_SFTP_BACKEND` env var (`"paramiko"` /
|
|
195
|
+
`"asyncssh"` / `"auto"`, default `"auto"`) selects the `sftp:` backend;
|
|
196
|
+
precedence is an explicit class attribute > this env var > auto-detect
|
|
197
|
+
(prefers asyncssh if importable). `gs:` honors `STORAGE_EMULATOR_HOST` (set
|
|
198
|
+
into `os.environ` for the `google-cloud-storage` client, e.g. for a local
|
|
199
|
+
emulator) when configured on the path/backend.
|
|
200
|
+
|
|
201
|
+
## Testing helpers (`pathlib_next.testing`)
|
|
202
|
+
|
|
203
|
+
Not imported by `pathlib_next/__init__.py` (needs `pytest`, a test-only
|
|
204
|
+
dependency) — import explicitly: `from pathlib_next.testing import
|
|
205
|
+
PathContract`.
|
|
206
|
+
|
|
207
|
+
- **`PurePathContract`** — pure-path tests (name/suffix/stem, parent/
|
|
208
|
+
parents, joinpath/`/`, match). Requires only a `root` fixture.
|
|
209
|
+
- **`ReadPathContract(PurePathContract)`** — read-only I/O tests (exists/
|
|
210
|
+
is_dir/is_file, read_text/read_bytes, iterdir, stat). `root` fixture must
|
|
211
|
+
point at a directory pre-populated with the standard fixture tree
|
|
212
|
+
(`a.txt`, `b.py`, `.hidden.txt`, `sub/c.py`, `sub/nested/d.py`,
|
|
213
|
+
`empty_dir/`).
|
|
214
|
+
- **`PathContract(ReadPathContract)`** — full read/write contract (mkdir,
|
|
215
|
+
write_text/write_bytes, unlink, rmdir, rm(recursive=True), copy, move,
|
|
216
|
+
touch(exist_ok=False), mkdir(parents=True)). `root` fixture must be
|
|
217
|
+
writable.
|
|
218
|
+
|
|
219
|
+
Subclass one of these with your own `root` fixture to verify a custom
|
|
220
|
+
`Path`/`UriPath` implementation against the shared contract.
|
|
221
|
+
|
|
222
|
+
## Utilities (`pathlib_next.utils`)
|
|
223
|
+
|
|
224
|
+
- **`glob.glob(path, *, dironly=False, root_dir=None, recursive=False,
|
|
225
|
+
include_hidden=False, case_sensitive=None) -> Iterable[path-like]`** — the
|
|
226
|
+
engine behind `Path.glob()`/`rglob()`; works over anything exposing
|
|
227
|
+
`iterdir()`/`is_dir()`/`name`/`parents`/`has_glob_pattern()`. Dotfiles are
|
|
228
|
+
excluded from `*`/`?` matches unless `include_hidden=True`.
|
|
229
|
+
**`glob.full_match(segments, pattern, case_sensitive) -> bool`** —
|
|
230
|
+
pathlib 3.13 `full_match()` semantics, `"**"` matches zero or more
|
|
231
|
+
segments. **`glob.RECURSIVE`** = `"**"`.
|
|
232
|
+
- **`sync.PathSyncer(checksum=None, /, remove_missing=False,
|
|
233
|
+
follow_symlinks=True, hook=None, ignore_error=False)`** — one-way
|
|
234
|
+
checksum-driven tree sync between any two `Path` implementations.
|
|
235
|
+
`checksum` defaults to `utils.checksum.md5`. `.sync(source, target, /,
|
|
236
|
+
dry_run=False, ignore_error=False)` copies/creates in `target` whatever
|
|
237
|
+
differs from `source`; `remove_missing=True` also removes `target`
|
|
238
|
+
entries absent from `source`. `hook`/`.log()`/subclassing `.log()` are the
|
|
239
|
+
progress/logging seams; `SyncEvent` enum names the events fired.
|
|
240
|
+
**`sync.PathAndStat`** — a `Path` + cached `stat()` (`None` if missing);
|
|
241
|
+
`is_*` attribute access delegates to the cached stat, returning a
|
|
242
|
+
false-returning callable when the path doesn't exist.
|
|
243
|
+
- **`stat.FileStat(FileStatLike)`** — `FileStat(st_mode=None, st_size=0,
|
|
244
|
+
st_mtime=0, is_dir=False)`, slotted, for backends without a real
|
|
245
|
+
`os.stat_result` (`MemPath`, `HttpPath`, ...). `FileStat.from_stat(stat)`
|
|
246
|
+
copies recognized fields from any stat-like object (passes an existing
|
|
247
|
+
`FileStat` through unchanged). `FileStat.from_path(path, *,
|
|
248
|
+
follow_symlink=True) -> FileStat | None` (`None` on `FileNotFoundError`).
|
|
249
|
+
`is_dir()`/`is_file()`/etc. are **methods**, not properties — `if
|
|
250
|
+
st.is_dir` (no parens) is always truthy.
|
|
251
|
+
- **`checksum.md5(path, chunk_size=65536) -> str`** /
|
|
252
|
+
**`checksum.sha256(path, chunk_size=65536) -> str`** — streaming file
|
|
253
|
+
checksums over any `Path`.
|
|
254
|
+
- **`archive.make_archive(src, format, target)`** (`format` is `"zip"` or
|
|
255
|
+
`"tar"`) / **`archive.unpack_archive(archive, dest)`** (format
|
|
256
|
+
auto-detected from `archive.name`, falling back to magic-byte sniffing) —
|
|
257
|
+
stream-first, so `src`/`target`/`archive`/`dest` can be any `Path`
|
|
258
|
+
implementation, not just local files.
|
|
259
|
+
- **`LRU(func, maxsize=128)`** — thread-safe memoizing cache wrapping
|
|
260
|
+
`func`, itself callable; `.invalidate(*args)` evicts and recomputes one
|
|
261
|
+
entry; `.maxsize` is a settable property that evicts down to the new size.
|
|
262
|
+
- **`notimplemented(method)`** — decorator marking a protocol method;
|
|
263
|
+
raises `NotImplementedError` naming the method when called. Callers that
|
|
264
|
+
want a graceful fallback catch `NotImplementedError` (e.g. `move()` falls
|
|
265
|
+
back to copy+unlink when `rename` isn't implemented).
|
|
266
|
+
- **`sizeof_fmt(num) -> str`** — human-readable byte size (`"1.5K"`, ...).
|
|
267
|
+
**`parsedate(date) -> float`** — epoch seconds from a `str`/
|
|
268
|
+
`time.struct_time`/`tuple`/`float`; unparseable or `None` input returns
|
|
269
|
+
`0`, not "now". **`get_machine_ips() -> list[IPv4Address | IPv6Address]`**
|
|
270
|
+
— `lru_cache(maxsize=1)`d.
|
|
@@ -115,6 +115,36 @@ class LocalPath(
|
|
|
115
115
|
self, top_down=top_down, on_error=on_error, follow_symlinks=follow_symlinks
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
+
def copy(
|
|
119
|
+
self,
|
|
120
|
+
target,
|
|
121
|
+
*,
|
|
122
|
+
overwrite=False,
|
|
123
|
+
follow_symlinks=True,
|
|
124
|
+
preserve_metadata=True,
|
|
125
|
+
recursive=False,
|
|
126
|
+
ignore_error=None,
|
|
127
|
+
):
|
|
128
|
+
# Python 3.14 added pathlib.Path.copy(), which sits ahead of our
|
|
129
|
+
# generic implementation in the MRO and does not accept pathlib_next's
|
|
130
|
+
# overwrite=/recursive=/ignore_error= extensions. Keep LocalPath's
|
|
131
|
+
# cross-version contract stable by routing explicitly to our method.
|
|
132
|
+
return _proto.Path.copy(
|
|
133
|
+
self,
|
|
134
|
+
target,
|
|
135
|
+
overwrite=overwrite,
|
|
136
|
+
follow_symlinks=follow_symlinks,
|
|
137
|
+
preserve_metadata=preserve_metadata,
|
|
138
|
+
recursive=recursive,
|
|
139
|
+
ignore_error=ignore_error,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def move(self, target, *, overwrite=False):
|
|
143
|
+
# Python 3.14 added pathlib.Path.move() alongside copy(); route around
|
|
144
|
+
# the same MRO collision so overwrite= and the generic fallback remain
|
|
145
|
+
# available on every supported Python version.
|
|
146
|
+
return _proto.Path.move(self, target, overwrite=overwrite)
|
|
147
|
+
|
|
118
148
|
def stat(self, *, follow_symlinks=True):
|
|
119
149
|
# pathlib.Path.stat() (next in MRO via WindowsPath/PosixPath) only
|
|
120
150
|
# accepts follow_symlinks= on 3.10+; below that, lstat() is the
|
|
@@ -129,9 +159,7 @@ class LocalPath(
|
|
|
129
159
|
# platforms without os.lchmod, e.g. Windows).
|
|
130
160
|
if _HAS_FOLLOW_SYMLINKS:
|
|
131
161
|
return super().chmod(mode, follow_symlinks=follow_symlinks)
|
|
132
|
-
return (
|
|
133
|
-
super().chmod(mode) if follow_symlinks else super().lchmod(mode)
|
|
134
|
-
)
|
|
162
|
+
return super().chmod(mode) if follow_symlinks else super().lchmod(mode)
|
|
135
163
|
|
|
136
164
|
def glob(
|
|
137
165
|
self,
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
import abc as _abc
|
|
11
11
|
import os as _os
|
|
12
12
|
import re as _re
|
|
13
|
+
import sys as _sys
|
|
13
14
|
import typing as _ty
|
|
14
15
|
|
|
15
16
|
from . import utils as _utils
|
|
@@ -160,7 +161,11 @@ class Pathname(FsPathLike, _ty.Generic[_P]):
|
|
|
160
161
|
def with_suffix(self, suffix: str) -> _ty.Self:
|
|
161
162
|
"""Return a new path with the suffix changed or added."""
|
|
162
163
|
name = self.name
|
|
163
|
-
if
|
|
164
|
+
if (
|
|
165
|
+
suffix
|
|
166
|
+
and not suffix.startswith(".")
|
|
167
|
+
or (suffix == "." and _sys.version_info < (3, 14))
|
|
168
|
+
):
|
|
164
169
|
raise ValueError("Invalid suffix %r" % (suffix))
|
|
165
170
|
if not name:
|
|
166
171
|
raise ValueError("%r has an empty name" % (self,))
|
|
@@ -693,4 +698,3 @@ class Path(Pathname, Chmod, Stat, BinaryOpen):
|
|
|
693
698
|
|
|
694
699
|
|
|
695
700
|
PathLike = _ty.Union[str, Path]
|
|
696
|
-
|
|
@@ -5,6 +5,7 @@ for the handful of methods LocalPath explicitly overrides (touch, mkdir,
|
|
|
5
5
|
glob, rm/copy/move which have no direct pathlib.Path equivalent to diverge
|
|
6
6
|
from pre-3.14).
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
import os
|
|
9
10
|
|
|
10
11
|
import pytest
|
|
@@ -84,7 +85,11 @@ def test_glob_hidden_excluded_by_default(fixture_tree):
|
|
|
84
85
|
def test_walk_matches_os_walk(fixture_tree):
|
|
85
86
|
root = pathlib_next.LocalPath(fixture_tree)
|
|
86
87
|
ours = sorted(
|
|
87
|
-
(
|
|
88
|
+
(
|
|
89
|
+
str(p.relative_to(root).as_posix() if p != root else "."),
|
|
90
|
+
sorted(d),
|
|
91
|
+
sorted(f),
|
|
92
|
+
)
|
|
88
93
|
for p, d, f in root.walk()
|
|
89
94
|
)
|
|
90
95
|
theirs = sorted(
|
|
@@ -201,6 +206,11 @@ def test_copy_recursive(tmp_path):
|
|
|
201
206
|
assert (dst / "f1.txt").read_text() == "1-updated"
|
|
202
207
|
|
|
203
208
|
|
|
209
|
+
def test_local_copy_and_move_resolve_to_pathlib_next():
|
|
210
|
+
assert pathlib_next.LocalPath.copy.__module__.startswith("pathlib_next.")
|
|
211
|
+
assert pathlib_next.LocalPath.move.__module__.startswith("pathlib_next.")
|
|
212
|
+
|
|
213
|
+
|
|
204
214
|
def test_move_recursive_fallback(tmp_path):
|
|
205
215
|
root = pathlib_next.LocalPath(tmp_path)
|
|
206
216
|
src = root / "src"
|
|
@@ -220,4 +230,3 @@ def test_move_recursive_fallback(tmp_path):
|
|
|
220
230
|
assert not src.exists()
|
|
221
231
|
assert (dst / "f1.txt").read_text() == "1"
|
|
222
232
|
assert (dst / "sub" / "f2.txt").read_text() == "2"
|
|
223
|
-
|
|
@@ -4,6 +4,9 @@ Pathname (PosixPathname -- LocalPath itself is excluded here since pathlib's
|
|
|
4
4
|
own PurePath wins those methods via MRO, see test_parity_pure.py instead),
|
|
5
5
|
Uri, and MemPath.
|
|
6
6
|
"""
|
|
7
|
+
|
|
8
|
+
import pathlib
|
|
9
|
+
|
|
7
10
|
import pytest
|
|
8
11
|
|
|
9
12
|
from pathlib_next.fspath import PosixPathname
|
|
@@ -57,8 +60,13 @@ def test_with_suffix_invalid_raises(cls):
|
|
|
57
60
|
p = cls("a/b.txt")
|
|
58
61
|
with pytest.raises(ValueError):
|
|
59
62
|
p.with_suffix("txt")
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
try:
|
|
64
|
+
expected = pathlib.PurePosixPath("a/b.txt").with_suffix(".").name
|
|
65
|
+
except ValueError:
|
|
66
|
+
with pytest.raises(ValueError):
|
|
67
|
+
p.with_suffix(".")
|
|
68
|
+
else:
|
|
69
|
+
assert p.with_suffix(".").name == expected
|
|
62
70
|
|
|
63
71
|
|
|
64
72
|
@pytest.mark.parametrize("cls", IMPLS)
|
|
@@ -88,7 +96,7 @@ def test_parent_and_parents(cls):
|
|
|
88
96
|
parents = [pp.as_posix() for pp in p.parents]
|
|
89
97
|
assert parents[:2] == ["a/b", "a"]
|
|
90
98
|
assert len(parents) == 3 # trailing root/"." element, like pathlib
|
|
91
|
-
|
|
99
|
+
|
|
92
100
|
# Slicing
|
|
93
101
|
try:
|
|
94
102
|
sliced = p.parents[0:2]
|
|
@@ -96,7 +104,7 @@ def test_parent_and_parents(cls):
|
|
|
96
104
|
except TypeError:
|
|
97
105
|
# Python 3.9 stdlib pathlib.PurePath.parents doesn't support slicing
|
|
98
106
|
pass
|
|
99
|
-
|
|
107
|
+
|
|
100
108
|
# Negative indexing
|
|
101
109
|
try:
|
|
102
110
|
assert p.parents[-1].as_posix() == "" or p.parents[-1].as_posix() == "."
|
|
@@ -105,7 +113,7 @@ def test_parent_and_parents(cls):
|
|
|
105
113
|
except IndexError:
|
|
106
114
|
# Python 3.9 stdlib pathlib.PurePath.parents doesn't support negative indexing
|
|
107
115
|
pass
|
|
108
|
-
|
|
116
|
+
|
|
109
117
|
# IndexError out of bounds
|
|
110
118
|
with pytest.raises(IndexError):
|
|
111
119
|
_ = p.parents[3]
|
|
@@ -113,7 +121,6 @@ def test_parent_and_parents(cls):
|
|
|
113
121
|
_ = p.parents[-4]
|
|
114
122
|
|
|
115
123
|
|
|
116
|
-
|
|
117
124
|
@pytest.mark.parametrize("cls", IMPLS)
|
|
118
125
|
def test_has_glob_pattern(cls):
|
|
119
126
|
assert cls("a/*.py").has_glob_pattern()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|