pathlib-next 0.8.2__tar.gz → 0.8.4__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 (110) hide show
  1. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/CHANGELOG.md +24 -1
  2. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/PKG-INFO +1 -1
  3. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/benchmarks.md +10 -0
  4. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/pyproject.toml +1 -1
  5. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/path.py +1 -1
  6. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/__init__.py +2 -2
  7. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/az.py +1 -1
  8. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/github.py +1 -1
  9. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/gitlab.py +1 -1
  10. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/gs.py +2 -2
  11. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/s3.py +1 -1
  12. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/sftp/_asyncssh.py +15 -1
  13. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/archive.py +0 -1
  14. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/stat.py +0 -1
  15. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_sftp_asyncssh.py +16 -2
  16. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/.gitignore +0 -0
  17. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/LICENSE +0 -0
  18. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/README.md +0 -0
  19. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/api/mempath.md +0 -0
  20. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/api/path.md +0 -0
  21. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/api/testing.md +0 -0
  22. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/api/uri.md +0 -0
  23. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/api/utils.md +0 -0
  24. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/changelog.md +0 -0
  25. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/divergences.md +0 -0
  26. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/guides/cli.md +0 -0
  27. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/guides/extending.md +0 -0
  28. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/guides/schemes.md +0 -0
  29. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/docs/index.md +0 -0
  30. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/az_listing.py +0 -0
  31. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/data_and_archive.py +0 -0
  32. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/ftp_listing.py +0 -0
  33. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/github_listing.py +0 -0
  34. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/gitlab_listing.py +0 -0
  35. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/gs_listing.py +0 -0
  36. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/http_listing.py +0 -0
  37. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/local_and_mem.py +0 -0
  38. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/s3_listing.py +0 -0
  39. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/sftp_sync.py +0 -0
  40. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/examples/webdav_roundtrip.py +0 -0
  41. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/mkdocs.yml +0 -0
  42. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/__init__.py +0 -0
  43. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/fspath.py +0 -0
  44. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/mempath.py +0 -0
  45. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/protocols/__init__.py +0 -0
  46. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/protocols/fs.py +0 -0
  47. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/protocols/io.py +0 -0
  48. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/py.typed +0 -0
  49. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/testing.py +0 -0
  50. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/tools/__init__.py +0 -0
  51. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/tools/uripath.py +0 -0
  52. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/query.py +0 -0
  53. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/__init__.py +0 -0
  54. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/_gitrepo.py +0 -0
  55. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/archive/__init__.py +0 -0
  56. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/archive/_base.py +0 -0
  57. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/archive/tar.py +0 -0
  58. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/archive/zip.py +0 -0
  59. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/data.py +0 -0
  60. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/dav.py +0 -0
  61. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/file.py +0 -0
  62. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/ftp.py +0 -0
  63. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/git/__init__.py +0 -0
  64. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/git/_base.py +0 -0
  65. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/git/github.py +0 -0
  66. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/git/gitlab.py +0 -0
  67. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/http.py +0 -0
  68. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/sftp/__init__.py +0 -0
  69. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/sftp/_paramiko.py +0 -0
  70. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/schemes/sftp/_sshconfig.py +0 -0
  71. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/uri/source.py +0 -0
  72. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/__init__.py +0 -0
  73. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/checksum.py +0 -0
  74. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/glob.py +0 -0
  75. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/src/pathlib_next/utils/sync.py +0 -0
  76. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/conftest.py +0 -0
  77. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_archive_uri.py +0 -0
  78. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_az.py +0 -0
  79. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_az_fake.py +0 -0
  80. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_contract.py +0 -0
  81. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_data_uri.py +0 -0
  82. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_dav.py +0 -0
  83. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_ftp.py +0 -0
  84. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_gitrepo.py +0 -0
  85. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_glob.py +0 -0
  86. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_gs.py +0 -0
  87. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_gs_fake.py +0 -0
  88. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_http.py +0 -0
  89. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_http_live.py +0 -0
  90. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_http_parser.py +0 -0
  91. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_local.py +0 -0
  92. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_mempath.py +0 -0
  93. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_parity_io.py +0 -0
  94. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_parity_pure.py +0 -0
  95. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_path_gaps.py +0 -0
  96. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_pathname.py +0 -0
  97. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_plugins.py +0 -0
  98. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_properties.py +0 -0
  99. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_query.py +0 -0
  100. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_s3.py +0 -0
  101. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_sftp.py +0 -0
  102. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_smoke.py +0 -0
  103. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_source.py +0 -0
  104. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_sync.py +0 -0
  105. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_uri_parse.py +0 -0
  106. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_uri_path.py +0 -0
  107. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_uripath_tool.py +0 -0
  108. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_utils.py +0 -0
  109. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_walk.py +0 -0
  110. {pathlib_next-0.8.2 → pathlib_next-0.8.4}/tests/test_webdav.py +0 -0
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.4] - 2026-07-18
11
+
12
+ ### Changed
13
+ - Internal tidy-up (no behavior change): removed dead imports (`Source` in the
14
+ `az`/`gs`/`s3` schemes, `io` in `utils.archive`, `abc` in `utils.stat`), dropped
15
+ unused local variables, and de-f-string'd a placeholder-less message. The
16
+ `RepoBackend` re-exports from the `github`/`gitlab` schemes are retained (public
17
+ API, marked `# noqa`).
18
+
19
+ ## [0.8.3] - 2026-07-18
20
+
21
+ ### Changed
22
+ - **`AsyncsshSftpBackend` default `max_concurrency` raised 8 → 16.** A 128-file
23
+ loopback recursive-copy/rm sweep of `mc ∈ {1,2,4,8,16}` (median of 3) showed
24
+ recursive copy improving monotonically with concurrency (mc=8→16 ≈3% faster on
25
+ top of mc=1→8 ≈1.13x) and recursive remove flat within noise, with 16
26
+ fastest-or-tied and well inside asyncssh's SFTP request window. Exposed as
27
+ `AsyncsshSftpBackend.DEFAULT_MAX_CONCURRENCY`; pass `max_concurrency=` to
28
+ override. Loopback evidence only — a high-latency remote link may warrant a
29
+ different value; 16 is a safe modest default, not a tuned optimum.
30
+
10
31
  ## [0.8.2] - 2026-07-18
11
32
 
12
33
  ### Fixed
@@ -495,7 +516,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
495
516
  - Sync error handling.
496
517
  - Generic Path Protocol based pathlib implementation for URI paths with file access support for sftp, http, file schemes.
497
518
 
498
- [Unreleased]: https://github.com/jose-pr/pathlib_next/compare/v0.8.2...HEAD
519
+ [Unreleased]: https://github.com/jose-pr/pathlib_next/compare/v0.8.4...HEAD
520
+ [0.8.4]: https://github.com/jose-pr/pathlib_next/compare/v0.8.3...v0.8.4
521
+ [0.8.3]: https://github.com/jose-pr/pathlib_next/compare/v0.8.2...v0.8.3
499
522
  [0.8.2]: https://github.com/jose-pr/pathlib_next/compare/v0.8.1...v0.8.2
500
523
  [0.8.1]: https://github.com/jose-pr/pathlib_next/compare/v0.8.0...v0.8.1
501
524
  [0.8.0]: https://github.com/jose-pr/pathlib_next/compare/v0.7.0...v0.8.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pathlib_next
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Generic Path Protocol based pathlib
5
5
  Project-URL: Homepage, https://github.com/jose-pr/pathlib_next/
6
6
  Project-URL: Documentation, https://jose-pr.github.io/pathlib_next/
@@ -142,6 +142,16 @@ Legend: `p` = `paramiko`, `a` = `asyncssh`.
142
142
  was fastest at `7.6068s`, with `4` at `14.2806s` and `8` at `13.3736s`.
143
143
  Treat this as workload-specific follow-up evidence, not as a default tuning
144
144
  decision.
145
+ - **2026-07-18 (0.8.3): `AsyncsshSftpBackend` default `max_concurrency` raised
146
+ 8 → 16.** A cleaner 128-file loopback sweep of `mc ∈ {1,2,4,8,16}` (median of
147
+ 3, py3.14) showed recursive copy improving *monotonically* with concurrency
148
+ (mc=1 `1.66s` → mc=8 `1.47s` ≈ 1.13x → mc=16 `1.42s`, a further ≈3%), and
149
+ recursive remove flat within noise (median spread `0.498`..`0.551`, mc=16
150
+ marginally best). This supersedes the earlier "mc=1 fastest" loopback reading
151
+ (from the older code path / a different venv). 16 stays within asyncssh's SFTP
152
+ request window. **Loopback only** — no per-op latency; a high-latency remote
153
+ link may favour higher concurrency still, so 16 is a safe modest default, not
154
+ a tuned optimum. Override per backend via `AsyncsshSftpBackend(max_concurrency=…)`.
145
155
  - `python benchmarks/bench.py syncer` on the same local run reported
146
156
  PathSyncer copy of 128 local files at `0.4524s`, dry-run at `0.0534s`, and
147
157
  remove-missing plus copy at `0.9260s`. This suggests metadata reuse may be
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "pathlib_next"
7
- version = "0.8.2"
7
+ version = "0.8.4"
8
8
  authors = [{ name = "Jose A" }]
9
9
  description = "Generic Path Protocol based pathlib"
10
10
  readme = "README.md"
@@ -188,7 +188,7 @@ class Pathname(FsPathLike, _ty.Generic[_P]):
188
188
  def __truediv__(self, key: _ty.Self | str) -> _ty.Self:
189
189
  try:
190
190
  return type(self)(self, key)
191
- except (TypeError, NotImplementedError) as _err:
191
+ except (TypeError, NotImplementedError):
192
192
  return NotImplemented
193
193
 
194
194
  def joinpath(self, *args: str | _ty.Self) -> _ty.Self:
@@ -263,7 +263,7 @@ class Uri(Pathname):
263
263
  elif os.name == "nt":
264
264
  return f"//{self.source.host}/{self.path.removeprefix('/')}"
265
265
  else:
266
- raise NotImplementedError(f"OS Support for not local fspath")
266
+ raise NotImplementedError("OS Support for not local fspath")
267
267
 
268
268
  raise NotImplementedError(f"fspath for {self.source.scheme}")
269
269
 
@@ -513,7 +513,7 @@ class UriPath(Uri, Path):
513
513
  def _schemes(cls) -> _ty.Sequence[str]:
514
514
  try:
515
515
  return getattr(cls, f"_{cls.__name__}__SCHEMES")
516
- except AttributeError as _e:
516
+ except AttributeError:
517
517
  return ()
518
518
 
519
519
  @classmethod
@@ -5,7 +5,7 @@ import typing as _ty
5
5
 
6
6
  from ... import utils as _utils
7
7
  from ...utils.stat import FileStat
8
- from .. import Source, Uri, UriPath
8
+ from .. import Uri, UriPath
9
9
 
10
10
 
11
11
  class BaseAzBackend(object):
@@ -6,7 +6,7 @@ import io as _io
6
6
  import urllib.parse as _urlparse
7
7
 
8
8
  from ...utils.stat import FileStat
9
- from ._gitrepo import RepoBackend, _RepoApiPath, _translate_repo_errors
9
+ from ._gitrepo import RepoBackend, _RepoApiPath, _translate_repo_errors # noqa: F401 (re-exported)
10
10
 
11
11
 
12
12
  class GitHubPath(_RepoApiPath):
@@ -4,7 +4,7 @@ import io as _io
4
4
  import urllib.parse as _urlparse
5
5
 
6
6
  from ...utils.stat import FileStat
7
- from ._gitrepo import RepoBackend, _RepoApiPath, _translate_repo_errors
7
+ from ._gitrepo import RepoBackend, _RepoApiPath, _translate_repo_errors # noqa: F401 (re-exported)
8
8
 
9
9
 
10
10
  class GitLabPath(_RepoApiPath):
@@ -5,7 +5,7 @@ import typing as _ty
5
5
 
6
6
  from ... import utils as _utils
7
7
  from ...utils.stat import FileStat
8
- from .. import Source, Uri, UriPath
8
+ from .. import Uri, UriPath
9
9
 
10
10
 
11
11
  class BaseGsBackend(object):
@@ -247,5 +247,5 @@ class GsPath(UriPath):
247
247
  target = Uri(self.parent, target)
248
248
  dest_key = target.path.lstrip("/")
249
249
  source_blob = self._bucket.blob(self.key)
250
- dest_blob = self._bucket.copy_blob(source_blob, self._bucket, dest_key)
250
+ self._bucket.copy_blob(source_blob, self._bucket, dest_key)
251
251
  source_blob.delete()
@@ -8,7 +8,7 @@ import botocore.exceptions as _botoexc
8
8
 
9
9
  from ... import utils as _utils
10
10
  from ...utils.stat import FileStat
11
- from .. import Source, Uri, UriPath
11
+ from .. import Uri, UriPath
12
12
 
13
13
 
14
14
  class BaseS3Backend(object):
@@ -482,14 +482,28 @@ class AsyncsshSftpBackend(BaseSftpBackend):
482
482
  #: real-world OpenSSH v3 servers, or the standard opcode against v5/v6).
483
483
  supports_hardlink = True
484
484
 
485
+ #: Default bound on concurrent SFTP requests during a recursive copy/rm.
486
+ #: 16 (raised from 8 in 0.8.3): a 128-file loopback sweep of mc in
487
+ #: {1,2,4,8,16} (median of 3, 3.14) showed recursive copy improving
488
+ #: monotonically with concurrency -- mc=1 -> mc=8 ~1.13x, mc=8 -> mc=16 a
489
+ #: further ~3% -- with recursive rm flat (spread within run-to-run noise) and
490
+ #: mc=16 fastest-or-tied for both. 16 stays well inside asyncssh's SFTP
491
+ #: request window, so the extra in-flight requests carry no overload risk.
492
+ #: NOTE: loopback evidence only (no per-op network latency); a high-latency
493
+ #: remote link may favour even higher concurrency, but 16 is a safe, modest
494
+ #: default. Override per-backend via ``max_concurrency=``.
495
+ DEFAULT_MAX_CONCURRENCY = 16
496
+
485
497
  def __init__(
486
498
  self,
487
499
  connect_opts: "dict[str, _ty.Any] | None" = None,
488
500
  *,
489
- max_concurrency: int = 8,
501
+ max_concurrency: "int | None" = None,
490
502
  sftp_version: int = 4,
491
503
  ssh_config=_DEFAULT_SSH_CONFIG,
492
504
  ):
505
+ if max_concurrency is None:
506
+ max_concurrency = self.DEFAULT_MAX_CONCURRENCY
493
507
  self.connect_opts = {} if connect_opts is None else dict(connect_opts)
494
508
  if "config" not in self.connect_opts:
495
509
  if ssh_config is None:
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import io
4
3
  import tarfile
5
4
  import typing as _ty
6
5
  import zipfile
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import abc as _abc
4
3
  import stat as _stat
5
4
  import typing as _ty
6
5
 
@@ -293,9 +293,12 @@ def test_asyncssh_backend_has_max_concurrency():
293
293
  assert backend.max_concurrency == 16
294
294
 
295
295
 
296
- def test_asyncssh_backend_max_concurrency_defaults_to_8():
296
+ def test_asyncssh_backend_max_concurrency_defaults_to_16():
297
+ # Raised from 8 to 16 in 0.8.3 (loopback recursive-copy sweep); see
298
+ # DEFAULT_MAX_CONCURRENCY's rationale in _asyncssh.py.
297
299
  backend = backend_mod.AsyncsshSftpBackend()
298
- assert backend.max_concurrency == 8
300
+ assert backend.max_concurrency == 16
301
+ assert backend.max_concurrency == backend_mod.AsyncsshSftpBackend.DEFAULT_MAX_CONCURRENCY
299
302
  assert "config" not in backend.connect_opts
300
303
 
301
304
 
@@ -757,3 +760,14 @@ def test_sftppath_rm_recursive_uses_concurrent_helper(monkeypatch):
757
760
  assert recorded["kwargs"]["max_concurrency"] == 6
758
761
  assert recorded["kwargs"]["missing_ok"] is True
759
762
  assert recorded["kwargs"]["on_error"](ValueError("ignored"), src) is True
763
+
764
+
765
+ # --- default max_concurrency -------------------------------------------------
766
+
767
+
768
+ def test_explicit_max_concurrency_overrides_default():
769
+ backend = backend_mod.AsyncsshSftpBackend({"config": None}, max_concurrency=4)
770
+ assert backend.max_concurrency == 4
771
+ # 0 is honored as given (the async helpers clamp to >=1 at use time via
772
+ # `max(1, max_concurrency)`), not silently replaced by the default.
773
+ assert backend_mod.AsyncsshSftpBackend({"config": None}, max_concurrency=1).max_concurrency == 1
File without changes
File without changes
File without changes
File without changes
File without changes