From 8b14050b9127fbbc96dd1b3d4b78d45c6e3fc318 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sat, 27 Dec 2025 18:41:33 +0900 Subject: [PATCH 01/14] 2025-12-27 18:41:33 (Sat) > DW-Mac > derekwan --- pyproject.toml | 4 +-- src/utilities/__init__.py | 2 +- src/utilities/subprocess.py | 66 ++++++++++++++++++++++++++++++++++++- uv.lock | 2 +- 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2a13f56ad9..771ddb5a70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.16" + version = "0.174.8" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" @@ -135,7 +135,7 @@ # bump-my-version [tool.bumpversion] allow_dirty = true - current_version = "0.174.16" + current_version = "0.174.8" [[tool.bumpversion.files]] filename = "src/utilities/__init__.py" diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index aed1b0104c..7b849f16e4 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.16" +__version__ = "0.174.8" diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 2bfc8ed837..d06e737380 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -17,7 +17,7 @@ from utilities.errors import ImpossibleCaseError from utilities.iterables import always_iterable from utilities.logging import to_logger -from utilities.permissions import ensure_perms +from utilities.tempfile import TemporaryDirectory from utilities.text import strip_and_dedent from utilities.whenever import to_seconds @@ -449,6 +449,69 @@ def rsync_cmd( ## +def rsync_many( + user: str, + hostname: str, + /, + *items: tuple[PathLike, PathLike], + retry: Retry | None = None, + logger: LoggerLike | None = None, + keep: bool = False, + sudo: tuple[PathLike, PathLike] | list[tuple[PathLike, PathLike]] | None = None, + print: bool = False, # noqa: A002 +) -> None: + i = 0 + cmds: list[str] = [] + with ( + TemporaryDirectory() as temp_src, + yield_ssh_temp_dir( + user, hostname, retry=retry, logger=logger, keep=keep + ) as temp_dest, + ): + for src, dest in items: + copy_file(src, temp_src / str(i)) + cmds.extend([mkdir_cmd(dest, parent=True), cp_cmd(str(i), dest)]) + i += 1 + for dest, txt in text: + write_text(temp_src / str(i), txt) + cmds.extend([mkdir_cmd(dest, parent=True), cp_cmd(str(i), dest)]) + i += 1 + for src, dest in sudo_files: + copy_file(src, temp_src / str(i)) + cmds.extend([ + maybe_sudo_cmd(mkdir_cmd(dest, parent=True), sudo=not root), + maybe_sudo_cmd(cp_cmd(str(i), dest), sudo=not root), + ]) + i += 1 + for dest, txt in sudo_text: + write_text(temp_src / str(i), txt) + cmds.extend([ + maybe_sudo_cmd(mkdir_cmd(dest, parent=True), sudo=not root), + maybe_sudo_cmd(cp_cmd(str(i), dest), sudo=not root), + ]) + i += 1 + rsync( + f"{temp_src}/*", + hostname, + temp_dest, + root=root, + retry=retry, + archive=True, + exclude=exclude, + ) + ssh(hostname, *cmds, root=root, cd=temp_dest, retry=retry) + + +def _rsync_many_prepare(src: PathLike, dest: PathLike, temp: PathLike, /) -> list[str]: + dest, temp = map(Path, [dest, temp]) + i = len(list(temp.iterdir())) + copyf + + +## +## + + @overload def run( cmd: str, @@ -1041,6 +1104,7 @@ def yield_ssh_temp_dir( "rm_cmd", "rsync", "rsync_cmd", + "rsync_many", "run", "set_hostname_cmd", "ssh", diff --git a/uv.lock b/uv.lock index 907bd640bb..db1f3afa1e 100644 --- a/uv.lock +++ b/uv.lock @@ -634,7 +634,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.16" +version = "0.174.8" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, From a65244b790ee1ff3ed1669d915ac942f3384c6b5 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sat, 27 Dec 2025 18:41:47 +0900 Subject: [PATCH 02/14] 2025-12-27 18:41:47 (Sat) > DW-Mac > derekwan --- pyproject.toml | 4 ++-- src/utilities/__init__.py | 2 +- src/utilities/subprocess.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 771ddb5a70..8696c5b3af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.8" + version = "0.174.13" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" @@ -135,7 +135,7 @@ # bump-my-version [tool.bumpversion] allow_dirty = true - current_version = "0.174.8" + current_version = "0.174.13" [[tool.bumpversion.files]] filename = "src/utilities/__init__.py" diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index 7b849f16e4..4e4fd1882b 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.8" +__version__ = "0.174.13" diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index d06e737380..c41ac8639c 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -504,7 +504,7 @@ def rsync_many( def _rsync_many_prepare(src: PathLike, dest: PathLike, temp: PathLike, /) -> list[str]: dest, temp = map(Path, [dest, temp]) - i = len(list(temp.iterdir())) + len(list(temp.iterdir())) copyf From 4ea9992f3a8b53e4df9004305e04eac0f9fe62b1 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sun, 28 Dec 2025 08:26:25 +0900 Subject: [PATCH 03/14] 2025-12-28 08:26:25 (Sun) > DW-Mac > derekwan --- src/utilities/subprocess.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index c41ac8639c..e9faa25f6e 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -17,6 +17,7 @@ from utilities.errors import ImpossibleCaseError from utilities.iterables import always_iterable from utilities.logging import to_logger +from utilities.permissions import ensure_perms from utilities.tempfile import TemporaryDirectory from utilities.text import strip_and_dedent from utilities.whenever import to_seconds @@ -504,12 +505,11 @@ def rsync_many( def _rsync_many_prepare(src: PathLike, dest: PathLike, temp: PathLike, /) -> list[str]: dest, temp = map(Path, [dest, temp]) - len(list(temp.iterdir())) - copyf + n = len(list(temp.iterdir())) + copy_file() ## -## @overload From 14a62770be626e11a2f5623aa83790126c7209e8 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sun, 28 Dec 2025 19:29:43 +0900 Subject: [PATCH 04/14] 2025-12-28 19:29:43 (Sun) > DW-Mac > derekwan --- pyproject.toml | 4 ++-- src/utilities/__init__.py | 2 +- src/utilities/subprocess.py | 2 +- uv.lock | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8696c5b3af..225ec7e2a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,7 +101,7 @@ name = "dycw-utilities" readme = "README.md" requires-python = ">= 3.12" - version = "0.174.13" + version = "0.174.17" [project.entry-points.pytest11] pytest-randomly = "utilities.pytest_plugins.pytest_randomly" @@ -135,7 +135,7 @@ # bump-my-version [tool.bumpversion] allow_dirty = true - current_version = "0.174.13" + current_version = "0.174.17" [[tool.bumpversion.files]] filename = "src/utilities/__init__.py" diff --git a/src/utilities/__init__.py b/src/utilities/__init__.py index 4e4fd1882b..88301dd685 100644 --- a/src/utilities/__init__.py +++ b/src/utilities/__init__.py @@ -1,3 +1,3 @@ from __future__ import annotations -__version__ = "0.174.13" +__version__ = "0.174.17" diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index e9faa25f6e..1437cb4f01 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -505,7 +505,7 @@ def rsync_many( def _rsync_many_prepare(src: PathLike, dest: PathLike, temp: PathLike, /) -> list[str]: dest, temp = map(Path, [dest, temp]) - n = len(list(temp.iterdir())) + len(list(temp.iterdir())) copy_file() diff --git a/uv.lock b/uv.lock index db1f3afa1e..6881af60ae 100644 --- a/uv.lock +++ b/uv.lock @@ -634,7 +634,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.8" +version = "0.174.13" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, From 30f05141c5d10099fdf43c8a9064f4da8c8769c0 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sun, 28 Dec 2025 19:50:58 +0900 Subject: [PATCH 05/14] 2025-12-28 19:50:58 (Sun) > DW-Mac > derekwan --- src/utilities/subprocess.py | 105 +++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 1437cb4f01..5328f95dad 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -16,8 +16,8 @@ from utilities.errors import ImpossibleCaseError from utilities.iterables import always_iterable -from utilities.logging import to_logger -from utilities.permissions import ensure_perms +from utilities.logging import add_adapter, to_logger +from utilities.permissions import Permissions, ensure_perms from utilities.tempfile import TemporaryDirectory from utilities.text import strip_and_dedent from utilities.whenever import to_seconds @@ -347,7 +347,7 @@ def rsync( chown_user: str | None = None, chown_group: str | None = None, exclude: MaybeIterable[str] | None = None, - chmod: str | None = None, + chmod: PermissionsLike | None = None, ) -> None: """Remote & local file copying.""" mkdir_args = maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo) # skipif-ci @@ -454,59 +454,88 @@ def rsync_many( user: str, hostname: str, /, - *items: tuple[PathLike, PathLike], + *items: tuple[PathLike, PathLike] + | tuple[Literal["sudo"], PathLike, PathLike] + | tuple[PathLike, PathLike, PermissionsLike], retry: Retry | None = None, logger: LoggerLike | None = None, keep: bool = False, - sudo: tuple[PathLike, PathLike] | list[tuple[PathLike, PathLike]] | None = None, + batch_mode: bool = True, + host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS, + strict_host_key_checking: bool = True, print: bool = False, # noqa: A002 ) -> None: - i = 0 - cmds: list[str] = [] - with ( + cmds: list[list[str]] = [] # skipif-ci + with ( # skipif-ci TemporaryDirectory() as temp_src, yield_ssh_temp_dir( user, hostname, retry=retry, logger=logger, keep=keep ) as temp_dest, ): - for src, dest in items: - copy_file(src, temp_src / str(i)) - cmds.extend([mkdir_cmd(dest, parent=True), cp_cmd(str(i), dest)]) - i += 1 - for dest, txt in text: - write_text(temp_src / str(i), txt) - cmds.extend([mkdir_cmd(dest, parent=True), cp_cmd(str(i), dest)]) - i += 1 - for src, dest in sudo_files: - copy_file(src, temp_src / str(i)) - cmds.extend([ - maybe_sudo_cmd(mkdir_cmd(dest, parent=True), sudo=not root), - maybe_sudo_cmd(cp_cmd(str(i), dest), sudo=not root), - ]) - i += 1 - for dest, txt in sudo_text: - write_text(temp_src / str(i), txt) - cmds.extend([ - maybe_sudo_cmd(mkdir_cmd(dest, parent=True), sudo=not root), - maybe_sudo_cmd(cp_cmd(str(i), dest), sudo=not root), - ]) - i += 1 + for item in items: + match item: + case Path() | str() as src, Path() | str() as dest: + cmds.extend(_rsync_many_prepare(src, dest, temp_src, temp_dest)) + case "sudo", Path() | str() as src, Path() | str() as dest: + cmds.extend( + _rsync_many_prepare(src, dest, temp_src, temp_dest, sudo=True) + ) + case ( + Path() | str() as src, + Path() | str() as dest, + Permissions() | int() | str() as perms, + ): + cmds.extend( + _rsync_many_prepare(src, dest, temp_src, temp_dest, perms=perms) + ) + case never: + assert_never(never) rsync( - f"{temp_src}/*", + temp_src, + user, hostname, temp_dest, - root=root, + batch_mode=batch_mode, + host_key_algorithms=host_key_algorithms, + strict_host_key_checking=strict_host_key_checking, + print=print, retry=retry, - archive=True, - exclude=exclude, + logger=logger, ) ssh(hostname, *cmds, root=root, cd=temp_dest, retry=retry) -def _rsync_many_prepare(src: PathLike, dest: PathLike, temp: PathLike, /) -> list[str]: - dest, temp = map(Path, [dest, temp]) - len(list(temp.iterdir())) - copy_file() +def _rsync_many_prepare( + src: PathLike, + dest: PathLike, + temp_src: PathLike, + temp_dest: PathLike, + /, + *, + sudo: bool = False, + perms: PermissionsLike | None = None, +) -> list[list[str]]: + dest, temp_src, temp_dest = map(Path, [dest, temp_src, temp_dest]) + n = len(list(temp_src.iterdir())) + name = str(n) + dest = temp_src / name + match src: + case Path(): + cp(src, dest) + case str(): + if Path(src).exists(): + cp(src, dest) + else: + tee(dest, src) + case never: + assert_never(never) + cmds: list[list[str]] = [ + maybe_sudo_cmd(*mkdir_cmd(dest, parent=True), sudo=sudo), + maybe_sudo_cmd(*cp_cmd(temp_dest / name, dest), sudo=sudo), + ] + if perms is not None: + cmds.append(maybe_sudo_cmd(*chmod_cmd(dest, perms), sudo=sudo)) + return cmds ## From 2a6df6312ddcd5eac676d43b69c4ecdb3452cf45 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Sun, 28 Dec 2025 19:51:12 +0900 Subject: [PATCH 06/14] 2025-12-28 19:51:12 (Sun) > DW-Mac > derekwan --- src/utilities/subprocess.py | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 5328f95dad..2808bd0792 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -16,7 +16,7 @@ from utilities.errors import ImpossibleCaseError from utilities.iterables import always_iterable -from utilities.logging import add_adapter, to_logger +from utilities.logging import to_logger from utilities.permissions import Permissions, ensure_perms from utilities.tempfile import TemporaryDirectory from utilities.text import strip_and_dedent diff --git a/uv.lock b/uv.lock index 6881af60ae..79d407c307 100644 --- a/uv.lock +++ b/uv.lock @@ -634,7 +634,7 @@ wheels = [ [[package]] name = "dycw-utilities" -version = "0.174.13" +version = "0.174.17" source = { editable = "." } dependencies = [ { name = "atomicwrites" }, From bc5bac512e19c7aea4ad19c64f9d1461f5e5b70d Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Mon, 29 Dec 2025 10:25:34 +0900 Subject: [PATCH 07/14] 2025-12-29 10:25:34 (Mon) > DW-Mac > derekwan --- src/tests/test_subprocess.py | 24 ++++++++++++++++++++++++ src/utilities/subprocess.py | 10 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index 75e81c4b21..8d7a7565a7 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -43,6 +43,7 @@ rm_cmd, rsync, rsync_cmd, + rsync_many, run, set_hostname_cmd, ssh, @@ -558,6 +559,29 @@ def test_parent(self) -> None: assert result == expected +class TestRsyncMany: + @mark.only + def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: + with ( + TemporaryDirectory() as src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp, + ): + src1, src2 = [src / f"file{i}.txt" for i in [1, 2]] + src1.touch() + src2.touch() + dest1, dest2 = [temp / src.name for src in [src1, src2]] + rsync_many(ssh_user, ssh_hostname, (src1, dest1), (src2, dest2)) + ssh( + ssh_user, + ssh_hostname, + *BASH_LS, + input=strip_and_dedent(f""" + if ! [ -d {dest1} ]; then exit 1; fi + if ! [ -f {dest1}/file.txt ]; then exit 1; fi + """), + ) + + class TestRun: def test_main(self, *, capsys: CaptureFixture) -> None: result = run("echo", "hi") diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 2808bd0792..2eacbe29d4 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -502,7 +502,15 @@ def rsync_many( retry=retry, logger=logger, ) - ssh(hostname, *cmds, root=root, cd=temp_dest, retry=retry) + ssh( + user, + hostname, + *BASH_LS, + input="\n".join(map(join, cmds)), + print=print, + retry=retry, + logger=logger, + ) def _rsync_many_prepare( From bda5653e8370918d42ace68591d8ca4b4502c72d Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Mon, 29 Dec 2025 10:25:48 +0900 Subject: [PATCH 08/14] 2025-12-29 10:25:48 (Mon) > DW-Mac > derekwan --- uv.lock | 186 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/uv.lock b/uv.lock index 79d407c307..b17f5ec4c8 100644 --- a/uv.lock +++ b/uv.lock @@ -433,76 +433,76 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/45/2c665ca77ec32ad67e25c77daf1cee28ee4558f3bc571cdbaf88a00b9f23/coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936", size = 820905, upload-time = "2025-12-08T13:14:38.055Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f1/2619559f17f31ba00fc40908efd1fbf1d0a5536eb75dc8341e7d660a08de/coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf", size = 218274, upload-time = "2025-12-08T13:12:52.095Z" }, - { url = "https://files.pythonhosted.org/packages/2b/11/30d71ae5d6e949ff93b2a79a2c1b4822e00423116c5c6edfaeef37301396/coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f", size = 218638, upload-time = "2025-12-08T13:12:53.418Z" }, - { url = "https://files.pythonhosted.org/packages/79/c2/fce80fc6ded8d77e53207489d6065d0fed75db8951457f9213776615e0f5/coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb", size = 250129, upload-time = "2025-12-08T13:12:54.744Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b6/51b5d1eb6fcbb9a1d5d6984e26cbe09018475c2922d554fd724dd0f056ee/coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621", size = 252885, upload-time = "2025-12-08T13:12:56.401Z" }, - { url = "https://files.pythonhosted.org/packages/0d/f8/972a5affea41de798691ab15d023d3530f9f56a72e12e243f35031846ff7/coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74", size = 253974, upload-time = "2025-12-08T13:12:57.718Z" }, - { url = "https://files.pythonhosted.org/packages/8a/56/116513aee860b2c7968aa3506b0f59b22a959261d1dbf3aea7b4450a7520/coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57", size = 250538, upload-time = "2025-12-08T13:12:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/d6/75/074476d64248fbadf16dfafbf93fdcede389ec821f74ca858d7c87d2a98c/coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8", size = 251912, upload-time = "2025-12-08T13:13:00.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d2/aa4f8acd1f7c06024705c12609d8698c51b27e4d635d717cd1934c9668e2/coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d", size = 250054, upload-time = "2025-12-08T13:13:01.892Z" }, - { url = "https://files.pythonhosted.org/packages/19/98/8df9e1af6a493b03694a1e8070e024e7d2cdc77adedc225a35e616d505de/coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b", size = 249619, upload-time = "2025-12-08T13:13:03.236Z" }, - { url = "https://files.pythonhosted.org/packages/d8/71/f8679231f3353018ca66ef647fa6fe7b77e6bff7845be54ab84f86233363/coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd", size = 251496, upload-time = "2025-12-08T13:13:04.511Z" }, - { url = "https://files.pythonhosted.org/packages/04/86/9cb406388034eaf3c606c22094edbbb82eea1fa9d20c0e9efadff20d0733/coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef", size = 220808, upload-time = "2025-12-08T13:13:06.422Z" }, - { url = "https://files.pythonhosted.org/packages/1c/59/af483673df6455795daf5f447c2f81a3d2fcfc893a22b8ace983791f6f34/coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae", size = 221616, upload-time = "2025-12-08T13:13:07.95Z" }, - { url = "https://files.pythonhosted.org/packages/64/b0/959d582572b30a6830398c60dd419c1965ca4b5fb38ac6b7093a0d50ca8d/coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080", size = 220261, upload-time = "2025-12-08T13:13:09.581Z" }, - { url = "https://files.pythonhosted.org/packages/7c/cc/bce226595eb3bf7d13ccffe154c3c487a22222d87ff018525ab4dd2e9542/coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf", size = 218297, upload-time = "2025-12-08T13:13:10.977Z" }, - { url = "https://files.pythonhosted.org/packages/3b/9f/73c4d34600aae03447dff3d7ad1d0ac649856bfb87d1ca7d681cfc913f9e/coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a", size = 218673, upload-time = "2025-12-08T13:13:12.562Z" }, - { url = "https://files.pythonhosted.org/packages/63/ab/8fa097db361a1e8586535ae5073559e6229596b3489ec3ef2f5b38df8cb2/coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74", size = 249652, upload-time = "2025-12-08T13:13:13.909Z" }, - { url = "https://files.pythonhosted.org/packages/90/3a/9bfd4de2ff191feb37ef9465855ca56a6f2f30a3bca172e474130731ac3d/coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6", size = 252251, upload-time = "2025-12-08T13:13:15.553Z" }, - { url = "https://files.pythonhosted.org/packages/df/61/b5d8105f016e1b5874af0d7c67542da780ccd4a5f2244a433d3e20ceb1ad/coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b", size = 253492, upload-time = "2025-12-08T13:13:16.849Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b8/0fad449981803cc47a4694768b99823fb23632150743f9c83af329bb6090/coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232", size = 249850, upload-time = "2025-12-08T13:13:18.142Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e9/8d68337c3125014d918cf4327d5257553a710a2995a6a6de2ac77e5aa429/coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971", size = 251633, upload-time = "2025-12-08T13:13:19.56Z" }, - { url = "https://files.pythonhosted.org/packages/55/14/d4112ab26b3a1bc4b3c1295d8452dcf399ed25be4cf649002fb3e64b2d93/coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d", size = 249586, upload-time = "2025-12-08T13:13:20.883Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a9/22b0000186db663b0d82f86c2f1028099ae9ac202491685051e2a11a5218/coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137", size = 249412, upload-time = "2025-12-08T13:13:22.22Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2e/42d8e0d9e7527fba439acdc6ed24a2b97613b1dc85849b1dd935c2cffef0/coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511", size = 251191, upload-time = "2025-12-08T13:13:23.899Z" }, - { url = "https://files.pythonhosted.org/packages/a4/af/8c7af92b1377fd8860536aadd58745119252aaaa71a5213e5a8e8007a9f5/coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1", size = 220829, upload-time = "2025-12-08T13:13:25.182Z" }, - { url = "https://files.pythonhosted.org/packages/58/f9/725e8bf16f343d33cbe076c75dc8370262e194ff10072c0608b8e5cf33a3/coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a", size = 221640, upload-time = "2025-12-08T13:13:26.836Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ff/e98311000aa6933cc79274e2b6b94a2fe0fe3434fca778eba82003675496/coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6", size = 220269, upload-time = "2025-12-08T13:13:28.116Z" }, - { url = "https://files.pythonhosted.org/packages/cf/cf/bbaa2e1275b300343ea865f7d424cc0a2e2a1df6925a070b2b2d5d765330/coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a", size = 218990, upload-time = "2025-12-08T13:13:29.463Z" }, - { url = "https://files.pythonhosted.org/packages/21/1d/82f0b3323b3d149d7672e7744c116e9c170f4957e0c42572f0366dbb4477/coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8", size = 219340, upload-time = "2025-12-08T13:13:31.524Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e3/fe3fd4702a3832a255f4d43013eacb0ef5fc155a5960ea9269d8696db28b/coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053", size = 260638, upload-time = "2025-12-08T13:13:32.965Z" }, - { url = "https://files.pythonhosted.org/packages/ad/01/63186cb000307f2b4da463f72af9b85d380236965574c78e7e27680a2593/coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071", size = 262705, upload-time = "2025-12-08T13:13:34.378Z" }, - { url = "https://files.pythonhosted.org/packages/7c/a1/c0dacef0cc865f2455d59eed3548573ce47ed603205ffd0735d1d78b5906/coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e", size = 265125, upload-time = "2025-12-08T13:13:35.73Z" }, - { url = "https://files.pythonhosted.org/packages/ef/92/82b99223628b61300bd382c205795533bed021505eab6dd86e11fb5d7925/coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493", size = 259844, upload-time = "2025-12-08T13:13:37.69Z" }, - { url = "https://files.pythonhosted.org/packages/cf/2c/89b0291ae4e6cd59ef042708e1c438e2290f8c31959a20055d8768349ee2/coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0", size = 262700, upload-time = "2025-12-08T13:13:39.525Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f9/a5f992efae1996245e796bae34ceb942b05db275e4b34222a9a40b9fbd3b/coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e", size = 260321, upload-time = "2025-12-08T13:13:41.172Z" }, - { url = "https://files.pythonhosted.org/packages/4c/89/a29f5d98c64fedbe32e2ac3c227fbf78edc01cc7572eee17d61024d89889/coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c", size = 259222, upload-time = "2025-12-08T13:13:43.282Z" }, - { url = "https://files.pythonhosted.org/packages/b3/c3/940fe447aae302a6701ee51e53af7e08b86ff6eed7631e5740c157ee22b9/coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e", size = 261411, upload-time = "2025-12-08T13:13:44.72Z" }, - { url = "https://files.pythonhosted.org/packages/eb/31/12a4aec689cb942a89129587860ed4d0fd522d5fda81237147fde554b8ae/coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46", size = 221505, upload-time = "2025-12-08T13:13:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/65/8c/3b5fe3259d863572d2b0827642c50c3855d26b3aefe80bdc9eba1f0af3b0/coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39", size = 222569, upload-time = "2025-12-08T13:13:47.79Z" }, - { url = "https://files.pythonhosted.org/packages/b0/39/f71fa8316a96ac72fc3908839df651e8eccee650001a17f2c78cdb355624/coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e", size = 220841, upload-time = "2025-12-08T13:13:49.243Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4b/9b54bedda55421449811dcd5263a2798a63f48896c24dfb92b0f1b0845bd/coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256", size = 218343, upload-time = "2025-12-08T13:13:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/59/df/c3a1f34d4bba2e592c8979f924da4d3d4598b0df2392fbddb7761258e3dc/coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a", size = 218672, upload-time = "2025-12-08T13:13:52.284Z" }, - { url = "https://files.pythonhosted.org/packages/07/62/eec0659e47857698645ff4e6ad02e30186eb8afd65214fd43f02a76537cb/coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9", size = 249715, upload-time = "2025-12-08T13:13:53.791Z" }, - { url = "https://files.pythonhosted.org/packages/23/2d/3c7ff8b2e0e634c1f58d095f071f52ed3c23ff25be524b0ccae8b71f99f8/coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19", size = 252225, upload-time = "2025-12-08T13:13:55.274Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ac/fb03b469d20e9c9a81093575003f959cf91a4a517b783aab090e4538764b/coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be", size = 253559, upload-time = "2025-12-08T13:13:57.161Z" }, - { url = "https://files.pythonhosted.org/packages/29/62/14afa9e792383c66cc0a3b872a06ded6e4ed1079c7d35de274f11d27064e/coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb", size = 249724, upload-time = "2025-12-08T13:13:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/31/b7/333f3dab2939070613696ab3ee91738950f0467778c6e5a5052e840646b7/coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8", size = 251582, upload-time = "2025-12-08T13:14:00.642Z" }, - { url = "https://files.pythonhosted.org/packages/81/cb/69162bda9381f39b2287265d7e29ee770f7c27c19f470164350a38318764/coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b", size = 249538, upload-time = "2025-12-08T13:14:02.556Z" }, - { url = "https://files.pythonhosted.org/packages/e0/76/350387b56a30f4970abe32b90b2a434f87d29f8b7d4ae40d2e8a85aacfb3/coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9", size = 249349, upload-time = "2025-12-08T13:14:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/86/0d/7f6c42b8d59f4c7e43ea3059f573c0dcfed98ba46eb43c68c69e52ae095c/coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927", size = 251011, upload-time = "2025-12-08T13:14:05.505Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f1/4bb2dff379721bb0b5c649d5c5eaf438462cad824acf32eb1b7ca0c7078e/coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f", size = 221091, upload-time = "2025-12-08T13:14:07.127Z" }, - { url = "https://files.pythonhosted.org/packages/ba/44/c239da52f373ce379c194b0ee3bcc121020e397242b85f99e0afc8615066/coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc", size = 221904, upload-time = "2025-12-08T13:14:08.542Z" }, - { url = "https://files.pythonhosted.org/packages/89/1f/b9f04016d2a29c2e4a0307baefefad1a4ec5724946a2b3e482690486cade/coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b", size = 220480, upload-time = "2025-12-08T13:14:10.958Z" }, - { url = "https://files.pythonhosted.org/packages/16/d4/364a1439766c8e8647860584171c36010ca3226e6e45b1753b1b249c5161/coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28", size = 219074, upload-time = "2025-12-08T13:14:13.345Z" }, - { url = "https://files.pythonhosted.org/packages/ce/f4/71ba8be63351e099911051b2089662c03d5671437a0ec2171823c8e03bec/coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe", size = 219342, upload-time = "2025-12-08T13:14:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/5e/25/127d8ed03d7711a387d96f132589057213e3aef7475afdaa303412463f22/coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657", size = 260713, upload-time = "2025-12-08T13:14:16.907Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/559fbb6def07d25b2243663b46ba9eb5a3c6586c0c6f4e62980a68f0ee1c/coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff", size = 262825, upload-time = "2025-12-08T13:14:18.68Z" }, - { url = "https://files.pythonhosted.org/packages/37/99/6ee5bf7eff884766edb43bd8736b5e1c5144d0fe47498c3779326fe75a35/coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3", size = 265233, upload-time = "2025-12-08T13:14:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/d8/90/92f18fe0356ea69e1f98f688ed80cec39f44e9f09a1f26a1bbf017cc67f2/coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b", size = 259779, upload-time = "2025-12-08T13:14:22.367Z" }, - { url = "https://files.pythonhosted.org/packages/90/5d/b312a8b45b37a42ea7d27d7d3ff98ade3a6c892dd48d1d503e773503373f/coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d", size = 262700, upload-time = "2025-12-08T13:14:24.309Z" }, - { url = "https://files.pythonhosted.org/packages/63/f8/b1d0de5c39351eb71c366f872376d09386640840a2e09b0d03973d791e20/coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e", size = 260302, upload-time = "2025-12-08T13:14:26.068Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7c/d42f4435bc40c55558b3109a39e2d456cddcec37434f62a1f1230991667a/coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940", size = 259136, upload-time = "2025-12-08T13:14:27.604Z" }, - { url = "https://files.pythonhosted.org/packages/b8/d3/23413241dc04d47cfe19b9a65b32a2edd67ecd0b817400c2843ebc58c847/coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2", size = 261467, upload-time = "2025-12-08T13:14:29.09Z" }, - { url = "https://files.pythonhosted.org/packages/13/e6/6e063174500eee216b96272c0d1847bf215926786f85c2bd024cf4d02d2f/coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7", size = 221875, upload-time = "2025-12-08T13:14:31.106Z" }, - { url = "https://files.pythonhosted.org/packages/3b/46/f4fb293e4cbe3620e3ac2a3e8fd566ed33affb5861a9b20e3dd6c1896cbc/coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc", size = 222982, upload-time = "2025-12-08T13:14:33.1Z" }, - { url = "https://files.pythonhosted.org/packages/68/62/5b3b9018215ed9733fbd1ae3b2ed75c5de62c3b55377a52cae732e1b7805/coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a", size = 221016, upload-time = "2025-12-08T13:14:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, +version = "7.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, + { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, + { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, + { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, + { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, + { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, + { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, + { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, + { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, + { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, + { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, + { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, + { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, + { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, + { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, + { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, + { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, + { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, + { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, + { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, ] [[package]] @@ -3469,34 +3469,34 @@ wheels = [ [[package]] name = "scs" -version = "3.2.9" +version = "3.2.10" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "scipy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/c0/b894547702586a252f8c417e0c77111c9d2ae1d69c4a7751eb505e4fdb62/scs-3.2.9.tar.gz", hash = "sha256:df9542d435d21938ed09494a6c525a9772779902b61300961e16890a2df7f572", size = 1690742, upload-time = "2025-10-12T20:20:21.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/ad/b92b49fb53dbeb710778e6b637c548ea199ca4e31076fe9db3f09b9e2c1c/scs-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25e1820f5b71430ab05c0b50a1b858b5e482c8fd8d47aa419d18684f5884fc43", size = 95655, upload-time = "2025-10-12T20:19:37.835Z" }, - { url = "https://files.pythonhosted.org/packages/c3/87/a475f78562a6306b854967922779ec0ca7b9463f8170a1edc6327a571f64/scs-3.2.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78b5a7fd71e4627d7faf0c84a808393a0e51864d0e3db92a69abdb33c4a6bf0c", size = 5069831, upload-time = "2025-10-12T20:19:38.876Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7d/ee3614881243a0b915cb613804e9f8435c252563e9e75666229c90ebb69e/scs-3.2.9-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf730f64158b6e924b43348a609bb0bac819b8e517a990c2f156b0de5251990f", size = 12078825, upload-time = "2025-10-12T20:19:40.362Z" }, - { url = "https://files.pythonhosted.org/packages/0c/24/d26dfe6c6ab91dd4b8f9e6061ddefb8926292e2ac4fae687203c33bbab42/scs-3.2.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9cfb3abb4662b1d4662415c7c6049b5b0f60299f515b64f0d4f2a8c53c0d5a4", size = 11972926, upload-time = "2025-10-12T20:19:42.511Z" }, - { url = "https://files.pythonhosted.org/packages/ea/64/fd0fca73d492657fdf97c1a335138b377675bc2b1539c90b571369148ac8/scs-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:e35cb089d536bc001a53fa708a40db0e6c304f1a318b623be3779db89d3411e7", size = 7458826, upload-time = "2025-10-12T20:19:44.517Z" }, - { url = "https://files.pythonhosted.org/packages/8b/47/cf86c988b341dd1dd59661f29b87756ccd67952c706937257b51775c0251/scs-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b585d33069829a16ef80bedd976fc6eee6e1287324f218ef6995f40e84a4566", size = 95653, upload-time = "2025-10-12T20:19:45.992Z" }, - { url = "https://files.pythonhosted.org/packages/2a/38/eb3b2d98aa5a24e6b4752520c220568e065809d02707336756e602dbdd93/scs-3.2.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c372807c261f3791b21b83d0a631f2a3bb79b73f16d9087dc289ea90560f59b1", size = 5069833, upload-time = "2025-10-12T20:19:47.056Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ef/26238d2f0e851ffbb73d0c34c5b59245229af6c8b979a959fda9ab5278ca/scs-3.2.9-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9835c50081dfc270735fe339cced27ce2818383ea779fc6c673c885b0cdf849f", size = 12078832, upload-time = "2025-10-12T20:19:49.112Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b8/b29c2813487c8718c679db2986ef27b13d4169696dd084ffab110cb34060/scs-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5188d3b77f618c321bcb9486a0864e39dea2774d8a52ed9b8355d7dc42f5ee77", size = 11972927, upload-time = "2025-10-12T20:19:51.153Z" }, - { url = "https://files.pythonhosted.org/packages/ea/02/97813588bd4cb26f45c293899dd2834e25b019724a390e1a224c4f128396/scs-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:6c75f835df827e8a9e1c19668fa4b21d4b7047017d19ceab4e98db2506acc466", size = 7458828, upload-time = "2025-10-12T20:19:52.98Z" }, - { url = "https://files.pythonhosted.org/packages/7c/8f/cf5e0414e2dfcd2fea25b6653ae1f94dc3221d0df6d0ddd3e345f1930039/scs-3.2.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f34aa0fb3776b546e4d3346a367fe32b72f9b5b302ec42cbb6ba157ee987f566", size = 95682, upload-time = "2025-10-12T20:19:54.375Z" }, - { url = "https://files.pythonhosted.org/packages/5d/d2/bc9b66c50d3bbec7bd51d78261d6b79ef02be170e8694b303efea79d7956/scs-3.2.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f884952d4faf9b9e7bdaa19b9b9563955c8e27f37fdf271354e2eb28d98bac0", size = 5069860, upload-time = "2025-10-12T20:19:55.723Z" }, - { url = "https://files.pythonhosted.org/packages/cc/7b/55bdd5f88e3abdee29bcae2a2dad907bdcac24ec79f005d372abae551e6a/scs-3.2.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f25e461f52d7d3128583a64ac8b724b976b7e18bc1f04ae98b3b75a5c11a7e2", size = 12078906, upload-time = "2025-10-12T20:19:57.78Z" }, - { url = "https://files.pythonhosted.org/packages/db/f9/036942285ea56febea84149aae7aed28451d0d7727af31f951da9beee6a5/scs-3.2.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0667f1ec3f4c141ee877531ef2e4568b82633b8a41de29c8341279d7e8e7ef5c", size = 11972938, upload-time = "2025-10-12T20:19:59.814Z" }, - { url = "https://files.pythonhosted.org/packages/03/76/3733c2bf1c2022d6bad92ed2a0146e2129ef992afa87708d20b1b36c5a1d/scs-3.2.9-cp314-cp314-win_amd64.whl", hash = "sha256:feb0a7e29bd26285270a9882d1bdd4b1e981a4e9cdb8eaee5f967a47bb2882ff", size = 7549101, upload-time = "2025-10-12T20:20:02.122Z" }, - { url = "https://files.pythonhosted.org/packages/a5/1a/e6a1049e741a541a675f37424aaa6461476ab5c3833858b87437154dc87a/scs-3.2.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8573e25bc85ef77ba43bb849b4a771aeb9eb1ad43497aa181d773cc543e9a39", size = 96375, upload-time = "2025-10-12T20:20:03.444Z" }, - { url = "https://files.pythonhosted.org/packages/58/e3/9ada9f242906c0abc642e3e70cb4b65f311bdaadfb83fbd063b581a7c7b6/scs-3.2.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d02756334b2263c7514604910000bffc016e148ef9a5a8eeeaaf52ec66f506", size = 5070943, upload-time = "2025-10-12T20:20:04.824Z" }, - { url = "https://files.pythonhosted.org/packages/36/75/c11551cebba8f36ce46a32cdc71c808b03aeb601c441fc194fe31d526ab4/scs-3.2.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4328aa741df45b3632253028f516d73f77081f372f90cdefeb3b94f4f7504ea4", size = 12079147, upload-time = "2025-10-12T20:20:06.266Z" }, - { url = "https://files.pythonhosted.org/packages/0d/94/c659c0442b0386bca295f7d5e8bae1b59af12d29370bd590b00cc2ddf730/scs-3.2.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1e786a28f942f5b0b5a28af552a6cb882b366cee4b9271267d147796d71e60d0", size = 11973255, upload-time = "2025-10-12T20:20:08.212Z" }, - { url = "https://files.pythonhosted.org/packages/27/d4/752ee94d27a400199b6fd952fe5f0253ae4b9eff565b3c6476fa6378f827/scs-3.2.9-cp314-cp314t-win_amd64.whl", hash = "sha256:23577f318d25cd623fedbe7f3330189f6d5f82c855db08e3b32bf77f43efdc4b", size = 7549595, upload-time = "2025-10-12T20:20:11.209Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ad/1f/12ad77d3857aca6f9ffce2b8767a25ca64c042bef61f72b51f2fbd20827d/scs-3.2.10.tar.gz", hash = "sha256:8204a88f423ea1fdeda358690b0e01552c6737a9b2408dbe956a937b60882ff7", size = 1690234, upload-time = "2025-12-28T15:58:27.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/f4/591454b91b8156d849c20f80344cc895c251f7cbe9e6f21f6b3cff76a9ad/scs-3.2.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f792283c0cffd0bb073077da641b52640eb2b962342ffdfb57e1ccd53a3bf2e1", size = 96247, upload-time = "2025-12-28T15:57:43.153Z" }, + { url = "https://files.pythonhosted.org/packages/6b/23/55857e376a2af96670ab9bf9d2a90ecb07f09b8f0ea001fa13c9a1ab2a74/scs-3.2.10-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91619c1319cfc22f3c0efed93cc5c978dc1c2d100d8889a5e236d31b2d9107e3", size = 5071335, upload-time = "2025-12-28T15:57:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/6d/73/ce3fa39e59ccc038aa8ef157988d8d0e7d9c1936fdeb88a8861ce4b05e54/scs-3.2.10-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3f474c8d1d6033d811113a4c25740915684dc7ea70c484a701402839e066224", size = 12079866, upload-time = "2025-12-28T15:57:46.137Z" }, + { url = "https://files.pythonhosted.org/packages/58/99/dce143312d5df7a40b3235a64f5558efcd5cba2c89ff34fefe0f3a656837/scs-3.2.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:965cf8ab48110328b354f551de922e85c3b6337922786a3c0a37092ff9ec2d32", size = 11973838, upload-time = "2025-12-28T15:57:48.048Z" }, + { url = "https://files.pythonhosted.org/packages/9f/48/f69adbe347b166e6126451f6000cd7060f2693519400aec7aea7cbd7d267/scs-3.2.10-cp312-cp312-win_amd64.whl", hash = "sha256:163e60dbe58508856966cd0244789e4ee5ceb161ef6623c47eee54b0c87c3001", size = 7478337, upload-time = "2025-12-28T15:57:49.982Z" }, + { url = "https://files.pythonhosted.org/packages/e4/17/b4d58f7a29fd647f28205807dbe1ab6de3b4baf1edb273e5b16121d69c4c/scs-3.2.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1652bd9eeadaaf7b5de4f50343ce4fd0eab8d863137f5c4aeb9ccdf72e334c13", size = 96249, upload-time = "2025-12-28T15:57:51.79Z" }, + { url = "https://files.pythonhosted.org/packages/71/3f/eb325b3d576a09f6b898cbe48792a054e24605dd93a15a641bdee35c20db/scs-3.2.10-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:432c41ec35dbffcb3a2eb3b2d168b19c942394f48728c9d3dcd0c49255972113", size = 5071337, upload-time = "2025-12-28T15:57:53.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bf/9ebc6896267b19f99c62a4ccc6db9b886e2cd5f6afbc5d2964348ca6474b/scs-3.2.10-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59a2407cd8e6b5a1907ec6eb3b595eb381f4c49ee4cfdf0c694d5e850d38a26d", size = 12079867, upload-time = "2025-12-28T15:57:54.817Z" }, + { url = "https://files.pythonhosted.org/packages/e0/83/94523722dc6c629f230d512e6342276995b147ad1b2101f63ae63825dda1/scs-3.2.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:412d3b256d85454e1bf6ac32d6892a3d778fb695aa298f3fddb97df75d1b7aa6", size = 11973835, upload-time = "2025-12-28T15:57:56.73Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/4359f2049c4743080b40441c68982287b753f9d276edb084510ec56c668a/scs-3.2.10-cp313-cp313-win_amd64.whl", hash = "sha256:eb3d4ebc19eb86e19038d62ebd084fb160835a5ce25e551db98da92cf53a0614", size = 7478338, upload-time = "2025-12-28T15:57:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/40/78/e5c3f69032e2415419cec2dc70835ca35b636789ac27604f4f407a49161f/scs-3.2.10-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1024d79bb7b195e81ec1638d007fa29e7770089dea14a93586c06cb7c211e71a", size = 96300, upload-time = "2025-12-28T15:58:00.6Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/246d0c9c42677d6067a2bbd2183149732e017f59bdfab79ef9045d1ae96c/scs-3.2.10-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:740a18d0a58119b74e952869feaf8fdefb94baf0876d9961017b8c28d2402d16", size = 5071354, upload-time = "2025-12-28T15:58:01.702Z" }, + { url = "https://files.pythonhosted.org/packages/d3/22/27dfb407d48bd7ac0c1c2aa0b545ec4f076c66802150befe23a0e4b1a498/scs-3.2.10-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91bea177f8e7f364d2d1c721b734ebadd3c28e55300217d972e0c5ebd68c780c", size = 12079918, upload-time = "2025-12-28T15:58:03.343Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d1/69fe948438dce3b3ed475d39de33b122d5232cb9a7d66ab6333384cbc24c/scs-3.2.10-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50612a84868d8aa5b8b90c217c0336fb1879fec99424bf8e8963af6d35d0f86f", size = 11973931, upload-time = "2025-12-28T15:58:05.729Z" }, + { url = "https://files.pythonhosted.org/packages/6a/3a/18e8f7a9a1912a331ff2b382c5584cc8ac88e9183506754bfc4b281f8c6e/scs-3.2.10-cp314-cp314-win_amd64.whl", hash = "sha256:440d85bc22be90f0aaa540f9e7e5f363267248b286c49c50bc558a76ae26691b", size = 7569613, upload-time = "2025-12-28T15:58:07.979Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/27a6e831798d79d0b296107542d268669351d84bab4a1a6cb0a6e00bbb04/scs-3.2.10-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7458bec4ecf349290fdbbebd2d40cd99265aa61fa6722ed22d2278bd23b50252", size = 96991, upload-time = "2025-12-28T15:58:09.809Z" }, + { url = "https://files.pythonhosted.org/packages/f9/42/d6bf40f1919c28b197d18c02fb4df8ea34de47a6e919a342e6909be391a1/scs-3.2.10-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900c55abd3722eb612c9a37c1d7670c01176d07537d97f6a802090b2438e2546", size = 5072342, upload-time = "2025-12-28T15:58:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/ce870c09e1547c9c5bb398fc3d47ebdb83baffa92e7bbc6c66099f1831b4/scs-3.2.10-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4be7feb1082f0184021a57a1b5524eabba7635b88d8d36275421967bd812b04c", size = 12080175, upload-time = "2025-12-28T15:58:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e2/f611ca84dd3dccc3f998d06749fc3e4b63390286ae72db51286284ee145a/scs-3.2.10-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:08907c2009109ac7084f6321d68fccea73f4428e9c908a83b23a7a11aeb227d5", size = 11974183, upload-time = "2025-12-28T15:58:14.468Z" }, + { url = "https://files.pythonhosted.org/packages/d7/72/5c8132096a4b7b16660d53da02d411719e63db4faf8e107c7affeae8c4dd/scs-3.2.10-cp314-cp314t-win_amd64.whl", hash = "sha256:09053722a783aa6743b303007a55270a7c39b7dd0a3baf107c890164a37995d2", size = 7570169, upload-time = "2025-12-28T15:58:16.346Z" }, ] [[package]] From 4877972028863d41b326b3a23a58b0470176c102 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 08:42:03 +0900 Subject: [PATCH 09/14] 2025-12-31 08:42:03 (Wed) > DW-Mac > derekwan --- src/tests/test_subprocess.py | 28 ++++++++++++++-- src/utilities/subprocess.py | 64 +++++++++++++++++++++++++++++------- uv.lock | 48 +++++++++++++-------------- 3 files changed, 102 insertions(+), 38 deletions(-) diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index 8d7a7565a7..c40d145481 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -10,6 +10,7 @@ from utilities.grp import EFFECTIVE_GROUP_NAME from utilities.iterables import one +from utilities.logging import basic_config from utilities.pathlib import get_file_group, get_file_owner from utilities.permissions import Permissions from utilities.pwd import EFFECTIVE_USER_NAME @@ -20,6 +21,9 @@ ChownCmdError, CpError, MvFileError, + RsyncCmdError, + RsyncCmdNoSourcesError, + RsyncCmdSourcesNotFoundError, apt_install_cmd, cat_cmd, cd_cmd, @@ -558,9 +562,27 @@ def test_parent(self) -> None: ] assert result == expected + def test_error_no_sources(self) -> None: + with raises( + RsyncCmdNoSourcesError, + match=r"No sources selected to send to user@hostname:dest", + ): + _ = rsync_cmd([], "user", "hostname", "dest") + + def test_error_sources_not_found(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + with raises( + RsyncCmdSourcesNotFoundError, + match=r"No sources selected to send to user@hostname:dest", + ): + _ = rsync_cmd(src, "user", "hostname", "dest") + + # TODO: add test preservation of trailing slash + class TestRsyncMany: @mark.only + @throttle(delta=5 * MINUTE) def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: with ( TemporaryDirectory() as src, @@ -571,13 +593,15 @@ def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: src2.touch() dest1, dest2 = [temp / src.name for src in [src1, src2]] rsync_many(ssh_user, ssh_hostname, (src1, dest1), (src2, dest2)) + breakpoint() + ssh( ssh_user, ssh_hostname, *BASH_LS, input=strip_and_dedent(f""" - if ! [ -d {dest1} ]; then exit 1; fi - if ! [ -f {dest1}/file.txt ]; then exit 1; fi + if ! [ -f {dest1} ]; then exit 1; fi + if ! [ -f {dest2} ]; then exit 1; fi """), ) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 2eacbe29d4..8badbb07ef 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -1,5 +1,6 @@ from __future__ import annotations +import builtins import shutil import sys from contextlib import contextmanager @@ -362,13 +363,13 @@ def rsync( retry=retry, logger=logger, ) - is_dir = any(Path(s).is_dir() for s in always_iterable(src_or_srcs)) # skipif-ci + srcs = list(always_iterable(src_or_srcs)) # skipif-ci rsync_args = rsync_cmd( # skipif-ci - src_or_srcs, + srcs, user, hostname, dest, - archive=is_dir, + archive=any(Path(s).is_dir() for s in srcs), chown_user=chown_user, chown_group=chown_group, exclude=exclude, @@ -376,7 +377,6 @@ def rsync( host_key_algorithms=host_key_algorithms, strict_host_key_checking=strict_host_key_checking, sudo=sudo, - parent=is_dir, ) run(*rsync_args, print=print, retry=retry, logger=logger) # skipif-ci if chmod is not None: # skipif-ci @@ -409,7 +409,6 @@ def rsync_cmd( host_key_algorithms: list[str] = _HOST_KEY_ALGORITHMS, strict_host_key_checking: bool = True, sudo: bool = False, - parent: bool = False, ) -> list[str]: """Command to use 'rsync' to do remote & local file copying.""" args: list[str] = ["rsync"] @@ -439,12 +438,43 @@ def rsync_cmd( args.extend(["--rsh", join(rsh_args)]) if sudo: args.extend(["--rsync-path", join(sudo_cmd("rsync"))]) - dest_use = maybe_parent(dest, parent=parent) - return [ - *args, - *map(str, always_iterable(src_or_srcs)), - f"{user}@{hostname}:{dest_use}", - ] + srcs = list(always_iterable(src_or_srcs)) # do not Path() + if len(srcs) == 0: + raise RsyncCmdNoSourcesError(user=user, hostname=hostname, dest=dest) + missing = [s for s in srcs if not (p := Path(s)).exists()] + if len(missing) >= 1: + raise RsyncCmdSourcesNotFoundError( + sources=missing, user=user, hostname=hostname, dest=dest + ) + return [*args, *map(str, srcs), f"{user}@{hostname}:{dest}"] + + +@dataclass(kw_only=True, slots=True) +class RsyncCmdError(Exception): + user: str + hostname: str + dest: PathLike + + @override + def __str__(self) -> str: + return f"No sources selected to send to {self.user}@{self.hostname}:{self.dest}" + + +@dataclass(kw_only=True, slots=True) +class RsyncCmdNoSourcesError(RsyncCmdError): + @override + def __str__(self) -> str: + return f"No sources selected to send to {self.user}@{self.hostname}:{self.dest}" + + +@dataclass(kw_only=True, slots=True) +class RsyncCmdSourcesNotFoundError(RsyncCmdError): + sources: list[PathLike] + + @override + def __str__(self) -> str: + desc = ", ".join(map(repr, map(str, self.sources))) + return f"The following sources were not found to send to {self.user}@{self.hostname}:{self.dest}: {desc}" ## @@ -491,7 +521,7 @@ def rsync_many( case never: assert_never(never) rsync( - temp_src, + f"{temp_src}/", user, hostname, temp_dest, @@ -502,6 +532,9 @@ def rsync_many( retry=retry, logger=logger, ) + from time import sleep + + sleep(1) ssh( user, hostname, @@ -511,6 +544,10 @@ def rsync_many( retry=retry, logger=logger, ) + joined = "\n".join(map(join, cmds)) + if False: + builtins.print(f"Just ran {joined}") + breakpoint() def _rsync_many_prepare( @@ -1119,6 +1156,9 @@ def yield_ssh_temp_dir( "ChownCmdError", "CpError", "MvFileError", + "RsyncCmdError", + "RsyncCmdNoSourcesError", + "RsyncCmdSourcesNotFoundError", "apt_install_cmd", "cd_cmd", "chmod", diff --git a/uv.lock b/uv.lock index b17f5ec4c8..87f8606f03 100644 --- a/uv.lock +++ b/uv.lock @@ -2515,30 +2515,30 @@ sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd0389 [[package]] name = "psutil" -version = "7.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/8e/b35aae6ed19bc4e2286cac4832e4d522fcf00571867b0a85a3f77ef96a80/psutil-7.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c31e927555539132a00380c971816ea43d089bf4bd5f3e918ed8c16776d68474", size = 129593, upload-time = "2025-12-23T20:26:28.019Z" }, - { url = "https://files.pythonhosted.org/packages/61/a2/773d17d74e122bbffe08b97f73f2d4a01ef53fb03b98e61b8e4f64a9c6b9/psutil-7.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:db8e44e766cef86dea47d9a1fa535d38dc76449e5878a92f33683b7dba5bfcb2", size = 130104, upload-time = "2025-12-23T20:26:30.27Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e3/d3a9b3f4bd231abbd70a988beb2e3edd15306051bccbfc4472bd34a56e01/psutil-7.2.0-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85ef849ac92169dedc59a7ac2fb565f47b3468fbe1524bf748746bc21afb94c7", size = 180579, upload-time = "2025-12-23T20:26:32.628Z" }, - { url = "https://files.pythonhosted.org/packages/66/f8/6c73044424aabe1b7824d4d4504029d406648286d8fe7ba8c4682e0d3042/psutil-7.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26782bdbae2f5c14ce9ebe8ad2411dc2ca870495e0cd90f8910ede7fa5e27117", size = 183171, upload-time = "2025-12-23T20:26:34.972Z" }, - { url = "https://files.pythonhosted.org/packages/48/7d/76d7a863340885d41826562225a566683e653ee6c9ba03c9f3856afa7d80/psutil-7.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b7665f612d3b38a583391b95969667a53aaf6c5706dc27a602c9a4874fbf09e4", size = 139055, upload-time = "2025-12-23T20:26:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/a0/48/200054ada0ae4872c8a71db54f3eb6a9af4101680ee6830d373b7fda526b/psutil-7.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4413373c174520ae28a24a8974ad8ce6b21f060d27dde94e25f8c73a7effe57a", size = 134737, upload-time = "2025-12-23T20:26:38.784Z" }, - { url = "https://files.pythonhosted.org/packages/44/86/98da45dff471b93ef5ce5bcaefa00e3038295a7880a77cf74018243d37fb/psutil-7.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2f2f53fd114e7946dfba3afb98c9b7c7f376009447360ca15bfb73f2066f84c7", size = 129692, upload-time = "2025-12-23T20:26:40.623Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/10eae91ba4ad071c92db3c178ba861f30406342de9f0ddbe6d51fd741236/psutil-7.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e65c41d7e60068f60ce43b31a3a7fc90deb0dfd34ffc824a2574c2e5279b377e", size = 130110, upload-time = "2025-12-23T20:26:42.569Z" }, - { url = "https://files.pythonhosted.org/packages/87/3a/2b2897443d56fedbbc34ac68a0dc7d55faa05d555372a2f989109052f86d/psutil-7.2.0-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc66d21366850a4261412ce994ae9976bba9852dafb4f2fa60db68ed17ff5281", size = 181487, upload-time = "2025-12-23T20:26:44.633Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/44308428f7333db42c5ea7390c52af1b38f59b80b80c437291f58b5dfdad/psutil-7.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e025d67b42b8f22b096d5d20f5171de0e0fefb2f0ce983a13c5a1b5ed9872706", size = 184320, upload-time = "2025-12-23T20:26:46.83Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/d2feadc7f18e501c5ce687c377db7dca924585418fd694272b8e488ea99f/psutil-7.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:45f6b91f7ad63414d6454fd609e5e3556d0e1038d5d9c75a1368513bdf763f57", size = 140372, upload-time = "2025-12-23T20:26:49.334Z" }, - { url = "https://files.pythonhosted.org/packages/b2/1d/48381f5fd0425aa054c4ee3de24f50de3d6c347019f3aec75f357377d447/psutil-7.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87b18a19574139d60a546e88b5f5b9cbad598e26cdc790d204ab95d7024f03ee", size = 135400, upload-time = "2025-12-23T20:26:51.585Z" }, - { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116, upload-time = "2025-12-23T20:26:53.516Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925, upload-time = "2025-12-23T20:26:55.573Z" }, - { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666, upload-time = "2025-12-23T20:26:57.312Z" }, - { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109, upload-time = "2025-12-23T20:26:58.851Z" }, - { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081, upload-time = "2025-12-23T20:27:01.318Z" }, - { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376, upload-time = "2025-12-23T20:27:03.347Z" }, - { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" }, - { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" }, +version = "7.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/8e/f0c242053a368c2aa89584ecd1b054a18683f13d6e5a318fc9ec36582c94/psutil-7.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9f33bb525b14c3ea563b2fd521a84d2fa214ec59e3e6a2858f78d0844dd60d", size = 129624, upload-time = "2025-12-29T08:26:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/a58a4968f8990617decee234258a2b4fc7cd9e35668387646c1963e69f26/psutil-7.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81442dac7abfc2f4f4385ea9e12ddf5a796721c0f6133260687fec5c3780fa49", size = 130132, upload-time = "2025-12-29T08:26:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/db/6d/ed44901e830739af5f72a85fa7ec5ff1edea7f81bfbf4875e409007149bd/psutil-7.2.1-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea46c0d060491051d39f0d2cff4f98d5c72b288289f57a21556cc7d504db37fc", size = 180612, upload-time = "2025-12-29T08:26:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/c7/65/b628f8459bca4efbfae50d4bf3feaab803de9a160b9d5f3bd9295a33f0c2/psutil-7.2.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35630d5af80d5d0d49cfc4d64c1c13838baf6717a13effb35869a5919b854cdf", size = 183201, upload-time = "2025-12-29T08:26:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/fb/23/851cadc9764edcc18f0effe7d0bf69f727d4cf2442deb4a9f78d4e4f30f2/psutil-7.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:923f8653416604e356073e6e0bccbe7c09990acef442def2f5640dd0faa9689f", size = 139081, upload-time = "2025-12-29T08:26:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/59/82/d63e8494ec5758029f31c6cb06d7d161175d8281e91d011a4a441c8a43b5/psutil-7.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cfbe6b40ca48019a51827f20d830887b3107a74a79b01ceb8cc8de4ccb17b672", size = 134767, upload-time = "2025-12-29T08:26:14.528Z" }, + { url = "https://files.pythonhosted.org/packages/05/c2/5fb764bd61e40e1fe756a44bd4c21827228394c17414ade348e28f83cd79/psutil-7.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:494c513ccc53225ae23eec7fe6e1482f1b8a44674241b54561f755a898650679", size = 129716, upload-time = "2025-12-29T08:26:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d2/935039c20e06f615d9ca6ca0ab756cf8408a19d298ffaa08666bc18dc805/psutil-7.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fce5f92c22b00cdefd1645aa58ab4877a01679e901555067b1bd77039aa589f", size = 130133, upload-time = "2025-12-29T08:26:18.009Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/19f1eb0e01d24c2b3eacbc2f78d3b5add8a89bf0bb69465bc8d563cc33de/psutil-7.2.1-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93f3f7b0bb07711b49626e7940d6fe52aa9940ad86e8f7e74842e73189712129", size = 181518, upload-time = "2025-12-29T08:26:20.241Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6d/7e18b1b4fa13ad370787626c95887b027656ad4829c156bb6569d02f3262/psutil-7.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d34d2ca888208eea2b5c68186841336a7f5e0b990edec929be909353a202768a", size = 184348, upload-time = "2025-12-29T08:26:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/98/60/1672114392dd879586d60dd97896325df47d9a130ac7401318005aab28ec/psutil-7.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2ceae842a78d1603753561132d5ad1b2f8a7979cb0c283f5b52fb4e6e14b1a79", size = 140400, upload-time = "2025-12-29T08:26:23.993Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7b/d0e9d4513c46e46897b46bcfc410d51fc65735837ea57a25170f298326e6/psutil-7.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:08a2f175e48a898c8eb8eace45ce01777f4785bc744c90aa2cc7f2fa5462a266", size = 135430, upload-time = "2025-12-29T08:26:25.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, ] [[package]] From 457d1e29072ab4b788b9907b3686bbafa1404a6e Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 08:50:04 +0900 Subject: [PATCH 10/14] 2025-12-31 08:50:04 (Wed) > DW-Mac > derekwan --- src/utilities/subprocess.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 8badbb07ef..2e70c3237b 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -532,9 +532,6 @@ def rsync_many( retry=retry, logger=logger, ) - from time import sleep - - sleep(1) ssh( user, hostname, @@ -544,10 +541,6 @@ def rsync_many( retry=retry, logger=logger, ) - joined = "\n".join(map(join, cmds)) - if False: - builtins.print(f"Just ran {joined}") - breakpoint() def _rsync_many_prepare( @@ -563,15 +556,14 @@ def _rsync_many_prepare( dest, temp_src, temp_dest = map(Path, [dest, temp_src, temp_dest]) n = len(list(temp_src.iterdir())) name = str(n) - dest = temp_src / name match src: case Path(): - cp(src, dest) + cp(src, temp_src / name) case str(): if Path(src).exists(): - cp(src, dest) + cp(src, temp_src / name) else: - tee(dest, src) + tee(temp_src / name, src) case never: assert_never(never) cmds: list[list[str]] = [ From 61b25551b9cc132722f83d6821975bcdd8fde099 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 09:04:23 +0900 Subject: [PATCH 11/14] 2025-12-31 09:04:23 (Wed) > DW-Mac > derekwan --- src/tests/test_subprocess.py | 178 +++++++++++++++++++++++------------ src/utilities/subprocess.py | 5 +- 2 files changed, 118 insertions(+), 65 deletions(-) diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index c40d145481..a2cfefab91 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -10,7 +10,6 @@ from utilities.grp import EFFECTIVE_GROUP_NAME from utilities.iterables import one -from utilities.logging import basic_config from utilities.pathlib import get_file_group, get_file_owner from utilities.permissions import Permissions from utilities.pwd import EFFECTIVE_USER_NAME @@ -21,7 +20,6 @@ ChownCmdError, CpError, MvFileError, - RsyncCmdError, RsyncCmdNoSourcesError, RsyncCmdSourcesNotFoundError, apt_install_cmd, @@ -412,50 +410,74 @@ def test_dir(self, *, ssh_user: str, ssh_hostname: str) -> None: class TestRsyncCmd: - def test_main(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest") - expected = [ + def test_main(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() + result = rsync_cmd(src, "user", "hostname", "dest") + expected: list[str] = [ "rsync", "--checksum", "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_multiple_sources(self) -> None: - result = rsync_cmd(["src1", "src2"], "user", "hostname", "dest") - expected = [ + def test_multiple_sources(self, *, tmp_path: Path) -> None: + src1, src2 = [tmp_path / f"file{i}.txt" for i in [1, 2]] + src1.touch() + src2.touch() + result = rsync_cmd([src1, src2], "user", "hostname", "dest") + expected: list[str] = [ "rsync", "--checksum", "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src1", - "src2", + str(src1), + str(src2), "user@hostname:dest", ] assert result == expected - def test_archive(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest", archive=True) - expected = [ + def test_source_with_trailing_slash(self, *, tmp_path: Path) -> None: + src = tmp_path / "src" + src.mkdir() + result = rsync_cmd(f"{src}/", "user", "hostname", "dest") + expected: list[str] = [ + "rsync", + "--checksum", + "--compress", + "--rsh", + "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", + f"{src}/", + "user@hostname:dest", + ] + assert result == expected + + def test_archive(self, *, tmp_path: Path) -> None: + src = tmp_path / "src" + src.mkdir() + result = rsync_cmd(src, "user", "hostname", "dest", archive=True) + expected: list[str] = [ "rsync", "--archive", "--checksum", "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_chown_user(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest", chown_user="user2") - expected = [ + def test_chown_user(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() + result = rsync_cmd(src, "user", "hostname", "dest", chown_user="user2") + expected: list[str] = [ "rsync", "--checksum", "--chown", @@ -463,14 +485,16 @@ def test_chown_user(self) -> None: "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_chown_group(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest", chown_group="group") - expected = [ + def test_chown_group(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() + result = rsync_cmd(src, "user", "hostname", "dest", chown_group="group") + expected: list[str] = [ "rsync", "--checksum", "--chown", @@ -478,16 +502,18 @@ def test_chown_group(self) -> None: "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_chown_user_and_group(self) -> None: + def test_chown_user_and_group(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() result = rsync_cmd( - "src", "user", "hostname", "dest", chown_user="user2", chown_group="group" + src, "user", "hostname", "dest", chown_user="user2", chown_group="group" ) - expected = [ + expected: list[str] = [ "rsync", "--checksum", "--chown", @@ -495,14 +521,16 @@ def test_chown_user_and_group(self) -> None: "--compress", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_exclude(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest", exclude="exclude") - expected = [ + def test_exclude(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() + result = rsync_cmd(src, "user", "hostname", "dest", exclude="exclude") + expected: list[str] = [ "rsync", "--checksum", "--compress", @@ -510,16 +538,18 @@ def test_exclude(self) -> None: "exclude", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_exclude_multiple(self) -> None: + def test_exclude_multiple(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() result = rsync_cmd( - "src", "user", "hostname", "dest", exclude=["exclude1", "exclude2"] + src, "user", "hostname", "dest", exclude=["exclude1", "exclude2"] ) - expected = [ + expected: list[str] = [ "rsync", "--checksum", "--compress", @@ -529,14 +559,16 @@ def test_exclude_multiple(self) -> None: "exclude2", "--rsh", "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_sudo(self) -> None: - result = rsync_cmd("src", "user", "hostname", "dest", sudo=True) - expected = [ + def test_sudo(self, *, tmp_path: Path) -> None: + src = tmp_path / "file.txt" + src.touch() + result = rsync_cmd(src, "user", "hostname", "dest", sudo=True) + expected: list[str] = [ "rsync", "--checksum", "--compress", @@ -544,24 +576,11 @@ def test_sudo(self) -> None: "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", "--rsync-path", "sudo rsync", - "src", + str(src), "user@hostname:dest", ] assert result == expected - def test_parent(self) -> None: - result = rsync_cmd("src", "user", "hostname", "~/dest", parent=True) - expected = [ - "rsync", - "--checksum", - "--compress", - "--rsh", - "ssh -o BatchMode=yes -o HostKeyAlgorithms=ssh-ed25519 -o StrictHostKeyChecking=yes -T", - "src", - "user@hostname:~", - ] - assert result == expected - def test_error_no_sources(self) -> None: with raises( RsyncCmdNoSourcesError, @@ -573,28 +592,40 @@ def test_error_sources_not_found(self, *, tmp_path: Path) -> None: src = tmp_path / "file.txt" with raises( RsyncCmdSourcesNotFoundError, - match=r"No sources selected to send to user@hostname:dest", + match=r"Sources selected to send to user@hostname:dest but not found: '.*/file\.txt'", ): _ = rsync_cmd(src, "user", "hostname", "dest") - # TODO: add test preservation of trailing slash - class TestRsyncMany: - @mark.only + @throttle(delta=5 * MINUTE) + def test_single_file(self, *, ssh_user: str, ssh_hostname: str) -> None: + with ( + TemporaryDirectory() as temp_src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, + ): + src = temp_src / "file.txt" + src.touch() + dest = temp_dest / src.name + rsync_many(ssh_user, ssh_hostname, (src, dest)) + ssh( + ssh_user, + ssh_hostname, + *BASH_LS, + input=f"if ! [ -f {dest} ]; then exit 1; fi", + ) + @throttle(delta=5 * MINUTE) def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: with ( - TemporaryDirectory() as src, - yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp, + TemporaryDirectory() as temp_src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, ): - src1, src2 = [src / f"file{i}.txt" for i in [1, 2]] + src1, src2 = [temp_src / f"file{i}.txt" for i in [1, 2]] src1.touch() src2.touch() - dest1, dest2 = [temp / src.name for src in [src1, src2]] + dest1, dest2 = [temp_dest / src.name for src in [src1, src2]] rsync_many(ssh_user, ssh_hostname, (src1, dest1), (src2, dest2)) - breakpoint() - ssh( ssh_user, ssh_hostname, @@ -605,6 +636,29 @@ def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: """), ) + @throttle(delta=5 * MINUTE) + def test_single_directory(self, *, ssh_user: str, ssh_hostname: str) -> None: + with ( + TemporaryDirectory() as temp_src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, + ): + src = temp_src / "dir" + src.mkdir() + (src / "file.txt").touch() + dest = temp_dest / src.name + rsync_many(ssh_user, ssh_hostname, (src, dest)) + ssh( + ssh_user, + ssh_hostname, + *BASH_LS, + input=( + f""" + if ! [ -d {dest} ]; then exit 1; fi + if ! [ -f {dest}/file.txt ]; then exit 1; fi + """ + ), + ) + class TestRun: def test_main(self, *, capsys: CaptureFixture) -> None: diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 2e70c3237b..aca5244d03 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -1,6 +1,5 @@ from __future__ import annotations -import builtins import shutil import sys from contextlib import contextmanager @@ -441,7 +440,7 @@ def rsync_cmd( srcs = list(always_iterable(src_or_srcs)) # do not Path() if len(srcs) == 0: raise RsyncCmdNoSourcesError(user=user, hostname=hostname, dest=dest) - missing = [s for s in srcs if not (p := Path(s)).exists()] + missing = [s for s in srcs if not (_p := Path(s)).exists()] if len(missing) >= 1: raise RsyncCmdSourcesNotFoundError( sources=missing, user=user, hostname=hostname, dest=dest @@ -474,7 +473,7 @@ class RsyncCmdSourcesNotFoundError(RsyncCmdError): @override def __str__(self) -> str: desc = ", ".join(map(repr, map(str, self.sources))) - return f"The following sources were not found to send to {self.user}@{self.hostname}:{self.dest}: {desc}" + return f"Sources selected to send to {self.user}@{self.hostname}:{self.dest} but not found: {desc}" ## From cd94cc293bc535733e9df7b084b8874734cd7a17 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 09:13:10 +0900 Subject: [PATCH 12/14] 2025-12-31 09:13:10 (Wed) > DW-Mac > derekwan --- src/tests/test_subprocess.py | 39 ++++++++++++++++++++++++++++++------ src/utilities/subprocess.py | 1 + 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index a2cfefab91..d7003da57a 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -377,9 +377,12 @@ def test_main(self) -> None: class TestRsync: @skipif_ci @throttle(delta=5 * MINUTE) - def test_main(self, *, ssh_user: str, ssh_hostname: str) -> None: - with TemporaryFile() as src, yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp: - dest = temp / src.name + def test_file(self, *, ssh_user: str, ssh_hostname: str) -> None: + with ( + TemporaryFile() as src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, + ): + dest = temp_dest / src.name rsync(src, ssh_user, ssh_hostname, dest) ssh( ssh_user, @@ -390,14 +393,38 @@ def test_main(self, *, ssh_user: str, ssh_hostname: str) -> None: @skipif_ci @throttle(delta=5 * MINUTE) - def test_dir(self, *, ssh_user: str, ssh_hostname: str) -> None: + def test_dir_without_trailing_slash( + self, *, ssh_user: str, ssh_hostname: str + ) -> None: with ( TemporaryDirectory() as src, - yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, ): (src / "file.txt").touch() - dest = temp / src.name + name = src.name + dest = temp_dest / name rsync(src, ssh_user, ssh_hostname, dest) + ssh( + ssh_user, + ssh_hostname, + *BASH_LS, + input=strip_and_dedent(f""" + if ! [ -d {dest} ]; then exit 1; fi + if ! [ -d {dest}/{name} ]; then exit 1; fi + if ! [ -f {dest}/{name}/file.txt ]; then exit 1; fi + """), + ) + + @skipif_ci + @throttle(delta=5 * MINUTE) + def test_dir_with_trailing_slash(self, *, ssh_user: str, ssh_hostname: str) -> None: + with ( + TemporaryDirectory() as src, + yield_ssh_temp_dir(ssh_user, ssh_hostname) as temp_dest, + ): + (src / "file.txt").touch() + dest = temp_dest / src.name + rsync(f"{src}/", ssh_user, ssh_hostname, dest) ssh( ssh_user, ssh_hostname, diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index aca5244d03..22ae8bf159 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -363,6 +363,7 @@ def rsync( logger=logger, ) srcs = list(always_iterable(src_or_srcs)) # skipif-ci + any(Path(s).is_dir() for s in srcs) rsync_args = rsync_cmd( # skipif-ci srcs, user, From fb9ff71c48c1cbbd82dd4ffa37e0543687ca6d49 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 09:14:34 +0900 Subject: [PATCH 13/14] 2025-12-31 09:14:34 (Wed) > DW-Mac > derekwan --- src/utilities/subprocess.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utilities/subprocess.py b/src/utilities/subprocess.py index 22ae8bf159..b2864aff71 100644 --- a/src/utilities/subprocess.py +++ b/src/utilities/subprocess.py @@ -363,7 +363,6 @@ def rsync( logger=logger, ) srcs = list(always_iterable(src_or_srcs)) # skipif-ci - any(Path(s).is_dir() for s in srcs) rsync_args = rsync_cmd( # skipif-ci srcs, user, @@ -441,7 +440,7 @@ def rsync_cmd( srcs = list(always_iterable(src_or_srcs)) # do not Path() if len(srcs) == 0: raise RsyncCmdNoSourcesError(user=user, hostname=hostname, dest=dest) - missing = [s for s in srcs if not (_p := Path(s)).exists()] + missing = [s for s in srcs if not Path(s).exists()] if len(missing) >= 1: raise RsyncCmdSourcesNotFoundError( sources=missing, user=user, hostname=hostname, dest=dest From c3b614adaa0ab4d11d497ff7ba688ab6380d4d92 Mon Sep 17 00:00:00 2001 From: Derek Wan Date: Wed, 31 Dec 2025 15:31:22 +0900 Subject: [PATCH 14/14] 2025-12-31 15:31:22 (Wed) > DW-Mac > derekwan --- src/tests/test_subprocess.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/test_subprocess.py b/src/tests/test_subprocess.py index d7003da57a..1b52890194 100644 --- a/src/tests/test_subprocess.py +++ b/src/tests/test_subprocess.py @@ -625,6 +625,7 @@ def test_error_sources_not_found(self, *, tmp_path: Path) -> None: class TestRsyncMany: + @skipif_ci @throttle(delta=5 * MINUTE) def test_single_file(self, *, ssh_user: str, ssh_hostname: str) -> None: with ( @@ -642,6 +643,7 @@ def test_single_file(self, *, ssh_user: str, ssh_hostname: str) -> None: input=f"if ! [ -f {dest} ]; then exit 1; fi", ) + @skipif_ci @throttle(delta=5 * MINUTE) def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: with ( @@ -663,6 +665,7 @@ def test_multiple_files(self, *, ssh_user: str, ssh_hostname: str) -> None: """), ) + @skipif_ci @throttle(delta=5 * MINUTE) def test_single_directory(self, *, ssh_user: str, ssh_hostname: str) -> None: with (