From c0459b92bc4a42b08281e69b8802d24c5d3415d4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2022 17:34:18 -0300 Subject: [PATCH 001/275] Fix CI for pytest 7.2+ This adds an explicit dependency to `py`, and also fixes some low hanging fruits replacing `py.test` by `pytest`. --- testing/conftest.py | 18 ++++++++---------- testing/test_channel.py | 3 +-- testing/test_gateway.py | 24 ++++++++++++------------ testing/test_multi.py | 4 ++-- testing/test_rsync.py | 4 ++-- testing/test_serializer.py | 4 ++-- testing/test_termination.py | 2 +- testing/test_threadpool.py | 4 ++-- tox.ini | 1 + 9 files changed, 31 insertions(+), 33 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index fd8a5ea5..b3a6f0e7 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -24,7 +24,7 @@ def pytest_runtest_setup(item): getspecssh(item.config) # will skip if no gx given yield if "pypy" in item.keywords and not item.config.option.pypy: - py.test.skip("pypy tests skipped, use --pypy to run them.") + pytest.skip("pypy tests skipped, use --pypy to run them.") @pytest.fixture @@ -74,28 +74,26 @@ def specsocket(request): return getsocketspec(request.config) -def getgspecs(config=None): - if config is None: - config = py.test.config +def getgspecs(config): return map(execnet.XSpec, config.getvalueorskip("gspecs")) -def getspecssh(config=None): +def getspecssh(config): xspecs = getgspecs(config) for spec in xspecs: if spec.ssh: if not py.path.local.sysfind("ssh"): - py.test.skip("command not found: ssh") + pytest.skip("command not found: ssh") return spec - py.test.skip("need '--gx ssh=...'") + pytest.skip("need '--gx ssh=...'") -def getsocketspec(config=None): +def getsocketspec(config): xspecs = getgspecs(config) for spec in xspecs: if spec.socket: return spec - py.test.skip("need '--gx socket=...'") + pytest.skip("need '--gx socket=...'") def pytest_generate_tests(metafunc): @@ -149,7 +147,7 @@ def anypython(request): if executable.check(): return executable executable = None - py.test.skip("no {} found".format(name)) + pytest.skip("no {} found".format(name)) if "execmodel" in request.fixturenames and name != "sys.executable": backend = request.getfixturevalue("execmodel").backend if backend != "thread": diff --git a/testing/test_channel.py b/testing/test_channel.py index 017abb03..c08c51e7 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -4,13 +4,12 @@ """ import time -import py import pytest from test_gateway import _find_version + needs_early_gc = pytest.mark.skipif("not hasattr(sys, 'getrefcount')") needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") -queue = py.builtin._tryimport("queue", "Queue") TESTTIMEOUT = 10.0 # seconds diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 402cb360..13ab9f60 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -16,7 +16,7 @@ from test_serializer import _find_version TESTTIMEOUT = 10.0 # seconds -needs_osdup = py.test.mark.skipif("not hasattr(os, 'dup')") +needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") flakytest = pytest.mark.xfail( @@ -36,10 +36,10 @@ def test_deprecation(recwarn, monkeypatch): execnet.PopenGateway().exit() assert recwarn.pop(DeprecationWarning) monkeypatch.setattr(socket, "socket", fails) - py.test.raises(Exception, execnet.SocketGateway, "localhost", 8811) + pytest.raises(Exception, execnet.SocketGateway, "localhost", 8811) assert recwarn.pop(DeprecationWarning) monkeypatch.setattr(subprocess, "Popen", fails) - py.test.raises(Exception, execnet.SshGateway, "not-existing") + pytest.raises(Exception, execnet.SshGateway, "not-existing") assert recwarn.pop(DeprecationWarning) @@ -213,7 +213,7 @@ def test_remote_exec_waitclose_noarg(self, gw): def test_remote_exec_error_after_close(self, gw): channel = gw.remote_exec("pass") channel.waitclose(TESTTIMEOUT) - py.test.raises(IOError, channel.send, 0) + pytest.raises(IOError, channel.send, 0) def test_remote_exec_no_explicit_close(self, gw): channel = gw.remote_exec("channel.close()") @@ -353,7 +353,7 @@ def test_receive_on_remote_sysexit(self, gw): raise SystemExit() """ ) - py.test.raises(channel.RemoteError, channel.receive) + pytest.raises(channel.RemoteError, channel.receive) def test_dont_write_bytecode(self, makegateway): check_sys_dont_write_bytecode = """ @@ -371,9 +371,9 @@ def test_dont_write_bytecode(self, makegateway): assert ret -@py.test.mark.skipif("config.option.broken_isp") +@pytest.mark.skipif("config.option.broken_isp") def test_socket_gw_host_not_found(gw, makegateway): - py.test.raises(execnet.HostNotFound, lambda: makegateway("socket=qwepoipqwe:9000")) + pytest.raises(execnet.HostNotFound, lambda: makegateway("socket=qwepoipqwe:9000")) class TestSshPopenGateway: @@ -384,7 +384,7 @@ def test_sshconfig_config_parsing(self, monkeypatch, makegateway): monkeypatch.setattr( gateway_io, "Popen2IOMaster", lambda *args, **kwargs: l.append(args[0]) ) - py.test.raises(AttributeError, lambda: makegateway("ssh=xyz//ssh_config=qwe")) + pytest.raises(AttributeError, lambda: makegateway("ssh=xyz//ssh_config=qwe")) assert len(l) == 1 popen_args = l[0] @@ -395,7 +395,7 @@ def test_sshaddress(self, gw, specssh): assert gw.remoteaddress == specssh.ssh def test_host_not_found(self, gw, makegateway): - py.test.raises( + pytest.raises( execnet.HostNotFound, lambda: makegateway("ssh=nowhere.codespeak.net") ) @@ -477,7 +477,7 @@ def test_popen_filetracing(self, testdir, monkeypatch, makegateway): if worker_line in line: break else: - py.test.fail("did not find {!r} in tracefile".format(worker_line)) + pytest.fail("did not find {!r} in tracefile".format(worker_line)) gw.exit() @skip_win_pypy @@ -498,7 +498,7 @@ def test_no_tracing_by_default(self): class TestStringCoerce: - @py.test.mark.skipif('sys.version>="3.0"') + @pytest.mark.skipif('sys.version>="3.0"') def test_2to3(self, makegateway): python = _find_version("3") gw = makegateway("popen//python=%s" % python) @@ -515,7 +515,7 @@ def test_2to3(self, makegateway): assert isinstance(res, str) gw.exit() - @py.test.mark.skipif('sys.version<"3.0"') + @pytest.mark.skipif('sys.version<"3.0"') def test_3to2(self, makegateway): python = _find_version("2") gw = makegateway("popen//python=%s" % python) diff --git a/testing/test_multi.py b/testing/test_multi.py index 007ec7bc..c7470b07 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -141,7 +141,7 @@ def join(self): def test_group_default_spec(self): group = Group() group.defaultspec = "not-existing-type" - py.test.raises(ValueError, group.makegateway) + pytest.raises(ValueError, group.makegateway) def test_group_PopenGateway(self): group = Group() @@ -176,7 +176,7 @@ def test_group_id_allocation(self): assert gw.id == "hello" gw = group.makegateway(specs[0]) assert gw.id == "gw0" - # py.test.raises(ValueError, + # pytest.raises(ValueError, # group.allocate_id, XSpec("popen//id=hello")) group.terminate() diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 995f229e..e0ecb862 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -123,7 +123,7 @@ def test_rsync_non_verbose(self, capsys, dirs, gw1): assert not out assert not err - @py.test.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + @pytest.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") def test_permissions(self, dirs, gw1, gw2): source = dirs.source dest = dirs.dest1 @@ -230,7 +230,7 @@ def filter(self, x): assert len(dest.listdir()) == 1 assert len(source.listdir()) == 1 - @py.test.mark.skipif("sys.version_info >= (3,)") + @pytest.mark.skipif("sys.version_info >= (3,)") def test_2_to_3_bridge_can_send_binary_files(self, tmpdir, makegateway): python = _find_version("3") gw = makegateway("popen//python=%s" % python) diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 083843b7..d8934c28 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -26,7 +26,7 @@ def _find_version(suffix=""): return path else: - py.test.skip("can't find a {!r} executable".format(name)) + pytest.skip("can't find a {!r} executable".format(name)) return executable @@ -147,7 +147,7 @@ def load(request): ] -@py.test.mark.parametrize(["tp_name", "repr"], simple_tests) +@pytest.mark.parametrize(["tp_name", "repr"], simple_tests) def test_simple(tp_name, repr, dump, load): if ( sys.platform.startswith("win") diff --git a/testing/test_termination.py b/testing/test_termination.py index 1137515b..fb5c7e93 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -57,7 +57,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): @skip_win_pypy def test_termination_on_remote_channel_receive(monkeypatch, makegateway): if not py.path.local.sysfind("ps"): - py.test.skip("need 'ps' command to externally check process status") + pytest.skip("need 'ps' command to externally check process status") monkeypatch.setenv("EXECNET_DEBUG", "2") gw = makegateway("popen") pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive() diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index d4694368..c12af669 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -126,7 +126,7 @@ def f(): raise ValueError("42") reply = pool.spawn(f) - with py.test.raises(ValueError): + with pytest.raises(ValueError): reply.get(1.0) with pytest.raises(ValueError): reply.get(1.0) @@ -145,7 +145,7 @@ def f(): assert pool.waitall(timeout=0.1) -@py.test.mark.skipif("not hasattr(os, 'dup')") +@pytest.mark.skipif("not hasattr(os, 'dup')") def test_pool_clean_shutdown(pool): capture = py.io.StdCaptureFD() q = pool.execmodel.queue.Queue() diff --git a/tox.ini b/tox.ini index 2fdede2c..b37215d3 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist=py27,py37,docs,linting [testenv] deps= setuptools_scm + py pytest pytest-timeout passenv = GITHUB_ACTIONS From d2ae1039311d4b6e654783e8a364e4d7ec7c1cc5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Nov 2022 17:41:58 -0300 Subject: [PATCH 002/275] Drop deprecated rsyncdirs setting from tox.ini Fix #158 --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index b37215d3..5a73b19a 100644 --- a/tox.ini +++ b/tox.ini @@ -30,5 +30,4 @@ commands = pre-commit run --all-files --show-diff-on-failure [pytest] timeout = 20 addopts = -ra -rsyncdirs = execnet testing testpaths = testing From 92d72265e14a85efe0c9b431f9ec6b51c3a557c6 Mon Sep 17 00:00:00 2001 From: Marcel Telka Date: Tue, 22 Nov 2022 15:28:19 +0100 Subject: [PATCH 003/275] Call pytest through python for tox-current-env support (#159) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 5a73b19a..37ab2746 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ deps= pytest-timeout passenv = GITHUB_ACTIONS commands= - pytest {posargs:testing} + python -m pytest {posargs:testing} [testenv:docs] skipsdist = True From 92d27720a4b60af3a73436e6e32f0f56090dc911 Mon Sep 17 00:00:00 2001 From: Apteryks Date: Sun, 11 Dec 2022 11:49:30 +0000 Subject: [PATCH 004/275] rsync_remote: Fix a problem when receiving read-only directories. (#145) Before this change, when the source directories hierarchy was read-only, the read-only mode would be preserved at the destination, preventing child directories to be recreated by a normal user (a permission denied error, EACCES would be raised). * execnet/rsync_remote.py (serve_rsync.receive_directory_structure): Bitwise OR to ensure the write bit is set on received directories. * testing/test_rsync.py (TestRSync) : New test. --- execnet/rsync_remote.py | 8 ++++++-- testing/test_rsync.py | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/execnet/rsync_remote.py b/execnet/rsync_remote.py index cd5e7654..55d154c1 100644 --- a/execnet/rsync_remote.py +++ b/execnet/rsync_remote.py @@ -35,7 +35,11 @@ def receive_directory_structure(path, relcomponents): os.makedirs(path) mode = msg.pop(0) if mode: - os.chmod(path, mode) + # Ensure directories are writable, otherwise a + # permission denied error (EACCES) would be raised + # when attempting to receive read-only directory + # structures. + os.chmod(path, mode | 0o700) entrynames = {} for entryname in msg: destpath = os.path.join(path, entryname) @@ -59,7 +63,7 @@ def receive_directory_structure(path, relcomponents): checksum = md5(f.read()).digest() f.close() elif msg_mode and msg_mode != st.st_mode: - os.chmod(path, msg_mode) + os.chmod(path, msg_mode | 0o700) return else: return # already fine diff --git a/testing/test_rsync.py b/testing/test_rsync.py index e0ecb862..79ad5c43 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -157,6 +157,23 @@ def test_permissions(self, dirs, gw1, gw2): mode = destdir.stat().mode assert mode & 511 == 504 + @py.test.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + def test_read_only_directories(self, dirs, gw1): + source = dirs.source + dest = dirs.dest1 + source.ensure("sub", "subsub", dir=True) + source.join("sub").chmod(0o500) + source.join("sub", "subsub").chmod(0o500) + + # The destination directories should be created with the write + # permission forced, to avoid raising an EACCES error. + rsync = RSync(source) + rsync.add_target(gw1, dest) + rsync.send() + + assert dest.join("sub").stat().mode & 0o700 + assert dest.join("sub").join("subsub").stat().mode & 0o700 + @needssymlink def test_symlink_rsync(self, dirs, gw1): source = dirs.source From 9e14b7dd6060f3694ae8351f8efbe704caff8242 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 23 Dec 2022 20:32:53 +0100 Subject: [PATCH 005/275] port to hatch --- doc/support.rst | 12 +++------- pyproject.toml | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 2 -- setup.py | 45 ------------------------------------- tox.ini | 2 +- 5 files changed, 64 insertions(+), 57 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/doc/support.rst b/doc/support.rst index c296588b..ae238462 100644 --- a/doc/support.rst +++ b/doc/support.rst @@ -7,16 +7,10 @@ are welcome to: * join `execnet-dev`_ for general discussions * join `execnet-commit`_ to be notified of changes * clone the `github repository`_ and submit patches -* hang out on the #pytest channel `on irc.libera.chat -`_ (using an IRC client, `via webchat -`_, or `via Matrix -`_). -* follow the `tetamap blog`_ or `Holger's twitter presence`_. -* contact merlinux_ if you want to buy teaching or other support. +* hang out on the #pytest channel on `irc.libera.chat `_ + (using an IRC client, via `webchat `_, + or `via Matrix `_). -.. _`Holger's twitter presence`: http://twitter.com/hpk42 -.. _merlinux: http://merlinux.eu -.. _`tetamap blog`: http://holgerkrekel.net .. _`execnet-dev`: http://mail.python.org/mailman/listinfo/execnet-dev .. _`execnet-commit`: http://mail.python.org/mailman/listinfo/execnet-commit .. _`github repository`: https://github.com/pytest-dev/execnet diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..b2054c7e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = [ + "hatchling", + "hatch-vcs", +] +build-backend = "hatchling.build" + +[project] +name = "execnet" +dynamic = ["version"] +description = "execnet: rapid multi-Python deployment" +long_description_file = "README.rst" +license = "MIT" +requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +authors = [ + { name = "holger krekel and others" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Distributed Computing", + "Topic :: System :: Networking", +] + +[project.optional-dependencies] +testing = [ + "pre-commit", + "pytest", + "tox", + "hatch", +] + +[project.urls] +Homepage = "https://execnet.readthedocs.io/en/latest/" + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.hooks.vcs] +version-file = "execnet/_version.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/execnet", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index a8e23cd1..00000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[wheel] -universal=true diff --git a/setup.py b/setup.py deleted file mode 100644 index e06133d3..00000000 --- a/setup.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -def main(): - from setuptools import setup - - with open("README.rst") as fp: - readme = fp.read() - setup( - name="execnet", - description="execnet: rapid multi-Python deployment", - long_description=readme, - use_scm_version={"write_to": "execnet/_version.py"}, - url="https://execnet.readthedocs.io/en/latest/", - license="MIT", - platforms=["unix", "linux", "osx", "cygwin", "win32"], - author="holger krekel and others", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: POSIX", - "Operating System :: Microsoft :: Windows", - "Operating System :: MacOS :: MacOS X", - "Topic :: Software Development :: Libraries", - "Topic :: System :: Distributed Computing", - "Topic :: System :: Networking", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", - ], - packages=["execnet", "execnet.script"], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", - extras_require={"testing": ["pre-commit"]}, - setup_requires=["setuptools_scm"], - ) - - -if __name__ == "__main__": - main() diff --git a/tox.ini b/tox.ini index 37ab2746..1221666f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist=py27,py37,docs,linting - +isolated_build = true [testenv] deps= setuptools_scm From 260fd0a3e0170754ba38605597de891caaaa157f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 23 Dec 2022 21:06:46 +0100 Subject: [PATCH 006/275] drop old python, test on newer --- .github/workflows/main.yml | 83 ++------------------------------------ pyproject.toml | 9 ++--- tox.ini | 2 +- 3 files changed, 8 insertions(+), 86 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 801f010a..16776e53 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -10,83 +10,8 @@ jobs: strategy: fail-fast: false matrix: - name: [ - "windows-py27", - "windows-py35", - "windows-py36", - "windows-py37", - "windows-py38", - "windows-py39", - - "linux-py27", - "linux-py35", - "linux-py36", - "linux-py37", - "linux-py38", - "linux-py39", - - "linux-pypy2", - "linux-pypy3", - ] - include: - - name: "windows-py27" - python: "2.7" - tox_env: "py27" - os: "windows-latest" - - name: "windows-py35" - python: "3.5" - tox_env: "py35" - os: "windows-latest" - - name: "windows-py36" - python: "3.6" - tox_env: "py36" - os: "windows-latest" - - name: "windows-py37" - python: "3.7" - tox_env: "py37" - os: "windows-latest" - - name: "windows-py38" - python: "3.8" - tox_env: "py38" - os: "windows-latest" - - name: "windows-py39" - python: "3.9" - tox_env: "py39" - os: "windows-latest" - - - name: "linux-py27" - python: "2.7" - tox_env: "py27" - os: "ubuntu-latest" - - name: "linux-py35" - python: "3.5" - tox_env: "py35" - os: "ubuntu-latest" - - name: "linux-py36" - python: "3.6" - tox_env: "py36" - os: "ubuntu-latest" - - name: "linux-py37" - python: "3.7" - tox_env: "py37" - os: "ubuntu-latest" - - name: "linux-py38" - python: "3.8" - tox_env: "py38" - os: "ubuntu-latest" - - name: "linux-py39" - python: "3.9" - tox_env: "py39" - os: "ubuntu-latest" - - - name: "linux-pypy2" - python: "pypy-2.7" - tox_env: "pypy2" - os: "ubuntu-latest" - - name: "linux-pypy3" - python: "pypy-3.7" - tox_env: "pypy3" - os: "ubuntu-latest" + os: [windows-latest, ubuntu-latest] + python: ["3.7","3.8","3.10","3.11", "pypy-3.7"] steps: - uses: actions/checkout@v1 @@ -97,10 +22,10 @@ jobs: - name: Install tox setuptools_scm run: | python -m pip install --upgrade pip - pip install tox setuptools_scm + pip install tox setuptools_scm hatch - name: Test run: | - tox -e ${{ matrix.tox_env }} + tox -e py deploy: diff --git a/pyproject.toml b/pyproject.toml index b2054c7e..78819678 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dynamic = ["version"] description = "execnet: rapid multi-Python deployment" long_description_file = "README.rst" license = "MIT" -requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +requires-python = ">=3.7" authors = [ { name = "holger krekel and others" }, ] @@ -22,14 +22,11 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries", diff --git a/tox.ini b/tox.ini index 1221666f..84a35670 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py37,docs,linting +envlist=py{37,38,39,310,311},docs,linting isolated_build = true [testenv] deps= From 1a11a8543dec76d268190f8edfbd373a30c121f4 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 23 Dec 2022 21:34:35 +0100 Subject: [PATCH 007/275] changelog --- CHANGELOG.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 95909e2e..aa6caca7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +2.0.0 (unreleased) +------------------ + +* removed support for Python < 3.7 +* migrate packaging to hatch + 1.9.0 (2021-06-13) ------------------ From 16e6f51f0da9080f4f0105aae300a3f2c938c58f Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 27 Dec 2022 13:00:01 +0100 Subject: [PATCH 008/275] simplify pipelines --- .github/workflows/main.yml | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 16776e53..f0b69e01 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: build on: [push, pull_request] jobs: - build: + tests: runs-on: ${{ matrix.os }} @@ -19,13 +19,10 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - - name: Install tox setuptools_scm - run: | - python -m pip install --upgrade pip - pip install tox setuptools_scm hatch + - name: Install tox + run: pip install tox - name: Test - run: | - tox -e py + run: tox -e py deploy: @@ -33,21 +30,18 @@ jobs: runs-on: ubuntu-latest - needs: build + needs: tests steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2 with: python-version: "3.7" - - name: Install wheel - run: | - python -m pip install --upgrade pip - pip install wheel + - name: Install hatch + run: pip install hatch - name: Build package - run: | - python setup.py sdist bdist_wheel + run: hatch build - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@master with: From 54770ebe8c50ac5fdcea4622d8e5ae06a244b682 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 28 Dec 2022 18:54:31 +0100 Subject: [PATCH 009/275] chore: drop coding lines --- .pre-commit-config.yaml | 1 - doc/__init__.py | 1 - doc/conf.py | 1 - doc/example/conftest.py | 1 - doc/example/funcmultiplier.py | 1 - doc/example/popen_read_multiple.py | 1 - doc/example/py3topy2.py | 1 - doc/example/redirect_remote_output.py | 1 - doc/example/remote1.py | 1 - doc/example/remotecmd.py | 1 - doc/example/servefiles.py | 1 - doc/example/svn-sync-repo.py | 1 - doc/example/sysinfo.py | 1 - doc/example/taskserver.py | 1 - doc/example/test_funcmultiplier.py | 1 - execnet/__init__.py | 1 - execnet/deprecated.py | 1 - execnet/gateway.py | 1 - execnet/gateway_base.py | 1 - execnet/gateway_bootstrap.py | 1 - execnet/gateway_io.py | 1 - execnet/gateway_socket.py | 1 - execnet/multi.py | 1 - execnet/rsync.py | 1 - execnet/rsync_remote.py | 1 - execnet/script/__init__.py | 1 - execnet/script/loop_socketserver.py | 1 - execnet/script/quitserver.py | 1 - execnet/script/shell.py | 1 - execnet/script/socketserver.py | 1 - execnet/script/socketserverservice.py | 1 - execnet/script/xx.py | 1 - execnet/xspec.py | 1 - testing/conftest.py | 1 - testing/test_basics.py | 1 - testing/test_channel.py | 1 - testing/test_compatibility_regressions.py | 1 - testing/test_gateway.py | 1 - testing/test_multi.py | 1 - testing/test_rsync.py | 1 - testing/test_serializer.py | 1 - testing/test_termination.py | 1 - testing/test_threadpool.py | 1 - testing/test_xspec.py | 1 - 44 files changed, 44 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1341641a..d8e571b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,6 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer - - id: fix-encoding-pragma - id: check-yaml - repo: https://github.com/asottile/reorder_python_imports rev: v3.1.0 diff --git a/doc/__init__.py b/doc/__init__.py index ec51c5a2..792d6005 100644 --- a/doc/__init__.py +++ b/doc/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- # diff --git a/doc/conf.py b/doc/conf.py index 7c9df522..c12a8c44 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # execnet documentation build configuration file, created by # sphinx-quickstart on Wed Sep 30 21:16:59 2009. diff --git a/doc/example/conftest.py b/doc/example/conftest.py index b81c67ca..5ea2370e 100644 --- a/doc/example/conftest.py +++ b/doc/example/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys import py diff --git a/doc/example/funcmultiplier.py b/doc/example/funcmultiplier.py index 47160162..d2dab757 100644 --- a/doc/example/funcmultiplier.py +++ b/doc/example/funcmultiplier.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import execnet diff --git a/doc/example/popen_read_multiple.py b/doc/example/popen_read_multiple.py index 4eb80d0a..a7e2ad5d 100644 --- a/doc/example/popen_read_multiple.py +++ b/doc/example/popen_read_multiple.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ example diff --git a/doc/example/py3topy2.py b/doc/example/py3topy2.py index c13bcfcd..3dcf0437 100644 --- a/doc/example/py3topy2.py +++ b/doc/example/py3topy2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import execnet gw = execnet.makegateway("popen//python=python2") diff --git a/doc/example/redirect_remote_output.py b/doc/example/redirect_remote_output.py index 5a509238..a6a2aa2f 100644 --- a/doc/example/redirect_remote_output.py +++ b/doc/example/redirect_remote_output.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ redirect output from remote to a local function showcasing features of the channel object: diff --git a/doc/example/remote1.py b/doc/example/remote1.py index f35f2a91..6036952f 100644 --- a/doc/example/remote1.py +++ b/doc/example/remote1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # content of a module remote1.py if __name__ == "__channelexec__": diff --git a/doc/example/remotecmd.py b/doc/example/remotecmd.py index 5d3fd995..d38947df 100644 --- a/doc/example/remotecmd.py +++ b/doc/example/remotecmd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os # contents of: remotecmd.py diff --git a/doc/example/servefiles.py b/doc/example/servefiles.py index 20e6bee1..f943de8e 100644 --- a/doc/example/servefiles.py +++ b/doc/example/servefiles.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # content of servefiles.py diff --git a/doc/example/svn-sync-repo.py b/doc/example/svn-sync-repo.py index 3e37d5c0..687d0114 100644 --- a/doc/example/svn-sync-repo.py +++ b/doc/example/svn-sync-repo.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ small utility for hot-syncing a svn repository through ssh. diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index 52d3fd22..9074e238 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ sysinfo.py [host1] [host2] [options] diff --git a/doc/example/taskserver.py b/doc/example/taskserver.py index 63e306a7..760e1f40 100644 --- a/doc/example/taskserver.py +++ b/doc/example/taskserver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import execnet group = execnet.Group() diff --git a/doc/example/test_funcmultiplier.py b/doc/example/test_funcmultiplier.py index b65b7e43..6b26fb7c 100644 --- a/doc/example/test_funcmultiplier.py +++ b/doc/example/test_funcmultiplier.py @@ -1,3 +1,2 @@ -# -*- coding: utf-8 -*- def test_function(): import funcmultiplier diff --git a/execnet/__init__.py b/execnet/__init__.py index f73c5531..fd6fbaee 100644 --- a/execnet/__init__.py +++ b/execnet/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ execnet ------- diff --git a/execnet/deprecated.py b/execnet/deprecated.py index 892c7e1a..1460bd88 100644 --- a/execnet/deprecated.py +++ b/execnet/deprecated.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ some deprecated calls diff --git a/execnet/gateway.py b/execnet/gateway.py index 85a5916d..ec74e059 100644 --- a/execnet/gateway.py +++ b/execnet/gateway.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ gateway code for initiating popen, socket and ssh connections. (c) 2004-2013, Holger Krekel and others diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 99cc3f60..32d552f2 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ base execnet gateway code send to the other side for bootstrapping. diff --git a/execnet/gateway_bootstrap.py b/execnet/gateway_bootstrap.py index 8df23045..50a1e373 100644 --- a/execnet/gateway_bootstrap.py +++ b/execnet/gateway_bootstrap.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ code to initialize the remote side of a gateway once the io is created """ diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index 8a17fd5b..de042a5a 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ execnet io initialization code diff --git a/execnet/gateway_socket.py b/execnet/gateway_socket.py index 259fd309..52625a95 100644 --- a/execnet/gateway_socket.py +++ b/execnet/gateway_socket.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys from execnet.gateway_bootstrap import HostNotFound diff --git a/execnet/multi.py b/execnet/multi.py index fe00e4b0..54048cf8 100644 --- a/execnet/multi.py +++ b/execnet/multi.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Managing Gateway Groups and interactions with multiple channels. diff --git a/execnet/rsync.py b/execnet/rsync.py index 230c8762..5c4d484b 100644 --- a/execnet/rsync.py +++ b/execnet/rsync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ 1:N rsync implemenation on top of execnet. diff --git a/execnet/rsync_remote.py b/execnet/rsync_remote.py index 55d154c1..6badf233 100644 --- a/execnet/rsync_remote.py +++ b/execnet/rsync_remote.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ (c) 2006-2013, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ diff --git a/execnet/script/__init__.py b/execnet/script/__init__.py index ec51c5a2..792d6005 100644 --- a/execnet/script/__init__.py +++ b/execnet/script/__init__.py @@ -1,2 +1 @@ -# -*- coding: utf-8 -*- # diff --git a/execnet/script/loop_socketserver.py b/execnet/script/loop_socketserver.py index 898b8bdc..a4688a80 100644 --- a/execnet/script/loop_socketserver.py +++ b/execnet/script/loop_socketserver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import subprocess import sys diff --git a/execnet/script/quitserver.py b/execnet/script/quitserver.py index daf5a107..a8db8ed7 100644 --- a/execnet/script/quitserver.py +++ b/execnet/script/quitserver.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ send a "quit" signal to a remote server diff --git a/execnet/script/shell.py b/execnet/script/shell.py index 705fea52..4ee01304 100755 --- a/execnet/script/shell.py +++ b/execnet/script/shell.py @@ -1,5 +1,4 @@ #! /usr/bin/env python -# -*- coding: utf-8 -*- """ a remote python shell diff --git a/execnet/script/socketserver.py b/execnet/script/socketserver.py index d39fd5f4..03641f89 100755 --- a/execnet/script/socketserver.py +++ b/execnet/script/socketserver.py @@ -1,5 +1,4 @@ #! /usr/bin/env python -# -*- coding: utf-8 -*- """ start socket based minimal readline exec server diff --git a/execnet/script/socketserverservice.py b/execnet/script/socketserverservice.py index 1f1ff35a..c85f00fb 100644 --- a/execnet/script/socketserverservice.py +++ b/execnet/script/socketserverservice.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ A windows service wrapper for the py.execnet socketserver. diff --git a/execnet/script/xx.py b/execnet/script/xx.py index e0fe758c..fd514c13 100644 --- a/execnet/script/xx.py +++ b/execnet/script/xx.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import sys import register diff --git a/execnet/xspec.py b/execnet/xspec.py index 50a99c68..7b101ef1 100644 --- a/execnet/xspec.py +++ b/execnet/xspec.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ (c) 2008-2013, holger krekel """ diff --git a/testing/conftest.py b/testing/conftest.py index b3a6f0e7..f88fd226 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import subprocess import sys diff --git a/testing/test_basics.py b/testing/test_basics.py index e5d288b2..c3048ee1 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import with_statement import inspect diff --git a/testing/test_channel.py b/testing/test_channel.py index c08c51e7..47608cef 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ mostly functional tests of gateways. """ diff --git a/testing/test_compatibility_regressions.py b/testing/test_compatibility_regressions.py index 925a53d5..6ad9f9d9 100644 --- a/testing/test_compatibility_regressions.py +++ b/testing/test_compatibility_regressions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from execnet import gateway_base diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 13ab9f60..bc49fa9e 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ mostly functional tests of gateways. """ diff --git a/testing/test_multi.py b/testing/test_multi.py index c7470b07..6504f3fb 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ tests for multi channels and gateway Groups """ diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 79ad5c43..5bdf6e0d 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import execnet import py import pytest diff --git a/testing/test_serializer.py b/testing/test_serializer.py index d8934c28..14306ba3 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import subprocess import sys diff --git a/testing/test_termination.py b/testing/test_termination.py index fb5c7e93..f0e484d1 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import subprocess import sys diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index c12af669..11cd2bbc 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import with_statement import os diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 0af63b9a..cab57216 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import subprocess import sys From 43939a0fe5186049a949a86609df08aa21eb9991 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 28 Dec 2022 19:01:27 +0100 Subject: [PATCH 010/275] apply pyupgrade + update pre-commit --- .pre-commit-config.yaml | 16 +++--- CHANGELOG.rst | 2 + doc/example/popen_read_multiple.py | 2 - doc/example/redirect_remote_output.py | 2 - doc/example/svn-sync-repo.py | 6 +-- doc/example/sysinfo.py | 6 +-- doc/example/taskserver.py | 4 +- execnet/deprecated.py | 4 +- execnet/gateway.py | 6 +-- execnet/gateway_base.py | 66 +++++++++++------------ execnet/gateway_bootstrap.py | 6 +-- execnet/gateway_io.py | 14 ++--- execnet/gateway_socket.py | 4 +- execnet/multi.py | 10 ++-- execnet/rsync.py | 12 ++--- execnet/rsync_remote.py | 2 +- execnet/script/shell.py | 2 +- execnet/script/socketserver.py | 12 ++--- execnet/xspec.py | 4 +- testing/conftest.py | 8 +-- testing/test_basics.py | 24 ++++----- testing/test_compatibility_regressions.py | 42 +++++++-------- testing/test_gateway.py | 2 +- testing/test_serializer.py | 20 +++---- testing/test_threadpool.py | 2 - 25 files changed, 131 insertions(+), 147 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8e571b3..05337011 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,26 +1,30 @@ exclude: doc/en/example/py2py3/test_py2.py repos: - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.12.0 hooks: - id: black - args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs rev: v1.12.1 hooks: - id: blacken-docs - additional_dependencies: [black==19.3b0] + additional_dependencies: [black==22.12.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml +- repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [--py37-plus] - repo: https://github.com/asottile/reorder_python_imports - rev: v3.1.0 + rev: v3.9.0 hooks: - id: reorder-python-imports - args: ['--application-directories=execnet'] + args: ['--application-directories=execnet', --py37-plus] - repo: local hooks: - id: rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index aa6caca7..8f44265d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,10 @@ ------------------ * removed support for Python < 3.7 + * apply pyupgrade --py37-plus * migrate packaging to hatch + 1.9.0 (2021-06-13) ------------------ diff --git a/doc/example/popen_read_multiple.py b/doc/example/popen_read_multiple.py index a7e2ad5d..571faf34 100644 --- a/doc/example/popen_read_multiple.py +++ b/doc/example/popen_read_multiple.py @@ -3,8 +3,6 @@ reading results from possibly blocking code running in sub processes. """ -from __future__ import print_function - import execnet NUM_PROCESSES = 5 diff --git a/doc/example/redirect_remote_output.py b/doc/example/redirect_remote_output.py index a6a2aa2f..b2b5bb54 100644 --- a/doc/example/redirect_remote_output.py +++ b/doc/example/redirect_remote_output.py @@ -7,8 +7,6 @@ - setting a callback for receiving channel data """ -from __future__ import print_function - import execnet gw = execnet.makegateway() diff --git a/doc/example/svn-sync-repo.py b/doc/example/svn-sync-repo.py index 687d0114..69c2dcb2 100644 --- a/doc/example/svn-sync-repo.py +++ b/doc/example/svn-sync-repo.py @@ -5,8 +5,6 @@ uses execnet. """ -from __future__ import print_function - import os import sys @@ -23,7 +21,7 @@ def main(args): remote = args[0] localrepo = py.path.local(args[1]) if not localrepo.check(dir=1): - raise SystemExit("localrepo {} does not exist".format(localrepo)) + raise SystemExit(f"localrepo {localrepo} does not exist") if len(args) == 3: configfile = args[2] else: @@ -95,7 +93,7 @@ def svn_load(repo, dumpchannel, maxcount=100): # growing buffers (execnet does not control # RAM usage or receive queue sizes) dumpchannel.send(maxcount) - f = os.popen("svnadmin load -q {}".format(repo), "w") + f = os.popen(f"svnadmin load -q {repo}", "w") count = maxcount for x in dumpchannel: sys.stdout.write(".") diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index 9074e238..99a523b5 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -127,13 +127,13 @@ def error(*args): def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): if ssh_config: - spec = "ssh=-F {} {}".format(ssh_config, sshname) + spec = f"ssh=-F {ssh_config} {sshname}" else: spec += "ssh=%s" % sshname debug("connecting to", repr(spec)) try: gw = execnet.makegateway(spec) - except IOError: + except OSError: error("could not get sshgatway", sshname) else: ri = RemoteInfo(gw) @@ -141,7 +141,7 @@ def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): prefix = sshname.upper() + " " print >> loginfo, prefix, "fqdn:", ri.getfqdn() for attr in ("sys.platform", "sys.version_info"): - loginfo.write("{} {}: ".format(prefix, attr)) + loginfo.write(f"{prefix} {attr}: ") loginfo.flush() value = ri.getmodattr(attr) loginfo.write(str(value)) diff --git a/doc/example/taskserver.py b/doc/example/taskserver.py index 760e1f40..bf1cdc18 100644 --- a/doc/example/taskserver.py +++ b/doc/example/taskserver.py @@ -36,7 +36,7 @@ def process_item(channel): break continue if item != "ready": - print("other side {} returned {!r}".format(channel.gateway.id, item)) + print(f"other side {channel.gateway.id} returned {item!r}") if not tasks: print("no tasks remain, sending termination request to all") mch.send_each(None) @@ -44,6 +44,6 @@ def process_item(channel): if tasks and tasks != -1: task = tasks.pop() channel.send(task) - print("sent task {!r} to {}".format(task, channel.gateway.id)) + print(f"sent task {task!r} to {channel.gateway.id}") group.terminate() diff --git a/execnet/deprecated.py b/execnet/deprecated.py index 1460bd88..729579b9 100644 --- a/execnet/deprecated.py +++ b/execnet/deprecated.py @@ -25,7 +25,7 @@ def SocketGateway(host, port): new_remote() method on existing gateways. """ APIWARN("1.0.0b4", "use makegateway('socket=host:port')") - spec = execnet.XSpec("socket={}:{}".format(host, port)) + spec = execnet.XSpec(f"socket={host}:{port}") return execnet.default_group.makegateway(spec) @@ -44,5 +44,5 @@ def SshGateway(sshaddress, remotepython=None, ssh_config=None): def APIWARN(version, msg, stacklevel=3): import warnings - Warn = DeprecationWarning("(since version {}) {}".format(version, msg)) + Warn = DeprecationWarning(f"(since version {version}) {msg}") warnings.warn(Warn, stacklevel=stacklevel) diff --git a/execnet/gateway.py b/execnet/gateway.py index ec74e059..c0ed0143 100644 --- a/execnet/gateway.py +++ b/execnet/gateway.py @@ -21,7 +21,7 @@ class Gateway(gateway_base.BaseGateway): """Gateway to a local or remote Python Intepreter.""" def __init__(self, io, spec): - super(Gateway, self).__init__(io=io, id=spec.id, _startcount=1) + super().__init__(io=io, id=spec.id, _startcount=1) self.spec = spec self._initreceive() @@ -56,7 +56,7 @@ def exit(self): self._send(Message.GATEWAY_TERMINATE) self._trace("--> io.close_write") self._io.close_write() - except (ValueError, EOFError, IOError): + except (ValueError, EOFError, OSError): v = sys.exc_info()[1] self._trace("io-error: could not send termination sequence") self._trace(" exception: %r" % v) @@ -211,7 +211,7 @@ def _source_of_function(function): try: source = inspect.getsource(function) - except IOError: + except OSError: raise ValueError("can't find source file for %s" % function) source = textwrap.dedent(source) # just for inner functions diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 32d552f2..8337f355 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -11,8 +11,6 @@ - Ronny Pfannschmidt - many others """ -from __future__ import with_statement - import os import struct import sys @@ -127,7 +125,7 @@ def exec_start(self, func, args=()): self._spawn_n(func, *args) else: - raise ValueError("unknown execmodel {!r}".format(backend)) + raise ValueError(f"unknown execmodel {backend!r}") class ExecModel: def __init__(self, name): @@ -185,7 +183,7 @@ def PopenPiped(self, args): return ExecModel(backend) -class Reply(object): +class Reply: """reply instances provide access to the result of a function execution that got dispatched through WorkerPool.spawn() @@ -210,7 +208,7 @@ def get(self, timeout=None): def waitfinish(self, timeout=None): if not self._result_ready.wait(timeout): - raise IOError("timeout waiting for {!r}".format(self.task)) + raise OSError(f"timeout waiting for {self.task!r}") def run(self): func, args, kwargs = self.task @@ -226,7 +224,7 @@ def run(self): self.running = False -class WorkerPool(object): +class WorkerPool: """A WorkerPool allows to spawn function executions to threads, returning a reply object on which you can ask for the result (and get exceptions reraised). @@ -342,7 +340,7 @@ def waitall(self, timeout=None): def trace(*msg): try: line = " ".join(map(str, msg)) - sys.stderr.write("[{}] {}\n".format(pid, line)) + sys.stderr.write(f"[{pid}] {line}\n") sys.stderr.flush() except Exception: pass # nothing we can do, likely interpreter-shutdown @@ -363,7 +361,7 @@ def trace(*msg): except Exception: try: v = sys.exc_info()[1] - sys.stderr.write("[{}] exception during tracing: {!r}\n".format(pid, v)) + sys.stderr.write(f"[{pid}] exception during tracing: {v!r}\n") except Exception: pass # nothing we can do, likely interpreter-shutdown @@ -383,7 +381,7 @@ def __init__(self, outfile, infile, execmodel): try: msvcrt.setmode(infile.fileno(), os.O_BINARY) msvcrt.setmode(outfile.fileno(), os.O_BINARY) - except (AttributeError, IOError): + except (AttributeError, OSError): pass self._read = getattr(infile, "buffer", infile).read self._write = getattr(outfile, "buffer", outfile).write @@ -392,7 +390,7 @@ def __init__(self, outfile, infile, execmodel): def read(self, numbytes): """Read exactly 'numbytes' bytes from the pipe.""" # a file in non-blocking mode may return less bytes, so we loop - buf = bytes() + buf = b"" while numbytes > len(buf): data = self._read(numbytes - len(buf)) if not data: @@ -517,7 +515,7 @@ def geterrortext(excinfo, format_exception=traceback.format_exception, sysex=sys except sysex: raise except: - errortext = "{}: {}".format(excinfo[0].__name__, excinfo[1]) + errortext = f"{excinfo[0].__name__}: {excinfo[1]}" return errortext @@ -532,12 +530,12 @@ def __str__(self): return self.formatted def __repr__(self): - return "{}: {}".format(self.__class__.__name__, self.formatted) + return f"{self.__class__.__name__}: {self.formatted}" def warn(self): if self.formatted != INTERRUPT_TEXT: # XXX do this better - sys.stderr.write("[%s] Warning: unhandled %r\n" % (os.getpid(), self)) + sys.stderr.write(f"[{os.getpid()}] Warning: unhandled {self!r}\n") class TimeoutError(IOError): @@ -547,7 +545,7 @@ class TimeoutError(IOError): NO_ENDMARKER_WANTED = object() -class Channel(object): +class Channel: "Communication channel between two Python Interpreter execution points." RemoteError = RemoteError TimeoutError = TimeoutError @@ -581,7 +579,7 @@ def setcallback(self, callback, endmarker=NO_ENDMARKER_WANTED): _callbacks = self.gateway._channelfactory._callbacks with self.gateway._receivelock: if self._items is None: - raise IOError("{!r} has callback already registered".format(self)) + raise OSError(f"{self!r} has callback already registered") items = self._items self._items = None while 1: @@ -630,7 +628,7 @@ def __del__(self): msgcode = Message.CHANNEL_CLOSE try: self.gateway._send(msgcode, self.id) - except (IOError, ValueError): # ignore problems with sending + except (OSError, ValueError): # ignore problems with sending pass def _getremoteerror(self): @@ -661,7 +659,7 @@ def makefile(self, mode="w", proxyclose=False): return ChannelFileWrite(channel=self, proxyclose=proxyclose) elif mode == "r": return ChannelFileRead(channel=self, proxyclose=proxyclose) - raise ValueError("mode {!r} not availabe".format(mode)) + raise ValueError(f"mode {mode!r} not availabe") def close(self, error=None): """close down this channel with an optional error message. @@ -670,7 +668,7 @@ def close(self, error=None): be done explicitely. """ if self._executing: - raise IOError("cannot explicitly close channel within remote_exec") + raise OSError("cannot explicitly close channel within remote_exec") if self._closed: self.gateway._trace(self, "ignoring redundant call to close()") if not self._closed: @@ -722,7 +720,7 @@ def send(self, item): raised if the write pipe was prematurely closed. """ if self.isclosed(): - raise IOError("cannot send to {!r}".format(self)) + raise OSError(f"cannot send to {self!r}") self.gateway._send(Message.CHANNEL_DATA, self.id, dumps_internal(item)) def receive(self, timeout=None): @@ -736,7 +734,7 @@ def receive(self, timeout=None): """ itemqueue = self._items if itemqueue is None: - raise IOError("cannot receive(), channel has receiver callback") + raise OSError("cannot receive(), channel has receiver callback") try: x = itemqueue.get(timeout=timeout) except self.gateway.execmodel.queue.Empty: @@ -773,7 +771,7 @@ def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): INTERRUPT_TEXT = "keyboard-interrupted" -class ChannelFactory(object): +class ChannelFactory: def __init__(self, gateway, startcount=1): self._channels = weakref.WeakValueDictionary() self._callbacks = {} @@ -787,7 +785,7 @@ def new(self, id=None): """create a new Channel with 'id' (or create new id if None).""" with self._writelock: if self.finished: - raise IOError("connexion already closed: {}".format(self.gateway)) + raise OSError(f"connexion already closed: {self.gateway}") if id is None: id = self.count self.count += 2 @@ -869,7 +867,7 @@ def _finished_receiving(self): self._no_longer_opened(id) -class ChannelFile(object): +class ChannelFile: def __init__(self, channel, proxyclose=True): self.channel = channel self._proxyclose = proxyclose @@ -896,7 +894,7 @@ def flush(self): class ChannelFileRead(ChannelFile): def __init__(self, channel, proxyclose=True): - super(ChannelFileRead, self).__init__(channel, proxyclose) + super().__init__(channel, proxyclose) self._buffer = None def read(self, n): @@ -930,7 +928,7 @@ def readline(self): return line -class BaseGateway(object): +class BaseGateway: exc_info = sys.exc_info _sysex = sysex id = "" @@ -988,16 +986,16 @@ def log(*msg): def _terminate_execution(self): pass - def _send(self, msgcode, channelid=0, data=bytes()): + def _send(self, msgcode, channelid=0, data=b""): message = Message(msgcode, channelid, data) try: message.to_io(self._io) self._trace("sent", message) - except (IOError, ValueError): + except (OSError, ValueError): e = sys.exc_info()[1] self._trace("failed to send", message, e) # ValueError might be because the IO is already closed - raise IOError("cannot send (already closed?)") + raise OSError("cannot send (already closed?)") def _local_schedulexec(self, channel, sourcetask): channel.close("execution disallowed") @@ -1074,7 +1072,7 @@ def executetask(self, item): newkwargs[name] = value kwargs = newkwargs loc = {"channel": channel, "__name__": "__channelexec__"} - self._trace("execution starts[%s]: %s" % (channel.id, repr(source)[:50])) + self._trace(f"execution starts[{channel.id}]: {repr(source)[:50]}") channel._executing = True try: co = compile(source + "\n", file_name or "", "exec") @@ -1093,7 +1091,7 @@ def executetask(self, item): excinfo = self.exc_info() if not isinstance(excinfo[1], EOFError): if not channel.gateway._channelfactory.finished: - self._trace("got exception: {!r}".format(excinfo[1])) + self._trace(f"got exception: {excinfo[1]!r}") errortext = self._geterrortext(excinfo) channel.close(errortext) return @@ -1140,7 +1138,7 @@ class _Stop(Exception): pass -class Unserializer(object): +class Unserializer: num2func = {} # is filled after this class definition py2str_as_py3str = True # True py3str_as_py2str = False # false means py2 will get unicode @@ -1367,7 +1365,7 @@ def dumps_internal(obj): return _Serializer().save(obj) -class _Serializer(object): +class _Serializer: _dispatch = {} def __init__(self, write=None): @@ -1398,7 +1396,7 @@ def _save(self, obj): methodname = "save_" + tp.__name__ meth = getattr(self.__class__, methodname, None) if meth is None: - raise DumpError("can't serialize {}".format(tp)) + raise DumpError(f"can't serialize {tp}") dispatch = self._dispatch[tp] = meth dispatch(self, obj) @@ -1546,5 +1544,5 @@ def init_popen_io(execmodel): def serve(io, id): - trace("creating workergateway on {!r}".format(io)) + trace(f"creating workergateway on {io!r}") WorkerGateway(io=io, id=id, _startcount=2).serve() diff --git a/execnet/gateway_bootstrap.py b/execnet/gateway_bootstrap.py index 50a1e373..a9a68cef 100644 --- a/execnet/gateway_bootstrap.py +++ b/execnet/gateway_bootstrap.py @@ -32,7 +32,7 @@ def bootstrap_import(io, spec): "serve(init_popen_io(execmodel), id='%s-worker')" % spec.id, ) s = io.read(1) - assert s == "1".encode("ascii"), repr(s) + assert s == b"1", repr(s) def bootstrap_exec(io, spec): @@ -46,7 +46,7 @@ def bootstrap_exec(io, spec): "serve(io, id='%s-worker')" % spec.id, ) s = io.read(1) - assert s == "1".encode("ascii") + assert s == b"1" except EOFError: ret = io.wait() if ret == 255: @@ -70,7 +70,7 @@ def bootstrap_socket(io, id): "serve(io, id='%s-worker')" % id, ) s = io.read(1) - assert s == "1".encode("ascii") + assert s == b"1" def sendexec(io, *sources): diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index de042a5a..69d31081 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -36,7 +36,7 @@ def killpopen(popen): popen.kill() else: killpid(popen.pid) - except EnvironmentError: + except OSError: sys.stderr.write("ERROR killing: %s\n" % (sys.exc_info()[1])) sys.stderr.flush() @@ -52,7 +52,7 @@ def killpid(pid): ctypes.windll.kernel32.TerminateProcess(handle, -1) ctypes.windll.kernel32.CloseHandle(handle) else: - raise EnvironmentError("no method to kill {}".format(pid)) + raise OSError(f"no method to kill {pid}") popen_bootstrapline = "import sys;exec(eval(sys.stdin.readline()))" @@ -90,7 +90,7 @@ def ssh_args(spec): args.extend(["-F", str(spec.ssh_config)]) args.extend(spec.ssh.split()) - remotecmd = '{} -c "{}"'.format(remotepython, popen_bootstrapline) + remotecmd = f'{remotepython} -c "{popen_bootstrapline}"' args.append(remotecmd) return args @@ -106,7 +106,7 @@ def vagrant_ssh_args(spec): args = ["vagrant", "ssh", spec.vagrant_ssh, "--", "-C"] if spec.ssh_config is not None: args.extend(["-F", str(spec.ssh_config)]) - remotecmd = '{} -c "{}"'.format(remotepython, popen_bootstrapline) + remotecmd = f'{remotepython} -c "{popen_bootstrapline}"' args.extend([remotecmd]) return args @@ -140,7 +140,7 @@ def create_io(spec, execmodel): RIO_CLOSE_WRITE = 4 -class ProxyIO(object): +class ProxyIO: """A Proxy IO object allows to instantiate a Gateway through another "via" gateway. A master:ProxyIO object provides an IO object effectively connected to the sub @@ -182,7 +182,7 @@ def remoteaddress(self): return self._controll(RIO_REMOTEADDRESS) def __repr__(self): - return "".format(self.iochan.gateway.id) + return f"" class PseudoSpec: @@ -230,7 +230,7 @@ def controll(data): # read bootstrap byte from sub, send it on to master log("reading bootstrap byte from sub", spec.id) initial = sub_io.read(1) - assert initial == "1".encode("ascii"), initial + assert initial == b"1", initial log("forwarding bootstrap byte from sub", spec.id) forward_to_master_file.write(initial) diff --git a/execnet/gateway_socket.py b/execnet/gateway_socket.py index 52625a95..386b9686 100644 --- a/execnet/gateway_socket.py +++ b/execnet/gateway_socket.py @@ -17,12 +17,12 @@ def __init__(self, sock, execmodel): # IPTOS_LOWDELAY sock.setsockopt(socket.SOL_IP, socket.IP_TOS, 0x10) sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) - except (AttributeError, socket.error): + except (AttributeError, OSError): sys.stderr.write("WARNING: cannot set socketoption") def read(self, numbytes): "Read exactly 'bytes' bytes from the socket." - buf = bytes() + buf = b"" while len(buf) < numbytes: t = self.sock.recv(numbytes - len(buf)) if not t: diff --git a/execnet/multi.py b/execnet/multi.py index 54048cf8..838fd546 100644 --- a/execnet/multi.py +++ b/execnet/multi.py @@ -18,7 +18,7 @@ NO_ENDMARKER_WANTED = object() -class Group(object): +class Group: """Gateway Groups.""" defaultspec = "popen" @@ -137,7 +137,7 @@ def makegateway(self, spec=None): io = gateway_socket.create_io(spec, self, execmodel=self.execmodel) gw = gateway_bootstrap.bootstrap(io, spec) else: - raise ValueError("no gateway type found for {!r}".format(spec._spec)) + raise ValueError(f"no gateway type found for {spec._spec!r}") gw.spec = spec self._register(gw) if spec.chdir or spec.nice or spec.env: @@ -168,7 +168,7 @@ def allocate_id(self, spec): id = "gw" + str(self._autoidcounter) self._autoidcounter += 1 if id in self: - raise ValueError("already have gateway with id {!r}".format(id)) + raise ValueError(f"already have gateway with id {id!r}") spec.id = id def _register(self, gateway): @@ -183,7 +183,7 @@ def _unregister(self, gateway): self._gateways_to_join.append(gateway) def _cleanup_atexit(self): - trace("=== atexit cleanup {!r} ===".format(self)) + trace(f"=== atexit cleanup {self!r} ===") self.terminate(timeout=1.0) def terminate(self, timeout=None): @@ -299,7 +299,7 @@ def termkill(termfunc, killfunc): termreply = workerpool.spawn(termfunc) try: termreply.get(timeout=timeout) - except IOError: + except OSError: killfunc() replylist = [] diff --git a/execnet/rsync.py b/execnet/rsync.py index 5c4d484b..2f06f0b0 100644 --- a/execnet/rsync.py +++ b/execnet/rsync.py @@ -15,7 +15,7 @@ import execnet.rsync_remote -class RSync(object): +class RSync: """This class allows to send a directory structure (recursively) to one or multiple remote filesystems. @@ -42,7 +42,7 @@ def _end_of_channel(self, channel): # too early! we must have got an error channel.waitclose() # or else we raise one - raise IOError("connection unexpectedly closed: {} ".format(channel.gateway)) + raise OSError(f"connection unexpectedly closed: {channel.gateway} ") def _process_link(self, channel): for link in self._links: @@ -70,7 +70,7 @@ def _send_item(self, channel, data): try: f = open(modifiedpath, "rb") data = f.read() - except IOError: + except OSError: data = None # provide info to progress callback function @@ -94,7 +94,7 @@ def _send_item(self, channel, data): def _report_send_file(self, gateway, modified_rel_path): if self._verbose: - print("{} <= {}".format(gateway, modified_rel_path)) + print(f"{gateway} <= {modified_rel_path}") def send(self, raises=True): """Sends a sourcedir to all added targets. Flag indicates @@ -103,7 +103,7 @@ def send(self, raises=True): """ if not self._channels: if raises: - raise IOError( + raise OSError( "no targets available, maybe you " "are trying call send() twice?" ) return @@ -200,4 +200,4 @@ def _send_directory_structure(self, path): elif stat.S_ISLNK(st.st_mode): self._send_link_structure(path) else: - raise ValueError("cannot sync {!r}".format(path)) + raise ValueError(f"cannot sync {path!r}") diff --git a/execnet/rsync_remote.py b/execnet/rsync_remote.py index 6badf233..67810613 100644 --- a/execnet/rsync_remote.py +++ b/execnet/rsync_remote.py @@ -81,7 +81,7 @@ def receive_directory_structure(path, relcomponents): channel.send(("ack", path[len(destdir) + 1 :])) if data is not None: if STRICT_CHECK and len(data) != size: - raise IOError("file modified during rsync: {!r}".format(path)) + raise OSError(f"file modified during rsync: {path!r}") f = open(path, "wb") f.write(data) f.close() diff --git a/execnet/script/shell.py b/execnet/script/shell.py index 4ee01304..4719b90a 100755 --- a/execnet/script/shell.py +++ b/execnet/script/shell.py @@ -16,7 +16,7 @@ def clientside(): print("client side starting") host, port = sys.argv[1].split(":") port = int(port) - myself = open(os.path.abspath(sys.argv[0]), "rU").read() + myself = open(os.path.abspath(sys.argv[0])).read() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.sendall(repr(myself) + "\n") diff --git a/execnet/script/socketserver.py b/execnet/script/socketserver.py index 03641f89..6180bcd4 100755 --- a/execnet/script/socketserver.py +++ b/execnet/script/socketserver.py @@ -39,16 +39,10 @@ def print_(*args): print(" ".join(str(arg) for arg in args)) -if sys.version_info > (3, 0): - exec( - """def exec_(source, locs): +exec( + """def exec_(source, locs): exec(source, locs)""" - ) -else: - exec( - """def exec_(source, locs): - exec source in locs""" - ) +) def exec_from_one_connection(serversock): diff --git a/execnet/xspec.py b/execnet/xspec.py index 7b101ef1..4d33ad67 100644 --- a/execnet/xspec.py +++ b/execnet/xspec.py @@ -29,7 +29,7 @@ def __init__(self, string): if key[0] == "_": raise AttributeError("%r not a valid XSpec key" % key) if key in self.__dict__: - raise ValueError("duplicate key: {!r} in {!r}".format(key, string)) + raise ValueError(f"duplicate key: {key!r} in {string!r}") if key.startswith("env:"): self.env[key[4:]] = value else: @@ -41,7 +41,7 @@ def __getattr__(self, name): return None def __repr__(self): - return "".format(self._spec) + return f"" def __str__(self): return self._spec diff --git a/testing/conftest.py b/testing/conftest.py index f88fd226..c183425f 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -146,11 +146,11 @@ def anypython(request): if executable.check(): return executable executable = None - pytest.skip("no {} found".format(name)) + pytest.skip(f"no {name} found") if "execmodel" in request.fixturenames and name != "sys.executable": backend = request.getfixturevalue("execmodel").backend if backend != "thread": - pytest.xfail("cannot run {!r} execmodel with bare {}".format(backend, name)) + pytest.xfail(f"cannot run {backend!r} execmodel with bare {name}") return executable @@ -186,7 +186,7 @@ def gw(request, execmodel, group): sshhost = request.getfixturevalue("specssh").ssh # we don't use execmodel.backend here # but you can set it when specifying the ssh spec - gw = group.makegateway("ssh={}//id=ssh".format(sshhost)) + gw = group.makegateway(f"ssh={sshhost}//id=ssh") elif request.param == "proxy": group.makegateway("popen//id=proxy-transport") gw = group.makegateway( @@ -194,7 +194,7 @@ def gw(request, execmodel, group): "//execmodel=%s" % execmodel.backend ) else: - assert 0, "unknown execmodel: {}".format(request.param) + assert 0, f"unknown execmodel: {request.param}" return gw diff --git a/testing/test_basics.py b/testing/test_basics.py index c3048ee1..be567848 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import inspect import os import subprocess @@ -117,7 +115,7 @@ def read_write_loop(): break sys.stdout.write("received: %s" % line) sys.stdout.flush() - except (IOError, EOFError): + except (OSError, EOFError): break @@ -181,15 +179,15 @@ def test_popen_io(anypython, tmpdir, execmodel): args = [str(anypython), str(check)] proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) - proc.stdin.write("x".encode("ascii")) + proc.stdin.write(b"x") stdout, stderr = proc.communicate() print(stderr) proc.wait() - assert "hello".encode("ascii") in stdout + assert b"hello" in stdout def test_popen_io_readloop(monkeypatch, execmodel): - sio = BytesIO("test".encode("ascii")) + sio = BytesIO(b"test") io = Popen2IO(sio, sio, execmodel) real_read = io._read @@ -200,7 +198,7 @@ def newread(numbytes): io._read = newread result = io.read(3) - assert result == "tes".encode("ascii") + assert result == b"tes" def test_rinfo_source(anypython, tmpdir): @@ -254,7 +252,7 @@ class Arg: def test_stdouterrin_setnull(execmodel): cap = py.io.StdCaptureFD() gateway_base.init_popen_io(execmodel) - os.write(1, "hello".encode("ascii")) + os.write(1, b"hello") os.read(0, 1) out, err = cap.reset() assert not out @@ -291,7 +289,7 @@ class TestMessage: def test_wire_protocol(self): for i, handler in enumerate(Message._types): one = py.io.BytesIO() - data = "23".encode("ascii") + data = b"23" Message(i, 42, data).to_io(one) two = py.io.BytesIO(one.getvalue()) msg = Message.from_io(two) @@ -333,7 +331,7 @@ def test_channel_makefile_incompatmode(self, fac): channel.makefile("rw") -class TestSourceOfFunction(object): +class TestSourceOfFunction: def test_lambda_unsupported(self): pytest.raises(ValueError, gateway._source_of_function, lambda: 1) @@ -367,7 +365,7 @@ def working(channel): assert send_source == expected -class TestGlobalFinder(object): +class TestGlobalFinder: def check(self, func): src = py.code.Source(func) code = py.code.Code(func) @@ -415,8 +413,8 @@ def func(channel, data): channel.send(data) gw = makegateway("popen//python=%s" % anypython) - print("local version_info {!r}".format(sys.version_info)) - print("remote info: {}".format(gw._rinfo())) + print(f"local version_info {sys.version_info!r}") + print(f"remote info: {gw._rinfo()}") ch = gw.remote_exec(func, data=1) result = ch.receive() assert result == 1 diff --git a/testing/test_compatibility_regressions.py b/testing/test_compatibility_regressions.py index 6ad9f9d9..bc788e20 100644 --- a/testing/test_compatibility_regressions.py +++ b/testing/test_compatibility_regressions.py @@ -5,28 +5,28 @@ def test_opcodes(): data = vars(gateway_base.opcode) computed = {k: v for k, v in data.items() if "__" not in k} assert computed == { - "BUILDTUPLE": "@".encode("ascii"), - "BYTES": "A".encode("ascii"), - "CHANNEL": "B".encode("ascii"), - "FALSE": "C".encode("ascii"), - "FLOAT": "D".encode("ascii"), - "FROZENSET": "E".encode("ascii"), - "INT": "F".encode("ascii"), - "LONG": "G".encode("ascii"), - "LONGINT": "H".encode("ascii"), - "LONGLONG": "I".encode("ascii"), - "NEWDICT": "J".encode("ascii"), - "NEWLIST": "K".encode("ascii"), - "NONE": "L".encode("ascii"), - "PY2STRING": "M".encode("ascii"), - "PY3STRING": "N".encode("ascii"), - "SET": "O".encode("ascii"), - "SETITEM": "P".encode("ascii"), - "STOP": "Q".encode("ascii"), - "TRUE": "R".encode("ascii"), - "UNICODE": "S".encode("ascii"), + "BUILDTUPLE": b"@", + "BYTES": b"A", + "CHANNEL": b"B", + "FALSE": b"C", + "FLOAT": b"D", + "FROZENSET": b"E", + "INT": b"F", + "LONG": b"G", + "LONGINT": b"H", + "LONGLONG": b"I", + "NEWDICT": b"J", + "NEWLIST": b"K", + "NONE": b"L", + "PY2STRING": b"M", + "PY3STRING": b"N", + "SET": b"O", + "SETITEM": b"P", + "STOP": b"Q", + "TRUE": b"R", + "UNICODE": b"S", # added in 1.4 # causes a regression since it was ordered in # between CHANNEL and FALSE as "C" moving the other items - "COMPLEX": "T".encode("ascii"), + "COMPLEX": b"T", } diff --git a/testing/test_gateway.py b/testing/test_gateway.py index bc49fa9e..9729b802 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -476,7 +476,7 @@ def test_popen_filetracing(self, testdir, monkeypatch, makegateway): if worker_line in line: break else: - pytest.fail("did not find {!r} in tracefile".format(worker_line)) + pytest.fail(f"did not find {worker_line!r} in tracefile") gw.exit() @skip_win_pypy diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 14306ba3..5586b5a6 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -16,16 +16,16 @@ def _find_version(suffix=""): if executable is None: if sys.platform == "win32" and suffix == "3": for name in ("python31", "python30"): - executable = py.path.local(r"c:\\{}\python.exe".format(name)) + executable = py.path.local(rf"c:\\{name}\python.exe") if executable.check(): return executable for tail in MINOR_VERSIONS.get(suffix, ""): - path = py.path.local.sysfind("{}.{}".format(name, tail)) + path = py.path.local.sysfind(f"{name}.{tail}") if path: return path else: - pytest.skip("can't find a {!r} executable".format(name)) + pytest.skip(f"can't find a {name!r} executable") return executable @@ -34,12 +34,8 @@ def _find_version(suffix=""): def setup_module(mod): mod.TEMPDIR = py.path.local(tempfile.mkdtemp()) - if sys.version_info > (3, 0): - mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) - mod._py2_wrapper = PythonWrapper(_find_version("2")) - else: - mod._py3_wrapper = PythonWrapper(_find_version("3")) - mod._py2_wrapper = PythonWrapper(py.path.local(sys.executable)) + mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) + mod._py2_wrapper = PythonWrapper(_find_version("2")) def teardown_module(mod): @@ -50,7 +46,7 @@ def teardown_module(mod): pyimportdir = str(py.path.local(execnet.__file__).dirpath()) -class PythonWrapper(object): +class PythonWrapper: def __init__(self, executable): self.executable = executable @@ -112,7 +108,7 @@ def load(self, data, option_args="__class__"): return [s.decode("ascii") for s in stdout.splitlines()] def __repr__(self): - return "".format(self.executable) + return f"" @pytest.fixture @@ -136,7 +132,7 @@ def load(request): simple_tests = [ - # type: expected before/after repr + # expected before/after repr ("int", "4"), ("float", "3.25"), ("complex", "(1.78+3.25j)"), diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 11cd2bbc..2e537b57 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -1,5 +1,3 @@ -from __future__ import with_statement - import os import sys From e4d606f97e59d1704475652333334c327210dbbe Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 27 Dec 2022 20:26:06 +0100 Subject: [PATCH 011/275] pre-commit and mypy fixes --- .pre-commit-config.yaml | 16 +++-- CHANGELOG.rst | 2 + README.rst | 3 - doc/example/remote1.py | 2 +- doc/example/remotecmd.py | 4 +- doc/example/servefiles.py | 2 +- doc/example/taskserver.py | 10 +-- execnet/gateway.py | 8 +-- execnet/gateway_base.py | 116 ++++++++---------------------- execnet/gateway_io.py | 4 +- execnet/rsync.py | 6 +- execnet/rsync_remote.py | 2 +- execnet/script/quitserver.py | 8 ++- testing/conftest.py | 14 +--- testing/test_basics.py | 22 +++--- testing/test_channel.py | 60 ---------------- testing/test_gateway.py | 37 ---------- testing/test_rsync.py | 13 ---- testing/test_serializer.py | 134 ++++++++--------------------------- 19 files changed, 101 insertions(+), 362 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05337011..0b0c28ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,11 +25,13 @@ repos: hooks: - id: reorder-python-imports args: ['--application-directories=execnet', --py37-plus] -- repo: local +- repo: https://github.com/PyCQA/doc8 + rev: 'v1.1.1' hooks: - - id: rst - name: rst - entry: rst-lint --encoding utf-8 - files: ^(CHANGELOG.rst|HOWTORELEASE.rst|README.rst)$ - language: python - additional_dependencies: [pygments, restructuredtext_lint] + - id: doc8 + args: ["--ignore", "D001"] + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: 'v0.991' + hooks: + - id: mypy diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f44265d..a7080181 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,9 +3,11 @@ * removed support for Python < 3.7 * apply pyupgrade --py37-plus + * minimal mypy fixes and python2 support code drop * migrate packaging to hatch + 1.9.0 (2021-06-13) ------------------ diff --git a/README.rst b/README.rst index 5c9c0573..b529ebe0 100644 --- a/README.rst +++ b/README.rst @@ -4,9 +4,6 @@ execnet: distributed Python deployment and communication Important --------- -**execnet currently is in maintenance-only mode, mostly because it is still the backend -of the pytest-xdist plugin. Do not use in new projects.** - .. image:: https://img.shields.io/pypi/v/execnet.svg :target: https://pypi.org/project/execnet/ diff --git a/doc/example/remote1.py b/doc/example/remote1.py index 6036952f..91c42585 100644 --- a/doc/example/remote1.py +++ b/doc/example/remote1.py @@ -1,4 +1,4 @@ # content of a module remote1.py if __name__ == "__channelexec__": - channel.send("initialization complete") + channel.send("initialization complete") # type: ignore[name-defined] diff --git a/doc/example/remotecmd.py b/doc/example/remotecmd.py index d38947df..47e1c985 100644 --- a/doc/example/remotecmd.py +++ b/doc/example/remotecmd.py @@ -10,5 +10,5 @@ def listdir(path): if __name__ == "__channelexec__": - for item in channel: - channel.send(eval(item)) + for item in channel: # type: ignore[name-defined] + channel.send(eval(item)) # type: ignore[name-defined] diff --git a/doc/example/servefiles.py b/doc/example/servefiles.py index f943de8e..f837493c 100644 --- a/doc/example/servefiles.py +++ b/doc/example/servefiles.py @@ -9,4 +9,4 @@ def servefiles(channel): if __name__ == "__channelexec__": - servefiles(channel) + servefiles(channel) # type: ignore[name-defined] diff --git a/doc/example/taskserver.py b/doc/example/taskserver.py index bf1cdc18..1cf89fb0 100644 --- a/doc/example/taskserver.py +++ b/doc/example/taskserver.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import execnet group = execnet.Group() @@ -24,7 +26,7 @@ def process_item(channel): # get a queue that gives us results q = mch.make_receive_queue(endmarker=-1) -tasks = range(10) # a list of tasks, here just integers +tasks: list[int] | None = list(range(10)) # a list of tasks, here just integers terminated = 0 while 1: channel, item = q.get() @@ -37,11 +39,11 @@ def process_item(channel): continue if item != "ready": print(f"other side {channel.gateway.id} returned {item!r}") - if not tasks: + if not tasks and tasks is not None: print("no tasks remain, sending termination request to all") mch.send_each(None) - tasks = -1 - if tasks and tasks != -1: + tasks = None + if tasks: task = tasks.pop() channel.send(task) print(f"sent task {task!r} to {channel.gateway.id}") diff --git a/execnet/gateway.py b/execnet/gateway.py index c0ed0143..578fb3ad 100644 --- a/execnet/gateway.py +++ b/execnet/gateway.py @@ -199,12 +199,8 @@ def _source_of_function(function): if not args or args[0] != "channel": raise ValueError("expected first function argument to be `channel`") - if gateway_base.ISPY3: - closure = function.__closure__ - codeobj = function.__code__ - else: - closure = function.func_closure - codeobj = function.func_code + closure = function.__closure__ + codeobj = function.__code__ if closure is not None: raise ValueError("functions with closures can't be passed") diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 8337f355..a1af932d 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -11,52 +11,26 @@ - Ronny Pfannschmidt - many others """ +from __future__ import annotations + import os import struct import sys import traceback import weakref +from io import BytesIO +from typing import Callable -# NOTE that we want to avoid try/except style importing -# to avoid setting sys.exc_info() during import -# - -ISPY3 = sys.version_info >= (3, 0) -if ISPY3: - from io import BytesIO - - exec("do_exec = exec") - - def reraise(cls, val, tb): - raise val.with_traceback(tb) - - unicode = str - _long_type = int - from _thread import interrupt_main - SUBPROCESS32 = False -else: - from StringIO import StringIO as BytesIO - - exec( - "def do_exec(co, loc): exec co in loc\n" - "def reraise(cls, val, tb): raise cls, val, tb\n" - ) - bytes = str - _long_type = long - try: - from thread import interrupt_main - except ImportError: - interrupt_main = None - try: - import subprocess32 # NOQA +def reraise(cls, val, tb): + raise val.with_traceback(tb) - SUBPROCESS32 = True - except ImportError: - SUBPROCESS32 = False - sys.exc_clear() +unicode = str +_long_type = int +from _thread import interrupt_main +SUBPROCESS32 = False # f = open("/tmp/execnet-%s" % os.getpid(), "w") # def log_extra(*msg): # f.write(" ".join([str(x) for x in msg]) + "\n") @@ -77,9 +51,9 @@ def get_execmodel(backend): "_thread::start_new_thread", ], "threading": ["threading"], - "queue": ["queue" if ISPY3 else "Queue"], + "queue": ["queue"], "sleep": ["time::sleep"], - "subprocess": ["subprocess32" if SUBPROCESS32 else "subprocess"], + "subprocess": ["subprocess"], "socket": ["socket"], "_fdopen": ["os::fdopen"], "_lock": ["threading"], @@ -414,7 +388,7 @@ def close_write(self): class Message: """encapsulates Messages and their wire protocol.""" - _types = [] + _types: list[Callable[[Message, BaseGateway], None]] = [] def __init__(self, msgcode, channelid=0, data=""): self.msgcode = msgcode @@ -554,6 +528,7 @@ class Channel: def __init__(self, gateway, id): assert isinstance(id, int) + assert not isinstance(gateway, type) self.gateway = gateway # XXX: defaults copied from Unserializer self._strconfig = getattr(gateway, "_strconfig", (True, False)) @@ -1062,21 +1037,12 @@ def trace(msg): def executetask(self, item): try: channel, (source, file_name, call_name, kwargs) = item - if not ISPY3 and kwargs: - # some python2 versions do not accept unicode keyword params - # note: Unserializer generally turns py2-str to py3-str objects - newkwargs = {} - for name, value in kwargs.items(): - if isinstance(name, unicode): - name = name.encode("ascii") - newkwargs[name] = value - kwargs = newkwargs loc = {"channel": channel, "__name__": "__channelexec__"} self._trace(f"execution starts[{channel.id}]: {repr(source)[:50]}") channel._executing = True try: co = compile(source + "\n", file_name or "", "exec") - do_exec(co, loc) # noqa + exec(co, loc) if call_name: self._trace("calling %s(**%60r)" % (call_name, kwargs)) function = loc[call_name] @@ -1116,15 +1082,11 @@ class LoadError(DataFormatError): """Error while unserializing an object.""" -if ISPY3: +def bchr(n): + return bytes([n]) - def bchr(n): - return bytes([n]) -else: - bchr = chr - -DUMPFORMAT_VERSION = bchr(1) +DUMPFORMAT_VERSION = bchr(2) FOUR_BYTE_INT_MAX = 2147483647 @@ -1139,7 +1101,9 @@ class _Stop(Exception): class Unserializer: - num2func = {} # is filled after this class definition + num2func: dict[ + int, Callable[[Unserializer], None] + ] = {} # is filled after this class definition py2str_as_py3str = True # True py3str_as_py2str = False # false means py2 will get unicode @@ -1193,18 +1157,8 @@ def load_longint(self): s = self._read_byte_string() self.stack.append(int(s)) - if ISPY3: - load_long = load_int - load_longlong = load_longint - else: - - def load_long(self): - i = self._read_int4() - self.stack.append(long(i)) - - def load_longlong(self): - l = self._read_byte_string() - self.stack.append(long(l)) + load_long = load_int + load_longlong = load_longint def load_float(self): binary = self.stream.read(FLOAT_FORMAT_SIZE) @@ -1224,7 +1178,7 @@ def _read_byte_string(self): def load_py3string(self): as_bytes = self._read_byte_string() - if not ISPY3 and self.py3str_as_py2str: + if self.py3str_as_py2str: # XXX Should we try to decode into latin-1? self.stack.append(as_bytes) else: @@ -1232,7 +1186,7 @@ def load_py3string(self): def load_py2string(self): as_bytes = self._read_byte_string() - if ISPY3 and self.py2str_as_py3str: + if self.py2str_as_py3str: s = as_bytes.decode("latin-1") else: s = as_bytes @@ -1366,7 +1320,7 @@ def dumps_internal(obj): class _Serializer: - _dispatch = {} + _dispatch = {object: Callable[["_Serializer", object], None]} def __init__(self, write=None): if write is None: @@ -1413,21 +1367,9 @@ def save_bytes(self, bytes_): self._write(opcode.BYTES) self._write_byte_sequence(bytes_) - if ISPY3: - - def save_str(self, s): - self._write(opcode.PY3STRING) - self._write_unicode_string(s) - - else: - - def save_str(self, s): - self._write(opcode.PY2STRING) - self._write_byte_sequence(s) - - def save_unicode(self, s): - self._write(opcode.UNICODE) - self._write_unicode_string(s) + def save_str(self, s): + self._write(opcode.PY3STRING) + self._write_unicode_string(s) def _write_unicode_string(self, s): try: diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index 69d31081..ed7d3056 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -10,7 +10,7 @@ try: from execnet.gateway_base import Popen2IO, Message except ImportError: - from __main__ import Popen2IO, Message + from __main__ import Popen2IO, Message # type: ignore[no-redef] from functools import partial @@ -246,4 +246,4 @@ def controll(data): if __name__ == "__channelexec__": - serve_proxy_io(channel) # noqa + serve_proxy_io(channel) # type: ignore[name-defined] diff --git a/execnet/rsync.py b/execnet/rsync.py index 2f06f0b0..b7183412 100644 --- a/execnet/rsync.py +++ b/execnet/rsync.py @@ -6,11 +6,7 @@ import os import stat from hashlib import md5 - -try: - from queue import Queue -except ImportError: - from Queue import Queue +from queue import Queue import execnet.rsync_remote diff --git a/execnet/rsync_remote.py b/execnet/rsync_remote.py index 67810613..4ac1880f 100644 --- a/execnet/rsync_remote.py +++ b/execnet/rsync_remote.py @@ -114,4 +114,4 @@ def receive_directory_structure(path, relcomponents): if __name__ == "__channelexec__": - serve_rsync(channel) # noqa + serve_rsync(channel) # type: ignore[name-defined] diff --git a/execnet/script/quitserver.py b/execnet/script/quitserver.py index a8db8ed7..9d5cbb15 100644 --- a/execnet/script/quitserver.py +++ b/execnet/script/quitserver.py @@ -3,13 +3,15 @@ send a "quit" signal to a remote server """ +from __future__ import annotations + import socket import sys -hostport = sys.argv[1] -host, port = hostport.split(":") + +host, port = sys.argv[1].split(":") hostport = (host, int(port)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(hostport) -sock.sendall('"raise KeyboardInterrupt"\n') +sock.sendall(b'"raise KeyboardInterrupt"\n') diff --git a/testing/conftest.py b/testing/conftest.py index c183425f..d5e84683 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -11,11 +11,6 @@ rsyncdirs = ["conftest.py", "execnet", "testing", "doc"] -winpymap = { - "python2.7": r"C:\Python27\python.exe", - "python3.4": r"C:\Python34\python.exe", -} - @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(item): @@ -109,7 +104,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize( "anypython", indirect=True, - argvalues=("sys.executable", "python2.7", "pypy", "jython"), + argvalues=("sys.executable", "pypy3"), ) @@ -139,13 +134,6 @@ def anypython(request): name = request.param executable = getexecutable(name) if executable is None: - if sys.platform == "win32": - executable = winpymap.get(name, None) - if executable: - executable = py.path.local(executable) - if executable.check(): - return executable - executable = None pytest.skip(f"no {name} found") if "execmodel" in request.fixturenames and name != "sys.executable": backend = request.getfixturevalue("execmodel").backend diff --git a/testing/test_basics.py b/testing/test_basics.py index be567848..2eac3401 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -2,6 +2,7 @@ import os import subprocess import sys +from io import BytesIO import execnet import py @@ -13,11 +14,6 @@ from execnet.gateway_base import Message from execnet.gateway_base import Popen2IO -try: - from StringIO import StringIO as BytesIO -except: - from io import BytesIO - skip_win_pypy = pytest.mark.xfail( condition=hasattr(sys, "pypy_version_info") and sys.platform.startswith("win"), @@ -165,14 +161,12 @@ def test_popen_io(anypython, tmpdir, execmodel): check.write( py.code.Source( gateway_base, - """ - do_exec("io = init_popen_io(get_execmodel({backend!r}))", globals()) + f""" + io = init_popen_io(get_execmodel({execmodel.backend!r})) io.write("hello".encode('ascii')) s = io.read(1) assert s == "x".encode('ascii') - """.format( - backend=execmodel.backend - ), + """, ) ) from subprocess import Popen, PIPE @@ -304,10 +298,14 @@ class TestPureChannel: @pytest.fixture def fac(self, execmodel): class Gateway: - pass + def _trace(self, *args): + pass + + def _send_(self, *k): + pass Gateway.execmodel = execmodel - return ChannelFactory(Gateway) + return ChannelFactory(Gateway()) def test_factory_create(self, fac): chan1 = fac.new() diff --git a/testing/test_channel.py b/testing/test_channel.py index 47608cef..bda30dc1 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -377,63 +377,3 @@ def test_channel_makefile_incompatmode(self, gw): channel = gw.newchannel() with pytest.raises(ValueError): channel.makefile("rw") - - -class TestStringCoerce: - @pytest.mark.skipif('sys.version>="3.0"') - def test_2to3(self, makegateway): - python = _find_version("3") - gw = makegateway("popen//python=%s" % python) - ch = gw.remote_exec("channel.send(channel.receive());" * 2) - ch.send("a") - res = ch.receive() - assert isinstance(res, unicode) - - ch.reconfigure(py3str_as_py2str=True) - - ch.send("a") - res = ch.receive() - assert isinstance(res, str) - - gw.reconfigure(py3str_as_py2str=True) - ch = gw.remote_exec("channel.send(channel.receive());" * 2) - - ch.send("a") - res = ch.receive() - assert isinstance(res, str) - ch.reconfigure(py3str_as_py2str=False, py2str_as_py3str=False) - - ch.send("a") - res = ch.receive() - assert isinstance(res, str) - gw.exit() - - @pytest.mark.skipif('sys.version<"3.0"') - def test_3to2(self, makegateway): - python = _find_version("2") - gw = makegateway("popen//python=%s" % python) - - ch = gw.remote_exec("channel.send(channel.receive());" * 2) - ch.send(bytes("a", "ascii")) - res = ch.receive() - assert isinstance(res, str) - - ch.reconfigure(py3str_as_py2str=True, py2str_as_py3str=False) - - ch.send("a") - res = ch.receive() - assert isinstance(res, bytes) - - gw.reconfigure(py3str_as_py2str=True, py2str_as_py3str=False) - ch = gw.remote_exec("channel.send(channel.receive());" * 2) - - ch.send("a") - res = ch.receive() - assert isinstance(res, bytes) - - ch.reconfigure(py3str_as_py2str=False, py2str_as_py3str=True) - ch.send(bytes("a", "ascii")) - res = ch.receive() - assert isinstance(res, str) - - gw.exit() diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 9729b802..bb52ba99 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -496,43 +496,6 @@ def test_no_tracing_by_default(self): ), "trace does not to default to empty tracing" -class TestStringCoerce: - @pytest.mark.skipif('sys.version>="3.0"') - def test_2to3(self, makegateway): - python = _find_version("3") - gw = makegateway("popen//python=%s" % python) - ch = gw.remote_exec("channel.send(channel.receive())") - ch.send("a") - res = ch.receive() - assert isinstance(res, unicode) - - gw.reconfigure(py3str_as_py2str=True) - - ch = gw.remote_exec("channel.send(channel.receive())") - ch.send("a") - res = ch.receive() - assert isinstance(res, str) - gw.exit() - - @pytest.mark.skipif('sys.version<"3.0"') - def test_3to2(self, makegateway): - python = _find_version("2") - gw = makegateway("popen//python=%s" % python) - - ch = gw.remote_exec("channel.send(channel.receive())") - ch.send(bytes("a", "ascii")) - res = ch.receive() - assert isinstance(res, str) - - gw.reconfigure(py3str_as_py2str=True, py2str_as_py3str=False) - - ch = gw.remote_exec("channel.send(channel.receive())") - ch.send("a") - res = ch.receive() - assert isinstance(res, bytes) - gw.exit() - - @pytest.mark.parametrize( "spec, expected_args", [ diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 5bdf6e0d..a72e1c72 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -245,16 +245,3 @@ def filter(self, x): assert rsync.x == 1 assert len(dest.listdir()) == 1 assert len(source.listdir()) == 1 - - @pytest.mark.skipif("sys.version_info >= (3,)") - def test_2_to_3_bridge_can_send_binary_files(self, tmpdir, makegateway): - python = _find_version("3") - gw = makegateway("popen//python=%s" % python) - source = tmpdir.ensure("source", dir=1) - for i, content in enumerate("foo bar baz \x10foo"): - source.join(str(i)).write(content) - rsync = RSync(source) - - target = tmpdir.join("target") - rsync.add_target(gw, target) - rsync.send() diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 5586b5a6..bb3a1308 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -35,7 +35,6 @@ def _find_version(suffix=""): def setup_module(mod): mod.TEMPDIR = py.path.local(tempfile.mkdtemp()) mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) - mod._py2_wrapper = PythonWrapper(_find_version("2")) def teardown_module(mod): @@ -111,24 +110,19 @@ def __repr__(self): return f"" -@pytest.fixture -def py2(request): - return _py2_wrapper - - @pytest.fixture def py3(request): return _py3_wrapper -@pytest.fixture(params=["py2", "py3"]) -def dump(request): - return request.getfixturevalue(request.param).dump +@pytest.fixture +def dump(py3): + return py3.dump -@pytest.fixture(params=["py2", "py3"]) -def load(request): - return request.getfixturevalue(request.param).load +@pytest.fixture +def load(py3): + return py3.load simple_tests = [ @@ -144,137 +138,67 @@ def load(request): @pytest.mark.parametrize(["tp_name", "repr"], simple_tests) def test_simple(tp_name, repr, dump, load): - if ( - sys.platform.startswith("win") - and os.environ.get("GITHUB_ACTIONS", "") == "true" - ): - pytest.skip( - "GitHub Actions on Windows doesn't support Python 2 and 3 at the same time." - ) - p = dump(repr) tp, v = load(p) assert tp == tp_name assert v == repr -def test_set(py2, py3, dump): +def test_set(load, dump): p = dump("set((1, 2, 3))") - tp, v = py2.load(p) - assert tp == "set" - # assert v == "set([1, 2, 3])" # ordering prevents this assertion - assert v.startswith("set([") and v.endswith("])") - assert "1" in v and "2" in v and "3" in v - tp, v = py3.load(p) + tp, v = load(p) assert tp == "set" # assert v == "{1, 2, 3}" # ordering prevents this assertion assert v.startswith("{") and v.endswith("}") assert "1" in v and "2" in v and "3" in v p = dump("set()") - tp, v = py2.load(p) - assert tp == "set" - assert v == "set([])" - tp, v = py3.load(p) + tp, v = load(p) assert tp == "set" assert v == "set()" -def test_frozenset(py2, py3, dump): +def test_frozenset(load, dump): p = dump("frozenset((1, 2, 3))") - tp, v = py2.load(p) - assert tp == "frozenset" - assert v == "frozenset([1, 2, 3])" - tp, v = py3.load(p) + tp, v = load(p) assert tp == "frozenset" assert v == "frozenset({1, 2, 3})" - p = dump("frozenset()") - tp, v = py2.load(p) - assert tp == "frozenset" - assert v == "frozenset([])" - tp, v = py3.load(p) - assert tp == "frozenset" - assert v == "frozenset()" -def test_long(py2, py3): +def test_long(load, dump): really_big = "9223372036854775807324234" - p = py2.dump(really_big) - tp, v = py2.load(p) - assert tp == "long" - assert v == really_big + "L" - tp, v = py3.load(p) - assert tp == "int" - assert v == really_big - p = py3.dump(really_big) - tp, v == py3.load(p) + p = dump(really_big) + tp, v = load(p) assert tp == "int" assert v == really_big - tp, v = py2.load(p) - assert tp == "long" - assert v == really_big + "L" - -def test_small_long(py2, py3): - p = py2.dump("123L") - tp, s = py2.load(p) - assert s == "123L" - tp, s = py3.load(p) - assert s == "123" - -def test_bytes(py2, py3): - p = py3.dump("b'hi'") - tp, v = py2.load(p) - assert tp == "str" - assert v == "'hi'" - tp, v = py3.load(p) +def test_bytes(dump, load): + p = dump("b'hi'") + tp, v = load(p) assert tp == "bytes" assert v == "b'hi'" -def test_str(py2, py3): - p = py2.dump("'xyz'") - tp, s = py2.load(p) - assert tp == "str" - assert s == "'xyz'" - tp, s = py3.load(p, "py2str_as_py3str=True") +def test_str(dump, load): + p = dump("'xyz'") + tp, s = load(p) assert tp == "str" assert s == "'xyz'" - tp, s = py3.load(p, "py2str_as_py3str=False") - assert s == "b'xyz'" - assert tp == "bytes" -def test_unicode(py2, py3): - p = py2.dump("u'hi'") - tp, s = py2.load(p) - assert tp == "unicode" - assert s == "u'hi'" - tp, s = py3.load(p) - assert tp == "str" - assert s == "'hi'" - p = py3.dump("'hi'") - tp, s = py3.load(p) +def test_unicode(load, dump): + p = dump("u'hi'") + tp, s = load(p) assert tp == "str" assert s == "'hi'" - tp, s = py2.load(p) - # depends on unserialization defaults - assert tp == "unicode" - assert s == "u'hi'" -def test_bool(py2, py3): - p = py2.dump("True") - tp, s = py2.load(p) - assert tp == "bool" - assert s == "True" - tp, s = py3.load(p) +def test_bool(dump, load): + p = dump("True") + tp, s = load(p) assert s == "True" assert tp == "bool" - p = py2.dump("False") - tp, s = py2.load(p) - assert s == "False" def test_none(dump, load): @@ -283,8 +207,8 @@ def test_none(dump, load): assert s == "None" -def test_tuple_nested_with_empty_in_between(py2): - p = py2.dump("(1, (), 3)") - tp, s = py2.load(p) +def test_tuple_nested_with_empty_in_between(dump, load): + p = dump("(1, (), 3)") + tp, s = load(p) assert tp == "tuple" assert s == "(1, (), 3)" From 2be734f52564d1b6a7c1f49b791ca7b1e39672c5 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 28 Dec 2022 19:07:06 +0100 Subject: [PATCH 012/275] add pypy37 to envlist --- tox.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 84a35670..be9c291b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py{37,38,39,310,311},docs,linting +envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true [testenv] deps= @@ -23,7 +23,6 @@ commands = [testenv:linting] skip_install = True -basepython = python3 deps = pre-commit>=1.11.0 commands = pre-commit run --all-files --show-diff-on-failure From 2a8b68cbe402b10587b482af2fbdc6f4d23c0070 Mon Sep 17 00:00:00 2001 From: Kian-Meng Ang Date: Sat, 31 Dec 2022 12:48:17 +0800 Subject: [PATCH 013/275] Fix typos and add `codespell` hook to pre-commit --- .pre-commit-config.yaml | 4 ++++ CHANGELOG.rst | 10 +++++----- ISSUES.txt | 6 +++--- README.rst | 2 +- doc/basics.rst | 2 +- doc/index.rst | 2 +- execnet/gateway.py | 6 +++--- execnet/gateway_base.py | 12 ++++++------ execnet/gateway_bootstrap.py | 2 +- execnet/gateway_io.py | 4 ++-- execnet/gateway_socket.py | 2 +- execnet/rsync.py | 4 ++-- testing/test_basics.py | 2 +- testing/test_rsync.py | 6 +++--- testing/test_serializer.py | 2 +- 15 files changed, 35 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b0c28ec..796d50d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,9 @@ exclude: doc/en/example/py2py3/test_py2.py repos: +- repo: https://github.com/codespell-project/codespell + rev: v2.2.2 + hooks: + - id: codespell - repo: https://github.com/psf/black rev: 22.12.0 hooks: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7080181..6597b8c1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -103,7 +103,7 @@ with previous versions and future versions additionally stored serialized objects containing complex objects will have a incompatible opcode when read with execnet < 1.4.0 - and wont be loadable with execnet 1.4.0 either + and won't be loadable with execnet 1.4.0 either its strongly suggested to avoid using the Serializer of execnet 1.4.0 this affects devpi and the external pytest-cache plugin @@ -157,7 +157,7 @@ - gateway.remote_exec() will now execute in multiple threads on the other side by default. The previous - neccessity of running "gateway.remote_init_threads()" + necessity of running "gateway.remote_init_threads()" to allow for such concurrency is gone. The latter method is now a no-op and will be removed in future versions of execnet. @@ -228,7 +228,7 @@ - fix issue #2 - properly reconfigure the channels string coercion for rsync, so it can send from python2 to python3 -- fix issue #9 - propperly terminate the worker threadpools in safe_terminate +- fix issue #9 - properly terminate the worker threadpools in safe_terminate - fix issue #8 - no longer kill remote pids locally on jython ssh gateways - refactor socketserver, so it can be directly remote_exec'd for starting a socket gateway on a remote @@ -257,7 +257,7 @@ 1.0.8 -------------------------------- -- new ``gateway.remote_exec(func, **kwargs)`` style fo executing +- new ``gateway.remote_exec(func, **kwargs)`` style for executing a pure function with parameters. The function on the remote side also needs to accept a ``channel`` which allows it to communicate back and forth. Thanks to Ronny Pfannschmidt @@ -336,7 +336,7 @@ - automatically close a channel when a remote callback raises an exception, makes communication more robust because until - now failing callbacks rendered the receiverthread unuseable + now failing callbacks rendered the receiverthread unusable leaving the remote side in-accessible. - internally split socket gateways, speeds up popen-gateways diff --git a/ISSUES.txt b/ISSUES.txt index bda7dc38..0b2e6b95 100644 --- a/ISSUES.txt +++ b/ISSUES.txt @@ -44,7 +44,7 @@ The client constructs a Channel() object which triggers these two connections, presenting a nice send/receive abstractions. -The code executing in the http-execserver controled +The code executing in the http-execserver controlled subprocess is a PopenGateway. It does not know that it is connected via the http client/server machinery. @@ -76,7 +76,7 @@ tags: 1.1 feature newdist A building block for establishing a permanent hierarchy of processes is a Gateway that operates on a Channel object. This way a gateway can be instantiated through another -intermediating gateway which bi-directionallry forwads +intermediating gateway which bi-directionallry forwards Messages through a existing channel. A code example could look like this: @@ -89,7 +89,7 @@ A code example could look like this: # on the remote place such that the same ssh connection is re-used # for multiple ssh=codespeak.net connections, makes for small latency. -some first simple implemenation might look like this:: +some first simple implementation might look like this:: gw = group.makegateway("socketservice//port=8888") # we can rely that the subprocess gw can import execnet diff --git a/README.rst b/README.rst index b529ebe0..d88c94e4 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ Important execnet_ provides carefully tested means to ad-hoc interact with Python interpreters across version, platform and network barriers. It provides -a minimal and fast API targetting the following uses: +a minimal and fast API targeting the following uses: * distribute tasks to local or remote processes * write and deploy hybrid multi-process applications diff --git a/doc/basics.rst b/doc/basics.rst index 6be90030..aa6dabaf 100644 --- a/doc/basics.rst +++ b/doc/basics.rst @@ -130,7 +130,7 @@ and manage the final termination procedure: .. automethod:: Group.terminate(timeout=None) -This method is implicitely called for each gateway group at +This method is implicitly called for each gateway group at process-exit, using a small timeout. This is fine for interactive sessions or random scripts which you rather like to error out than hang. If you start many diff --git a/doc/index.rst b/doc/index.rst index 0a75cb8b..fe1028c0 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -15,7 +15,7 @@ all major computing platforms today. **execnet** provides a `share-nothing model`_ with `channel-send/receive`_ communication for distributing execution across many Python interpreters across version, platform and network barriers. It has -a minimal and fast API targetting the following uses: +a minimal and fast API targeting the following uses: * distribute tasks to (many) local or remote CPUs * write and deploy hybrid multi-process applications diff --git a/execnet/gateway.py b/execnet/gateway.py index 578fb3ad..3d0bdddf 100644 --- a/execnet/gateway.py +++ b/execnet/gateway.py @@ -18,7 +18,7 @@ class Gateway(gateway_base.BaseGateway): - """Gateway to a local or remote Python Intepreter.""" + """Gateway to a local or remote Python Interpreter.""" def __init__(self, io, spec): super().__init__(io=io, id=spec.id, _startcount=1) @@ -188,8 +188,8 @@ def _find_non_builtin_globals(source, codeobj): def _source_of_function(function): if function.__name__ == "": raise ValueError("can't evaluate lambda functions'") - # XXX: we dont check before remote instanciation - # if arguments are used propperly + # XXX: we dont check before remote instantiation + # if arguments are used properly try: sig = inspect.getfullargspec(function) except AttributeError: diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index a1af932d..fd3c7f63 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -403,7 +403,7 @@ def from_io(io): raise EOFError("empty read") except EOFError: e = sys.exc_info()[1] - raise EOFError("couldnt load message header, " + e.args[0]) + raise EOFError("couldn't load message header, " + e.args[0]) msgtype, channel, payload = struct.unpack("!bii", header) return Message(msgtype, channel, io.read(payload)) @@ -634,13 +634,13 @@ def makefile(self, mode="w", proxyclose=False): return ChannelFileWrite(channel=self, proxyclose=proxyclose) elif mode == "r": return ChannelFileRead(channel=self, proxyclose=proxyclose) - raise ValueError(f"mode {mode!r} not availabe") + raise ValueError(f"mode {mode!r} not available") def close(self, error=None): """close down this channel with an optional error message. Note that closing of a channel tied to remote_exec happens automatically at the end of execution and cannot - be done explicitely. + be done explicitly. """ if self._executing: raise OSError("cannot explicitly close channel within remote_exec") @@ -760,7 +760,7 @@ def new(self, id=None): """create a new Channel with 'id' (or create new id if None).""" with self._writelock: if self.finished: - raise OSError(f"connexion already closed: {self.gateway}") + raise OSError(f"connection already closed: {self.gateway}") if id is None: id = self.count self.count += 2 @@ -1130,7 +1130,7 @@ def load(self, versioned=False): loader = self.num2func[opcode] except KeyError: raise LoadError( - "unkown opcode %r - " "wire protocol corruption?" % (opcode,) + "unknown opcode %r - " "wire protocol corruption?" % (opcode,) ) loader(self) except _Stop: @@ -1331,7 +1331,7 @@ def __init__(self, write=None): def save(self, obj, versioned=False): # calling here is not re-entrant but multiple instances # may write to the same stream because of the common platform - # atomic-write guaruantee (concurrent writes each happen atomicly) + # atomic-write guarantee (concurrent writes each happen atomically) if versioned: self._write(DUMPFORMAT_VERSION) self._save(obj) diff --git a/execnet/gateway_bootstrap.py b/execnet/gateway_bootstrap.py index a9a68cef..30b6d866 100644 --- a/execnet/gateway_bootstrap.py +++ b/execnet/gateway_bootstrap.py @@ -103,7 +103,7 @@ def bootstrap(io, spec): elif spec.socket: bootstrap_socket(io, spec) else: - raise ValueError("unknown gateway type, cant bootstrap") + raise ValueError("unknown gateway type, can't bootstrap") gw = Gateway(io, spec) fix_pid_for_jython_popen(gw) return gw diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index ed7d3056..1e49543f 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -212,7 +212,7 @@ def forward_to_sub(data): proxy_channelX.setcallback(forward_to_sub) - def controll(data): + def control(data): if data == RIO_WAIT: control_chan.send(sub_io.wait()) elif data == RIO_KILL: @@ -222,7 +222,7 @@ def controll(data): elif data == RIO_CLOSE_WRITE: control_chan.send(sub_io.close_write()) - control_chan.setcallback(controll) + control_chan.setcallback(control) # write data to the master coming from the sub forward_to_master_file = proxy_channelX.makefile("w") diff --git a/execnet/gateway_socket.py b/execnet/gateway_socket.py index 386b9686..175112bc 100644 --- a/execnet/gateway_socket.py +++ b/execnet/gateway_socket.py @@ -54,7 +54,7 @@ def kill(self): def start_via(gateway, hostport=None): """return a host, port tuple, - after instanciating a socketserver on the given gateway + after instantiating a socketserver on the given gateway """ if hostport is None: host, port = ("localhost", 0) diff --git a/execnet/rsync.py b/execnet/rsync.py index b7183412..32911e7e 100644 --- a/execnet/rsync.py +++ b/execnet/rsync.py @@ -1,5 +1,5 @@ """ -1:N rsync implemenation on top of execnet. +1:N rsync implementation on top of execnet. (c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ @@ -17,7 +17,7 @@ class RSync: There is limited support for symlinks, which means that symlinks pointing to the sourcetree will be send "as is" while external - symlinks will be just copied (regardless of existance of such + symlinks will be just copied (regardless of existence of such a path on remote side). """ diff --git a/testing/test_basics.py b/testing/test_basics.py index 2eac3401..42eee1b7 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -340,7 +340,7 @@ def prototype(wrong): pytest.raises(ValueError, gateway._source_of_function, prototype) def test_function_without_known_source_fails(self): - # this one wont be able to find the source + # this one won't be able to find the source mess = {} py.builtin.exec_("def fail(channel): pass", mess, mess) print(inspect.getsourcefile(mess["fail"])) diff --git a/testing/test_rsync.py b/testing/test_rsync.py index a72e1c72..ef7ba206 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -177,7 +177,7 @@ def test_read_only_directories(self, dirs, gw1): def test_symlink_rsync(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - sourcefile = dirs.source.ensure("subdir", "existant") + sourcefile = dirs.source.ensure("subdir", "existent") source.join("rellink").mksymlinkto(sourcefile, absolute=0) source.join("abslink").mksymlinkto(sourcefile) @@ -186,7 +186,7 @@ def test_symlink_rsync(self, dirs, gw1): rsync.send() expected = dest.join(sourcefile.relto(dirs.source)) - assert dest.join("rellink").readlink() == "subdir/existant" + assert dest.join("rellink").readlink() == "subdir/existent" assert dest.join("abslink").readlink() == expected @needssymlink @@ -211,7 +211,7 @@ def test_symlink2_rsync(self, dirs, gw1): def test_callback(self, dirs, gw1): dest = dirs.dest1 source = dirs.source - source.ensure("existant").write("a" * 100) + source.ensure("existent").write("a" * 100) source.ensure("existant2").write("a" * 10) total = {} diff --git a/testing/test_serializer.py b/testing/test_serializer.py index bb3a1308..594ac7c1 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -41,7 +41,7 @@ def teardown_module(mod): TEMPDIR.remove(True) -# we use the execnet folder in order to avoid tiggering a missing apipkg +# we use the execnet folder in order to avoid triggering a missing apipkg pyimportdir = str(py.path.local(execnet.__file__).dirpath()) From 0bed37e5e30d0bb5fc69673112047f8d13aec2a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Jan 2023 02:35:26 +0000 Subject: [PATCH 014/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/blacken-docs: v1.12.1 → 1.13.0](https://github.com/asottile/blacken-docs/compare/v1.12.1...1.13.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 796d50d7..107c01a0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + rev: 1.13.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] From ed508aacdbfbbccbd54955bed69e9f25544a22bc Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 10 Jan 2023 16:38:00 +0100 Subject: [PATCH 015/275] drop deprecated calls --- CHANGELOG.rst | 1 + execnet/__init__.py | 6 ------ execnet/deprecated.py | 48 ----------------------------------------- testing/test_gateway.py | 15 ------------- 4 files changed, 1 insertion(+), 69 deletions(-) delete mode 100644 execnet/deprecated.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6597b8c1..ed771cdb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ * apply pyupgrade --py37-plus * minimal mypy fixes and python2 support code drop * migrate packaging to hatch +* drop deprecated apis of old makegateway names diff --git a/execnet/__init__.py b/execnet/__init__.py index fd6fbaee..c403e2bb 100644 --- a/execnet/__init__.py +++ b/execnet/__init__.py @@ -7,9 +7,6 @@ (c) 2012, Holger Krekel and others """ from ._version import version as __version__ -from .deprecated import PopenGateway -from .deprecated import SocketGateway -from .deprecated import SshGateway from .gateway_base import DataFormatError from .gateway_base import dump from .gateway_base import dumps @@ -29,9 +26,6 @@ __all__ = [ "__version__", - "PopenGateway", - "SocketGateway", - "SshGateway", "makegateway", "set_execmodel", "HostNotFound", diff --git a/execnet/deprecated.py b/execnet/deprecated.py deleted file mode 100644 index 729579b9..00000000 --- a/execnet/deprecated.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -some deprecated calls - -(c) 2008-2009, Holger Krekel and others -""" -import execnet - - -def PopenGateway(python=None): - """instantiate a gateway to a subprocess - started with the given 'python' executable. - """ - APIWARN("1.0.0b4", "use makegateway('popen')") - spec = execnet.XSpec("popen") - spec.python = python - return execnet.default_group.makegateway(spec) - - -def SocketGateway(host, port): - """This Gateway provides interaction with a remote process - by connecting to a specified socket. On the remote - side you need to manually start a small script - (py/execnet/script/socketserver.py) that accepts - SocketGateway connections or use the experimental - new_remote() method on existing gateways. - """ - APIWARN("1.0.0b4", "use makegateway('socket=host:port')") - spec = execnet.XSpec(f"socket={host}:{port}") - return execnet.default_group.makegateway(spec) - - -def SshGateway(sshaddress, remotepython=None, ssh_config=None): - """instantiate a remote ssh process with the - given 'sshaddress' and remotepython version. - you may specify an ssh_config file. - """ - APIWARN("1.0.0b4", "use makegateway('ssh=host')") - spec = execnet.XSpec("ssh=%s" % sshaddress) - spec.python = remotepython - spec.ssh_config = ssh_config - return execnet.default_group.makegateway(spec) - - -def APIWARN(version, msg, stacklevel=3): - import warnings - - Warn = DeprecationWarning(f"(since version {version}) {msg}") - warnings.warn(Warn, stacklevel=stacklevel) diff --git a/testing/test_gateway.py b/testing/test_gateway.py index bb52ba99..d4b5847f 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -27,21 +27,6 @@ ) -def fails(*args, **kwargs): - 0 / 0 - - -def test_deprecation(recwarn, monkeypatch): - execnet.PopenGateway().exit() - assert recwarn.pop(DeprecationWarning) - monkeypatch.setattr(socket, "socket", fails) - pytest.raises(Exception, execnet.SocketGateway, "localhost", 8811) - assert recwarn.pop(DeprecationWarning) - monkeypatch.setattr(subprocess, "Popen", fails) - pytest.raises(Exception, execnet.SshGateway, "not-existing") - assert recwarn.pop(DeprecationWarning) - - class TestBasicGateway: def test_correct_setup(self, gw): assert gw.hasreceiver() From dd3f23908227a07a6da722f7a19bbba1dd70305e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 22 Jan 2023 22:13:31 +0100 Subject: [PATCH 016/275] add dependabot config --- .github/dependabot.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yaml diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..5ace4600 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" From 296b8bbfafb47065bb10c7b81a253168238db8bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Jan 2023 21:30:12 +0000 Subject: [PATCH 017/275] Bump actions/setup-python from 2 to 4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f0b69e01..051f1a1b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: Install tox @@ -35,7 +35,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - name: Install hatch From f6f9e61fe9eb7b949f7ab0c3fd99d94700fc5110 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 22 Jan 2023 21:30:15 +0000 Subject: [PATCH 018/275] Bump actions/checkout from 1 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 1 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f0b69e01..6a107c4e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: python: ["3.7","3.8","3.10","3.11", "pypy-3.7"] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 with: @@ -33,7 +33,7 @@ jobs: needs: tests steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 with: From 0c2dd4979888a993e4582aa1a56e2e8882b79b53 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 22 Jan 2023 22:35:10 +0100 Subject: [PATCH 019/275] ensure fetch-depth --- .github/workflows/main.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6a107c4e..7d438000 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,6 +15,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v2 with: @@ -34,6 +36,8 @@ jobs: steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v2 with: From 22c3217cf5d5cea669339c28bbd194e57c863091 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 10 Jan 2023 16:35:16 +0100 Subject: [PATCH 020/275] make vagrant test depend on vagrant already running --- .gitignore | 5 ++++ Vagrantfile | 70 +++++++++++++++++++++++++++++++++++++++++++ testing/test_xspec.py | 34 ++++++++++++++------- 3 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 Vagrantfile diff --git a/.gitignore b/.gitignore index 91c2dee0..aafe44fc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,8 @@ bin/ include/ .Python .env/ +.cache/ +.vagrant/ +.vagrant.d/ +.config/ +.local/ diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..d099fa42 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,70 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "generic/debian11" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + # vb.memory = "1024" + # end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + # apt-get update + # apt-get install -y apache2 + # SHELL +end diff --git a/testing/test_xspec.py b/testing/test_xspec.py index cab57216..a75e5a1c 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -1,4 +1,7 @@ +from __future__ import annotations + import os +import shutil import subprocess import sys @@ -21,10 +24,10 @@ class TestXSpec: def test_norm_attributes(self): spec = XSpec( - r"socket=192.168.102.2:8888//python=c:/this/python2.5" r"//chdir=d:\hello" + r"socket=192.168.102.2:8888//python=c:/this/python3.8//chdir=d:\hello" ) assert spec.socket == "192.168.102.2:8888" - assert spec.python == "c:/this/python2.5" + assert spec.python == "c:/this/python3.8" assert spec.chdir == r"d:\hello" assert spec.nice is None assert not hasattr(spec, "_xyz") @@ -195,19 +198,28 @@ def test_ssh(self, specssh, makegateway): assert rinfo.cwd == rinfo2.cwd assert rinfo.version_info == rinfo2.version_info - @pytest.mark.xfail(reason="bad image name", run=False) - def test_vagrant(self, makegateway, tmpdir, monkeypatch): - vagrant = py.path.local.sysfind("vagrant") - if vagrant is None: + def test_vagrant(self, makegateway): + vagrant_bin = shutil.which("vagrant") + if vagrant_bin is None: pytest.skip("Vagrant binary not in PATH") - monkeypatch.chdir(tmpdir) - subprocess.check_call("vagrant init hashicorp/precise32", shell=True) - subprocess.check_call("vagrant up --provider virtualbox", shell=True) - gw = makegateway("vagrant_ssh=default") + res = subprocess.run( + [vagrant_bin, "status", "default", "--machine-readable"], + capture_output=True, + encoding="utf-8", + errors="replace", + ).stdout + print(res) + if ",default,state,shutoff\n" in res: + pytest.xfail("vm shutoff, run `vagrant up` first") + if ",default,state,not_created\n" in res: + pytest.xfail("vm not created, run `vagrant up` first") + if ",default,state,running\n" not in res: + pytest.fail("unknown vm state") + + gw = makegateway("vagrant_ssh=default//python=python3") rinfo = gw._rinfo() rinfo.cwd == "/home/vagrant" rinfo.executable == "/usr/bin/python" - subprocess.check_call("vagrant halt", shell=True) def test_socket(self, specsocket, makegateway): gw = makegateway("socket=%s//id=sock1" % specsocket.socket) From 584e8811f04cd3337900b0bb80c45d2a34aeee85 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 24 Jan 2023 10:39:06 +0100 Subject: [PATCH 021/275] add test that demonstrates #123 --- testing/test_gateway.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/testing/test_gateway.py b/testing/test_gateway.py index d4b5847f..1579d25b 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -500,3 +500,31 @@ def test_popen_args(spec, expected_args): expected_args = expected_args + ["-u", "-c", gateway_io.popen_bootstrapline] args = gateway_io.popen_args(execnet.XSpec(spec)) assert args == expected_args + + +@pytest.mark.parametrize( + "interleave_getstatus", + [ + pytest.param(True, id="interleave-remote-status"), + pytest.param( + False, + id="no-interleave-remote-status", + marks=pytest.mark.xfail( + reason="https://github.com/pytest-dev/execnet/issues/123", + ), + ), + ], +) +def test_regression_gevent_hangs(group, interleave_getstatus): + pytest.importorskip("gevent") + gw = group.makegateway("popen//execmodel=gevent") + + print(gw.remote_status()) + + def sendback(channel): + channel.send(1234) + + ch = gw.remote_exec(sendback) + if interleave_getstatus: + print(gw.remote_status()) + assert ch.receive(timeout=0.5) == 1234 From c92723238c9c983ddcf41242365a79db493fb393 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 07:58:02 -0300 Subject: [PATCH 022/275] [pre-commit.ci] pre-commit autoupdate (#177) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- doc/example/redirect_remote_output.py | 1 + doc/example/remotecmd.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 107c01a0..5a04d6ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/asottile/blacken-docs diff --git a/doc/example/redirect_remote_output.py b/doc/example/redirect_remote_output.py index b2b5bb54..8900a658 100644 --- a/doc/example/redirect_remote_output.py +++ b/doc/example/redirect_remote_output.py @@ -20,6 +20,7 @@ """ ).receive() + # note: callbacks execute in receiver thread! def write(data): print("received:", repr(data)) diff --git a/doc/example/remotecmd.py b/doc/example/remotecmd.py index 47e1c985..d99ca43d 100644 --- a/doc/example/remotecmd.py +++ b/doc/example/remotecmd.py @@ -1,5 +1,6 @@ import os + # contents of: remotecmd.py def simple(arg): return arg + 1 From d7ca9815734a4efb168c3ef997858e38c040fc70 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Feb 2023 07:29:52 -0300 Subject: [PATCH 023/275] [pre-commit.ci] pre-commit autoupdate (#178) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.991 → v1.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v0.991...v1.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a04d6ea..ad223274 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.991' + rev: 'v1.0.0' hooks: - id: mypy From 1f0a229b7dcaf73005b88638c00776dff7311bdb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 08:55:07 -0300 Subject: [PATCH 024/275] [pre-commit.ci] pre-commit autoupdate (#179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.0.0 → v1.0.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.0...v1.0.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad223274..15e60319 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.0.0' + rev: 'v1.0.1' hooks: - id: mypy From 3165e40d55a15527d9a3682e655c4beac82e7c02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Mar 2023 07:50:13 -0300 Subject: [PATCH 025/275] [pre-commit.ci] pre-commit autoupdate (#180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.2.2 → v2.2.4](https://github.com/codespell-project/codespell/compare/v2.2.2...v2.2.4) - [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 15e60319..514351be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ exclude: doc/en/example/py2py3/test_py2.py repos: - repo: https://github.com/codespell-project/codespell - rev: v2.2.2 + rev: v2.2.4 hooks: - id: codespell - repo: https://github.com/psf/black @@ -36,6 +36,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.0.1' + rev: 'v1.1.1' hooks: - id: mypy From 34210c0be9dfdb372fc93633afeb7971620eacd9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 09:01:44 -0300 Subject: [PATCH 026/275] [pre-commit.ci] pre-commit autoupdate (#181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 514351be..93491d1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/asottile/blacken-docs From 066fe9a67a97908a974b6dcc338245b0eb4f194a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Apr 2023 03:57:08 +0000 Subject: [PATCH 027/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.1.1 → v1.2.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.1.1...v1.2.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93491d1a..0e06e6ad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.1.1' + rev: 'v1.2.0' hooks: - id: mypy From 42fc7c6e66055c9f3a9e0f274cc0f525d66a4dfc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 22 Apr 2023 13:27:02 +0300 Subject: [PATCH 028/275] doc/install: update --- doc/install.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index 6da4d70a..04f1313a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,16 +1,16 @@ Info in a nutshell ==================== -**Pythons**: 2.7, 3.4+, PyPy 2 & 3 +**Pythons**: 3.7+, PyPy 3 **Operating systems**: Linux, Windows, OSX, Unix **Distribution names**: * PyPI name: ``execnet`` -* redhat fedora: ``python-execnet`` -* debian: ``python-execnet`` -* gentoo: ``dev-python/execnet`` +* Redhat Fedora: ``python-execnet`` +* Debian: ``python-execnet`` +* Gentoo: ``dev-python/execnet`` **git repository**: https://github.com/pytest-dev/execnet @@ -34,5 +34,4 @@ Next checkout the basic api and examples: .. _`github repository`: https://github.com/pytest-dev/execnet .. _`execnet git repository`: https://github.com/pytest-dev/execnet .. _`pypi release`: http://pypi.python.org/pypi/execnet -.. _setuptools: http://pypi.python.org/pypi/setuptools .. _distribute: http://pypi.python.org/pypi/distribute From 4e9a2829e257409ec5fe90fbcb87bdbeeca654ef Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 22 Apr 2023 13:27:18 +0300 Subject: [PATCH 029/275] tox: remove setuptools-scm dependency Not longer used since switch to Hatch. --- tox.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/tox.ini b/tox.ini index be9c291b..f4f785bf 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true [testenv] deps= - setuptools_scm py pytest pytest-timeout From bb6eee6c3e45961b08f2a387050eb69f5b59acc6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 22 Apr 2023 15:58:27 +0300 Subject: [PATCH 030/275] ci: don't run on every push Running on every push is redundant and somewhat annoying in forks. --- .github/workflows/main.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6503a864..9cb6bf70 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,17 @@ name: build -on: [push, pull_request] +on: + push: + branches: + - "master" + - "test-me-*" + tags: + - "[0-9]+.[0-9]+.[0-9]+" + - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" + + pull_request: + branches: + - "master" jobs: tests: From 113976cfda638c9b3e9cf5f27c260d3f3e6db5a7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 11:56:51 +0300 Subject: [PATCH 031/275] testing: remove some unused code --- execnet/gateway.py | 8 ++------ testing/test_basics.py | 8 ++------ testing/test_channel.py | 8 ++------ testing/test_gateway.py | 3 --- testing/test_rsync.py | 1 - testing/test_serializer.py | 30 +++++------------------------- 6 files changed, 11 insertions(+), 47 deletions(-) diff --git a/execnet/gateway.py b/execnet/gateway.py index 3d0bdddf..6e0b8a7d 100644 --- a/execnet/gateway.py +++ b/execnet/gateway.py @@ -169,11 +169,7 @@ def rinfo_source(channel): def _find_non_builtin_globals(source, codeobj): import ast - - try: - import __builtin__ - except ImportError: - import builtins as __builtin__ + import builtins vars = dict.fromkeys(codeobj.co_varnames) return [ @@ -181,7 +177,7 @@ def _find_non_builtin_globals(source, codeobj): for node in ast.walk(ast.parse(source)) if isinstance(node, ast.Name) and node.id not in vars - and node.id not in __builtin__.__dict__ + and node.id not in builtins.__dict__ ] diff --git a/testing/test_basics.py b/testing/test_basics.py index 42eee1b7..4f2603e3 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -75,8 +75,7 @@ def test_subprocess_interaction(anypython): def send(line): popen.stdin.write(line) - if sys.version_info > (3, 0) or sys.platform.startswith("java"): - popen.stdin.flush() + popen.stdin.flush() def receive(): return popen.stdout.readline() @@ -121,10 +120,7 @@ def test_io_message(anypython, tmpdir, execmodel): py.code.Source( gateway_base, """ - try: - from io import BytesIO - except ImportError: - from StringIO import StringIO as BytesIO + from io import BytesIO import tempfile temp_out = BytesIO() temp_in = BytesIO() diff --git a/testing/test_channel.py b/testing/test_channel.py index bda30dc1..a1bfbae9 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -4,7 +4,6 @@ import time import pytest -from test_gateway import _find_version needs_early_gc = pytest.mark.skipif("not hasattr(sys, 'getrefcount')") @@ -211,17 +210,14 @@ def check_channel_callback_stays_active(self, gw, earlyfree=True): l = [] channel = gw.remote_exec( source=""" - try: - import thread - except ImportError: - import _thread as thread + import _thread import time def producer(subchannel): for i in range(5): time.sleep(0.15) subchannel.send(i*100) channel2 = channel.receive() - thread.start_new_thread(producer, (channel2,)) + _thread.start_new_thread(producer, (channel2,)) del channel2 """ ) diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 1579d25b..dabaf34b 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -2,8 +2,6 @@ mostly functional tests of gateways. """ import os -import socket -import subprocess import sys from textwrap import dedent @@ -12,7 +10,6 @@ import pytest from execnet import gateway_base from execnet import gateway_io -from test_serializer import _find_version TESTTIMEOUT = 10.0 # seconds needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") diff --git a/testing/test_rsync.py b/testing/test_rsync.py index ef7ba206..9f9d6e1a 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -2,7 +2,6 @@ import py import pytest from execnet import RSync -from test_serializer import _find_version @pytest.fixture(scope="module") diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 594ac7c1..62038196 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,4 +1,3 @@ -import os import subprocess import sys import tempfile @@ -10,26 +9,8 @@ MINOR_VERSIONS = {"3": "543210", "2": "76"} -def _find_version(suffix=""): - name = "python" + suffix - executable = py.path.local.sysfind(name) - if executable is None: - if sys.platform == "win32" and suffix == "3": - for name in ("python31", "python30"): - executable = py.path.local(rf"c:\\{name}\python.exe") - if executable.check(): - return executable - for tail in MINOR_VERSIONS.get(suffix, ""): - path = py.path.local.sysfind(f"{name}.{tail}") - if path: - return path - - else: - pytest.skip(f"can't find a {name!r} executable") - return executable - - -TEMPDIR = _py2_wrapper = _py3_wrapper = None +TEMPDIR = None +_py3_wrapper = None def setup_module(mod): @@ -56,8 +37,8 @@ def dump(self, obj_rep): import sys sys.path.insert(0, %r) import gateway_base as serializer -if sys.version_info > (3, 0): # Need binary output - sys.stdout = sys.stdout.detach() +# Need binary output +sys.stdout = sys.stdout.detach() sys.stdout.write(serializer.dumps_internal(%s)) """ % (pyimportdir, obj_rep) @@ -83,8 +64,7 @@ def load(self, data, option_args="__class__"): import sys sys.path.insert(0, %r) import gateway_base as serializer -if sys.version_info > (3, 0): - sys.stdin = sys.stdin.detach() +sys.stdin = sys.stdin.detach() loader = serializer.Unserializer(sys.stdin) loader.%s obj = loader.load() From 8f0e8ec1d0233d49cf0aec938c47c53208ed76e0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 22 Apr 2023 12:50:30 +0300 Subject: [PATCH 032/275] testing/test_basics: fix typo in Gateway mock Fix: ``` testing/test_basics.py::TestPureChannel::test_factory_create[thread] testing/test_basics.py::TestPureChannel::test_factory_getitem[thread] testing/test_basics.py::TestPureChannel::test_channel_timeouterror[thread] testing/test_basics.py::TestPureChannel::test_channel_makefile_incompatmode[thread] /home/ran/src/execnet/.tox/py310/lib/python3.10/site-packages/_pytest/unraisableexception.py:78: PytestUnraisableExceptionWarning: Exception ignored in: Traceback (most recent call last): File "/home/ran/src/execnet/execnet/gateway_base.py", line 605, in __del__ self.gateway._send(msgcode, self.id) AttributeError: 'Gateway' object has no attribute '_send'. Did you mean: '_send_'? ``` --- testing/test_basics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_basics.py b/testing/test_basics.py index 4f2603e3..37333910 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -297,7 +297,7 @@ class Gateway: def _trace(self, *args): pass - def _send_(self, *k): + def _send(self, *k): pass Gateway.execmodel = execmodel From db7f204e4378e4fa86239976a3392bfd832e0de7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 22 Apr 2023 16:55:45 +0300 Subject: [PATCH 033/275] rsync: use a more robust check for relative link than "startswith" `startswith` is incorrect for paths in general, e.g. doesn't handle Windows extended-length paths and other such. The particular code here also wasn't careful about `/a/bc` not being relative to `/a/b`. --- execnet/rsync.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/execnet/rsync.py b/execnet/rsync.py index 32911e7e..1484d49d 100644 --- a/execnet/rsync.py +++ b/execnet/rsync.py @@ -173,10 +173,26 @@ def _send_directory(self, path): self._send_directory_structure(p) def _send_link_structure(self, path): - linkpoint = os.readlink(path) + sourcedir = self._sourcedir basename = path[len(self._sourcedir) + 1 :] - if linkpoint.startswith(self._sourcedir): - self._send_link("linkbase", basename, linkpoint[len(self._sourcedir) + 1 :]) + linkpoint = os.readlink(path) + # On Windows, readlink returns an extended path (//?/) for + # absolute links, but relpath doesn't like mixing extended + # and non-extended paths. So fix it up ourselves. + if ( + os.path.__name__ == "ntpath" + and linkpoint.startswith("\\\\?\\") + and not self._sourcedir.startswith("\\\\?\\") + ): + sourcedir = "\\\\?\\" + self._sourcedir + try: + relpath = os.path.relpath(linkpoint, sourcedir) + except ValueError: + relpath = None + if relpath not in (None, os.curdir, os.pardir) and not relpath.startswith( + os.pardir + os.sep + ): + self._send_link("linkbase", basename, relpath) else: # relative or absolute link, just send it self._send_link("link", basename, linkpoint) From 63869800a2c359db8938dba76cc2030e0033e030 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 07:38:48 -0300 Subject: [PATCH 034/275] [pre-commit.ci] pre-commit autoupdate (#187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.1 → v3.3.2](https://github.com/asottile/pyupgrade/compare/v3.3.1...v3.3.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e06e6ad..93c6ccd7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.3.1 + rev: v3.3.2 hooks: - id: pyupgrade args: [--py37-plus] From ef710363c44c2f2ca1fe2885a10ea4418c234a81 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 11:40:38 +0300 Subject: [PATCH 035/275] doc,testing: drop use of `py` & `tmpdir` Use modern alternatives instead. --- CHANGELOG.rst | 1 + doc/example/conftest.py | 10 +- doc/example/svn-sync-repo.py | 28 ++++-- doc/example/sysinfo.py | 15 +-- testing/conftest.py | 10 +- testing/test_basics.py | 108 ++++++++++---------- testing/test_gateway.py | 64 ++++++------ testing/test_multi.py | 1 - testing/test_rsync.py | 186 ++++++++++++++++++++++------------- testing/test_serializer.py | 27 ++--- testing/test_termination.py | 16 +-- testing/test_threadpool.py | 17 ++-- testing/test_xspec.py | 13 ++- tox.ini | 1 - 14 files changed, 283 insertions(+), 214 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ed771cdb..39e5be84 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ * minimal mypy fixes and python2 support code drop * migrate packaging to hatch * drop deprecated apis of old makegateway names +* Removed ``py`` testing dependency diff --git a/doc/example/conftest.py b/doc/example/conftest.py index 5ea2370e..5044b9eb 100644 --- a/doc/example/conftest.py +++ b/doc/example/conftest.py @@ -1,13 +1,13 @@ +import pathlib import sys -import py -# make execnet and example code importable -cand = py.path.local(__file__).dirpath().dirpath().dirpath() -if cand.join("execnet", "__init__.py").check(): +# Make execnet and example code importable. +cand = pathlib.Path(__file__).parent.parent.parent +if cand.joinpath("execnet", "__init__.py").exists(): if str(cand) not in sys.path: sys.path.insert(0, str(cand)) -cand = py.path.local(__file__).dirpath() +cand = pathlib.Path(__file__).parent if str(cand) not in sys.path: sys.path.insert(0, str(cand)) diff --git a/doc/example/svn-sync-repo.py b/doc/example/svn-sync-repo.py index 69c2dcb2..24e7bd58 100644 --- a/doc/example/svn-sync-repo.py +++ b/doc/example/svn-sync-repo.py @@ -6,10 +6,11 @@ """ import os +import pathlib +import subprocess import sys import execnet -import py def usage(): @@ -19,8 +20,8 @@ def usage(): def main(args): remote = args[0] - localrepo = py.path.local(args[1]) - if not localrepo.check(dir=1): + localrepo = pathlib.Path(args[1]) + if not localrepo.is_dir(): raise SystemExit(f"localrepo {localrepo} does not exist") if len(args) == 3: configfile = args[2] @@ -39,12 +40,18 @@ def main(args): # 4. client goes back to step 1 c = gw.remote_exec( """ - import py import os + import subprocess import time + remote_rev, repopath = channel.receive() - while 1: - rev = py.process.cmdexec('svnlook youngest "%s"' % repopath) + while True: + rev = subprocess.run( + ["svnlook", "youngest", repopath], + check=True, + capture_output=True, + text=True, + ).stdout rev = int(rev) if rev > remote_rev: revrange = (remote_rev+1, rev) @@ -103,12 +110,17 @@ def svn_load(repo, dumpchannel, maxcount=100): if count <= 0: dumpchannel.send(maxcount) count = maxcount - print >> sys.stdout + print() f.close() def get_svn_youngest(repo): - rev = py.process.cmdexec('svnlook youngest "%s"' % repo) + rev = subprocess.run( + ["svnlook", "youngest", repo], + check=True, + capture_output=True, + text=True, + ).stdout return int(rev) diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index 99a523b5..c88517d7 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -6,11 +6,11 @@ (c) Holger Krekel, MIT license """ import optparse +import pathlib import re import sys import execnet -import py parser = optparse.OptionParser(usage=__doc__) @@ -34,14 +34,15 @@ def parsehosts(path): - path = py.path.local(path) + path = pathlib.Path(path) l = [] rex = re.compile(r"Host\s*(\S+)") - for line in path.readlines(): - m = rex.match(line) - if m is not None: - (sshname,) = m.groups() - l.append(sshname) + with path.open() as f: + for line in f: + m = rex.match(line) + if m is not None: + (sshname,) = m.groups() + l.append(sshname) return l diff --git a/testing/conftest.py b/testing/conftest.py index d5e84683..4ea857a9 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,8 +1,9 @@ +import pathlib +import shutil import subprocess import sys import execnet -import py import pytest from execnet.gateway_base import get_execmodel from execnet.gateway_base import WorkerPool @@ -76,7 +77,7 @@ def getspecssh(config): xspecs = getgspecs(config) for spec in xspecs: if spec.ssh: - if not py.path.local.sysfind("ssh"): + if not shutil.which("ssh"): pytest.skip("command not found: ssh") return spec pytest.skip("need '--gx ssh=...'") @@ -113,8 +114,9 @@ def getexecutable(name, cache={}): return cache[name] except KeyError: if name == "sys.executable": - return py.path.local(sys.executable) - executable = py.path.local.sysfind(name) + return pathlib.Path(sys.executable) + path = shutil.which(name) + executable = pathlib.Path(path) if path is not None else None if executable: if name == "jython": popen = subprocess.Popen( diff --git a/testing/test_basics.py b/testing/test_basics.py index 37333910..141f7d3f 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -2,10 +2,10 @@ import os import subprocess import sys +import textwrap from io import BytesIO import execnet -import py import pytest from execnet import gateway from execnet import gateway_base @@ -29,20 +29,20 @@ def test_serializer_api(self, val): val2 = execnet.loads(dumped) assert val == val2 - def test_mmap(self, tmpdir, val): + def test_mmap(self, tmp_path, val): mmap = pytest.importorskip("mmap").mmap - p = tmpdir.join("data") + p = tmp_path / "data" with p.open("wb") as f: f.write(execnet.dumps(val)) - f = p.open("r+b") - m = mmap(f.fileno(), 0) - val2 = execnet.load(m) + with p.open("r+b") as f: + m = mmap(f.fileno(), 0) + val2 = execnet.load(m) assert val == val2 def test_bytesio(self, val): - f = py.io.BytesIO() + f = BytesIO() execnet.dump(f, val) - read = py.io.BytesIO(f.getvalue()) + read = BytesIO(f.getvalue()) val2 = execnet.load(read) assert val == val2 @@ -81,10 +81,8 @@ def receive(): return popen.stdout.readline() try: - source = py.code.Source(read_write_loop, "read_write_loop()") - repr_source = repr(str(source)) + "\n" - sendline = repr_source - send(sendline) + source = inspect.getsource(read_write_loop) + "read_write_loop()" + send(repr(source) + "\n") s = receive() assert s == "ok\n" send("hello\n") @@ -114,11 +112,11 @@ def read_write_loop(): break -def test_io_message(anypython, tmpdir, execmodel): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_io_message(anypython, tmp_path, execmodel): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( """ from io import BytesIO import tempfile @@ -146,24 +144,25 @@ def test_io_message(anypython, tmpdir, execmodel): ), ) ) - # out = py.process.cmdexec("%s %s" %(executable,check)) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -def test_popen_io(anypython, tmpdir, execmodel): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_popen_io(anypython, tmp_path, execmodel): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( f""" io = init_popen_io(get_execmodel({execmodel.backend!r})) io.write("hello".encode('ascii')) s = io.read(1) assert s == "x".encode('ascii') - """, - ) + """ + ), ) from subprocess import Popen, PIPE @@ -191,32 +190,36 @@ def newread(numbytes): assert result == b"tes" -def test_rinfo_source(anypython, tmpdir): - check = tmpdir.join("check.py") - check.write( - py.code.Source( +def test_rinfo_source(anypython, tmp_path): + check = tmp_path / "check.py" + check.write_text( + textwrap.dedent( """ class Channel: def send(self, data): assert eval(repr(data), {}) == data channel = Channel() - """, - gateway.rinfo_source, + """ + ) + + inspect.getsource(gateway.rinfo_source) + + textwrap.dedent( """ print ('all passed') - """, + """ ) ) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -def test_geterrortext(anypython, tmpdir): - check = tmpdir.join("check.py") - check.write( - py.code.Source( - gateway_base, +def test_geterrortext(anypython, tmp_path): + check = tmp_path / "check.py" + check.write_text( + inspect.getsource(gateway_base) + + textwrap.dedent( """ class Arg: pass @@ -230,21 +233,22 @@ class Arg: s = geterrortext(excinfo) assert "17" in s print ("all passed") - """, + """ ) ) - out = anypython.sysexec(check) + out = subprocess.run( + [str(anypython), str(check)], text=True, capture_output=True, check=True + ).stdout print(out) assert "all passed" in out -@pytest.mark.skipif("not hasattr(os, 'dup')") -def test_stdouterrin_setnull(execmodel): - cap = py.io.StdCaptureFD() +@pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") +def test_stdouterrin_setnull(execmodel, capfd): gateway_base.init_popen_io(execmodel) os.write(1, b"hello") os.read(0, 1) - out, err = cap.reset() + out, err = capfd.readouterr() assert not out assert not err @@ -267,7 +271,7 @@ def close(self, errortext=None): def test_exectask(execmodel): - io = py.io.BytesIO() + io = BytesIO() io.execmodel = execmodel gw = gateway_base.WorkerGateway(io, id="something") ch = PseudoChannel() @@ -278,10 +282,10 @@ def test_exectask(execmodel): class TestMessage: def test_wire_protocol(self): for i, handler in enumerate(Message._types): - one = py.io.BytesIO() + one = BytesIO() data = b"23" Message(i, 42, data).to_io(one) - two = py.io.BytesIO(one.getvalue()) + two = BytesIO(one.getvalue()) msg = Message.from_io(two) assert msg.msgcode == i assert isinstance(msg, Message) @@ -338,7 +342,7 @@ def prototype(wrong): def test_function_without_known_source_fails(self): # this one won't be able to find the source mess = {} - py.builtin.exec_("def fail(channel): pass", mess, mess) + exec("def fail(channel): pass", mess, mess) print(inspect.getsourcefile(mess["fail"])) pytest.raises(ValueError, gateway._source_of_function, mess["fail"]) @@ -361,9 +365,9 @@ def working(channel): class TestGlobalFinder: def check(self, func): - src = py.code.Source(func) - code = py.code.Code(func) - return gateway._find_non_builtin_globals(str(src), code.raw) + src = textwrap.dedent(inspect.getsource(func)) + code = func.__code__ + return gateway._find_non_builtin_globals(src, code) def test_local(self): def f(a, b, c): diff --git a/testing/test_gateway.py b/testing/test_gateway.py index dabaf34b..809a13d9 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -2,11 +2,13 @@ mostly functional tests of gateways. """ import os +import pathlib +import shutil +import signal import sys from textwrap import dedent import execnet -import py import pytest from execnet import gateway_base from execnet import gateway_io @@ -90,22 +92,22 @@ def test_gateway_status_busy(self, gw): # race condition assert status.numchannels <= numchannels - def test_remote_exec_module(self, tmpdir, gw): - p = tmpdir.join("remotetest.py") - p.write("channel.send(1)") + def test_remote_exec_module(self, tmp_path, gw): + p = tmp_path / "remotetest.py" + p.write_text("channel.send(1)") mod = type(os)("remotetest") mod.__file__ = str(p) channel = gw.remote_exec(mod) name = channel.receive() assert name == 1 - p.write("channel.send(2)") + p.write_text("channel.send(2)") channel = gw.remote_exec(mod) name = channel.receive() assert name == 2 - def test_remote_exec_module_is_removed(self, gw, tmpdir, monkeypatch): - remotetest = tmpdir.join("remote.py") - remotetest.write( + def test_remote_exec_module_is_removed(self, gw, tmp_path, monkeypatch): + remotetest = tmp_path / "remote.py" + remotetest.write_text( dedent( """ def remote(): @@ -119,13 +121,13 @@ def remote(): ) ) - monkeypatch.syspath_prepend(tmpdir) + monkeypatch.syspath_prepend(tmp_path) import remote ch = gw.remote_exec(remote) # simulate sending the code to a remote location that does not have # access to the source - tmpdir.remove() + shutil.rmtree(tmp_path) ch.send("remote()") try: result = ch.receive() @@ -134,9 +136,9 @@ def remote(): assert result is True - def test_remote_exec_module_with_traceback(self, gw, tmpdir, monkeypatch): - remotetest = tmpdir.join("remotetest.py") - remotetest.write( + def test_remote_exec_module_with_traceback(self, gw, tmp_path, monkeypatch): + remotetestpy = tmp_path / "remotetest.py" + remotetestpy.write_text( dedent( """ def run_me(channel=None): @@ -148,7 +150,7 @@ def run_me(channel=None): ) ) - monkeypatch.syspath_prepend(tmpdir) + monkeypatch.syspath_prepend(tmp_path) import remotetest ch = gw.remote_exec(remotetest) @@ -274,15 +276,13 @@ def test__rinfo(self, gw): class TestPopenGateway: gwtype = "popen" - def test_chdir_separation(self, tmpdir, makegateway): - old = tmpdir.chdir() - try: + def test_chdir_separation(self, tmp_path, makegateway): + with pytest.MonkeyPatch.context() as mp: + mp.chdir(tmp_path) gw = makegateway("popen") - finally: - waschangedir = old.chdir() c = gw.remote_exec("import os ; channel.send(os.getcwd())") x = c.receive() - assert x.lower() == str(waschangedir).lower() + assert x.lower() == str(tmp_path).lower() def test_remoteerror_readable_traceback(self, gw): with pytest.raises(gateway_base.RemoteError) as e: @@ -320,7 +320,7 @@ def test_waitclose_on_remote_killed(self, makegateway): """ ) remotepid = channel.receive() - py.process.kill(remotepid) + os.kill(remotepid, signal.SIGTERM) with pytest.raises(EOFError): channel.waitclose(TESTTIMEOUT) with pytest.raises(IOError): @@ -441,24 +441,24 @@ def test_status_with_threads(self, makegateway): class TestTracing: - def test_popen_filetracing(self, testdir, monkeypatch, makegateway): - tmpdir = testdir.tmpdir - monkeypatch.setenv("TMP", str(tmpdir)) - monkeypatch.setenv("TEMP", str(tmpdir)) # windows + def test_popen_filetracing(self, tmp_path, monkeypatch, makegateway): + monkeypatch.setenv("TMP", str(tmp_path)) + monkeypatch.setenv("TEMP", str(tmp_path)) # windows monkeypatch.setenv("EXECNET_DEBUG", "1") gw = makegateway("popen") # hack out the debuffilename fn = gw.remote_exec( "import execnet;channel.send(execnet.gateway_base.fn)" ).receive() - workerfile = py.path.local(fn) - assert workerfile.check() + workerfile = pathlib.Path(fn) + assert workerfile.exists() worker_line = "creating workergateway" - for line in workerfile.readlines(): - if worker_line in line: - break - else: - pytest.fail(f"did not find {worker_line!r} in tracefile") + with workerfile.open() as f: + for line in f: + if worker_line in line: + break + else: + pytest.fail(f"did not find {worker_line!r} in tracefile") gw.exit() @skip_win_pypy diff --git a/testing/test_multi.py b/testing/test_multi.py index 6504f3fb..79861b11 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -5,7 +5,6 @@ from time import sleep import execnet -import py import pytest from execnet import XSpec from execnet.gateway_base import Channel diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 9f9d6e1a..b8d39f86 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -1,5 +1,9 @@ +import os +import pathlib +import platform +import sys + import execnet -import py import pytest from execnet import RSync @@ -26,23 +30,43 @@ def gw2(request, group): needssymlink = pytest.mark.skipif( - not hasattr(py.path.local, "mksymlinkto"), - reason="py.path.local has no mksymlinkto() on this platform", + not hasattr(os, "symlink") + or (platform.python_implementation() == "PyPy" and sys.platform == "win32"), + reason="os.symlink not available", ) @pytest.fixture -def dirs(request, tmpdir): - t = tmpdir +def dirs(request, tmp_path): + t = tmp_path class dirs: - source = t.join("source") - dest1 = t.join("dest1") - dest2 = t.join("dest2") + source = t / "source" + dest1 = t / "dest1" + dest2 = t / "dest2" + + dirs.source.mkdir() + dirs.dest1.mkdir() + dirs.dest2.mkdir() return dirs +def are_paths_equal(path1, path2): + if os.path.__name__ == "ntpath": + # On Windows, os.readlink returns an extended path (\\?\) + # for absolute symlinks. However, extended does not compare + # equal to non-extended, even when they refer to the same + # path otherwise. So we have to fix it up ourselves... + is_extended1 = str(path1).startswith("\\\\?\\") + is_extended2 = str(path2).startswith("\\\\?\\") + if is_extended1 and not is_extended2: + path2 = pathlib.Path("\\\\?\\" + str(path2)) + if not is_extended1 and is_extended2: + path1 = pathlib.Path("\\\\?\\" + str(path1)) + return path1 == path2 + + class TestRSync: def test_notargets(self, dirs): rsync = RSync(dirs.source) @@ -56,55 +80,57 @@ def test_dirsync(self, dirs, gw1, gw2): source = dirs.source for s in ("content1", "content2", "content2-a-bit-longer"): - source.ensure("subdir", "file1").write(s) + subdir = source / "subdir" + subdir.mkdir(exist_ok=True) + subdir.joinpath("file1").write_text(s) rsync = RSync(dirs.source) rsync.add_target(gw1, dest) rsync.add_target(gw2, dest2) rsync.send() - assert dest.join("subdir").check(dir=1) - assert dest.join("subdir", "file1").check(file=1) - assert dest.join("subdir", "file1").read() == s - assert dest2.join("subdir").check(dir=1) - assert dest2.join("subdir", "file1").check(file=1) - assert dest2.join("subdir", "file1").read() == s + assert dest.joinpath("subdir").is_dir() + assert dest.joinpath("subdir", "file1").is_file() + assert dest.joinpath("subdir", "file1").read_text() == s + assert dest2.joinpath("subdir").is_dir() + assert dest2.joinpath("subdir", "file1").is_file() + assert dest2.joinpath("subdir", "file1").read_text() == s for x in dest, dest2: - fn = x.join("subdir", "file1") - fn.setmtime(0) + fn = x.joinpath("subdir", "file1") + os.utime(fn, (0, 0)) - source.join("subdir").remove("file1") + source.joinpath("subdir", "file1").unlink() rsync = RSync(source) rsync.add_target(gw2, dest2) rsync.add_target(gw1, dest) rsync.send() - assert dest.join("subdir", "file1").check(file=1) - assert dest2.join("subdir", "file1").check(file=1) + assert dest.joinpath("subdir", "file1").is_file() + assert dest2.joinpath("subdir", "file1").is_file() rsync = RSync(source) rsync.add_target(gw1, dest, delete=True) rsync.add_target(gw2, dest2) rsync.send() - assert not dest.join("subdir", "file1").check() - assert dest2.join("subdir", "file1").check() + assert not dest.joinpath("subdir", "file1").exists() + assert dest2.joinpath("subdir", "file1").exists() def test_dirsync_twice(self, dirs, gw1, gw2): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source) rsync.add_target(gw1, dirs.dest1) rsync.send() - assert dirs.dest1.join("hello").check() + assert dirs.dest1.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() assert rsync.send(raises=False) is None rsync.add_target(gw1, dirs.dest2) rsync.send() - assert dirs.dest2.join("hello").check() + assert dirs.dest2.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() assert rsync.send(raises=False) is None def test_rsync_default_reporting(self, capsys, dirs, gw1): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source) rsync.add_target(gw1, dirs.dest1) rsync.send() @@ -113,7 +139,7 @@ def test_rsync_default_reporting(self, capsys, dirs, gw1): def test_rsync_non_verbose(self, capsys, dirs, gw1): source = dirs.source - source.ensure("hello") + source.joinpath("hello").touch() rsync = RSync(source, verbose=False) rsync.add_target(gw1, dirs.dest1) rsync.send() @@ -121,47 +147,58 @@ def test_rsync_non_verbose(self, capsys, dirs, gw1): assert not out assert not err - @pytest.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + @pytest.mark.skipif( + sys.platform == "win32" or getattr(os, "_name", "") == "nt", + reason="irrelevant on windows", + ) def test_permissions(self, dirs, gw1, gw2): source = dirs.source dest = dirs.dest1 - onedir = dirs.source.ensure("one", dir=1) + onedir = dirs.source / "one" + onedir.mkdir() onedir.chmod(448) - onefile = dirs.source.ensure("file") + onefile = dirs.source / "file" + onefile.touch() onefile.chmod(504) - onefile_mtime = onefile.stat().mtime + onefile_mtime = onefile.stat().st_mtime rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - destdir = dirs.dest1.join(onedir.basename) - destfile = dirs.dest1.join(onefile.basename) - assert destfile.stat().mode & 511 == 504 - mode = destdir.stat().mode + destdir = dirs.dest1 / onedir.name + destfile = dirs.dest1 / onefile.name + assert destfile.stat().st_mode & 511 == 504 + mode = destdir.stat().st_mode assert mode & 511 == 448 # transfer again with changed permissions onedir.chmod(504) onefile.chmod(448) - onefile.setmtime(onefile_mtime) + os.utime(onefile, (onefile_mtime, onefile_mtime)) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - mode = destfile.stat().mode + mode = destfile.stat().st_mode assert mode & 511 == 448, mode - mode = destdir.stat().mode + mode = destdir.stat().st_mode assert mode & 511 == 504 - @py.test.mark.skipif("sys.platform == 'win32' or getattr(os, '_name', '') == 'nt'") + @pytest.mark.skipif( + sys.platform == "win32" or getattr(os, "_name", "") == "nt", + reason="irrelevant on windows", + ) def test_read_only_directories(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - source.ensure("sub", "subsub", dir=True) - source.join("sub").chmod(0o500) - source.join("sub", "subsub").chmod(0o500) + sub = source / "sub" + sub.mkdir() + subsub = sub / "subsub" + subsub.mkdir() + sub.chmod(0o500) + subsub.chmod(0o500) # The destination directories should be created with the write # permission forced, to avoid raising an EACCES error. @@ -169,49 +206,62 @@ def test_read_only_directories(self, dirs, gw1): rsync.add_target(gw1, dest) rsync.send() - assert dest.join("sub").stat().mode & 0o700 - assert dest.join("sub").join("subsub").stat().mode & 0o700 + assert dest.joinpath("sub").stat().st_mode & 0o700 + assert dest.joinpath("sub", "subsub").stat().st_mode & 0o700 @needssymlink def test_symlink_rsync(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - sourcefile = dirs.source.ensure("subdir", "existent") - source.join("rellink").mksymlinkto(sourcefile, absolute=0) - source.join("abslink").mksymlinkto(sourcefile) + subdir = dirs.source / "subdir" + subdir.mkdir() + sourcefile = subdir / "existent" + sourcefile.touch() + source.joinpath("rellink").symlink_to(sourcefile.relative_to(source)) + source.joinpath("abslink").symlink_to(sourcefile) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - expected = dest.join(sourcefile.relto(dirs.source)) - assert dest.join("rellink").readlink() == "subdir/existent" - assert dest.join("abslink").readlink() == expected + rellink = pathlib.Path(os.readlink(str(dest / "rellink"))) + assert rellink == pathlib.Path("subdir/existent") + + abslink = pathlib.Path(os.readlink(str(dest / "abslink"))) + expected = dest.joinpath(sourcefile.relative_to(source)) + assert are_paths_equal(abslink, expected) @needssymlink def test_symlink2_rsync(self, dirs, gw1): source = dirs.source dest = dirs.dest1 - subdir = dirs.source.ensure("subdir", dir=1) - sourcefile = subdir.ensure("somefile") - subdir.join("link1").mksymlinkto(subdir.join("link2"), absolute=0) - subdir.join("link2").mksymlinkto(sourcefile, absolute=1) - subdir.join("link3").mksymlinkto(source.dirpath(), absolute=1) + subdir = dirs.source / "subdir" + subdir.mkdir() + sourcefile = subdir / "somefile" + sourcefile.touch() + subdir.joinpath("link1").symlink_to( + subdir.joinpath("link2").relative_to(subdir) + ) + subdir.joinpath("link2").symlink_to(sourcefile) + subdir.joinpath("link3").symlink_to(source.parent) rsync = RSync(source) rsync.add_target(gw1, dest) rsync.send() - expected = dest.join(sourcefile.relto(dirs.source)) - destsub = dest.join("subdir") - assert destsub.check() - assert destsub.join("link1").readlink() == "link2" - assert destsub.join("link2").readlink() == expected - assert destsub.join("link3").readlink() == source.dirpath() + expected = dest.joinpath(sourcefile.relative_to(dirs.source)) + destsub = dest.joinpath("subdir") + assert destsub.exists() + link1 = pathlib.Path(os.readlink(str(destsub / "link1"))) + assert are_paths_equal(link1, pathlib.Path("link2")) + link2 = pathlib.Path(os.readlink(str(destsub / "link2"))) + assert are_paths_equal(link2, expected) + link3 = pathlib.Path(os.readlink(str(destsub / "link3"))) + assert are_paths_equal(link3, source.parent) def test_callback(self, dirs, gw1): dest = dirs.dest1 source = dirs.source - source.ensure("existent").write("a" * 100) - source.ensure("existant2").write("a" * 10) + source.joinpath("existent").write_text("a" * 100) + source.joinpath("existant2").write_text("a" * 10) total = {} def callback(cmd, lgt, channel): @@ -227,20 +277,20 @@ def callback(cmd, lgt, channel): def test_file_disappearing(self, dirs, gw1): dest = dirs.dest1 source = dirs.source - source.ensure("ex").write("a" * 100) - source.ensure("ex2").write("a" * 100) + source.joinpath("ex").write_text("a" * 100) + source.joinpath("ex2").write_text("a" * 100) class DRsync(RSync): def filter(self, x): assert x != source if x.endswith("ex2"): self.x = 1 - source.join("ex2").remove() + source.joinpath("ex2").unlink() return True rsync = DRsync(source) rsync.add_target(gw1, dest) rsync.send() assert rsync.x == 1 - assert len(dest.listdir()) == 1 - assert len(source.listdir()) == 1 + assert len(list(dest.iterdir())) == 1 + assert len(list(source.iterdir())) == 1 diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 62038196..f71486e7 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,9 +1,10 @@ +import pathlib +import shutil import subprocess import sys import tempfile import execnet -import py import pytest MINOR_VERSIONS = {"3": "543210", "2": "76"} @@ -14,16 +15,16 @@ def setup_module(mod): - mod.TEMPDIR = py.path.local(tempfile.mkdtemp()) - mod._py3_wrapper = PythonWrapper(py.path.local(sys.executable)) + mod.TEMPDIR = pathlib.Path(tempfile.mkdtemp()) + mod._py3_wrapper = PythonWrapper(pathlib.Path(sys.executable)) def teardown_module(mod): - TEMPDIR.remove(True) + shutil.rmtree(TEMPDIR) # we use the execnet folder in order to avoid triggering a missing apipkg -pyimportdir = str(py.path.local(execnet.__file__).dirpath()) +pyimportdir = str(pathlib.Path(execnet.__file__).parent) class PythonWrapper: @@ -31,8 +32,8 @@ def __init__(self, executable): self.executable = executable def dump(self, obj_rep): - script_file = TEMPDIR.join("dump.py") - script_file.write( + script_file = TEMPDIR.joinpath("dump.py") + script_file.write_text( """ import sys sys.path.insert(0, %r) @@ -52,14 +53,14 @@ def dump(self, obj_rep): stdout, stderr = popen.communicate() ret = popen.returncode if ret: - raise py.process.cmdexec.Error( - ret, ret, str(self.executable), stdout, stderr + raise Exception( + "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) ) return stdout def load(self, data, option_args="__class__"): - script_file = TEMPDIR.join("load.py") - script_file.write( + script_file = TEMPDIR.joinpath("load.py") + script_file.write_text( r""" import sys sys.path.insert(0, %r) @@ -81,8 +82,8 @@ def load(self, data, option_args="__class__"): stdout, stderr = popen.communicate(data) ret = popen.returncode if ret: - raise py.process.cmdexec.Error( - ret, ret, str(self.executable), stdout, stderr + raise Exception( + "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) ) return [s.decode("ascii") for s in stdout.splitlines()] diff --git a/testing/test_termination.py b/testing/test_termination.py index f0e484d1..0df29a76 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -1,12 +1,15 @@ +import os +import pathlib +import shutil +import signal import subprocess import sys import execnet -import py import pytest from test_gateway import TESTTIMEOUT -execnetdir = py.path.local(execnet.__file__).dirpath().dirpath() +execnetdir = pathlib.Path(execnet.__file__).parent.parent skip_win_pypy = pytest.mark.xfail( condition=hasattr(sys, "pypy_version_info") and sys.platform.startswith("win"), @@ -45,7 +48,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): """ ) pid = channel.receive() - py.process.kill(pid) + os.kill(pid, signal.SIGTERM) channel.setcallback(q.put, endmarker=999) val = q.get(TESTTIMEOUT) assert val == 999 @@ -55,7 +58,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): @skip_win_pypy def test_termination_on_remote_channel_receive(monkeypatch, makegateway): - if not py.path.local.sysfind("ps"): + if not shutil.which("ps"): pytest.skip("need 'ps' command to externally check process status") monkeypatch.setenv("EXECNET_DEBUG", "2") gw = makegateway("popen") @@ -63,9 +66,10 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): gw.remote_exec("channel.receive()") gw._group.terminate() command = ["ps", "-p", str(pid)] - popen = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + popen = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True + ) out, err = popen.communicate() - out = py.builtin._totext(out, "utf8") assert str(pid) not in out, out diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 2e537b57..75df627e 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -6,11 +6,11 @@ from execnet.gateway_base import WorkerPool -def test_execmodel(execmodel, tmpdir): +def test_execmodel(execmodel, tmp_path): assert execmodel.backend - p = tmpdir.join("somefile") - p.write("content") - fd = os.open(str(p), os.O_RDONLY) + p = tmp_path / "somefile" + p.write_text("content") + fd = os.open(p, os.O_RDONLY) f = execmodel.fdopen(fd, "r") assert f.read() == "content" f.close() @@ -142,9 +142,8 @@ def f(): assert pool.waitall(timeout=0.1) -@pytest.mark.skipif("not hasattr(os, 'dup')") -def test_pool_clean_shutdown(pool): - capture = py.io.StdCaptureFD() +@pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") +def test_pool_clean_shutdown(pool, capfd): q = pool.execmodel.queue.Queue() def f(): @@ -162,9 +161,7 @@ def wait_then_put(): pool.execmodel.start(wait_then_put) assert pool.waitall() - out, err = capture.reset() - sys.stdout.write(out + "\n") - sys.stderr.write(err + "\n") + out, err = capfd.readouterr() assert err == "" diff --git a/testing/test_xspec.py b/testing/test_xspec.py index a75e5a1c..2218e736 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -6,7 +6,6 @@ import sys import execnet -import py import pytest from execnet.gateway_io import popen_args from execnet.gateway_io import ssh_args @@ -174,17 +173,17 @@ def test_popen_explicit(self, makegateway): assert rinfo.version_info == sys.version_info @skip_win_pypy - def test_popen_chdir_absolute(self, testdir, makegateway): - gw = makegateway("popen//chdir=%s" % testdir.tmpdir) + def test_popen_chdir_absolute(self, tmp_path, makegateway): + gw = makegateway("popen//chdir=%s" % tmp_path) rinfo = gw._rinfo() - assert rinfo.cwd == str(testdir.tmpdir.realpath()) + assert rinfo.cwd == str(tmp_path.resolve()) @skip_win_pypy - def test_popen_chdir_newsub(self, testdir, makegateway): - testdir.chdir() + def test_popen_chdir_newsub(self, monkeypatch, tmp_path, makegateway): + monkeypatch.chdir(tmp_path) gw = makegateway("popen//chdir=hello") rinfo = gw._rinfo() - expected = str(testdir.tmpdir.join("hello").realpath()).lower() + expected = str(tmp_path.joinpath("hello").resolve()).lower() assert rinfo.cwd.lower() == expected def test_ssh(self, specssh, makegateway): diff --git a/tox.ini b/tox.ini index f4f785bf..131b80c9 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,6 @@ envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true [testenv] deps= - py pytest pytest-timeout passenv = GITHUB_ACTIONS From 7b4510eb730a015a9e951b1fe0082e19fb6d26a3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 26 Apr 2023 10:10:53 +0300 Subject: [PATCH 036/275] ci: tags patterns are missing a `v` --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cb6bf70..c59fc200 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,8 +6,8 @@ on: - "master" - "test-me-*" tags: - - "[0-9]+.[0-9]+.[0-9]+" - - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+" pull_request: branches: From 56a507d7d870b34c02f012e759dafae056266256 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:44:29 +0300 Subject: [PATCH 037/275] pre-commit: remove no longer needed exclude --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93c6ccd7..cc1e978f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,3 @@ -exclude: doc/en/example/py2py3/test_py2.py repos: - repo: https://github.com/codespell-project/codespell rev: v2.2.4 From 4e039f32804c2e8eca1446345f4afb6c8da3d9c9 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:47:26 +0300 Subject: [PATCH 038/275] pyproject.toml: set mypy python version to 3.7 --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 78819678..218fac81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,3 +55,7 @@ version-file = "execnet/_version.py" include = [ "/execnet", ] + + +[tool.mypy] +python_version = "3.7" From 8ff949e9e595ed3bac3ed6ee3e547d9ec6e73a28 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:53:50 +0300 Subject: [PATCH 039/275] tox: pass more envs and set usedevelop=true --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 131b80c9..11f19831 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,11 @@ envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true [testenv] +usedevelop=true deps= pytest pytest-timeout -passenv = GITHUB_ACTIONS +passenv = GITHUB_ACTIONS, HOME, USER, XDG_* commands= python -m pytest {posargs:testing} From 44fd96e9a858fca4522cfcd7430e91ef95671ac9 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:46:06 +0300 Subject: [PATCH 040/275] doc/example/sysinfo: port to Python 3 --- doc/example/sysinfo.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index c88517d7..ce3bb9ce 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -6,7 +6,6 @@ (c) Holger Krekel, MIT license """ import optparse -import pathlib import re import sys @@ -34,12 +33,12 @@ def parsehosts(path): - path = pathlib.Path(path) + host_regex = re.compile(r"Host\s*(\S+)") l = [] - rex = re.compile(r"Host\s*(\S+)") - with path.open() as f: - for line in f: - m = rex.match(line) + + with open(path) as fp: + for line in fp: + m = host_regex.match(line) if m is not None: (sshname,) = m.groups() l.append(sshname) @@ -119,7 +118,7 @@ def getcpuinfo(self): def debug(*args): - print >> sys.stderr, " ".join(map(str, args)) + print(" ".join(map(str, args)), file=sys.stderr) def error(*args): @@ -140,7 +139,7 @@ def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): ri = RemoteInfo(gw) # print "%s info:" % sshname prefix = sshname.upper() + " " - print >> loginfo, prefix, "fqdn:", ri.getfqdn() + print(prefix, "fqdn:", ri.getfqdn(), file=loginfo) for attr in ("sys.platform", "sys.version_info"): loginfo.write(f"{prefix} {attr}: ") loginfo.flush() @@ -151,12 +150,12 @@ def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): memswap = ri.getmemswap() if memswap: mem, swap = memswap - print >> loginfo, prefix, "Memory:", mem, "Swap:", swap + print(prefix, "Memory:", mem, "Swap:", swap, file=loginfo) cpuinfo = ri.getcpuinfo() if cpuinfo: numcpu, model = cpuinfo - print >> loginfo, prefix, "number of cpus:", numcpu - print >> loginfo, prefix, "cpu model", model + print(prefix, "number of cpus:", numcpu, file=loginfo) + print(prefix, "cpu model", model, file=loginfo) return ri From 844b3da05ffe1e5c5b06cc8731fb4def94546a8b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:46:39 +0300 Subject: [PATCH 041/275] gateway_io: remove Python 2 compat code --- execnet/gateway_io.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index 1e49543f..84352655 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -32,29 +32,12 @@ def kill(self): def killpopen(popen): try: - if hasattr(popen, "kill"): - popen.kill() - else: - killpid(popen.pid) - except OSError: - sys.stderr.write("ERROR killing: %s\n" % (sys.exc_info()[1])) + popen.kill() + except OSError as e: + sys.stderr.write("ERROR killing: %s\n" % e) sys.stderr.flush() -def killpid(pid): - if hasattr(os, "kill"): - os.kill(pid, 15) - elif sys.platform == "win32" or getattr(os, "_name", None) == "nt": - import ctypes - - PROCESS_TERMINATE = 1 - handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, False, pid) - ctypes.windll.kernel32.TerminateProcess(handle, -1) - ctypes.windll.kernel32.CloseHandle(handle) - else: - raise OSError(f"no method to kill {pid}") - - popen_bootstrapline = "import sys;exec(eval(sys.stdin.readline()))" @@ -72,10 +55,8 @@ def shell_split_path(path): def popen_args(spec): args = shell_split_path(spec.python) if spec.python else [sys.executable] args.append("-u") - if spec is not None and spec.dont_write_bytecode: + if spec.dont_write_bytecode: args.append("-B") - # Slight gymnastics in ordering these arguments because CPython (as of - # 2.7.1) ignores -B if you provide `python -c "something" -B` args.extend(["-c", popen_bootstrapline]) return args From 35e03eaf081c8c4178f0c9814cec43e40e37cdb3 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 26 Apr 2023 10:38:33 +0300 Subject: [PATCH 042/275] testing: some cleanups ran: cherry-picked and adapted slightly. --- testing/conftest.py | 49 +++----- testing/test_basics.py | 223 ++++++++++++++++++------------------ testing/test_rsync.py | 20 ++-- testing/test_serializer.py | 96 ++++++---------- testing/test_termination.py | 7 +- testing/test_threadpool.py | 1 - 6 files changed, 178 insertions(+), 218 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index 4ea857a9..066df23a 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,7 +1,8 @@ -import pathlib import shutil -import subprocess import sys +from functools import lru_cache +from typing import Callable +from typing import Iterator import execnet import pytest @@ -23,10 +24,15 @@ def pytest_runtest_setup(item): @pytest.fixture -def makegateway(request): +def group_function() -> Iterator[execnet.Group]: group = execnet.Group() - request.addfinalizer(lambda: group.terminate(0.5)) - return group.makegateway + yield group + group.terminate(0.5) + + +@pytest.fixture +def makegateway(group_function) -> Callable[[str], execnet.gateway.Gateway]: + return group_function.makegateway pytest_plugins = ["pytester", "doctest"] @@ -101,37 +107,16 @@ def pytest_generate_tests(metafunc): else: gwtypes = ["popen", "socket", "ssh", "proxy"] metafunc.parametrize("gw", gwtypes, indirect=True) - elif "anypython" in metafunc.fixturenames: - metafunc.parametrize( - "anypython", - indirect=True, - argvalues=("sys.executable", "pypy3"), - ) -def getexecutable(name, cache={}): - try: - return cache[name] - except KeyError: - if name == "sys.executable": - return pathlib.Path(sys.executable) - path = shutil.which(name) - executable = pathlib.Path(path) if path is not None else None - if executable: - if name == "jython": - popen = subprocess.Popen( - [str(executable), "--version"], - universal_newlines=True, - stderr=subprocess.PIPE, - ) - out, err = popen.communicate() - if not err or "2.5" not in err: - executable = None - cache[name] = executable - return executable +@lru_cache() +def getexecutable(name): + if name == "sys.executable": + return sys.executable + return shutil.which(name) -@pytest.fixture +@pytest.fixture(params=("sys.executable", "pypy3")) def anypython(request): name = request.param executable = getexecutable(name) diff --git a/testing/test_basics.py b/testing/test_basics.py index 141f7d3f..5820c492 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -1,9 +1,14 @@ +from __future__ import annotations + import inspect import os import subprocess import sys import textwrap +from dataclasses import dataclass from io import BytesIO +from pathlib import Path +from typing import Any import execnet import pytest @@ -21,9 +26,8 @@ ) +@pytest.mark.parametrize("val", ["123", 42, [1, 2, 3], ["23", 25]]) class TestSerializeAPI: - pytestmark = [pytest.mark.parametrize("val", ["123", 42, [1, 2, 3], ["23", 25]])] - def test_serializer_api(self, val): dumped = execnet.dumps(val) val2 = execnet.loads(dumped) @@ -31,9 +35,9 @@ def test_serializer_api(self, val): def test_mmap(self, tmp_path, val): mmap = pytest.importorskip("mmap").mmap - p = tmp_path / "data" - with p.open("wb") as f: - f.write(execnet.dumps(val)) + p = tmp_path / "data.bin" + + p.write_bytes(execnet.dumps(val)) with p.open("r+b") as f: m = mmap(f.fileno(), 0) val2 = execnet.load(m) @@ -112,67 +116,83 @@ def read_write_loop(): break -def test_io_message(anypython, tmp_path, execmodel): - check = tmp_path / "check.py" - check.write_text( - inspect.getsource(gateway_base) - + textwrap.dedent( - """ - from io import BytesIO - import tempfile - temp_out = BytesIO() - temp_in = BytesIO() - io = Popen2IO(temp_out, temp_in, get_execmodel({backend!r})) - for i, handler in enumerate(Message._types): - print ("checking %s %s" %(i, handler)) - for data in "hello", "hello".encode('ascii'): - msg1 = Message(i, i, dumps(data)) - msg1.to_io(io) - x = io.outfile.getvalue() - io.outfile.truncate(0) - io.outfile.seek(0) - io.infile.seek(0) - io.infile.write(x) - io.infile.seek(0) - msg2 = Message.from_io(io) - assert msg1.channelid == msg2.channelid, (msg1, msg2) - assert msg1.data == msg2.data, (msg1.data, msg2.data) - assert msg1.msgcode == msg2.msgcode - print ("all passed") - """.format( - backend=execmodel.backend - ), +IO_MESSAGE_EXTRA_SOURCE = """ +import sys +backend = sys.argv[1] +try: + from io import BytesIO +except ImportError: + from StringIO import StringIO as BytesIO +import tempfile +temp_out = BytesIO() +temp_in = BytesIO() +io = Popen2IO(temp_out, temp_in, get_execmodel(backend)) +for i, handler in enumerate(Message._types): + print ("checking", i, handler) + for data in "hello", "hello".encode('ascii'): + msg1 = Message(i, i, dumps(data)) + msg1.to_io(io) + x = io.outfile.getvalue() + io.outfile.truncate(0) + io.outfile.seek(0) + io.infile.seek(0) + io.infile.write(x) + io.infile.seek(0) + msg2 = Message.from_io(io) + assert msg1.channelid == msg2.channelid, (msg1, msg2) + assert msg1.data == msg2.data, (msg1.data, msg2.data) + assert msg1.msgcode == msg2.msgcode +print ("all passed") +""" + + +@dataclass +class Checker: + python: str + path: Path + idx: int = 0 + + def run_check( + self, script: str, *extra_args: str, **process_args: Any + ) -> subprocess.CompletedProcess[str]: + self.idx += 1 + check_path = self.path / f"check{self.idx}.py" + check_path.write_text(script) + return subprocess.run( + [self.python, os.fspath(check_path), *extra_args], + capture_output=True, + text=True, + check=True, + **process_args, ) + + +@pytest.fixture +def checker(anypython: str, tmp_path: Path) -> Checker: + return Checker(python=anypython, path=tmp_path) + + +def test_io_message(checker, execmodel): + out = checker.run_check( + inspect.getsource(gateway_base) + IO_MESSAGE_EXTRA_SOURCE, execmodel.backend ) - out = subprocess.run( - [str(anypython), str(check)], text=True, capture_output=True, check=True - ).stdout - print(out) - assert "all passed" in out + print(out.stdout) + assert "all passed" in out.stdout -def test_popen_io(anypython, tmp_path, execmodel): - check = tmp_path / "check.py" - check.write_text( +def test_popen_io(checker, execmodel): + out = checker.run_check( inspect.getsource(gateway_base) - + textwrap.dedent( - f""" - io = init_popen_io(get_execmodel({execmodel.backend!r})) - io.write("hello".encode('ascii')) - s = io.read(1) - assert s == "x".encode('ascii') - """ - ), + + f""" +io = init_popen_io(get_execmodel({execmodel.backend!r})) +io.write(b"hello") +s = io.read(1) +assert s == b"x" +""", + input="x", ) - from subprocess import Popen, PIPE - - args = [str(anypython), str(check)] - proc = Popen(args, stdin=PIPE, stdout=PIPE, stderr=PIPE) - proc.stdin.write(b"x") - stdout, stderr = proc.communicate() - print(stderr) - proc.wait() - assert b"hello" in stdout + print(out.stderr) + assert "hello" in out.stdout def test_popen_io_readloop(monkeypatch, execmodel): @@ -190,60 +210,45 @@ def newread(numbytes): assert result == b"tes" -def test_rinfo_source(anypython, tmp_path): - check = tmp_path / "check.py" - check.write_text( - textwrap.dedent( - """ - class Channel: - def send(self, data): - assert eval(repr(data), {}) == data - channel = Channel() - """ - ) - + inspect.getsource(gateway.rinfo_source) - + textwrap.dedent( - """ - print ('all passed') - """ - ) +def test_rinfo_source(checker): + out = checker.run_check( + f""" +class Channel: + def send(self, data): + assert eval(repr(data), {{}}) == data +channel = Channel() +{inspect.getsource(gateway.rinfo_source)} +print ('all passed') +""" ) - out = subprocess.run( - [str(anypython), str(check)], text=True, capture_output=True, check=True - ).stdout - print(out) - assert "all passed" in out + print(out.stdout) + assert "all passed" in out.stdout -def test_geterrortext(anypython, tmp_path): - check = tmp_path / "check.py" - check.write_text( + +def test_geterrortext(checker): + out = checker.run_check( inspect.getsource(gateway_base) - + textwrap.dedent( - """ - class Arg: - pass - errortext = geterrortext((Arg, "1", 4)) - assert "Arg" in errortext - import sys - try: - raise ValueError("17") - except ValueError: - excinfo = sys.exc_info() - s = geterrortext(excinfo) - assert "17" in s - print ("all passed") + + """ +class Arg: + pass +errortext = geterrortext((Arg, "1", 4)) +assert "Arg" in errortext +import sys +try: + raise ValueError("17") +except ValueError: + excinfo = sys.exc_info() + s = geterrortext(excinfo) + assert "17" in s + print ("all passed") """ - ) ) - out = subprocess.run( - [str(anypython), str(check)], text=True, capture_output=True, check=True - ).stdout - print(out) - assert "all passed" in out + print(out.stdout) + assert "all passed" in out.stdout -@pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") +@pytest.mark.skipif("not hasattr(os, 'dup')") def test_stdouterrin_setnull(execmodel, capfd): gateway_base.init_popen_io(execmodel) os.write(1, b"hello") @@ -297,15 +302,15 @@ def test_wire_protocol(self): class TestPureChannel: @pytest.fixture def fac(self, execmodel): - class Gateway: + class FakeGateway: def _trace(self, *args): pass def _send(self, *k): pass - Gateway.execmodel = execmodel - return ChannelFactory(Gateway()) + FakeGateway.execmodel = execmodel + return ChannelFactory(FakeGateway()) def test_factory_create(self, fac): chan1 = fac.new() diff --git a/testing/test_rsync.py b/testing/test_rsync.py index b8d39f86..fefd9a06 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -2,6 +2,7 @@ import pathlib import platform import sys +import types import execnet import pytest @@ -36,19 +37,22 @@ def gw2(request, group): ) -@pytest.fixture -def dirs(request, tmp_path): - t = tmp_path +class _dirs(types.SimpleNamespace): + source: pathlib.Path + dest1: pathlib.Path + dest2: pathlib.Path - class dirs: - source = t / "source" - dest1 = t / "dest1" - dest2 = t / "dest2" +@pytest.fixture +def dirs(request, tmp_path) -> _dirs: + dirs = _dirs( + source=tmp_path / "source", + dest1=tmp_path / "dest1", + dest2=tmp_path / "dest2", + ) dirs.source.mkdir() dirs.dest1.mkdir() dirs.dest2.mkdir() - return dirs diff --git a/testing/test_serializer.py b/testing/test_serializer.py index f71486e7..fed4a5de 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,99 +1,69 @@ -import pathlib -import shutil +import os import subprocess import sys -import tempfile +from pathlib import Path import execnet import pytest -MINOR_VERSIONS = {"3": "543210", "2": "76"} - -TEMPDIR = None -_py3_wrapper = None - - -def setup_module(mod): - mod.TEMPDIR = pathlib.Path(tempfile.mkdtemp()) - mod._py3_wrapper = PythonWrapper(pathlib.Path(sys.executable)) - - -def teardown_module(mod): - shutil.rmtree(TEMPDIR) - - -# we use the execnet folder in order to avoid triggering a missing apipkg -pyimportdir = str(pathlib.Path(execnet.__file__).parent) +# We use the execnet folder in order to avoid triggering a missing apipkg. +pyimportdir = os.fspath(Path(execnet.__file__).parent) class PythonWrapper: - def __init__(self, executable): + def __init__(self, executable, tmp_path): self.executable = executable + self.tmp_path = tmp_path - def dump(self, obj_rep): - script_file = TEMPDIR.joinpath("dump.py") + def dump(self, obj_rep: str) -> bytes: + script_file = self.tmp_path.joinpath("dump.py") script_file.write_text( - """ + f""" import sys -sys.path.insert(0, %r) +sys.path.insert(0, {pyimportdir!r}) import gateway_base as serializer -# Need binary output sys.stdout = sys.stdout.detach() -sys.stdout.write(serializer.dumps_internal(%s)) +sys.stdout.write(serializer.dumps_internal({obj_rep})) """ - % (pyimportdir, obj_rep) ) - popen = subprocess.Popen( - [str(self.executable), str(script_file)], - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, + res = subprocess.run( + [str(self.executable), str(script_file)], capture_output=True, check=True ) - stdout, stderr = popen.communicate() - ret = popen.returncode - if ret: - raise Exception( - "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) - ) - return stdout - - def load(self, data, option_args="__class__"): - script_file = TEMPDIR.joinpath("load.py") + return res.stdout + + def load(self, data: bytes): + script_file = self.tmp_path.joinpath("load.py") script_file.write_text( - r""" + rf""" import sys -sys.path.insert(0, %r) +sys.path.insert(0, {pyimportdir!r}) import gateway_base as serializer -sys.stdin = sys.stdin.detach() -loader = serializer.Unserializer(sys.stdin) -loader.%s +from io import BytesIO +data = {data!r} +io = BytesIO(data) +loader = serializer.Unserializer(io) obj = loader.load() sys.stdout.write(type(obj).__name__ + "\n") -sys.stdout.write(repr(obj))""" - % (pyimportdir, option_args) +sys.stdout.write(repr(obj)) +""" ) - popen = subprocess.Popen( + res = subprocess.run( [str(self.executable), str(script_file)], - stdin=subprocess.PIPE, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, + capture_output=True, ) - stdout, stderr = popen.communicate(data) - ret = popen.returncode - if ret: - raise Exception( - "ExecutionFailed: %d %s\n%s" % (ret, self.executable, stderr) - ) - return [s.decode("ascii") for s in stdout.splitlines()] + if res.returncode: + raise ValueError(res.stderr) + + return res.stdout.decode("ascii").splitlines() def __repr__(self): return f"" @pytest.fixture -def py3(request): - return _py3_wrapper +def py3(request, tmp_path): + return PythonWrapper(sys.executable, tmp_path) @pytest.fixture diff --git a/testing/test_termination.py b/testing/test_termination.py index 0df29a76..282c1979 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -66,11 +66,8 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): gw.remote_exec("channel.receive()") gw._group.terminate() command = ["ps", "-p", str(pid)] - popen = subprocess.Popen( - command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True - ) - out, err = popen.communicate() - assert str(pid) not in out, out + output = subprocess.run(command, capture_output=True, text=True) + assert str(pid) not in output.stdout, output def test_close_initiating_remote_no_error(testdir, anypython): diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 75df627e..47a226d0 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -1,7 +1,6 @@ import os import sys -import py import pytest from execnet.gateway_base import WorkerPool From a2dc98a35ede7d98f776bead1f1f4677e5608ae6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 12:47:07 +0300 Subject: [PATCH 043/275] gateway_base: remove _setupmessages() to allow for static analysis --- execnet/gateway_base.py | 85 ++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index fd3c7f63..3c3f1533 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -388,7 +388,8 @@ def close_write(self): class Message: """encapsulates Messages and their wire protocol.""" - _types: list[Callable[[Message, BaseGateway], None]] = [] + # message code -> name, handler + _types: dict[int, tuple[str, Callable[[Message, BaseGateway], None]]] = {} def __init__(self, msgcode, channelid=0, data=""): self.msgcode = msgcode @@ -412,21 +413,16 @@ def to_io(self, io): io.write(header + self.data) def received(self, gateway): - self._types[self.msgcode](self, gateway) + handler = self._types[self.msgcode][1] + handler(self, gateway) def __repr__(self): - name = self._types[self.msgcode].__name__.upper() + name = self._types[self.msgcode][0] return "".format( name, self.channelid, len(self.data) ) - -class GatewayReceivedTerminate(Exception): - """Receiverthread got termination message.""" - - -def _setupmessages(): - def status(message, gateway): + def _status(message, gateway): # we use the channelid to send back information # but don't instantiate a channel object d = { @@ -437,49 +433,60 @@ def status(message, gateway): gateway._send(Message.CHANNEL_DATA, message.channelid, dumps_internal(d)) gateway._send(Message.CHANNEL_CLOSE, message.channelid) - def channel_exec(message, gateway): + STATUS = 0 + _types[STATUS] = ("STATUS", _status) + + def _reconfigure(message, gateway): + if message.channelid == 0: + target = gateway + else: + target = gateway._channelfactory.new(message.channelid) + target._strconfig = loads_internal(message.data, gateway) + + RECONFIGURE = 1 + _types[RECONFIGURE] = ("RECONFIGURE", _reconfigure) + + def _gateway_terminate(message, gateway): + raise GatewayReceivedTerminate(gateway) + + GATEWAY_TERMINATE = 2 + _types[GATEWAY_TERMINATE] = ("GATEWAY_TERMINATE", _gateway_terminate) + + def _channel_exec(message, gateway): channel = gateway._channelfactory.new(message.channelid) gateway._local_schedulexec(channel=channel, sourcetask=message.data) - def channel_data(message, gateway): + CHANNEL_EXEC = 3 + _types[CHANNEL_EXEC] = ("CHANNEL_EXEC", _channel_exec) + + def _channel_data(message, gateway): gateway._channelfactory._local_receive(message.channelid, message.data) - def channel_close(message, gateway): + CHANNEL_DATA = 4 + _types[CHANNEL_DATA] = ("CHANNEL_DATA", _channel_data) + + def _channel_close(message, gateway): gateway._channelfactory._local_close(message.channelid) - def channel_close_error(message, gateway): + CHANNEL_CLOSE = 5 + _types[CHANNEL_CLOSE] = ("CHANNEL_CLOSE", _channel_close) + + def _channel_close_error(message, gateway): remote_error = RemoteError(loads_internal(message.data)) gateway._channelfactory._local_close(message.channelid, remote_error) - def channel_last_message(message, gateway): + CHANNEL_CLOSE_ERROR = 6 + _types[CHANNEL_CLOSE_ERROR] = ("CHANNEL_CLOSE_ERROR", _channel_close_error) + + def _channel_last_message(message, gateway): gateway._channelfactory._local_close(message.channelid, sendonly=True) - def gateway_terminate(message, gateway): - raise GatewayReceivedTerminate(gateway) + CHANNEL_LAST_MESSAGE = 7 + _types[CHANNEL_LAST_MESSAGE] = ("CHANNEL_LAST_MESSAGE", _channel_last_message) - def reconfigure(message, gateway): - if message.channelid == 0: - target = gateway - else: - target = gateway._channelfactory.new(message.channelid) - target._strconfig = loads_internal(message.data, gateway) - types = [ - status, - reconfigure, - gateway_terminate, - channel_exec, - channel_data, - channel_close, - channel_close_error, - channel_last_message, - ] - for i, handler in enumerate(types): - Message._types.append(handler) - setattr(Message, handler.__name__.upper(), i) - - -_setupmessages() +class GatewayReceivedTerminate(Exception): + """Receiverthread got termination message.""" def geterrortext(excinfo, format_exception=traceback.format_exception, sysex=sysex): From 9324d193799b33dc9372e47c9e2ac9dac3a26c55 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 18:34:30 +0300 Subject: [PATCH 044/275] gateway_base: remove _buildopcodes() to allow for static analysis --- execnet/gateway_base.py | 94 +++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 28 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 3c3f1533..1852536f 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -1107,10 +1107,34 @@ class _Stop(Exception): pass +class opcode: + """container for name -> num mappings.""" + + BUILDTUPLE = b"@" + BYTES = b"A" + CHANNEL = b"B" + FALSE = b"C" + FLOAT = b"D" + FROZENSET = b"E" + INT = b"F" + LONG = b"G" + LONGINT = b"H" + LONGLONG = b"I" + NEWDICT = b"J" + NEWLIST = b"K" + NONE = b"L" + PY2STRING = b"M" + PY3STRING = b"N" + SET = b"O" + SETITEM = b"P" + STOP = b"Q" + TRUE = b"R" + UNICODE = b"S" + COMPLEX = b"T" + + class Unserializer: - num2func: dict[ - int, Callable[[Unserializer], None] - ] = {} # is filled after this class definition + num2func: dict[bytes, Callable[[Unserializer], None]] = {} py2str_as_py3str = True # True py3str_as_py2str = False # false means py2 will get unicode @@ -1150,31 +1174,47 @@ def load(self, versioned=False): def load_none(self): self.stack.append(None) + num2func[opcode.NONE] = load_none + def load_true(self): self.stack.append(True) + num2func[opcode.TRUE] = load_true + def load_false(self): self.stack.append(False) + num2func[opcode.FALSE] = load_false + def load_int(self): i = self._read_int4() self.stack.append(i) + num2func[opcode.INT] = load_int + def load_longint(self): s = self._read_byte_string() self.stack.append(int(s)) + num2func[opcode.LONGINT] = load_longint + load_long = load_int + num2func[opcode.LONG] = load_long load_longlong = load_longint + num2func[opcode.LONGLONG] = load_longlong def load_float(self): binary = self.stream.read(FLOAT_FORMAT_SIZE) self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0]) + num2func[opcode.FLOAT] = load_float + def load_complex(self): binary = self.stream.read(COMPLEX_FORMAT_SIZE) self.stack.append(complex(*struct.unpack(COMPLEX_FORMAT, binary))) + num2func[opcode.COMPLEX] = load_complex + def _read_int4(self): return struct.unpack("!i", self.stream.read(4))[0] @@ -1191,6 +1231,8 @@ def load_py3string(self): else: self.stack.append(as_bytes.decode("utf-8")) + num2func[opcode.PY3STRING] = load_py3string + def load_py2string(self): as_bytes = self._read_byte_string() if self.py2str_as_py3str: @@ -1199,17 +1241,25 @@ def load_py2string(self): s = as_bytes self.stack.append(s) + num2func[opcode.PY2STRING] = load_py2string + def load_bytes(self): s = self._read_byte_string() self.stack.append(s) + num2func[opcode.BYTES] = load_bytes + def load_unicode(self): self.stack.append(self._read_byte_string().decode("utf-8")) + num2func[opcode.UNICODE] = load_unicode + def load_newlist(self): length = self._read_int4() self.stack.append([None] * length) + num2func[opcode.NEWLIST] = load_newlist + def load_setitem(self): if len(self.stack) < 3: raise LoadError("not enough items for setitem") @@ -1217,9 +1267,13 @@ def load_setitem(self): key = self.stack.pop() self.stack[-1][key] = value + num2func[opcode.SETITEM] = load_setitem + def load_newdict(self): self.stack.append({}) + num2func[opcode.NEWDICT] = load_newdict + def _load_collection(self, type_): length = self._read_int4() if length: @@ -1232,45 +1286,29 @@ def _load_collection(self, type_): def load_buildtuple(self): self._load_collection(tuple) + num2func[opcode.BUILDTUPLE] = load_buildtuple + def load_set(self): self._load_collection(set) + num2func[opcode.SET] = load_set + def load_frozenset(self): self._load_collection(frozenset) + num2func[opcode.FROZENSET] = load_frozenset + def load_stop(self): raise _Stop + num2func[opcode.STOP] = load_stop + def load_channel(self): id = self._read_int4() newchannel = self.channelfactory.new(id) self.stack.append(newchannel) - -# automatically build opcodes and byte-encoding - - -class opcode: - """container for name -> num mappings.""" - - -def _buildopcodes(): - l = [] - later_added = {"COMPLEX": 1} - for name, func in Unserializer.__dict__.items(): - if name.startswith("load_"): - opname = name[5:].upper() - l.append((opname, func)) - l.sort(key=lambda x: (later_added.get(x[0], 0), x[0])) - - for i, (opname, func) in enumerate(l): - assert i < 26, "xxx" - i = bchr(64 + i) - Unserializer.num2func[i] = func - setattr(opcode, opname, i) - - -_buildopcodes() + num2func[opcode.CHANNEL] = load_channel def dumps(obj): From 25c9b1d0db43b7c872ac33c6b5d678dbb9473925 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 00:12:39 +0300 Subject: [PATCH 045/275] Remove various no longer needed compat code --- execnet/gateway_base.py | 20 +++++--------------- execnet/gateway_socket.py | 5 ----- execnet/multi.py | 3 +-- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 1852536f..d3ab0a45 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -18,18 +18,11 @@ import sys import traceback import weakref +from _thread import interrupt_main from io import BytesIO from typing import Callable -def reraise(cls, val, tb): - raise val.with_traceback(tb) - - -unicode = str -_long_type = int -from _thread import interrupt_main - SUBPROCESS32 = False # f = open("/tmp/execnet-%s" % os.getpid(), "w") # def log_extra(*msg): @@ -45,11 +38,8 @@ def get_execmodel(backend): return backend if backend == "thread": importdef = { - "get_ident": ["thread::get_ident", "_thread::get_ident"], - "_start_new_thread": [ - "thread::start_new_thread", - "_thread::start_new_thread", - ], + "get_ident": ["_thread::get_ident"], + "_start_new_thread": ["_thread::start_new_thread"], "threading": ["threading"], "queue": ["queue"], "sleep": ["time::sleep"], @@ -178,7 +168,7 @@ def get(self, timeout=None): try: return self._result except AttributeError: - reraise(*(self._excinfo[:3])) # noqa + raise self._excinfo[1].with_traceback(self._excinfo[2]) def waitfinish(self, timeout=None): if not self._result_ready.wait(timeout): @@ -1385,7 +1375,7 @@ def save(self, obj, versioned=False): streamlist = self._streamlist except AttributeError: return None - return type(streamlist[0])().join(streamlist) + return b"".join(streamlist) def _save(self, obj): tp = type(obj) diff --git a/execnet/gateway_socket.py b/execnet/gateway_socket.py index 175112bc..4379e015 100644 --- a/execnet/gateway_socket.py +++ b/execnet/gateway_socket.py @@ -2,11 +2,6 @@ from execnet.gateway_bootstrap import HostNotFound -try: - bytes -except NameError: - bytes = str - class SocketIO: def __init__(self, sock, execmodel): diff --git a/execnet/multi.py b/execnet/multi.py index 838fd546..f7481cd0 100644 --- a/execnet/multi.py +++ b/execnet/multi.py @@ -11,7 +11,6 @@ from . import gateway_bootstrap from . import gateway_io from .gateway_base import get_execmodel -from .gateway_base import reraise from .gateway_base import trace from .xspec import XSpec @@ -289,7 +288,7 @@ def waitclose(self): if first is None: first = sys.exc_info() if first: - reraise(*first) + raise first[1].with_traceback(first[2]) def safe_terminate(execmodel, timeout, list_of_paired_functions): From e846c64a3f5da604c5a5b846e794b7cc6719b0c3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 00:40:59 +0300 Subject: [PATCH 046/275] gateway_base: fix incorrect type of Message(data) default --- execnet/gateway_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index d3ab0a45..e58ffe5f 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -381,7 +381,7 @@ class Message: # message code -> name, handler _types: dict[int, tuple[str, Callable[[Message, BaseGateway], None]]] = {} - def __init__(self, msgcode, channelid=0, data=""): + def __init__(self, msgcode, channelid=0, data=b""): self.msgcode = msgcode self.channelid = channelid self.data = data From da86c5d6a5a7f123b135153b1f4d4b2b6f374474 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 00:44:21 +0300 Subject: [PATCH 047/275] Replace bare `except`s with `except BaseException` --- execnet/gateway_base.py | 6 +++--- execnet/script/shell.py | 4 ++-- execnet/script/socketserver.py | 2 +- execnet/script/xx.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index e58ffe5f..c872e547 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -179,7 +179,7 @@ def run(self): try: try: self._result = func(*args, **kwargs) - except: + except BaseException: # sys may be already None when shutting down the interpreter if sys is not None: self._excinfo = sys.exc_info() @@ -485,7 +485,7 @@ def geterrortext(excinfo, format_exception=traceback.format_exception, sysex=sys errortext = "".join(l) except sysex: raise - except: + except BaseException: errortext = f"{excinfo[0].__name__}: {excinfo[1]}" return errortext @@ -1050,7 +1050,7 @@ def executetask(self, item): except KeyboardInterrupt: channel.close(INTERRUPT_TEXT) raise - except: + except BaseException: excinfo = self.exc_info() if not isinstance(excinfo[1], EOFError): if not channel.gateway._channelfactory.finished: diff --git a/execnet/script/shell.py b/execnet/script/shell.py index 4719b90a..a221756d 100755 --- a/execnet/script/shell.py +++ b/execnet/script/shell.py @@ -32,7 +32,7 @@ def clientside(): line = sock.recv(4096) sys.stdout.write(line) sys.stdout.flush() - except: + except BaseException: import traceback print(traceback.print_exc()) @@ -66,7 +66,7 @@ def run(self): try: try: exec(compile(line + "\n", "", "single")) - except: + except BaseException: print_exc() finally: sys.stdout = oldout diff --git a/execnet/script/socketserver.py b/execnet/script/socketserver.py index 6180bcd4..034b4346 100755 --- a/execnet/script/socketserver.py +++ b/execnet/script/socketserver.py @@ -94,7 +94,7 @@ def startserver(serversock, loop=False): exec_from_one_connection(serversock) except (KeyboardInterrupt, SystemExit): raise - except: + except BaseException: if debug: import traceback diff --git a/execnet/script/xx.py b/execnet/script/xx.py index fd514c13..687cc81e 100644 --- a/execnet/script/xx.py +++ b/execnet/script/xx.py @@ -7,6 +7,6 @@ try: hostport = sys.argv[1] -except: +except BaseException: hostport = ":8888" gw = register.ServerGateway(hostport) From a2aeb41bb984279b85ef5e2fc3c04e9619a263e8 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 19:07:46 +0300 Subject: [PATCH 048/275] Use super().__init__() --- execnet/gateway_base.py | 2 +- execnet/gateway_io.py | 2 +- execnet/script/shell.py | 2 +- execnet/script/socketserverservice.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index c872e547..69ca8051 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -494,8 +494,8 @@ class RemoteError(Exception): """Exception containing a stringified error from the other side.""" def __init__(self, formatted): + super().__init__() self.formatted = formatted - Exception.__init__(self) def __str__(self): return self.formatted diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index 84352655..ea0fc92d 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -18,7 +18,7 @@ class Popen2IOMaster(Popen2IO): def __init__(self, args, execmodel): self.popen = p = execmodel.PopenPiped(args) - Popen2IO.__init__(self, p.stdin, p.stdout, execmodel=execmodel) + super().__init__(p.stdin, p.stdout, execmodel=execmodel) def wait(self): try: diff --git a/execnet/script/shell.py b/execnet/script/shell.py index a221756d..f47cd4d3 100755 --- a/execnet/script/shell.py +++ b/execnet/script/shell.py @@ -43,7 +43,7 @@ def clientside(): class promptagent(Thread): def __init__(self, clientsock): print("server side starting") - Thread.__init__(self) + super.__init__() self.clientsock = clientsock def run(self): diff --git a/execnet/script/socketserverservice.py b/execnet/script/socketserverservice.py index c85f00fb..3d64f139 100644 --- a/execnet/script/socketserverservice.py +++ b/execnet/script/socketserverservice.py @@ -36,7 +36,7 @@ def __init__(self, args): win32evtlogutil.AddSourceToRegistry( self._svc_display_name_, servicemanager.__file__, "Application" ) - win32serviceutil.ServiceFramework.__init__(self, args) + super.__init__(args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) self.WAIT_TIME = 1000 # in milliseconds From 9572fa6053529a6e74e0ea1b38a1b0e40b862bc5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 01:11:36 +0300 Subject: [PATCH 049/275] multi: fix typo in assert The `id` here referred to the builtin `id` function, that's not the intention. --- execnet/multi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execnet/multi.py b/execnet/multi.py index f7481cd0..28489dbd 100644 --- a/execnet/multi.py +++ b/execnet/multi.py @@ -173,7 +173,7 @@ def allocate_id(self, spec): def _register(self, gateway): assert not hasattr(gateway, "_group") assert gateway.id - assert id not in self + assert gateway.id not in self self._gateways.append(gateway) gateway._group = self From 39e2ba903a29e7fbf8e38b62d2a9a6ab7f163106 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 19 Apr 2023 18:48:53 +0300 Subject: [PATCH 050/275] gateway_base: fix typing typo --- execnet/gateway_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 69ca8051..67011f53 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -1355,7 +1355,7 @@ def dumps_internal(obj): class _Serializer: - _dispatch = {object: Callable[["_Serializer", object], None]} + _dispatch: dict[type, Callable[[_Serializer, object], None]] = {} def __init__(self, write=None): if write is None: From 7ecb41fec3fc473d44d7380ed1e32ac2f320afac Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 20 Apr 2023 23:54:01 +0300 Subject: [PATCH 051/275] gateway_base: remove on longer needed importdef fallbacks --- execnet/gateway_base.py | 86 ++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 67011f53..66cb9c80 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -38,16 +38,16 @@ def get_execmodel(backend): return backend if backend == "thread": importdef = { - "get_ident": ["_thread::get_ident"], - "_start_new_thread": ["_thread::start_new_thread"], - "threading": ["threading"], - "queue": ["queue"], - "sleep": ["time::sleep"], - "subprocess": ["subprocess"], - "socket": ["socket"], - "_fdopen": ["os::fdopen"], - "_lock": ["threading"], - "_event": ["threading"], + "get_ident": "_thread::get_ident", + "_start_new_thread": "_thread::start_new_thread", + "threading": "threading", + "queue": "queue", + "sleep": "time::sleep", + "subprocess": "subprocess", + "socket": "socket", + "_fdopen": "os::fdopen", + "_lock": "threading", + "_event": "threading", } def exec_start(self, func, args=()): @@ -55,16 +55,16 @@ def exec_start(self, func, args=()): elif backend == "eventlet": importdef = { - "get_ident": ["eventlet.green.thread::get_ident"], - "_spawn_n": ["eventlet::spawn_n"], - "threading": ["eventlet.green.threading"], - "queue": ["eventlet.queue"], - "sleep": ["eventlet::sleep"], - "subprocess": ["eventlet.green.subprocess"], - "socket": ["eventlet.green.socket"], - "_fdopen": ["eventlet.green.os::fdopen"], - "_lock": ["eventlet.green.threading"], - "_event": ["eventlet.green.threading"], + "get_ident": "eventlet.green.thread::get_ident", + "_spawn_n": "eventlet::spawn_n", + "threading": "eventlet.green.threading", + "queue": "eventlet.queue", + "sleep": "eventlet::sleep", + "subprocess": "eventlet.green.subprocess", + "socket": "eventlet.green.socket", + "_fdopen": "eventlet.green.os::fdopen", + "_lock": "eventlet.green.threading", + "_event": "eventlet.green.threading", } def exec_start(self, func, args=()): @@ -72,17 +72,17 @@ def exec_start(self, func, args=()): elif backend == "gevent": importdef = { - "get_ident": ["gevent.thread::get_ident"], - "_spawn_n": ["gevent::spawn"], - "threading": ["threading"], - "queue": ["gevent.queue"], - "sleep": ["gevent::sleep"], - "subprocess": ["gevent.subprocess"], - "socket": ["gevent.socket"], + "get_ident": "gevent.thread::get_ident", + "_spawn_n": "gevent::spawn", + "threading": "threading", + "queue": "gevent.queue", + "sleep": "gevent::sleep", + "subprocess": "gevent.subprocess", + "socket": "gevent.socket", # XXX - "_fdopen": ["gevent.fileobject::FileObjectThread"], - "_lock": ["gevent.lock"], - "_event": ["gevent.event"], + "_fdopen": "gevent.fileobject::FileObjectThread", + "_lock": "gevent.lock", + "_event": "gevent.event", } def exec_start(self, func, args=()): @@ -101,21 +101,19 @@ def __repr__(self): return "" % self.backend def __getattr__(self, name): - locs = self._importdef.get(name) - if locs is None: + loc = self._importdef.get(name) + if loc is None: raise AttributeError(name) - for loc in locs: - parts = loc.split("::") - loc = parts.pop(0) - try: - mod = __import__(loc, None, None, "__doc__") - except ImportError: - pass - else: - if parts: - mod = getattr(mod, parts[0]) - setattr(self, name, mod) - return mod + parts = loc.split("::") + try: + mod = __import__(parts[0], None, None, "__doc__") + except ImportError: + pass + else: + if len(parts) > 1: + mod = getattr(mod, parts[1]) + setattr(self, name, mod) + return mod raise AttributeError(name) start = exec_start From e4f6090de02862fbb52291b16b3022df7ce3c433 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 10:02:42 +0300 Subject: [PATCH 052/275] gateway_base: remove `threading` from exec model The gevent one is wrong (should be `gevent.threading`), which made it clear it's not used at all. --- execnet/gateway_base.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 66cb9c80..ed951503 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -40,7 +40,6 @@ def get_execmodel(backend): importdef = { "get_ident": "_thread::get_ident", "_start_new_thread": "_thread::start_new_thread", - "threading": "threading", "queue": "queue", "sleep": "time::sleep", "subprocess": "subprocess", @@ -57,7 +56,6 @@ def exec_start(self, func, args=()): importdef = { "get_ident": "eventlet.green.thread::get_ident", "_spawn_n": "eventlet::spawn_n", - "threading": "eventlet.green.threading", "queue": "eventlet.queue", "sleep": "eventlet::sleep", "subprocess": "eventlet.green.subprocess", @@ -74,7 +72,6 @@ def exec_start(self, func, args=()): importdef = { "get_ident": "gevent.thread::get_ident", "_spawn_n": "gevent::spawn", - "threading": "threading", "queue": "gevent.queue", "sleep": "gevent::sleep", "subprocess": "gevent.subprocess", From 6c56e88d48a34396e6a4aa0e96bc810ab07c8b8e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 10:08:34 +0300 Subject: [PATCH 053/275] gateway_base: remove unused ExecModel `_count` attribute --- execnet/gateway_base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index ed951503..d6fc9e28 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -92,7 +92,6 @@ class ExecModel: def __init__(self, name): self._importdef = importdef self.backend = name - self._count = 0 def __repr__(self): return "" % self.backend From c2b9e60afdb5975b27ad9648a6d05ab9d6e41859 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 00:28:17 +0300 Subject: [PATCH 054/275] gateway_base: remove execmodel.{WorkerPool,PopenPiped} `execmodel.WorkerPool` creates an unneeded dependency cycle. And it's not any better than the alternative really. `execmodel.PopenPiped` is a convenience that's not really needed at the abstraction layer, clearer to just inline it to the one place it's used. --- execnet/gateway_base.py | 11 ++--------- execnet/gateway_io.py | 3 ++- execnet/multi.py | 3 ++- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index d6fc9e28..c50e41b7 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -117,9 +117,6 @@ def __getattr__(self, name): def fdopen(self, fd, mode, bufsize=1): return self._fdopen(fd, mode, bufsize) - def WorkerPool(self, hasprimary=False): - return WorkerPool(self, hasprimary=hasprimary) - def Semaphore(self, size=None): if size is None: return EmptySemaphore() @@ -134,10 +131,6 @@ def RLock(self): def Event(self): return self._event.Event() - def PopenPiped(self, args): - PIPE = self.subprocess.PIPE - return self.subprocess.Popen(args, stdout=PIPE, stdin=PIPE) - return ExecModel(backend) @@ -909,7 +902,7 @@ def __init__(self, io, id, _startcount=2): # globals may be NONE at process-termination self.__trace = trace self._geterrortext = geterrortext - self._receivepool = self.execmodel.WorkerPool() + self._receivepool = WorkerPool(self.execmodel) def _trace(self, *msg): self.__trace(self.id, *msg) @@ -1011,7 +1004,7 @@ def trace(msg): self._trace("[serve] " + msg) hasprimary = self.execmodel.backend == "thread" - self._execpool = self.execmodel.WorkerPool(hasprimary=hasprimary) + self._execpool = WorkerPool(self.execmodel, hasprimary=hasprimary) trace("spawning receiver thread") self._initreceive() try: diff --git a/execnet/gateway_io.py b/execnet/gateway_io.py index ea0fc92d..c631f8d9 100644 --- a/execnet/gateway_io.py +++ b/execnet/gateway_io.py @@ -17,7 +17,8 @@ class Popen2IOMaster(Popen2IO): def __init__(self, args, execmodel): - self.popen = p = execmodel.PopenPiped(args) + PIPE = execmodel.subprocess.PIPE + self.popen = p = execmodel.subprocess.Popen(args, stdout=PIPE, stdin=PIPE) super().__init__(p.stdin, p.stdout, execmodel=execmodel) def wait(self): diff --git a/execnet/multi.py b/execnet/multi.py index 28489dbd..64e95017 100644 --- a/execnet/multi.py +++ b/execnet/multi.py @@ -12,6 +12,7 @@ from . import gateway_io from .gateway_base import get_execmodel from .gateway_base import trace +from .gateway_base import WorkerPool from .xspec import XSpec NO_ENDMARKER_WANTED = object() @@ -292,7 +293,7 @@ def waitclose(self): def safe_terminate(execmodel, timeout, list_of_paired_functions): - workerpool = execmodel.WorkerPool() + workerpool = WorkerPool(execmodel) def termkill(termfunc, killfunc): termreply = workerpool.spawn(termfunc) From fdf6b1e23aa14b14ca08119bfcbade5d87cb0846 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 10:53:04 +0300 Subject: [PATCH 055/275] gateway_base: remove Semaphore from execmodel Not used since 120f63962390fb0c43278da5023b77b82d37b335. --- execnet/gateway_base.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index c50e41b7..49f7e4a9 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -29,10 +29,6 @@ # f.write(" ".join([str(x) for x in msg]) + "\n") -class EmptySemaphore: - acquire = release = lambda self: None - - def get_execmodel(backend): if hasattr(backend, "backend"): return backend @@ -117,11 +113,6 @@ def __getattr__(self, name): def fdopen(self, fd, mode, bufsize=1): return self._fdopen(fd, mode, bufsize) - def Semaphore(self, size=None): - if size is None: - return EmptySemaphore() - return self._lock.Semaphore(size) - def Lock(self): return self._lock.RLock() From cdfcc2d4166309c582b1857e6c2eee30e7aaf6db Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 21 Apr 2023 10:25:16 +0300 Subject: [PATCH 056/275] gateway_base: refactor execmodel to avoid dynamic features Allow for some static analysis. --- execnet/gateway_base.py | 303 +++++++++++++++++++++++++++++----------- 1 file changed, 220 insertions(+), 83 deletions(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 49f7e4a9..5db185fb 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -13,6 +13,7 @@ """ from __future__ import annotations +import abc import os import struct import sys @@ -23,106 +24,242 @@ from typing import Callable -SUBPROCESS32 = False -# f = open("/tmp/execnet-%s" % os.getpid(), "w") -# def log_extra(*msg): -# f.write(" ".join([str(x) for x in msg]) + "\n") +class ExecModel(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def backend(self): + raise NotImplementedError() + def __repr__(self): + return "" % self.backend -def get_execmodel(backend): - if hasattr(backend, "backend"): - return backend - if backend == "thread": - importdef = { - "get_ident": "_thread::get_ident", - "_start_new_thread": "_thread::start_new_thread", - "queue": "queue", - "sleep": "time::sleep", - "subprocess": "subprocess", - "socket": "socket", - "_fdopen": "os::fdopen", - "_lock": "threading", - "_event": "threading", - } + @property + @abc.abstractmethod + def queue(self): + raise NotImplementedError() - def exec_start(self, func, args=()): - self._start_new_thread(func, args) + @property + @abc.abstractmethod + def subprocess(self): + raise NotImplementedError() - elif backend == "eventlet": - importdef = { - "get_ident": "eventlet.green.thread::get_ident", - "_spawn_n": "eventlet::spawn_n", - "queue": "eventlet.queue", - "sleep": "eventlet::sleep", - "subprocess": "eventlet.green.subprocess", - "socket": "eventlet.green.socket", - "_fdopen": "eventlet.green.os::fdopen", - "_lock": "eventlet.green.threading", - "_event": "eventlet.green.threading", - } + @property + @abc.abstractmethod + def socket(self): + raise NotImplementedError() - def exec_start(self, func, args=()): - self._spawn_n(func, *args) + @abc.abstractmethod + def start(self, func, args=()): + raise NotImplementedError() - elif backend == "gevent": - importdef = { - "get_ident": "gevent.thread::get_ident", - "_spawn_n": "gevent::spawn", - "queue": "gevent.queue", - "sleep": "gevent::sleep", - "subprocess": "gevent.subprocess", - "socket": "gevent.socket", - # XXX - "_fdopen": "gevent.fileobject::FileObjectThread", - "_lock": "gevent.lock", - "_event": "gevent.event", - } + @abc.abstractmethod + def get_ident(self): + raise NotImplementedError() - def exec_start(self, func, args=()): - self._spawn_n(func, *args) + @abc.abstractmethod + def sleep(self, delay): + raise NotImplementedError() - else: - raise ValueError(f"unknown execmodel {backend!r}") + @abc.abstractmethod + def fdopen(self, fd, mode, bufsize=1): + raise NotImplementedError() - class ExecModel: - def __init__(self, name): - self._importdef = importdef - self.backend = name + @abc.abstractmethod + def Lock(self): + raise NotImplementedError() - def __repr__(self): - return "" % self.backend + @abc.abstractmethod + def RLock(self): + raise NotImplementedError() - def __getattr__(self, name): - loc = self._importdef.get(name) - if loc is None: - raise AttributeError(name) - parts = loc.split("::") - try: - mod = __import__(parts[0], None, None, "__doc__") - except ImportError: - pass - else: - if len(parts) > 1: - mod = getattr(mod, parts[1]) - setattr(self, name, mod) - return mod - raise AttributeError(name) + @abc.abstractmethod + def Event(self): + raise NotImplementedError() + + +class ThreadExecModel(ExecModel): + backend = "thread" + + @property + def queue(self): + import queue + + return queue + + @property + def subprocess(self): + import subprocess + + return subprocess + + @property + def socket(self): + import socket + + return socket + + def get_ident(self): + import _thread + + return _thread.get_ident() + + def sleep(self, delay): + import time + + time.sleep(delay) + + def start(self, func, args=()): + import _thread + + return _thread.start_new_thread(func, args) + + def fdopen(self, fd, mode, bufsize=1): + import os + + return os.fdopen(fd, mode, bufsize) + + def Lock(self): + import threading + + return threading.RLock() + + def RLock(self): + import threading + + return threading.RLock() + + def Event(self): + import threading + + return threading.Event() + + +class EventletExecModel(ExecModel): + backend = "eventlet" + + @property + def queue(self): + import eventlet + + return eventlet.queue + + @property + def subprocess(self): + import eventlet.green.subprocess + + return eventlet.green.subprocess - start = exec_start + @property + def socket(self): + import eventlet.green.socket - def fdopen(self, fd, mode, bufsize=1): - return self._fdopen(fd, mode, bufsize) + return eventlet.green.socket - def Lock(self): - return self._lock.RLock() + def get_ident(self): + import eventlet.green.thread - def RLock(self): - return self._lock.RLock() + return eventlet.green.thread.get_ident() - def Event(self): - return self._event.Event() + def sleep(self, delay): + import eventlet - return ExecModel(backend) + eventlet.sleep(delay) + + def start(self, func, args=()): + import eventlet + + return eventlet.spawn_n(func, *args) + + def fdopen(self, fd, mode, bufsize=1): + import eventlet.green.os + + return eventlet.green.os.fdopen(fd, mode, bufsize) + + def Lock(self): + import eventlet.green.threading + + return eventlet.green.threading.RLock() + + def RLock(self): + import eventlet.green.threading + + return eventlet.green.threading.RLock() + + def Event(self): + import eventlet.green.threading + + return eventlet.green.threading.Event() + + +class GeventExecModel(ExecModel): + backend = "gevent" + + @property + def queue(self): + import gevent.queue + + return gevent.queue + + @property + def subprocess(self): + import gevent.subprocess + + return gevent.subprocess + + @property + def socket(self): + import gevent + + return gevent.socket + + def get_ident(self): + import gevent.thread + + return gevent.thread.get_ident() + + def sleep(self, delay): + import gevent + + gevent.sleep(delay) + + def start(self, func, args=()): + import gevent + + return gevent.spawn(func, *args) + + def fdopen(self, fd, mode, bufsize=1): + # XXX + import gevent.fileobject + + return gevent.fileobject.FileObjectThread(fd, mode, bufsize) + + def Lock(self): + import gevent.lock + + return gevent.lock.RLock() + + def RLock(self): + import gevent.lock + + return gevent.lock.RLock() + + def Event(self): + import gevent.event + + return gevent.event.Event() + + +def get_execmodel(backend): + if hasattr(backend, "backend"): + return backend + if backend == "thread": + return ThreadExecModel() + elif backend == "eventlet": + return EventletExecModel() + elif backend == "gevent": + return GeventExecModel() + else: + raise ValueError(f"unknown execmodel {backend!r}") class Reply: From 4707b7837aa9859cbe888f646d76926a0b1d304b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 May 2023 14:38:35 -0300 Subject: [PATCH 057/275] [pre-commit.ci] pre-commit autoupdate (#191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.3.2 → v3.4.0](https://github.com/asottile/pyupgrade/compare/v3.3.2...v3.4.0) - https://github.com/asottile/reorder_python_imports → https://github.com/asottile/reorder-python-imports Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc1e978f..954b7721 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,11 +19,11 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.3.2 + rev: v3.4.0 hooks: - id: pyupgrade args: [--py37-plus] -- repo: https://github.com/asottile/reorder_python_imports +- repo: https://github.com/asottile/reorder-python-imports rev: v3.9.0 hooks: - id: reorder-python-imports From f34350583cc2c88ad0b62972d044e376e134f8f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 May 2023 08:00:03 -0300 Subject: [PATCH 058/275] [pre-commit.ci] pre-commit autoupdate (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.2.0 → v1.3.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.2.0...v1.3.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 954b7721..4ea1c568 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.2.0' + rev: 'v1.3.0' hooks: - id: mypy From 6dabb0f8bb498d0007c82bee011be36313849bef Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 08:42:24 -0300 Subject: [PATCH 059/275] [pre-commit.ci] pre-commit autoupdate (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.4.0 → v3.6.0](https://github.com/asottile/pyupgrade/compare/v3.4.0...v3.6.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4ea1c568..7f66caed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.4.0 + rev: v3.6.0 hooks: - id: pyupgrade args: [--py37-plus] From fd392fa3cf41f4aed7b342370aea2de277dfad2e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 07:55:54 -0300 Subject: [PATCH 060/275] [pre-commit.ci] pre-commit autoupdate (#194) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.2.4 → v2.2.5](https://github.com/codespell-project/codespell/compare/v2.2.4...v2.2.5) - [github.com/asottile/blacken-docs: 1.13.0 → 1.14.0](https://github.com/asottile/blacken-docs/compare/1.13.0...1.14.0) - [github.com/asottile/pyupgrade: v3.6.0 → v3.7.0](https://github.com/asottile/pyupgrade/compare/v3.6.0...v3.7.0) - [github.com/asottile/reorder-python-imports: v3.9.0 → v3.10.0](https://github.com/asottile/reorder-python-imports/compare/v3.9.0...v3.10.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f66caed..905305e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.5 hooks: - id: codespell - repo: https://github.com/psf/black @@ -8,7 +8,7 @@ repos: hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: 1.13.0 + rev: 1.14.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -19,12 +19,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.6.0 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/asottile/reorder-python-imports - rev: v3.9.0 + rev: v3.10.0 hooks: - id: reorder-python-imports args: ['--application-directories=execnet', --py37-plus] From 2dc1048a1f56d6148237bfbf68f2e1ea933e30d5 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 26 Jun 2023 20:17:46 -0400 Subject: [PATCH 061/275] Specify encoding in fdopen. Fixes #195. --- execnet/gateway_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execnet/gateway_base.py b/execnet/gateway_base.py index 5db185fb..83c23e90 100644 --- a/execnet/gateway_base.py +++ b/execnet/gateway_base.py @@ -116,7 +116,7 @@ def start(self, func, args=()): def fdopen(self, fd, mode, bufsize=1): import os - return os.fdopen(fd, mode, bufsize) + return os.fdopen(fd, mode, bufsize, encoding="utf-8") def Lock(self): import threading From 0c4d991bdaf22484872a951f9c8cfed8c260b7cb Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Mon, 26 Jun 2023 20:23:39 -0400 Subject: [PATCH 062/275] Run tests with PYTHONWARNDEFAULTENCODING for visibility of warnings such as those reported in #195. --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 11f19831..2449698a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,11 @@ [tox] envlist=py{37,38,39,310,311,pypy37},docs,linting isolated_build = true + [testenv] usedevelop=true +setenv = + PYTHONWARNDEFAULTENCODING = 1 deps= pytest pytest-timeout From 6e4ee2c5250534d9296755620607028ee027dc57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 19:45:27 -0300 Subject: [PATCH 063/275] [pre-commit.ci] pre-commit autoupdate (#197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 905305e6..abae65bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.3.0' + rev: 'v1.4.1' hooks: - id: mypy From 259c35e6a76ad1878575d0ddad6e8de473cf94ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jul 2023 06:40:02 +0000 Subject: [PATCH 064/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.7.0 → v3.8.0](https://github.com/asottile/pyupgrade/compare/v3.7.0...v3.8.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index abae65bc..2cec5ad2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.7.0 + rev: v3.8.0 hooks: - id: pyupgrade args: [--py37-plus] From e2e0852ee698d0d671eafddaa493aa129f136b95 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 4 Jul 2023 07:59:57 -0300 Subject: [PATCH 065/275] Add #195 to CHANGELOG and improve formatting/grammar --- CHANGELOG.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 39e5be84..92108f1b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,13 +1,15 @@ 2.0.0 (unreleased) ------------------ -* removed support for Python < 3.7 - * apply pyupgrade --py37-plus - * minimal mypy fixes and python2 support code drop -* migrate packaging to hatch -* drop deprecated apis of old makegateway names -* Removed ``py`` testing dependency +* Removed support for Python < 3.7. + - Applied ``pyupgrade --py37-plus``. + - Minimal ``mypy`` fixes and dropped Python 2 support code. + +* Migrated packaging to ``hatch``. +* Dropped deprecated APIs of old makegateway names. +* Removed ``py`` testing dependency. +* Explicitly pass ``encoding`` when opening files in the gateway to get rid of warnings when using ``PYTHONWARNDEFAULTENCODING=1`` (#195). 1.9.0 (2021-06-13) From 58d0215ab7c1ea8060f5ac6eef068ca19048da16 Mon Sep 17 00:00:00 2001 From: Zac Hatfield-Dodds Date: Wed, 5 Jul 2023 23:31:52 -0700 Subject: [PATCH 066/275] Use utf-8 so we support non-ascii module names It's safe to assume that this is always utf-8-safe because we only ever use the function to send Python source code, which must be utf-8 encodable (and indeed utf-8 at rest) --- execnet/gateway_bootstrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execnet/gateway_bootstrap.py b/execnet/gateway_bootstrap.py index 30b6d866..ba5bf104 100644 --- a/execnet/gateway_bootstrap.py +++ b/execnet/gateway_bootstrap.py @@ -75,7 +75,7 @@ def bootstrap_socket(io, id): def sendexec(io, *sources): source = "\n".join(sources) - io.write((repr(source) + "\n").encode("ascii")) + io.write((repr(source) + "\n").encode("utf-8")) def fix_pid_for_jython_popen(gw): From d7761df9f54e8dac27ef298f7d5fbd811f457e16 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 5 Jul 2023 20:00:21 -0300 Subject: [PATCH 067/275] Fix pyproject.toml description/readme --- README.rst | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index d88c94e4..5b14301d 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ a minimal and fast API targeting the following uses: * write scripts to administer multiple hosts Features ------------------- +-------- * zero-install bootstrapping: no remote installation required! diff --git a/pyproject.toml b/pyproject.toml index 218fac81..966b6ba1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "hatchling.build" name = "execnet" dynamic = ["version"] description = "execnet: rapid multi-Python deployment" -long_description_file = "README.rst" +readme = {"file" = "README.rst", "content-type" = "text/x-rst"} license = "MIT" requires-python = ">=3.7" authors = [ From a79b2ccd7865b0ae49729acf185388e47d0fd70c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 5 Jul 2023 19:46:20 -0300 Subject: [PATCH 068/275] Improve CI This applies the improvements done recently in other pytest-dev repositories, like `pytest-xdist`: * Split CI into test and deploy. * Build the package only once. * Deploy is manually triggered and uses trusted publishers. --- .gitattributes | 2 ++ .github/workflows/deploy.yml | 49 +++++++++++++++++++++++++++ .github/workflows/main.yml | 64 ------------------------------------ .github/workflows/test.yml | 63 +++++++++++++++++++++++++++++++++++ README.rst | 4 +-- RELEASING.rst | 10 ++---- 6 files changed, 119 insertions(+), 73 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/test.yml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..003dbf49 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# restructured text files forced to LF because of doc8 pre-commit hook. +*.rst eol=lf diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..86d55d57 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,49 @@ +name: deploy + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + default: '1.2.3' + +jobs: + + package: + runs-on: ubuntu-latest + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} + + steps: + - uses: actions/checkout@v3 + + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + deploy: + needs: package + runs-on: ubuntu-latest + environment: deploy + permissions: + id-token: write # For PyPI trusted publishers. + contents: write # For tag. + + steps: + - uses: actions/checkout@v3 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.8.5 + + - name: Push tag + run: | + git config user.name "pytest bot" + git config user.email "pytestbot@gmail.com" + git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }} + git push origin v${{ github.event.inputs.version }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index c59fc200..00000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: build - -on: - push: - branches: - - "master" - - "test-me-*" - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - - "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+" - - pull_request: - branches: - - "master" - -jobs: - tests: - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [windows-latest, ubuntu-latest] - python: ["3.7","3.8","3.10","3.11", "pypy-3.7"] - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - - name: Install tox - run: pip install tox - - name: Test - run: tox -e py - - deploy: - - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') - - runs-on: ubuntu-latest - - needs: tests - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: "3.7" - - name: Install hatch - run: pip install hatch - - name: Build package - run: hatch build - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..c3b0e71f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,63 @@ +name: test + +on: + push: + branches: + - "master" + - "test-me-*" + + pull_request: + branches: + - "master" + +# Cancel running jobs for the same workflow and branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + package: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + test: + + needs: [package] + + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ windows-latest, ubuntu-latest ] + python: [ "3.7","3.8","3.10","3.11", "pypy-3.7" ] + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + + - name: Install tox + run: | + python -m pip install --upgrade pip + pip install tox + + - name: Test + shell: bash + run: | + tox run -e py --installpkg `find dist/*.tar.gz` diff --git a/README.rst b/README.rst index 5b14301d..a79845eb 100644 --- a/README.rst +++ b/README.rst @@ -13,8 +13,8 @@ Important .. image:: https://img.shields.io/pypi/pyversions/execnet.svg :target: https://pypi.org/project/execnet/ -.. image:: https://github.com/pytest-dev/execnet/workflows/build/badge.svg - :target: https://github.com/pytest-dev/execnet/actions?query=workflow%3Abuild +.. image:: https://github.com/pytest-dev/execnet/workflows/test/badge.svg + :target: https://github.com/pytest-dev/execnet/actions?query=workflow%3Atest .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/python/black diff --git a/RELEASING.rst b/RELEASING.rst index 93cd442f..bc64a19a 100644 --- a/RELEASING.rst +++ b/RELEASING.rst @@ -26,12 +26,8 @@ To publish a new release ``X.Y.Z``, the steps are as follows: #. Update the ``CHANGELOG.rst`` file with the new release information. -#. Commit and push the branch for review. +#. Commit and push the branch to ``upstream`` and open a PR. -#. Once PR is **green** and **approved**, create and push a tag:: +#. Once the PR is **green** and **approved**, start the ``deploy`` workflow manually from the branch ``release-VERSION``, passing ``VERSION`` as parameter. - $ export VERSION=X.Y.Z - $ git tag v$VERSION release-$VERSION - $ git push git@github.com:pytest-dev/execnet.git v$VERSION - -That will build the package and publish it on ``PyPI`` automatically. +#. Merge the release PR to ``master``. From 452b2238df8d8b28e87b619d046d39b518d1e7ad Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 5 Jul 2023 20:22:38 -0300 Subject: [PATCH 069/275] Adopt src layout --- .gitignore | 3 +-- pyproject.toml | 4 ++-- {execnet => src/execnet}/__init__.py | 0 {execnet => src/execnet}/gateway.py | 0 {execnet => src/execnet}/gateway_base.py | 0 {execnet => src/execnet}/gateway_bootstrap.py | 0 {execnet => src/execnet}/gateway_io.py | 0 {execnet => src/execnet}/gateway_socket.py | 0 {execnet => src/execnet}/multi.py | 0 {execnet => src/execnet}/rsync.py | 0 {execnet => src/execnet}/rsync_remote.py | 0 {execnet => src/execnet}/script/__init__.py | 0 {execnet => src/execnet}/script/loop_socketserver.py | 0 {execnet => src/execnet}/script/quitserver.py | 0 {execnet => src/execnet}/script/shell.py | 0 {execnet => src/execnet}/script/socketserver.py | 0 {execnet => src/execnet}/script/socketserverservice.py | 0 {execnet => src/execnet}/script/xx.py | 0 {execnet => src/execnet}/xspec.py | 0 19 files changed, 3 insertions(+), 4 deletions(-) rename {execnet => src/execnet}/__init__.py (100%) rename {execnet => src/execnet}/gateway.py (100%) rename {execnet => src/execnet}/gateway_base.py (100%) rename {execnet => src/execnet}/gateway_bootstrap.py (100%) rename {execnet => src/execnet}/gateway_io.py (100%) rename {execnet => src/execnet}/gateway_socket.py (100%) rename {execnet => src/execnet}/multi.py (100%) rename {execnet => src/execnet}/rsync.py (100%) rename {execnet => src/execnet}/rsync_remote.py (100%) rename {execnet => src/execnet}/script/__init__.py (100%) rename {execnet => src/execnet}/script/loop_socketserver.py (100%) rename {execnet => src/execnet}/script/quitserver.py (100%) rename {execnet => src/execnet}/script/shell.py (100%) mode change 100755 => 100644 rename {execnet => src/execnet}/script/socketserver.py (100%) mode change 100755 => 100644 rename {execnet => src/execnet}/script/socketserverservice.py (100%) rename {execnet => src/execnet}/script/xx.py (100%) rename {execnet => src/execnet}/xspec.py (100%) diff --git a/.gitignore b/.gitignore index aafe44fc..d734b32f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ doc/_build build/ -execnet.egg-info/ -execnet/_version.py +src/execnet/_version.py dist/ .pytest_cache/ .eggs/ diff --git a/pyproject.toml b/pyproject.toml index 966b6ba1..8dadf768 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,11 +49,11 @@ Homepage = "https://execnet.readthedocs.io/en/latest/" source = "vcs" [tool.hatch.build.hooks.vcs] -version-file = "execnet/_version.py" +version-file = "src/execnet/_version.py" [tool.hatch.build.targets.sdist] include = [ - "/execnet", + "/src/execnet", ] diff --git a/execnet/__init__.py b/src/execnet/__init__.py similarity index 100% rename from execnet/__init__.py rename to src/execnet/__init__.py diff --git a/execnet/gateway.py b/src/execnet/gateway.py similarity index 100% rename from execnet/gateway.py rename to src/execnet/gateway.py diff --git a/execnet/gateway_base.py b/src/execnet/gateway_base.py similarity index 100% rename from execnet/gateway_base.py rename to src/execnet/gateway_base.py diff --git a/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py similarity index 100% rename from execnet/gateway_bootstrap.py rename to src/execnet/gateway_bootstrap.py diff --git a/execnet/gateway_io.py b/src/execnet/gateway_io.py similarity index 100% rename from execnet/gateway_io.py rename to src/execnet/gateway_io.py diff --git a/execnet/gateway_socket.py b/src/execnet/gateway_socket.py similarity index 100% rename from execnet/gateway_socket.py rename to src/execnet/gateway_socket.py diff --git a/execnet/multi.py b/src/execnet/multi.py similarity index 100% rename from execnet/multi.py rename to src/execnet/multi.py diff --git a/execnet/rsync.py b/src/execnet/rsync.py similarity index 100% rename from execnet/rsync.py rename to src/execnet/rsync.py diff --git a/execnet/rsync_remote.py b/src/execnet/rsync_remote.py similarity index 100% rename from execnet/rsync_remote.py rename to src/execnet/rsync_remote.py diff --git a/execnet/script/__init__.py b/src/execnet/script/__init__.py similarity index 100% rename from execnet/script/__init__.py rename to src/execnet/script/__init__.py diff --git a/execnet/script/loop_socketserver.py b/src/execnet/script/loop_socketserver.py similarity index 100% rename from execnet/script/loop_socketserver.py rename to src/execnet/script/loop_socketserver.py diff --git a/execnet/script/quitserver.py b/src/execnet/script/quitserver.py similarity index 100% rename from execnet/script/quitserver.py rename to src/execnet/script/quitserver.py diff --git a/execnet/script/shell.py b/src/execnet/script/shell.py old mode 100755 new mode 100644 similarity index 100% rename from execnet/script/shell.py rename to src/execnet/script/shell.py diff --git a/execnet/script/socketserver.py b/src/execnet/script/socketserver.py old mode 100755 new mode 100644 similarity index 100% rename from execnet/script/socketserver.py rename to src/execnet/script/socketserver.py diff --git a/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py similarity index 100% rename from execnet/script/socketserverservice.py rename to src/execnet/script/socketserverservice.py diff --git a/execnet/script/xx.py b/src/execnet/script/xx.py similarity index 100% rename from execnet/script/xx.py rename to src/execnet/script/xx.py diff --git a/execnet/xspec.py b/src/execnet/xspec.py similarity index 100% rename from execnet/xspec.py rename to src/execnet/xspec.py From f746bb7874626c109fac710a7b9177ee6bdf996a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 6 Jul 2023 07:56:19 -0300 Subject: [PATCH 070/275] Update CHANGELOG for 2.0.0 Co-authored-by: Zac Hatfield-Dodds --- CHANGELOG.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 92108f1b..d5248615 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4 +1,4 @@ -2.0.0 (unreleased) +2.0.0 (2023-07-06) ------------------ * Removed support for Python < 3.7. @@ -10,6 +10,7 @@ * Dropped deprecated APIs of old makegateway names. * Removed ``py`` testing dependency. * Explicitly pass ``encoding`` when opening files in the gateway to get rid of warnings when using ``PYTHONWARNDEFAULTENCODING=1`` (#195). +* Fixed error when loading source code files from a path containing non-ascii characters. 1.9.0 (2021-06-13) From 17776dfddd0cdcd6e41ffde86796b4c64ed97382 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 8 Jul 2023 08:47:41 -0300 Subject: [PATCH 071/275] Remove obsolete script This script was used in AppVeyor to install PyPy, which is not necessary anymore as we use GitHub actions. --- scripts/install-pypy.bat | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 scripts/install-pypy.bat diff --git a/scripts/install-pypy.bat b/scripts/install-pypy.bat deleted file mode 100644 index 8012ea46..00000000 --- a/scripts/install-pypy.bat +++ /dev/null @@ -1,6 +0,0 @@ -REM install pypy using choco -REM redirect to a file because choco install python.pypy is too noisy. If the command fails, write output to console -choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) -set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy -echo PyPy installed -pypy --version From 773107e305f6799e29d604d00bf6ca14b0c61b50 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 8 Jul 2023 08:50:40 -0300 Subject: [PATCH 072/275] Remove obsolete ISSUES.txt The contents have been posted in #205 for review/posterity. --- ISSUES.txt | 132 ----------------------------------------------------- 1 file changed, 132 deletions(-) delete mode 100644 ISSUES.txt diff --git a/ISSUES.txt b/ISSUES.txt deleted file mode 100644 index 0b2e6b95..00000000 --- a/ISSUES.txt +++ /dev/null @@ -1,132 +0,0 @@ -rsync links from posix to windows ------------------------------------------ -tags: 1.1 feature -bb: http://bitbucket.org/hpk42/py-trunk/issue/35 -path: execnet/rsync.py - -currently rsyncing bails out with a strange message when trying to rsync -links. should provide better message or even some support for doing it. - -http process for mediating client/remote-subprocess execution ------------------------------------------------------------------- - -tags: 1.2 feature newdist - -A client client connects to an http-execserver. An http-execserver -routes incoming "POST /exec" requests to a subprocess and returns a PID. -The PID identifies the subprocess which is used for further communication. - -Client http-exec subprocess - -makegw("...") -> POST /popen -> Popen() with bootstrap - (bootstrap) - <- PID - -c=rexec(...) POST /exec/PID exec source -(source) - -c.send(1) POST /exec/PID x = c.receive() - (1) assert x == 1 - c.send(2) - -z = c.receive() GET /exec/PID - <- (2) -assert z == 2 - -The client connects via "POST /exec/PID" -and pushes data to the remotely-executing code -in the subprocess. - -The client connects via "GET /exec/PID" -and receives data from the remotely-executing code. - -The client constructs a Channel() object which triggers -these two connections, presenting a nice send/receive -abstractions. - -The code executing in the http-execserver controlled -subprocess is a PopenGateway. It does not know that it -is connected via the http client/server machinery. - -The http-execserver proxies data from the subprocess -to the client. If there is no active GET request, the -data is queued. - -The http-execserver forwards data from the client to the -subprocess, using the PID. If the subprocess is not -connected, an ERROR code is returned. - -Example session: - - gw = group.makegateway("http=codespeak.net") - - def fun(channel): - return channel.send(channel.receive()+1) - - ch = gw.remote_exec(fun) - ch.send(1) - x = ch.receive() - assert x == 2 - - -a channel-IO based gateway --------------------------------------------------- -tags: 1.1 feature newdist - -A building block for establishing a permanent hierarchy -of processes is a Gateway that operates on a Channel object. -This way a gateway can be instantiated through another -intermediating gateway which bi-directionallry forwards -Messages through a existing channel. - -A code example could look like this: - - group.setlocalfactory(port=8888) - gw = group.makegateway("ssh=codespeak.net") - # local ssh-process would be a child of the factory service - # which could have timeouts for killing, or allow resuming. - # for instantiating remote it could also install a multiplexer - # on the remote place such that the same ssh connection is re-used - # for multiple ssh=codespeak.net connections, makes for small latency. - -some first simple implementation might look like this:: - - gw = group.makegateway("socketservice//port=8888") - # we can rely that the subprocess gw can import execnet - ch = gw.remote_exec(""" - import execnet.service - execnet.service.gatewaymaker(channel) - """) - ch.send(xspec) - subchannel = ch.receive() - return ChannelGateway(subchannel) - -creating virtualenv environments on the fly ------------------------------------------------ -tags: 1.1 wish - -there should be a (plugin-provided?) way to create -a virtual environment: - gw = makegateway("ssh=codespeak.net") - newgw = xdeploy.create_venv_gateway(gw, "venvname", depstring) - newgw # lives in home/ dir of remote virtualenv - # maybe have get_temproot default to tmp/ there? - sf = xdeploy.SetupFile("..setup.py") - sf.sdist_remote_install(newgw) - - -have callback accept errors / errback ------------------------------------------------ -tags: 1.1 wish - -If using channel.setcallback() it currently is not -possible to notice the exact errors that might happen -on the other side. Either introduce an "errback" -or (probably better) optionally allow an extra 'error' -parameter to execnet callbacks. - -fix rsync between python/jython ------------------------------------------------ -tags: 1.0 bug - -rsync does not complete with jython. From 2757f05c1a808463d731954fe6047781eecc45f7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 8 Jul 2023 08:46:45 -0300 Subject: [PATCH 073/275] Include docs and testing into sdist Fix #204 --- CHANGELOG.rst | 5 +++++ pyproject.toml | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d5248615..2d16ffc7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +2.0.1 (2023-07-08) +------------------ + +* Re-release without code changes, just to include docs and tests into the source distribution. + 2.0.0 (2023-07-06) ------------------ diff --git a/pyproject.toml b/pyproject.toml index 8dadf768..b1c13e51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,9 @@ version-file = "src/execnet/_version.py" [tool.hatch.build.targets.sdist] include = [ - "/src/execnet", + "/src", + "/doc", + "/testing", ] From 030839490f85dad2a2d58f1fe17bdf16f09c4e8e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 9 Jul 2023 12:04:38 -0300 Subject: [PATCH 074/275] Include tox.ini into the source distribution Fix #207 --- CHANGELOG.rst | 5 +++++ pyproject.toml | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2d16ffc7..3776bad1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +2.0.2 (2023-07-09) +------------------ + +* Re-release without code changes, just to include ``tox.ini`` into the source distribution. + 2.0.1 (2023-07-08) ------------------ diff --git a/pyproject.toml b/pyproject.toml index b1c13e51..c010499f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,11 +53,11 @@ version-file = "src/execnet/_version.py" [tool.hatch.build.targets.sdist] include = [ - "/src", "/doc", + "/src", "/testing", + "tox.ini", ] - [tool.mypy] python_version = "3.7" From 6a56163d74493e826e231198b29cd81895527a3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 05:20:23 +0000 Subject: [PATCH 075/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.5 to 1.8.7 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.5 to 1.8.7. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.5...v1.8.7) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 86d55d57..f32e08f5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.5 + uses: pypa/gh-action-pypi-publish@v1.8.7 - name: Push tag run: | From 66d9b27b993c540cc79ce4223d2ef235db26b747 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 10 Jul 2023 08:53:43 -0300 Subject: [PATCH 076/275] Add readthedocs configuration We need to use the configuration file to specify `pip` as the installation method; by default it uses `setuptools` via `setup.py`, which we no longer have since ported to `hatch`. Fix #210 --- .readthedocs.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..ed4a5df0 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,12 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3" + +python: + install: + - method: pip + path: . + From cb785cdb3be46b782a31efbc35febfd15440a5a4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:58:42 +0000 Subject: [PATCH 077/275] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .readthedocs.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ed4a5df0..516834fe 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,4 +9,3 @@ python: install: - method: pip path: . - From cbeeb6e19f61957148aa73f61147a978751d0f91 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 10 Jul 2023 12:04:41 -0300 Subject: [PATCH 078/275] Bring back notice about maintenance only Seems this was removed by accident in https://github.com/pytest-dev/execnet/commit/e4d606f97e59d1704475652333334c327210dbbe#diff-7b3ed02bc73dc06b7db906cf97aa91dec2b2eb21f2d92bc5caa761df5bbc168f. --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index a79845eb..bf910873 100644 --- a/README.rst +++ b/README.rst @@ -4,6 +4,9 @@ execnet: distributed Python deployment and communication Important --------- +**execnet currently is in maintenance-only mode, mostly because it is still the backend +of the pytest-xdist plugin. Do not use in new projects.** + .. image:: https://img.shields.io/pypi/v/execnet.svg :target: https://pypi.org/project/execnet/ From efffc629fdf28be67e3a19aa1a59fe4fa9e6034d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 10 Jul 2023 14:07:30 -0300 Subject: [PATCH 079/275] Update README.rst --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index bf910873..7ee0f8ae 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,6 @@ execnet: distributed Python deployment and communication ======================================================== -Important ---------- - **execnet currently is in maintenance-only mode, mostly because it is still the backend of the pytest-xdist plugin. Do not use in new projects.** From 7b450493766b5103ab0b6e8680961397d8c68d55 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 10 Jul 2023 14:08:05 -0300 Subject: [PATCH 080/275] Update README.rst --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index 7ee0f8ae..6687e19d 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,6 @@ execnet: distributed Python deployment and communication ======================================================== -**execnet currently is in maintenance-only mode, mostly because it is still the backend -of the pytest-xdist plugin. Do not use in new projects.** - .. image:: https://img.shields.io/pypi/v/execnet.svg :target: https://pypi.org/project/execnet/ From e66800d3d49148b542ef4db19a0b3dd25e406c3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 07:56:19 -0300 Subject: [PATCH 081/275] [pre-commit.ci] pre-commit autoupdate (#213) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0) - [github.com/asottile/blacken-docs: 1.14.0 → 1.15.0](https://github.com/asottile/blacken-docs/compare/1.14.0...1.15.0) - [github.com/asottile/pyupgrade: v3.8.0 → v3.9.0](https://github.com/asottile/pyupgrade/compare/v3.8.0...v3.9.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2cec5ad2..1a0b47de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,11 +4,11 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: 1.14.0 + rev: 1.15.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -19,7 +19,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.8.0 + rev: v3.9.0 hooks: - id: pyupgrade args: [--py37-plus] From 58bc50666372c06aca05234bac73c2d4a073557d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 05:34:12 +0000 Subject: [PATCH 082/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.7 to 1.8.8. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.7...v1.8.8) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f32e08f5..ecfd014f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.7 + uses: pypa/gh-action-pypi-publish@v1.8.8 - name: Push tag run: | From 101868716b9244872492e648a3c90f209ce31a08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 19:14:44 -0300 Subject: [PATCH 083/275] [pre-commit.ci] pre-commit autoupdate (#215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.9.0 → v3.10.1](https://github.com/asottile/pyupgrade/compare/v3.9.0...v3.10.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a0b47de..d3cb32fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.9.0 + rev: v3.10.1 hooks: - id: pyupgrade args: [--py37-plus] From a20821f7e51b2c92444708ad996c31b4c14619fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Aug 2023 05:45:13 +0000 Subject: [PATCH 084/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.8 to 1.8.10 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.8 to 1.8.10. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.8...v1.8.10) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ecfd014f..713f295d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.8 + uses: pypa/gh-action-pypi-publish@v1.8.10 - name: Push tag run: | From f63f708ca720d1de237859e170e1a547ac00c278 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 09:24:45 -0300 Subject: [PATCH 085/275] [pre-commit.ci] pre-commit autoupdate (#217) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.4.1 → v1.5.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.4.1...v1.5.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3cb32fa..aba94bd8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.4.1' + rev: 'v1.5.0' hooks: - id: mypy From f3a64878929bca2347beea2965cd5390a6c731bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Aug 2023 19:04:20 -0300 Subject: [PATCH 086/275] [pre-commit.ci] pre-commit autoupdate (#218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/blacken-docs: 1.15.0 → 1.16.0](https://github.com/asottile/blacken-docs/compare/1.15.0...1.16.0) - [github.com/pre-commit/mirrors-mypy: v1.5.0 → v1.5.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.0...v1.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aba94bd8..df803345 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: hooks: - id: black - repo: https://github.com/asottile/blacken-docs - rev: 1.15.0 + rev: 1.16.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.5.0' + rev: 'v1.5.1' hooks: - id: mypy From 10ecd739729742bd908bf69436f08456f1cc53ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 06:51:35 -0300 Subject: [PATCH 087/275] build(deps): bump actions/checkout from 3 to 4 (#219) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 713f295d..9131e1f0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,7 @@ jobs: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v1.5 @@ -30,7 +30,7 @@ jobs: contents: write # For tag. steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download Package uses: actions/download-artifact@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c3b0e71f..1947b7ef 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v1.5 @@ -37,7 +37,7 @@ jobs: python: [ "3.7","3.8","3.10","3.11", "pypy-3.7" ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 From 64c130aa9494c5940584120316d3f9e3727a4427 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:31:30 +0000 Subject: [PATCH 088/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.7.0 → 23.9.1](https://github.com/psf/black/compare/23.7.0...23.9.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df803345..edd399a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/asottile/blacken-docs From 9737a960908862233b63b6e01d8bf94ba495030a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 09:15:34 -0300 Subject: [PATCH 089/275] [pre-commit.ci] pre-commit autoupdate (#222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.10.1 → v3.11.0](https://github.com/asottile/pyupgrade/compare/v3.10.1...v3.11.0) - [github.com/asottile/reorder-python-imports: v3.10.0 → v3.11.0](https://github.com/asottile/reorder-python-imports/compare/v3.10.0...v3.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index edd399a7..6ce457bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,12 +19,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.11.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.11.0 hooks: - id: reorder-python-imports args: ['--application-directories=execnet', --py37-plus] From 3ee8d6b6af0e6619aa52f860b0e530e6f7d097b2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Sep 2023 06:57:41 -0300 Subject: [PATCH 090/275] [pre-commit.ci] pre-commit autoupdate (#225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.11.0 → v3.13.0](https://github.com/asottile/pyupgrade/compare/v3.11.0...v3.13.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ce457bc..53565e3a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,7 +19,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.11.0 + rev: v3.13.0 hooks: - id: pyupgrade args: [--py37-plus] From 72169dfb90a1fdc6c58d450d1c9d167765850b5b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 09:20:12 -0300 Subject: [PATCH 091/275] [pre-commit.ci] pre-commit autoupdate (#226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/codespell-project/codespell: v2.2.5 → v2.2.6](https://github.com/codespell-project/codespell/compare/v2.2.5...v2.2.6) - [github.com/asottile/pyupgrade: v3.13.0 → v3.14.0](https://github.com/asottile/pyupgrade/compare/v3.13.0...v3.14.0) - [github.com/asottile/reorder-python-imports: v3.11.0 → v3.12.0](https://github.com/asottile/reorder-python-imports/compare/v3.11.0...v3.12.0) * Fix typos * Fix typos --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bruno Oliveira --- .pre-commit-config.yaml | 6 +++--- CHANGELOG.rst | 4 ++-- doc/example/test_proxy.rst | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53565e3a..43122b6e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell - repo: https://github.com/psf/black @@ -19,12 +19,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + rev: v3.14.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/asottile/reorder-python-imports - rev: v3.11.0 + rev: v3.12.0 hooks: - id: reorder-python-imports args: ['--application-directories=execnet', --py37-plus] diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3776bad1..a0f0d67a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -130,7 +130,7 @@ (this also fixes the bpython interaction issues) - Fix issue38: provide ability to connect to Vagrant VMs easily - using :code:`vagrant_ssh=defaut` or :code:`vagrant_ssh=machinename` + using :code:`vagrant_ssh=default` or :code:`vagrant_ssh=machinename` this feature is experimental and will be refined in future releases. Thanks Christian Theune for the discussion and the initial pull request. @@ -432,7 +432,7 @@ * make internal protocols more robust against serialization failures -* fix a seralization bug with nested tuples containing empty tuples +* fix a serialization bug with nested tuples containing empty tuples (thanks to ronny for discovering it) * setting the environment variable EXECNET_DEBUG will generate per diff --git a/doc/example/test_proxy.rst b/doc/example/test_proxy.rst index 0a90f258..a4f45f37 100644 --- a/doc/example/test_proxy.rst +++ b/doc/example/test_proxy.rst @@ -7,7 +7,7 @@ Simple Proxying Using the via arg of specs we can create a gateway whose io is created on a remote gateway and proxied to the master. -The simlest use case, is where one creates one master process +The simplest use case, is where one creates one master process and uses it to control new workers and their environment :: From adbb837d8b499a4870dac2b884ff1f600101181f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 08:11:47 -0300 Subject: [PATCH 092/275] [pre-commit.ci] pre-commit autoupdate (#227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.4.0 → v4.5.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.4.0...v4.5.0) - [github.com/asottile/pyupgrade: v3.14.0 → v3.15.0](https://github.com/asottile/pyupgrade/compare/v3.14.0...v3.15.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43122b6e..26f3a832 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,13 +13,13 @@ repos: - id: blacken-docs additional_dependencies: [black==22.12.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.14.0 + rev: v3.15.0 hooks: - id: pyupgrade args: [--py37-plus] From fa5e30653ab98a9708f7456a81c60df039032b37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:21:54 -0300 Subject: [PATCH 093/275] [pre-commit.ci] pre-commit autoupdate (#228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v1.5.1 → v1.6.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.5.1...v1.6.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 26f3a832..96cb87df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.5.1' + rev: 'v1.6.0' hooks: - id: mypy From e20f783959449b6c9d46af5ec059ace48cd89f42 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 07:45:52 -0300 Subject: [PATCH 094/275] [pre-commit.ci] pre-commit autoupdate (#229) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.9.1 → 23.10.1](https://github.com/psf/black/compare/23.9.1...23.10.1) - [github.com/pre-commit/mirrors-mypy: v1.6.0 → v1.6.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.0...v1.6.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96cb87df..36d34d81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black - repo: https://github.com/asottile/blacken-docs @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.6.0' + rev: 'v1.6.1' hooks: - id: mypy From d678ed063f48caf8760a09406ef323093bf34255 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 08:42:03 -0300 Subject: [PATCH 095/275] [pre-commit.ci] pre-commit autoupdate (#230) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 23.10.1 → 23.11.0](https://github.com/psf/black/compare/23.10.1...23.11.0) - [github.com/pre-commit/mirrors-mypy: v1.6.1 → v1.7.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.6.1...v1.7.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36d34d81..0cd053b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/psf/black - rev: 23.10.1 + rev: 23.11.0 hooks: - id: black - repo: https://github.com/asottile/blacken-docs @@ -35,6 +35,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.6.1' + rev: 'v1.7.0' hooks: - id: mypy From f71dfb556e0e75bb8c469dc7b2bcef641935a1de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:17:34 -0300 Subject: [PATCH 096/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.10 to 1.8.11 (#232) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.10 to 1.8.11. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.10...v1.8.11) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9131e1f0..705a32e1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.10 + uses: pypa/gh-action-pypi-publish@v1.8.11 - name: Push tag run: | From 001d2c04e849f8b2ea3156362fb9afb70d3d38b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 08:01:57 -0300 Subject: [PATCH 097/275] build(deps): bump actions/setup-python from 4 to 5 (#234) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1947b7ef..5cc7af8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: path: dist - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} From 907e87a212f6238c32a101bf60468d3df6a814e4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 30 Jan 2024 13:05:52 -0300 Subject: [PATCH 098/275] Use ruff instead of black and reorder-python-imports (#239) Unfortunately black and reorder-python-imports are no longer compatible between each other: https://github.com/asottile/reorder-python-imports/issues/367 https://github.com/asottile/reorder-python-imports/issues/366 https://github.com/psf/black/issues/4175 Take this opportunity to try out ruff. Closes #231 --- .pre-commit-config.yaml | 14 ++++++-------- pyproject.toml | 6 ++++++ src/execnet/gateway_base.py | 5 +++-- src/execnet/gateway_io.py | 3 +-- src/execnet/rsync_remote.py | 2 +- src/execnet/script/shell.py | 2 +- testing/test_threadpool.py | 3 +-- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0cd053b4..cb1a9c0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,10 +3,6 @@ repos: rev: v2.2.6 hooks: - id: codespell -- repo: https://github.com/psf/black - rev: 23.11.0 - hooks: - - id: black - repo: https://github.com/asottile/blacken-docs rev: 1.16.0 hooks: @@ -23,11 +19,13 @@ repos: hooks: - id: pyupgrade args: [--py37-plus] -- repo: https://github.com/asottile/reorder-python-imports - rev: v3.12.0 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.14 hooks: - - id: reorder-python-imports - args: ['--application-directories=execnet', --py37-plus] + - id: ruff + args: [ --fix ] + exclude: "^doc/" + - id: ruff-format - repo: https://github.com/PyCQA/doc8 rev: 'v1.1.1' hooks: diff --git a/pyproject.toml b/pyproject.toml index c010499f..9a276f9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,12 @@ testing = [ [project.urls] Homepage = "https://execnet.readthedocs.io/en/latest/" +[tool.ruff.lint] +ignore = ["E741"] + +[tool.ruff.lint.isort] +case-sensitive = true + [tool.hatch.version] source = "vcs" diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 83c23e90..74eacd33 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -596,7 +596,7 @@ class GatewayReceivedTerminate(Exception): def geterrortext(excinfo, format_exception=traceback.format_exception, sysex=sysex): try: - l = format_exception(*excinfo) + l = format_exception(*excinfo) # noqa:E741 errortext = "".join(l) except sysex: raise @@ -633,6 +633,7 @@ class TimeoutError(IOError): class Channel: "Communication channel between two Python Interpreter execution points." + RemoteError = RemoteError TimeoutError = TimeoutError _INTERNALWAKEUP = 1000 @@ -1543,7 +1544,7 @@ def _save_integral(self, i, short_op, long_op): def save_int(self, i): self._save_integral(i, opcode.INT, opcode.LONGINT) - def save_long(self, l): + def save_long(self, l): # noqa:E741 self._save_integral(l, opcode.LONG, opcode.LONGLONG) def save_float(self, flt): diff --git a/src/execnet/gateway_io.py b/src/execnet/gateway_io.py index c631f8d9..730af868 100644 --- a/src/execnet/gateway_io.py +++ b/src/execnet/gateway_io.py @@ -3,7 +3,6 @@ creates io instances used for gateway io """ -import os import shlex import sys @@ -228,4 +227,4 @@ def control(data): if __name__ == "__channelexec__": - serve_proxy_io(channel) # type: ignore[name-defined] + serve_proxy_io(channel) # type: ignore[name-defined] # noqa:F821 diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 4ac1880f..3a8e71e9 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -114,4 +114,4 @@ def receive_directory_structure(path, relcomponents): if __name__ == "__channelexec__": - serve_rsync(channel) # type: ignore[name-defined] + serve_rsync(channel) # type: ignore[name-defined] # noqa:F821 diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index f47cd4d3..a80b80ea 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -26,7 +26,7 @@ def clientside(): while 1: r, w, e = select.select(inputlist, [], []) if sys.stdin in r: - line = raw_input() + line = raw_input() # noqa:F821 sock.sendall(line + "\n") if sock in r: line = sock.recv(4096) diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 47a226d0..4d1edd8c 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -1,5 +1,4 @@ import os -import sys import pytest from execnet.gateway_base import WorkerPool @@ -61,7 +60,7 @@ def first(): def test_waitfinish_on_reply(pool): - l = [] + l = [] # noqa:E741 reply = pool.spawn(lambda: l.append(1)) reply.waitfinish() assert l == [1] From e84967517fdd57f824b51d0d00ac2907f3a5b0d1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 30 Jan 2024 16:46:24 -0300 Subject: [PATCH 099/275] Fix ruff isort configuration (#240) As brought up in https://github.com/pytest-dev/execnet/pull/239#issuecomment-1917570146, the isort settings for ruff were not being applied. --- pyproject.toml | 4 +++- src/execnet/__init__.py | 9 ++++----- src/execnet/gateway.py | 2 +- src/execnet/gateway_base.py | 2 +- src/execnet/gateway_io.py | 6 ++++-- src/execnet/multi.py | 2 +- src/execnet/rsync_remote.py | 2 +- src/execnet/script/quitserver.py | 1 - src/execnet/script/socketserverservice.py | 1 - testing/conftest.py | 2 +- testing/test_basics.py | 1 - testing/test_channel.py | 1 - testing/test_serializer.py | 1 - 13 files changed, 16 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a276f9c..50efb9f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,9 +47,11 @@ Homepage = "https://execnet.readthedocs.io/en/latest/" [tool.ruff.lint] ignore = ["E741"] +extend-select = ["I001"] [tool.ruff.lint.isort] -case-sensitive = true +force-single-line = true +known-third-party = ["src"] [tool.hatch.version] source = "vcs" diff --git a/src/execnet/__init__.py b/src/execnet/__init__.py index c403e2bb..d22eb1af 100644 --- a/src/execnet/__init__.py +++ b/src/execnet/__init__.py @@ -8,22 +8,21 @@ """ from ._version import version as __version__ from .gateway_base import DataFormatError +from .gateway_base import RemoteError +from .gateway_base import TimeoutError from .gateway_base import dump from .gateway_base import dumps from .gateway_base import load from .gateway_base import loads -from .gateway_base import RemoteError -from .gateway_base import TimeoutError from .gateway_bootstrap import HostNotFound -from .multi import default_group from .multi import Group -from .multi import makegateway from .multi import MultiChannel +from .multi import default_group +from .multi import makegateway from .multi import set_execmodel from .rsync import RSync from .xspec import XSpec - __all__ = [ "__version__", "makegateway", diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 6e0b8a7d..5f066fb0 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -153,8 +153,8 @@ def __repr__(self): def rinfo_source(channel): - import sys import os + import sys channel.send( dict( diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 74eacd33..c055d64f 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -425,8 +425,8 @@ def trace(*msg): pass # nothing we can do, likely interpreter-shutdown elif DEBUG: - import tempfile import os + import tempfile fn = os.path.join(tempfile.gettempdir(), "execnet-debug-%d" % pid) # sys.stderr.write("execnet-debug at %r" % (fn,)) diff --git a/src/execnet/gateway_io.py b/src/execnet/gateway_io.py index 730af868..40ca5c80 100644 --- a/src/execnet/gateway_io.py +++ b/src/execnet/gateway_io.py @@ -7,9 +7,11 @@ import sys try: - from execnet.gateway_base import Popen2IO, Message + from execnet.gateway_base import Message + from execnet.gateway_base import Popen2IO except ImportError: - from __main__ import Popen2IO, Message # type: ignore[no-redef] + from __main__ import Message # type: ignore[no-redef] + from __main__ import Popen2IO # type: ignore[no-redef] from functools import partial diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 64e95017..7629ddb3 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -10,9 +10,9 @@ from . import gateway_bootstrap from . import gateway_io +from .gateway_base import WorkerPool from .gateway_base import get_execmodel from .gateway_base import trace -from .gateway_base import WorkerPool from .xspec import XSpec NO_ENDMARKER_WANTED = object() diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 3a8e71e9..3dbe2d84 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -5,8 +5,8 @@ def serve_rsync(channel): import os - import stat import shutil + import stat from hashlib import md5 destdir, options = channel.receive() diff --git a/src/execnet/script/quitserver.py b/src/execnet/script/quitserver.py index 9d5cbb15..1ea3fccd 100644 --- a/src/execnet/script/quitserver.py +++ b/src/execnet/script/quitserver.py @@ -8,7 +8,6 @@ import socket import sys - host, port = sys.argv[1].split(":") hostport = (host, int(port)) diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index 3d64f139..bf17fd39 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -15,7 +15,6 @@ import win32service import win32serviceutil - appname = "ExecNetSocketServer" diff --git a/testing/conftest.py b/testing/conftest.py index 066df23a..9ec44d30 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -6,8 +6,8 @@ import execnet import pytest -from execnet.gateway_base import get_execmodel from execnet.gateway_base import WorkerPool +from execnet.gateway_base import get_execmodel collect_ignore = ["build", "doc/_build"] diff --git a/testing/test_basics.py b/testing/test_basics.py index 5820c492..321e18f1 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -19,7 +19,6 @@ from execnet.gateway_base import Message from execnet.gateway_base import Popen2IO - skip_win_pypy = pytest.mark.xfail( condition=hasattr(sys, "pypy_version_info") and sys.platform.startswith("win"), reason="failing on Windows on PyPy (#63)", diff --git a/testing/test_channel.py b/testing/test_channel.py index a1bfbae9..0f1588e9 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -5,7 +5,6 @@ import pytest - needs_early_gc = pytest.mark.skipif("not hasattr(sys, 'getrefcount')") needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") TESTTIMEOUT = 10.0 # seconds diff --git a/testing/test_serializer.py b/testing/test_serializer.py index fed4a5de..9b11d1b7 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -6,7 +6,6 @@ import execnet import pytest - # We use the execnet folder in order to avoid triggering a missing apipkg. pyimportdir = os.fspath(Path(execnet.__file__).parent) From 7286608fc2a1218c832cc80a13573eab6d5dd96d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 09:40:15 -0300 Subject: [PATCH 100/275] [pre-commit.ci] pre-commit autoupdate (#241) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.14 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.14...v0.2.0) - [github.com/pre-commit/mirrors-mypy: v1.7.0 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.0...v1.8.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb1a9c0d..dcae7d7f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 + rev: v0.2.0 hooks: - id: ruff args: [ --fix ] @@ -33,6 +33,6 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.7.0' + rev: 'v1.8.0' hooks: - id: mypy From 372168eac2912274b27d252c09d163cd10608a01 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 13:11:41 -0300 Subject: [PATCH 101/275] [pre-commit.ci] pre-commit autoupdate (#242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.0 → v0.2.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.0...v0.2.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dcae7d7f..af7d776d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.2.1 hooks: - id: ruff args: [ --fix ] From 13a070badd6f9cbb801838e9515ed56691244149 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:34:21 -0300 Subject: [PATCH 102/275] [pre-commit.ci] pre-commit autoupdate (#244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.15.0 → v3.15.1](https://github.com/asottile/pyupgrade/compare/v3.15.0...v3.15.1) - [github.com/astral-sh/ruff-pre-commit: v0.2.1 → v0.2.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.1...v0.2.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af7d776d..75b18443 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,12 +15,12 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/asottile/pyupgrade - rev: v3.15.0 + rev: v3.15.1 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.2.2 hooks: - id: ruff args: [ --fix ] From 6b87f99fa797939f1fd5cb882fa7027566dd3e76 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Mon, 19 Feb 2024 19:55:28 -0800 Subject: [PATCH 103/275] Add main_thread_only execmodel In order to prevent tasks from running in a non-main thread, wait for the previous task inside _try_send_to_primary_thread, then schedule the next task. Add a main_thread_only execmodel to distinguish this new behavior from the existing thread execmodel, since users of the thread execmodel expect that tasks can run in multiple threads concurrently. If concurrent remote_exec requests are submitted for the main_thread_only execmodel, then the channel will raise a RemoteError with this message: concurrent remote_exec would cause deadlock for main_thread_only execmodel In order for main_thread_only users to avoid this error, remote_exec callers must use the returned channel to wait for a task to complete before they call remote_exec again, as demonstrated in test_assert_main_thread_only. Testing of main_thread_only with pytest-xdist has shown that this behavior is compatible with the existing pytest-xdist usage because it never calls remote_exec more than once per gateway. --- doc/basics.rst | 10 ++--- src/execnet/gateway_base.py | 59 +++++++++++++++++++++++++-- src/execnet/multi.py | 2 +- testing/conftest.py | 8 ++-- testing/test_gateway.py | 79 +++++++++++++++++++++++++++++++++++++ testing/test_multi.py | 6 ++- testing/test_termination.py | 8 +++- testing/test_threadpool.py | 4 +- 8 files changed, 157 insertions(+), 19 deletions(-) diff --git a/doc/basics.rst b/doc/basics.rst index aa6dabaf..f0eebd85 100644 --- a/doc/basics.rst +++ b/doc/basics.rst @@ -138,14 +138,14 @@ processes then you often want to call ``group.terminate()`` yourself and specify a larger or not timeout. -threading models: gevent, eventlet, thread -=========================================== +threading models: gevent, eventlet, thread, main_thread_only +==================================================================== .. versionadded:: 1.2 (status: experimental!) -execnet supports "thread", "eventlet" and "gevent" as thread models -on each of the two sides. You need to decide which model to use -before you create any gateways:: +execnet supports "main_thread_only", "thread", "eventlet" and "gevent" +as thread models on each of the two sides. You need to decide which +model to use before you create any gateways:: # content of threadmodel.py import execnet diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index c055d64f..954423de 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -134,6 +134,10 @@ def Event(self): return threading.Event() +class MainThreadOnlyExecModel(ThreadExecModel): + backend = "main_thread_only" + + class EventletExecModel(ExecModel): backend = "eventlet" @@ -254,6 +258,8 @@ def get_execmodel(backend): return backend if backend == "thread": return ThreadExecModel() + elif backend == "main_thread_only": + return MainThreadOnlyExecModel() elif backend == "eventlet": return EventletExecModel() elif backend == "gevent": @@ -322,7 +328,7 @@ def __init__(self, execmodel, hasprimary=False): self._shuttingdown = False self._waitall_events = [] if hasprimary: - if self.execmodel.backend != "thread": + if self.execmodel.backend not in ("thread", "main_thread_only"): raise ValueError("hasprimary=True requires thread model") self._primary_thread_task_ready = self.execmodel.Event() else: @@ -332,7 +338,7 @@ def integrate_as_primary_thread(self): """integrate the thread with which we are called as a primary thread for executing functions triggered with spawn(). """ - assert self.execmodel.backend == "thread", self.execmodel + assert self.execmodel.backend in ("thread", "main_thread_only"), self.execmodel primary_thread_task_ready = self._primary_thread_task_ready # interacts with code at REF1 while 1: @@ -345,7 +351,11 @@ def integrate_as_primary_thread(self): with self._running_lock: if self._shuttingdown: break - primary_thread_task_ready.clear() + # Only clear if _try_send_to_primary_thread has not + # yet set the next self._primary_thread_task reply + # after waiting for this one to complete. + if reply is self._primary_thread_task: + primary_thread_task_ready.clear() def trigger_shutdown(self): with self._running_lock: @@ -376,6 +386,19 @@ def _try_send_to_primary_thread(self, reply): # wake up primary thread primary_thread_task_ready.set() return True + elif ( + self.execmodel.backend == "main_thread_only" + and self._primary_thread_task is not None + ): + self._primary_thread_task.waitfinish() + self._primary_thread_task = reply + # wake up primary thread (it's okay if this is already set + # because we waited for the previous task to finish above + # and integrate_as_primary_thread will not clear it when + # it enters self._running_lock if it detects that a new + # task is available) + primary_thread_task_ready.set() + return True return False def spawn(self, func, *args, **kwargs): @@ -857,6 +880,9 @@ def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): ENDMARKER = object() INTERRUPT_TEXT = "keyboard-interrupted" +MAIN_THREAD_ONLY_DEADLOCK_TEXT = ( + "concurrent remote_exec would cause deadlock for main_thread_only execmodel" +) class ChannelFactory: @@ -1105,6 +1131,20 @@ def join(self, timeout=None): class WorkerGateway(BaseGateway): def _local_schedulexec(self, channel, sourcetask): + if self._execpool.execmodel.backend == "main_thread_only": + # It's necessary to wait for a short time in order to ensure + # that we do not report a false-positive deadlock error, since + # channel close does not elicit a response that would provide + # a guarantee to remote_exec callers that the previous task + # has released the main thread. If the timeout expires then it + # should be practically impossible to report a false-positive. + if not self._executetask_complete.wait(timeout=1): + channel.close(MAIN_THREAD_ONLY_DEADLOCK_TEXT) + return + # It's only safe to clear here because the above wait proves + # that there is not a previous task about to set it again. + self._executetask_complete.clear() + sourcetask = loads_internal(sourcetask) self._execpool.spawn(self.executetask, (channel, sourcetask)) @@ -1132,8 +1172,14 @@ def serve(self): def trace(msg): self._trace("[serve] " + msg) - hasprimary = self.execmodel.backend == "thread" + hasprimary = self.execmodel.backend in ("thread", "main_thread_only") self._execpool = WorkerPool(self.execmodel, hasprimary=hasprimary) + self._executetask_complete = None + if self.execmodel.backend == "main_thread_only": + self._executetask_complete = self.execmodel.Event() + # Initialize state to indicate that there is no previous task + # executing so that we don't need a separate flag to track this. + self._executetask_complete.set() trace("spawning receiver thread") self._initreceive() try: @@ -1176,6 +1222,11 @@ def executetask(self, item): return self._trace("ignoring EOFError because receiving finished") channel.close() + if self._executetask_complete is not None: + # Indicate that this task has finished executing, meaning + # that there is no possibility of it triggering a deadlock + # for the next spawn call. + self._executetask_complete.set() # diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 7629ddb3..c63a57e8 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -107,7 +107,7 @@ def makegateway(self, spec=None): id= specifies the gateway id python= specifies which python interpreter to execute - execmodel=model 'thread', 'eventlet', 'gevent' model for execution + execmodel=model 'thread', 'main_thread_only', 'eventlet', 'gevent' model for execution chdir= specifies to which directory to change nice= specifies process priority of new process env:NAME=value specifies a remote environment variable setting. diff --git a/testing/conftest.py b/testing/conftest.py index 9ec44d30..cd44f92d 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -124,7 +124,7 @@ def anypython(request): pytest.skip(f"no {name} found") if "execmodel" in request.fixturenames and name != "sys.executable": backend = request.getfixturevalue("execmodel").backend - if backend != "thread": + if backend not in ("thread", "main_thread_only"): pytest.xfail(f"cannot run {backend!r} execmodel with bare {name}") return executable @@ -173,9 +173,11 @@ def gw(request, execmodel, group): return gw -@pytest.fixture(params=["thread", "eventlet", "gevent"], scope="session") +@pytest.fixture( + params=["thread", "main_thread_only", "eventlet", "gevent"], scope="session" +) def execmodel(request): - if request.param != "thread": + if request.param not in ("thread", "main_thread_only"): pytest.importorskip(request.param) if request.param in ("eventlet", "gevent") and sys.platform == "win32": pytest.xfail(request.param + " does not work on win32") diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 809a13d9..ee4ce375 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -525,3 +525,82 @@ def sendback(channel): if interleave_getstatus: print(gw.remote_status()) assert ch.receive(timeout=0.5) == 1234 + + +def test_assert_main_thread_only(execmodel, makegateway): + if execmodel.backend != "main_thread_only": + pytest.skip("can only run with main_thread_only") + + gw = makegateway(spec=f"execmodel={execmodel.backend}//popen") + + try: + # Submit multiple remote_exec requests in quick succession and + # assert that all tasks execute in the main thread. It is + # necessary to call receive on each channel before the next + # remote_exec call, since the channel will raise an error if + # concurrent remote_exec requests are submitted as in + # test_main_thread_only_concurrent_remote_exec_deadlock. + for i in range(10): + ch = gw.remote_exec( + """ + import time, threading + time.sleep(0.02) + channel.send(threading.current_thread() is threading.main_thread()) + """ + ) + + try: + res = ch.receive() + finally: + ch.close() + # This doesn't actually block because we closed + # the channel already, but it does check for remote + # errors and raise them. + ch.waitclose() + if res is not True: + pytest.fail("remote raised\n%s" % res) + finally: + gw.exit() + gw.join() + + +def test_main_thread_only_concurrent_remote_exec_deadlock(execmodel, makegateway): + if execmodel.backend != "main_thread_only": + pytest.skip("can only run with main_thread_only") + + gw = makegateway(spec=f"execmodel={execmodel.backend}//popen") + channels = [] + try: + # Submit multiple remote_exec requests in quick succession and + # assert that MAIN_THREAD_ONLY_DEADLOCK_TEXT is raised if + # concurrent remote_exec requests are submitted for the + # main_thread_only execmodel (as compensation for the lack of + # back pressure in remote_exec calls which do not attempt to + # block until the remote main thread is idle). + for i in range(2): + channels.append( + gw.remote_exec( + """ + import threading + channel.send(threading.current_thread() is threading.main_thread()) + # Wait forever, ensuring that the deadlock case triggers. + channel.gateway.execmodel.Event().wait() + """ + ) + ) + + expected_results = ( + True, + execnet.gateway_base.MAIN_THREAD_ONLY_DEADLOCK_TEXT, + ) + for expected, ch in zip(expected_results, channels): + try: + res = ch.receive() + except execnet.RemoteError as e: + res = e.formatted + assert res == expected + finally: + for ch in channels: + ch.close() + gw.exit() + gw.join() diff --git a/testing/test_multi.py b/testing/test_multi.py index 79861b11..3996a964 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -223,8 +223,9 @@ def test_terminate_with_proxying(self): group.terminate(1.0) +@pytest.mark.xfail(reason="active_count() has been broken for some time") def test_safe_terminate(execmodel): - if execmodel.backend != "threading": + if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail( "execution model %r does not support task count" % execmodel.backend ) @@ -246,8 +247,9 @@ def kill(): assert execmodel.active_count() == active +@pytest.mark.xfail(reason="active_count() has been broken for some time") def test_safe_terminate2(execmodel): - if execmodel.backend != "threading": + if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail( "execution model %r does not support task count" % execmodel.backend ) diff --git a/testing/test_termination.py b/testing/test_termination.py index 282c1979..5da516c4 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -36,7 +36,7 @@ def doit(): def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): - if execmodel.backend != "thread": + if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail("test and execnet not compatible to greenlets yet") gw = makegateway("popen") q = execmodel.queue.Queue() @@ -97,8 +97,12 @@ def test_close_initiating_remote_no_error(testdir, anypython): def test_terminate_implicit_does_trykill(testdir, anypython, capfd, pool): - if pool.execmodel != "thread": + if pool.execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail("only os threading model supported") + if sys.version_info >= (3, 12): + pytest.xfail( + "since python3.12 this test triggers RuntimeError: can't create new thread at interpreter shutdown" + ) p = testdir.makepyfile( """ import sys diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 4d1edd8c..0162e2ea 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -164,7 +164,7 @@ def wait_then_put(): def test_primary_thread_integration(execmodel): - if execmodel.backend != "thread": + if execmodel.backend not in ("thread", "main_thread_only"): with pytest.raises(ValueError): WorkerPool(execmodel=execmodel, hasprimary=True) return @@ -188,7 +188,7 @@ def func(): def test_primary_thread_integration_shutdown(execmodel): - if execmodel.backend != "thread": + if execmodel.backend not in ("thread", "main_thread_only"): pytest.skip("can only run with threading") pool = WorkerPool(execmodel=execmodel, hasprimary=True) queue = execmodel.queue.Queue() From 8f7f3a0ea139bbefee194c00ab2d3edbc48ee9ec Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Mon, 19 Feb 2024 19:55:52 -0800 Subject: [PATCH 104/275] Popen2IO: Fix "Bad file descriptor" error Fix init_popen_io to leave behind a sane state (0 and 1 file descriptors open), in order to prevent "Bad file descriptor" errors. Also fix test_stdouterrin_setnull to restore stdin state, while relying on capfd to do this for stdout and stderr. The "Bad file descriptor" error doesn't trigger reliably because it's triggered by garbage collection of the Popen2IO instance returned from init_popen_io as shown in this job: https://github.com/pytest-dev/execnet/actions/runs/7955155978/job/21716386705?pr=243 =================================== FAILURES =================================== __________________ test_stdouterrin_setnull[main_thread_only] __________________ execmodel = capfd = <_pytest.capture.CaptureFixture object at 0x7fba8c333990> @pytest.mark.skipif("not hasattr(os, 'dup')") def test_stdouterrin_setnull(execmodel, capfd): gateway_base.init_popen_io(execmodel) os.write(1, b"hello") > os.read(0, 1) E OSError: [Errno 9] Bad file descriptor testing/test_basics.py:254: OSError --- src/execnet/gateway_base.py | 20 +++++++++++--------- testing/test_basics.py | 25 +++++++++++++++++++------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 954423de..2e2859f9 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -61,7 +61,7 @@ def sleep(self, delay): raise NotImplementedError() @abc.abstractmethod - def fdopen(self, fd, mode, bufsize=1): + def fdopen(self, fd, mode, bufsize=1, closefd=True): raise NotImplementedError() @abc.abstractmethod @@ -113,10 +113,10 @@ def start(self, func, args=()): return _thread.start_new_thread(func, args) - def fdopen(self, fd, mode, bufsize=1): + def fdopen(self, fd, mode, bufsize=1, closefd=True): import os - return os.fdopen(fd, mode, bufsize, encoding="utf-8") + return os.fdopen(fd, mode, bufsize, encoding="utf-8", closefd=closefd) def Lock(self): import threading @@ -174,10 +174,10 @@ def start(self, func, args=()): return eventlet.spawn_n(func, *args) - def fdopen(self, fd, mode, bufsize=1): + def fdopen(self, fd, mode, bufsize=1, closefd=True): import eventlet.green.os - return eventlet.green.os.fdopen(fd, mode, bufsize) + return eventlet.green.os.fdopen(fd, mode, bufsize, closefd=closefd) def Lock(self): import eventlet.green.threading @@ -231,11 +231,11 @@ def start(self, func, args=()): return gevent.spawn(func, *args) - def fdopen(self, fd, mode, bufsize=1): + def fdopen(self, fd, mode, bufsize=1, closefd=True): # XXX import gevent.fileobject - return gevent.fileobject.FileObjectThread(fd, mode, bufsize) + return gevent.fileobject.FileObjectThread(fd, mode, bufsize, closefd=closefd) def Lock(self): import gevent.lock @@ -1682,8 +1682,10 @@ def init_popen_io(execmodel): os.dup2(fd, 2) os.close(fd) io = Popen2IO(stdout, stdin, execmodel) - sys.stdin = execmodel.fdopen(0, "r", 1) - sys.stdout = execmodel.fdopen(1, "w", 1) + # Use closefd=False since 0 and 1 are shared with + # sys.__stdin__ and sys.__stdout__. + sys.stdin = execmodel.fdopen(0, "r", 1, closefd=False) + sys.stdout = execmodel.fdopen(1, "w", 1, closefd=False) return io diff --git a/testing/test_basics.py b/testing/test_basics.py index 321e18f1..26816f3e 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -249,12 +249,25 @@ class Arg: @pytest.mark.skipif("not hasattr(os, 'dup')") def test_stdouterrin_setnull(execmodel, capfd): - gateway_base.init_popen_io(execmodel) - os.write(1, b"hello") - os.read(0, 1) - out, err = capfd.readouterr() - assert not out - assert not err + # Backup and restore stdin state, and rely on capfd to handle + # this for stdout and stderr. + orig_stdin = sys.stdin + orig_stdin_fd = os.dup(0) + try: + # The returned Popen2IO instance can be garbage collected + # prematurely since we don't hold a reference here, but we + # tolerate this because it is intended to leave behind a + # sane state afterwards. + gateway_base.init_popen_io(execmodel) + os.write(1, b"hello") + os.read(0, 1) + out, err = capfd.readouterr() + assert not out + assert not err + finally: + sys.stdin = orig_stdin + os.dup2(orig_stdin_fd, 0) + os.close(orig_stdin_fd) class PseudoChannel: From c5cef445df8ee98128de5a05a7fc06417f4c5d5d Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Thu, 22 Feb 2024 14:15:48 -0800 Subject: [PATCH 105/275] Add #243 to CHANGELOG --- CHANGELOG.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0f0d67a..dceb30c1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,20 @@ +2.1.0 (UNRELEASED) +------------------ + +* `#243 `__: Added ``main_thread_only`` + execmodel which is derived from the thread execmodel and only executes ``remote_exec`` + calls in the main thread. + + Callers of ``remote_exec`` must use the returned channel to wait for a task to complete + before they call remote_exec again, otherwise the ``remote_exec`` call will fail with a + ``concurrent remote_exec would cause deadlock`` error. The main_thread_only execmodel + provides solutions for `#96 `__ and + `pytest-dev/pytest-xdist#620 `__ + (pending a new `pytest-xdist` release). + + Also fixed ``init_popen_io`` to use ``closefd=False`` for shared stdin and stdout file + descriptors, preventing ``Bad file descriptor`` errors triggered by test_stdouterrin_setnull. + 2.0.2 (2023-07-09) ------------------ From 1627d7466a2b13d76ee03f6416d2f7bd6b46638a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 10:54:44 +0200 Subject: [PATCH 106/275] Remove support for Python 3.7, add official support for Python 3.12 3.7 is EOL. --- .github/workflows/test.yml | 6 ++---- .pre-commit-config.yaml | 2 +- CHANGELOG.rst | 5 +++++ doc/install.rst | 2 +- pyproject.toml | 6 +++--- testing/conftest.py | 2 +- tox.ini | 2 +- 7 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cc7af8b..c388cf57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: os: [ windows-latest, ubuntu-latest ] - python: [ "3.7","3.8","3.10","3.11", "pypy-3.7" ] + python: [ "3.8","3.10","3.11","3.12", "pypy-3.8" ] steps: - uses: actions/checkout@v4 @@ -53,9 +53,7 @@ jobs: python-version: ${{ matrix.python }} - name: Install tox - run: | - python -m pip install --upgrade pip - pip install tox + run: pip install tox - name: Test shell: bash diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 75b18443..ea3571c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: rev: v3.15.1 hooks: - id: pyupgrade - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.2 hooks: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0f0d67a..5c3148ef 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +2.1.0 (UNRELEASED) + +* Removed support for Python 3.7. +* Added official support for Python 3.12. + 2.0.2 (2023-07-09) ------------------ diff --git a/doc/install.rst b/doc/install.rst index 04f1313a..e7bb7038 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,7 +1,7 @@ Info in a nutshell ==================== -**Pythons**: 3.7+, PyPy 3 +**Pythons**: 3.8+, PyPy 3 **Operating systems**: Linux, Windows, OSX, Unix diff --git a/pyproject.toml b/pyproject.toml index 50efb9f1..cd6e5abb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dynamic = ["version"] description = "execnet: rapid multi-Python deployment" readme = {"file" = "README.rst", "content-type" = "text/x-rst"} license = "MIT" -requires-python = ">=3.7" +requires-python = ">=3.8" authors = [ { name = "holger krekel and others" }, ] @@ -22,11 +22,11 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries", @@ -68,4 +68,4 @@ include = [ ] [tool.mypy] -python_version = "3.7" +python_version = "3.8" diff --git a/testing/conftest.py b/testing/conftest.py index 9ec44d30..86bd93e0 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -109,7 +109,7 @@ def pytest_generate_tests(metafunc): metafunc.parametrize("gw", gwtypes, indirect=True) -@lru_cache() +@lru_cache def getexecutable(name): if name == "sys.executable": return sys.executable diff --git a/tox.ini b/tox.ini index 2449698a..4c743a66 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py{37,38,39,310,311,pypy37},docs,linting +envlist=py{38,39,310,311,312,pypy38},docs,linting isolated_build = true [testenv] From b5f08e276458fcf8b34b09350614c0618dcd3763 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 28 Apr 2023 11:13:28 +0300 Subject: [PATCH 107/275] script: remove xx.py It is unclear what the `register` module is (it is not the one on PyPI) or what is the purpose of this script. --- src/execnet/script/xx.py | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 src/execnet/script/xx.py diff --git a/src/execnet/script/xx.py b/src/execnet/script/xx.py deleted file mode 100644 index 687cc81e..00000000 --- a/src/execnet/script/xx.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys - -import register -import rlcompleter2 - -rlcompleter2.setup() - -try: - hostport = sys.argv[1] -except BaseException: - hostport = ":8888" -gw = register.ServerGateway(hostport) From 5cdc09f9f47ba9851a8103d53a8ec4348642e491 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 11:40:15 +0200 Subject: [PATCH 108/275] Avoid using sys.exc_info In Python 3 it's better to handle exception objects directly, they now carry their traceback. --- src/execnet/gateway.py | 6 ++-- src/execnet/gateway_base.py | 56 ++++++++++++++---------------- src/execnet/gateway_socket.py | 4 +-- src/execnet/multi.py | 7 ++-- src/execnet/script/socketserver.py | 5 ++- testing/test_basics.py | 10 +++--- 6 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 5f066fb0..55ce16fa 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -5,7 +5,6 @@ import inspect import linecache import os -import sys import textwrap import types @@ -56,10 +55,9 @@ def exit(self): self._send(Message.GATEWAY_TERMINATE) self._trace("--> io.close_write") self._io.close_write() - except (ValueError, EOFError, OSError): - v = sys.exc_info()[1] + except (ValueError, EOFError, OSError) as exc: self._trace("io-error: could not send termination sequence") - self._trace(" exception: %r" % v) + self._trace(" exception: %r" % exc) def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): """ diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index c055d64f..075274ca 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -283,7 +283,7 @@ def get(self, timeout=None): try: return self._result except AttributeError: - raise self._excinfo[1].with_traceback(self._excinfo[2]) + raise self._exc def waitfinish(self, timeout=None): if not self._result_ready.wait(timeout): @@ -294,10 +294,8 @@ def run(self): try: try: self._result = func(*args, **kwargs) - except BaseException: - # sys may be already None when shutting down the interpreter - if sys is not None: - self._excinfo = sys.exc_info() + except BaseException as exc: + self._exc = exc finally: self._result_ready.set() self.running = False @@ -437,10 +435,9 @@ def trace(*msg): line = " ".join(map(str, msg)) debugfile.write(line + "\n") debugfile.flush() - except Exception: + except Exception as exc: try: - v = sys.exc_info()[1] - sys.stderr.write(f"[{pid}] exception during tracing: {v!r}\n") + sys.stderr.write(f"[{pid}] exception during tracing: {exc!r}\n") except Exception: pass # nothing we can do, likely interpreter-shutdown @@ -507,8 +504,7 @@ def from_io(io): header = io.read(9) # type 1, channel 4, payload 4 if not header: raise EOFError("empty read") - except EOFError: - e = sys.exc_info()[1] + except EOFError as e: raise EOFError("couldn't load message header, " + e.args[0]) msgtype, channel, payload = struct.unpack("!bii", header) return Message(msgtype, channel, io.read(payload)) @@ -594,14 +590,20 @@ class GatewayReceivedTerminate(Exception): """Receiverthread got termination message.""" -def geterrortext(excinfo, format_exception=traceback.format_exception, sysex=sysex): +def geterrortext( + exc: BaseException, + format_exception=traceback.format_exception, + sysex=sysex, +) -> str: try: - l = format_exception(*excinfo) # noqa:E741 + # In py310, can change this to: + # l = format_exception(exc) + l = format_exception(type(exc), exc, exc.__traceback__) errortext = "".join(l) except sysex: raise except BaseException: - errortext = f"{excinfo[0].__name__}: {excinfo[1]}" + errortext = f"{type(exc).__name__}: {exc}" return errortext @@ -937,10 +939,9 @@ def _local_receive(self, id, data): try: data = loads_internal(data, channel, strconfig) callback(data) # even if channel may be already closed - except Exception: - excinfo = sys.exc_info() - self.gateway._trace("exception during callback: %s" % excinfo[1]) - errortext = self.gateway._geterrortext(excinfo) + except Exception as exc: + self.gateway._trace("exception during callback: %s" % exc) + errortext = self.gateway._geterrortext(exc) self.gateway._send( Message.CHANNEL_CLOSE_ERROR, id, dumps_internal(errortext) ) @@ -1017,7 +1018,6 @@ def readline(self): class BaseGateway: - exc_info = sys.exc_info _sysex = sysex id = "" @@ -1054,11 +1054,11 @@ def log(*msg): del msg except (KeyboardInterrupt, GatewayReceivedTerminate): pass - except EOFError: + except EOFError as exc: log("EOF without prior gateway termination message") - self._error = self.exc_info()[1] - except Exception: - log(self._geterrortext(self.exc_info())) + self._error = exc + except Exception as exc: + log(self._geterrortext(exc)) log("finishing receiving thread") # wake up and terminate any execution waiting to receive self._channelfactory._finished_receiving() @@ -1079,8 +1079,7 @@ def _send(self, msgcode, channelid=0, data=b""): try: message.to_io(self._io) self._trace("sent", message) - except (OSError, ValueError): - e = sys.exc_info()[1] + except (OSError, ValueError) as e: self._trace("failed to send", message, e) # ValueError might be because the IO is already closed raise OSError("cannot send (already closed?)") @@ -1166,12 +1165,11 @@ def executetask(self, item): except KeyboardInterrupt: channel.close(INTERRUPT_TEXT) raise - except BaseException: - excinfo = self.exc_info() - if not isinstance(excinfo[1], EOFError): + except BaseException as exc: + if not isinstance(exc, EOFError): if not channel.gateway._channelfactory.finished: - self._trace(f"got exception: {excinfo[1]!r}") - errortext = self._geterrortext(excinfo) + self._trace(f"got exception: {exc!r}") + errortext = self._geterrortext(exc) channel.close(errortext) return self._trace("ignoring EOFError because receiving finished") diff --git a/src/execnet/gateway_socket.py b/src/execnet/gateway_socket.py index 4379e015..3d3a9b6e 100644 --- a/src/execnet/gateway_socket.py +++ b/src/execnet/gateway_socket.py @@ -84,6 +84,6 @@ def create_io(spec, group, execmodel): io.remoteaddress = "%s:%d" % (host, port) try: sock.connect((host, port)) - except execmodel.socket.gaierror: - raise HostNotFound(str(sys.exc_info()[1])) + except execmodel.socket.gaierror as e: + raise HostNotFound() from e return io diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 7629ddb3..194e3365 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -4,7 +4,6 @@ (c) 2008-2014, Holger Krekel and others """ import atexit -import sys from functools import partial from threading import Lock @@ -285,11 +284,11 @@ def waitclose(self): for ch in self._channels: try: ch.waitclose() - except ch.RemoteError: + except ch.RemoteError as exc: if first is None: - first = sys.exc_info() + first = exc if first: - raise first[1].with_traceback(first[2]) + raise first def safe_terminate(execmodel, timeout, list_of_paired_functions): diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 034b4346..95f93289 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -94,14 +94,13 @@ def startserver(serversock, loop=False): exec_from_one_connection(serversock) except (KeyboardInterrupt, SystemExit): raise - except BaseException: + except BaseException as exc: if debug: import traceback traceback.print_exc() else: - excinfo = sys.exc_info() - print_("got exception", excinfo[1]) + print_("got exception", exc) os.chdir(execute_path) if not loop: break diff --git a/testing/test_basics.py b/testing/test_basics.py index 321e18f1..a4a77661 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -229,16 +229,14 @@ def test_geterrortext(checker): out = checker.run_check( inspect.getsource(gateway_base) + """ -class Arg: +class Arg(Exception): pass -errortext = geterrortext((Arg, "1", 4)) +errortext = geterrortext(Arg()) assert "Arg" in errortext -import sys try: raise ValueError("17") -except ValueError: - excinfo = sys.exc_info() - s = geterrortext(excinfo) +except ValueError as exc: + s = geterrortext(exc) assert "17" in s print ("all passed") """ From 9af6b934eb8781bcf2750bd5ea07b73029e906c1 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 13:20:44 +0200 Subject: [PATCH 109/275] Remove a workaround for Jython Jython is no longer supported by execnet. --- src/execnet/gateway_bootstrap.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index ba5bf104..a924090e 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -78,20 +78,6 @@ def sendexec(io, *sources): io.write((repr(source) + "\n").encode("utf-8")) -def fix_pid_for_jython_popen(gw): - """ - fix for jython 2.5.1 - """ - spec, io = gw.spec, gw._io - if spec.popen and not spec.via: - # XXX: handle the case of remote being jython - # and not having the popen pid - if io.popen.pid is None: - io.popen.pid = gw.remote_exec( - "import os; channel.send(os.getpid())" - ).receive() - - def bootstrap(io, spec): if spec.popen: if spec.via or spec.python: @@ -105,5 +91,4 @@ def bootstrap(io, spec): else: raise ValueError("unknown gateway type, can't bootstrap") gw = Gateway(io, spec) - fix_pid_for_jython_popen(gw) return gw From 7914459ce866af67e5797e68a5d7dbc6b91e3aad Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 15:42:47 +0200 Subject: [PATCH 110/275] testing: fix broken test `SshGateway` no longer exists. --- testing/test_xspec.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 2218e736..9fccc5b8 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -189,13 +189,7 @@ def test_popen_chdir_newsub(self, monkeypatch, tmp_path, makegateway): def test_ssh(self, specssh, makegateway): sshhost = specssh.ssh gw = makegateway("ssh=%s//id=ssh1" % sshhost) - rinfo = gw._rinfo() assert gw.id == "ssh1" - gw2 = execnet.SshGateway(sshhost) - rinfo2 = gw2._rinfo() - assert rinfo.executable == rinfo2.executable - assert rinfo.cwd == rinfo2.cwd - assert rinfo.version_info == rinfo2.version_info def test_vagrant(self, makegateway): vagrant_bin = shutil.which("vagrant") From 4ef79b5bc50f2c1de7ee07d8c78947c1b53d8d66 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 18:54:16 +0200 Subject: [PATCH 111/275] testing: switch testdir -> pytester testdir is using the legacy py.path. --- testing/test_termination.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/test_termination.py b/testing/test_termination.py index 5da516c4..6abd444e 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -70,8 +70,8 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): assert str(pid) not in output.stdout, output -def test_close_initiating_remote_no_error(testdir, anypython): - p = testdir.makepyfile( +def test_close_initiating_remote_no_error(pytester, anypython): + p = pytester.makepyfile( """ import sys sys.path.insert(0, sys.argv[1]) @@ -96,14 +96,14 @@ def test_close_initiating_remote_no_error(testdir, anypython): assert not lines -def test_terminate_implicit_does_trykill(testdir, anypython, capfd, pool): +def test_terminate_implicit_does_trykill(pytester, anypython, capfd, pool): if pool.execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail("only os threading model supported") if sys.version_info >= (3, 12): pytest.xfail( "since python3.12 this test triggers RuntimeError: can't create new thread at interpreter shutdown" ) - p = testdir.makepyfile( + p = pytester.makepyfile( """ import sys sys.path.insert(0, %r) From 63cd953c19b2cb8cd38ec6db7319165364dd40ad Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 19:18:22 +0200 Subject: [PATCH 112/275] Always explicitly chain exceptions So we have "exception 1 caused by exception 2" instead of "during handling of exception 2, another exception was raised". --- src/execnet/gateway.py | 4 ++-- src/execnet/gateway_base.py | 22 +++++++++++----------- src/execnet/gateway_bootstrap.py | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 55ce16fa..2a3714b4 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -201,8 +201,8 @@ def _source_of_function(function): try: source = inspect.getsource(function) - except OSError: - raise ValueError("can't find source file for %s" % function) + except OSError as e: + raise ValueError("can't find source file for %s" % function) from e source = textwrap.dedent(source) # just for inner functions diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index be2bc13b..d863bbcf 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -289,7 +289,7 @@ def get(self, timeout=None): try: return self._result except AttributeError: - raise self._exc + raise self._exc from None def waitfinish(self, timeout=None): if not self._result_ready.wait(timeout): @@ -528,7 +528,7 @@ def from_io(io): if not header: raise EOFError("empty read") except EOFError as e: - raise EOFError("couldn't load message header, " + e.args[0]) + raise EOFError("couldn't load message header, " + e.args[0]) from None msgtype, channel, payload = struct.unpack("!bii", header) return Message(msgtype, channel, io.read(payload)) @@ -851,7 +851,7 @@ def receive(self, timeout=None): try: x = itemqueue.get(timeout=timeout) except self.gateway.execmodel.queue.Empty: - raise self.TimeoutError("no item after %r seconds" % timeout) + raise self.TimeoutError("no item after %r seconds" % timeout) from None if x is ENDMARKER: itemqueue.put(x) # for other receivers raise self._getremoteerror() or EOFError() @@ -865,7 +865,7 @@ def next(self): try: return self.receive() except EOFError: - raise StopIteration + raise StopIteration from None __next__ = next @@ -1108,7 +1108,7 @@ def _send(self, msgcode, channelid=0, data=b""): except (OSError, ValueError) as e: self._trace("failed to send", message, e) # ValueError might be because the IO is already closed - raise OSError("cannot send (already closed?)") + raise OSError("cannot send (already closed?)") from e def _local_schedulexec(self, channel, sourcetask): channel.close("execution disallowed") @@ -1316,12 +1316,12 @@ def load(self, versioned=False): loader = self.num2func[opcode] except KeyError: raise LoadError( - "unknown opcode %r - " "wire protocol corruption?" % (opcode,) - ) + "unknown opcode %r - wire protocol corruption?" % (opcode,) + ) from None loader(self) except _Stop: if len(self.stack) != 1: - raise LoadError("internal unserialization error") + raise LoadError("internal unserialization error") from None return self.stack.pop(0) else: raise LoadError("didn't get STOP") @@ -1550,7 +1550,7 @@ def _save(self, obj): methodname = "save_" + tp.__name__ meth = getattr(self.__class__, methodname, None) if meth is None: - raise DumpError(f"can't serialize {tp}") + raise DumpError(f"can't serialize {tp}") from None dispatch = self._dispatch[tp] = meth dispatch(self, obj) @@ -1574,8 +1574,8 @@ def save_str(self, s): def _write_unicode_string(self, s): try: as_bytes = s.encode("utf-8") - except UnicodeEncodeError: - raise DumpError("strings must be utf-8 encodable") + except UnicodeEncodeError as e: + raise DumpError("strings must be utf-8 encodable") from e self._write_byte_sequence(as_bytes) def _write_byte_sequence(self, bytes_): diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index a924090e..dfa632ea 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -50,7 +50,7 @@ def bootstrap_exec(io, spec): except EOFError: ret = io.wait() if ret == 255: - raise HostNotFound(io.remoteaddress) + raise HostNotFound(io.remoteaddress) from None def bootstrap_socket(io, id): From 0c118227800bda7aae1f4cae17a3841054da4753 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 19:09:35 +0200 Subject: [PATCH 113/275] Enable more lints List adapted from pytest's configuration. --- .pre-commit-config.yaml | 7 ------- pyproject.toml | 31 ++++++++++++++++++++++++++++-- src/execnet/gateway.py | 2 +- src/execnet/gateway_base.py | 8 +++----- src/execnet/rsync.py | 4 ++-- src/execnet/rsync_remote.py | 2 +- src/execnet/script/shell.py | 2 +- src/execnet/script/socketserver.py | 4 ++-- testing/conftest.py | 4 ++-- testing/test_basics.py | 1 + testing/test_gateway.py | 2 +- testing/test_serializer.py | 3 +-- testing/test_termination.py | 2 +- testing/test_threadpool.py | 2 +- testing/test_xspec.py | 5 +++-- 15 files changed, 49 insertions(+), 30 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea3571c9..0202c21e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,14 +11,7 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - id: check-yaml -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 - hooks: - - id: pyupgrade - args: [--py38-plus] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.2.2 hooks: diff --git a/pyproject.toml b/pyproject.toml index cd6e5abb..c05e314a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,8 +46,35 @@ testing = [ Homepage = "https://execnet.readthedocs.io/en/latest/" [tool.ruff.lint] -ignore = ["E741"] -extend-select = ["I001"] +extend-select = [ + "B", # bugbear + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PYI", # flake8-pyi + "UP", # pyupgrade + "RUF", # ruff + "W", # pycodestyle + "PIE", # flake8-pie + "PGH", # pygrep-hooks + "PLE", # pylint error + "PLW", # pylint warning +] +ignore = [ + # bugbear ignore + "B007", # Loop control variable `i` not used within loop body + "B011", # Do not `assert False` (`python -O` removes these calls) + # pycodestyle ignore + "E501", # Line too long + "E741", # Ambiguous variable name + # ruff ignore + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + # pylint ignore + "PLW0603", # Using the global statement + "PLW0120", # remove the else and dedent its contents + "PLW2901", # for loop variable overwritten by assignment target + "PLR5501", # Use `elif` instead of `else` then `if` +] [tool.ruff.lint.isort] force-single-line = true diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 2a3714b4..870897ee 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -143,7 +143,7 @@ def __init__(self, kwargs): self.__dict__.update(kwargs) def __repr__(self): - info = ", ".join("%s=%s" % item for item in sorted(self.__dict__.items())) + info = ", ".join(f"{k}={v}" for k, v in sorted(self.__dict__.items())) return "" % info diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index d863bbcf..9141f7e6 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -542,9 +542,7 @@ def received(self, gateway): def __repr__(self): name = self._types[self.msgcode][0] - return "".format( - name, self.channelid, len(self.data) - ) + return f"" def _status(message, gateway): # we use the channelid to send back information @@ -1316,7 +1314,7 @@ def load(self, versioned=False): loader = self.num2func[opcode] except KeyError: raise LoadError( - "unknown opcode %r - wire protocol corruption?" % (opcode,) + f"unknown opcode {opcode!r} - wire protocol corruption?" ) from None loader(self) except _Stop: @@ -1593,7 +1591,7 @@ def _save_integral(self, i, short_op, long_op): def save_int(self, i): self._save_integral(i, opcode.INT, opcode.LONGINT) - def save_long(self, l): # noqa:E741 + def save_long(self, l): self._save_integral(l, opcode.LONG, opcode.LONGLONG) def save_float(self, flt): diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 1484d49d..8afa4dac 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -24,7 +24,7 @@ class RSync: def __init__(self, sourcedir, callback=None, verbose=True): self._sourcedir = str(sourcedir) self._verbose = verbose - assert callback is None or hasattr(callback, "__call__") + assert callback is None or callable(callback) self._callback = callback self._channels = {} self._receivequeue = Queue() @@ -168,7 +168,7 @@ def _send_directory(self, path): names.append(name) subpaths.append(p) mode = os.lstat(path).st_mode - self._broadcast([mode] + names) + self._broadcast([mode, *names]) for p in subpaths: self._send_directory_structure(p) diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 3dbe2d84..758c4ca4 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -42,7 +42,7 @@ def receive_directory_structure(path, relcomponents): entrynames = {} for entryname in msg: destpath = os.path.join(path, entryname) - receive_directory_structure(destpath, relcomponents + [entryname]) + receive_directory_structure(destpath, [*relcomponents, entryname]) entrynames[entryname] = True if options.get("delete"): for othername in os.listdir(path): diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index a80b80ea..c69a57ef 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -55,7 +55,7 @@ def run(self): while 1: try: - clientfile.write("%s %s >>> " % loc) + clientfile.write("{} {} >>> ".format(*loc)) clientfile.flush() line = filein.readline() if not line: diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 95f93289..f56f5e76 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -48,7 +48,7 @@ def print_(*args): def exec_from_one_connection(serversock): print_(progname, "Entering Accept loop", serversock.getsockname()) clientsock, address = serversock.accept() - print_(progname, "got new connection from %s %s" % address) + print_(progname, "got new connection from {} {}".format(*address)) clientfile = clientsock.makefile("rb") print_("reading line") # rstrip so that we can use \r\n for telnet testing @@ -60,7 +60,7 @@ def exec_from_one_connection(serversock): co = compile(source + "\n", "", "exec") print_(progname, "compiled source, executing") try: - exec_(co, g) # noqa + exec_(co, g) # noqa: F821 finally: print_(progname, "finished executing code") # background thread might hold a reference to this (!?) diff --git a/testing/conftest.py b/testing/conftest.py index 2ee21202..133c1db4 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -152,8 +152,8 @@ def gw(request, execmodel, group): proxygw = group.makegateway("popen//id=%s" % pname) # assert group['proxygw'].remote_status().receiving gw = group.makegateway( - "socket//id=socket//installvia=%s" - "//execmodel=%s" % (pname, execmodel.backend) + f"socket//id=socket//installvia={pname}" + f"//execmodel={execmodel.backend}" ) gw.proxygw = proxygw assert pname in group diff --git a/testing/test_basics.py b/testing/test_basics.py index b9433291..e52007b8 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -1,3 +1,4 @@ +# ruff: noqa: B018 from __future__ import annotations import inspect diff --git a/testing/test_gateway.py b/testing/test_gateway.py index ee4ce375..a2567485 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -494,7 +494,7 @@ def test_no_tracing_by_default(self): ], ) def test_popen_args(spec, expected_args): - expected_args = expected_args + ["-u", "-c", gateway_io.popen_bootstrapline] + expected_args = [*expected_args, "-u", "-c", gateway_io.popen_bootstrapline] args = gateway_io.popen_args(execnet.XSpec(spec)) assert args == expected_args diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 9b11d1b7..ca2fbf9d 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -50,9 +50,8 @@ def load(self, data: bytes): res = subprocess.run( [str(self.executable), str(script_file)], capture_output=True, + check=True, ) - if res.returncode: - raise ValueError(res.stderr) return res.stdout.decode("ascii").splitlines() diff --git a/testing/test_termination.py b/testing/test_termination.py index 6abd444e..39108717 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -66,7 +66,7 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): gw.remote_exec("channel.receive()") gw._group.terminate() command = ["ps", "-p", str(pid)] - output = subprocess.run(command, capture_output=True, text=True) + output = subprocess.run(command, capture_output=True, text=True, check=False) assert str(pid) not in output.stdout, output diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 0162e2ea..98187349 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -60,7 +60,7 @@ def first(): def test_waitfinish_on_reply(pool): - l = [] # noqa:E741 + l = [] reply = pool.spawn(lambda: l.append(1)) reply.waitfinish() assert l == [1] diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 9fccc5b8..30552dca 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -200,6 +200,7 @@ def test_vagrant(self, makegateway): capture_output=True, encoding="utf-8", errors="replace", + check=True, ).stdout print(res) if ",default,state,shutoff\n" in res: @@ -211,8 +212,8 @@ def test_vagrant(self, makegateway): gw = makegateway("vagrant_ssh=default//python=python3") rinfo = gw._rinfo() - rinfo.cwd == "/home/vagrant" - rinfo.executable == "/usr/bin/python" + assert rinfo.cwd == "/home/vagrant" + assert rinfo.executable == "/usr/bin/python" def test_socket(self, specsocket, makegateway): gw = makegateway("socket=%s//id=sock1" % specsocket.socket) From c58d485ff97d401dd02516cce511fefcdd0fd411 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 24 Feb 2024 19:32:42 +0200 Subject: [PATCH 114/275] Add initial typing The typing is still not optimal and not ready to be exposed, but can serve as an initial basis. --- .pre-commit-config.yaml | 4 + doc/example/redirect_remote_output.py | 2 +- doc/example/sysinfo.py | 3 +- pyproject.toml | 15 + src/execnet/gateway.py | 61 ++- src/execnet/gateway_base.py | 505 ++++++++++++++-------- src/execnet/gateway_bootstrap.py | 16 +- src/execnet/gateway_io.py | 104 +++-- src/execnet/gateway_socket.py | 36 +- src/execnet/multi.py | 114 +++-- src/execnet/rsync.py | 110 +++-- src/execnet/rsync_remote.py | 22 +- src/execnet/script/shell.py | 23 +- src/execnet/script/socketserver.py | 33 +- src/execnet/script/socketserverservice.py | 14 +- src/execnet/xspec.py | 34 +- testing/conftest.py | 44 +- testing/test_basics.py | 149 ++++--- testing/test_channel.py | 108 +++-- testing/test_compatibility_regressions.py | 2 +- testing/test_gateway.py | 140 +++--- testing/test_multi.py | 85 ++-- testing/test_rsync.py | 47 +- testing/test_serializer.py | 32 +- testing/test_termination.py | 38 +- testing/test_threadpool.py | 62 +-- testing/test_xspec.py | 73 ++-- 27 files changed, 1152 insertions(+), 724 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0202c21e..7c6175e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,3 +29,7 @@ repos: rev: 'v1.8.0' hooks: - id: mypy + additional_dependencies: + - pytest + - types-pywin32 + - types-gevent diff --git a/doc/example/redirect_remote_output.py b/doc/example/redirect_remote_output.py index 8900a658..d5fb43e0 100644 --- a/doc/example/redirect_remote_output.py +++ b/doc/example/redirect_remote_output.py @@ -26,7 +26,7 @@ def write(data): print("received:", repr(data)) -outchan.setcallback(write) +outchan.setcallback(write) # type: ignore[attr-defined] gw.remote_exec( """ diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index ce3bb9ce..f13d1199 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -11,7 +11,6 @@ import execnet - parser = optparse.OptionParser(usage=__doc__) parser.add_option( "-f", @@ -129,7 +128,7 @@ def getinfo(sshname, ssh_config=None, loginfo=sys.stdout): if ssh_config: spec = f"ssh=-F {ssh_config} {sshname}" else: - spec += "ssh=%s" % sshname + spec = "ssh=%s" % sshname debug("connecting to", repr(spec)) try: gw = execnet.makegateway(spec) diff --git a/pyproject.toml b/pyproject.toml index c05e314a..3a7ffd44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,3 +96,18 @@ include = [ [tool.mypy] python_version = "3.8" +mypy_path = ["src"] +files = ["src", "testing"] +strict = true +warn_unreachable = true +warn_unused_ignores = false +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_incomplete_defs = false + +[[tool.mypy.overrides]] +module = [ + "eventlet.*", + "gevent.thread.*", +] +ignore_missing_imports = true diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 870897ee..c7a64153 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -2,16 +2,25 @@ gateway code for initiating popen, socket and ssh connections. (c) 2004-2013, Holger Krekel and others """ +from __future__ import annotations + import inspect import linecache import os import textwrap import types +from typing import TYPE_CHECKING +from typing import Any +from typing import Callable import execnet from . import gateway_base +from .gateway_base import IO +from .gateway_base import Channel from .gateway_base import Message +from .multi import Group +from .xspec import XSpec importdir = os.path.dirname(os.path.dirname(execnet.__file__)) @@ -19,20 +28,23 @@ class Gateway(gateway_base.BaseGateway): """Gateway to a local or remote Python Interpreter.""" - def __init__(self, io, spec): + _group: Group + + def __init__(self, io: IO, spec: XSpec) -> None: super().__init__(io=io, id=spec.id, _startcount=1) self.spec = spec self._initreceive() @property - def remoteaddress(self): - return self._io.remoteaddress + def remoteaddress(self) -> str: + # Only defined for remote IO types. + return self._io.remoteaddress # type: ignore[attr-defined,no-any-return] - def __repr__(self): + def __repr__(self) -> str: """return string representing gateway type and status.""" try: - r = self.hasreceiver() and "receive-live" or "not-receiving" - i = len(self._channelfactory.channels()) + r: str = self.hasreceiver() and "receive-live" or "not-receiving" + i = str(len(self._channelfactory.channels())) except AttributeError: r = "uninitialized" i = "no" @@ -40,7 +52,7 @@ def __repr__(self): self.__class__.__name__, self.id, r, self.execmodel.backend, i ) - def exit(self): + def exit(self) -> None: """trigger gateway exit. Defer waiting for finishing of receiver-thread and subprocess activity to when group.terminate() is called. @@ -59,7 +71,9 @@ def exit(self): self._trace("io-error: could not send termination sequence") self._trace(" exception: %r" % exc) - def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): + def reconfigure( + self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False + ) -> None: """ set the string coercion for this gateway the default is to try to convert py2 str as py3 str, @@ -69,7 +83,7 @@ def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): data = gateway_base.dumps_internal(self._strconfig) self._send(Message.RECONFIGURE, data=data) - def _rinfo(self, update=False): + def _rinfo(self, update: bool = False) -> RInfo: """return some sys/env information from remote.""" if update or not hasattr(self, "_cache_rinfo"): ch = self.remote_exec(rinfo_source) @@ -79,11 +93,11 @@ def _rinfo(self, update=False): ch.waitclose() return self._cache_rinfo - def hasreceiver(self): + def hasreceiver(self) -> bool: """return True if gateway is able to receive data.""" return self._receivepool.active_count() > 0 - def remote_status(self): + def remote_status(self) -> RemoteStatus: """return information object about remote execution status.""" channel = self.newchannel() self._send(Message.STATUS, channel.id) @@ -93,7 +107,11 @@ def remote_status(self): self._channelfactory._local_close(channel.id) return RemoteStatus(statusdict) - def remote_exec(self, source, **kwargs): + def remote_exec( + self, + source: str | types.FunctionType | Callable[..., object] | types.ModuleType, + **kwargs: object, + ) -> Channel: """return channel object and connect it to a remote execution thread where the given ``source`` executes. @@ -113,7 +131,7 @@ def remote_exec(self, source, **kwargs): file_name = None if isinstance(source, types.ModuleType): file_name = inspect.getsourcefile(source) - linecache.updatecache(file_name) + linecache.updatecache(file_name) # type: ignore[arg-type] source = inspect.getsource(source) elif isinstance(source, types.FunctionType): call_name = source.__name__ @@ -133,24 +151,29 @@ def remote_exec(self, source, **kwargs): ) return channel - def remote_init_threads(self, num=None): + def remote_init_threads(self, num: int | None = None) -> None: """DEPRECATED. Is currently a NO-OPERATION already.""" print("WARNING: remote_init_threads()" " is a no-operation in execnet-1.2") class RInfo: - def __init__(self, kwargs): + def __init__(self, kwargs) -> None: self.__dict__.update(kwargs) - def __repr__(self): + def __repr__(self) -> str: info = ", ".join(f"{k}={v}" for k, v in sorted(self.__dict__.items())) return "" % info + if TYPE_CHECKING: + + def __getattr__(self, name: str) -> Any: + ... + RemoteStatus = RInfo -def rinfo_source(channel): +def rinfo_source(channel) -> None: import os import sys @@ -165,7 +188,7 @@ def rinfo_source(channel): ) -def _find_non_builtin_globals(source, codeobj): +def _find_non_builtin_globals(source: str, codeobj: types.CodeType) -> list[str]: import ast import builtins @@ -179,7 +202,7 @@ def _find_non_builtin_globals(source, codeobj): ] -def _source_of_function(function): +def _source_of_function(function: types.FunctionType | Callable[..., object]) -> str: if function.__name__ == "": raise ValueError("can't evaluate lambda functions'") # XXX: we dont check before remote instantiation diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 9141f7e6..494a6839 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -21,16 +21,71 @@ import weakref from _thread import interrupt_main from io import BytesIO +from typing import Any from typing import Callable +from typing import Iterator +from typing import Literal +from typing import MutableSet +from typing import Protocol +from typing import cast +from typing import overload + + +class WriteIO(Protocol): + def write(self, data: bytes, /) -> None: + ... + + +class ReadIO(Protocol): + def read(self, numbytes: int, /) -> bytes: + ... + + +class IO(Protocol): + execmodel: ExecModel + + def read(self, numbytes: int, /) -> bytes: + ... + + def write(self, data: bytes, /) -> None: + ... + + def close_read(self) -> None: + ... + + def close_write(self) -> None: + ... + + def wait(self) -> int | None: + ... + + def kill(self) -> None: + ... + + +class Event(Protocol): + """Protocol for types which look like threading.Event.""" + + def is_set(self) -> bool: + ... + + def set(self) -> None: + ... + + def clear(self) -> None: + ... + + def wait(self, timeout: float | None = None) -> bool: + ... class ExecModel(metaclass=abc.ABCMeta): @property @abc.abstractmethod - def backend(self): + def backend(self) -> str: raise NotImplementedError() - def __repr__(self): + def __repr__(self) -> str: return "" % self.backend @property @@ -49,15 +104,15 @@ def socket(self): raise NotImplementedError() @abc.abstractmethod - def start(self, func, args=()): + def start(self, func, args=()) -> None: raise NotImplementedError() @abc.abstractmethod - def get_ident(self): + def get_ident(self) -> int: raise NotImplementedError() @abc.abstractmethod - def sleep(self, delay): + def sleep(self, delay: float) -> None: raise NotImplementedError() @abc.abstractmethod @@ -73,7 +128,7 @@ def RLock(self): raise NotImplementedError() @abc.abstractmethod - def Event(self): + def Event(self) -> Event: raise NotImplementedError() @@ -98,20 +153,20 @@ def socket(self): return socket - def get_ident(self): + def get_ident(self) -> int: import _thread return _thread.get_ident() - def sleep(self, delay): + def sleep(self, delay: float) -> None: import time time.sleep(delay) - def start(self, func, args=()): + def start(self, func, args=()) -> None: import _thread - return _thread.start_new_thread(func, args) + _thread.start_new_thread(func, args) def fdopen(self, fd, mode, bufsize=1, closefd=True): import os @@ -159,20 +214,20 @@ def socket(self): return eventlet.green.socket - def get_ident(self): + def get_ident(self) -> int: import eventlet.green.thread - return eventlet.green.thread.get_ident() + return eventlet.green.thread.get_ident() # type: ignore[no-any-return] - def sleep(self, delay): + def sleep(self, delay: float) -> None: import eventlet eventlet.sleep(delay) - def start(self, func, args=()): + def start(self, func, args=()) -> None: import eventlet - return eventlet.spawn_n(func, *args) + eventlet.spawn_n(func, *args) def fdopen(self, fd, mode, bufsize=1, closefd=True): import eventlet.green.os @@ -216,20 +271,20 @@ def socket(self): return gevent.socket - def get_ident(self): + def get_ident(self) -> int: import gevent.thread - return gevent.thread.get_ident() + return gevent.thread.get_ident() # type: ignore[no-any-return] - def sleep(self, delay): + def sleep(self, delay: float) -> None: import gevent gevent.sleep(delay) - def start(self, func, args=()): + def start(self, func, args=()) -> None: import gevent - return gevent.spawn(func, *args) + gevent.spawn(func, *args) def fdopen(self, fd, mode, bufsize=1, closefd=True): # XXX @@ -253,8 +308,8 @@ def Event(self): return gevent.event.Event() -def get_execmodel(backend): - if hasattr(backend, "backend"): +def get_execmodel(backend: str | ExecModel) -> ExecModel: + if isinstance(backend, ExecModel): return backend if backend == "thread": return ThreadExecModel() @@ -274,12 +329,12 @@ class Reply: through WorkerPool.spawn() """ - def __init__(self, task, threadmodel): + def __init__(self, task, threadmodel) -> None: self.task = task self._result_ready = threadmodel.Event() self.running = True - def get(self, timeout=None): + def get(self, timeout: float | None = None): """get the result object from an asynchronous function execution. if the function execution raised an exception, then calling get() will reraise that exception @@ -291,11 +346,11 @@ def get(self, timeout=None): except AttributeError: raise self._exc from None - def waitfinish(self, timeout=None): + def waitfinish(self, timeout: float | None = None) -> None: if not self._result_ready.wait(timeout): raise OSError(f"timeout waiting for {self.task!r}") - def run(self): + def run(self) -> None: func, args, kwargs = self.task try: try: @@ -318,26 +373,29 @@ class WorkerPool: when the pool received a trigger_shutdown(). """ - def __init__(self, execmodel, hasprimary=False): + _primary_thread_task: Reply | None + + def __init__(self, execmodel: ExecModel, hasprimary: bool = False) -> None: """by default allow unlimited number of spawns.""" self.execmodel = execmodel self._running_lock = self.execmodel.Lock() - self._running = set() + self._running: MutableSet[Reply] = set() self._shuttingdown = False - self._waitall_events = [] + self._waitall_events: list[Event] = [] if hasprimary: if self.execmodel.backend not in ("thread", "main_thread_only"): raise ValueError("hasprimary=True requires thread model") - self._primary_thread_task_ready = self.execmodel.Event() + self._primary_thread_task_ready: Event | None = self.execmodel.Event() else: self._primary_thread_task_ready = None - def integrate_as_primary_thread(self): + def integrate_as_primary_thread(self) -> None: """integrate the thread with which we are called as a primary thread for executing functions triggered with spawn(). """ assert self.execmodel.backend in ("thread", "main_thread_only"), self.execmodel primary_thread_task_ready = self._primary_thread_task_ready + assert primary_thread_task_ready is not None # interacts with code at REF1 while 1: primary_thread_task_ready.wait() @@ -355,17 +413,17 @@ def integrate_as_primary_thread(self): if reply is self._primary_thread_task: primary_thread_task_ready.clear() - def trigger_shutdown(self): + def trigger_shutdown(self) -> None: with self._running_lock: self._shuttingdown = True if self._primary_thread_task_ready is not None: self._primary_thread_task = None self._primary_thread_task_ready.set() - def active_count(self): + def active_count(self) -> int: return len(self._running) - def _perform_spawn(self, reply): + def _perform_spawn(self, reply: Reply) -> None: reply.run() with self._running_lock: self._running.remove(reply) @@ -374,7 +432,7 @@ def _perform_spawn(self, reply): waitall_event = self._waitall_events.pop() waitall_event.set() - def _try_send_to_primary_thread(self, reply): + def _try_send_to_primary_thread(self, reply: Reply) -> bool: # REF1 in 'thread' model we give priority to running in main thread # note that we should be called with _running_lock hold primary_thread_task_ready = self._primary_thread_task_ready @@ -399,7 +457,7 @@ def _try_send_to_primary_thread(self, reply): return True return False - def spawn(self, func, *args, **kwargs): + def spawn(self, func, *args, **kwargs) -> Reply: """return Reply object for the asynchronous dispatch of the given func(*args, **kwargs). """ @@ -412,12 +470,12 @@ def spawn(self, func, *args, **kwargs): self.execmodel.start(self._perform_spawn, (reply,)) return reply - def terminate(self, timeout=None): + def terminate(self, timeout: float | None = None) -> bool: """trigger shutdown and wait for completion of all executions.""" self.trigger_shutdown() return self.waitall(timeout=timeout) - def waitall(self, timeout=None): + def waitall(self, timeout: float | None = None) -> bool: """wait until all active spawns have finished executing.""" with self._running_lock: if not self._running: @@ -437,7 +495,7 @@ def waitall(self, timeout=None): pid = os.getpid() if DEBUG == "2": - def trace(*msg): + def trace(*msg: object) -> None: try: line = " ".join(map(str, msg)) sys.stderr.write(f"[{pid}] {line}\n") @@ -453,7 +511,7 @@ def trace(*msg): # sys.stderr.write("execnet-debug at %r" % (fn,)) debugfile = open(fn, "w") - def trace(*msg): + def trace(*msg: object) -> None: try: line = " ".join(map(str, msg)) debugfile.write(line + "\n") @@ -471,7 +529,7 @@ def trace(*msg): class Popen2IO: error = (IOError, OSError, EOFError) - def __init__(self, outfile, infile, execmodel): + def __init__(self, outfile, infile, execmodel: ExecModel) -> None: # we need raw byte streams self.outfile, self.infile = outfile, infile if sys.platform == "win32": @@ -486,7 +544,7 @@ def __init__(self, outfile, infile, execmodel): self._write = getattr(outfile, "buffer", outfile).write self.execmodel = execmodel - def read(self, numbytes): + def read(self, numbytes: int) -> bytes: """Read exactly 'numbytes' bytes from the pipe.""" # a file in non-blocking mode may return less bytes, so we loop buf = b"" @@ -497,16 +555,16 @@ def read(self, numbytes): buf += data return buf - def write(self, data): + def write(self, data: bytes) -> None: """write out all data bytes.""" assert isinstance(data, bytes) self._write(data) self.outfile.flush() - def close_read(self): + def close_read(self) -> None: self.infile.close() - def close_write(self): + def close_write(self) -> None: self.outfile.close() @@ -516,13 +574,13 @@ class Message: # message code -> name, handler _types: dict[int, tuple[str, Callable[[Message, BaseGateway], None]]] = {} - def __init__(self, msgcode, channelid=0, data=b""): + def __init__(self, msgcode: int, channelid: int = 0, data: bytes = b"") -> None: self.msgcode = msgcode self.channelid = channelid self.data = data @staticmethod - def from_io(io): + def from_io(io: ReadIO) -> Message: try: header = io.read(9) # type 1, channel 4, payload 4 if not header: @@ -532,24 +590,25 @@ def from_io(io): msgtype, channel, payload = struct.unpack("!bii", header) return Message(msgtype, channel, io.read(payload)) - def to_io(self, io): + def to_io(self, io: WriteIO) -> None: header = struct.pack("!bii", self.msgcode, self.channelid, len(self.data)) io.write(header + self.data) - def received(self, gateway): + def received(self, gateway: BaseGateway) -> None: handler = self._types[self.msgcode][1] handler(self, gateway) - def __repr__(self): + def __repr__(self) -> str: name = self._types[self.msgcode][0] return f"" - def _status(message, gateway): + def _status(message: Message, gateway: BaseGateway) -> None: # we use the channelid to send back information # but don't instantiate a channel object d = { "numchannels": len(gateway._channelfactory._channels), - "numexecuting": gateway._execpool.active_count(), + # TODO(typing): Attribute `_execpool` is only on WorkerGateway. + "numexecuting": gateway._execpool.active_count(), # type: ignore[attr-defined] "execmodel": gateway.execmodel.backend, } gateway._send(Message.CHANNEL_DATA, message.channelid, dumps_internal(d)) @@ -558,49 +617,53 @@ def _status(message, gateway): STATUS = 0 _types[STATUS] = ("STATUS", _status) - def _reconfigure(message, gateway): + def _reconfigure(message: Message, gateway: BaseGateway) -> None: + data = loads_internal(message.data, gateway) + assert isinstance(data, tuple) + strconfig: tuple[bool, bool] = data if message.channelid == 0: - target = gateway + gateway._strconfig = strconfig else: - target = gateway._channelfactory.new(message.channelid) - target._strconfig = loads_internal(message.data, gateway) + gateway._channelfactory.new(message.channelid)._strconfig = strconfig RECONFIGURE = 1 _types[RECONFIGURE] = ("RECONFIGURE", _reconfigure) - def _gateway_terminate(message, gateway): + def _gateway_terminate(message: Message, gateway: BaseGateway) -> None: raise GatewayReceivedTerminate(gateway) GATEWAY_TERMINATE = 2 _types[GATEWAY_TERMINATE] = ("GATEWAY_TERMINATE", _gateway_terminate) - def _channel_exec(message, gateway): + def _channel_exec(message: Message, gateway: BaseGateway) -> None: channel = gateway._channelfactory.new(message.channelid) gateway._local_schedulexec(channel=channel, sourcetask=message.data) CHANNEL_EXEC = 3 _types[CHANNEL_EXEC] = ("CHANNEL_EXEC", _channel_exec) - def _channel_data(message, gateway): + def _channel_data(message: Message, gateway: BaseGateway) -> None: gateway._channelfactory._local_receive(message.channelid, message.data) CHANNEL_DATA = 4 _types[CHANNEL_DATA] = ("CHANNEL_DATA", _channel_data) - def _channel_close(message, gateway): + def _channel_close(message: Message, gateway: BaseGateway) -> None: gateway._channelfactory._local_close(message.channelid) CHANNEL_CLOSE = 5 _types[CHANNEL_CLOSE] = ("CHANNEL_CLOSE", _channel_close) - def _channel_close_error(message, gateway): - remote_error = RemoteError(loads_internal(message.data)) + def _channel_close_error(message: Message, gateway: BaseGateway) -> None: + error_message = loads_internal(message.data) + assert isinstance(error_message, str) + remote_error = RemoteError(error_message) gateway._channelfactory._local_close(message.channelid, remote_error) CHANNEL_CLOSE_ERROR = 6 _types[CHANNEL_CLOSE_ERROR] = ("CHANNEL_CLOSE_ERROR", _channel_close_error) - def _channel_last_message(message, gateway): + def _channel_last_message(message: Message, gateway: BaseGateway) -> None: gateway._channelfactory._local_close(message.channelid, sendonly=True) CHANNEL_LAST_MESSAGE = 7 @@ -614,7 +677,7 @@ class GatewayReceivedTerminate(Exception): def geterrortext( exc: BaseException, format_exception=traceback.format_exception, - sysex=sysex, + sysex: tuple[type[BaseException], ...] = sysex, ) -> str: try: # In py310, can change this to: @@ -631,17 +694,17 @@ def geterrortext( class RemoteError(Exception): """Exception containing a stringified error from the other side.""" - def __init__(self, formatted): + def __init__(self, formatted: str) -> None: super().__init__() self.formatted = formatted - def __str__(self): + def __str__(self) -> str: return self.formatted - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}: {self.formatted}" - def warn(self): + def warn(self) -> None: if self.formatted != INTERRUPT_TEXT: # XXX do this better sys.stderr.write(f"[{os.getpid()}] Warning: unhandled {self!r}\n") @@ -662,7 +725,7 @@ class Channel: _INTERNALWAKEUP = 1000 _executing = False - def __init__(self, gateway, id): + def __init__(self, gateway: BaseGateway, id: int) -> None: assert isinstance(id, int) assert not isinstance(gateway, type) self.gateway = gateway @@ -672,12 +735,16 @@ def __init__(self, gateway, id): self._items = self.gateway.execmodel.queue.Queue() self._closed = False self._receiveclosed = self.gateway.execmodel.Event() - self._remoteerrors = [] + self._remoteerrors: list[RemoteError] = [] - def _trace(self, *msg): + def _trace(self, *msg: object) -> None: self.gateway._trace(self.id, *msg) - def setcallback(self, callback, endmarker=NO_ENDMARKER_WANTED): + def setcallback( + self, + callback: Callable[[Any], Any], + endmarker: object = NO_ENDMARKER_WANTED, + ) -> None: """set a callback function for receiving items. All already queued items will immediately trigger the callback. @@ -709,13 +776,14 @@ def setcallback(self, callback, endmarker=NO_ENDMARKER_WANTED): else: callback(olditem) - def __repr__(self): + def __repr__(self) -> str: flag = self.isclosed() and "closed" or "open" return "" % (self.id, flag) - def __del__(self): + def __del__(self) -> None: if self.gateway is None: # can be None in tests - return + return # type: ignore[unreachable] + self._trace("channel.__del__") # no multithreading issues here, because we have the last ref to 'self' if self._closed: @@ -755,13 +823,29 @@ def _getremoteerror(self): # # public API for channel objects # - def isclosed(self): + def isclosed(self) -> bool: """return True if the channel is closed. A closed channel may still hold items. """ return self._closed - def makefile(self, mode="w", proxyclose=False): + @overload + def makefile(self, mode: Literal["r"], proxyclose: bool = ...) -> ChannelFileRead: + pass + + @overload + def makefile( + self, + mode: Literal["w"] = ..., + proxyclose: bool = ..., + ) -> ChannelFileWrite: + pass + + def makefile( + self, + mode: Literal["r", "w"] = "w", + proxyclose: bool = False, + ) -> ChannelFileWrite | ChannelFileRead: """return a file-like object. mode can be 'w' or 'r' for writeable/readable files. if proxyclose is true file.close() will also close the channel. @@ -772,7 +856,7 @@ def makefile(self, mode="w", proxyclose=False): return ChannelFileRead(channel=self, proxyclose=proxyclose) raise ValueError(f"mode {mode!r} not available") - def close(self, error=None): + def close(self, error=None) -> None: """close down this channel with an optional error message. Note that closing of a channel tied to remote_exec happens automatically at the end of execution and cannot @@ -804,7 +888,7 @@ def close(self, error=None): queue.put(ENDMARKER) self.gateway._channelfactory._no_longer_opened(self.id) - def waitclose(self, timeout=None): + def waitclose(self, timeout: float | None = None) -> None: """wait until this channel is closed (or the remote side otherwise signalled that no more data was being sent). The channel may still hold receiveable items, but not receive @@ -823,7 +907,7 @@ def waitclose(self, timeout=None): if error: raise error - def send(self, item): + def send(self, item: object) -> None: """sends the given item to the other side of the channel, possibly blocking if the sender queue is full. The item must be a simple python type and will be @@ -834,7 +918,7 @@ def send(self, item): raise OSError(f"cannot send to {self!r}") self.gateway._send(Message.CHANNEL_DATA, self.id, dumps_internal(item)) - def receive(self, timeout=None): + def receive(self, timeout: float | None = None) -> object: """receive a data item that was sent from the other side. timeout: None [default] blocked waiting. A positive number indicates the number of seconds after which a channel.TimeoutError @@ -856,10 +940,10 @@ def receive(self, timeout=None): else: return x - def __iter__(self): + def __iter__(self) -> Iterator[object]: return self - def next(self): + def next(self) -> object: try: return self.receive() except EOFError: @@ -867,7 +951,9 @@ def next(self): __next__ = next - def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): + def reconfigure( + self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False + ) -> None: """ set the string coercion for this channel the default is to try to convert py2 str as py3 str, @@ -886,16 +972,21 @@ def reconfigure(self, py2str_as_py3str=True, py3str_as_py2str=False): class ChannelFactory: - def __init__(self, gateway, startcount=1): - self._channels = weakref.WeakValueDictionary() - self._callbacks = {} + def __init__(self, gateway: BaseGateway, startcount: int = 1) -> None: + self._channels: weakref.WeakValueDictionary[ + int, Channel + ] = weakref.WeakValueDictionary() + # Channel ID => (callback, end marker, strconfig) + self._callbacks: dict[ + int, tuple[Callable[[Any], Any], object, tuple[bool, bool]] + ] = {} self._writelock = gateway.execmodel.Lock() self.gateway = gateway self.count = startcount self.finished = False self._list = list # needed during interp-shutdown - def new(self, id=None): + def new(self, id: int | None = None) -> Channel: """create a new Channel with 'id' (or create new id if None).""" with self._writelock: if self.finished: @@ -909,13 +1000,13 @@ def new(self, id=None): channel = self._channels[id] = Channel(self.gateway, id) return channel - def channels(self): + def channels(self) -> list[Channel]: return self._list(self._channels.values()) # # internal methods, called from the receiver thread # - def _no_longer_opened(self, id): + def _no_longer_opened(self, id: int) -> None: try: del self._channels[id] except KeyError: @@ -928,7 +1019,7 @@ def _no_longer_opened(self, id): if endmarker is not NO_ENDMARKER_WANTED: callback(endmarker) - def _local_close(self, id, remoteerror=None, sendonly=False): + def _local_close(self, id: int, remoteerror=None, sendonly: bool = False) -> None: channel = self._channels.get(id) if channel is None: # channel already in "deleted" state @@ -947,13 +1038,13 @@ def _local_close(self, id, remoteerror=None, sendonly=False): channel._closed = True # --> "closed" channel._receiveclosed.set() - def _local_receive(self, id, data): + def _local_receive(self, id: int, data) -> None: # executes in receiver thread channel = self._channels.get(id) try: callback, endmarker, strconfig = self._callbacks[id] except KeyError: - queue = channel and channel._items + queue = channel._items if channel is not None else None if queue is None: pass # drop data else: @@ -971,7 +1062,7 @@ def _local_receive(self, id, data): ) self._local_close(id, errortext) - def _finished_receiving(self): + def _finished_receiving(self) -> None: with self._writelock: self.finished = True for id in self._list(self._channels): @@ -981,41 +1072,41 @@ def _finished_receiving(self): class ChannelFile: - def __init__(self, channel, proxyclose=True): + def __init__(self, channel: Channel, proxyclose: bool = True) -> None: self.channel = channel self._proxyclose = proxyclose - def isatty(self): + def isatty(self) -> bool: return False - def close(self): + def close(self) -> None: if self._proxyclose: self.channel.close() - def __repr__(self): + def __repr__(self) -> str: state = self.channel.isclosed() and "closed" or "open" return "" % (self.channel.id, state) class ChannelFileWrite(ChannelFile): - def write(self, out): + def write(self, out: bytes) -> None: self.channel.send(out) - def flush(self): + def flush(self) -> None: pass class ChannelFileRead(ChannelFile): - def __init__(self, channel, proxyclose=True): + def __init__(self, channel: Channel, proxyclose: bool = True) -> None: super().__init__(channel, proxyclose) - self._buffer = None + self._buffer: str | None = None - def read(self, n): + def read(self, n: int) -> str: try: if self._buffer is None: - self._buffer = self.channel.receive() + self._buffer = cast(str, self.channel.receive()) while len(self._buffer) < n: - self._buffer += self.channel.receive() + self._buffer += cast(str, self.channel.receive()) except EOFError: self.close() if self._buffer is None: @@ -1025,7 +1116,7 @@ def read(self, n): self._buffer = self._buffer[n:] return ret - def readline(self): + def readline(self) -> str: if self._buffer is not None: i = self._buffer.find("\n") if i != -1: @@ -1045,7 +1136,7 @@ class BaseGateway: _sysex = sysex id = "" - def __init__(self, io, id, _startcount=2): + def __init__(self, io: IO, id, _startcount: int = 2) -> None: self.execmodel = io.execmodel self._io = io self.id = id @@ -1057,14 +1148,14 @@ def __init__(self, io, id, _startcount=2): self._geterrortext = geterrortext self._receivepool = WorkerPool(self.execmodel) - def _trace(self, *msg): + def _trace(self, *msg: object) -> None: self.__trace(self.id, *msg) - def _initreceive(self): + def _initreceive(self) -> None: self._receivepool.spawn(self._thread_receiver) - def _thread_receiver(self): - def log(*msg): + def _thread_receiver(self) -> None: + def log(*msg: object) -> None: self._trace("[receiver-thread]", *msg) log("RECEIVERTHREAD: starting to run") @@ -1095,10 +1186,10 @@ def log(*msg): log("terminating our receive pseudo pool") self._receivepool.trigger_shutdown() - def _terminate_execution(self): + def _terminate_execution(self) -> None: pass - def _send(self, msgcode, channelid=0, data=b""): + def _send(self, msgcode: int, channelid: int = 0, data: bytes = b"") -> None: message = Message(msgcode, channelid, data) try: message.to_io(self._io) @@ -1108,7 +1199,7 @@ def _send(self, msgcode, channelid=0, data=b""): # ValueError might be because the IO is already closed raise OSError("cannot send (already closed?)") from e - def _local_schedulexec(self, channel, sourcetask): + def _local_schedulexec(self, channel: Channel, sourcetask: bytes) -> None: channel.close("execution disallowed") # _____________________________________________________________________ @@ -1116,19 +1207,20 @@ def _local_schedulexec(self, channel, sourcetask): # High Level Interface # _____________________________________________________________________ # - def newchannel(self): + def newchannel(self) -> Channel: """return a new independent channel.""" return self._channelfactory.new() - def join(self, timeout=None): + def join(self, timeout: float | None = None) -> None: """Wait for receiverthread to terminate.""" self._trace("waiting for receiver thread to finish") self._receivepool.waitall() class WorkerGateway(BaseGateway): - def _local_schedulexec(self, channel, sourcetask): + def _local_schedulexec(self, channel: Channel, sourcetask: bytes) -> None: if self._execpool.execmodel.backend == "main_thread_only": + assert self._executetask_complete is not None # It's necessary to wait for a short time in order to ensure # that we do not report a false-positive deadlock error, since # channel close does not elicit a response that would provide @@ -1142,10 +1234,10 @@ def _local_schedulexec(self, channel, sourcetask): # that there is not a previous task about to set it again. self._executetask_complete.clear() - sourcetask = loads_internal(sourcetask) - self._execpool.spawn(self.executetask, (channel, sourcetask)) + sourcetask_ = loads_internal(sourcetask) + self._execpool.spawn(self.executetask, (channel, sourcetask_)) - def _terminate_execution(self): + def _terminate_execution(self) -> None: # called from receiverthread self._trace("shutting down execution pool") self._execpool.trigger_shutdown() @@ -1165,8 +1257,8 @@ def _terminate_execution(self): ) os._exit(1) - def serve(self): - def trace(msg): + def serve(self) -> None: + def trace(msg: str) -> None: self._trace("[serve] " + msg) hasprimary = self.execmodel.backend in ("thread", "main_thread_only") @@ -1190,10 +1282,13 @@ def trace(msg): # in the worker we can't really do anything sensible trace("swallowing keyboardinterrupt, serve finished") - def executetask(self, item): + def executetask( + self, + item: tuple[Channel, tuple[str, str | None, str | None, dict[str, object]]], + ) -> None: try: channel, (source, file_name, call_name, kwargs) = item - loc = {"channel": channel, "__name__": "__channelexec__"} + loc: dict[str, Any] = {"channel": channel, "__name__": "__channelexec__"} self._trace(f"execution starts[{channel.id}]: {repr(source)[:50]}") channel._executing = True try: @@ -1242,7 +1337,7 @@ class LoadError(DataFormatError): """Error while unserializing an object.""" -def bchr(n): +def bchr(n: int) -> bytes: return bytes([n]) @@ -1291,20 +1386,34 @@ class Unserializer: py2str_as_py3str = True # True py3str_as_py2str = False # false means py2 will get unicode - def __init__(self, stream, channel_or_gateway=None, strconfig=None): - gateway = getattr(channel_or_gateway, "gateway", channel_or_gateway) - strconfig = getattr(channel_or_gateway, "_strconfig", strconfig) + def __init__( + self, + stream: ReadIO, + channel_or_gateway: Channel | BaseGateway | None = None, + strconfig: tuple[bool, bool] | None = None, + ) -> None: + if isinstance(channel_or_gateway, Channel): + gw: BaseGateway | None = channel_or_gateway.gateway + else: + gw = channel_or_gateway + if channel_or_gateway is None: + strconfig = None + else: + strconfig = channel_or_gateway._strconfig if strconfig: self.py2str_as_py3str, self.py3str_as_py2str = strconfig self.stream = stream - self.channelfactory = getattr(gateway, "_channelfactory", gateway) + if gw is None: + self.channelfactory = None + else: + self.channelfactory = gw._channelfactory - def load(self, versioned=False): + def load(self, versioned: bool = False) -> object: if versioned: ver = self.stream.read(1) if ver != DUMPFORMAT_VERSION: raise LoadError("wrong dumpformat version %r" % ver) - self.stack = [] + self.stack: list[object] = [] try: while True: opcode = self.stream.read(1) @@ -1324,28 +1433,28 @@ def load(self, versioned=False): else: raise LoadError("didn't get STOP") - def load_none(self): + def load_none(self) -> None: self.stack.append(None) num2func[opcode.NONE] = load_none - def load_true(self): + def load_true(self) -> None: self.stack.append(True) num2func[opcode.TRUE] = load_true - def load_false(self): + def load_false(self) -> None: self.stack.append(False) num2func[opcode.FALSE] = load_false - def load_int(self): + def load_int(self) -> None: i = self._read_int4() self.stack.append(i) num2func[opcode.INT] = load_int - def load_longint(self): + def load_longint(self) -> None: s = self._read_byte_string() self.stack.append(int(s)) @@ -1356,27 +1465,28 @@ def load_longint(self): load_longlong = load_longint num2func[opcode.LONGLONG] = load_longlong - def load_float(self): + def load_float(self) -> None: binary = self.stream.read(FLOAT_FORMAT_SIZE) self.stack.append(struct.unpack(FLOAT_FORMAT, binary)[0]) num2func[opcode.FLOAT] = load_float - def load_complex(self): + def load_complex(self) -> None: binary = self.stream.read(COMPLEX_FORMAT_SIZE) self.stack.append(complex(*struct.unpack(COMPLEX_FORMAT, binary))) num2func[opcode.COMPLEX] = load_complex - def _read_int4(self): - return struct.unpack("!i", self.stream.read(4))[0] + def _read_int4(self) -> int: + value: int = struct.unpack("!i", self.stream.read(4))[0] + return value - def _read_byte_string(self): + def _read_byte_string(self) -> bytes: length = self._read_int4() as_bytes = self.stream.read(length) return as_bytes - def load_py3string(self): + def load_py3string(self) -> None: as_bytes = self._read_byte_string() if self.py3str_as_py2str: # XXX Should we try to decode into latin-1? @@ -1386,48 +1496,48 @@ def load_py3string(self): num2func[opcode.PY3STRING] = load_py3string - def load_py2string(self): + def load_py2string(self) -> None: as_bytes = self._read_byte_string() if self.py2str_as_py3str: - s = as_bytes.decode("latin-1") + s: bytes | str = as_bytes.decode("latin-1") else: s = as_bytes self.stack.append(s) num2func[opcode.PY2STRING] = load_py2string - def load_bytes(self): + def load_bytes(self) -> None: s = self._read_byte_string() self.stack.append(s) num2func[opcode.BYTES] = load_bytes - def load_unicode(self): + def load_unicode(self) -> None: self.stack.append(self._read_byte_string().decode("utf-8")) num2func[opcode.UNICODE] = load_unicode - def load_newlist(self): + def load_newlist(self) -> None: length = self._read_int4() self.stack.append([None] * length) num2func[opcode.NEWLIST] = load_newlist - def load_setitem(self): + def load_setitem(self) -> None: if len(self.stack) < 3: raise LoadError("not enough items for setitem") value = self.stack.pop() key = self.stack.pop() - self.stack[-1][key] = value + self.stack[-1][key] = value # type: ignore[index] num2func[opcode.SETITEM] = load_setitem - def load_newdict(self): + def load_newdict(self) -> None: self.stack.append({}) num2func[opcode.NEWDICT] = load_newdict - def _load_collection(self, type_): + def _load_collection(self, type_: type) -> None: length = self._read_int4() if length: res = type_(self.stack[-length:]) @@ -1436,50 +1546,53 @@ def _load_collection(self, type_): else: self.stack.append(type_()) - def load_buildtuple(self): + def load_buildtuple(self) -> None: self._load_collection(tuple) num2func[opcode.BUILDTUPLE] = load_buildtuple - def load_set(self): + def load_set(self) -> None: self._load_collection(set) num2func[opcode.SET] = load_set - def load_frozenset(self): + def load_frozenset(self) -> None: self._load_collection(frozenset) num2func[opcode.FROZENSET] = load_frozenset - def load_stop(self): + def load_stop(self) -> None: raise _Stop num2func[opcode.STOP] = load_stop - def load_channel(self): + def load_channel(self) -> None: id = self._read_int4() + assert self.channelfactory is not None newchannel = self.channelfactory.new(id) self.stack.append(newchannel) num2func[opcode.CHANNEL] = load_channel -def dumps(obj): +def dumps(obj: object) -> bytes: """return a serialized bytestring of the given obj. The obj and all contained objects must be of a builtin python type (so nested dicts, sets, etc. are all ok but not user-level instances). """ - return _Serializer().save(obj, versioned=True) + return _Serializer().save(obj, versioned=True) # type: ignore[return-value] -def dump(byteio, obj): +def dump(byteio, obj: object) -> None: """write a serialized bytestring of the given obj to the given stream.""" _Serializer(write=byteio.write).save(obj, versioned=True) -def loads(bytestring, py2str_as_py3str=False, py3str_as_py2str=False): +def loads( + bytestring: bytes, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False +) -> object: """return the object as deserialized from the given bytestring. py2str_as_py3str: if true then string (str) objects previously @@ -1499,7 +1612,9 @@ def loads(bytestring, py2str_as_py3str=False, py3str_as_py2str=False): ) -def load(io, py2str_as_py3str=False, py3str_as_py2str=False): +def load( + io: ReadIO, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False +) -> object: """derserialize an object form the specified stream. Behaviour and parameters are otherwise the same as with ``loads`` @@ -1508,25 +1623,29 @@ def load(io, py2str_as_py3str=False, py3str_as_py2str=False): return Unserializer(io, strconfig=strconfig).load(versioned=True) -def loads_internal(bytestring, channelfactory=None, strconfig=None): +def loads_internal( + bytestring: bytes, + channelfactory=None, + strconfig: tuple[bool, bool] | None = None, +) -> object: io = BytesIO(bytestring) return Unserializer(io, channelfactory, strconfig).load() -def dumps_internal(obj): - return _Serializer().save(obj) +def dumps_internal(obj: object) -> bytes: + return _Serializer().save(obj) # type: ignore[return-value] class _Serializer: _dispatch: dict[type, Callable[[_Serializer, object], None]] = {} - def __init__(self, write=None): + def __init__(self, write: Callable[[bytes], None] | None = None) -> None: if write is None: - self._streamlist = [] + self._streamlist: list[bytes] = [] write = self._streamlist.append self._write = write - def save(self, obj, versioned=False): + def save(self, obj: object, versioned: bool = False) -> bytes | None: # calling here is not re-entrant but multiple instances # may write to the same stream because of the common platform # atomic-write guarantee (concurrent writes each happen atomically) @@ -1540,47 +1659,49 @@ def save(self, obj, versioned=False): return None return b"".join(streamlist) - def _save(self, obj): + def _save(self, obj: object) -> None: tp = type(obj) try: dispatch = self._dispatch[tp] except KeyError: methodname = "save_" + tp.__name__ - meth = getattr(self.__class__, methodname, None) + meth: Callable[[_Serializer, object], None] | None = getattr( + self.__class__, methodname, None + ) if meth is None: raise DumpError(f"can't serialize {tp}") from None dispatch = self._dispatch[tp] = meth dispatch(self, obj) - def save_NoneType(self, non): + def save_NoneType(self, non: None) -> None: self._write(opcode.NONE) - def save_bool(self, boolean): + def save_bool(self, boolean: bool) -> None: if boolean: self._write(opcode.TRUE) else: self._write(opcode.FALSE) - def save_bytes(self, bytes_): + def save_bytes(self, bytes_: bytes) -> None: self._write(opcode.BYTES) self._write_byte_sequence(bytes_) - def save_str(self, s): + def save_str(self, s: str) -> None: self._write(opcode.PY3STRING) self._write_unicode_string(s) - def _write_unicode_string(self, s): + def _write_unicode_string(self, s: str) -> None: try: as_bytes = s.encode("utf-8") except UnicodeEncodeError as e: raise DumpError("strings must be utf-8 encodable") from e self._write_byte_sequence(as_bytes) - def _write_byte_sequence(self, bytes_): + def _write_byte_sequence(self, bytes_: bytes) -> None: self._write_int4(len(bytes_), "string is too long") self._write(bytes_) - def _save_integral(self, i, short_op, long_op): + def _save_integral(self, i: int, short_op: bytes, long_op: bytes) -> None: if i <= FOUR_BYTE_INT_MAX: self._write(short_op) self._write_int4(i) @@ -1588,65 +1709,67 @@ def _save_integral(self, i, short_op, long_op): self._write(long_op) self._write_byte_sequence(str(i).rstrip("L").encode("ascii")) - def save_int(self, i): + def save_int(self, i: int) -> None: self._save_integral(i, opcode.INT, opcode.LONGINT) - def save_long(self, l): + def save_long(self, l: int) -> None: self._save_integral(l, opcode.LONG, opcode.LONGLONG) - def save_float(self, flt): + def save_float(self, flt: float) -> None: self._write(opcode.FLOAT) self._write(struct.pack(FLOAT_FORMAT, flt)) - def save_complex(self, cpx): + def save_complex(self, cpx: complex) -> None: self._write(opcode.COMPLEX) self._write(struct.pack(COMPLEX_FORMAT, cpx.real, cpx.imag)) - def _write_int4(self, i, error="int must be less than %i" % (FOUR_BYTE_INT_MAX,)): + def _write_int4( + self, i: int, error: str = "int must be less than %i" % (FOUR_BYTE_INT_MAX,) + ) -> None: if i > FOUR_BYTE_INT_MAX: raise DumpError(error) self._write(struct.pack("!i", i)) - def save_list(self, L): + def save_list(self, L: list[object]) -> None: self._write(opcode.NEWLIST) self._write_int4(len(L), "list is too long") for i, item in enumerate(L): self._write_setitem(i, item) - def _write_setitem(self, key, value): + def _write_setitem(self, key: object, value: object) -> None: self._save(key) self._save(value) self._write(opcode.SETITEM) - def save_dict(self, d): + def save_dict(self, d: dict[object, object]) -> None: self._write(opcode.NEWDICT) for key, value in d.items(): self._write_setitem(key, value) - def save_tuple(self, tup): + def save_tuple(self, tup: tuple[object, ...]) -> None: for item in tup: self._save(item) self._write(opcode.BUILDTUPLE) self._write_int4(len(tup), "tuple is too long") - def _write_set(self, s, op): + def _write_set(self, s: set[object] | frozenset[object], op: bytes) -> None: for item in s: self._save(item) self._write(op) self._write_int4(len(s), "set is too long") - def save_set(self, s): + def save_set(self, s: set[object]) -> None: self._write_set(s, opcode.SET) - def save_frozenset(self, s): + def save_frozenset(self, s: frozenset[object]) -> None: self._write_set(s, opcode.FROZENSET) - def save_Channel(self, channel): + def save_Channel(self, channel: Channel) -> None: self._write(opcode.CHANNEL) self._write_int4(channel.id) -def init_popen_io(execmodel): +def init_popen_io(execmodel: ExecModel) -> Popen2IO: if not hasattr(os, "dup"): # jython io = Popen2IO(sys.stdout, sys.stdin, execmodel) import tempfile @@ -1685,6 +1808,6 @@ def init_popen_io(execmodel): return io -def serve(io, id): +def serve(io: IO, id) -> None: trace(f"creating workergateway on {io!r}") WorkerGateway(io=io, id=id, _startcount=2).serve() diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index dfa632ea..6ea7113b 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -1,6 +1,8 @@ """ code to initialize the remote side of a gateway once the io is created """ +from __future__ import annotations + import inspect import os @@ -8,6 +10,8 @@ from . import gateway_base from .gateway import Gateway +from .gateway_base import IO +from .xspec import XSpec importdir = os.path.dirname(os.path.dirname(execnet.__file__)) @@ -16,7 +20,7 @@ class HostNotFound(Exception): pass -def bootstrap_import(io, spec): +def bootstrap_import(io: IO, spec: XSpec) -> None: # only insert the importdir into the path if we must. This prevents # bugs where backports expect to be shadowed by the standard library on # newer versions of python but would instead shadow the standard library @@ -35,7 +39,7 @@ def bootstrap_import(io, spec): assert s == b"1", repr(s) -def bootstrap_exec(io, spec): +def bootstrap_exec(io: IO, spec: XSpec) -> None: try: sendexec( io, @@ -49,11 +53,11 @@ def bootstrap_exec(io, spec): assert s == b"1" except EOFError: ret = io.wait() - if ret == 255: + if ret == 255 and hasattr(io, "remoteaddress"): raise HostNotFound(io.remoteaddress) from None -def bootstrap_socket(io, id): +def bootstrap_socket(io: IO, id) -> None: # XXX: switch to spec from execnet.gateway_socket import SocketIO @@ -73,12 +77,12 @@ def bootstrap_socket(io, id): assert s == b"1" -def sendexec(io, *sources): +def sendexec(io: IO, *sources: str) -> None: source = "\n".join(sources) io.write((repr(source) + "\n").encode("utf-8")) -def bootstrap(io, spec): +def bootstrap(io: IO, spec: XSpec) -> Gateway: if spec.popen: if spec.via or spec.python: bootstrap_exec(io, spec) diff --git a/src/execnet/gateway_io.py b/src/execnet/gateway_io.py index 40ca5c80..37b00de0 100644 --- a/src/execnet/gateway_io.py +++ b/src/execnet/gateway_io.py @@ -3,8 +3,17 @@ creates io instances used for gateway io """ +from __future__ import annotations + import shlex import sys +from typing import TYPE_CHECKING +from typing import cast + +if TYPE_CHECKING: + from execnet.gateway_base import Channel + from execnet.gateway_base import ExecModel + from execnet.xspec import XSpec try: from execnet.gateway_base import Message @@ -17,33 +26,32 @@ class Popen2IOMaster(Popen2IO): - def __init__(self, args, execmodel): + # Set externally, for some specs only. + remoteaddress: str + + def __init__(self, args, execmodel) -> None: PIPE = execmodel.subprocess.PIPE self.popen = p = execmodel.subprocess.Popen(args, stdout=PIPE, stdin=PIPE) super().__init__(p.stdin, p.stdout, execmodel=execmodel) - def wait(self): + def wait(self) -> int | None: try: - return self.popen.wait() + return self.popen.wait() # type: ignore[no-any-return] except OSError: - pass # subprocess probably dead already - - def kill(self): - killpopen(self.popen) + return None - -def killpopen(popen): - try: - popen.kill() - except OSError as e: - sys.stderr.write("ERROR killing: %s\n" % e) - sys.stderr.flush() + def kill(self) -> None: + try: + self.popen.kill() + except OSError as e: + sys.stderr.write("ERROR killing: %s\n" % e) + sys.stderr.flush() popen_bootstrapline = "import sys;exec(eval(sys.stdin.readline()))" -def shell_split_path(path): +def shell_split_path(path: str) -> list[str]: """ Use shell lexer to split the given path into a list of components, taking care to handle Windows' '\' correctly. @@ -54,7 +62,7 @@ def shell_split_path(path): return shlex.split(path) -def popen_args(spec): +def popen_args(spec: XSpec) -> list[str]: args = shell_split_path(spec.python) if spec.python else [sys.executable] args.append("-u") if spec.dont_write_bytecode: @@ -63,7 +71,7 @@ def popen_args(spec): return args -def ssh_args(spec): +def ssh_args(spec: XSpec) -> list[str]: # NOTE: If changing this, you need to sync those changes to vagrant_args # as well, or, take some time to further refactor the commonalities of # ssh_args and vagrant_args. @@ -72,19 +80,21 @@ def ssh_args(spec): if spec.ssh_config is not None: args.extend(["-F", str(spec.ssh_config)]) + assert spec.ssh is not None args.extend(spec.ssh.split()) remotecmd = f'{remotepython} -c "{popen_bootstrapline}"' args.append(remotecmd) return args -def vagrant_ssh_args(spec): +def vagrant_ssh_args(spec: XSpec) -> list[str]: # This is the vagrant-wrapped version of SSH. Unfortunately the # command lines are incompatible to just channel through ssh_args # due to ordering/templating issues. # NOTE: This should be kept in sync with the ssh_args behaviour. # spec.vagrant is identical to spec.ssh in that they both carry # the remote host "address". + assert spec.vagrant_ssh is not None remotepython = spec.python or "python" args = ["vagrant", "ssh", spec.vagrant_ssh, "--", "-C"] if spec.ssh_config is not None: @@ -94,7 +104,7 @@ def vagrant_ssh_args(spec): return args -def create_io(spec, execmodel): +def create_io(spec: XSpec, execmodel) -> Popen2IOMaster: if spec.popen: args = popen_args(spec) return Popen2IOMaster(args, execmodel) @@ -108,6 +118,7 @@ def create_io(spec, execmodel): io = Popen2IOMaster(args, execmodel) io.remoteaddress = spec.vagrant_ssh return io + assert False # @@ -132,7 +143,7 @@ class ProxyIO: instantiates and interacts with the sub. """ - def __init__(self, proxy_channel, execmodel): + def __init__(self, proxy_channel: Channel, execmodel: ExecModel) -> None: # after exchanging the control channel we use proxy_channel # for messaging IO self.controlchan = proxy_channel.gateway.newchannel() @@ -141,69 +152,80 @@ def __init__(self, proxy_channel, execmodel): self.iochan_file = self.iochan.makefile("r") self.execmodel = execmodel - def read(self, nbytes): - return self.iochan_file.read(nbytes) + def read(self, nbytes: int) -> bytes: + # TODO(typing): The IO protocol requires bytes here but ChannelFileRead + # returns str. + return self.iochan_file.read(nbytes) # type: ignore[return-value] - def write(self, data): - return self.iochan.send(data) + def write(self, data: bytes) -> None: + self.iochan.send(data) - def _controll(self, event): + def _controll(self, event: int) -> object: self.controlchan.send(event) return self.controlchan.receive() - def close_write(self): + def close_write(self) -> None: self._controll(RIO_CLOSE_WRITE) - def kill(self): + def close_read(self) -> None: + raise NotImplementedError() + + def kill(self) -> None: self._controll(RIO_KILL) - def wait(self): - return self._controll(RIO_WAIT) + def wait(self) -> int | None: + response = self._controll(RIO_WAIT) + assert response is None or isinstance(response, int) + return response @property - def remoteaddress(self): - return self._controll(RIO_REMOTEADDRESS) + def remoteaddress(self) -> str: + response = self._controll(RIO_REMOTEADDRESS) + assert isinstance(response, str) + return response - def __repr__(self): + def __repr__(self) -> str: return f"" class PseudoSpec: - def __init__(self, vars): + def __init__(self, vars) -> None: self.__dict__.update(vars) - def __getattr__(self, name): + def __getattr__(self, name: str) -> None: return None -def serve_proxy_io(proxy_channelX): +def serve_proxy_io(proxy_channelX: Channel) -> None: execmodel = proxy_channelX.gateway.execmodel log = partial( proxy_channelX.gateway._trace, "serve_proxy_io:%s" % proxy_channelX.id ) - spec = PseudoSpec(proxy_channelX.receive()) + spec = cast("XSpec", PseudoSpec(proxy_channelX.receive())) # create sub IO object which we will proxy back to our proxy initiator sub_io = create_io(spec, execmodel) - control_chan = proxy_channelX.receive() + control_chan = cast("Channel", proxy_channelX.receive()) log("got control chan", control_chan) # read data from master, forward it to the sub # XXX writing might block, thus blocking the receiver thread - def forward_to_sub(data): + def forward_to_sub(data: bytes) -> None: log("forward data to sub, size %s" % len(data)) sub_io.write(data) proxy_channelX.setcallback(forward_to_sub) - def control(data): + def control(data: int) -> None: if data == RIO_WAIT: control_chan.send(sub_io.wait()) elif data == RIO_KILL: - control_chan.send(sub_io.kill()) + sub_io.kill() + control_chan.send(None) elif data == RIO_REMOTEADDRESS: control_chan.send(sub_io.remoteaddress) elif data == RIO_CLOSE_WRITE: - control_chan.send(sub_io.close_write()) + sub_io.close_write() + control_chan.send(None) control_chan.setcallback(control) diff --git a/src/execnet/gateway_socket.py b/src/execnet/gateway_socket.py index 3d3a9b6e..fff9afcd 100644 --- a/src/execnet/gateway_socket.py +++ b/src/execnet/gateway_socket.py @@ -1,10 +1,19 @@ +from __future__ import annotations + import sys +from typing import cast +from execnet.gateway import Gateway +from execnet.gateway_base import ExecModel from execnet.gateway_bootstrap import HostNotFound +from execnet.multi import Group +from execnet.xspec import XSpec class SocketIO: - def __init__(self, sock, execmodel): + remoteaddress: str + + def __init__(self, sock, execmodel: ExecModel) -> None: self.sock = sock self.execmodel = execmodel socket = execmodel.socket @@ -15,7 +24,7 @@ def __init__(self, sock, execmodel): except (AttributeError, OSError): sys.stderr.write("WARNING: cannot set socketoption") - def read(self, numbytes): + def read(self, numbytes: int) -> bytes: "Read exactly 'bytes' bytes from the socket." buf = b"" while len(buf) < numbytes: @@ -25,29 +34,31 @@ def read(self, numbytes): buf += t return buf - def write(self, data): + def write(self, data: bytes) -> None: self.sock.sendall(data) - def close_read(self): + def close_read(self) -> None: try: self.sock.shutdown(0) except self.execmodel.socket.error: pass - def close_write(self): + def close_write(self) -> None: try: self.sock.shutdown(1) except self.execmodel.socket.error: pass - def wait(self): + def wait(self) -> None: pass - def kill(self): + def kill(self) -> None: pass -def start_via(gateway, hostport=None): +def start_via( + gateway: Gateway, hostport: tuple[str, int] | None = None +) -> tuple[str, int]: """return a host, port tuple, after instantiating a socketserver on the given gateway """ @@ -61,7 +72,7 @@ def start_via(gateway, hostport=None): # execute the above socketserverbootstrap on the other side channel = gateway.remote_exec(socketserver) channel.send((host, port)) - (realhost, realport) = channel.receive() + realhost, realport = cast("tuple[str, int]", channel.receive()) # self._trace("new_remote received" # "port=%r, hostname = %r" %(realport, hostname)) if not realhost or realhost == "0.0.0.0": @@ -69,14 +80,15 @@ def start_via(gateway, hostport=None): return realhost, realport -def create_io(spec, group, execmodel): +def create_io(spec: XSpec, group: Group, execmodel: ExecModel) -> SocketIO: + assert spec.socket is not None assert not spec.python, "socket: specifying python executables not yet supported" gateway_id = spec.installvia if gateway_id: host, port = start_via(group[gateway_id]) else: - host, port = spec.socket.split(":") - port = int(port) + host, port_str = spec.socket.split(":") + port = int(port_str) socket = execmodel.socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) diff --git a/src/execnet/multi.py b/src/execnet/multi.py index b0c119e8..6ac76884 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -3,17 +3,33 @@ (c) 2008-2014, Holger Krekel and others """ +from __future__ import annotations + import atexit +import types from functools import partial from threading import Lock +from typing import TYPE_CHECKING +from typing import Callable +from typing import Iterable +from typing import Iterator +from typing import Literal +from typing import Sequence +from typing import overload from . import gateway_bootstrap from . import gateway_io +from .gateway_base import Channel +from .gateway_base import ExecModel from .gateway_base import WorkerPool from .gateway_base import get_execmodel from .gateway_base import trace from .xspec import XSpec +if TYPE_CHECKING: + from .gateway import Gateway + + NO_ENDMARKER_WANTED = object() @@ -22,14 +38,16 @@ class Group: defaultspec = "popen" - def __init__(self, xspecs=(), execmodel="thread"): + def __init__( + self, xspecs: Iterable[XSpec | str | None] = (), execmodel: str = "thread" + ) -> None: """initialize group and make gateways as specified. execmodel can be 'thread' or 'eventlet'. """ - self._gateways = [] + self._gateways: list[Gateway] = [] self._autoidcounter = 0 self._autoidlock = Lock() - self._gateways_to_join = [] + self._gateways_to_join: list[Gateway] = [] # we use the same execmodel for all of the Gateway objects # we spawn on our side. Probably we should not allow different # execmodels between different groups but not clear. @@ -41,14 +59,16 @@ def __init__(self, xspecs=(), execmodel="thread"): atexit.register(self._cleanup_atexit) @property - def execmodel(self): + def execmodel(self) -> ExecModel: return self._execmodel @property - def remote_execmodel(self): + def remote_execmodel(self) -> ExecModel: return self._remote_execmodel - def set_execmodel(self, execmodel, remote_execmodel=None): + def set_execmodel( + self, execmodel: str, remote_execmodel: str | None = None + ) -> None: """Set the execution model for local and remote site. execmodel can be one of "thread" or "eventlet" (XXX gevent). @@ -68,11 +88,11 @@ def set_execmodel(self, execmodel, remote_execmodel=None): self._execmodel = get_execmodel(execmodel) self._remote_execmodel = get_execmodel(remote_execmodel) - def __repr__(self): + def __repr__(self) -> str: idgateways = [gw.id for gw in self] return "" % idgateways - def __getitem__(self, key): + def __getitem__(self, key: int | str | Gateway) -> Gateway: if isinstance(key, int): return self._gateways[key] for gw in self._gateways: @@ -80,20 +100,20 @@ def __getitem__(self, key): return gw raise KeyError(key) - def __contains__(self, key): + def __contains__(self, key: str) -> bool: try: self[key] return True except KeyError: return False - def __len__(self): + def __len__(self) -> int: return len(self._gateways) - def __iter__(self): + def __iter__(self) -> Iterator[Gateway]: return iter(list(self._gateways)) - def makegateway(self, spec=None): + def makegateway(self, spec: XSpec | str | None = None) -> Gateway: """create and configure a gateway to a Python interpreter. The ``spec`` string encodes the target gateway type and configuration information. The general format is:: @@ -133,8 +153,8 @@ def makegateway(self, spec=None): elif spec.socket: from . import gateway_socket - io = gateway_socket.create_io(spec, self, execmodel=self.execmodel) - gw = gateway_bootstrap.bootstrap(io, spec) + sio = gateway_socket.create_io(spec, self, execmodel=self.execmodel) + gw = gateway_bootstrap.bootstrap(sio, spec) else: raise ValueError(f"no gateway type found for {spec._spec!r}") gw.spec = spec @@ -160,7 +180,7 @@ def makegateway(self, spec=None): channel.waitclose() return gw - def allocate_id(self, spec): + def allocate_id(self, spec: XSpec) -> None: """(re-entrant) allocate id for the given xspec object.""" if spec.id is None: with self._autoidlock: @@ -170,22 +190,22 @@ def allocate_id(self, spec): raise ValueError(f"already have gateway with id {id!r}") spec.id = id - def _register(self, gateway): + def _register(self, gateway: Gateway) -> None: assert not hasattr(gateway, "_group") assert gateway.id assert gateway.id not in self self._gateways.append(gateway) gateway._group = self - def _unregister(self, gateway): + def _unregister(self, gateway: Gateway) -> None: self._gateways.remove(gateway) self._gateways_to_join.append(gateway) - def _cleanup_atexit(self): + def _cleanup_atexit(self) -> None: trace(f"=== atexit cleanup {self!r} ===") self.terminate(timeout=1.0) - def terminate(self, timeout=None): + def terminate(self, timeout: float | None = None) -> None: """trigger exit of member gateways and wait for termination of member gateways and associated subprocesses. After waiting timeout seconds try to to kill local sub processes of popen- @@ -194,19 +214,19 @@ def terminate(self, timeout=None): """ while self: - vias = {} + vias: set[str] = set() for gw in self: if gw.spec.via: - vias[gw.spec.via] = True + vias.add(gw.spec.via) for gw in self: if gw.id not in vias: gw.exit() - def join_wait(gw): + def join_wait(gw: Gateway) -> None: gw.join() gw._io.wait() - def kill(gw): + def kill(gw: Gateway) -> None: trace("Gateways did not come down after timeout: %r" % gw) gw._io.kill() @@ -220,7 +240,11 @@ def kill(gw): ) self._gateways_to_join[:] = [] - def remote_exec(self, source, **kwargs): + def remote_exec( + self, + source: str | types.FunctionType | Callable[..., object] | types.ModuleType, + **kwargs, + ) -> MultiChannel: """remote_exec source on all member gateways and return MultiChannel connecting to all sub processes. """ @@ -231,28 +255,38 @@ def remote_exec(self, source, **kwargs): class MultiChannel: - def __init__(self, channels): + def __init__(self, channels: Sequence[Channel]) -> None: self._channels = channels - def __len__(self): + def __len__(self) -> int: return len(self._channels) - def __iter__(self): + def __iter__(self) -> Iterator[Channel]: return iter(self._channels) - def __getitem__(self, key): + def __getitem__(self, key: int) -> Channel: return self._channels[key] - def __contains__(self, chan): + def __contains__(self, chan: Channel) -> bool: return chan in self._channels - def send_each(self, item): + def send_each(self, item: object) -> None: for ch in self._channels: ch.send(item) - def receive_each(self, withchannel=False): + @overload + def receive_each(self, withchannel: Literal[False] = ...) -> list[object]: + pass + + @overload + def receive_each(self, withchannel: Literal[True]) -> list[tuple[Channel, object]]: + pass + + def receive_each( + self, withchannel: bool = False + ) -> list[tuple[Channel, object]] | list[object]: assert not hasattr(self, "_queue") - l = [] + l: list[object] = [] for ch in self._channels: obj = ch.receive() if withchannel: @@ -261,17 +295,17 @@ def receive_each(self, withchannel=False): l.append(obj) return l - def make_receive_queue(self, endmarker=NO_ENDMARKER_WANTED): + def make_receive_queue(self, endmarker: object = NO_ENDMARKER_WANTED): try: - return self._queue + return self._queue # type: ignore[has-type] except AttributeError: self._queue = None for ch in self._channels: if self._queue is None: self._queue = ch.gateway.execmodel.queue.Queue() - def putreceived(obj, channel=ch): - self._queue.put((channel, obj)) + def putreceived(obj, channel: Channel = ch) -> None: + self._queue.put((channel, obj)) # type: ignore[union-attr] if endmarker is NO_ENDMARKER_WANTED: ch.setcallback(putreceived) @@ -279,7 +313,7 @@ def putreceived(obj, channel=ch): ch.setcallback(putreceived, endmarker=endmarker) return self._queue - def waitclose(self): + def waitclose(self) -> None: first = None for ch in self._channels: try: @@ -291,10 +325,12 @@ def waitclose(self): raise first -def safe_terminate(execmodel, timeout, list_of_paired_functions): +def safe_terminate( + execmodel: ExecModel, timeout: float | None, list_of_paired_functions +) -> None: workerpool = WorkerPool(execmodel) - def termkill(termfunc, killfunc): + def termkill(termfunc, killfunc) -> None: termreply = workerpool.spawn(termfunc) try: termreply.get(timeout=timeout) diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 8afa4dac..648021d9 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -3,12 +3,19 @@ (c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ +from __future__ import annotations + import os import stat from hashlib import md5 from queue import Queue +from typing import Callable +from typing import Literal import execnet.rsync_remote +from execnet.gateway import Gateway +from execnet.gateway_base import BaseGateway +from execnet.gateway_base import Channel class RSync: @@ -21,48 +28,64 @@ class RSync: a path on remote side). """ - def __init__(self, sourcedir, callback=None, verbose=True): + def __init__(self, sourcedir, callback=None, verbose: bool = True) -> None: self._sourcedir = str(sourcedir) self._verbose = verbose assert callback is None or callable(callback) self._callback = callback - self._channels = {} - self._receivequeue = Queue() - self._links = [] - - def filter(self, path): + self._channels: dict[Channel, Callable[[], None] | None] = {} + self._receivequeue: Queue[ + tuple[ + Channel, + ( + None + | tuple[Literal["send"], tuple[list[str], bytes]] + | tuple[Literal["list_done"], None] + | tuple[Literal["ack"], str] + | tuple[Literal["links"], None] + | tuple[Literal["done"], None] + ), + ] + ] = Queue() + self._links: list[tuple[Literal["linkbase", "link"], str, str]] = [] + + def filter(self, path: str) -> bool: return True - def _end_of_channel(self, channel): + def _end_of_channel(self, channel: Channel) -> None: if channel in self._channels: # too early! we must have got an error channel.waitclose() # or else we raise one raise OSError(f"connection unexpectedly closed: {channel.gateway} ") - def _process_link(self, channel): + def _process_link(self, channel: Channel) -> None: for link in self._links: channel.send(link) # completion marker, this host is done channel.send(42) - def _done(self, channel): + def _done(self, channel: Channel) -> None: """Call all callbacks""" finishedcallback = self._channels.pop(channel) if finishedcallback: finishedcallback() channel.waitclose() - def _list_done(self, channel): + def _list_done(self, channel: Channel) -> None: # sum up all to send if self._callback: s = sum([self._paths[i] for i in self._to_send[channel]]) self._callback("list", s, channel) - def _send_item(self, channel, data): + def _send_item( + self, + channel: Channel, + modified_rel_path_components: list[str], + checksum: bytes, + ) -> None: """Send one item""" - modified_rel_path, checksum = data - modifiedpath = os.path.join(self._sourcedir, *modified_rel_path) + modifiedpath = os.path.join(self._sourcedir, *modified_rel_path_components) try: f = open(modifiedpath, "rb") data = f.read() @@ -70,7 +93,7 @@ def _send_item(self, channel, data): data = None # provide info to progress callback function - modified_rel_path = "/".join(modified_rel_path) + modified_rel_path = "/".join(modified_rel_path_components) if data is not None: self._paths[modified_rel_path] = len(data) else: @@ -88,11 +111,11 @@ def _send_item(self, channel, data): self._report_send_file(channel.gateway, modified_rel_path) channel.send(data) - def _report_send_file(self, gateway, modified_rel_path): + def _report_send_file(self, gateway: BaseGateway, modified_rel_path: str) -> None: if self._verbose: print(f"{gateway} <= {modified_rel_path}") - def send(self, raises=True): + def send(self, raises: bool = True) -> None: """Sends a sourcedir to all added targets. Flag indicates whether to raise an error or return in case of lack of targets @@ -110,8 +133,8 @@ def send(self, raises=True): # paths and to_send are only used for doing # progress-related callbacks - self._paths = {} - self._to_send = {} + self._paths: dict[str, int] = {} + self._to_send: dict[Channel, list[str]] = {} # send modified file to clients while self._channels: @@ -119,30 +142,34 @@ def send(self, raises=True): if req is None: self._end_of_channel(channel) else: - command, data = req - if command == "links": + if req[0] == "links": self._process_link(channel) - elif command == "done": + elif req[0] == "done": self._done(channel) - elif command == "ack": + elif req[0] == "ack": if self._callback: - self._callback("ack", self._paths[data], channel) - elif command == "list_done": + self._callback("ack", self._paths[req[1]], channel) + elif req[0] == "list_done": self._list_done(channel) - elif command == "send": - self._send_item(channel, data) - del data + elif req[0] == "send": + self._send_item(channel, req[1][0], req[1][1]) else: - assert "Unknown command %s" % command - - def add_target(self, gateway, destdir, finishedcallback=None, **options): + assert "Unknown command %s" % req[0] # type: ignore[unreachable] + + def add_target( + self, + gateway: Gateway, + destdir: str | os.PathLike[str], + finishedcallback: Callable[[], None] | None = None, + **options, + ) -> None: """Adds a remote target specified via a gateway and a remote destination directory. """ for name in options: assert name in ("delete",) - def itemcallback(req): + def itemcallback(req) -> None: self._receivequeue.put((channel, req)) channel = gateway.remote_exec(execnet.rsync_remote) @@ -151,14 +178,19 @@ def itemcallback(req): channel.send((str(destdir), options)) self._channels[channel] = finishedcallback - def _broadcast(self, msg): + def _broadcast(self, msg: object) -> None: for channel in self._channels: channel.send(msg) - def _send_link(self, linktype, basename, linkpoint): + def _send_link( + self, + linktype: Literal["linkbase", "link"], + basename: str, + linkpoint: str, + ) -> None: self._links.append((linktype, basename, linkpoint)) - def _send_directory(self, path): + def _send_directory(self, path: str) -> None: # dir: send a list of entries names = [] subpaths = [] @@ -172,7 +204,7 @@ def _send_directory(self, path): for p in subpaths: self._send_directory_structure(p) - def _send_link_structure(self, path): + def _send_link_structure(self, path: str) -> None: sourcedir = self._sourcedir basename = path[len(self._sourcedir) + 1 :] linkpoint = os.readlink(path) @@ -189,8 +221,10 @@ def _send_link_structure(self, path): relpath = os.path.relpath(linkpoint, sourcedir) except ValueError: relpath = None - if relpath not in (None, os.curdir, os.pardir) and not relpath.startswith( - os.pardir + os.sep + if ( + relpath is not None + and relpath not in (os.curdir, os.pardir) + and not relpath.startswith(os.pardir + os.sep) ): self._send_link("linkbase", basename, relpath) else: @@ -198,7 +232,7 @@ def _send_link_structure(self, path): self._send_link("link", basename, linkpoint) self._broadcast(None) - def _send_directory_structure(self, path): + def _send_directory_structure(self, path: str) -> None: try: st = os.lstat(path) except OSError: diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 758c4ca4..24dccc9c 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -1,18 +1,26 @@ """ (c) 2006-2013, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ +from __future__ import annotations +from typing import TYPE_CHECKING +from typing import Literal +from typing import cast -def serve_rsync(channel): +if TYPE_CHECKING: + from execnet.gateway_base import Channel + + +def serve_rsync(channel: Channel) -> None: import os import shutil import stat from hashlib import md5 - destdir, options = channel.receive() + destdir, options = cast("tuple[str, dict[str, object]]", channel.receive()) modifiedfiles = [] - def remove(path): + def remove(path: str) -> None: assert path.startswith(destdir) try: os.unlink(path) @@ -20,7 +28,7 @@ def remove(path): # assume it's a dir shutil.rmtree(path, True) - def receive_directory_structure(path, relcomponents): + def receive_directory_structure(path: str, relcomponents: list[str]) -> None: try: st = os.lstat(path) except OSError: @@ -77,7 +85,7 @@ def receive_directory_structure(path, relcomponents): channel.send(("list_done", None)) for path, (mode, time, size) in modifiedfiles: - data = channel.receive() + data = cast(bytes, channel.receive()) channel.send(("ack", path[len(destdir) + 1 :])) if data is not None: if STRICT_CHECK and len(data) != size: @@ -97,7 +105,9 @@ def receive_directory_structure(path, relcomponents): msg = channel.receive() while msg != 42: # we get symlink - _type, relpath, linkpoint = msg + _type, relpath, linkpoint = cast( + "tuple[Literal['linkbase', 'link'], str, str]", msg + ) path = os.path.join(destdir, relpath) try: remove(path) diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index c69a57ef..7f4bc02b 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -10,43 +10,44 @@ import sys from threading import Thread from traceback import print_exc +from typing import NoReturn -def clientside(): +def clientside() -> NoReturn: print("client side starting") - host, port = sys.argv[1].split(":") - port = int(port) + host, portstr = sys.argv[1].split(":") + port = int(portstr) myself = open(os.path.abspath(sys.argv[0])).read() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) - sock.sendall(repr(myself) + "\n") + sock.sendall((repr(myself) + "\n").encode()) print("send boot string") inputlist = [sock, sys.stdin] try: while 1: r, w, e = select.select(inputlist, [], []) if sys.stdin in r: - line = raw_input() # noqa:F821 - sock.sendall(line + "\n") + line = input() + sock.sendall((line + "\n").encode()) if sock in r: - line = sock.recv(4096) + line = sock.recv(4096).decode() sys.stdout.write(line) sys.stdout.flush() except BaseException: import traceback - print(traceback.print_exc()) + traceback.print_exc() sys.exit(1) class promptagent(Thread): - def __init__(self, clientsock): + def __init__(self, clientsock) -> None: print("server side starting") - super.__init__() + super.__init__() # type: ignore[call-overload] self.clientsock = clientsock - def run(self): + def run(self) -> None: print("Entering thread prompt loop") clientfile = self.clientsock.makefile("w") diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index f56f5e76..34dfc0a3 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -11,21 +11,23 @@ """ # this part of the program only executes on the server side # +from __future__ import annotations + import os import sys +from typing import TYPE_CHECKING -progname = "socket_readline_exec_server-1.2" +try: + import fcntl +except ImportError: + fcntl = None # type: ignore[assignment] +if TYPE_CHECKING: + from execnet.gateway_base import Channel + from execnet.gateway_base import ExecModel -def get_fcntl(): - try: - import fcntl - except ImportError: - fcntl = None - return fcntl - +progname = "socket_readline_exec_server-1.2" -fcntl = get_fcntl() debug = 0 @@ -35,7 +37,7 @@ def get_fcntl(): sys.stdout = sys.stderr = f -def print_(*args): +def print_(*args) -> None: print(" ".join(str(arg) for arg in args)) @@ -45,7 +47,7 @@ def print_(*args): ) -def exec_from_one_connection(serversock): +def exec_from_one_connection(serversock) -> None: print_(progname, "Entering Accept loop", serversock.getsockname()) clientsock, address = serversock.accept() print_(progname, "got new connection from {} {}".format(*address)) @@ -60,14 +62,14 @@ def exec_from_one_connection(serversock): co = compile(source + "\n", "", "exec") print_(progname, "compiled source, executing") try: - exec_(co, g) # noqa: F821 + exec_(co, g) # type: ignore[name-defined] # noqa: F821 finally: print_(progname, "finished executing code") # background thread might hold a reference to this (!?) # clientsock.close() -def bind_and_listen(hostport, execmodel): +def bind_and_listen(hostport: str | tuple[str, int], execmodel: ExecModel): socket = execmodel.socket if isinstance(hostport, str): host, port = hostport.split(":") @@ -86,7 +88,7 @@ def bind_and_listen(hostport, execmodel): return serversock -def startserver(serversock, loop=False): +def startserver(serversock, loop: bool = False) -> None: execute_path = os.getcwd() try: while 1: @@ -123,9 +125,10 @@ def startserver(serversock, loop=False): startserver(serversock, loop=True) elif __name__ == "__channelexec__": - chan = globals()["channel"] + chan: Channel = globals()["channel"] execmodel = chan.gateway.execmodel bindname = chan.receive() + assert isinstance(bindname, (str, tuple)) sock = bind_and_listen(bindname, execmodel) port = sock.getsockname() chan.send(port) diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index bf17fd39..cc525e94 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -5,7 +5,6 @@ python socketserverservice.py register net start ExecNetSocketServer """ -import socketserver import sys import threading @@ -15,6 +14,10 @@ import win32service import win32serviceutil +from execnet.gateway_base import get_execmodel + +from . import socketserver + appname = "ExecNetSocketServer" @@ -23,7 +26,7 @@ class SocketServerService(win32serviceutil.ServiceFramework): _svc_display_name_ = "%s" % appname _svc_deps_ = ["EventLog"] - def __init__(self, args): + def __init__(self, args) -> None: # The exe-file has messages for the Event Log Viewer. # Register the exe-file as event source. # @@ -39,11 +42,11 @@ def __init__(self, args): self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) self.WAIT_TIME = 1000 # in milliseconds - def SvcStop(self): + def SvcStop(self) -> None: self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) win32event.SetEvent(self.hWaitStop) - def SvcDoRun(self): + def SvcDoRun(self) -> None: # Redirect stdout and stderr to prevent "IOError: [Errno 9] # Bad file descriptor". Windows services don't have functional # output streams. @@ -61,7 +64,8 @@ def SvcDoRun(self): hostport = ":8888" print("Starting py.execnet SocketServer on %s" % hostport) - serversock = socketserver.bind_and_listen(hostport) + exec_model = get_execmodel("thread") + serversock = socketserver.bind_and_listen(hostport, exec_model) thread = threading.Thread( target=socketserver.startserver, args=(serversock,), kwargs={"loop": True} ) diff --git a/src/execnet/xspec.py b/src/execnet/xspec.py index 4d33ad67..5bc437ea 100644 --- a/src/execnet/xspec.py +++ b/src/execnet/xspec.py @@ -1,6 +1,7 @@ """ (c) 2008-2013, holger krekel """ +from __future__ import annotations class XSpec: @@ -13,15 +14,26 @@ class XSpec: """ # XXX allow customization, for only allow specific key names - popen = ( - ssh - ) = socket = python = chdir = nice = dont_write_bytecode = execmodel = None + chdir: str | None = None + dont_write_bytecode: bool | None = None + execmodel: str | None = None + id: str | None = None + installvia: str | None = None + nice: str | None = None + popen: bool | None = None + python: str | None = None + socket: str | None = None + ssh: str | None = None + ssh_config: str | None = None + vagrant_ssh: str | None = None + via: str | None = None - def __init__(self, string): + def __init__(self, string: str) -> None: self._spec = string self.env = {} for keyvalue in string.split("//"): i = keyvalue.find("=") + value: str | bool if i == -1: key, value = keyvalue, True else: @@ -35,25 +47,25 @@ def __init__(self, string): else: setattr(self, key, value) - def __getattr__(self, name): + def __getattr__(self, name: str) -> None | bool | str: if name[0] == "_": raise AttributeError(name) return None - def __repr__(self): + def __repr__(self) -> str: return f"" - def __str__(self): + def __str__(self) -> str: return self._spec - def __hash__(self): + def __hash__(self) -> int: return hash(self._spec) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: return self._spec == getattr(other, "_spec", None) - def __ne__(self, other): + def __ne__(self, other: object) -> bool: return self._spec != getattr(other, "_spec", None) - def _samefilesystem(self): + def _samefilesystem(self) -> bool: return self.popen is not None and self.chdir is None diff --git a/testing/conftest.py b/testing/conftest.py index 133c1db4..c5fa4cf5 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,11 +1,16 @@ +from __future__ import annotations + import shutil import sys from functools import lru_cache from typing import Callable +from typing import Generator from typing import Iterator import execnet import pytest +from execnet.gateway import Gateway +from execnet.gateway_base import ExecModel from execnet.gateway_base import WorkerPool from execnet.gateway_base import get_execmodel @@ -15,7 +20,7 @@ @pytest.hookimpl(hookwrapper=True) -def pytest_runtest_setup(item): +def pytest_runtest_setup(item: pytest.Item) -> Generator[None, None, None]: if item.fspath.purebasename in ("test_group", "test_info"): getspecssh(item.config) # will skip if no gx given yield @@ -31,7 +36,7 @@ def group_function() -> Iterator[execnet.Group]: @pytest.fixture -def makegateway(group_function) -> Callable[[str], execnet.gateway.Gateway]: +def makegateway(group_function: execnet.Group) -> Callable[[str], Gateway]: return group_function.makegateway @@ -39,7 +44,7 @@ def makegateway(group_function) -> Callable[[str], execnet.gateway.Gateway]: # configuration information for tests -def pytest_addoption(parser): +def pytest_addoption(parser: pytest.Parser) -> None: group = parser.getgroup("execnet", "execnet testing options") group.addoption( "--gx", @@ -66,20 +71,20 @@ def pytest_addoption(parser): @pytest.fixture -def specssh(request): +def specssh(request: pytest.FixtureRequest) -> execnet.XSpec: return getspecssh(request.config) @pytest.fixture -def specsocket(request): +def specsocket(request: pytest.FixtureRequest) -> execnet.XSpec: return getsocketspec(request.config) -def getgspecs(config): - return map(execnet.XSpec, config.getvalueorskip("gspecs")) +def getgspecs(config: pytest.Config) -> list[execnet.XSpec]: + return [execnet.XSpec(gspec) for gspec in config.getvalueorskip("gspecs")] -def getspecssh(config): +def getspecssh(config: pytest.Config) -> execnet.XSpec: xspecs = getgspecs(config) for spec in xspecs: if spec.ssh: @@ -89,7 +94,7 @@ def getspecssh(config): pytest.skip("need '--gx ssh=...'") -def getsocketspec(config): +def getsocketspec(config: pytest.Config) -> execnet.XSpec: xspecs = getgspecs(config) for spec in xspecs: if spec.socket: @@ -97,7 +102,7 @@ def getsocketspec(config): pytest.skip("need '--gx socket=...'") -def pytest_generate_tests(metafunc): +def pytest_generate_tests(metafunc: pytest.Metafunc) -> None: if "gw" in metafunc.fixturenames: assert "anypython" not in metafunc.fixturenames, "need combine?" if hasattr(metafunc.function, "gwtypes"): @@ -110,14 +115,14 @@ def pytest_generate_tests(metafunc): @lru_cache -def getexecutable(name): +def getexecutable(name: str) -> str | None: if name == "sys.executable": return sys.executable return shutil.which(name) @pytest.fixture(params=("sys.executable", "pypy3")) -def anypython(request): +def anypython(request: pytest.FixtureRequest) -> str: name = request.param executable = getexecutable(name) if executable is None: @@ -130,14 +135,18 @@ def anypython(request): @pytest.fixture(scope="session") -def group(): +def group() -> Iterator[execnet.Group]: g = execnet.Group() yield g g.terminate(timeout=1) @pytest.fixture -def gw(request, execmodel, group): +def gw( + request: pytest.FixtureRequest, + execmodel: ExecModel, + group: execnet.Group, +) -> Gateway: try: return group[request.param] except KeyError: @@ -155,7 +164,8 @@ def gw(request, execmodel, group): f"socket//id=socket//installvia={pname}" f"//execmodel={execmodel.backend}" ) - gw.proxygw = proxygw + # TODO(typing): Clarify this assignment. + gw.proxygw = proxygw # type: ignore[attr-defined] assert pname in group elif request.param == "ssh": sshhost = request.getfixturevalue("specssh").ssh @@ -176,7 +186,7 @@ def gw(request, execmodel, group): @pytest.fixture( params=["thread", "main_thread_only", "eventlet", "gevent"], scope="session" ) -def execmodel(request): +def execmodel(request: pytest.FixtureRequest) -> ExecModel: if request.param not in ("thread", "main_thread_only"): pytest.importorskip(request.param) if request.param in ("eventlet", "gevent") and sys.platform == "win32": @@ -185,5 +195,5 @@ def execmodel(request): @pytest.fixture -def pool(execmodel): +def pool(execmodel: ExecModel) -> WorkerPool: return WorkerPool(execmodel=execmodel) diff --git a/testing/test_basics.py b/testing/test_basics.py index e52007b8..5ed53e97 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -10,6 +10,7 @@ from io import BytesIO from pathlib import Path from typing import Any +from typing import Callable import execnet import pytest @@ -17,6 +18,7 @@ from execnet import gateway_base from execnet import gateway_io from execnet.gateway_base import ChannelFactory +from execnet.gateway_base import ExecModel from execnet.gateway_base import Message from execnet.gateway_base import Popen2IO @@ -28,12 +30,12 @@ @pytest.mark.parametrize("val", ["123", 42, [1, 2, 3], ["23", 25]]) class TestSerializeAPI: - def test_serializer_api(self, val): + def test_serializer_api(self, val: object) -> None: dumped = execnet.dumps(val) val2 = execnet.loads(dumped) assert val == val2 - def test_mmap(self, tmp_path, val): + def test_mmap(self, tmp_path: Path, val: object) -> None: mmap = pytest.importorskip("mmap").mmap p = tmp_path / "data.bin" @@ -43,7 +45,7 @@ def test_mmap(self, tmp_path, val): val2 = execnet.load(m) assert val == val2 - def test_bytesio(self, val): + def test_bytesio(self, val: object) -> None: f = BytesIO() execnet.dump(f, val) read = BytesIO(f.getvalue()) @@ -51,7 +53,7 @@ def test_bytesio(self, val): assert val == val2 -def test_serializer_api_version_error(monkeypatch): +def test_serializer_api_version_error(monkeypatch: pytest.MonkeyPatch) -> None: bchr = gateway_base.bchr monkeypatch.setattr(gateway_base, "DUMPFORMAT_VERSION", bchr(1)) dumped = execnet.dumps(42) @@ -59,13 +61,13 @@ def test_serializer_api_version_error(monkeypatch): pytest.raises(execnet.DataFormatError, lambda: execnet.loads(dumped)) -def test_errors_on_execnet(): +def test_errors_on_execnet() -> None: assert hasattr(execnet, "RemoteError") assert hasattr(execnet, "TimeoutError") assert hasattr(execnet, "DataFormatError") -def test_subprocess_interaction(anypython): +def test_subprocess_interaction(anypython: str) -> None: line = gateway_io.popen_bootstrapline compile(line, "xyz", "exec") args = [str(anypython), "-c", line] @@ -77,11 +79,16 @@ def test_subprocess_interaction(anypython): stdout=subprocess.PIPE, ) - def send(line): + assert popen.stdin is not None + assert popen.stdout is not None + + def send(line: str) -> None: + assert popen.stdin is not None popen.stdin.write(line) popen.stdin.flush() - def receive(): + def receive() -> str: + assert popen.stdout is not None return popen.stdout.readline() try: @@ -102,7 +109,7 @@ def receive(): popen.wait() -def read_write_loop(): +def read_write_loop() -> None: sys.stdout.write("ok\n") sys.stdout.flush() while 1: @@ -119,10 +126,7 @@ def read_write_loop(): IO_MESSAGE_EXTRA_SOURCE = """ import sys backend = sys.argv[1] -try: - from io import BytesIO -except ImportError: - from StringIO import StringIO as BytesIO +from io import BytesIO import tempfile temp_out = BytesIO() temp_in = BytesIO() @@ -172,7 +176,7 @@ def checker(anypython: str, tmp_path: Path) -> Checker: return Checker(python=anypython, path=tmp_path) -def test_io_message(checker, execmodel): +def test_io_message(checker: Checker, execmodel: ExecModel) -> None: out = checker.run_check( inspect.getsource(gateway_base) + IO_MESSAGE_EXTRA_SOURCE, execmodel.backend ) @@ -180,7 +184,7 @@ def test_io_message(checker, execmodel): assert "all passed" in out.stdout -def test_popen_io(checker, execmodel): +def test_popen_io(checker: Checker, execmodel: ExecModel) -> None: out = checker.run_check( inspect.getsource(gateway_base) + f""" @@ -195,22 +199,22 @@ def test_popen_io(checker, execmodel): assert "hello" in out.stdout -def test_popen_io_readloop(monkeypatch, execmodel): +def test_popen_io_readloop(execmodel: ExecModel) -> None: sio = BytesIO(b"test") io = Popen2IO(sio, sio, execmodel) real_read = io._read - def newread(numbytes): + def newread(numbytes: int) -> bytes: if numbytes > 1: numbytes = numbytes - 1 - return real_read(numbytes) + return real_read(numbytes) # type: ignore[no-any-return] io._read = newread result = io.read(3) assert result == b"tes" -def test_rinfo_source(checker): +def test_rinfo_source(checker: Checker) -> None: out = checker.run_check( f""" class Channel: @@ -226,7 +230,7 @@ def send(self, data): assert "all passed" in out.stdout -def test_geterrortext(checker): +def test_geterrortext(checker: Checker) -> None: out = checker.run_check( inspect.getsource(gateway_base) + """ @@ -247,7 +251,9 @@ class Arg(Exception): @pytest.mark.skipif("not hasattr(os, 'dup')") -def test_stdouterrin_setnull(execmodel, capfd): +def test_stdouterrin_setnull( + execmodel: ExecModel, capfd: pytest.CaptureFixture[str] +) -> None: # Backup and restore stdin state, and rely on capfd to handle # this for stdout and stderr. orig_stdin = sys.stdin @@ -274,33 +280,34 @@ class gateway: class _channelfactory: finished = False - def __init__(self): - self._sent = [] - self._closed = [] + def __init__(self) -> None: + self._sent: list[object] = [] + self._closed: list[str | None] = [] self.id = 1000 - def send(self, obj): + def send(self, obj: object) -> None: self._sent.append(obj) - def close(self, errortext=None): + def close(self, errortext: str | None = None) -> None: self._closed.append(errortext) -def test_exectask(execmodel): +def test_exectask(execmodel: ExecModel) -> None: io = BytesIO() - io.execmodel = execmodel - gw = gateway_base.WorkerGateway(io, id="something") + io.execmodel = execmodel # type: ignore[attr-defined] + gw = gateway_base.WorkerGateway(io, id="something") # type: ignore[arg-type] ch = PseudoChannel() - gw.executetask((ch, ("raise ValueError()", None, {}))) + gw.executetask((ch, ("raise ValueError()", None, {}))) # type: ignore[arg-type] assert "ValueError" in str(ch._closed[0]) class TestMessage: - def test_wire_protocol(self): + def test_wire_protocol(self) -> None: for i, handler in enumerate(Message._types): one = BytesIO() data = b"23" - Message(i, 42, data).to_io(one) + # TODO(typing): Maybe make this work. + Message(i, 42, data).to_io(one) # type: ignore[arg-type] two = BytesIO(one.getvalue()) msg = Message.from_io(two) assert msg.msgcode == i @@ -312,87 +319,89 @@ def test_wire_protocol(self): class TestPureChannel: @pytest.fixture - def fac(self, execmodel): + def fac(self, execmodel: ExecModel) -> ChannelFactory: class FakeGateway: - def _trace(self, *args): + def _trace(self, *args) -> None: pass - def _send(self, *k): + def _send(self, *k) -> None: pass - FakeGateway.execmodel = execmodel - return ChannelFactory(FakeGateway()) + FakeGateway.execmodel = execmodel # type: ignore[attr-defined] + return ChannelFactory(FakeGateway()) # type: ignore[arg-type] - def test_factory_create(self, fac): + def test_factory_create(self, fac: ChannelFactory) -> None: chan1 = fac.new() assert chan1.id == 1 chan2 = fac.new() assert chan2.id == 3 - def test_factory_getitem(self, fac): + def test_factory_getitem(self, fac: ChannelFactory) -> None: chan1 = fac.new() assert fac._channels[chan1.id] == chan1 chan2 = fac.new() assert fac._channels[chan2.id] == chan2 - def test_channel_timeouterror(self, fac): + def test_channel_timeouterror(self, fac: ChannelFactory) -> None: channel = fac.new() pytest.raises(IOError, channel.waitclose, timeout=0.01) - def test_channel_makefile_incompatmode(self, fac): + def test_channel_makefile_incompatmode(self, fac) -> None: channel = fac.new() with pytest.raises(ValueError): channel.makefile("rw") class TestSourceOfFunction: - def test_lambda_unsupported(self): + def test_lambda_unsupported(self) -> None: pytest.raises(ValueError, gateway._source_of_function, lambda: 1) - def test_wrong_prototype_fails(self): - def prototype(wrong): + def test_wrong_prototype_fails(self) -> None: + def prototype(wrong) -> None: pass pytest.raises(ValueError, gateway._source_of_function, prototype) - def test_function_without_known_source_fails(self): + def test_function_without_known_source_fails(self) -> None: # this one won't be able to find the source - mess = {} + mess: dict[str, Any] = {} exec("def fail(channel): pass", mess, mess) print(inspect.getsourcefile(mess["fail"])) - pytest.raises(ValueError, gateway._source_of_function, mess["fail"]) + with pytest.raises(ValueError): + gateway._source_of_function(mess["fail"]) - def test_function_with_closure_fails(self): - mess = {} + def test_function_with_closure_fails(self) -> None: + mess: dict[str, Any] = {} - def closure(channel): + def closure(channel: object) -> None: print(mess) - pytest.raises(ValueError, gateway._source_of_function, closure) + with pytest.raises(ValueError): + gateway._source_of_function(closure) - def test_source_of_nested_function(self): - def working(channel): + def test_source_of_nested_function(self) -> None: + def working(channel: object) -> None: pass send_source = gateway._source_of_function(working).lstrip("\r\n") - expected = "def working(channel):\n pass\n" + expected = "def working(channel: object) -> None:\n pass\n" assert send_source == expected class TestGlobalFinder: - def check(self, func): + def check(self, func) -> list[str]: src = textwrap.dedent(inspect.getsource(func)) code = func.__code__ return gateway._find_non_builtin_globals(src, code) - def test_local(self): + def test_local(self) -> None: def f(a, b, c): d = 3 return d assert self.check(f) == [] - def test_global(self): + def test_global(self) -> None: def f(a, b): sys d = 4 @@ -400,19 +409,19 @@ def f(a, b): assert self.check(f) == ["sys"] - def test_builtin(self): - def f(): + def test_builtin(self) -> None: + def f() -> None: len assert self.check(f) == [] - def test_function_with_global_fails(self): - def func(channel): + def test_function_with_global_fails(self) -> None: + def func(channel) -> None: sys pytest.raises(ValueError, gateway._source_of_function, func) - def test_method_call(self): + def test_method_call(self) -> None: # method names are reason # for the simple code object based heusteric failing def f(channel): @@ -422,8 +431,10 @@ def f(channel): @skip_win_pypy -def test_remote_exec_function_with_kwargs(anypython, makegateway): - def func(channel, data): +def test_remote_exec_function_with_kwargs( + anypython: str, makegateway: Callable[[str], gateway.Gateway] +) -> None: + def func(channel, data) -> None: channel.send(data) gw = makegateway("popen//python=%s" % anypython) @@ -434,7 +445,7 @@ def func(channel, data): assert result == 1 -def test_remote_exc__no_kwargs(makegateway): +def test_remote_exc__no_kwargs(makegateway: Callable[[], gateway.Gateway]) -> None: gw = makegateway() with pytest.raises(TypeError): gw.remote_exec(gateway_base, kwarg=1) @@ -443,7 +454,9 @@ def test_remote_exc__no_kwargs(makegateway): @skip_win_pypy -def test_remote_exec_inspect_stack(makegateway): +def test_remote_exec_inspect_stack( + makegateway: Callable[[], gateway.Gateway], +) -> None: gw = makegateway() ch = gw.remote_exec( """ @@ -453,5 +466,7 @@ def test_remote_exec_inspect_stack(makegateway): channel.send('\\n'.join(traceback.format_stack())) """ ) - assert 'File ""' in ch.receive() + received = ch.receive() + assert isinstance(received, str) + assert 'File ""' in received ch.waitclose() diff --git a/testing/test_channel.py b/testing/test_channel.py index 0f1588e9..67039825 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -1,9 +1,13 @@ """ mostly functional tests of gateways. """ +from __future__ import annotations + import time import pytest +from execnet.gateway import Gateway +from execnet.gateway_base import Channel needs_early_gc = pytest.mark.skipif("not hasattr(sys, 'getrefcount')") needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") @@ -11,16 +15,16 @@ class TestChannelBasicBehaviour: - def test_serialize_error(self, gw): + def test_serialize_error(self, gw: Gateway) -> None: ch = gw.remote_exec("channel.send(ValueError(42))") excinfo = pytest.raises(ch.RemoteError, ch.receive) assert "can't serialize" in str(excinfo.value) - def test_channel_close_and_then_receive_error(self, gw): + def test_channel_close_and_then_receive_error(self, gw: Gateway) -> None: channel = gw.remote_exec("raise ValueError") pytest.raises(channel.RemoteError, channel.receive) - def test_channel_finish_and_then_EOFError(self, gw): + def test_channel_finish_and_then_EOFError(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.send(42)") x = channel.receive() assert x == 42 @@ -28,20 +32,22 @@ def test_channel_finish_and_then_EOFError(self, gw): pytest.raises(EOFError, channel.receive) pytest.raises(EOFError, channel.receive) - def test_waitclose_timeouterror(self, gw): + def test_waitclose_timeouterror(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.receive()") pytest.raises(channel.TimeoutError, channel.waitclose, 0.02) channel.send(1) channel.waitclose(timeout=TESTTIMEOUT) - def test_channel_receive_timeout(self, gw): + def test_channel_receive_timeout(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.send(channel.receive())") with pytest.raises(channel.TimeoutError): channel.receive(timeout=0.2) channel.send(1) channel.receive(timeout=TESTTIMEOUT) - def test_channel_receive_internal_timeout(self, gw, monkeypatch): + def test_channel_receive_internal_timeout( + self, gw: Gateway, monkeypatch: pytest.MonkeyPatch + ) -> None: channel = gw.remote_exec( """ import time @@ -52,23 +58,23 @@ def test_channel_receive_internal_timeout(self, gw, monkeypatch): monkeypatch.setattr(channel.__class__, "_INTERNALWAKEUP", 0.2) channel.receive() - def test_channel_close_and_then_receive_error_multiple(self, gw): + def test_channel_close_and_then_receive_error_multiple(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.send(42) ; raise ValueError") x = channel.receive() assert x == 42 pytest.raises(channel.RemoteError, channel.receive) - def test_channel__local_close(self, gw): + def test_channel__local_close(self, gw: Gateway) -> None: channel = gw._channelfactory.new() gw._channelfactory._local_close(channel.id) channel.waitclose(0.1) - def test_channel__local_close_error(self, gw): + def test_channel__local_close_error(self, gw: Gateway) -> None: channel = gw._channelfactory.new() gw._channelfactory._local_close(channel.id, channel.RemoteError("error")) pytest.raises(channel.RemoteError, channel.waitclose, 0.01) - def test_channel_error_reporting(self, gw): + def test_channel_error_reporting(self, gw: Gateway) -> None: channel = gw.remote_exec("def foo():\n return foobar()\nfoo()\n") excinfo = pytest.raises(channel.RemoteError, channel.receive) msg = str(excinfo.value) @@ -76,7 +82,7 @@ def test_channel_error_reporting(self, gw): assert "NameError" in msg assert "foobar" in msg - def test_channel_syntax_error(self, gw): + def test_channel_syntax_error(self, gw: Gateway) -> None: # missing colon channel = gw.remote_exec("def foo()\n return 1\nfoo()\n") excinfo = pytest.raises(channel.RemoteError, channel.receive) @@ -84,7 +90,7 @@ def test_channel_syntax_error(self, gw): assert msg.startswith("Traceback (most recent call last):") assert "SyntaxError" in msg - def test_channel_iter(self, gw): + def test_channel_iter(self, gw: Gateway) -> None: channel = gw.remote_exec( """ for x in range(3): @@ -94,7 +100,7 @@ def test_channel_iter(self, gw): l = list(channel) assert l == [0, 1, 2] - def test_channel_pass_in_structure(self, gw): + def test_channel_pass_in_structure(self, gw: Gateway) -> None: channel = gw.remote_exec( """ ch1, ch2 = channel.receive() @@ -109,7 +115,7 @@ def test_channel_pass_in_structure(self, gw): data = newchan2.receive() assert data == 2 - def test_channel_multipass(self, gw): + def test_channel_multipass(self, gw: Gateway) -> None: channel = gw.remote_exec( """ channel.send(channel) @@ -122,15 +128,16 @@ def test_channel_multipass(self, gw): channel.send(newchan) channel.waitclose() - def test_channel_passing_over_channel(self, gw): + def test_channel_passing_over_channel(self, gw: Gateway) -> None: channel = gw.remote_exec( """ - c = channel.gateway.newchannel() - channel.send(c) - c.send(42) - """ + c = channel.gateway.newchannel() + channel.send(c) + c.send(42) + """ ) c = channel.receive() + assert isinstance(c, Channel) x = c.receive() assert x == 42 @@ -139,15 +146,15 @@ def test_channel_passing_over_channel(self, gw): # assert c.id not in gw._channelfactory newchan = gw.remote_exec( """ - assert %d not in channel.gateway._channelfactory._channels - """ + assert %d not in channel.gateway._channelfactory._channels + """ % channel.id ) newchan.waitclose(TESTTIMEOUT) assert channel.id not in gw._channelfactory._channels - def test_channel_receiver_callback(self, gw): - l = [] + def test_channel_receiver_callback(self, gw: Gateway) -> None: + l: list[int] = [] # channel = gw.newchannel(receiver=l.append) channel = gw.remote_exec( source=""" @@ -163,8 +170,8 @@ def test_channel_receiver_callback(self, gw): assert l[:2] == [42, 13] assert isinstance(l[2], channel.__class__) - def test_channel_callback_after_receive(self, gw): - l = [] + def test_channel_callback_after_receive(self, gw: Gateway) -> None: + l: list[int] = [] channel = gw.remote_exec( source=""" channel.send(42) @@ -181,10 +188,10 @@ def test_channel_callback_after_receive(self, gw): assert l[0] == 13 assert isinstance(l[1], channel.__class__) - def test_waiting_for_callbacks(self, gw): + def test_waiting_for_callbacks(self, gw: Gateway) -> None: l = [] - def callback(msg): + def callback(msg) -> None: import time time.sleep(0.2) @@ -199,14 +206,16 @@ def callback(msg): channel.waitclose(TESTTIMEOUT) assert l == [42] - def test_channel_callback_stays_active(self, gw): + def test_channel_callback_stays_active(self, gw: Gateway) -> None: self.check_channel_callback_stays_active(gw, earlyfree=True) - def check_channel_callback_stays_active(self, gw, earlyfree=True): + def check_channel_callback_stays_active( + self, gw: Gateway, earlyfree: bool = True + ) -> Channel | None: if gw.spec.execmodel == "gevent": pytest.xfail("investigate gevent failure") # with 'earlyfree==True', this tests the "sendonly" channel state. - l = [] + l: list[int] = [] channel = gw.remote_exec( source=""" import _thread @@ -223,11 +232,10 @@ def producer(subchannel): subchannel = gw.newchannel() subchannel.setcallback(l.append) channel.send(subchannel) - if earlyfree: - subchannel = None + subchan = None if earlyfree else subchannel counter = 100 while len(l) < 5: - if subchannel and subchannel.isclosed(): + if subchan and subchan.isclosed(): break counter -= 1 print(counter) @@ -235,16 +243,17 @@ def producer(subchannel): pytest.fail("timed out waiting for the answer[%d]" % len(l)) time.sleep(0.04) # busy-wait assert l == [0, 100, 200, 300, 400] - return subchannel + return subchan @needs_early_gc - def test_channel_callback_remote_freed(self, gw): + def test_channel_callback_remote_freed(self, gw: Gateway) -> None: channel = self.check_channel_callback_stays_active(gw, earlyfree=False) + assert channel is not None # freed automatically at the end of producer() channel.waitclose(TESTTIMEOUT) - def test_channel_endmarker_callback(self, gw): - l = [] + def test_channel_endmarker_callback(self, gw: Gateway) -> None: + l: list[int | Channel] = [] channel = gw.remote_exec( source=""" channel.send(42) @@ -260,7 +269,7 @@ def test_channel_endmarker_callback(self, gw): assert isinstance(l[2], channel.__class__) assert l[3] == 999 - def test_channel_endmarker_callback_error(self, gw): + def test_channel_endmarker_callback_error(self, gw: Gateway) -> None: q = gw.execmodel.queue.Queue() channel = gw.remote_exec( source=""" @@ -274,7 +283,7 @@ def test_channel_endmarker_callback_error(self, gw): assert err assert str(err).find("ValueError") != -1 - def test_channel_callback_error(self, gw): + def test_channel_callback_error(self, gw: Gateway) -> None: channel = gw.remote_exec( """ def f(item): @@ -287,6 +296,7 @@ def f(item): """ ) subchan = channel.receive() + assert isinstance(subchan, Channel) subchan.send(1) with pytest.raises(subchan.RemoteError) as excinfo: subchan.waitclose(TESTTIMEOUT) @@ -296,7 +306,7 @@ def f(item): class TestChannelFile: - def test_channel_file_write(self, gw): + def test_channel_file_write(self, gw: Gateway) -> None: channel = gw.remote_exec( """ f = channel.makefile() @@ -306,19 +316,20 @@ def test_channel_file_write(self, gw): """ ) first = channel.receive() + assert isinstance(first, str) assert first.strip() == "hello world" second = channel.receive() assert second == 42 - def test_channel_file_write_error(self, gw): + def test_channel_file_write_error(self, gw: Gateway) -> None: channel = gw.remote_exec("pass") f = channel.makefile() assert not f.isatty() channel.waitclose(TESTTIMEOUT) with pytest.raises(IOError): - f.write("hello") + f.write(b"hello") - def test_channel_file_proxyclose(self, gw): + def test_channel_file_proxyclose(self, gw: Gateway) -> None: channel = gw.remote_exec( """ f = channel.makefile(proxyclose=True) @@ -328,10 +339,11 @@ def test_channel_file_proxyclose(self, gw): """ ) first = channel.receive() + assert isinstance(first, str) assert first.strip() == "hello world" pytest.raises(channel.RemoteError, channel.receive) - def test_channel_file_read(self, gw): + def test_channel_file_read(self, gw: Gateway) -> None: channel = gw.remote_exec( """ f = channel.makefile(mode='r') @@ -347,7 +359,7 @@ def test_channel_file_read(self, gw): assert s1 == "xy" assert s2 == "abcde" - def test_channel_file_read_empty(self, gw): + def test_channel_file_read_empty(self, gw: Gateway) -> None: channel = gw.remote_exec("pass") f = channel.makefile(mode="r") s = f.read(3) @@ -355,7 +367,7 @@ def test_channel_file_read_empty(self, gw): s = f.read(5) assert s == "" - def test_channel_file_readline_remote(self, gw): + def test_channel_file_readline_remote(self, gw: Gateway) -> None: channel = gw.remote_exec( """ channel.send('123\\n45') @@ -368,7 +380,7 @@ def test_channel_file_readline_remote(self, gw): s = f.readline() assert s == "45" - def test_channel_makefile_incompatmode(self, gw): + def test_channel_makefile_incompatmode(self, gw: Gateway) -> None: channel = gw.newchannel() with pytest.raises(ValueError): - channel.makefile("rw") + channel.makefile("rw") # type: ignore[call-overload] diff --git a/testing/test_compatibility_regressions.py b/testing/test_compatibility_regressions.py index bc788e20..5343e7d5 100644 --- a/testing/test_compatibility_regressions.py +++ b/testing/test_compatibility_regressions.py @@ -1,7 +1,7 @@ from execnet import gateway_base -def test_opcodes(): +def test_opcodes() -> None: data = vars(gateway_base.opcode) computed = {k: v for k, v in data.items() if "__" not in k} assert computed == { diff --git a/testing/test_gateway.py b/testing/test_gateway.py index a2567485..e507a935 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -1,17 +1,21 @@ """ mostly functional tests of gateways. """ +from __future__ import annotations + import os import pathlib import shutil import signal import sys from textwrap import dedent +from typing import Callable import execnet import pytest from execnet import gateway_base from execnet import gateway_io +from execnet.gateway import Gateway TESTTIMEOUT = 10.0 # seconds needs_osdup = pytest.mark.skipif("not hasattr(os, 'dup')") @@ -27,25 +31,25 @@ class TestBasicGateway: - def test_correct_setup(self, gw): + def test_correct_setup(self, gw: Gateway) -> None: assert gw.hasreceiver() assert gw in gw._group assert gw.id in gw._group assert gw.spec - def test_repr_doesnt_crash(self, gw): + def test_repr_doesnt_crash(self, gw: Gateway) -> None: assert isinstance(repr(gw), str) - def test_attribute__name__(self, gw): + def test_attribute__name__(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.send(__name__)") name = channel.receive() assert name == "__channelexec__" - def test_gateway_status_simple(self, gw): + def test_gateway_status_simple(self, gw: Gateway) -> None: status = gw.remote_status() assert status.numexecuting == 0 - def test_exc_info_is_clear_after_gateway_startup(self, gw): + def test_exc_info_is_clear_after_gateway_startup(self, gw: Gateway) -> None: ch = gw.remote_exec( """ import traceback, sys @@ -61,7 +65,7 @@ def test_exc_info_is_clear_after_gateway_startup(self, gw): if res != 0: pytest.fail("remote raised\n%s" % res) - def test_gateway_status_no_real_channel(self, gw): + def test_gateway_status_no_real_channel(self, gw: Gateway) -> None: numchan = gw._channelfactory.channels() gw.remote_status() numchan2 = gw._channelfactory.channels() @@ -71,7 +75,7 @@ def test_gateway_status_no_real_channel(self, gw): assert numchan2 == numchan @flakytest - def test_gateway_status_busy(self, gw): + def test_gateway_status_busy(self, gw: Gateway) -> None: numchannels = gw.remote_status().numchannels ch1 = gw.remote_exec("channel.send(1); channel.receive()") ch2 = gw.remote_exec("channel.receive()") @@ -92,7 +96,7 @@ def test_gateway_status_busy(self, gw): # race condition assert status.numchannels <= numchannels - def test_remote_exec_module(self, tmp_path, gw): + def test_remote_exec_module(self, tmp_path: pathlib.Path, gw: Gateway) -> None: p = tmp_path / "remotetest.py" p.write_text("channel.send(1)") mod = type(os)("remotetest") @@ -105,7 +109,9 @@ def test_remote_exec_module(self, tmp_path, gw): name = channel.receive() assert name == 2 - def test_remote_exec_module_is_removed(self, gw, tmp_path, monkeypatch): + def test_remote_exec_module_is_removed( + self, gw: Gateway, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch + ) -> None: remotetest = tmp_path / "remote.py" remotetest.write_text( dedent( @@ -122,7 +128,7 @@ def remote(): ) monkeypatch.syspath_prepend(tmp_path) - import remote + import remote # type: ignore[import-not-found] ch = gw.remote_exec(remote) # simulate sending the code to a remote location that does not have @@ -136,7 +142,12 @@ def remote(): assert result is True - def test_remote_exec_module_with_traceback(self, gw, tmp_path, monkeypatch): + def test_remote_exec_module_with_traceback( + self, + gw: Gateway, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: remotetestpy = tmp_path / "remotetest.py" remotetestpy.write_text( dedent( @@ -151,7 +162,7 @@ def run_me(channel=None): ) monkeypatch.syspath_prepend(tmp_path) - import remotetest + import remotetest # type: ignore[import-not-found] ch = gw.remote_exec(remotetest) try: @@ -171,7 +182,7 @@ def run_me(channel=None): finally: ch.close() - def test_correct_setup_no_py(self, gw): + def test_correct_setup_no_py(self, gw: Gateway) -> None: channel = gw.remote_exec( """ import sys @@ -179,32 +190,33 @@ def test_correct_setup_no_py(self, gw): """ ) remotemodules = channel.receive() + assert isinstance(remotemodules, list) assert "py" not in remotemodules, "py should not be imported on remote side" - def test_remote_exec_waitclose(self, gw): + def test_remote_exec_waitclose(self, gw: Gateway) -> None: channel = gw.remote_exec("pass") channel.waitclose(TESTTIMEOUT) - def test_remote_exec_waitclose_2(self, gw): + def test_remote_exec_waitclose_2(self, gw: Gateway) -> None: channel = gw.remote_exec("def gccycle(): pass") channel.waitclose(TESTTIMEOUT) - def test_remote_exec_waitclose_noarg(self, gw): + def test_remote_exec_waitclose_noarg(self, gw: Gateway) -> None: channel = gw.remote_exec("pass") channel.waitclose() - def test_remote_exec_error_after_close(self, gw): + def test_remote_exec_error_after_close(self, gw: Gateway) -> None: channel = gw.remote_exec("pass") channel.waitclose(TESTTIMEOUT) pytest.raises(IOError, channel.send, 0) - def test_remote_exec_no_explicit_close(self, gw): + def test_remote_exec_no_explicit_close(self, gw: Gateway) -> None: channel = gw.remote_exec("channel.close()") with pytest.raises(channel.RemoteError) as excinfo: channel.waitclose(TESTTIMEOUT) assert "explicit" in excinfo.value.formatted - def test_remote_exec_channel_anonymous(self, gw): + def test_remote_exec_channel_anonymous(self, gw: Gateway) -> None: channel = gw.remote_exec( """ obj = channel.receive() @@ -216,7 +228,7 @@ def test_remote_exec_channel_anonymous(self, gw): assert result == 42 @needs_osdup - def test_confusion_from_os_write_stdout(self, gw): + def test_confusion_from_os_write_stdout(self, gw: Gateway) -> None: channel = gw.remote_exec( """ import os @@ -233,7 +245,7 @@ def test_confusion_from_os_write_stdout(self, gw): assert res == 42 @needs_osdup - def test_confusion_from_os_write_stderr(self, gw): + def test_confusion_from_os_write_stderr(self, gw: Gateway) -> None: channel = gw.remote_exec( """ import os @@ -249,7 +261,7 @@ def test_confusion_from_os_write_stderr(self, gw): res = channel.receive() assert res == 42 - def test__rinfo(self, gw): + def test__rinfo(self, gw: Gateway) -> None: rinfo = gw._rinfo() assert rinfo.executable assert rinfo.cwd @@ -276,20 +288,23 @@ def test__rinfo(self, gw): class TestPopenGateway: gwtype = "popen" - def test_chdir_separation(self, tmp_path, makegateway): + def test_chdir_separation( + self, tmp_path: pathlib.Path, makegateway: Callable[[str], Gateway] + ) -> None: with pytest.MonkeyPatch.context() as mp: mp.chdir(tmp_path) gw = makegateway("popen") c = gw.remote_exec("import os ; channel.send(os.getcwd())") x = c.receive() + assert isinstance(x, str) assert x.lower() == str(tmp_path).lower() - def test_remoteerror_readable_traceback(self, gw): + def test_remoteerror_readable_traceback(self, gw: Gateway) -> None: with pytest.raises(gateway_base.RemoteError) as e: gw.remote_exec("x y").waitclose() assert "gateway_base" in e.value.formatted - def test_many_popen(self, makegateway): + def test_many_popen(self, makegateway: Callable[[str], Gateway]) -> None: num = 4 l = [] for i in range(num): @@ -303,13 +318,15 @@ def test_many_popen(self, makegateway): ret = channel.receive() assert ret == 42 - def test_rinfo_popen(self, gw): + def test_rinfo_popen(self, gw: Gateway) -> None: rinfo = gw._rinfo() assert rinfo.executable == sys.executable assert rinfo.cwd == os.getcwd() assert rinfo.version_info == sys.version_info - def test_waitclose_on_remote_killed(self, makegateway): + def test_waitclose_on_remote_killed( + self, makegateway: Callable[[str], Gateway] + ) -> None: gw = makegateway("popen") channel = gw.remote_exec( """ @@ -320,6 +337,7 @@ def test_waitclose_on_remote_killed(self, makegateway): """ ) remotepid = channel.receive() + assert isinstance(remotepid, int) os.kill(remotepid, signal.SIGTERM) with pytest.raises(EOFError): channel.waitclose(TESTTIMEOUT) @@ -328,7 +346,7 @@ def test_waitclose_on_remote_killed(self, makegateway): with pytest.raises(EOFError): channel.receive() - def test_receive_on_remote_sysexit(self, gw): + def test_receive_on_remote_sysexit(self, gw: Gateway) -> None: channel = gw.remote_exec( """ raise SystemExit() @@ -336,7 +354,7 @@ def test_receive_on_remote_sysexit(self, gw): ) pytest.raises(channel.RemoteError, channel.receive) - def test_dont_write_bytecode(self, makegateway): + def test_dont_write_bytecode(self, makegateway: Callable[[str], Gateway]) -> None: check_sys_dont_write_bytecode = """ import sys channel.send(sys.dont_write_bytecode) @@ -353,36 +371,41 @@ def test_dont_write_bytecode(self, makegateway): @pytest.mark.skipif("config.option.broken_isp") -def test_socket_gw_host_not_found(gw, makegateway): - pytest.raises(execnet.HostNotFound, lambda: makegateway("socket=qwepoipqwe:9000")) +def test_socket_gw_host_not_found(makegateway: Callable[[str], Gateway]) -> None: + with pytest.raises(execnet.HostNotFound): + makegateway("socket=qwepoipqwe:9000") class TestSshPopenGateway: gwtype = "ssh" - def test_sshconfig_config_parsing(self, monkeypatch, makegateway): + def test_sshconfig_config_parsing( + self, monkeypatch: pytest.MonkeyPatch, makegateway: Callable[[str], Gateway] + ) -> None: l = [] monkeypatch.setattr( gateway_io, "Popen2IOMaster", lambda *args, **kwargs: l.append(args[0]) ) - pytest.raises(AttributeError, lambda: makegateway("ssh=xyz//ssh_config=qwe")) + with pytest.raises(AttributeError): + makegateway("ssh=xyz//ssh_config=qwe") assert len(l) == 1 popen_args = l[0] i = popen_args.index("-F") assert popen_args[i + 1] == "qwe" - def test_sshaddress(self, gw, specssh): + def test_sshaddress(self, gw: Gateway, specssh: execnet.XSpec) -> None: assert gw.remoteaddress == specssh.ssh - def test_host_not_found(self, gw, makegateway): - pytest.raises( - execnet.HostNotFound, lambda: makegateway("ssh=nowhere.codespeak.net") - ) + def test_host_not_found( + self, gw: Gateway, makegateway: Callable[[str], Gateway] + ) -> None: + with pytest.raises(execnet.HostNotFound): + makegateway("ssh=nowhere.codespeak.net") class TestThreads: - def test_threads(self, makegateway): + def test_threads(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("popen") gw.remote_init_threads(3) c1 = gw.remote_exec("channel.send(channel.receive())") @@ -394,7 +417,7 @@ def test_threads(self, makegateway): res = c1.receive() assert res == 42 - def test_threads_race_sending(self, makegateway): + def test_threads_race_sending(self, makegateway: Callable[[str], Gateway]) -> None: # multiple threads sending data in parallel gw = makegateway("popen") num = 5 @@ -418,7 +441,7 @@ def test_threads_race_sending(self, makegateway): ch.waitclose(TESTTIMEOUT) @flakytest - def test_status_with_threads(self, makegateway): + def test_status_with_threads(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("popen") c1 = gw.remote_exec("channel.send(1) ; channel.receive()") c2 = gw.remote_exec("channel.send(2) ; channel.receive()") @@ -441,7 +464,12 @@ def test_status_with_threads(self, makegateway): class TestTracing: - def test_popen_filetracing(self, tmp_path, monkeypatch, makegateway): + def test_popen_filetracing( + self, + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, + makegateway: Callable[[str], Gateway], + ) -> None: monkeypatch.setenv("TMP", str(tmp_path)) monkeypatch.setenv("TEMP", str(tmp_path)) # windows monkeypatch.setenv("EXECNET_DEBUG", "1") @@ -450,6 +478,7 @@ def test_popen_filetracing(self, tmp_path, monkeypatch, makegateway): fn = gw.remote_exec( "import execnet;channel.send(execnet.gateway_base.fn)" ).receive() + assert isinstance(fn, str) workerfile = pathlib.Path(fn) assert workerfile.exists() worker_line = "creating workergateway" @@ -463,7 +492,12 @@ def test_popen_filetracing(self, tmp_path, monkeypatch, makegateway): @skip_win_pypy @flakytest - def test_popen_stderr_tracing(self, capfd, monkeypatch, makegateway): + def test_popen_stderr_tracing( + self, + capfd: pytest.CaptureFixture[str], + monkeypatch: pytest.MonkeyPatch, + makegateway: Callable[[str], Gateway], + ) -> None: monkeypatch.setenv("EXECNET_DEBUG", "2") gw = makegateway("popen") pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive() @@ -493,7 +527,7 @@ def test_no_tracing_by_default(self): ('popen//python="/u/test me/python" -e', ["/u/test me/python", "-e"]), ], ) -def test_popen_args(spec, expected_args): +def test_popen_args(spec: str, expected_args: list[str]) -> None: expected_args = [*expected_args, "-u", "-c", gateway_io.popen_bootstrapline] args = gateway_io.popen_args(execnet.XSpec(spec)) assert args == expected_args @@ -512,13 +546,15 @@ def test_popen_args(spec, expected_args): ), ], ) -def test_regression_gevent_hangs(group, interleave_getstatus): +def test_regression_gevent_hangs( + group: execnet.Group, interleave_getstatus: bool +) -> None: pytest.importorskip("gevent") gw = group.makegateway("popen//execmodel=gevent") print(gw.remote_status()) - def sendback(channel): + def sendback(channel) -> None: channel.send(1234) ch = gw.remote_exec(sendback) @@ -527,11 +563,13 @@ def sendback(channel): assert ch.receive(timeout=0.5) == 1234 -def test_assert_main_thread_only(execmodel, makegateway): +def test_assert_main_thread_only( + execmodel: gateway_base.ExecModel, makegateway: Callable[[str], Gateway] +) -> None: if execmodel.backend != "main_thread_only": pytest.skip("can only run with main_thread_only") - gw = makegateway(spec=f"execmodel={execmodel.backend}//popen") + gw = makegateway(f"execmodel={execmodel.backend}//popen") try: # Submit multiple remote_exec requests in quick succession and @@ -564,11 +602,13 @@ def test_assert_main_thread_only(execmodel, makegateway): gw.join() -def test_main_thread_only_concurrent_remote_exec_deadlock(execmodel, makegateway): +def test_main_thread_only_concurrent_remote_exec_deadlock( + execmodel: gateway_base.ExecModel, makegateway: Callable[[str], Gateway] +) -> None: if execmodel.backend != "main_thread_only": pytest.skip("can only run with main_thread_only") - gw = makegateway(spec=f"execmodel={execmodel.backend}//popen") + gw = makegateway(f"execmodel={execmodel.backend}//popen") channels = [] try: # Submit multiple remote_exec requests in quick succession and diff --git a/testing/test_multi.py b/testing/test_multi.py index 3996a964..445b3f15 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -1,19 +1,26 @@ """ tests for multi channels and gateway Groups """ +from __future__ import annotations + import gc from time import sleep +from typing import Callable import execnet import pytest from execnet import XSpec +from execnet.gateway import Gateway from execnet.gateway_base import Channel +from execnet.gateway_base import ExecModel from execnet.multi import Group from execnet.multi import safe_terminate class TestMultiChannelAndGateway: - def test_multichannel_container_basics(self, gw, execmodel): + def test_multichannel_container_basics( + self, gw: Gateway, execmodel: ExecModel + ) -> None: mch = execnet.MultiChannel([Channel(gw, i) for i in range(3)]) assert len(mch) == 3 channels = list(mch) @@ -26,21 +33,21 @@ def test_multichannel_container_basics(self, gw, execmodel): assert channels[1] in mch assert channels[2] in mch - def test_multichannel_receive_each(self): + def test_multichannel_receive_each(self) -> None: class pseudochannel: - def receive(self): + def receive(self) -> object: return 12 pc1 = pseudochannel() pc2 = pseudochannel() - multichannel = execnet.MultiChannel([pc1, pc2]) + multichannel = execnet.MultiChannel([pc1, pc2]) # type: ignore[list-item] l = multichannel.receive_each(withchannel=True) assert len(l) == 2 - assert l == [(pc1, 12), (pc2, 12)] - l = multichannel.receive_each(withchannel=False) - assert l == [12, 12] + assert l == [(pc1, 12), (pc2, 12)] # type: ignore[comparison-overlap] + l2 = multichannel.receive_each(withchannel=False) + assert l2 == [12, 12] - def test_multichannel_send_each(self): + def test_multichannel_send_each(self) -> None: gm = execnet.Group(["popen"] * 2) mc = gm.remote_exec( """ @@ -52,12 +59,12 @@ def test_multichannel_send_each(self): l = mc.receive_each() assert l == [42, 42] - def test_Group_execmodel_setting(self): + def test_Group_execmodel_setting(self) -> None: gm = execnet.Group() gm.set_execmodel("thread") assert gm.execmodel.backend == "thread" assert gm.remote_execmodel.backend == "thread" - gm._gateways.append(1) + gm._gateways.append(1) # type: ignore[arg-type] try: with pytest.raises(ValueError): gm.set_execmodel("eventlet") @@ -65,7 +72,7 @@ def test_Group_execmodel_setting(self): finally: gm._gateways.pop() - def test_multichannel_receive_queue_for_two_subprocesses(self): + def test_multichannel_receive_queue_for_two_subprocesses(self) -> None: gm = execnet.Group(["popen"] * 2) mc = gm.remote_exec( """ @@ -81,23 +88,23 @@ def test_multichannel_receive_queue_for_two_subprocesses(self): assert item != item2 mc.waitclose() - def test_multichannel_waitclose(self): + def test_multichannel_waitclose(self) -> None: l = [] class pseudochannel: - def waitclose(self): + def waitclose(self) -> None: l.append(0) - multichannel = execnet.MultiChannel([pseudochannel(), pseudochannel()]) + multichannel = execnet.MultiChannel([pseudochannel(), pseudochannel()]) # type: ignore[list-item] multichannel.waitclose() assert len(l) == 2 class TestGroup: - def test_basic_group(self, monkeypatch): + def test_basic_group(self, monkeypatch: pytest.MonkeyPatch) -> None: import atexit - atexitlist = [] + atexitlist: list[Callable[[], object]] = [] monkeypatch.setattr(atexit, "register", atexitlist.append) group = Group() assert atexitlist == [group._cleanup_atexit] @@ -105,7 +112,7 @@ def test_basic_group(self, monkeypatch): joinlist = [] class PseudoIO: - def wait(self): + def wait(self) -> None: pass class PseudoSpec: @@ -116,15 +123,15 @@ class PseudoGW: _io = PseudoIO() spec = PseudoSpec() - def exit(self): + def exit(self) -> None: exitlist.append(self) - group._unregister(self) + group._unregister(self) # type: ignore[arg-type] - def join(self): + def join(self) -> None: joinlist.append(self) gw = PseudoGW() - group._register(gw) + group._register(gw) # type: ignore[arg-type] assert len(exitlist) == 0 assert len(joinlist) == 0 group._cleanup_atexit() @@ -136,12 +143,12 @@ def join(self): assert len(exitlist) == 1 assert len(joinlist) == 1 - def test_group_default_spec(self): + def test_group_default_spec(self) -> None: group = Group() group.defaultspec = "not-existing-type" pytest.raises(ValueError, group.makegateway) - def test_group_PopenGateway(self): + def test_group_PopenGateway(self) -> None: group = Group() gw = group.makegateway("popen") assert list(group) == [gw] @@ -150,7 +157,7 @@ def test_group_PopenGateway(self): group._cleanup_atexit() assert not group._gateways - def test_group_ordering_and_termination(self): + def test_group_ordering_and_termination(self) -> None: group = Group() group.makegateway("popen//id=3") group.makegateway("popen//id=2") @@ -165,7 +172,7 @@ def test_group_ordering_and_termination(self): assert not group assert repr(group) == "" - def test_group_id_allocation(self): + def test_group_id_allocation(self) -> None: group = Group() specs = [XSpec("popen"), XSpec("popen//id=hello")] group.allocate_id(specs[0]) @@ -178,14 +185,14 @@ def test_group_id_allocation(self): # group.allocate_id, XSpec("popen//id=hello")) group.terminate() - def test_gateway_and_id(self): + def test_gateway_and_id(self) -> None: group = Group() gw = group.makegateway("popen//id=hello") assert group["hello"] == gw with pytest.raises((TypeError, AttributeError)): - del group["hello"] + del group["hello"] # type: ignore[attr-defined] with pytest.raises((TypeError, AttributeError)): - group["hello"] = 5 + group["hello"] = 5 # type: ignore[index] assert "hello" in group assert gw in group assert len(group) == 1 @@ -194,7 +201,7 @@ def test_gateway_and_id(self): with pytest.raises(KeyError): _ = group["hello"] - def test_default_group(self): + def test_default_group(self) -> None: oldlist = list(execnet.default_group) gw = execnet.makegateway("popen") try: @@ -205,18 +212,18 @@ def test_default_group(self): finally: gw.exit() - def test_remote_exec_args(self): + def test_remote_exec_args(self) -> None: group = Group() group.makegateway("popen") - def fun(channel, arg): + def fun(channel, arg) -> None: channel.send(arg) mch = group.remote_exec(fun, arg=1) result = mch.receive_each() assert result == [1] - def test_terminate_with_proxying(self): + def test_terminate_with_proxying(self) -> None: group = Group() group.makegateway("popen//id=master") group.makegateway("popen//via=master//id=worker") @@ -224,7 +231,7 @@ def test_terminate_with_proxying(self): @pytest.mark.xfail(reason="active_count() has been broken for some time") -def test_safe_terminate(execmodel): +def test_safe_terminate(execmodel: ExecModel) -> None: if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail( "execution model %r does not support task count" % execmodel.backend @@ -234,21 +241,21 @@ def test_safe_terminate(execmodel): active = threading.active_count() l = [] - def term(): + def term() -> None: sleep(3) - def kill(): + def kill() -> None: l.append(1) safe_terminate(execmodel, 1, [(term, kill)] * 10) assert len(l) == 10 sleep(0.1) gc.collect() - assert execmodel.active_count() == active + assert execmodel.active_count() == active # type: ignore[attr-defined] @pytest.mark.xfail(reason="active_count() has been broken for some time") -def test_safe_terminate2(execmodel): +def test_safe_terminate2(execmodel: ExecModel) -> None: if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail( "execution model %r does not support task count" % execmodel.backend @@ -258,10 +265,10 @@ def test_safe_terminate2(execmodel): active = threading.active_count() l = [] - def term(): + def term() -> None: return - def kill(): + def kill() -> None: l.append(1) safe_terminate(execmodel, 3, [(term, kill)] * 10) diff --git a/testing/test_rsync.py b/testing/test_rsync.py index fefd9a06..6f40bc44 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -7,24 +7,25 @@ import execnet import pytest from execnet import RSync +from execnet.gateway import Gateway @pytest.fixture(scope="module") -def group(request): +def group(request: pytest.FixtureRequest) -> execnet.Group: group = execnet.Group() request.addfinalizer(group.terminate) return group @pytest.fixture(scope="module") -def gw1(request, group): +def gw1(request: pytest.FixtureRequest, group: execnet.Group) -> Gateway: gw = group.makegateway("popen//id=gw1") request.addfinalizer(gw.exit) return gw @pytest.fixture(scope="module") -def gw2(request, group): +def gw2(request: pytest.FixtureRequest, group: execnet.Group) -> Gateway: gw = group.makegateway("popen//id=gw2") request.addfinalizer(gw.exit) return gw @@ -44,7 +45,7 @@ class _dirs(types.SimpleNamespace): @pytest.fixture -def dirs(request, tmp_path) -> _dirs: +def dirs(tmp_path: pathlib.Path) -> _dirs: dirs = _dirs( source=tmp_path / "source", dest1=tmp_path / "dest1", @@ -56,7 +57,7 @@ def dirs(request, tmp_path) -> _dirs: return dirs -def are_paths_equal(path1, path2): +def are_paths_equal(path1: pathlib.Path, path2: pathlib.Path) -> bool: if os.path.__name__ == "ntpath": # On Windows, os.readlink returns an extended path (\\?\) # for absolute symlinks. However, extended does not compare @@ -72,13 +73,13 @@ def are_paths_equal(path1, path2): class TestRSync: - def test_notargets(self, dirs): + def test_notargets(self, dirs: _dirs) -> None: rsync = RSync(dirs.source) with pytest.raises(IOError): rsync.send() - assert rsync.send(raises=False) is None + assert rsync.send(raises=False) is None # type: ignore[func-returns-value] - def test_dirsync(self, dirs, gw1, gw2): + def test_dirsync(self, dirs: _dirs, gw1: Gateway, gw2: Gateway) -> None: dest = dirs.dest1 dest2 = dirs.dest2 source = dirs.source @@ -115,7 +116,7 @@ def test_dirsync(self, dirs, gw1, gw2): assert not dest.joinpath("subdir", "file1").exists() assert dest2.joinpath("subdir", "file1").exists() - def test_dirsync_twice(self, dirs, gw1, gw2): + def test_dirsync_twice(self, dirs: _dirs, gw1: Gateway, gw2: Gateway) -> None: source = dirs.source source.joinpath("hello").touch() rsync = RSync(source) @@ -124,15 +125,17 @@ def test_dirsync_twice(self, dirs, gw1, gw2): assert dirs.dest1.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() - assert rsync.send(raises=False) is None + assert rsync.send(raises=False) is None # type: ignore[func-returns-value] rsync.add_target(gw1, dirs.dest2) rsync.send() assert dirs.dest2.joinpath("hello").exists() with pytest.raises(IOError): rsync.send() - assert rsync.send(raises=False) is None + assert rsync.send(raises=False) is None # type: ignore[func-returns-value] - def test_rsync_default_reporting(self, capsys, dirs, gw1): + def test_rsync_default_reporting( + self, capsys: pytest.CaptureFixture[str], dirs: _dirs, gw1: Gateway + ) -> None: source = dirs.source source.joinpath("hello").touch() rsync = RSync(source) @@ -141,7 +144,9 @@ def test_rsync_default_reporting(self, capsys, dirs, gw1): out, err = capsys.readouterr() assert out.find("hello") != -1 - def test_rsync_non_verbose(self, capsys, dirs, gw1): + def test_rsync_non_verbose( + self, capsys: pytest.CaptureFixture[str], dirs: _dirs, gw1: Gateway + ) -> None: source = dirs.source source.joinpath("hello").touch() rsync = RSync(source, verbose=False) @@ -155,7 +160,7 @@ def test_rsync_non_verbose(self, capsys, dirs, gw1): sys.platform == "win32" or getattr(os, "_name", "") == "nt", reason="irrelevant on windows", ) - def test_permissions(self, dirs, gw1, gw2): + def test_permissions(self, dirs: _dirs, gw1: Gateway, gw2: Gateway) -> None: source = dirs.source dest = dirs.dest1 onedir = dirs.source / "one" @@ -194,7 +199,7 @@ def test_permissions(self, dirs, gw1, gw2): sys.platform == "win32" or getattr(os, "_name", "") == "nt", reason="irrelevant on windows", ) - def test_read_only_directories(self, dirs, gw1): + def test_read_only_directories(self, dirs: _dirs, gw1: Gateway) -> None: source = dirs.source dest = dirs.dest1 sub = source / "sub" @@ -214,7 +219,7 @@ def test_read_only_directories(self, dirs, gw1): assert dest.joinpath("sub", "subsub").stat().st_mode & 0o700 @needssymlink - def test_symlink_rsync(self, dirs, gw1): + def test_symlink_rsync(self, dirs: _dirs, gw1: Gateway) -> None: source = dirs.source dest = dirs.dest1 subdir = dirs.source / "subdir" @@ -236,7 +241,7 @@ def test_symlink_rsync(self, dirs, gw1): assert are_paths_equal(abslink, expected) @needssymlink - def test_symlink2_rsync(self, dirs, gw1): + def test_symlink2_rsync(self, dirs: _dirs, gw1: Gateway) -> None: source = dirs.source dest = dirs.dest1 subdir = dirs.source / "subdir" @@ -261,7 +266,7 @@ def test_symlink2_rsync(self, dirs, gw1): link3 = pathlib.Path(os.readlink(str(destsub / "link3"))) assert are_paths_equal(link3, source.parent) - def test_callback(self, dirs, gw1): + def test_callback(self, dirs: _dirs, gw1: Gateway) -> None: dest = dirs.dest1 source = dirs.source source.joinpath("existent").write_text("a" * 100) @@ -278,15 +283,15 @@ def callback(cmd, lgt, channel): assert total == {("list", 110): True, ("ack", 100): True, ("ack", 10): True} - def test_file_disappearing(self, dirs, gw1): + def test_file_disappearing(self, dirs: _dirs, gw1: Gateway) -> None: dest = dirs.dest1 source = dirs.source source.joinpath("ex").write_text("a" * 100) source.joinpath("ex2").write_text("a" * 100) class DRsync(RSync): - def filter(self, x): - assert x != source + def filter(self, x: str) -> bool: + assert x != str(source) if x.endswith("ex2"): self.x = 1 source.joinpath("ex2").unlink() diff --git a/testing/test_serializer.py b/testing/test_serializer.py index ca2fbf9d..64539399 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import subprocess import sys @@ -11,7 +13,7 @@ class PythonWrapper: - def __init__(self, executable, tmp_path): + def __init__(self, executable: str, tmp_path: Path) -> None: self.executable = executable self.tmp_path = tmp_path @@ -31,7 +33,7 @@ def dump(self, obj_rep: str) -> bytes: ) return res.stdout - def load(self, data: bytes): + def load(self, data: bytes) -> list[str]: script_file = self.tmp_path.joinpath("load.py") script_file.write_text( rf""" @@ -55,22 +57,22 @@ def load(self, data: bytes): return res.stdout.decode("ascii").splitlines() - def __repr__(self): + def __repr__(self) -> str: return f"" @pytest.fixture -def py3(request, tmp_path): +def py3(tmp_path: Path) -> PythonWrapper: return PythonWrapper(sys.executable, tmp_path) @pytest.fixture -def dump(py3): +def dump(py3: PythonWrapper): return py3.dump @pytest.fixture -def load(py3): +def load(py3: PythonWrapper): return py3.load @@ -86,14 +88,14 @@ def load(py3): @pytest.mark.parametrize(["tp_name", "repr"], simple_tests) -def test_simple(tp_name, repr, dump, load): +def test_simple(tp_name, repr, dump, load) -> None: p = dump(repr) tp, v = load(p) assert tp == tp_name assert v == repr -def test_set(load, dump): +def test_set(load, dump) -> None: p = dump("set((1, 2, 3))") tp, v = load(p) @@ -114,7 +116,7 @@ def test_frozenset(load, dump): assert v == "frozenset({1, 2, 3})" -def test_long(load, dump): +def test_long(load, dump) -> None: really_big = "9223372036854775807324234" p = dump(really_big) tp, v = load(p) @@ -122,41 +124,41 @@ def test_long(load, dump): assert v == really_big -def test_bytes(dump, load): +def test_bytes(dump, load) -> None: p = dump("b'hi'") tp, v = load(p) assert tp == "bytes" assert v == "b'hi'" -def test_str(dump, load): +def test_str(dump, load) -> None: p = dump("'xyz'") tp, s = load(p) assert tp == "str" assert s == "'xyz'" -def test_unicode(load, dump): +def test_unicode(load, dump) -> None: p = dump("u'hi'") tp, s = load(p) assert tp == "str" assert s == "'hi'" -def test_bool(dump, load): +def test_bool(dump, load) -> None: p = dump("True") tp, s = load(p) assert s == "True" assert tp == "bool" -def test_none(dump, load): +def test_none(dump, load) -> None: p = dump("None") tp, s = load(p) assert s == "None" -def test_tuple_nested_with_empty_in_between(dump, load): +def test_tuple_nested_with_empty_in_between(dump, load) -> None: p = dump("(1, (), 3)") tp, s = load(p) assert tp == "tuple" diff --git a/testing/test_termination.py b/testing/test_termination.py index 39108717..44bd1a52 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -4,9 +4,13 @@ import signal import subprocess import sys +from typing import Callable import execnet import pytest +from execnet.gateway import Gateway +from execnet.gateway_base import ExecModel +from execnet.gateway_base import WorkerPool from test_gateway import TESTTIMEOUT execnetdir = pathlib.Path(execnet.__file__).parent.parent @@ -17,7 +21,9 @@ ) -def test_exit_blocked_worker_execution_gateway(anypython, makegateway, pool): +def test_exit_blocked_worker_execution_gateway( + anypython: str, makegateway: Callable[[str], Gateway], pool: WorkerPool +) -> None: gateway = makegateway("popen//python=%s" % anypython) gateway.remote_exec( """ @@ -26,7 +32,7 @@ def test_exit_blocked_worker_execution_gateway(anypython, makegateway, pool): """ ) - def doit(): + def doit() -> int: gateway.exit() return 17 @@ -35,7 +41,9 @@ def doit(): assert x == 17 -def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): +def test_endmarker_delivery_on_remote_killterm( + makegateway: Callable[[str], Gateway], execmodel: ExecModel +) -> None: if execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail("test and execnet not compatible to greenlets yet") gw = makegateway("popen") @@ -48,6 +56,7 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): """ ) pid = channel.receive() + assert isinstance(pid, int) os.kill(pid, signal.SIGTERM) channel.setcallback(q.put, endmarker=999) val = q.get(TESTTIMEOUT) @@ -57,7 +66,9 @@ def test_endmarker_delivery_on_remote_killterm(makegateway, execmodel): @skip_win_pypy -def test_termination_on_remote_channel_receive(monkeypatch, makegateway): +def test_termination_on_remote_channel_receive( + monkeypatch: pytest.MonkeyPatch, makegateway: Callable[[str], Gateway] +) -> None: if not shutil.which("ps"): pytest.skip("need 'ps' command to externally check process status") monkeypatch.setenv("EXECNET_DEBUG", "2") @@ -70,7 +81,9 @@ def test_termination_on_remote_channel_receive(monkeypatch, makegateway): assert str(pid) not in output.stdout, output -def test_close_initiating_remote_no_error(pytester, anypython): +def test_close_initiating_remote_no_error( + pytester: pytest.Pytester, anypython: str +) -> None: p = pytester.makepyfile( """ import sys @@ -86,17 +99,21 @@ def test_close_initiating_remote_no_error(pytester, anypython): """ ) popen = subprocess.Popen( - [str(anypython), str(p), str(execnetdir)], stdout=None, stderr=subprocess.PIPE + [anypython, str(p), str(execnetdir)], stdout=None, stderr=subprocess.PIPE ) out, err = popen.communicate() print(err) - err = err.decode("utf8") - lines = [x for x in err.splitlines() if "*sys-package" not in x] - # print (lines) + errstr = err.decode("utf8") + lines = [x for x in errstr.splitlines() if "*sys-package" not in x] assert not lines -def test_terminate_implicit_does_trykill(pytester, anypython, capfd, pool): +def test_terminate_implicit_does_trykill( + pytester: pytest.Pytester, + anypython: str, + capfd: pytest.CaptureFixture[str], + pool: WorkerPool, +) -> None: if pool.execmodel.backend not in ("thread", "main_thread_only"): pytest.xfail("only os threading model supported") if sys.version_info >= (3, 12): @@ -129,6 +146,7 @@ def flush(self): ) popen = subprocess.Popen([str(anypython), str(p)], stdout=subprocess.PIPE) # sync with start-up + assert popen.stdout is not None popen.stdout.readline() reply = pool.spawn(popen.communicate) reply.get(timeout=50) diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 98187349..480282d9 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -1,10 +1,12 @@ import os +from pathlib import Path import pytest +from execnet.gateway_base import ExecModel from execnet.gateway_base import WorkerPool -def test_execmodel(execmodel, tmp_path): +def test_execmodel(execmodel: ExecModel, tmp_path: Path) -> None: assert execmodel.backend p = tmp_path / "somefile" p.write_text("content") @@ -14,22 +16,22 @@ def test_execmodel(execmodel, tmp_path): f.close() -def test_execmodel_basic_attrs(execmodel): +def test_execmodel_basic_attrs(execmodel: ExecModel) -> None: m = execmodel assert callable(m.start) assert m.get_ident() -def test_simple(pool): +def test_simple(pool: WorkerPool) -> None: reply = pool.spawn(lambda: 42) assert reply.get() == 42 -def test_some(pool, execmodel): +def test_some(pool: WorkerPool, execmodel: ExecModel) -> None: q = execmodel.queue.Queue() num = 4 - def f(i): + def f(i: int) -> None: q.put(i) while q.qsize(): execmodel.sleep(0.01) @@ -44,10 +46,10 @@ def f(i): assert len(pool._running) == 0 -def test_running_semnatics(pool, execmodel): +def test_running_semnatics(pool: WorkerPool, execmodel: ExecModel) -> None: q = execmodel.queue.Queue() - def first(): + def first() -> None: q.get() reply = pool.spawn(first) @@ -59,7 +61,7 @@ def first(): assert not reply.running -def test_waitfinish_on_reply(pool): +def test_waitfinish_on_reply(pool: WorkerPool) -> None: l = [] reply = pool.spawn(lambda: l.append(1)) reply.waitfinish() @@ -70,20 +72,20 @@ def test_waitfinish_on_reply(pool): @pytest.mark.xfail(reason="WorkerPool does not implement limited size") -def test_limited_size(execmodel): - pool = WorkerPool(execmodel, size=1) +def test_limited_size(execmodel: ExecModel) -> None: + pool = WorkerPool(execmodel, size=1) # type: ignore[call-arg] q = execmodel.queue.Queue() q2 = execmodel.queue.Queue() q3 = execmodel.queue.Queue() - def first(): + def first() -> None: q.put(1) q2.get() pool.spawn(first) assert q.get() == 1 - def second(): + def second() -> None: q3.put(3) # we spawn a second pool to spawn the second function @@ -97,8 +99,8 @@ def second(): assert pool.waitall() -def test_get(pool): - def f(): +def test_get(pool: WorkerPool) -> None: + def f() -> int: return 42 reply = pool.spawn(f) @@ -106,8 +108,8 @@ def f(): assert result == 42 -def test_get_timeout(execmodel, pool): - def f(): +def test_get_timeout(execmodel: ExecModel, pool: WorkerPool) -> None: + def f() -> int: execmodel.sleep(0.2) return 42 @@ -116,8 +118,8 @@ def f(): reply.get(timeout=0.01) -def test_get_excinfo(pool): - def f(): +def test_get_excinfo(pool: WorkerPool) -> None: + def f() -> None: raise ValueError("42") reply = pool.spawn(f) @@ -127,10 +129,10 @@ def f(): reply.get(1.0) -def test_waitall_timeout(pool, execmodel): +def test_waitall_timeout(pool: WorkerPool, execmodel: ExecModel) -> None: q = execmodel.queue.Queue() - def f(): + def f() -> None: q.get() reply = pool.spawn(f) @@ -141,10 +143,12 @@ def f(): @pytest.mark.skipif(not hasattr(os, "dup"), reason="no os.dup") -def test_pool_clean_shutdown(pool, capfd): +def test_pool_clean_shutdown( + pool: WorkerPool, capfd: pytest.CaptureFixture[str] +) -> None: q = pool.execmodel.queue.Queue() - def f(): + def f() -> None: q.get() pool.spawn(f) @@ -153,7 +157,7 @@ def f(): with pytest.raises(ValueError): pool.spawn(f) - def wait_then_put(): + def wait_then_put() -> None: pool.execmodel.sleep(0.1) q.put(1) @@ -163,7 +167,7 @@ def wait_then_put(): assert err == "" -def test_primary_thread_integration(execmodel): +def test_primary_thread_integration(execmodel: ExecModel) -> None: if execmodel.backend not in ("thread", "main_thread_only"): with pytest.raises(ValueError): WorkerPool(execmodel=execmodel, hasprimary=True) @@ -171,13 +175,13 @@ def test_primary_thread_integration(execmodel): pool = WorkerPool(execmodel=execmodel, hasprimary=True) queue = execmodel.queue.Queue() - def do_integrate(): + def do_integrate() -> None: queue.put(execmodel.get_ident()) pool.integrate_as_primary_thread() execmodel.start(do_integrate) - def func(): + def func() -> None: queue.put(execmodel.get_ident()) pool.spawn(func) @@ -187,13 +191,13 @@ def func(): pool.terminate() -def test_primary_thread_integration_shutdown(execmodel): +def test_primary_thread_integration_shutdown(execmodel: ExecModel) -> None: if execmodel.backend not in ("thread", "main_thread_only"): pytest.skip("can only run with threading") pool = WorkerPool(execmodel=execmodel, hasprimary=True) queue = execmodel.queue.Queue() - def do_integrate(): + def do_integrate() -> None: queue.put(execmodel.get_ident()) pool.integrate_as_primary_thread() @@ -202,7 +206,7 @@ def do_integrate(): queue2 = execmodel.queue.Queue() - def get_two(): + def get_two() -> None: queue.put(execmodel.get_ident()) queue2.get() diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 30552dca..4c9ff8d6 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -4,16 +4,17 @@ import shutil import subprocess import sys +from pathlib import Path +from typing import Callable import execnet import pytest +from execnet import XSpec +from execnet.gateway import Gateway from execnet.gateway_io import popen_args from execnet.gateway_io import ssh_args from execnet.gateway_io import vagrant_ssh_args -XSpec = execnet.XSpec - - skip_win_pypy = pytest.mark.xfail( condition=hasattr(sys, "pypy_version_info") and sys.platform.startswith("win"), reason="failing on Windows on PyPy (#63)", @@ -21,7 +22,7 @@ class TestXSpec: - def test_norm_attributes(self): + def test_norm_attributes(self) -> None: spec = XSpec( r"socket=192.168.102.2:8888//python=c:/this/python3.8//chdir=d:\hello" ) @@ -32,7 +33,7 @@ def test_norm_attributes(self): assert not hasattr(spec, "_xyz") with pytest.raises(AttributeError): - spec._hello() + spec._hello() # type: ignore[misc,operator] spec = XSpec("socket=192.168.102.2:8888//python=python2.5//nice=3") assert spec.socket == "192.168.102.2:8888" @@ -48,7 +49,7 @@ def test_norm_attributes(self): spec = XSpec("popen") assert spec.popen is True - def test_ssh_options(self): + def test_ssh_options(self) -> None: spec = XSpec("ssh=-p 22100 user@host//python=python3") assert spec.ssh == "-p 22100 user@host" assert spec.python == "python3" @@ -60,22 +61,22 @@ def test_ssh_options(self): assert spec.ssh == "-i ~/.ssh/id_rsa-passwordless_login -p 22100 user@host" assert spec.python == "python3" - def test_execmodel(self): + def test_execmodel(self) -> None: spec = XSpec("execmodel=thread") assert spec.execmodel == "thread" spec = XSpec("execmodel=eventlet") assert spec.execmodel == "eventlet" - def test_ssh_options_and_config(self): + def test_ssh_options_and_config(self) -> None: spec = XSpec("ssh=-p 22100 user@host//python=python3") spec.ssh_config = "/home/user/ssh_config" assert ssh_args(spec)[:6] == ["ssh", "-C", "-F", spec.ssh_config, "-p", "22100"] - def test_vagrant_options(self): + def test_vagrant_options(self) -> None: spec = XSpec("vagrant_ssh=default//python=python3") assert vagrant_ssh_args(spec)[:-1] == ["vagrant", "ssh", "default", "--", "-C"] - def test_popen_with_sudo_python(self): + def test_popen_with_sudo_python(self) -> None: spec = XSpec("popen//python=sudo python3") assert popen_args(spec) == [ "sudo", @@ -85,33 +86,33 @@ def test_popen_with_sudo_python(self): "import sys;exec(eval(sys.stdin.readline()))", ] - def test_env(self): + def test_env(self) -> None: xspec = XSpec("popen//env:NAME=value1") assert xspec.env["NAME"] == "value1" - def test__samefilesystem(self): + def test__samefilesystem(self) -> None: assert XSpec("popen")._samefilesystem() assert XSpec("popen//python=123")._samefilesystem() assert not XSpec("popen//chdir=hello")._samefilesystem() - def test__spec_spec(self): + def test__spec_spec(self) -> None: for x in ("popen", "popen//python=this"): assert XSpec(x)._spec == x - def test_samekeyword_twice_raises(self): + def test_samekeyword_twice_raises(self) -> None: pytest.raises(ValueError, XSpec, "popen//popen") pytest.raises(ValueError, XSpec, "popen//popen=123") - def test_unknown_keys_allowed(self): + def test_unknown_keys_allowed(self) -> None: xspec = XSpec("hello=3") assert xspec.hello == "3" - def test_repr_and_string(self): + def test_repr_and_string(self) -> None: for x in ("popen", "popen//python=this"): assert repr(XSpec(x)).find("popen") != -1 assert str(XSpec(x)) == x - def test_hash_equality(self): + def test_hash_equality(self) -> None: assert XSpec("popen") == XSpec("popen") assert hash(XSpec("popen")) == hash(XSpec("popen")) assert XSpec("popen//python=123") != XSpec("popen") @@ -119,11 +120,11 @@ def test_hash_equality(self): class TestMakegateway: - def test_no_type(self, makegateway): + def test_no_type(self, makegateway: Callable[[str], Gateway]) -> None: pytest.raises(ValueError, lambda: makegateway("hello")) @skip_win_pypy - def test_popen_default(self, makegateway): + def test_popen_default(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("") assert gw.spec.popen assert gw.spec.python is None @@ -134,10 +135,10 @@ def test_popen_default(self, makegateway): @pytest.mark.skipif("not hasattr(os, 'nice')") @pytest.mark.xfail(reason="fails due to timing problems on busy single-core VMs") - def test_popen_nice(self, makegateway): + def test_popen_nice(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("popen") - def getnice(channel): + def getnice(channel) -> None: import os if hasattr(os, "nice"): @@ -146,13 +147,14 @@ def getnice(channel): channel.send(None) remotenice = gw.remote_exec(getnice).receive() + assert isinstance(remotenice, int) gw.exit() if remotenice is not None: gw = makegateway("popen//nice=5") remotenice2 = gw.remote_exec(getnice).receive() assert remotenice2 == remotenice + 5 - def test_popen_env(self, makegateway): + def test_popen_env(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("popen//env:NAME123=123") ch = gw.remote_exec( """ @@ -164,7 +166,7 @@ def test_popen_env(self, makegateway): assert value == "123" @skip_win_pypy - def test_popen_explicit(self, makegateway): + def test_popen_explicit(self, makegateway: Callable[[str], Gateway]) -> None: gw = makegateway("popen//python=%s" % sys.executable) assert gw.spec.python == sys.executable rinfo = gw._rinfo() @@ -173,25 +175,32 @@ def test_popen_explicit(self, makegateway): assert rinfo.version_info == sys.version_info @skip_win_pypy - def test_popen_chdir_absolute(self, tmp_path, makegateway): + def test_popen_chdir_absolute( + self, tmp_path: Path, makegateway: Callable[[str], Gateway] + ) -> None: gw = makegateway("popen//chdir=%s" % tmp_path) rinfo = gw._rinfo() assert rinfo.cwd == str(tmp_path.resolve()) @skip_win_pypy - def test_popen_chdir_newsub(self, monkeypatch, tmp_path, makegateway): + def test_popen_chdir_newsub( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + makegateway: Callable[[str], Gateway], + ) -> None: monkeypatch.chdir(tmp_path) gw = makegateway("popen//chdir=hello") rinfo = gw._rinfo() expected = str(tmp_path.joinpath("hello").resolve()).lower() assert rinfo.cwd.lower() == expected - def test_ssh(self, specssh, makegateway): + def test_ssh(self, specssh: XSpec, makegateway: Callable[[str], Gateway]) -> None: sshhost = specssh.ssh gw = makegateway("ssh=%s//id=ssh1" % sshhost) assert gw.id == "ssh1" - def test_vagrant(self, makegateway): + def test_vagrant(self, makegateway: Callable[[str], Gateway]) -> None: vagrant_bin = shutil.which("vagrant") if vagrant_bin is None: pytest.skip("Vagrant binary not in PATH") @@ -215,7 +224,9 @@ def test_vagrant(self, makegateway): assert rinfo.cwd == "/home/vagrant" assert rinfo.executable == "/usr/bin/python" - def test_socket(self, specsocket, makegateway): + def test_socket( + self, specsocket: XSpec, makegateway: Callable[[str], Gateway] + ) -> None: gw = makegateway("socket=%s//id=sock1" % specsocket.socket) rinfo = gw._rinfo() assert rinfo.executable @@ -225,7 +236,9 @@ def test_socket(self, specsocket, makegateway): # we cannot instantiate a second gateway @pytest.mark.xfail(reason="we can't instantiate a second gateway") - def test_socket_second(self, specsocket, makegateway): + def test_socket_second( + self, specsocket: XSpec, makegateway: Callable[[str], Gateway] + ) -> None: gw = makegateway("socket=%s//id=sock1" % specsocket.socket) gw2 = makegateway("socket=%s//id=sock1" % specsocket.socket) rinfo = gw._rinfo() @@ -234,7 +247,7 @@ def test_socket_second(self, specsocket, makegateway): assert rinfo.cwd == rinfo2.cwd assert rinfo.version_info == rinfo2.version_info - def test_socket_installvia(self): + def test_socket_installvia(self) -> None: group = execnet.Group() group.makegateway("popen//id=p1") gw = group.makegateway("socket//installvia=p1//id=s1") From f062fb8cbea5d6223aea9dc2372d475e4cae2d57 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:01:47 -0300 Subject: [PATCH 115/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.12 (#254) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.11 to 1.8.12. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.11...v1.8.12) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 705a32e1..6e6682bb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.11 + uses: pypa/gh-action-pypi-publish@v1.8.12 - name: Push tag run: | From b9ed3cd2820c2df4d6bbafae157d1298082da76f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 06:57:29 -0300 Subject: [PATCH 116/275] [pre-commit.ci] pre-commit autoupdate (#256) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.2.2 → v0.3.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.2.2...v0.3.2) - [github.com/pre-commit/mirrors-mypy: v1.8.0 → v1.9.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.8.0...v1.9.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 +-- doc/example/popen_read_multiple.py | 1 + doc/example/redirect_remote_output.py | 1 + doc/example/svn-sync-repo.py | 1 + doc/example/sysinfo.py | 1 + src/execnet/__init__.py | 1 + src/execnet/gateway.py | 8 ++--- src/execnet/gateway_base.py | 43 +++++++++-------------- src/execnet/gateway_bootstrap.py | 1 + src/execnet/gateway_io.py | 1 + src/execnet/multi.py | 1 + src/execnet/rsync.py | 1 + src/execnet/rsync_remote.py | 1 + src/execnet/script/quitserver.py | 3 +- src/execnet/script/shell.py | 1 + src/execnet/script/socketserver.py | 9 ++--- src/execnet/script/socketserverservice.py | 1 + src/execnet/xspec.py | 1 + testing/test_channel.py | 1 + testing/test_gateway.py | 1 + testing/test_multi.py | 3 +- 21 files changed, 45 insertions(+), 40 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7c6175e0..99bb98bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.3.2 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.8.0' + rev: 'v1.9.0' hooks: - id: mypy additional_dependencies: diff --git a/doc/example/popen_read_multiple.py b/doc/example/popen_read_multiple.py index 571faf34..43d0ed04 100644 --- a/doc/example/popen_read_multiple.py +++ b/doc/example/popen_read_multiple.py @@ -3,6 +3,7 @@ reading results from possibly blocking code running in sub processes. """ + import execnet NUM_PROCESSES = 5 diff --git a/doc/example/redirect_remote_output.py b/doc/example/redirect_remote_output.py index d5fb43e0..0fea4ebb 100644 --- a/doc/example/redirect_remote_output.py +++ b/doc/example/redirect_remote_output.py @@ -7,6 +7,7 @@ - setting a callback for receiving channel data """ + import execnet gw = execnet.makegateway() diff --git a/doc/example/svn-sync-repo.py b/doc/example/svn-sync-repo.py index 24e7bd58..4377dc3f 100644 --- a/doc/example/svn-sync-repo.py +++ b/doc/example/svn-sync-repo.py @@ -5,6 +5,7 @@ uses execnet. """ + import os import pathlib import subprocess diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index f13d1199..b3fa2d5e 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -5,6 +5,7 @@ (c) Holger Krekel, MIT license """ + import optparse import re import sys diff --git a/src/execnet/__init__.py b/src/execnet/__init__.py index d22eb1af..a266bded 100644 --- a/src/execnet/__init__.py +++ b/src/execnet/__init__.py @@ -6,6 +6,7 @@ (c) 2012, Holger Krekel and others """ + from ._version import version as __version__ from .gateway_base import DataFormatError from .gateway_base import RemoteError diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index c7a64153..ec197398 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -2,6 +2,7 @@ gateway code for initiating popen, socket and ssh connections. (c) 2004-2013, Holger Krekel and others """ + from __future__ import annotations import inspect @@ -48,9 +49,7 @@ def __repr__(self) -> str: except AttributeError: r = "uninitialized" i = "no" - return "<{} id={!r} {}, {} model, {} active channels>".format( - self.__class__.__name__, self.id, r, self.execmodel.backend, i - ) + return f"<{self.__class__.__name__} id={self.id!r} {r}, {self.execmodel.backend} model, {i} active channels>" def exit(self) -> None: """trigger gateway exit. Defer waiting for finishing @@ -166,8 +165,7 @@ def __repr__(self) -> str: if TYPE_CHECKING: - def __getattr__(self, name: str) -> Any: - ... + def __getattr__(self, name: str) -> Any: ... RemoteStatus = RInfo diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 494a6839..d88e6585 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -11,6 +11,7 @@ - Ronny Pfannschmidt - many others """ + from __future__ import annotations import abc @@ -32,51 +33,39 @@ class WriteIO(Protocol): - def write(self, data: bytes, /) -> None: - ... + def write(self, data: bytes, /) -> None: ... class ReadIO(Protocol): - def read(self, numbytes: int, /) -> bytes: - ... + def read(self, numbytes: int, /) -> bytes: ... class IO(Protocol): execmodel: ExecModel - def read(self, numbytes: int, /) -> bytes: - ... + def read(self, numbytes: int, /) -> bytes: ... - def write(self, data: bytes, /) -> None: - ... + def write(self, data: bytes, /) -> None: ... - def close_read(self) -> None: - ... + def close_read(self) -> None: ... - def close_write(self) -> None: - ... + def close_write(self) -> None: ... - def wait(self) -> int | None: - ... + def wait(self) -> int | None: ... - def kill(self) -> None: - ... + def kill(self) -> None: ... class Event(Protocol): """Protocol for types which look like threading.Event.""" - def is_set(self) -> bool: - ... + def is_set(self) -> bool: ... - def set(self) -> None: - ... + def set(self) -> None: ... - def clear(self) -> None: - ... + def clear(self) -> None: ... - def wait(self, timeout: float | None = None) -> bool: - ... + def wait(self, timeout: float | None = None) -> bool: ... class ExecModel(metaclass=abc.ABCMeta): @@ -973,9 +962,9 @@ def reconfigure( class ChannelFactory: def __init__(self, gateway: BaseGateway, startcount: int = 1) -> None: - self._channels: weakref.WeakValueDictionary[ - int, Channel - ] = weakref.WeakValueDictionary() + self._channels: weakref.WeakValueDictionary[int, Channel] = ( + weakref.WeakValueDictionary() + ) # Channel ID => (callback, end marker, strconfig) self._callbacks: dict[ int, tuple[Callable[[Any], Any], object, tuple[bool, bool]] diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index 6ea7113b..165e8cad 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -1,6 +1,7 @@ """ code to initialize the remote side of a gateway once the io is created """ + from __future__ import annotations import inspect diff --git a/src/execnet/gateway_io.py b/src/execnet/gateway_io.py index 37b00de0..a29fcc78 100644 --- a/src/execnet/gateway_io.py +++ b/src/execnet/gateway_io.py @@ -3,6 +3,7 @@ creates io instances used for gateway io """ + from __future__ import annotations import shlex diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 6ac76884..2b447d57 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -3,6 +3,7 @@ (c) 2008-2014, Holger Krekel and others """ + from __future__ import annotations import atexit diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 648021d9..6e14fef5 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -3,6 +3,7 @@ (c) 2006-2009, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ + from __future__ import annotations import os diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index 24dccc9c..b560df75 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -1,6 +1,7 @@ """ (c) 2006-2013, Armin Rigo, Holger Krekel, Maciej Fijalkowski """ + from __future__ import annotations from typing import TYPE_CHECKING diff --git a/src/execnet/script/quitserver.py b/src/execnet/script/quitserver.py index 1ea3fccd..4c94c383 100644 --- a/src/execnet/script/quitserver.py +++ b/src/execnet/script/quitserver.py @@ -1,8 +1,9 @@ """ - send a "quit" signal to a remote server +send a "quit" signal to a remote server """ + from __future__ import annotations import socket diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index 7f4bc02b..569d4042 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -4,6 +4,7 @@ for injection into startserver.py """ + import os import select import socket diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 34dfc0a3..b58d1a4c 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -1,14 +1,15 @@ #! /usr/bin/env python """ - start socket based minimal readline exec server +start socket based minimal readline exec server - it can exeuted in 2 modes of operation +it can exeuted in 2 modes of operation - 1. as normal script, that listens for new connections +1. as normal script, that listens for new connections - 2. via existing_gateway.remote_exec (as imported module) +2. via existing_gateway.remote_exec (as imported module) """ + # this part of the program only executes on the server side # from __future__ import annotations diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index cc525e94..9c18c12c 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -5,6 +5,7 @@ python socketserverservice.py register net start ExecNetSocketServer """ + import sys import threading diff --git a/src/execnet/xspec.py b/src/execnet/xspec.py index 5bc437ea..0cbf3de7 100644 --- a/src/execnet/xspec.py +++ b/src/execnet/xspec.py @@ -1,6 +1,7 @@ """ (c) 2008-2013, holger krekel """ + from __future__ import annotations diff --git a/testing/test_channel.py b/testing/test_channel.py index 67039825..f500f117 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -1,6 +1,7 @@ """ mostly functional tests of gateways. """ + from __future__ import annotations import time diff --git a/testing/test_gateway.py b/testing/test_gateway.py index e507a935..4291404e 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -1,6 +1,7 @@ """ mostly functional tests of gateways. """ + from __future__ import annotations import os diff --git a/testing/test_multi.py b/testing/test_multi.py index 445b3f15..edc93037 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -1,6 +1,7 @@ """ - tests for multi channels and gateway Groups +tests for multi channels and gateway Groups """ + from __future__ import annotations import gc From c302c836a3a4c00918d3a98cb5ef4ef7c9128114 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 06:57:40 -0300 Subject: [PATCH 117/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 (#255) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.12 to 1.8.14. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.12...v1.8.14) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6e6682bb..74fc68ee 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.12 + uses: pypa/gh-action-pypi-publish@v1.8.14 - name: Push tag run: | From 7bad7942c94af142490a7cfc696cf6cf74ead6f0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 25 Mar 2024 09:47:35 +0200 Subject: [PATCH 118/275] doc/basic: remove no longer correct comment --- doc/basics.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/basics.rst b/doc/basics.rst index f0eebd85..d767ad8e 100644 --- a/doc/basics.rst +++ b/doc/basics.rst @@ -164,13 +164,6 @@ you can execute this little test file:: 1 -.. note:: - - With python3 you can (as of December 2013) only use the thread model - because neither eventlet-0.14.0 nor gevent-1.0 support Python3. - When they start to support Python3, execnet will probably just work - because it is itself Python3 compatible. - How to execute in the main thread ------------------------------------------------ From 56941795b524be8c63b1581abc82c784df081b0a Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 25 Mar 2024 10:03:35 +0200 Subject: [PATCH 119/275] doc: minor copy-edits --- doc/basics.rst | 14 ++-- doc/example/test_debug.rst | 2 +- doc/example/test_group.rst | 12 ++-- doc/example/test_info.rst | 20 +++--- doc/example/test_multi.rst | 8 +-- doc/example/test_proxy.rst | 6 +- doc/example/test_ssh_fileserver.rst | 1 - doc/implnotes.rst | 3 +- doc/index.rst | 25 +++---- doc/support.rst | 8 +-- src/execnet/gateway.py | 33 ++++----- src/execnet/gateway_base.py | 105 +++++++++++++++------------- src/execnet/gateway_bootstrap.py | 8 +-- src/execnet/gateway_io.py | 20 +++--- src/execnet/gateway_socket.py | 5 +- src/execnet/multi.py | 35 +++++----- src/execnet/rsync.py | 16 ++--- src/execnet/xspec.py | 11 +-- 18 files changed, 167 insertions(+), 165 deletions(-) diff --git a/doc/basics.rst b/doc/basics.rst index d767ad8e..78672647 100644 --- a/doc/basics.rst +++ b/doc/basics.rst @@ -6,7 +6,7 @@ execnet ad-hoc instantiates local and remote Python interpreters. Each interpreter is accessible through a **Gateway** which manages code and data communication. **Channels** allow to exchange data between the local and the remote end. **Groups** -help to manage creation and termination of sub interpreters. +help to manage creation and termination of sub-interpreters. .. image:: _static/basic1.png @@ -26,10 +26,10 @@ Here is an example which instantiates a simple Python subprocess:: >>> gateway = execnet.makegateway() -gateways allow to `remote execute code`_ and +Gateways allow to `remote execute code`_ and `exchange data`_ bidirectionally. -examples for valid gateway specifications +Examples for valid gateway specifications ------------------------------------------- * ``ssh=wyvern//python=python3.3//chdir=mycache`` specifies a Python3.3 @@ -82,7 +82,7 @@ in the instantiated subprocess-interpreter: .. automethod:: Gateway.remote_exec(source) It is allowed to pass a module object as source code -in which case it's source code will be obtained and +in which case its source code will be obtained and get sent for remote execution. ``remote_exec`` returns a channel object whose symmetric counterpart channel is available to the remotely executing source. @@ -90,7 +90,7 @@ is available to the remotely executing source. .. method:: Gateway.reconfigure([py2str_as_py3str=True, py3str_as_py2str=False]) - reconfigures the string-coercion behaviour of the gateway + Reconfigures the string-coercion behaviour of the gateway .. _`Channel`: .. _`channel-api`: @@ -167,7 +167,7 @@ you can execute this little test file:: How to execute in the main thread ------------------------------------------------ -When the remote side of a gateway uses the 'thread' model, execution +When the remote side of a gateway uses the "thread" model, execution will preferably run in the main thread. This allows GUI loops or other code to behave correctly. If you, however, start multiple executions concurrently, they will run in non-main threads. @@ -220,7 +220,7 @@ configure a tracing mechanism: .. _`dumps/loads`: .. _`dumps/loads API`: -cross-interpreter serialization of python objects +Cross-interpreter serialization of Python objects ======================================================= .. versionadded:: 1.1 diff --git a/doc/example/test_debug.rst b/doc/example/test_debug.rst index f0a71641..144a197f 100644 --- a/doc/example/test_debug.rst +++ b/doc/example/test_debug.rst @@ -1,5 +1,5 @@ -Debugging execnet / Wire messages +Debugging execnet / wire messages =============================================================== By setting the environment variable ``EXECNET_DEBUG`` you can diff --git a/doc/example/test_group.rst b/doc/example/test_group.rst index ed7a07b6..dd6275b5 100644 --- a/doc/example/test_group.rst +++ b/doc/example/test_group.rst @@ -5,7 +5,7 @@ Usings Groups for managing multiple gateways ------------------------------------------------------ Use ``execnet.Group`` to manage membership and lifetime of -of multiple gateways:: +multiple gateways:: >>> import execnet >>> group = execnet.Group(['popen'] * 2) @@ -25,7 +25,7 @@ of multiple gateways:: >>> group -Assigning Gateway IDs +Assigning gateway IDs ------------------------------------------------------ All gateways are created as part of a group and receive @@ -66,12 +66,12 @@ you actually use the ``execnet.default_group``:: >>> execnet.default_group.defaultspec # used for empty makegateway() calls 'popen' -Robust Termination of ssh/popen processes +Robust termination of SSH/popen processes ----------------------------------------------- Use ``group.terminate(timeout)`` if you want to terminate -member gateways and ensure that no local sub processes remain -you can specify a ``timeout`` after which an attempt at killing +member gateways and ensure that no local subprocesses remain. +You can specify a ``timeout`` after which an attempt at killing the related process is made:: >>> import execnet @@ -86,7 +86,7 @@ the related process is made:: execnet aims to provide totally robust termination so if you have left-over processes or other termination issues -please :doc:`report them <../support>`. thanks! +please :doc:`report them <../support>`. Thanks! Using Groups to manage a certain type of gateway diff --git a/doc/example/test_info.rst b/doc/example/test_info.rst index 8dbfa538..0777dd7b 100644 --- a/doc/example/test_info.rst +++ b/doc/example/test_info.rst @@ -1,5 +1,5 @@ -basic local and remote communication -========================================= +Basic local and remote communication +==================================== Execute source code in subprocess, communicate through a channel ------------------------------------------------------------------- @@ -25,8 +25,8 @@ messages between two processes. .. _`share-nothing model`: http://en.wikipedia.org/wiki/Shared_nothing_architecture -remote-exec a function (avoiding inlined source part I) -------------------------------------------------------------------- +Remote-exec a function (avoiding inlined source part I) +------------------------------------------------------- You can send and remote execute parametrized pure functions like this: @@ -49,8 +49,8 @@ Notes: between the nodes). -remote-exec a module (avoiding inlined source part II) --------------------------------------------------------------- +Remote-exec a module (avoiding inlined source part II) +------------------------------------------------------ You can pass a module object to ``remote_exec`` in which case its source code will be sent. No dependencies will be transferred @@ -86,8 +86,8 @@ A local subprocess gateway has the same working directory as the instantiatior:: "ssh" gateways default to the login home directory. -Get information from remote ssh account --------------------------------------------- +Get information from remote SSH account +--------------------------------------- Use simple execution to obtain information from remote environments:: @@ -143,7 +143,7 @@ and use it to transfer information:: -a simple command loop pattern +A simple command loop pattern -------------------------------------------------------------- If you want the remote side to serve a number @@ -189,6 +189,6 @@ itself into the remote socket endpoint:: gw = execnet.makegateway("socket=TARGET-IP:8888") That's it, you can now use the gateway object just like -a popen- or ssh-based one. +a popen- or SSH-based one. .. include:: test_ssh_fileserver.rst diff --git a/doc/example/test_multi.rst b/doc/example/test_multi.rst index 611a3808..306b960d 100644 --- a/doc/example/test_multi.rst +++ b/doc/example/test_multi.rst @@ -1,4 +1,4 @@ -advanced (multi) channel communication +Advanced (multi) channel communication ===================================================== MultiChannel: container for multiple channels @@ -19,7 +19,7 @@ Use ``execnet.MultiChannel`` to work with multiple channels:: >>> sum(mch.receive_each()) 3 -receive results from sub processes with a Queue +Receive results from sub processes with a Queue ----------------------------------------------------- Use ``MultiChannel.make_receive_queue()`` to get a queue @@ -52,7 +52,7 @@ data immediately and without blocking execution:: Note that the callback function will be executed in the receiver thread and should not block or run for too long. -robustly receive results and termination notification +Robustly receive results and termination notification ----------------------------------------------------- Use ``MultiChannel.make_receive_queue(endmarker)`` to specify @@ -76,7 +76,7 @@ is blocked in execution and is terminated/killed:: -saturate multiple Hosts and CPUs with tasks to process +Saturate multiple Hosts and CPUs with tasks to process -------------------------------------------------------- If you have multiple CPUs or hosts you can create as many diff --git a/doc/example/test_proxy.rst b/doc/example/test_proxy.rst index a4f45f37..f2af992d 100644 --- a/doc/example/test_proxy.rst +++ b/doc/example/test_proxy.rst @@ -1,10 +1,10 @@ -Managing Proxied gateways +Managing proxied gateways ========================== -Simple Proxying +Simple proxying ---------------- -Using the via arg of specs we can create a gateway +Using the ``via`` arg of specs we can create a gateway whose io is created on a remote gateway and proxied to the master. The simplest use case, is where one creates one master process diff --git a/doc/example/test_ssh_fileserver.rst b/doc/example/test_ssh_fileserver.rst index 54807452..e7a82817 100644 --- a/doc/example/test_ssh_fileserver.rst +++ b/doc/example/test_ssh_fileserver.rst @@ -1,4 +1,3 @@ - Receive file contents from remote SSH account ----------------------------------------------------- diff --git a/doc/implnotes.rst b/doc/implnotes.rst index 9223fd9b..d13d9e87 100644 --- a/doc/implnotes.rst +++ b/doc/implnotes.rst @@ -1,8 +1,7 @@ - gateway_base.py ---------------------- -the code of this module is sent to the "other side" +The code of this module is sent to the "other side" as a means of bootstrapping a Gateway object capable of receiving and executing code, and routing data through channels. diff --git a/doc/index.rst b/doc/index.rst index fe1028c0..19a20eb8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,5 +1,3 @@ - - .. image:: _static/pythonring.png :align: right @@ -17,9 +15,9 @@ communication for distributing execution across many Python interpreters across version, platform and network barriers. It has a minimal and fast API targeting the following uses: -* distribute tasks to (many) local or remote CPUs -* write and deploy hybrid multi-process applications -* write scripts to administer multiple environments +* Distribute tasks to (many) local or remote CPUs +* Write and deploy hybrid multi-process applications +* Write scripts to administer multiple environments .. _`channel-send/receive`: http://en.wikipedia.org/wiki/Channel_(programming) .. _`share-nothing model`: http://en.wikipedia.org/wiki/Shared_nothing_architecture @@ -30,25 +28,25 @@ a minimal and fast API targeting the following uses: Features ------------------ -* automatic bootstrapping: no manual remote installation. +* Automatic bootstrapping: no manual remote installation. -* safe and simple serialization of python builtin +* Safe and simple serialization of Python builtin types for sending/receiving structured data messages. (New in 1.1) execnet offers a new :ref:`dumps/loads ` API which allows cross-interpreter compatible serialization of Python builtin types. -* flexible communication: synchronous send/receive as well as +* Flexible communication: synchronous send/receive as well as callback/queue mechanisms supported -* easy creation, handling and termination of multiple processes +* Easy creation, handling and termination of multiple processes -* well tested interactions between CPython 2.5-2.7, CPython-3.3, Jython 2.5.1 +* Well tested interactions between CPython 2.5-2.7, CPython-3.3, Jython 2.5.1 and PyPy interpreters. -* fully interoperable between Windows and Unix-ish systems. +* Fully interoperable between Windows and Unix-ish systems. -* many tested :doc:`examples` +* Many tested :doc:`examples` Known uses ------------------- @@ -64,7 +62,7 @@ Known uses * Ronny Pfannschmidt uses it for his `anyvc`_ VCS-abstraction project to bridge the Python2/Python3 version gap. -* sysadmins and developers are using it for ad-hoc custom scripting +* Sysadmins and developers are using it for ad-hoc custom scripting .. _`quora`: http://quora.com .. _`connecting CPython and PyPy`: http://www.quora.com/Quora-Infrastructure/Did-Quoras-switch-to-PyPy-result-in-increased-memory-consumption @@ -87,7 +85,6 @@ used as backend of the popular `pytest-xdist `_ +* Join `execnet-dev`_ for general discussions +* Join `execnet-commit`_ to be notified of changes +* Clone the `github repository`_ and submit patches +* Hang out on the #pytest channel on `irc.libera.chat `_ (using an IRC client, via `webchat `_, or `via Matrix `_). diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index ec197398..a3c04e5f 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -1,5 +1,5 @@ -""" -gateway code for initiating popen, socket and ssh connections. +"""Gateway code for initiating popen, socket and ssh connections. + (c) 2004-2013, Holger Krekel and others """ @@ -42,7 +42,7 @@ def remoteaddress(self) -> str: return self._io.remoteaddress # type: ignore[attr-defined,no-any-return] def __repr__(self) -> str: - """return string representing gateway type and status.""" + """A string representing gateway type and status.""" try: r: str = self.hasreceiver() and "receive-live" or "not-receiving" i = str(len(self._channelfactory.channels())) @@ -52,9 +52,10 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} id={self.id!r} {r}, {self.execmodel.backend} model, {i} active channels>" def exit(self) -> None: - """trigger gateway exit. Defer waiting for finishing - of receiver-thread and subprocess activity to when - group.terminate() is called. + """Trigger gateway exit. + + Defer waiting for finishing of receiver-thread and subprocess activity + to when group.terminate() is called. """ self._trace("gateway.exit() called") if self not in self._group: @@ -73,17 +74,17 @@ def exit(self) -> None: def reconfigure( self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False ) -> None: - """ - set the string coercion for this gateway - the default is to try to convert py2 str as py3 str, - but not to try and convert py3 str to py2 str + """Set the string coercion for this gateway. + + The default is to try to convert py2 str as py3 str, but not to try and + convert py3 str to py2 str. """ self._strconfig = (py2str_as_py3str, py3str_as_py2str) data = gateway_base.dumps_internal(self._strconfig) self._send(Message.RECONFIGURE, data=data) def _rinfo(self, update: bool = False) -> RInfo: - """return some sys/env information from remote.""" + """Return some sys/env information from remote.""" if update or not hasattr(self, "_cache_rinfo"): ch = self.remote_exec(rinfo_source) try: @@ -93,11 +94,11 @@ def _rinfo(self, update: bool = False) -> RInfo: return self._cache_rinfo def hasreceiver(self) -> bool: - """return True if gateway is able to receive data.""" + """Whether gateway is able to receive data.""" return self._receivepool.active_count() > 0 def remote_status(self) -> RemoteStatus: - """return information object about remote execution status.""" + """Obtain information about the remote execution status.""" channel = self.newchannel() self._send(Message.STATUS, channel.id) statusdict = channel.receive() @@ -111,7 +112,7 @@ def remote_exec( source: str | types.FunctionType | Callable[..., object] | types.ModuleType, **kwargs: object, ) -> Channel: - """return channel object and connect it to a remote + """Return channel object and connect it to a remote execution thread where the given ``source`` executes. * ``source`` is a string: execute source string remotely @@ -120,7 +121,7 @@ def remote_exec( call function with ``**kwargs``, adding a ``channel`` object to the keyword arguments. * ``source`` is a pure module: execute source of module - with a ``channel`` in its global namespace + with a ``channel`` in its global namespace. In all cases the binding ``__name__='__channelexec__'`` will be available in the global namespace of the remotely @@ -152,7 +153,7 @@ def remote_exec( def remote_init_threads(self, num: int | None = None) -> None: """DEPRECATED. Is currently a NO-OPERATION already.""" - print("WARNING: remote_init_threads()" " is a no-operation in execnet-1.2") + print("WARNING: remote_init_threads() is a no-operation in execnet-1.2") class RInfo: diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index d88e6585..08f413cd 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -1,7 +1,4 @@ -""" -base execnet gateway code send to the other side for bootstrapping. - -NOTE: aims to be compatible to Python 2.5-3.X, Jython and IronPython +"""Base execnet gateway code send to the other side for bootstrapping. :copyright: 2004-2015 :authors: @@ -313,12 +310,10 @@ def get_execmodel(backend: str | ExecModel) -> ExecModel: class Reply: - """reply instances provide access to the result - of a function execution that got dispatched - through WorkerPool.spawn() - """ + """Provide access to the result of a function execution that got dispatched + through WorkerPool.spawn().""" - def __init__(self, task, threadmodel) -> None: + def __init__(self, task, threadmodel: ExecModel) -> None: self.task = task self._result_ready = threadmodel.Event() self.running = True @@ -360,12 +355,13 @@ class WorkerPool: itself into performing function execution through calling integrate_as_primary_thread() which will return when the pool received a trigger_shutdown(). + + By default allows unlimited number of spawns. """ _primary_thread_task: Reply | None def __init__(self, execmodel: ExecModel, hasprimary: bool = False) -> None: - """by default allow unlimited number of spawns.""" self.execmodel = execmodel self._running_lock = self.execmodel.Lock() self._running: MutableSet[Reply] = set() @@ -379,9 +375,8 @@ def __init__(self, execmodel: ExecModel, hasprimary: bool = False) -> None: self._primary_thread_task_ready = None def integrate_as_primary_thread(self) -> None: - """integrate the thread with which we are called as a primary - thread for executing functions triggered with spawn(). - """ + """Integrate the thread with which we are called as a primary + thread for executing functions triggered with spawn().""" assert self.execmodel.backend in ("thread", "main_thread_only"), self.execmodel primary_thread_task_ready = self._primary_thread_task_ready assert primary_thread_task_ready is not None @@ -447,9 +442,7 @@ def _try_send_to_primary_thread(self, reply: Reply) -> bool: return False def spawn(self, func, *args, **kwargs) -> Reply: - """return Reply object for the asynchronous dispatch - of the given func(*args, **kwargs). - """ + """Asynchronously dispatch func(*args, **kwargs) and return a Reply.""" reply = Reply((func, args, kwargs), self.execmodel) with self._running_lock: if self._shuttingdown: @@ -460,12 +453,12 @@ def spawn(self, func, *args, **kwargs) -> Reply: return reply def terminate(self, timeout: float | None = None) -> bool: - """trigger shutdown and wait for completion of all executions.""" + """Trigger shutdown and wait for completion of all executions.""" self.trigger_shutdown() return self.waitall(timeout=timeout) def waitall(self, timeout: float | None = None) -> bool: - """wait until all active spawns have finished executing.""" + """Wait until all active spawns have finished executing.""" with self._running_lock: if not self._running: return True @@ -545,7 +538,7 @@ def read(self, numbytes: int) -> bytes: return buf def write(self, data: bytes) -> None: - """write out all data bytes.""" + """Write out all data bytes.""" assert isinstance(data, bytes) self._write(data) self.outfile.flush() @@ -558,7 +551,7 @@ def close_write(self) -> None: class Message: - """encapsulates Messages and their wire protocol.""" + """Encapsulates Messages and their wire protocol.""" # message code -> name, handler _types: dict[int, tuple[str, Callable[[Message, BaseGateway], None]]] = {} @@ -707,7 +700,7 @@ class TimeoutError(IOError): class Channel: - "Communication channel between two Python Interpreter execution points." + """Communication channel between two Python Interpreter execution points.""" RemoteError = RemoteError TimeoutError = TimeoutError @@ -734,9 +727,9 @@ def setcallback( callback: Callable[[Any], Any], endmarker: object = NO_ENDMARKER_WANTED, ) -> None: - """set a callback function for receiving items. + """Set a callback function for receiving items. - All already queued items will immediately trigger the callback. + All already-queued items will immediately trigger the callback. Afterwards the callback will execute in the receiver thread for each received data item and calls to ``receive()`` will raise an error. @@ -813,8 +806,9 @@ def _getremoteerror(self): # public API for channel objects # def isclosed(self) -> bool: - """return True if the channel is closed. A closed - channel may still hold items. + """Return True if the channel is closed. + + A closed channel may still hold items. """ return self._closed @@ -835,9 +829,10 @@ def makefile( mode: Literal["r", "w"] = "w", proxyclose: bool = False, ) -> ChannelFileWrite | ChannelFileRead: - """return a file-like object. + """Return a file-like object. + mode can be 'w' or 'r' for writeable/readable files. - if proxyclose is true file.close() will also close the channel. + If proxyclose is true, file.close() will also close the channel. """ if mode == "w": return ChannelFileWrite(channel=self, proxyclose=proxyclose) @@ -846,7 +841,8 @@ def makefile( raise ValueError(f"mode {mode!r} not available") def close(self, error=None) -> None: - """close down this channel with an optional error message. + """Close down this channel with an optional error message. + Note that closing of a channel tied to remote_exec happens automatically at the end of execution and cannot be done explicitly. @@ -878,13 +874,18 @@ def close(self, error=None) -> None: self.gateway._channelfactory._no_longer_opened(self.id) def waitclose(self, timeout: float | None = None) -> None: - """wait until this channel is closed (or the remote side + """Wait until this channel is closed (or the remote side otherwise signalled that no more data was being sent). + The channel may still hold receiveable items, but not receive - any more after waitclose() has returned. Exceptions from executing - code on the other side are reraised as local channel.RemoteErrors. + any more after waitclose() has returned. + + Exceptions from executing code on the other side are reraised as local + channel.RemoteErrors. + EOFError is raised if the reading-connection was prematurely closed, which often indicates a dying process. + self.TimeoutError is raised after the specified number of seconds (default is None, i.e. wait indefinitely). """ @@ -897,21 +898,25 @@ def waitclose(self, timeout: float | None = None) -> None: raise error def send(self, item: object) -> None: - """sends the given item to the other side of the channel, + """Sends the given item to the other side of the channel, possibly blocking if the sender queue is full. - The item must be a simple python type and will be - copied to the other side by value. IOError is - raised if the write pipe was prematurely closed. + + The item must be a simple Python type and will be + copied to the other side by value. + + OSError is raised if the write pipe was prematurely closed. """ if self.isclosed(): raise OSError(f"cannot send to {self!r}") self.gateway._send(Message.CHANNEL_DATA, self.id, dumps_internal(item)) def receive(self, timeout: float | None = None) -> object: - """receive a data item that was sent from the other side. - timeout: None [default] blocked waiting. A positive number + """Receive a data item that was sent from the other side. + + timeout: None [default] blocked waiting. A positive number indicates the number of seconds after which a channel.TimeoutError exception will be raised if no item was received. + Note that exceptions from the remotely executing code will be reraised as channel.RemoteError exceptions containing a textual representation of the remote traceback. @@ -943,9 +948,9 @@ def next(self) -> object: def reconfigure( self, py2str_as_py3str: bool = True, py3str_as_py2str: bool = False ) -> None: - """ - set the string coercion for this channel - the default is to try to convert py2 str as py3 str, + """Set the string coercion for this channel. + + The default is to try to convert py2 str as py3 str, but not to try and convert py3 str to py2 str """ self._strconfig = (py2str_as_py3str, py3str_as_py2str) @@ -976,7 +981,7 @@ def __init__(self, gateway: BaseGateway, startcount: int = 1) -> None: self._list = list # needed during interp-shutdown def new(self, id: int | None = None) -> Channel: - """create a new Channel with 'id' (or create new id if None).""" + """Create a new Channel with 'id' (or create new id if None).""" with self._writelock: if self.finished: raise OSError(f"connection already closed: {self.gateway}") @@ -1197,7 +1202,7 @@ def _local_schedulexec(self, channel: Channel, sourcetask: bytes) -> None: # _____________________________________________________________________ # def newchannel(self) -> Channel: - """return a new independent channel.""" + """Return a new independent channel.""" return self._channelfactory.new() def join(self, timeout: float | None = None) -> None: @@ -1345,7 +1350,7 @@ class _Stop(Exception): class opcode: - """container for name -> num mappings.""" + """Container for name -> num mappings.""" BUILDTUPLE = b"@" BYTES = b"A" @@ -1565,10 +1570,10 @@ def load_channel(self) -> None: def dumps(obj: object) -> bytes: - """return a serialized bytestring of the given obj. + """Serialize the given obj to a bytestring. The obj and all contained objects must be of a builtin - python type (so nested dicts, sets, etc. are all ok but + Python type (so nested dicts, sets, etc. are all OK but not user-level instances). """ return _Serializer().save(obj, versioned=True) # type: ignore[return-value] @@ -1582,16 +1587,16 @@ def dump(byteio, obj: object) -> None: def loads( bytestring: bytes, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False ) -> object: - """return the object as deserialized from the given bytestring. + """Deserialize the given bytestring to an object. - py2str_as_py3str: if true then string (str) objects previously + py2str_as_py3str: If true then string (str) objects previously dumped on Python2 will be loaded as Python3 strings which really are text objects. - py3str_as_py2str: if true then string (str) objects previously + py3str_as_py2str: If true then string (str) objects previously dumped on Python3 will be loaded as Python2 strings instead of unicode objects. - if the bytestring was dumped with an incompatible protocol + If the bytestring was dumped with an incompatible protocol version or if the bytestring is corrupted, the ``execnet.DataFormatError`` will be raised. """ @@ -1604,7 +1609,7 @@ def loads( def load( io: ReadIO, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False ) -> object: - """derserialize an object form the specified stream. + """Derserialize an object form the specified stream. Behaviour and parameters are otherwise the same as with ``loads`` """ diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index 165e8cad..17cdc937 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -1,6 +1,4 @@ -""" -code to initialize the remote side of a gateway once the io is created -""" +"""Code to initialize the remote side of a gateway once the IO is created.""" from __future__ import annotations @@ -22,9 +20,9 @@ class HostNotFound(Exception): def bootstrap_import(io: IO, spec: XSpec) -> None: - # only insert the importdir into the path if we must. This prevents + # Only insert the importdir into the path if we must. This prevents # bugs where backports expect to be shadowed by the standard library on - # newer versions of python but would instead shadow the standard library + # newer versions of python but would instead shadow the standard library. sendexec( io, "import sys", diff --git a/src/execnet/gateway_io.py b/src/execnet/gateway_io.py index a29fcc78..21285ab4 100644 --- a/src/execnet/gateway_io.py +++ b/src/execnet/gateway_io.py @@ -1,7 +1,6 @@ -""" -execnet io initialization code +"""execnet IO initialization code. -creates io instances used for gateway io +Creates IO instances used for gateway IO. """ from __future__ import annotations @@ -30,7 +29,7 @@ class Popen2IOMaster(Popen2IO): # Set externally, for some specs only. remoteaddress: str - def __init__(self, args, execmodel) -> None: + def __init__(self, args, execmodel: ExecModel) -> None: PIPE = execmodel.subprocess.PIPE self.popen = p = execmodel.subprocess.Popen(args, stdout=PIPE, stdin=PIPE) super().__init__(p.stdin, p.stdout, execmodel=execmodel) @@ -105,7 +104,7 @@ def vagrant_ssh_args(spec: XSpec) -> list[str]: return args -def create_io(spec: XSpec, execmodel) -> Popen2IOMaster: +def create_io(spec: XSpec, execmodel: ExecModel) -> Popen2IOMaster: if spec.popen: args = popen_args(spec) return Popen2IOMaster(args, execmodel) @@ -137,11 +136,12 @@ def create_io(spec: XSpec, execmodel) -> Popen2IOMaster: class ProxyIO: """A Proxy IO object allows to instantiate a Gateway - through another "via" gateway. A master:ProxyIO object - provides an IO object effectively connected to the sub - via the forwarder. To achieve this, master:ProxyIO interacts - with forwarder:serve_proxy_io() which itself - instantiates and interacts with the sub. + through another "via" gateway. + + A master:ProxyIO object provides an IO object effectively connected to the + sub via the forwarder. To achieve this, master:ProxyIO interacts with + forwarder:serve_proxy_io() which itself instantiates and interacts with the + sub. """ def __init__(self, proxy_channel: Channel, execmodel: ExecModel) -> None: diff --git a/src/execnet/gateway_socket.py b/src/execnet/gateway_socket.py index fff9afcd..be42f1ab 100644 --- a/src/execnet/gateway_socket.py +++ b/src/execnet/gateway_socket.py @@ -59,8 +59,9 @@ def kill(self) -> None: def start_via( gateway: Gateway, hostport: tuple[str, int] | None = None ) -> tuple[str, int]: - """return a host, port tuple, - after instantiating a socketserver on the given gateway + """Instantiate a socketserver on the given gateway. + + Returns a host, port tuple. """ if hostport is None: host, port = ("localhost", 0) diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 2b447d57..f9d01d90 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -35,15 +35,16 @@ class Group: - """Gateway Groups.""" + """Gateway Group.""" defaultspec = "popen" def __init__( self, xspecs: Iterable[XSpec | str | None] = (), execmodel: str = "thread" ) -> None: - """initialize group and make gateways as specified. - execmodel can be 'thread' or 'eventlet'. + """Initialize a group and make gateways as specified. + + execmodel can be one of the supported execution models. """ self._gateways: list[Gateway] = [] self._autoidcounter = 0 @@ -72,13 +73,11 @@ def set_execmodel( ) -> None: """Set the execution model for local and remote site. - execmodel can be one of "thread" or "eventlet" (XXX gevent). + execmodel can be one of the supported execution models. It determines the execution model for any newly created gateway. - If remote_execmodel is not specified it takes on the value - of execmodel. + If remote_execmodel is not specified it takes on the value of execmodel. NOTE: Execution models can only be set before any gateway is created. - """ if self._gateways: raise ValueError( @@ -115,7 +114,8 @@ def __iter__(self) -> Iterator[Gateway]: return iter(list(self._gateways)) def makegateway(self, spec: XSpec | str | None = None) -> Gateway: - """create and configure a gateway to a Python interpreter. + """Create and configure a gateway to a Python interpreter. + The ``spec`` string encodes the target gateway type and configuration information. The general format is:: @@ -127,7 +127,7 @@ def makegateway(self, spec: XSpec | str | None = None) -> Gateway: id= specifies the gateway id python= specifies which python interpreter to execute - execmodel=model 'thread', 'main_thread_only', 'eventlet', 'gevent' model for execution + execmodel=model 'thread', 'main_thread_only', 'eventlet', 'gevent' execution model chdir= specifies to which directory to change nice= specifies process priority of new process env:NAME=value specifies a remote environment variable setting. @@ -207,13 +207,15 @@ def _cleanup_atexit(self) -> None: self.terminate(timeout=1.0) def terminate(self, timeout: float | None = None) -> None: - """trigger exit of member gateways and wait for termination - of member gateways and associated subprocesses. After waiting - timeout seconds try to to kill local sub processes of popen- - and ssh-gateways. Timeout defaults to None meaning - open-ended waiting and no kill attempts. - """ + """Trigger exit of member gateways and wait for termination + of member gateways and associated subprocesses. + After waiting timeout seconds try to to kill local sub processes of + popen- and ssh-gateways. + + Timeout defaults to None meaning open-ended waiting and no kill + attempts. + """ while self: vias: set[str] = set() for gw in self: @@ -247,8 +249,7 @@ def remote_exec( **kwargs, ) -> MultiChannel: """remote_exec source on all member gateways and return - MultiChannel connecting to all sub processes. - """ + a MultiChannel connecting to all sub processes.""" channels = [] for gw in self: channels.append(gw.remote_exec(source, **kwargs)) diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 6e14fef5..84820532 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -67,7 +67,7 @@ def _process_link(self, channel: Channel) -> None: channel.send(42) def _done(self, channel: Channel) -> None: - """Call all callbacks""" + """Call all callbacks.""" finishedcallback = self._channels.pop(channel) if finishedcallback: finishedcallback() @@ -85,7 +85,7 @@ def _send_item( modified_rel_path_components: list[str], checksum: bytes, ) -> None: - """Send one item""" + """Send one item.""" modifiedpath = os.path.join(self._sourcedir, *modified_rel_path_components) try: f = open(modifiedpath, "rb") @@ -117,9 +117,10 @@ def _report_send_file(self, gateway: BaseGateway, modified_rel_path: str) -> Non print(f"{gateway} <= {modified_rel_path}") def send(self, raises: bool = True) -> None: - """Sends a sourcedir to all added targets. Flag indicates - whether to raise an error or return in case of lack of - targets + """Sends a sourcedir to all added targets. + + raises indicates whether to raise an error or return in case of lack of + targets. """ if not self._channels: if raises: @@ -164,9 +165,8 @@ def add_target( finishedcallback: Callable[[], None] | None = None, **options, ) -> None: - """Adds a remote target specified via a gateway - and a remote destination directory. - """ + """Add a remote target specified via a gateway and a remote destination + directory.""" for name in options: assert name in ("delete",) diff --git a/src/execnet/xspec.py b/src/execnet/xspec.py index 0cbf3de7..0559ed8c 100644 --- a/src/execnet/xspec.py +++ b/src/execnet/xspec.py @@ -7,11 +7,12 @@ class XSpec: """Execution Specification: key1=value1//key2=value2 ... - * keys need to be unique within the specification scope - * neither key nor value are allowed to contain "//" - * keys are not allowed to contain "=" - * keys are not allowed to start with underscore - * if no "=value" is given, assume a boolean True value + + * Keys need to be unique within the specification scope + * Neither key nor value are allowed to contain "//" + * Keys are not allowed to contain "=" + * Keys are not allowed to start with underscore + * If no "=value" is given, assume a boolean True value """ # XXX allow customization, for only allow specific key names From d1c962f49f3fb0410cc174130e49902b9c85d34b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 26 Mar 2024 00:18:48 +0200 Subject: [PATCH 120/275] doc: enable nitpicky mode Ensures all references resolve. --- doc/conf.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index c12a8c44..3d380529 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,11 @@ # Add any Sphinx extension module names here, as strings. # They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.doctest"] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -83,6 +87,16 @@ # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), +} + +nitpicky = True +nitpick_ignore = [ + ("py:class", "execnet.gateway_base.ChannelFileRead"), + ("py:class", "execnet.gateway_base.ChannelFileWrite"), + ("py:class", "execnet.gateway.Gateway"), +] # -- Options for HTML output -------------------------------------------------- From 40b8fb27fc755c501e96004f2d2f34128d504e92 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 26 Mar 2024 00:22:56 +0200 Subject: [PATCH 121/275] README: replace link from codespeak.net to RTD The codespeak.net docs have not updated in a few years. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6687e19d..7624091a 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ execnet: distributed Python deployment and communication .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/python/black -.. _execnet: http://codespeak.net/execnet +.. _execnet: https://execnet.readthedocs.io execnet_ provides carefully tested means to ad-hoc interact with Python interpreters across version, platform and network barriers. It provides From 6235a1a8e9377658ab754ac823a9dffa27191df5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 26 Mar 2024 00:23:58 +0200 Subject: [PATCH 122/275] doc: remove google analytics I doubt anyone is looking at it, let's stop feeding the beast. --- doc/_templates/layout.html | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index f4463252..b6665ee9 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -18,16 +18,3 @@

execnet: Distributed Python deployment and communication

{% endblock %} - -{% block footer %} -{{ super() }} - - -{% endblock %} From 83c4fd0126ae07d5b110414aece2d4f797a636f3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:03:48 -0300 Subject: [PATCH 123/275] [pre-commit.ci] pre-commit autoupdate (#257) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.2 → v0.3.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.2...v0.3.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99bb98bb..bf6bf123 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.2 + rev: v0.3.4 hooks: - id: ruff args: [ --fix ] From 51725638b157493cf91a4b80569706901ffe0ed7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 25 Mar 2024 10:14:20 +0200 Subject: [PATCH 124/275] gateway_base: fix Gateway.join ignoring timeout --- CHANGELOG.rst | 1 + src/execnet/gateway_base.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 03806770..33872c14 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Also fixed ``init_popen_io`` to use ``closefd=False`` for shared stdin and stdout file descriptors, preventing ``Bad file descriptor`` errors triggered by test_stdouterrin_setnull. +* Fixed ``GatewayBase.join()`` timeout argument getting ignored. * Removed support for Python 3.7. * Added official support for Python 3.12. diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index d88e6585..12c90b11 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -1203,7 +1203,7 @@ def newchannel(self) -> Channel: def join(self, timeout: float | None = None) -> None: """Wait for receiverthread to terminate.""" self._trace("waiting for receiver thread to finish") - self._receivepool.waitall() + self._receivepool.waitall(timeout) class WorkerGateway(BaseGateway): From db2f88eac869a8d9273f85a419e40e885e052cc9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 27 Mar 2024 20:57:59 +0200 Subject: [PATCH 125/275] gateway: remove an unused import --- src/execnet/gateway.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index a3c04e5f..da25bd3e 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -7,15 +7,12 @@ import inspect import linecache -import os import textwrap import types from typing import TYPE_CHECKING from typing import Any from typing import Callable -import execnet - from . import gateway_base from .gateway_base import IO from .gateway_base import Channel @@ -23,8 +20,6 @@ from .multi import Group from .xspec import XSpec -importdir = os.path.dirname(os.path.dirname(execnet.__file__)) - class Gateway(gateway_base.BaseGateway): """Gateway to a local or remote Python Interpreter.""" From fff4381c107f1d7963cd375e19df37e749ea964b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 27 Mar 2024 20:55:03 +0200 Subject: [PATCH 126/275] Expose `execnet.Gateway` and `execnet.Channel` for typing purposes These two types prominently appear in the public API so let's allow to refer to them. The constructors are considered private. --- CHANGELOG.rst | 2 ++ src/execnet/__init__.py | 4 ++++ src/execnet/gateway.py | 1 + src/execnet/gateway_base.py | 1 + src/execnet/gateway_bootstrap.py | 5 ++--- 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 33872c14..f4e38751 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,8 @@ Also fixed ``init_popen_io`` to use ``closefd=False`` for shared stdin and stdout file descriptors, preventing ``Bad file descriptor`` errors triggered by test_stdouterrin_setnull. +* Re-exported ``Gateway`` and ``Channel`` from ``execnet``. The constructors + are private. * Fixed ``GatewayBase.join()`` timeout argument getting ignored. * Removed support for Python 3.7. * Added official support for Python 3.12. diff --git a/src/execnet/__init__.py b/src/execnet/__init__.py index a266bded..e1095e5a 100644 --- a/src/execnet/__init__.py +++ b/src/execnet/__init__.py @@ -8,6 +8,8 @@ """ from ._version import version as __version__ +from .gateway import Gateway +from .gateway_base import Channel from .gateway_base import DataFormatError from .gateway_base import RemoteError from .gateway_base import TimeoutError @@ -32,10 +34,12 @@ "RemoteError", "TimeoutError", "XSpec", + "Gateway", "Group", "MultiChannel", "RSync", "default_group", + "Channel", "dumps", "loads", "load", diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index da25bd3e..547be291 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -27,6 +27,7 @@ class Gateway(gateway_base.BaseGateway): _group: Group def __init__(self, io: IO, spec: XSpec) -> None: + """:private:""" super().__init__(io=io, id=spec.id, _startcount=1) self.spec = spec self._initreceive() diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index b99eb2e8..c491b51f 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -708,6 +708,7 @@ class Channel: _executing = False def __init__(self, gateway: BaseGateway, id: int) -> None: + """:private:""" assert isinstance(id, int) assert not isinstance(gateway, type) self.gateway = gateway diff --git a/src/execnet/gateway_bootstrap.py b/src/execnet/gateway_bootstrap.py index 17cdc937..e9d7efe1 100644 --- a/src/execnet/gateway_bootstrap.py +++ b/src/execnet/gateway_bootstrap.py @@ -8,7 +8,6 @@ import execnet from . import gateway_base -from .gateway import Gateway from .gateway_base import IO from .xspec import XSpec @@ -81,7 +80,7 @@ def sendexec(io: IO, *sources: str) -> None: io.write((repr(source) + "\n").encode("utf-8")) -def bootstrap(io: IO, spec: XSpec) -> Gateway: +def bootstrap(io: IO, spec: XSpec) -> execnet.Gateway: if spec.popen: if spec.via or spec.python: bootstrap_exec(io, spec) @@ -93,5 +92,5 @@ def bootstrap(io: IO, spec: XSpec) -> Gateway: bootstrap_socket(io, spec) else: raise ValueError("unknown gateway type, can't bootstrap") - gw = Gateway(io, spec) + gw = execnet.Gateway(io, spec) return gw From dd32bdddaa35916b288620b5fe62af7fba0888e7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 27 Mar 2024 21:05:58 +0200 Subject: [PATCH 127/275] Expose types -- add py.typed file The typing is not perfect, but better than nothing, so let's expose it. --- CHANGELOG.rst | 1 + src/execnet/py.typed | 0 2 files changed, 1 insertion(+) create mode 100644 src/execnet/py.typed diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f4e38751..8f95f9a1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Also fixed ``init_popen_io`` to use ``closefd=False`` for shared stdin and stdout file descriptors, preventing ``Bad file descriptor`` errors triggered by test_stdouterrin_setnull. +* The library is now typed and the typing is exposed to type-checkers. * Re-exported ``Gateway`` and ``Channel`` from ``execnet``. The constructors are private. * Fixed ``GatewayBase.join()`` timeout argument getting ignored. diff --git a/src/execnet/py.typed b/src/execnet/py.typed new file mode 100644 index 00000000..e69de29b From dcbe6872d11081d613a5dc28e55825fba5a4827b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 05:43:52 +0000 Subject: [PATCH 128/275] build(deps): bump hynek/build-and-inspect-python-package from 1.5 to 2.2 Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 1.5 to 2.2. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v1.5...v2.2) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 74fc68ee..8b3599dc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5 + uses: hynek/build-and-inspect-python-package@v2.2 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c388cf57..29b03da3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v1.5 + uses: hynek/build-and-inspect-python-package@v2.2 test: From a382d2f62aaf76bf84701440ad22f5c7cd36a769 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 1 Apr 2024 16:41:54 -0300 Subject: [PATCH 129/275] Update download-artifact to 4.1.4 Seems like build-and-inspect-python-package is using a more recent upload-artifact version, so we need to update download-artifact along with it. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29b03da3..a5ead60f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.1.4 with: name: Packages path: dist From 8a50839b7d4b26949f83ae27d4b4fa7be5226ec1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 22:13:30 +0000 Subject: [PATCH 130/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.4 → v0.3.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.3.5) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf6bf123..eaf6fc6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.4 + rev: v0.3.5 hooks: - id: ruff args: [ --fix ] From e218d45239417343bad55239899f389eabd40245 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 3 Apr 2024 18:33:21 +0300 Subject: [PATCH 131/275] gateway_base: replace `-> object` return types to `-> Any` While `object` is type-safe, it is somewhat hostile to users (forces isinstance checks), so the common practice is to return `Any` in these cases. --- src/execnet/gateway_base.py | 14 +++++++------- src/execnet/multi.py | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index c491b51f..f5333064 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -911,7 +911,7 @@ def send(self, item: object) -> None: raise OSError(f"cannot send to {self!r}") self.gateway._send(Message.CHANNEL_DATA, self.id, dumps_internal(item)) - def receive(self, timeout: float | None = None) -> object: + def receive(self, timeout: float | None = None) -> Any: """Receive a data item that was sent from the other side. timeout: None [default] blocked waiting. A positive number @@ -935,10 +935,10 @@ def receive(self, timeout: float | None = None) -> object: else: return x - def __iter__(self) -> Iterator[object]: + def __iter__(self) -> Iterator[Any]: return self - def next(self) -> object: + def next(self) -> Any: try: return self.receive() except EOFError: @@ -1403,7 +1403,7 @@ def __init__( else: self.channelfactory = gw._channelfactory - def load(self, versioned: bool = False) -> object: + def load(self, versioned: bool = False) -> Any: if versioned: ver = self.stream.read(1) if ver != DUMPFORMAT_VERSION: @@ -1587,7 +1587,7 @@ def dump(byteio, obj: object) -> None: def loads( bytestring: bytes, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False -) -> object: +) -> Any: """Deserialize the given bytestring to an object. py2str_as_py3str: If true then string (str) objects previously @@ -1609,7 +1609,7 @@ def loads( def load( io: ReadIO, py2str_as_py3str: bool = False, py3str_as_py2str: bool = False -) -> object: +) -> Any: """Derserialize an object form the specified stream. Behaviour and parameters are otherwise the same as with ``loads`` @@ -1622,7 +1622,7 @@ def loads_internal( bytestring: bytes, channelfactory=None, strconfig: tuple[bool, bool] | None = None, -) -> object: +) -> Any: io = BytesIO(bytestring) return Unserializer(io, channelfactory, strconfig).load() diff --git a/src/execnet/multi.py b/src/execnet/multi.py index f9d01d90..42a2dc36 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -11,6 +11,7 @@ from functools import partial from threading import Lock from typing import TYPE_CHECKING +from typing import Any from typing import Callable from typing import Iterable from typing import Iterator @@ -277,16 +278,16 @@ def send_each(self, item: object) -> None: ch.send(item) @overload - def receive_each(self, withchannel: Literal[False] = ...) -> list[object]: + def receive_each(self, withchannel: Literal[False] = ...) -> list[Any]: pass @overload - def receive_each(self, withchannel: Literal[True]) -> list[tuple[Channel, object]]: + def receive_each(self, withchannel: Literal[True]) -> list[tuple[Channel, Any]]: pass def receive_each( self, withchannel: bool = False - ) -> list[tuple[Channel, object]] | list[object]: + ) -> list[tuple[Channel, Any]] | list[Any]: assert not hasattr(self, "_queue") l: list[object] = [] for ch in self._channels: From 50359132a4581c77243054736e98d345c964f6aa Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Thu, 4 Apr 2024 19:18:29 +0300 Subject: [PATCH 132/275] Export `DumpError`, `LoadError` These are custom exception that are raised by the public `dumps`/`loads` methods. They are used by pytest-xdist which currently imports them from `execnet.gateway_base`. --- CHANGELOG.rst | 4 ++-- src/execnet/__init__.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8f95f9a1..b050717a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,8 +15,8 @@ Also fixed ``init_popen_io`` to use ``closefd=False`` for shared stdin and stdout file descriptors, preventing ``Bad file descriptor`` errors triggered by test_stdouterrin_setnull. * The library is now typed and the typing is exposed to type-checkers. -* Re-exported ``Gateway`` and ``Channel`` from ``execnet``. The constructors - are private. +* Re-exported ``Gateway``, ``Channel``, ``DumpError`` and ``LoadError`` from + ``execnet``. The constructors are private. * Fixed ``GatewayBase.join()`` timeout argument getting ignored. * Removed support for Python 3.7. * Added official support for Python 3.12. diff --git a/src/execnet/__init__.py b/src/execnet/__init__.py index e1095e5a..5c3f20e3 100644 --- a/src/execnet/__init__.py +++ b/src/execnet/__init__.py @@ -11,6 +11,8 @@ from .gateway import Gateway from .gateway_base import Channel from .gateway_base import DataFormatError +from .gateway_base import DumpError +from .gateway_base import LoadError from .gateway_base import RemoteError from .gateway_base import TimeoutError from .gateway_base import dump @@ -41,8 +43,10 @@ "default_group", "Channel", "dumps", + "dump", + "DumpError", "loads", "load", - "dump", + "LoadError", "DataFormatError", ] From 89c99c9e367789c182cbcc33f7df461e45c2316b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 5 Apr 2024 21:14:46 +0300 Subject: [PATCH 133/275] ci: update download-artifact action to fix deploy job --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b3599dc..1ba879bf 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - name: Download Package - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: Packages path: dist From 64ecf67b02306b535eef81381d2bd659ab704685 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 5 Apr 2024 14:59:47 +0300 Subject: [PATCH 134/275] Update CHANGELOG for 2.1.0 --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b050717a..a05344bf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,4 +1,4 @@ -2.1.0 (UNRELEASED) +2.1.0 (2024-04-05) ------------------ * `#243 `__: Added ``main_thread_only`` From d08b7e72c994d9bb4b4d7cc0e9b2220d441f1dbf Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 7 Apr 2024 21:15:34 +0300 Subject: [PATCH 135/275] gateway_base: fix `load(strconfig)` getting ignored Accidental regression in 2.1.0 (c58d485ff97d401dd02516cce). Fix #267. --- CHANGELOG.rst | 6 ++++++ src/execnet/gateway_base.py | 4 +--- testing/test_serializer.py | 5 +++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a05344bf..8fee91e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +TBD +--- + +* `#267 `__ Fixed regression + in 2.1.0 where the ``strconfig`` argument to ``load``/``loads`` is ignored. + 2.1.0 (2024-04-05) ------------------ diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index f5333064..9ba25b42 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -1391,9 +1391,7 @@ def __init__( gw: BaseGateway | None = channel_or_gateway.gateway else: gw = channel_or_gateway - if channel_or_gateway is None: - strconfig = None - else: + if channel_or_gateway is not None: strconfig = channel_or_gateway._strconfig if strconfig: self.py2str_as_py3str, self.py3str_as_py2str = strconfig diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 64539399..aff41a98 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -163,3 +163,8 @@ def test_tuple_nested_with_empty_in_between(dump, load) -> None: tp, s = load(p) assert tp == "tuple" assert s == "(1, (), 3)" + + +def test_py2_string_loads() -> None: + """Regression test for #267.""" + assert execnet.loads(b"\x02M\x00\x00\x00\x01aQ") == b"a" From e1332b1b02b82bb0639e398bd1290cc2f6a3e536 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 7 Apr 2024 23:33:21 +0300 Subject: [PATCH 136/275] Update CHANGELOG for 2.1.1 --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8fee91e5..dfe36275 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,5 @@ -TBD ---- +2.1.1 (2024-04-08) +------------------ * `#267 `__ Fixed regression in 2.1.0 where the ``strconfig`` argument to ``load``/``loads`` is ignored. From 5641f2db10b0a4cb31e745537f487fcea6ef6b9e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:36:37 +0000 Subject: [PATCH 137/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eaf6fc6d..c8715af5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: - id: blacken-docs additional_dependencies: [black==22.12.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit From 3083c533ccbe2e4c08d9fe912dde73c2cf18a47d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Apr 2024 05:36:04 +0000 Subject: [PATCH 138/275] build(deps): bump hynek/build-and-inspect-python-package from 2.2 to 2.4 Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.2 to 2.4. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.2...v2.4) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1ba879bf..4b310384 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.2 + uses: hynek/build-and-inspect-python-package@v2.4 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5ead60f..74aafb48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.2 + uses: hynek/build-and-inspect-python-package@v2.4 test: From 4999a108a416ed7ae25111a6d87b2b852584c578 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 08:43:30 -0300 Subject: [PATCH 139/275] [pre-commit.ci] pre-commit autoupdate (#273) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.5 → v0.3.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.5...v0.3.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c8715af5..c9f64b34 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.3.7 hooks: - id: ruff args: [ --fix ] From 0ce9444c0df5b4de606227c1f8971d084052b099 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:27:08 -0300 Subject: [PATCH 140/275] [pre-commit.ci] pre-commit autoupdate (#275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.3.7 → v0.4.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.7...v0.4.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c9f64b34..949168d7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.7 + rev: v0.4.1 hooks: - id: ruff args: [ --fix ] From 85ccec694e4a672d9bcf596f9eee341de9510a7a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 22:11:04 +0000 Subject: [PATCH 141/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.2.6 → v2.3.0](https://github.com/codespell-project/codespell/compare/v2.2.6...v2.3.0) - [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.7) - [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.9.0...v1.10.0) --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 949168d7..c897f87d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.1 + rev: v0.4.7 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.9.0' + rev: 'v1.10.0' hooks: - id: mypy additional_dependencies: From 0399936bcda4f0f4a86313042ae8c2f3cdc5ae7d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 4 Jun 2024 10:00:07 -0300 Subject: [PATCH 142/275] Ignore "Use format specifiers instead of percent format" ruff error --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 3a7ffd44..3c304f3e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,6 +74,7 @@ ignore = [ "PLW0120", # remove the else and dedent its contents "PLW2901", # for loop variable overwritten by assignment target "PLR5501", # Use `elif` instead of `else` then `if` + "UP031", # Use format specifiers instead of percent format ] [tool.ruff.lint.isort] From 66d8dc3793458fcac0a22329f9eb6900ad5b6636 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 08:42:02 -0300 Subject: [PATCH 143/275] build(deps): bump hynek/build-and-inspect-python-package from 2.4 to 2.6 (#278) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.4 to 2.6. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.4...v2.6) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4b310384..920dd302 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.4 + uses: hynek/build-and-inspect-python-package@v2.6 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74aafb48..c2d9ed81 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.4 + uses: hynek/build-and-inspect-python-package@v2.6 test: From 4f4fcc8dbaf4cd9963678715182df72609575ec3 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 21:42:26 +0200 Subject: [PATCH 144/275] =?UTF-8?q?=F0=9F=92=85=20Add=20a=20config=20for?= =?UTF-8?q?=20the=20Chronographer=20GitHub=20App?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This app allows requiring changelog fragments to be included with each pull request. A new `skip news` label is expected to be created in the repo to match the configuration. --- .github/chronographer.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/chronographer.yml diff --git a/.github/chronographer.yml b/.github/chronographer.yml new file mode 100644 index 00000000..803db1e3 --- /dev/null +++ b/.github/chronographer.yml @@ -0,0 +1,20 @@ +--- + +branch-protection-check-name: Changelog entry +action-hints: + check-title-prefix: "Chronographer: " + external-docs-url: >- + https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests + inline-markdown: >- + See + https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests + for details. +enforce-name: + suffix: .rst +exclude: + humans: + - pyup-bot +labels: + skip-changelog: skip news + +... From 2c0d5acb415b16a81752e402bf837a0d0a29e143 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 21:44:20 +0200 Subject: [PATCH 145/275] =?UTF-8?q?=F0=9F=92=85=20Add=20a=20config=20for?= =?UTF-8?q?=20the=20Patchback=20GitHub=20App?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch prepares the project's backporting process to start being handled by the Patchback GitHub App [[1]]. It is expected that a new label like `backport 2.1.x` is created to match the configuration. [1]: https://github.com/apps/patchback --- .github/patchback.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/patchback.yml diff --git a/.github/patchback.yml b/.github/patchback.yml new file mode 100644 index 00000000..5d62fca1 --- /dev/null +++ b/.github/patchback.yml @@ -0,0 +1,7 @@ +--- + +backport_branch_prefix: patchback/backports/ +backport_label_prefix: 'backport ' # IMPORTANT: the labels are space-delimited +# target_branch_prefix: '' # The project's backport branches are non-prefixed + +... From 48ca3bb828944924b3832ac332551e216b4c7954 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:37:28 -0300 Subject: [PATCH 146/275] [pre-commit.ci] pre-commit autoupdate (#280) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c897f87d..1a1c562d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.7 + rev: v0.4.10 hooks: - id: ruff args: [ --fix ] From c93372f308a742c275a7faad483586d6550b5ca9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 07:53:27 -0300 Subject: [PATCH 147/275] [pre-commit.ci] pre-commit autoupdate (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/blacken-docs: 1.16.0 → 1.18.0](https://github.com/asottile/blacken-docs/compare/1.16.0...1.18.0) - [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0) - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- src/execnet/script/shell.py | 2 +- src/execnet/script/socketserverservice.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1a1c562d..234e80fb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs - rev: 1.16.0 + rev: 1.18.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.10 + rev: v0.5.0 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.10.0' + rev: 'v1.10.1' hooks: - id: mypy additional_dependencies: diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index 569d4042..cc851a5d 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -45,7 +45,7 @@ def clientside() -> NoReturn: class promptagent(Thread): def __init__(self, clientsock) -> None: print("server side starting") - super.__init__() # type: ignore[call-overload] + super().__init__() # type: ignore[call-overload] self.clientsock = clientsock def run(self) -> None: diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index 9c18c12c..18e375c4 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -39,7 +39,7 @@ def __init__(self, args) -> None: win32evtlogutil.AddSourceToRegistry( self._svc_display_name_, servicemanager.__file__, "Application" ) - super.__init__(args) + super().__init__(args) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) self.WAIT_TIME = 1000 # in milliseconds From 2a9697d0fe6fd02e6dd04620f91a4c6fc793c510 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:35:00 -0300 Subject: [PATCH 148/275] [pre-commit.ci] pre-commit autoupdate (#285) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 234e80fb..2da019eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 + rev: v0.5.1 hooks: - id: ruff args: [ --fix ] From 52e1839f5cb39561d94107614097189f68850b78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:20:22 -0300 Subject: [PATCH 149/275] build(deps): bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 (#281) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.14 to 1.9.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.14...v1.9.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 920dd302..4dd7ae67 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.14 + uses: pypa/gh-action-pypi-publish@v1.9.0 - name: Push tag run: | From 1c1c04b54ee61773806821a7d3cd5ebf3d26ad19 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 08:49:37 -0300 Subject: [PATCH 150/275] [pre-commit.ci] pre-commit autoupdate (#286) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2da019eb..dbf36f67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.1 + rev: v0.5.2 hooks: - id: ruff args: [ --fix ] From d709dd90bc198d738b9980f20f9cfcd993dd7a8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 09:04:52 -0300 Subject: [PATCH 151/275] build(deps): bump hynek/build-and-inspect-python-package from 2.6 to 2.7 (#287) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.6 to 2.7. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.6...v2.7) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4dd7ae67..5d45dc2d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.6 + uses: hynek/build-and-inspect-python-package@v2.7 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2d9ed81..a78b2802 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.6 + uses: hynek/build-and-inspect-python-package@v2.7 test: From ae8ae355954f2ae02ca93b0a0434ee7b496f543b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 08:11:07 -0300 Subject: [PATCH 152/275] [pre-commit.ci] pre-commit autoupdate (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.2 → v0.5.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.2...v0.5.4) - [github.com/pre-commit/mirrors-mypy: v1.10.1 → v1.11.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.1...v1.11.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbf36f67..792e31cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.2 + rev: v0.5.4 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.10.1' + rev: 'v1.11.0' hooks: - id: mypy additional_dependencies: From 64e04aed73317b86a778f8675189a620ad424450 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 08:48:13 -0300 Subject: [PATCH 153/275] build(deps): bump hynek/build-and-inspect-python-package from 2.7 to 2.8 (#289) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.7 to 2.8. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.7...v2.8) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5d45dc2d..c83200a9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.7 + uses: hynek/build-and-inspect-python-package@v2.8 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a78b2802..7dc16f13 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.7 + uses: hynek/build-and-inspect-python-package@v2.8 test: From 3ff051f18e566fd86559a56443a3bc7d9f314ba4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 21:56:09 -0300 Subject: [PATCH 154/275] [pre-commit.ci] pre-commit autoupdate (#290) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.4 → v0.5.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.4...v0.5.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 792e31cf..7182ef32 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.4 + rev: v0.5.5 hooks: - id: ruff args: [ --fix ] From 70aac4067f791c906953d4c8deda13b0175f0375 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Aug 2024 09:44:43 -0300 Subject: [PATCH 155/275] [pre-commit.ci] pre-commit autoupdate (#291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.5 → v0.5.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.5...v0.5.6) - [github.com/pre-commit/mirrors-mypy: v1.11.0 → v1.11.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.0...v1.11.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7182ef32..9e3a375e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.5 + rev: v0.5.6 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.0' + rev: 'v1.11.1' hooks: - id: mypy additional_dependencies: From 298987ebc6a24c17c394584c71616e62caf7228a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:54:20 -0300 Subject: [PATCH 156/275] [pre-commit.ci] pre-commit autoupdate (#292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.6 → v0.5.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.6...v0.5.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e3a375e..708d3f8d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.6 + rev: v0.5.7 hooks: - id: ruff args: [ --fix ] From d492bbab36f465c9b1e338a8f445b5bd0f58fdaa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:27:29 -0300 Subject: [PATCH 157/275] [pre-commit.ci] pre-commit autoupdate (#293) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.7 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.7...v0.6.2) - [github.com/pre-commit/mirrors-mypy: v1.11.1 → v1.11.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.1...v1.11.2) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- testing/conftest.py | 3 ++- testing/test_basics.py | 3 ++- testing/test_channel.py | 1 + testing/test_gateway.py | 3 ++- testing/test_multi.py | 3 ++- testing/test_rsync.py | 3 ++- testing/test_serializer.py | 3 ++- testing/test_termination.py | 5 +++-- testing/test_threadpool.py | 1 + testing/test_xspec.py | 3 ++- 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 708d3f8d..b4338513 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.6.2 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.1' + rev: 'v1.11.2' hooks: - id: mypy additional_dependencies: diff --git a/testing/conftest.py b/testing/conftest.py index c5fa4cf5..a7c2eede 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -7,8 +7,9 @@ from typing import Generator from typing import Iterator -import execnet import pytest + +import execnet from execnet.gateway import Gateway from execnet.gateway_base import ExecModel from execnet.gateway_base import WorkerPool diff --git a/testing/test_basics.py b/testing/test_basics.py index 5ed53e97..427ded3c 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -12,8 +12,9 @@ from typing import Any from typing import Callable -import execnet import pytest + +import execnet from execnet import gateway from execnet import gateway_base from execnet import gateway_io diff --git a/testing/test_channel.py b/testing/test_channel.py index f500f117..d4712277 100644 --- a/testing/test_channel.py +++ b/testing/test_channel.py @@ -7,6 +7,7 @@ import time import pytest + from execnet.gateway import Gateway from execnet.gateway_base import Channel diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 4291404e..257fd941 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -12,8 +12,9 @@ from textwrap import dedent from typing import Callable -import execnet import pytest + +import execnet from execnet import gateway_base from execnet import gateway_io from execnet.gateway import Gateway diff --git a/testing/test_multi.py b/testing/test_multi.py index edc93037..e1dc40d1 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -8,8 +8,9 @@ from time import sleep from typing import Callable -import execnet import pytest + +import execnet from execnet import XSpec from execnet.gateway import Gateway from execnet.gateway_base import Channel diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 6f40bc44..40af7ca2 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -4,8 +4,9 @@ import sys import types -import execnet import pytest + +import execnet from execnet import RSync from execnet.gateway import Gateway diff --git a/testing/test_serializer.py b/testing/test_serializer.py index aff41a98..4c78f1ce 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -5,9 +5,10 @@ import sys from pathlib import Path -import execnet import pytest +import execnet + # We use the execnet folder in order to avoid triggering a missing apipkg. pyimportdir = os.fspath(Path(execnet.__file__).parent) diff --git a/testing/test_termination.py b/testing/test_termination.py index 44bd1a52..83f0b980 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -6,12 +6,13 @@ import sys from typing import Callable -import execnet import pytest +from test_gateway import TESTTIMEOUT + +import execnet from execnet.gateway import Gateway from execnet.gateway_base import ExecModel from execnet.gateway_base import WorkerPool -from test_gateway import TESTTIMEOUT execnetdir = pathlib.Path(execnet.__file__).parent.parent diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index 480282d9..becc401c 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -2,6 +2,7 @@ from pathlib import Path import pytest + from execnet.gateway_base import ExecModel from execnet.gateway_base import WorkerPool diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 4c9ff8d6..eaf30e1d 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -7,8 +7,9 @@ from pathlib import Path from typing import Callable -import execnet import pytest + +import execnet from execnet import XSpec from execnet.gateway import Gateway from execnet.gateway_io import popen_args From 457b5e7710775b581a323b395105dca7a4066679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:00:14 -0300 Subject: [PATCH 158/275] build(deps): bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0 (#294) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c83200a9..6d77a974 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.9.0 + uses: pypa/gh-action-pypi-publish@v1.10.0 - name: Push tag run: | From f3fc5ee70cf384176ede0ad4c7e6a81cc2a94067 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 21:30:22 -0300 Subject: [PATCH 159/275] [pre-commit.ci] pre-commit autoupdate (#295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.2 → v0.6.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.2...v0.6.3) - [github.com/PyCQA/doc8: v1.1.1 → v1.1.2](https://github.com/PyCQA/doc8/compare/v1.1.1...v1.1.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4338513..08654c9a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,14 +13,14 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.2 + rev: v0.6.3 hooks: - id: ruff args: [ --fix ] exclude: "^doc/" - id: ruff-format - repo: https://github.com/PyCQA/doc8 - rev: 'v1.1.1' + rev: 'v1.1.2' hooks: - id: doc8 args: ["--ignore", "D001"] From cc6204e561b26e08d710747dc4fb56a481efbbc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:18:55 -0300 Subject: [PATCH 160/275] build(deps): bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.1 (#296) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.0 to 1.10.1. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.0...v1.10.1) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6d77a974..3a7b9468 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.10.0 + uses: pypa/gh-action-pypi-publish@v1.10.1 - name: Push tag run: | From d83d25b25f03a1223ef7e070f3681c15aff036a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 20:28:03 -0300 Subject: [PATCH 161/275] [pre-commit.ci] pre-commit autoupdate (#297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.3 → v0.6.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.3...v0.6.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 08654c9a..d7982d5a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.6.4 hooks: - id: ruff args: [ --fix ] From 774e2b5e79bf9c9f3cedddba150418a1dff957db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:53:11 -0300 Subject: [PATCH 162/275] build(deps): bump hynek/build-and-inspect-python-package from 2.8 to 2.9 (#298) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.8 to 2.9. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.8...v2.9) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3a7b9468..f9903f8d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.8 + uses: hynek/build-and-inspect-python-package@v2.9 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7dc16f13..731930a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.8 + uses: hynek/build-and-inspect-python-package@v2.9 test: From acc2c6fcd39cc7e2e1b00e2cc80debce2278eaa9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 19:46:44 -0300 Subject: [PATCH 163/275] [pre-commit.ci] pre-commit autoupdate (#299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.4 → v0.6.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.4...v0.6.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d7982d5a..2cba6f85 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.4 + rev: v0.6.5 hooks: - id: ruff args: [ --fix ] From 95ee5c3bdb4d986602ecd33f913d5cbe7ea96743 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:06:38 -0300 Subject: [PATCH 164/275] build(deps): bump pypa/gh-action-pypi-publish from 1.10.1 to 1.10.2 (#300) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.1 to 1.10.2. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.1...v1.10.2) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f9903f8d..a8d809d6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.10.1 + uses: pypa/gh-action-pypi-publish@v1.10.2 - name: Push tag run: | From 4dc8c3989f73d5a02d237a56ef0fe18a864386f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:19:40 -0300 Subject: [PATCH 165/275] [pre-commit.ci] pre-commit autoupdate (#301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.5 → v0.6.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.5...v0.6.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2cba6f85..43d7fb30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.5 + rev: v0.6.7 hooks: - id: ruff args: [ --fix ] From 4686dba92988a64baa8c5bc378bccf0851cb0e26 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 07:08:48 -0300 Subject: [PATCH 166/275] [pre-commit.ci] pre-commit autoupdate (#302) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.7 → v0.6.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.7...v0.6.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43d7fb30..b18b4adb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.7 + rev: v0.6.8 hooks: - id: ruff args: [ --fix ] From 9555f26781c1da46252d828623a4ba503f85806f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 07:49:52 -0300 Subject: [PATCH 167/275] build(deps): bump pypa/gh-action-pypi-publish from 1.10.2 to 1.10.3 (#303) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.2 to 1.10.3. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.2...v1.10.3) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a8d809d6..b1d7b11e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.10.2 + uses: pypa/gh-action-pypi-publish@v1.10.3 - name: Push tag run: | From 8e9da514ecc35c1bcc6399a365ede68c30321f75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 06:59:38 -0300 Subject: [PATCH 168/275] [pre-commit.ci] pre-commit autoupdate (#304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/blacken-docs: 1.18.0 → 1.19.0](https://github.com/asottile/blacken-docs/compare/1.18.0...1.19.0) - [github.com/pre-commit/pre-commit-hooks: v4.6.0 → v5.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.6.0...v5.0.0) - [github.com/astral-sh/ruff-pre-commit: v0.6.8 → v0.6.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.8...v0.6.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b18b4adb..115a53d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,16 +4,16 @@ repos: hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs - rev: 1.18.0 + rev: 1.19.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.6.9 hooks: - id: ruff args: [ --fix ] From 6c59a7c4f2c7fa8caacafee3fa362b66b5fb9e08 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Oct 2024 07:27:54 -0300 Subject: [PATCH 169/275] [pre-commit.ci] pre-commit autoupdate (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.0) - [github.com/pre-commit/mirrors-mypy: v1.11.2 → v1.12.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.11.2...v1.12.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 115a53d8..2e3c78f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.0 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.11.2' + rev: 'v1.12.1' hooks: - id: mypy additional_dependencies: From 28cfae9c545230905afd6b35a4c940c89ec83768 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 21:50:06 -0300 Subject: [PATCH 170/275] [pre-commit.ci] pre-commit autoupdate (#308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/blacken-docs: 1.19.0 → 1.19.1](https://github.com/asottile/blacken-docs/compare/1.19.0...1.19.1) - [github.com/astral-sh/ruff-pre-commit: v0.7.0 → v0.7.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.0...v0.7.1) - [github.com/pre-commit/mirrors-mypy: v1.12.1 → v1.13.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.12.1...v1.13.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e3c78f6..a9222940 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs - rev: 1.19.0 + rev: 1.19.1 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.0 + rev: v0.7.1 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.12.1' + rev: 'v1.13.0' hooks: - id: mypy additional_dependencies: From ad428bdafec91a12bb4c1aa95725fb06dd3d9ece Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 23:19:23 +0000 Subject: [PATCH 171/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.1 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.1...v0.7.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9222940..b699995b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.7.2 hooks: - id: ruff args: [ --fix ] From 214228dc24ab3d82b90a8ce0daace65a4d652f72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:26:11 -0300 Subject: [PATCH 172/275] build(deps): bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0 (#310) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.3 to 1.11.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.3...v1.11.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b1d7b11e..1ffff26a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.10.3 + uses: pypa/gh-action-pypi-publish@v1.11.0 - name: Push tag run: | From f5c4acf186acfb137ae8d53919c46bfa1ae0cafa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 08:26:21 -0300 Subject: [PATCH 173/275] build(deps): bump hynek/build-and-inspect-python-package (#309) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.9 to 2.10. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.9...v2.10) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1ffff26a..f4e04ae0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.9 + uses: hynek/build-and-inspect-python-package@v2.10 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 731930a8..2dab475d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.9 + uses: hynek/build-and-inspect-python-package@v2.10 test: From c7babf927902870103e42a1e20cf9bf449def395 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:12:14 -0300 Subject: [PATCH 174/275] build(deps): bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2 (#312) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.11.0 to 1.12.2. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.11.0...v1.12.2) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f4e04ae0..d1442719 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.11.0 + uses: pypa/gh-action-pypi-publish@v1.12.2 - name: Push tag run: | From 314329baa03627d6a1c9dc69b9274ce24b5faa07 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 23:01:00 +0000 Subject: [PATCH 175/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.7.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.7.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b699995b..d243a94c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.2 + rev: v0.7.3 hooks: - id: ruff args: [ --fix ] From 98c70bccce6e66b532fa9a107949d4dcc775bca1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:38:42 -0300 Subject: [PATCH 176/275] [pre-commit.ci] pre-commit autoupdate (#314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.3 → v0.7.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.3...v0.7.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d243a94c..d4be101e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.3 + rev: v0.7.4 hooks: - id: ruff args: [ --fix ] From 8c77a081ef7aae878e274bfb60972b8da852440a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 07:51:03 -0300 Subject: [PATCH 177/275] [pre-commit.ci] pre-commit autoupdate (#315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.4 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.4...v0.8.1) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- src/execnet/__init__.py | 26 +++++++++++++------------- src/execnet/gateway.py | 2 +- src/execnet/gateway_base.py | 4 ++-- src/execnet/multi.py | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d4be101e..f2080f14 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.1 hooks: - id: ruff args: [ --fix ] diff --git a/src/execnet/__init__.py b/src/execnet/__init__.py index 5c3f20e3..1edf3254 100644 --- a/src/execnet/__init__.py +++ b/src/execnet/__init__.py @@ -29,24 +29,24 @@ from .xspec import XSpec __all__ = [ - "__version__", - "makegateway", - "set_execmodel", - "HostNotFound", - "RemoteError", - "TimeoutError", - "XSpec", + "Channel", + "DataFormatError", + "DumpError", "Gateway", "Group", + "HostNotFound", + "LoadError", "MultiChannel", "RSync", + "RemoteError", + "TimeoutError", + "XSpec", + "__version__", "default_group", - "Channel", - "dumps", "dump", - "DumpError", - "loads", + "dumps", "load", - "LoadError", - "DataFormatError", + "loads", + "makegateway", + "set_execmodel", ] diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 547be291..645de0fc 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -40,7 +40,7 @@ def remoteaddress(self) -> str: def __repr__(self) -> str: """A string representing gateway type and status.""" try: - r: str = self.hasreceiver() and "receive-live" or "not-receiving" + r: str = (self.hasreceiver() and "receive-live") or "not-receiving" i = str(len(self._channelfactory.channels())) except AttributeError: r = "uninitialized" diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 9ba25b42..22756778 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -760,7 +760,7 @@ def setcallback( callback(olditem) def __repr__(self) -> str: - flag = self.isclosed() and "closed" or "open" + flag = (self.isclosed() and "closed") or "open" return "" % (self.id, flag) def __del__(self) -> None: @@ -1079,7 +1079,7 @@ def close(self) -> None: self.channel.close() def __repr__(self) -> str: - state = self.channel.isclosed() and "closed" or "open" + state = (self.channel.isclosed() and "closed") or "open" return "" % (self.channel.id, state) diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 42a2dc36..90742f26 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -177,7 +177,7 @@ def makegateway(self, spec: XSpec | str | None = None) -> Gateway: os.environ[name] = value """ ) - nice = spec.nice and int(spec.nice) or 0 + nice = (spec.nice and int(spec.nice)) or 0 channel.send((spec.chdir, nice, spec.env)) channel.waitclose() return gw From 085bafe5ae6ff74ee9d7390ef1994f331a5bfa29 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 07:48:29 -0300 Subject: [PATCH 178/275] [pre-commit.ci] pre-commit autoupdate (#316) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2080f14..61d840fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.1 + rev: v0.8.2 hooks: - id: ruff args: [ --fix ] From e9ae7ac8aa965676d8bb6404756c193b28bc3fd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 07:58:16 -0300 Subject: [PATCH 179/275] build(deps): bump hynek/build-and-inspect-python-package (#317) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.10 to 2.11. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.10...v2.11) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d1442719..47cf6c3f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.10 + uses: hynek/build-and-inspect-python-package@v2.11 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2dab475d..d9969c95 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.10 + uses: hynek/build-and-inspect-python-package@v2.11 test: From 17ae32abe23b527eac424ff68f76e9fd188d53bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:53:15 -0300 Subject: [PATCH 180/275] build(deps): bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3 (#318) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.2 to 1.12.3. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.2...v1.12.3) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 47cf6c3f..682698c0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.12.2 + uses: pypa/gh-action-pypi-publish@v1.12.3 - name: Push tag run: | From a676e450de377820dfc2b4e87d21cd0703bbc844 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Dec 2024 06:35:00 -0300 Subject: [PATCH 181/275] [pre-commit.ci] pre-commit autoupdate (#319) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.2 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.2...v0.8.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 61d840fd..51eba432 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.2 + rev: v0.8.3 hooks: - id: ruff args: [ --fix ] From 30a12ed1b803d42b8fc5685bda7ae6ee9447d80e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Dec 2024 12:57:49 -0300 Subject: [PATCH 182/275] [pre-commit.ci] pre-commit autoupdate (#321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.8.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.3...v0.8.4) - [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 51eba432..525a549d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.3 + rev: v0.8.4 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.13.0' + rev: 'v1.14.0' hooks: - id: mypy additional_dependencies: From 2f4578fd5329751e86147c5a9420fddc62da8ba2 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 7 Jan 2025 12:31:38 +0200 Subject: [PATCH 183/275] ci: update actions/download-artifact to fix vulnerability Fix this vulnerability: https://github.com/advisories/GHSA-6q32-hq47-5qq3 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d9969c95..5a4c79ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v4.1.4 + uses: actions/download-artifact@v4 with: name: Packages path: dist From 31e039e52feb31516742ce1598aa9d81071ad826 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:00:27 -0300 Subject: [PATCH 184/275] [pre-commit.ci] pre-commit autoupdate (#322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6) - [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.0...v1.14.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 525a549d..109f8bf6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.8.6 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.14.0' + rev: 'v1.14.1' hooks: - id: mypy additional_dependencies: From 1440cdc80066b5082cb3c2ba2df986c1689e4a1c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:05:06 +0000 Subject: [PATCH 185/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.9.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.9.1) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 109f8bf6..285e4a9d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.1 hooks: - id: ruff args: [ --fix ] From 76f118c55c0ed3bc1f98110836d511bab5374e55 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:05:18 +0000 Subject: [PATCH 186/275] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/example/sysinfo.py | 5 ++--- src/execnet/gateway_base.py | 4 ++-- src/execnet/multi.py | 2 +- src/execnet/rsync.py | 2 +- testing/conftest.py | 6 ++---- testing/test_gateway.py | 6 +++--- testing/test_xspec.py | 5 ++--- 7 files changed, 13 insertions(+), 17 deletions(-) diff --git a/doc/example/sysinfo.py b/doc/example/sysinfo.py index b3fa2d5e..927b57c0 100644 --- a/doc/example/sysinfo.py +++ b/doc/example/sysinfo.py @@ -19,8 +19,7 @@ action="store", dest="ssh_config", default=None, - help="use given ssh config file," - " and add info all contained hosts for getting info", + help="use given ssh config file, and add info all contained hosts for getting info", ) parser.add_option( "-i", @@ -28,7 +27,7 @@ action="store", dest="ignores", default=None, - help="ignore hosts " "(useful if the list of hostnames come from a file list)", + help="ignore hosts (useful if the list of hostnames come from a file list)", ) diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 22756778..60fa783c 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -1237,7 +1237,7 @@ def _terminate_execution(self) -> None: self._trace("shutting down execution pool") self._execpool.trigger_shutdown() if not self._execpool.waitall(5.0): - self._trace("execution ongoing after 5 secs," " trying interrupt_main") + self._trace("execution ongoing after 5 secs, trying interrupt_main") # We try hard to terminate execution based on the assumption # that there is only one gateway object running per-process. if sys.platform != "win32": @@ -1248,7 +1248,7 @@ def _terminate_execution(self) -> None: interrupt_main() if not self._execpool.waitall(10.0): self._trace( - "execution did not finish in another 10 secs, " "calling os._exit()" + "execution did not finish in another 10 secs, calling os._exit()" ) os._exit(1) diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 90742f26..c4f35813 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -82,7 +82,7 @@ def set_execmodel( """ if self._gateways: raise ValueError( - "can not set execution models if " "gateways have been created already" + "can not set execution models if gateways have been created already" ) if remote_execmodel is None: remote_execmodel = execmodel diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 84820532..2b5698d8 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -125,7 +125,7 @@ def send(self, raises: bool = True) -> None: if not self._channels: if raises: raise OSError( - "no targets available, maybe you " "are trying call send() twice?" + "no targets available, maybe you are trying call send() twice?" ) return # normalize a trailing '/' away diff --git a/testing/conftest.py b/testing/conftest.py index a7c2eede..d9667ac1 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -162,8 +162,7 @@ def gw( proxygw = group.makegateway("popen//id=%s" % pname) # assert group['proxygw'].remote_status().receiving gw = group.makegateway( - f"socket//id=socket//installvia={pname}" - f"//execmodel={execmodel.backend}" + f"socket//id=socket//installvia={pname}//execmodel={execmodel.backend}" ) # TODO(typing): Clarify this assignment. gw.proxygw = proxygw # type: ignore[attr-defined] @@ -176,8 +175,7 @@ def gw( elif request.param == "proxy": group.makegateway("popen//id=proxy-transport") gw = group.makegateway( - "popen//via=proxy-transport//id=proxy" - "//execmodel=%s" % execmodel.backend + "popen//via=proxy-transport//id=proxy//execmodel=%s" % execmodel.backend ) else: assert 0, f"unknown execmodel: {request.param}" diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 257fd941..8df5f21e 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -509,9 +509,9 @@ def test_popen_stderr_tracing( gw.exit() def test_no_tracing_by_default(self): - assert ( - gateway_base.trace == gateway_base.notrace - ), "trace does not to default to empty tracing" + assert gateway_base.trace == gateway_base.notrace, ( + "trace does not to default to empty tracing" + ) @pytest.mark.parametrize( diff --git a/testing/test_xspec.py b/testing/test_xspec.py index eaf30e1d..3e5041be 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -42,7 +42,7 @@ def test_norm_attributes(self) -> None: assert spec.chdir is None assert spec.nice == "3" - spec = XSpec("ssh=user@host" "//chdir=/hello/this//python=/usr/bin/python2.5") + spec = XSpec("ssh=user@host//chdir=/hello/this//python=/usr/bin/python2.5") assert spec.ssh == "user@host" assert spec.python == "/usr/bin/python2.5" assert spec.chdir == "/hello/this" @@ -56,8 +56,7 @@ def test_ssh_options(self) -> None: assert spec.python == "python3" spec = XSpec( - "ssh=-i ~/.ssh/id_rsa-passwordless_login -p 22100 user@host" - "//python=python3" + "ssh=-i ~/.ssh/id_rsa-passwordless_login -p 22100 user@host//python=python3" ) assert spec.ssh == "-i ~/.ssh/id_rsa-passwordless_login -p 22100 user@host" assert spec.python == "python3" From fb4a7d4aa7e97530f84eba08d7b82dc0a952e763 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 14 Jan 2025 06:29:35 -0300 Subject: [PATCH 187/275] Add required sphinx conf path to .readthedocs.yaml This is now required: https://about.readthedocs.com/blog/2024/12/deprecate-config-files-without-sphinx-or-mkdocs-config/ --- .readthedocs.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 516834fe..0697e236 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,3 +9,6 @@ python: install: - method: pip path: . + +sphinx: + configuration: doc/conf.py From 54c3a2eb8fbbb7f01d20bd39bf61bd9f1e6ab940 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 23:00:39 +0000 Subject: [PATCH 188/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.1 → v0.9.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.1...v0.9.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 285e4a9d..622abb0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.1 + rev: v0.9.2 hooks: - id: ruff args: [ --fix ] From 156dd87d6dadc9c5be44b0bd28a670966b8ea9f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 05:33:41 +0000 Subject: [PATCH 189/275] build(deps): bump pypa/gh-action-pypi-publish from 1.12.3 to 1.12.4 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.3 to 1.12.4. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.3...v1.12.4) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 682698c0..c9aa506a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.12.3 + uses: pypa/gh-action-pypi-publish@v1.12.4 - name: Push tag run: | From 4527f39c16e6852e212a1d0d5fb777db918b5733 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 05:33:42 +0000 Subject: [PATCH 190/275] build(deps): bump hynek/build-and-inspect-python-package Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.11 to 2.12. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.11...v2.12) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 682698c0..cc607dee 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.11 + uses: hynek/build-and-inspect-python-package@v2.12 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a4c79ad..c1d8d1ea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.11 + uses: hynek/build-and-inspect-python-package@v2.12 test: From 8bad0304d44255ceb684eff6a2153cc01293f53c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 23:08:14 +0000 Subject: [PATCH 191/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.3.0 → v2.4.0](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.0) - [github.com/astral-sh/ruff-pre-commit: v0.9.2 → v0.9.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.2...v0.9.3) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 622abb0f..6ce66675 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.0 hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.2 + rev: v0.9.3 hooks: - id: ruff args: [ --fix ] From 500792be8bff734fe811d6ad4fa63cc27ab763b5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 31 Jan 2025 19:34:40 -0300 Subject: [PATCH 192/275] Fix typo: reused --- src/execnet/script/socketserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index b58d1a4c..55af5481 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -80,7 +80,7 @@ def bind_and_listen(hostport: str | tuple[str, int], execmodel: ExecModel): if hasattr(fcntl, "FD_CLOEXEC"): old = fcntl.fcntl(serversock.fileno(), fcntl.F_GETFD) fcntl.fcntl(serversock.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC) - # allow the address to be re-used in a reasonable amount of time + # allow the address to be reused in a reasonable amount of time if os.name == "posix" and sys.platform != "cygwin": serversock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) From 2332277fde5e5f6ca5c478a4b811fdbbd8d147ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 20:36:54 -0300 Subject: [PATCH 193/275] [pre-commit.ci] pre-commit autoupdate (#329) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.4.0 → v2.4.1](https://github.com/codespell-project/codespell/compare/v2.4.0...v2.4.1) - [github.com/astral-sh/ruff-pre-commit: v0.9.3 → v0.9.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.3...v0.9.6) - [github.com/pre-commit/mirrors-mypy: v1.14.1 → v1.15.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.1...v1.15.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ce66675..eef95e1b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.4.0 + rev: v2.4.1 hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.3 + rev: v0.9.6 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.14.1' + rev: 'v1.15.0' hooks: - id: mypy additional_dependencies: From 8c9564f2e2a21e23a810d84ca554d4224cee9c58 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 21:30:40 -0300 Subject: [PATCH 194/275] [pre-commit.ci] pre-commit autoupdate (#330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.9.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.9.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eef95e1b..d1836459 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.6 + rev: v0.9.7 hooks: - id: ruff args: [ --fix ] From b0b5e4c3391865985f27cbb1e8b20af0f34d9423 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:03:18 -0300 Subject: [PATCH 195/275] [pre-commit.ci] pre-commit autoupdate (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1836459..4b80653c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.7 + rev: v0.9.9 hooks: - id: ruff args: [ --fix ] From 442781d14f3fb855624aa357514a9d4c47bdb795 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Mar 2025 10:04:05 -0300 Subject: [PATCH 196/275] [pre-commit.ci] pre-commit autoupdate (#332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4b80653c..9be7e639 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.9 + rev: v0.9.10 hooks: - id: ruff args: [ --fix ] From 3121cb883dd9275371236f9870105c59ac2a4bc8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 20:20:32 +0000 Subject: [PATCH 197/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9be7e639..c6d05943 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.9.10 + rev: v0.11.0 hooks: - id: ruff args: [ --fix ] From ee72599fe0b789478ff897fb2d86f491ef9ec178 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 17:49:46 -0300 Subject: [PATCH 198/275] [pre-commit.ci] pre-commit autoupdate (#334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c6d05943..0d9f96cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.0 + rev: v0.11.2 hooks: - id: ruff args: [ --fix ] From 87050a28938a3a232a14c1007d2264e82c840259 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 07:43:54 -0300 Subject: [PATCH 199/275] [pre-commit.ci] pre-commit autoupdate (#335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0d9f96cb..487b5d05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.2 + rev: v0.11.4 hooks: - id: ruff args: [ --fix ] From 3cf2141eddacea5eae719c7150d7d108a20286de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 21:32:04 -0300 Subject: [PATCH 200/275] [pre-commit.ci] pre-commit autoupdate (#337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 487b5d05..cdd6d516 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.11.5 hooks: - id: ruff args: [ --fix ] From 18d50d6f7da32cd83840cf718b9bdc9fafeeb792 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 22:47:08 -0300 Subject: [PATCH 201/275] [pre-commit.ci] pre-commit autoupdate (#338) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.5 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.5...v0.11.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cdd6d516..21e03f1a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.5 + rev: v0.11.6 hooks: - id: ruff args: [ --fix ] From c549bc94790e79c7f956a69cbb1ef87443056ea2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 20:18:23 -0300 Subject: [PATCH 202/275] [pre-commit.ci] pre-commit autoupdate (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21e03f1a..87f0a45a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.6 + rev: v0.11.7 hooks: - id: ruff args: [ --fix ] From 5c3ac173d42406747d4b209a9a7f5d1a8328e1ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 08:01:33 -0300 Subject: [PATCH 203/275] [pre-commit.ci] pre-commit autoupdate (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 87f0a45a..6e145be7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.7 + rev: v0.11.8 hooks: - id: ruff args: [ --fix ] From e7339806fda598660af20e5f495e733d7df01ed1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 23:04:29 -0300 Subject: [PATCH 204/275] [pre-commit.ci] pre-commit autoupdate (#341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6e145be7..1571e532 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.8 + rev: v0.11.9 hooks: - id: ruff args: [ --fix ] From d0ca25a8ea83435f2ff7105129a97ce6a40f6dd8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 09:25:40 -0300 Subject: [PATCH 205/275] [pre-commit.ci] pre-commit autoupdate (#342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.9 → v0.11.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.9...v0.11.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1571e532..7f6c2e0f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.9 + rev: v0.11.10 hooks: - id: ruff args: [ --fix ] From 439dab2616431c31b029747a7d1823064bfe9514 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 18:45:54 -0300 Subject: [PATCH 206/275] [pre-commit.ci] pre-commit autoupdate (#344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.10 → v0.11.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.10...v0.11.11) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7f6c2e0f..cb5f4400 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.10 + rev: v0.11.11 hooks: - id: ruff args: [ --fix ] From e0d703541453d4d7a623e749184b73440f63d525 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:41:10 -0300 Subject: [PATCH 207/275] [pre-commit.ci] pre-commit autoupdate (#345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.11 → v0.11.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.11...v0.11.12) - [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.16.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.16.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb5f4400..d3223520 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.11 + rev: v0.11.12 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.15.0' + rev: 'v1.16.0' hooks: - id: mypy additional_dependencies: From 9992933237d9a2b869a0fe91a1054cf88ab9e62b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:31:32 -0300 Subject: [PATCH 208/275] [pre-commit.ci] pre-commit autoupdate (#346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.12 → v0.11.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.12...v0.11.13) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3223520..441fa22c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.12 + rev: v0.11.13 hooks: - id: ruff args: [ --fix ] From 30652d249c7336363a05804a5c111a26cf5c4ce4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 17:43:25 -0300 Subject: [PATCH 209/275] [pre-commit.ci] pre-commit autoupdate (#347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/doc8: v1.1.2 → v2.0.0](https://github.com/PyCQA/doc8/compare/v1.1.2...v2.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 441fa22c..aba03d66 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: exclude: "^doc/" - id: ruff-format - repo: https://github.com/PyCQA/doc8 - rev: 'v1.1.2' + rev: 'v2.0.0' hooks: - id: doc8 args: ["--ignore", "D001"] From 5108b5e454ea4cd4bc32712269135498fe092127 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:56:35 -0300 Subject: [PATCH 210/275] build(deps): bump hynek/build-and-inspect-python-package (#348) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.12 to 2.13. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.12...v2.13) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-version: '2.13' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7a5c05d1..648097dc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.12 + uses: hynek/build-and-inspect-python-package@v2.13 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1d8d1ea..4fced055 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.12 + uses: hynek/build-and-inspect-python-package@v2.13 test: From 89cb1d52216a15a8b2095bb535ab95cf520964fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 08:22:33 -0300 Subject: [PATCH 211/275] [pre-commit.ci] pre-commit autoupdate (#349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.11.13 → v0.12.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.13...v0.12.0) - [github.com/pre-commit/mirrors-mypy: v1.16.0 → v1.16.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.16.0...v1.16.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aba03d66..eaf90f37 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.0 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.16.0' + rev: 'v1.16.1' hooks: - id: mypy additional_dependencies: From 84dd0d33b039155abc590841c9945413b4aa5ed3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 18:34:23 -0300 Subject: [PATCH 212/275] [pre-commit.ci] pre-commit autoupdate (#350) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.0 → v0.12.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.0...v0.12.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eaf90f37..f6493669 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.0 + rev: v0.12.1 hooks: - id: ruff args: [ --fix ] From e5b177a6daa9c8b546ebb0af85db79ad41c35718 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 11:42:19 -0300 Subject: [PATCH 213/275] [pre-commit.ci] pre-commit autoupdate (#351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.1 → v0.12.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.1...v0.12.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6493669..89127a2a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.1 + rev: v0.12.2 hooks: - id: ruff args: [ --fix ] From 0ae3add1ec62c0349957385349bbd37382456460 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:34:07 -0300 Subject: [PATCH 214/275] [pre-commit.ci] pre-commit autoupdate (#352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.2 → v0.12.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.2...v0.12.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 89127a2a..2953d9de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.2 + rev: v0.12.3 hooks: - id: ruff args: [ --fix ] From f225efb2b8358b9608a40e7ff71d515e635f29eb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 08:25:47 -0300 Subject: [PATCH 215/275] [pre-commit.ci] pre-commit autoupdate (#353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.3 → v0.12.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.3...v0.12.5) - [github.com/pre-commit/mirrors-mypy: v1.16.1 → v1.17.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.16.1...v1.17.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2953d9de..e214140a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.3 + rev: v0.12.5 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.16.1' + rev: 'v1.17.0' hooks: - id: mypy additional_dependencies: From db4caef02cf050a0818c14fc219c0a4e10abce37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:55:33 -0300 Subject: [PATCH 216/275] [pre-commit.ci] pre-commit autoupdate (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.5 → v0.12.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.5...v0.12.7) - [github.com/pre-commit/mirrors-mypy: v1.17.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.0...v1.17.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e214140a..066b0d94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.5 + rev: v0.12.7 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.17.0' + rev: 'v1.17.1' hooks: - id: mypy additional_dependencies: From ff175e652a018e043817a0d176abf914d8c98006 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:22:40 -0300 Subject: [PATCH 217/275] build(deps): bump actions/download-artifact from 4 to 5 (#355) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 648097dc..2299af84 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - name: Download Package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fced055..bf836a63 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: name: Packages path: dist From 3d459e5e1c0145affac5cd67bd498b90aabee792 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:10:34 -0300 Subject: [PATCH 218/275] [pre-commit.ci] pre-commit autoupdate (#356) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0) - [github.com/astral-sh/ruff-pre-commit: v0.12.7 → v0.12.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.7...v0.12.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 066b0d94..3804301a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,11 +9,11 @@ repos: - id: blacken-docs additional_dependencies: [black==22.12.0] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.7 + rev: v0.12.8 hooks: - id: ruff args: [ --fix ] From 48109a13a790042307f8a99129d84fe3b99c128d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:15:05 -0300 Subject: [PATCH 219/275] build(deps): bump actions/checkout from 4 to 5 (#357) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2299af84..4209c7f2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,7 @@ jobs: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.13 @@ -30,7 +30,7 @@ jobs: contents: write # For tag. steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Download Package uses: actions/download-artifact@v5 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf836a63..50550e55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.13 @@ -37,7 +37,7 @@ jobs: python: [ "3.8","3.10","3.11","3.12", "pypy-3.8" ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 From bb3709339a44a08c921623b8ffbc2db34a13a291 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:00:48 -0300 Subject: [PATCH 220/275] [pre-commit.ci] pre-commit autoupdate (#358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.8 → v0.12.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.8...v0.12.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3804301a..c5834d33 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.8 + rev: v0.12.9 hooks: - id: ruff args: [ --fix ] From 6cd38a8359239aaa60d63ecf96c3e5da7c8bef71 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 19:39:36 -0300 Subject: [PATCH 221/275] [pre-commit.ci] pre-commit autoupdate (#359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.9 → v0.12.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.9...v0.12.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c5834d33..371f8b02 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.9 + rev: v0.12.10 hooks: - id: ruff args: [ --fix ] From b04c3bb90f90f178e2ea56ff5ed34f672b7a252e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 06:45:00 -0300 Subject: [PATCH 222/275] [pre-commit.ci] pre-commit autoupdate (#360) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.10 → v0.12.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.10...v0.12.11) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 371f8b02..6235bc42 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.10 + rev: v0.12.11 hooks: - id: ruff args: [ --fix ] From cf2981863834852d10730235072ccf21f7b3cd55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:05:29 -0300 Subject: [PATCH 223/275] build(deps): bump pypa/gh-action-pypi-publish in /.github/workflows (#361) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.4 to 1.13.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.4...v1.13.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.13.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4209c7f2..d9af656a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.12.4 + uses: pypa/gh-action-pypi-publish@v1.13.0 - name: Push tag run: | From c5726ed77f495f8dd9fe6d8913de8f691ef881c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 07:57:59 -0300 Subject: [PATCH 224/275] build(deps): bump actions/setup-python from 5 to 6 (#362) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 50550e55..da22c41a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: path: dist - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} From 2fa3fa66311ef503c20b185d1a01ead4930a1f1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 18:48:05 -0300 Subject: [PATCH 225/275] [pre-commit.ci] pre-commit autoupdate (#363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/blacken-docs: 1.19.1 → 1.20.0](https://github.com/asottile/blacken-docs/compare/1.19.1...1.20.0) - [github.com/astral-sh/ruff-pre-commit: v0.12.11 → v0.12.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.11...v0.12.12) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6235bc42..5e1fe5d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ repos: hooks: - id: codespell - repo: https://github.com/asottile/blacken-docs - rev: 1.19.1 + rev: 1.20.0 hooks: - id: blacken-docs additional_dependencies: [black==22.12.0] @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.11 + rev: v0.12.12 hooks: - id: ruff args: [ --fix ] From b76236d029340ffe079b9b7d8493a2f592057764 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:27:47 -0300 Subject: [PATCH 226/275] [pre-commit.ci] pre-commit autoupdate (#364) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.12.12 → v0.13.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.12...v0.13.0) - [github.com/pre-commit/mirrors-mypy: v1.17.1 → v1.18.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.17.1...v1.18.1) Co-authored-by: Bruno Oliveira --- .pre-commit-config.yaml | 4 ++-- src/execnet/gateway_base.py | 4 ++-- src/execnet/script/shell.py | 2 +- testing/test_gateway.py | 2 +- testing/test_rsync.py | 2 +- testing/test_serializer.py | 2 +- testing/test_termination.py | 4 ++-- testing/test_threadpool.py | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e1fe5d1..89e836b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.12 + rev: v0.13.0 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.17.1' + rev: 'v1.18.1' hooks: - id: mypy additional_dependencies: diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 60fa783c..bdee89af 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -1007,7 +1007,7 @@ def _no_longer_opened(self, id: int) -> None: except KeyError: pass try: - callback, endmarker, strconfig = self._callbacks.pop(id) + callback, endmarker, _strconfig = self._callbacks.pop(id) except KeyError: pass else: @@ -1037,7 +1037,7 @@ def _local_receive(self, id: int, data) -> None: # executes in receiver thread channel = self._channels.get(id) try: - callback, endmarker, strconfig = self._callbacks[id] + callback, _endmarker, strconfig = self._callbacks[id] except KeyError: queue = channel._items if channel is not None else None if queue is None: diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index cc851a5d..d37a48c2 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -26,7 +26,7 @@ def clientside() -> NoReturn: inputlist = [sock, sys.stdin] try: while 1: - r, w, e = select.select(inputlist, [], []) + r, _w, _e = select.select(inputlist, [], []) if sys.stdin in r: line = input() sock.sendall((line + "\n").encode()) diff --git a/testing/test_gateway.py b/testing/test_gateway.py index 8df5f21e..bd527ffe 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -503,7 +503,7 @@ def test_popen_stderr_tracing( monkeypatch.setenv("EXECNET_DEBUG", "2") gw = makegateway("popen") pid = gw.remote_exec("import os ; channel.send(os.getpid())").receive() - out, err = capfd.readouterr() + _out, err = capfd.readouterr() worker_line = "[%s] creating workergateway" % pid assert worker_line in err gw.exit() diff --git a/testing/test_rsync.py b/testing/test_rsync.py index 40af7ca2..2112439f 100644 --- a/testing/test_rsync.py +++ b/testing/test_rsync.py @@ -142,7 +142,7 @@ def test_rsync_default_reporting( rsync = RSync(source) rsync.add_target(gw1, dirs.dest1) rsync.send() - out, err = capsys.readouterr() + out, _err = capsys.readouterr() assert out.find("hello") != -1 def test_rsync_non_verbose( diff --git a/testing/test_serializer.py b/testing/test_serializer.py index 4c78f1ce..06a84cd2 100644 --- a/testing/test_serializer.py +++ b/testing/test_serializer.py @@ -155,7 +155,7 @@ def test_bool(dump, load) -> None: def test_none(dump, load) -> None: p = dump("None") - tp, s = load(p) + _tp, s = load(p) assert s == "None" diff --git a/testing/test_termination.py b/testing/test_termination.py index 83f0b980..3d14d999 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -102,7 +102,7 @@ def test_close_initiating_remote_no_error( popen = subprocess.Popen( [anypython, str(p), str(execnetdir)], stdout=None, stderr=subprocess.PIPE ) - out, err = popen.communicate() + _out, err = popen.communicate() print(err) errstr = err.decode("utf8") lines = [x for x in errstr.splitlines() if "*sys-package" not in x] @@ -151,6 +151,6 @@ def flush(self): popen.stdout.readline() reply = pool.spawn(popen.communicate) reply.get(timeout=50) - out, err = capfd.readouterr() + _out, err = capfd.readouterr() lines = [x for x in err.splitlines() if "*sys-package" not in x] assert not lines or "Killed" in err diff --git a/testing/test_threadpool.py b/testing/test_threadpool.py index becc401c..510ae020 100644 --- a/testing/test_threadpool.py +++ b/testing/test_threadpool.py @@ -164,7 +164,7 @@ def wait_then_put() -> None: pool.execmodel.start(wait_then_put) assert pool.waitall() - out, err = capfd.readouterr() + _out, err = capfd.readouterr() assert err == "" From 5d92c45524474942767b558e43d889e6facc003b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 08:14:36 -0300 Subject: [PATCH 227/275] [pre-commit.ci] pre-commit autoupdate (#365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.0 → v0.13.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.0...v0.13.1) - [github.com/pre-commit/mirrors-mypy: v1.18.1 → v1.18.2](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.1...v1.18.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 89e836b1..f88769d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.0 + rev: v0.13.1 hooks: - id: ruff args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.18.1' + rev: 'v1.18.2' hooks: - id: mypy additional_dependencies: From 32d5ad919879601d1f862c0ace4ac8ca1bf6aa3c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:09:11 -0300 Subject: [PATCH 228/275] [pre-commit.ci] pre-commit autoupdate (#366) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.1 → v0.13.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.1...v0.13.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f88769d8..660c0da5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.1 + rev: v0.13.2 hooks: - id: ruff args: [ --fix ] From cfc8afca70bd8e7be6cb8eaf91d94559db931afe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 08:56:58 -0300 Subject: [PATCH 229/275] [pre-commit.ci] pre-commit autoupdate (#367) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.2 → v0.13.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.2...v0.13.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 660c0da5..80aafa24 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.2 + rev: v0.13.3 hooks: - id: ruff args: [ --fix ] From d823f13ba81a1d14051dda54eb0931c73b7afdf7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:10:36 -0300 Subject: [PATCH 230/275] build(deps): bump hynek/build-and-inspect-python-package (#368) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.13 to 2.14. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.13...v2.14) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-version: '2.14' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d9af656a..ea9bc33e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v5 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.13 + uses: hynek/build-and-inspect-python-package@v2.14 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da22c41a..8574b199 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.13 + uses: hynek/build-and-inspect-python-package@v2.14 test: From 35aa8073a2ae51a38bb787784fd79d0bfba517cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:41:59 -0300 Subject: [PATCH 231/275] [pre-commit.ci] pre-commit autoupdate (#369) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.13.3 → v0.14.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.13.3...v0.14.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 80aafa24..0a480cb6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.13.3 + rev: v0.14.0 hooks: - id: ruff args: [ --fix ] From d3074cd90921317e8d9510bf2fb41e48ed7b62fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 20:57:12 -0300 Subject: [PATCH 232/275] [pre-commit.ci] pre-commit autoupdate (#370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.0 → v0.14.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.0...v0.14.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a480cb6..378e4dc9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.0 + rev: v0.14.1 hooks: - id: ruff args: [ --fix ] From caf108da5946aa4355eb853cb0f1fb5d6a359013 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:14:00 -0300 Subject: [PATCH 233/275] build(deps): bump actions/download-artifact from 5 to 6 (#371) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ea9bc33e..d23f0e94 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v5 - name: Download Package - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: Packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8574b199..5cd67aae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: Packages path: dist From 9bfff13008d02cb5e0943ac57811b07ed7d81ffd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 11:23:35 -0300 Subject: [PATCH 234/275] [pre-commit.ci] pre-commit autoupdate (#372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.1 → v0.14.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.1...v0.14.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 378e4dc9..29cd64b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.1 + rev: v0.14.2 hooks: - id: ruff args: [ --fix ] From a9d75e74b6913b9f4940de4d90e796b464ca88f9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 09:35:27 -0300 Subject: [PATCH 235/275] [pre-commit.ci] pre-commit autoupdate (#373) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.2 → v0.14.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.2...v0.14.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 29cd64b4..486933f9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.2 + rev: v0.14.3 hooks: - id: ruff args: [ --fix ] From f05618084b3e524babe6d684e97443a16c4ecaf2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 07:20:06 -0300 Subject: [PATCH 236/275] [pre-commit.ci] pre-commit autoupdate (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.3 → v0.14.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.3...v0.14.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 486933f9..2daf0219 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.3 + rev: v0.14.4 hooks: - id: ruff args: [ --fix ] From 03462f5ad357cb28de83f28af6977e620b81c607 Mon Sep 17 00:00:00 2001 From: Sebastian Elsner <902798+sebastianelsner@users.noreply.github.com> Date: Tue, 11 Nov 2025 14:45:00 +0100 Subject: [PATCH 237/275] Update hatchling version requirement in pyproject.toml (#377) --------- Co-authored-by: Bruno Oliveira --- .github/workflows/test.yml | 9 +++++++-- CHANGELOG.rst | 6 ++++++ pyproject.toml | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cd67aae..5beaa0a2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,8 +52,13 @@ jobs: with: python-version: ${{ matrix.python }} - - name: Install tox - run: pip install tox + - name: Install dependencies + run: pip install tox twine + + - name: Check package + shell: bash + run: | + twine check --strict dist/* - name: Test shell: bash diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dfe36275..579aa531 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,9 @@ +2.1.2 (2025-11-10) +------------------ + +* `#376 `__ fix artifact building - pin minimal version of hatch + + 2.1.1 (2024-04-08) ------------------ diff --git a/pyproject.toml b/pyproject.toml index 3c304f3e..eb202ee9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "hatchling", + "hatchling>=1.26", "hatch-vcs", ] build-backend = "hatchling.build" From 45a4d872775ae77366b62d6c93adb5c3ec51c966 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 11 Nov 2025 15:23:14 +0100 Subject: [PATCH 238/275] Test on pypy-3.11 instead of pypy-3.8 (#375) --- .github/workflows/test.yml | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5beaa0a2..b56bd08c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: os: [ windows-latest, ubuntu-latest ] - python: [ "3.8","3.10","3.11","3.12", "pypy-3.8" ] + python: [ "3.8", "3.10", "3.11", "3.12", "pypy-3.11" ] steps: - uses: actions/checkout@v5 diff --git a/tox.ini b/tox.ini index 4c743a66..115070df 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py{38,39,310,311,312,pypy38},docs,linting +envlist=py{38,39,310,311,312,pypy311},docs,linting isolated_build = true [testenv] From 9313ece783ba2bd565eb1a2a8a71be441af56199 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 11 Nov 2025 11:23:51 -0300 Subject: [PATCH 239/275] Release 2.1.2 --- CHANGELOG.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 579aa531..cf852a95 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,7 @@ -2.1.2 (2025-11-10) +2.1.2 (2025-11-11) ------------------ -* `#376 `__ fix artifact building - pin minimal version of hatch +* `#376 `__ fix artifact building - pin minimal version of hatch. 2.1.1 (2024-04-08) From 250c8136ab7b007a03b8997cde064ae6d6c38d76 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 12 Nov 2025 16:58:04 +0200 Subject: [PATCH 240/275] Add support for Python 3.13-3.14 and drop EOL 3.8-3.9 (#380) --------- Co-authored-by: Bruno Oliveira --- .github/workflows/test.yml | 2 +- .pre-commit-config.yaml | 6 +++--- CHANGELOG.rst | 5 +++++ doc/install.rst | 2 +- pyproject.toml | 8 ++++---- src/execnet/gateway.py | 2 +- src/execnet/gateway_base.py | 6 +++--- src/execnet/multi.py | 8 ++++---- src/execnet/rsync.py | 2 +- testing/conftest.py | 6 +++--- testing/test_basics.py | 2 +- testing/test_gateway.py | 4 ++-- testing/test_multi.py | 2 +- testing/test_termination.py | 2 +- testing/test_xspec.py | 2 +- tox.ini | 2 +- 16 files changed, 33 insertions(+), 28 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b56bd08c..75025355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: os: [ windows-latest, ubuntu-latest ] - python: [ "3.8", "3.10", "3.11", "3.12", "pypy-3.11" ] + python: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11" ] steps: - uses: actions/checkout@v5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2daf0219..71750f1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,11 +3,11 @@ repos: rev: v2.4.1 hooks: - id: codespell -- repo: https://github.com/asottile/blacken-docs +- repo: https://github.com/adamchainz/blacken-docs rev: 1.20.0 hooks: - id: blacken-docs - additional_dependencies: [black==22.12.0] + additional_dependencies: [black==25.11.0] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -15,7 +15,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.4 hooks: - - id: ruff + - id: ruff-check args: [ --fix ] exclude: "^doc/" - id: ruff-format diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cf852a95..a7af6272 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,8 @@ +2.2.0 (UNRELEASED) +------------------ + +* `#380 `__: Add support for Python 3.13 and 3.14, and drop EOL 3.8 and 3.9. + 2.1.2 (2025-11-11) ------------------ diff --git a/doc/install.rst b/doc/install.rst index e7bb7038..adbb9341 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -1,7 +1,7 @@ Info in a nutshell ==================== -**Pythons**: 3.8+, PyPy 3 +**Pythons**: 3.10+, PyPy 3 **Operating systems**: Linux, Windows, OSX, Unix diff --git a/pyproject.toml b/pyproject.toml index eb202ee9..c527642b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ dynamic = ["version"] description = "execnet: rapid multi-Python deployment" readme = {"file" = "README.rst", "content-type" = "text/x-rst"} license = "MIT" -requires-python = ">=3.8" +requires-python = ">=3.10" authors = [ { name = "holger krekel and others" }, ] @@ -22,11 +22,11 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries", @@ -96,7 +96,7 @@ include = [ ] [tool.mypy] -python_version = "3.8" +python_version = "3.10" mypy_path = ["src"] files = ["src", "testing"] strict = true diff --git a/src/execnet/gateway.py b/src/execnet/gateway.py index 645de0fc..1d3eb59d 100644 --- a/src/execnet/gateway.py +++ b/src/execnet/gateway.py @@ -9,9 +9,9 @@ import linecache import textwrap import types +from collections.abc import Callable from typing import TYPE_CHECKING from typing import Any -from typing import Callable from . import gateway_base from .gateway_base import IO diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index bdee89af..e1a5fe38 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -18,12 +18,12 @@ import traceback import weakref from _thread import interrupt_main +from collections.abc import Callable +from collections.abc import Iterator +from collections.abc import MutableSet from io import BytesIO from typing import Any -from typing import Callable -from typing import Iterator from typing import Literal -from typing import MutableSet from typing import Protocol from typing import cast from typing import overload diff --git a/src/execnet/multi.py b/src/execnet/multi.py index c4f35813..1ea07eee 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -8,15 +8,15 @@ import atexit import types +from collections.abc import Callable +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Sequence from functools import partial from threading import Lock from typing import TYPE_CHECKING from typing import Any -from typing import Callable -from typing import Iterable -from typing import Iterator from typing import Literal -from typing import Sequence from typing import overload from . import gateway_bootstrap diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index 2b5698d8..c12a739a 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -8,9 +8,9 @@ import os import stat +from collections.abc import Callable from hashlib import md5 from queue import Queue -from typing import Callable from typing import Literal import execnet.rsync_remote diff --git a/testing/conftest.py b/testing/conftest.py index d9667ac1..c75f96c7 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -2,10 +2,10 @@ import shutil import sys +from collections.abc import Callable +from collections.abc import Generator +from collections.abc import Iterator from functools import lru_cache -from typing import Callable -from typing import Generator -from typing import Iterator import pytest diff --git a/testing/test_basics.py b/testing/test_basics.py index 427ded3c..1756ec34 100644 --- a/testing/test_basics.py +++ b/testing/test_basics.py @@ -6,11 +6,11 @@ import subprocess import sys import textwrap +from collections.abc import Callable from dataclasses import dataclass from io import BytesIO from pathlib import Path from typing import Any -from typing import Callable import pytest diff --git a/testing/test_gateway.py b/testing/test_gateway.py index bd527ffe..634f237a 100644 --- a/testing/test_gateway.py +++ b/testing/test_gateway.py @@ -9,8 +9,8 @@ import shutil import signal import sys +from collections.abc import Callable from textwrap import dedent -from typing import Callable import pytest @@ -635,7 +635,7 @@ def test_main_thread_only_concurrent_remote_exec_deadlock( True, execnet.gateway_base.MAIN_THREAD_ONLY_DEADLOCK_TEXT, ) - for expected, ch in zip(expected_results, channels): + for expected, ch in zip(expected_results, channels, strict=True): try: res = ch.receive() except execnet.RemoteError as e: diff --git a/testing/test_multi.py b/testing/test_multi.py index e1dc40d1..81e4525b 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -5,8 +5,8 @@ from __future__ import annotations import gc +from collections.abc import Callable from time import sleep -from typing import Callable import pytest diff --git a/testing/test_termination.py b/testing/test_termination.py index 3d14d999..ca119304 100644 --- a/testing/test_termination.py +++ b/testing/test_termination.py @@ -4,7 +4,7 @@ import signal import subprocess import sys -from typing import Callable +from collections.abc import Callable import pytest from test_gateway import TESTTIMEOUT diff --git a/testing/test_xspec.py b/testing/test_xspec.py index 3e5041be..f837b07a 100644 --- a/testing/test_xspec.py +++ b/testing/test_xspec.py @@ -4,8 +4,8 @@ import shutil import subprocess import sys +from collections.abc import Callable from pathlib import Path -from typing import Callable import pytest diff --git a/tox.ini b/tox.ini index 115070df..5880696d 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py{38,39,310,311,312,pypy311},docs,linting +envlist=py{310,311,312,313,314,py311},docs,linting isolated_build = true [testenv] From 92087615420eee3bcf7a04426edd68bcc583568f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 08:07:33 -0300 Subject: [PATCH 241/275] [pre-commit.ci] pre-commit autoupdate (#381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.4 → v0.14.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.4...v0.14.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 71750f1e..6f7f901e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.4 + rev: v0.14.5 hooks: - id: ruff-check args: [ --fix ] From 0937534f64e41cc70f651617334f06319f281242 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 08:51:58 -0300 Subject: [PATCH 242/275] build(deps): bump actions/checkout from 5 to 6 (#382) Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d23f0e94..53fbdf22 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,7 @@ jobs: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.14 @@ -30,7 +30,7 @@ jobs: contents: write # For tag. steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Download Package uses: actions/download-artifact@v6 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75025355..596b2b92 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.14 @@ -37,7 +37,7 @@ jobs: python: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11" ] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: fetch-depth: 0 From 6607f8d95ce0b8be381d4f19e9de1be13337afc1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:48:04 -0300 Subject: [PATCH 243/275] [pre-commit.ci] pre-commit autoupdate (#383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.5 → v0.14.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.5...v0.14.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f7f901e..7259d874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.5 + rev: v0.14.6 hooks: - id: ruff-check args: [ --fix ] From 89389e44e2d7372d0a28474b716867063bc5e1dd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 10:42:47 -0300 Subject: [PATCH 244/275] [pre-commit.ci] pre-commit autoupdate (#385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.6 → v0.14.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.6...v0.14.7) - [github.com/pre-commit/mirrors-mypy: v1.18.2 → v1.19.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.18.2...v1.19.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7259d874..64dcf493 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.6 + rev: v0.14.7 hooks: - id: ruff-check args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.18.2' + rev: 'v1.19.0' hooks: - id: mypy additional_dependencies: From e4243f3edb0abf1cf77679fcc428f283c3200e13 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 01:31:39 -0300 Subject: [PATCH 245/275] [pre-commit.ci] pre-commit autoupdate (#386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.7 → v0.14.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.7...v0.14.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64dcf493..54d04e94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.7 + rev: v0.14.8 hooks: - id: ruff-check args: [ --fix ] From c268e516c3ca7edc7cce0cc4d5c3dae74609decd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 08:20:56 -0300 Subject: [PATCH 246/275] build(deps): bump actions/download-artifact from 6 to 7 (#387) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 53fbdf22..99706a19 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v6 - name: Download Package - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: Packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 596b2b92..54b733ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: Packages path: dist From 7aec3b07678497ed885db2663902a7b43a39dacb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 19:29:38 -0300 Subject: [PATCH 247/275] [pre-commit.ci] pre-commit autoupdate (#388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.8 → v0.14.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.8...v0.14.9) - [github.com/pre-commit/mirrors-mypy: v1.19.0 → v1.19.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.0...v1.19.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 54d04e94..652da812 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.8 + rev: v0.14.9 hooks: - id: ruff-check args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.19.0' + rev: 'v1.19.1' hooks: - id: mypy additional_dependencies: From b03d25b504dfdc6be6cd99b2ffbd34a21603eee0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:18:24 -0300 Subject: [PATCH 248/275] [pre-commit.ci] pre-commit autoupdate (#389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.9 → v0.14.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.9...v0.14.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 652da812..96f76cf2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.9 + rev: v0.14.10 hooks: - id: ruff-check args: [ --fix ] From 141ccfd8de55c77c8c00ff2ce1d7fc5ef36cba2b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 19:17:58 -0300 Subject: [PATCH 249/275] [pre-commit.ci] pre-commit autoupdate (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.10 → v0.14.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.10...v0.14.11) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 96f76cf2..b5f1a138 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 + rev: v0.14.11 hooks: - id: ruff-check args: [ --fix ] From 60b58379ae295dd3ae3ac881593d038aa8d35cb1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:38:05 -0300 Subject: [PATCH 250/275] [pre-commit.ci] pre-commit autoupdate (#391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.11 → v0.14.13](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.11...v0.14.13) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b5f1a138..4db1983c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.11 + rev: v0.14.13 hooks: - id: ruff-check args: [ --fix ] From 2079d0ea87bbba3c4cda1a7f29ed45b22a9ad9be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:39:34 +0000 Subject: [PATCH 251/275] [pre-commit.ci] pre-commit autoupdate (#392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.13 → v0.15.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.13...v0.15.1) * Update pyproject.toml --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bruno Oliveira --- .pre-commit-config.yaml | 2 +- pyproject.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4db1983c..b8e0b5da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.13 + rev: v0.15.1 hooks: - id: ruff-check args: [ --fix ] diff --git a/pyproject.toml b/pyproject.toml index c527642b..6d7e8671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,12 +69,14 @@ ignore = [ "E741", # Ambiguous variable name # ruff ignore "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + "RUF061", # Use context-manager form of `pytest.raises()` # pylint ignore "PLW0603", # Using the global statement "PLW0120", # remove the else and dedent its contents "PLW2901", # for loop variable overwritten by assignment target "PLR5501", # Use `elif` instead of `else` then `if` "UP031", # Use format specifiers instead of percent format + ] [tool.ruff.lint.isort] From c9002c5bb4c87c083577dfa1d71f2c5d583c2d82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 Feb 2026 21:24:34 +0000 Subject: [PATCH 252/275] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.1 → v0.15.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.1...v0.15.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8e0b5da..8696cc4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.1 + rev: v0.15.2 hooks: - id: ruff-check args: [ --fix ] From 7746470945e892a0b0c456b969386dcfd479ae0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:05:52 -0300 Subject: [PATCH 253/275] build(deps): bump actions/download-artifact from 7 to 8 (#394) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 99706a19..90d050db 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v6 - name: Download Package - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: Packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 54b733ce..2a9ea1e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Download Package - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: Packages path: dist From 5dd3948df4169712a70b369d434dca3ab330d2b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:31:48 -0300 Subject: [PATCH 254/275] [pre-commit.ci] pre-commit autoupdate (#395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/codespell-project/codespell: v2.4.1 → v2.4.2](https://github.com/codespell-project/codespell/compare/v2.4.1...v2.4.2) - [github.com/astral-sh/ruff-pre-commit: v0.15.2 → v0.15.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.2...v0.15.5) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8696cc4c..ed275066 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/codespell-project/codespell - rev: v2.4.1 + rev: v2.4.2 hooks: - id: codespell - repo: https://github.com/adamchainz/blacken-docs @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.15.5 hooks: - id: ruff-check args: [ --fix ] From 59a2112ac35305c65b6f9fa66e3752fff52ee8d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:05:38 -0300 Subject: [PATCH 255/275] [pre-commit.ci] pre-commit autoupdate (#396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.5 → v0.15.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.5...v0.15.6) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed275066..11591131 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.5 + rev: v0.15.6 hooks: - id: ruff-check args: [ --fix ] From 25fa2269dcaa6f17c6ba14bfb4e1d52d1cee894c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:21:21 -0300 Subject: [PATCH 256/275] build(deps): bump hynek/build-and-inspect-python-package (#397) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.14 to 2.15. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.14...v2.15) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-version: '2.15' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 90d050db..46ecc428 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.14 + uses: hynek/build-and-inspect-python-package@v2.15 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a9ea1e1..8ec1b407 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.14 + uses: hynek/build-and-inspect-python-package@v2.15 test: From 26ba7e2ae3ee2604a380734f85f4a6adc00312f4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 29 Mar 2026 10:21:31 -0300 Subject: [PATCH 257/275] [pre-commit.ci] pre-commit autoupdate (#398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.6 → v0.15.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.6...v0.15.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11591131..df34ee5f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.6 + rev: v0.15.7 hooks: - id: ruff-check args: [ --fix ] From a91fa70c12c19ec2aed06d0f1de456ada418a9ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:39:54 -0300 Subject: [PATCH 258/275] build(deps): bump hynek/build-and-inspect-python-package (#399) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.15 to 2.17. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.15...v2.17) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-version: '2.17' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 46ecc428..b4ab4320 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.15 + uses: hynek/build-and-inspect-python-package@v2.17 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8ec1b407..929a126a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.15 + uses: hynek/build-and-inspect-python-package@v2.17 test: From 194cdada00a97ace098b8332afba221cc9b8094e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:40:05 -0300 Subject: [PATCH 259/275] [pre-commit.ci] pre-commit autoupdate (#400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.7 → v0.15.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.7...v0.15.8) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df34ee5f..e64948b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.7 + rev: v0.15.8 hooks: - id: ruff-check args: [ --fix ] From bd94c6cd415e578899cf82b4f151c82988f80b1a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 11 Apr 2026 10:10:40 -0300 Subject: [PATCH 260/275] [pre-commit.ci] pre-commit autoupdate (#401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.8 → v0.15.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.8...v0.15.9) - [github.com/pre-commit/mirrors-mypy: v1.19.1 → v1.20.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.19.1...v1.20.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e64948b3..b14b99a6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.8 + rev: v0.15.9 hooks: - id: ruff-check args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.19.1' + rev: 'v1.20.0' hooks: - id: mypy additional_dependencies: From 58a1b545f533e599b898d9c02fd4a6037b73f137 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 11:44:25 -0300 Subject: [PATCH 261/275] build(deps): bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 (#402) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...v1.14.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b4ab4320..79012f23 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.13.0 + uses: pypa/gh-action-pypi-publish@v1.14.0 - name: Push tag run: | From 163e70dbfd9e476d072e8fefc7f21aab7f3511ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 08:06:19 -0300 Subject: [PATCH 262/275] [pre-commit.ci] pre-commit autoupdate (#403) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.9 → v0.15.12](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.9...v0.15.12) - [github.com/pre-commit/mirrors-mypy: v1.20.0 → v2.0.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.20.0...v2.0.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b14b99a6..be4b6e07 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: hooks: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.9 + rev: v0.15.12 hooks: - id: ruff-check args: [ --fix ] @@ -26,7 +26,7 @@ repos: args: ["--ignore", "D001"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.20.0' + rev: 'v2.0.0' hooks: - id: mypy additional_dependencies: From 0c0a150cf226337a161940bd0e8ee1890bb2ec58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 May 2026 10:09:48 -0300 Subject: [PATCH 263/275] build(deps): bump hynek/build-and-inspect-python-package (#404) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.17 to 2.18. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.17...v2.18) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-version: '2.18' dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 79012f23..f5a1c4c2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.17 + uses: hynek/build-and-inspect-python-package@v2.18 deploy: needs: package diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 929a126a..1a2c9642 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.17 + uses: hynek/build-and-inspect-python-package@v2.18 test: From 399221a8405b0921854576b8539b4f1ce8b2600b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 05:42:28 +0000 Subject: [PATCH 264/275] build(deps): bump actions/checkout from 6 to 7 Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f5a1c4c2..2dd5cc85 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -16,7 +16,7 @@ jobs: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.18 @@ -30,7 +30,7 @@ jobs: contents: write # For tag. steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Download Package uses: actions/download-artifact@v8 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a2c9642..6565f820 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v2.18 @@ -37,7 +37,7 @@ jobs: python: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11" ] steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 From 7cdb4ba13f80f23e56b87343450f77934b897474 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:42:29 +0000 Subject: [PATCH 265/275] build(deps): bump pypa/gh-action-pypi-publish from 1.14.0 to 1.14.1 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.14.0 to 1.14.1. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.14.0...v1.14.1) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f5a1c4c2..1c42ffb7 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -39,7 +39,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.14.0 + uses: pypa/gh-action-pypi-publish@v1.14.1 - name: Push tag run: | From b0401577243346d325a9cc72fc88babd86032644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 05:42:32 +0000 Subject: [PATCH 266/275] build(deps): bump actions/setup-python from 6 to 7 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a2c9642..aa082c05 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: path: dist - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python }} From 43f206c2bf2919ac85d2cab7cb5d811829fb0215 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 21 Jul 2026 16:54:51 +0200 Subject: [PATCH 267/275] Adopt uv dependency groups and enable ruff SIM cleanups Move testing deps to PEP 735 dependency-groups with a lockfile for reproducible local setup, and apply SIM-driven style simplifications. Co-authored-by: Cursor AI Co-authored-by: Cursor Grok 4.5 --- pyproject.toml | 11 +- src/execnet/gateway_base.py | 53 +- src/execnet/rsync.py | 7 +- src/execnet/rsync_remote.py | 16 +- src/execnet/script/shell.py | 3 +- src/execnet/script/socketserver.py | 7 +- src/execnet/script/socketserverservice.py | 2 +- uv.lock | 1088 +++++++++++++++++++++ 8 files changed, 1133 insertions(+), 54 deletions(-) create mode 100644 uv.lock diff --git a/pyproject.toml b/pyproject.toml index 6d7e8671..a5454de8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,12 +34,15 @@ classifiers = [ "Topic :: System :: Networking", ] -[project.optional-dependencies] +[dependency-groups] testing = [ "pre-commit", - "pytest", + "pytest>8.0", + "pytest-timeout", "tox", "hatch", + "uv", + "gevent", ] [project.urls] @@ -59,6 +62,7 @@ extend-select = [ "PGH", # pygrep-hooks "PLE", # pylint error "PLW", # pylint warning + "SIM", ] ignore = [ # bugbear ignore @@ -96,7 +100,8 @@ include = [ "/testing", "tox.ini", ] - +[tool.uv] +default-groups = ["testing"] [tool.mypy] python_version = "3.10" mypy_path = ["src"] diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index e1a5fe38..4fb6af0c 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -20,7 +20,7 @@ from _thread import interrupt_main from collections.abc import Callable from collections.abc import Iterator -from collections.abc import MutableSet +from contextlib import suppress from io import BytesIO from typing import Any from typing import Literal @@ -364,7 +364,7 @@ class WorkerPool: def __init__(self, execmodel: ExecModel, hasprimary: bool = False) -> None: self.execmodel = execmodel self._running_lock = self.execmodel.Lock() - self._running: MutableSet[Reply] = set() + self._running: set[Reply] = set() self._shuttingdown = False self._waitall_events: list[Event] = [] if hasprimary: @@ -491,7 +491,7 @@ def trace(*msg: object) -> None: fn = os.path.join(tempfile.gettempdir(), "execnet-debug-%d" % pid) # sys.stderr.write("execnet-debug at %r" % (fn,)) - debugfile = open(fn, "w") + debugfile = open(fn, "w") # noqa: SIM115 def trace(*msg: object) -> None: try: @@ -499,7 +499,7 @@ def trace(*msg: object) -> None: debugfile.write(line + "\n") debugfile.flush() except Exception as exc: - try: + try: # noqa: SIM105 sys.stderr.write(f"[{pid}] exception during tracing: {exc!r}\n") except Exception: pass # nothing we can do, likely interpreter-shutdown @@ -788,10 +788,8 @@ def __del__(self) -> None: msgcode = Message.CHANNEL_LAST_MESSAGE else: msgcode = Message.CHANNEL_CLOSE - try: + with suppress(KeyError, ValueError): # ignore problems with sending self.gateway._send(msgcode, self.id) - except (OSError, ValueError): # ignore problems with sending - pass def _getremoteerror(self): try: @@ -1002,17 +1000,12 @@ def channels(self) -> list[Channel]: # internal methods, called from the receiver thread # def _no_longer_opened(self, id: int) -> None: - try: - del self._channels[id] - except KeyError: - pass - try: - callback, endmarker, _strconfig = self._callbacks.pop(id) - except KeyError: - pass - else: - if endmarker is not NO_ENDMARKER_WANTED: - callback(endmarker) + self._channels.pop(id, None) + callback, endmarker, _strconfig = self._callbacks.pop( + id, (lambda x: None, NO_ENDMARKER_WANTED, None) + ) + if endmarker is not NO_ENDMARKER_WANTED: + callback(endmarker) def _local_close(self, id: int, remoteerror=None, sendonly: bool = False) -> None: channel = self._channels.get(id) @@ -1299,14 +1292,15 @@ def executetask( except KeyboardInterrupt: channel.close(INTERRUPT_TEXT) raise - except BaseException as exc: - if not isinstance(exc, EOFError): - if not channel.gateway._channelfactory.finished: - self._trace(f"got exception: {exc!r}") - errortext = self._geterrortext(exc) - channel.close(errortext) - return + except EOFError: self._trace("ignoring EOFError because receiving finished") + + except BaseException as exc: + if not channel.gateway._channelfactory.finished: + self._trace(f"got exception: {exc!r}") + errortext = self._geterrortext(exc) + channel.close(errortext) + return channel.close() if self._executetask_complete is not None: # Indicate that this task has finished executing, meaning @@ -1767,16 +1761,13 @@ def init_popen_io(execmodel: ExecModel) -> Popen2IO: io = Popen2IO(sys.stdout, sys.stdin, execmodel) import tempfile - sys.stdin = tempfile.TemporaryFile("r") - sys.stdout = tempfile.TemporaryFile("w") + sys.stdin = tempfile.TemporaryFile("r") # noqa: SIM115 + sys.stdout = tempfile.TemporaryFile("w") # noqa: SIM115 else: try: devnull = os.devnull except AttributeError: - if os.name == "nt": - devnull = "NUL" - else: - devnull = "/dev/null" + devnull = "NUL" if os.name == "nt" else "/dev/null" # stdin stdin = execmodel.fdopen(os.dup(0), "r", 1) fd = os.open(devnull, os.O_RDONLY) diff --git a/src/execnet/rsync.py b/src/execnet/rsync.py index c12a739a..f92bc37a 100644 --- a/src/execnet/rsync.py +++ b/src/execnet/rsync.py @@ -86,10 +86,10 @@ def _send_item( checksum: bytes, ) -> None: """Send one item.""" - modifiedpath = os.path.join(self._sourcedir, *modified_rel_path_components) + modified_path = os.path.join(self._sourcedir, *modified_rel_path_components) try: - f = open(modifiedpath, "rb") - data = f.read() + with open(modified_path, "rb") as fp: + data = fp.read() except OSError: data = None @@ -105,7 +105,6 @@ def _send_item( # print "sending", modified_rel_path, data and len(data) or 0, checksum if data is not None: - f.close() if checksum is not None and checksum == md5(data).digest(): data = None # not really modified else: diff --git a/src/execnet/rsync_remote.py b/src/execnet/rsync_remote.py index b560df75..a8467b76 100644 --- a/src/execnet/rsync_remote.py +++ b/src/execnet/rsync_remote.py @@ -4,6 +4,7 @@ from __future__ import annotations +from contextlib import suppress from typing import TYPE_CHECKING from typing import Literal from typing import cast @@ -67,9 +68,8 @@ def receive_directory_structure(path: str, relcomponents: list[str]) -> None: if msg_size != st.st_size: pass elif msg_mtime != st.st_mtime: - f = open(path, "rb") - checksum = md5(f.read()).digest() - f.close() + with open(path, "rb") as fp: + checksum = md5(fp.read()).digest() elif msg_mode and msg_mode != st.st_mode: os.chmod(path, msg_mode | 0o700) return @@ -91,9 +91,8 @@ def receive_directory_structure(path: str, relcomponents: list[str]) -> None: if data is not None: if STRICT_CHECK and len(data) != size: raise OSError(f"file modified during rsync: {path!r}") - f = open(path, "wb") - f.write(data) - f.close() + with open(path, "wb") as fp: + fp.write(data) try: if mode: os.chmod(path, mode) @@ -110,10 +109,9 @@ def receive_directory_structure(path: str, relcomponents: list[str]) -> None: "tuple[Literal['linkbase', 'link'], str, str]", msg ) path = os.path.join(destdir, relpath) - try: + with suppress(OSError): remove(path) - except OSError: - pass + if _type == "linkbase": src = os.path.join(destdir, linkpoint) else: diff --git a/src/execnet/script/shell.py b/src/execnet/script/shell.py index d37a48c2..de6f8ded 100644 --- a/src/execnet/script/shell.py +++ b/src/execnet/script/shell.py @@ -18,7 +18,8 @@ def clientside() -> NoReturn: print("client side starting") host, portstr = sys.argv[1].split(":") port = int(portstr) - myself = open(os.path.abspath(sys.argv[0])).read() + with open(os.path.abspath(sys.argv[0])) as fd: + myself = fd.read() sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port)) sock.sendall((repr(myself) + "\n").encode()) diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 55af5481..1a06ab29 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -33,7 +33,7 @@ debug = 0 if debug: # and not os.isatty(sys.stdin.fileno()) - f = open("/tmp/execnet-socket-pyout.log", "w") + f = open("/tmp/execnet-socket-pyout.log", "w") # noqa: SIM115 old = sys.stdout, sys.stderr sys.stdout = sys.stderr = f @@ -115,10 +115,7 @@ def startserver(serversock, loop: bool = False) -> None: if __name__ == "__main__": import sys - if len(sys.argv) > 1: - hostport = sys.argv[1] - else: - hostport = ":8888" + hostport = sys.argv[1] if len(sys.argv) > 1 else ":8888" from execnet.gateway_base import get_execmodel execmodel = get_execmodel("thread") diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index 18e375c4..1dbc4140 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -51,7 +51,7 @@ def SvcDoRun(self) -> None: # Redirect stdout and stderr to prevent "IOError: [Errno 9] # Bad file descriptor". Windows services don't have functional # output streams. - sys.stdout = sys.stderr = open("nul", "w") + sys.stdout = sys.stderr = open("nul", "w") # noqa: SIM115 # Write a 'started' event to the event log... win32evtlogutil.ReportEvent( diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..7bc44d78 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1088 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "anyio" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, +] + +[[package]] +name = "backports-tarfile" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, +] + +[[package]] +name = "cachetools" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/b0/f539a1ddff36644c28a61490056e5bae43bd7386d9f9c69beae2d7e7d6d1/cachetools-6.0.0.tar.gz", hash = "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf", size = 30160, upload-time = "2025-05-23T20:01:13.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/c3/8bb087c903c95a570015ce84e0c23ae1d79f528c349cbc141b5c4e250293/cachetools-6.0.0-py3-none-any.whl", hash = "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e", size = 10964, upload-time = "2025-05-23T20:01:11.323Z" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, +] + +[[package]] +name = "click" +version = "8.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/47/92a8914716f2405f33f1814b97353e3cfa223cd94a77104075d42de3099e/cryptography-45.0.2.tar.gz", hash = "sha256:d784d57b958ffd07e9e226d17272f9af0c41572557604ca7554214def32c26bf", size = 743865, upload-time = "2025-05-18T02:46:34.986Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/52/49e6c86278e1b5ec226e96b62322538ccc466306517bf9aad8854116a088/cryptography-45.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cc31c66411e14dd70e2f384a9204a859dc25b05e1f303df0f5326691061b839", size = 4201098, upload-time = "2025-05-18T02:45:15.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3a/201272539ac5b66b4cb1af89021e423fc0bfacb73498950280c51695fb78/cryptography-45.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:463096533acd5097f8751115bc600b0b64620c4aafcac10c6d0041e6e68f88fe", size = 4429839, upload-time = "2025-05-18T02:45:17.614Z" }, + { url = "https://files.pythonhosted.org/packages/99/89/fa1a84832b8f8f3917875cb15324bba98def5a70175a889df7d21a45dc75/cryptography-45.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:cdafb86eb673c3211accffbffdb3cdffa3aaafacd14819e0898d23696d18e4d3", size = 4205154, upload-time = "2025-05-18T02:45:19.874Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c5/5225d5230d538ab461725711cf5220560a813d1eb68bafcfb00131b8f631/cryptography-45.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05c2385b1f5c89a17df19900cfb1345115a77168f5ed44bdf6fd3de1ce5cc65b", size = 3897145, upload-time = "2025-05-18T02:45:22.209Z" }, + { url = "https://files.pythonhosted.org/packages/fe/24/f19aae32526cc55ae17d473bc4588b1234af2979483d99cbfc57e55ffea6/cryptography-45.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e9e4bdcd70216b08801e267c0b563316b787f957a46e215249921f99288456f9", size = 4462192, upload-time = "2025-05-18T02:45:24.773Z" }, + { url = "https://files.pythonhosted.org/packages/19/18/4a69ac95b0b3f03355970baa6c3f9502bbfc54e7df81fdb179654a00f48e/cryptography-45.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b2de529027579e43b6dc1f805f467b102fb7d13c1e54c334f1403ee2b37d0059", size = 4208093, upload-time = "2025-05-18T02:45:27.028Z" }, + { url = "https://files.pythonhosted.org/packages/7c/54/2dea55ccc9558b8fa14f67156250b6ee231e31765601524e4757d0b5db6b/cryptography-45.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10d68763892a7b19c22508ab57799c4423c7c8cd61d7eee4c5a6a55a46511949", size = 4461819, upload-time = "2025-05-18T02:45:29.39Z" }, + { url = "https://files.pythonhosted.org/packages/37/f1/1b220fcd5ef4b1f0ff3e59e733b61597505e47f945606cc877adab2c1a17/cryptography-45.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2a90ce2f0f5b695e4785ac07c19a58244092f3c85d57db6d8eb1a2b26d2aad6", size = 4329202, upload-time = "2025-05-18T02:45:31.925Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e0/51d1dc4f96f819a56db70f0b4039b4185055bbb8616135884c3c3acc4c6d/cryptography-45.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:59c0c8f043dd376bbd9d4f636223836aed50431af4c5a467ed9bf61520294627", size = 4570412, upload-time = "2025-05-18T02:45:34.348Z" }, + { url = "https://files.pythonhosted.org/packages/31/a3/a3e4a298d3db4a04085728f5ae6c8cda157e49c5bb784886d463b9fbff70/cryptography-45.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e328357b6bbf79928363dbf13f4635b7aac0306afb7e5ad24d21d0c5761c3253", size = 4189148, upload-time = "2025-05-18T02:45:42.538Z" }, + { url = "https://files.pythonhosted.org/packages/53/90/100dfadd4663b389cb56972541ec1103490a19ebad0132af284114ba0868/cryptography-45.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49af56491473231159c98c2c26f1a8f3799a60e5cf0e872d00745b858ddac9d2", size = 4424113, upload-time = "2025-05-18T02:45:44.316Z" }, + { url = "https://files.pythonhosted.org/packages/0d/40/e2b9177dbed6f3fcbbf1942e1acea2fd15b17007204b79d675540dd053af/cryptography-45.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f169469d04a23282de9d0be349499cb6683b6ff1b68901210faacac9b0c24b7d", size = 4189696, upload-time = "2025-05-18T02:45:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/ec29c79f481e1767c2ff916424ba36f3cf7774de93bbd60428a3c52d1357/cryptography-45.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9cfd1399064b13043082c660ddd97a0358e41c8b0dc7b77c1243e013d305c344", size = 3881498, upload-time = "2025-05-18T02:45:48.884Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4a/72937090e5637a232b2f73801c9361cd08404a2d4e620ca4ec58c7ea4b70/cryptography-45.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f8084b7ca3ce1b8d38bdfe33c48116edf9a08b4d056ef4a96dceaa36d8d965", size = 4451678, upload-time = "2025-05-18T02:45:50.706Z" }, + { url = "https://files.pythonhosted.org/packages/d3/fa/1377fced81fd67a4a27514248261bb0d45c3c1e02169411fe231583088c8/cryptography-45.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2cb03a944a1a412724d15a7c051d50e63a868031f26b6a312f2016965b661942", size = 4192296, upload-time = "2025-05-18T02:45:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/b6fe837c83a08b9df81e63299d75fc5b3c6d82cf24b3e1e0e331050e9e5c/cryptography-45.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a9727a21957d3327cf6b7eb5ffc9e4b663909a25fea158e3fcbc49d4cdd7881b", size = 4451749, upload-time = "2025-05-18T02:45:55.025Z" }, + { url = "https://files.pythonhosted.org/packages/af/d8/5a655675cc635c7190bfc8cffb84bcdc44fc62ce945ad1d844adaa884252/cryptography-45.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddb8d01aa900b741d6b7cc585a97aff787175f160ab975e21f880e89d810781a", size = 4317601, upload-time = "2025-05-18T02:45:56.911Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d4/75d2375a20d80aa262a8adee77bf56950e9292929e394b9fae2481803f11/cryptography-45.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c0c000c1a09f069632d8a9eb3b610ac029fcc682f1d69b758e625d6ee713f4ed", size = 4560535, upload-time = "2025-05-18T02:45:59.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/7b/00e18d24f08bc642e4018e0066a6f872d85c744e3265910c3beabb1f4d73/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:965611880c3fa8e504b7458484c0697e00ae6e937279cd6734fdaa2bc954dc49", size = 4135515, upload-time = "2025-05-18T02:46:07.241Z" }, + { url = "https://files.pythonhosted.org/packages/29/9f/ea7ad5239c33c36f0e2cbdf631a0e3b7633466e87e55923f5b5ea1b0b92d/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d891942592789fa0ab71b502550bbadb12f540d7413d7d7c4cef4b02af0f5bc6", size = 4378133, upload-time = "2025-05-18T02:46:09.035Z" }, + { url = "https://files.pythonhosted.org/packages/47/f8/b4e29d87fbc4d2cf46b36e01fcb98305bf76699f34de6b877cddd8bc3a64/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b19f4b28dd2ef2e6d600307fee656c00825a2980c4356a7080bd758d633c3a6f", size = 4136787, upload-time = "2025-05-18T02:46:10.772Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/ac19bbf24d261667a67aac712d8aa3bb740f94bc2391f06ccc90e783f3ff/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7c73968fbb7698a4c5d6160859db560d3aac160edde89c751edd5a8bc6560c88", size = 4377741, upload-time = "2025-05-18T02:46:13.215Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/57bc98fa5d93e74c2c2b16a3c5383f7ec218f957aa44559c0008a46c3629/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc7693573f16535428183de8fd27f0ca1ca37a51baa0b41dc5ed7b3d68fe80e2", size = 4143347, upload-time = "2025-05-18T02:46:19.934Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/d015e7e7bd7f3a6c538973005de5a780d93b68138c2d88c804422cf46b1c/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:614bca7c6ed0d8ad1dce683a6289afae1f880675b4090878a0136c3da16bc693", size = 4387414, upload-time = "2025-05-18T02:46:21.944Z" }, + { url = "https://files.pythonhosted.org/packages/de/9e/fa5ec89cce7e4b86e430438da4d66b79113bdf321d0a00167d34b61daf19/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:4142e20c29224cec63e9e32eb1e6014fb285fe39b7be66b3564ca978a3a8afe9", size = 4145849, upload-time = "2025-05-18T02:46:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/7c/09/5887d4fcc6f9c6fb19920789d094c4e25c2f604cc1b10b7e69d6f56187fe/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9a900036b42f7324df7c7ad9569eb92ba0b613cf699160dd9c2154b24fd02f8e", size = 4387449, upload-time = "2025-05-18T02:46:26.144Z" }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "execnet" +source = { editable = "." } + +[package.dev-dependencies] +testing = [ + { name = "gevent" }, + { name = "hatch" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-timeout" }, + { name = "tox" }, + { name = "uv" }, +] + +[package.metadata] + +[package.metadata.requires-dev] +testing = [ + { name = "gevent" }, + { name = "hatch" }, + { name = "pre-commit" }, + { name = "pytest", specifier = ">8.0" }, + { name = "pytest-timeout" }, + { name = "tox" }, + { name = "uv" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + +[[package]] +name = "gevent" +version = "25.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, + { name = "greenlet", marker = "platform_python_implementation == 'CPython'" }, + { name = "zope-event" }, + { name = "zope-interface" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/58/267e8160aea00ab00acd2de97197eecfe307064a376fb5c892870a8a6159/gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", size = 6388207, upload-time = "2025-05-12T12:57:59.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/a7/438568c37fb255f80e710318bfcad04731b92ce764bc16adee278fdc6b4d/gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9", size = 2922800, upload-time = "2025-05-12T11:11:46.728Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b3/b44d8b1c4a4d01097a7f82ffbc582d054007365c27b28867f0b2d4241d73/gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52", size = 1812954, upload-time = "2025-05-12T11:52:27.059Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c6/935b4c973ad827c9ec49c354d68d047da1d23e3018bda63d3723cce43178/gevent-25.5.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4", size = 1900169, upload-time = "2025-05-12T11:54:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/38/8a/b745bddfec35fb723cafb036f191e5e0a0013f1698bf0ba4fa2cb8e01879/gevent-25.5.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229", size = 1849786, upload-time = "2025-05-12T12:00:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b3/7aa7b09d91207bebe7608699558bbadd34f63e32904351867c29f8be25de/gevent-25.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9", size = 2139021, upload-time = "2025-05-12T11:32:58.961Z" }, + { url = "https://files.pythonhosted.org/packages/74/da/cf52ae0c84361f4164a04f3338508b1234331ce79719db103e50dbc5598c/gevent-25.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8", size = 1830758, upload-time = "2025-05-12T11:59:55.666Z" }, + { url = "https://files.pythonhosted.org/packages/93/93/73a49b896d78eec27f0895ce3008f9825db748a5aacbca47404d1014da4b/gevent-25.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1", size = 2199993, upload-time = "2025-05-12T11:40:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/df/c7/34680b7d2a75492fa032fa8ecaacc03c1940767a35125f6740954a0132a3/gevent-25.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817", size = 1652665, upload-time = "2025-05-12T12:35:58.105Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/015e93f16a718e2f836ecebecae9bcd7b4d2a5695d1c8bd5bba2d5d91548/gevent-25.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e", size = 2877441, upload-time = "2025-05-12T11:14:57.735Z" }, + { url = "https://files.pythonhosted.org/packages/7b/86/42d191a6f6672ca59d6d79b4cd9b89d4a15f59c843fbbad42f2b749f8ea9/gevent-25.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928", size = 1774873, upload-time = "2025-05-12T11:52:29.015Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9f/42dd255849c9ca2e814f5cbe180980594007ba19044a132cf674069e38bf/gevent-25.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41", size = 1857911, upload-time = "2025-05-12T11:54:19.523Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fc/8e799a733be48f6114bfc531b94e28812741664d8af89872dd90e117f8a4/gevent-25.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d", size = 1812751, upload-time = "2025-05-12T12:00:03.719Z" }, + { url = "https://files.pythonhosted.org/packages/52/4f/a3f3acd961887da10cb0b49c3d915201973d59ce6bf49e2922eaf2058d5f/gevent-25.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25", size = 2087115, upload-time = "2025-05-12T11:33:01.128Z" }, + { url = "https://files.pythonhosted.org/packages/b6/27/bb38e005106a53787c13ad1f9f73ed990e403e462108acae6320ab11d442/gevent-25.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368", size = 1793549, upload-time = "2025-05-12T11:59:57.854Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/da817bc69e1f0ae8438f12f2cd150656b09a8c3576c6d12f992dc9ca64ef/gevent-25.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a", size = 2145899, upload-time = "2025-05-12T11:40:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/b8/42/989403abbdbb1346a1507083c02018bee3fedaef3f9648940c767d8c0958/gevent-25.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575", size = 1635771, upload-time = "2025-05-12T12:26:47.644Z" }, + { url = "https://files.pythonhosted.org/packages/58/c5/cf71423666a0b83db3d7e3f85788bc47d573fca5fe62b798fe2c4273de7c/gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f", size = 2909333, upload-time = "2025-05-12T11:11:34.883Z" }, + { url = "https://files.pythonhosted.org/packages/26/7e/d2f174ee8bec6eb85d961ca203bc599d059c857b8412e367b8fa206603a5/gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649", size = 1788420, upload-time = "2025-05-12T11:52:30.306Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f3/3aba8c147b9108e62ba348c726fe38ae69735a233db425565227336e8ce6/gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519", size = 1868854, upload-time = "2025-05-12T11:54:21.564Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b1/11a5453f8fcebe90a456471fad48bd154c6a62fcb96e3475a5e408d05fc8/gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec", size = 1833946, upload-time = "2025-05-12T12:00:05.514Z" }, + { url = "https://files.pythonhosted.org/packages/70/1c/37d4a62303f86e6af67660a8df38c1171b7290df61b358e618c6fea79567/gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837", size = 2070583, upload-time = "2025-05-12T11:33:02.803Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/3b14929ff28263aba1d268ea97bcf104be1a86ba6f6bb4633838e7a1905e/gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310", size = 1808341, upload-time = "2025-05-12T11:59:59.154Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fc/674ec819fb8a96e482e4d21f8baa43d34602dba09dfce7bbdc8700899d1b/gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c", size = 2137974, upload-time = "2025-05-12T11:40:54.78Z" }, + { url = "https://files.pythonhosted.org/packages/05/9a/048b7f5e28c54e4595ad4a8ad3c338fa89560e558db2bbe8273f44f030de/gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806", size = 1638344, upload-time = "2025-05-12T12:08:31.776Z" }, + { url = "https://files.pythonhosted.org/packages/10/25/2162b38d7b48e08865db6772d632bd1648136ce2bb50e340565e45607cad/gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc", size = 2928044, upload-time = "2025-05-12T11:11:36.33Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e0/dbd597a964ed00176da122ea759bf2a6c1504f1e9f08e185379f92dc355f/gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922", size = 1788751, upload-time = "2025-05-12T11:52:32.643Z" }, + { url = "https://files.pythonhosted.org/packages/f1/74/960cc4cf4c9c90eafbe0efc238cdf588862e8e278d0b8c0d15a0da4ed480/gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d", size = 1869766, upload-time = "2025-05-12T11:54:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/56/78/fa84b1c7db79b156929685db09a7c18c3127361dca18a09e998e98118506/gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d", size = 1835358, upload-time = "2025-05-12T12:00:06.794Z" }, + { url = "https://files.pythonhosted.org/packages/00/5c/bfefe3822bbca5b83bfad256c82251b3f5be13d52d14e17a786847b9b625/gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e", size = 2073071, upload-time = "2025-05-12T11:33:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/20/e4/08a77a3839a37db96393dea952e992d5846a881b887986dde62ead6b48a1/gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce", size = 1809805, upload-time = "2025-05-12T12:00:00.537Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ac/28848348f790c1283df74b0fc0a554271d0606676470f848eccf84eae42a/gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276", size = 2138305, upload-time = "2025-05-12T11:40:56.566Z" }, + { url = "https://files.pythonhosted.org/packages/52/9e/0e9e40facd2d714bfb00f71fc6dacaacc82c24c1c2e097bf6461e00dec9f/gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896", size = 1637444, upload-time = "2025-05-12T12:17:45.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/16/b71171e97ec7b4ded8669542f4369d88d5a289e2704efbbde51e858e062a/gevent-25.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0", size = 2937113, upload-time = "2025-05-12T11:12:03.191Z" }, + { url = "https://files.pythonhosted.org/packages/11/81/834da3c1ea5e71e4dc1a78a034a15f2813d9760d135464aae5d1f058a8c6/gevent-25.5.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1", size = 1291540, upload-time = "2025-05-12T11:11:55.456Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/c1/a82edae11d46c0d83481aacaa1e578fea21d94a1ef400afd734d47ad95ad/greenlet-3.2.2.tar.gz", hash = "sha256:ad053d34421a2debba45aa3cc39acf454acbcd025b3fc1a9f8a0dee237abd485", size = 185797, upload-time = "2025-05-09T19:47:35.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/66/910217271189cc3f32f670040235f4bf026ded8ca07270667d69c06e7324/greenlet-3.2.2-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c49e9f7c6f625507ed83a7485366b46cbe325717c60837f7244fc99ba16ba9d6", size = 267395, upload-time = "2025-05-09T14:50:45.357Z" }, + { url = "https://files.pythonhosted.org/packages/a8/36/8d812402ca21017c82880f399309afadb78a0aa300a9b45d741e4df5d954/greenlet-3.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3cc1a3ed00ecfea8932477f729a9f616ad7347a5e55d50929efa50a86cb7be7", size = 625742, upload-time = "2025-05-09T15:23:58.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/77/66d7b59dfb7cc1102b2f880bc61cb165ee8998c9ec13c96606ba37e54c77/greenlet-3.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c9896249fbef2c615853b890ee854f22c671560226c9221cfd27c995db97e5c", size = 637014, upload-time = "2025-05-09T15:24:47.025Z" }, + { url = "https://files.pythonhosted.org/packages/36/a7/ff0d408f8086a0d9a5aac47fa1b33a040a9fca89bd5a3f7b54d1cd6e2793/greenlet-3.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7409796591d879425997a518138889d8d17e63ada7c99edc0d7a1c22007d4907", size = 632874, upload-time = "2025-05-09T15:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/a1/75/1dc2603bf8184da9ebe69200849c53c3c1dca5b3a3d44d9f5ca06a930550/greenlet-3.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7791dcb496ec53d60c7f1c78eaa156c21f402dda38542a00afc3e20cae0f480f", size = 631652, upload-time = "2025-05-09T14:53:30.961Z" }, + { url = "https://files.pythonhosted.org/packages/7b/74/ddc8c3bd4c2c20548e5bf2b1d2e312a717d44e2eca3eadcfc207b5f5ad80/greenlet-3.2.2-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8009ae46259e31bc73dc183e402f548e980c96f33a6ef58cc2e7865db012e13", size = 580619, upload-time = "2025-05-09T14:53:42.049Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f2/40f26d7b3077b1c7ae7318a4de1f8ffc1d8ccbad8f1d8979bf5080250fd6/greenlet-3.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fd9fb7c941280e2c837b603850efc93c999ae58aae2b40765ed682a6907ebbc5", size = 1109809, upload-time = "2025-05-09T15:26:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/c5/21/9329e8c276746b0d2318b696606753f5e7b72d478adcf4ad9a975521ea5f/greenlet-3.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:00cd814b8959b95a546e47e8d589610534cfb71f19802ea8a2ad99d95d702057", size = 1133455, upload-time = "2025-05-09T14:53:55.823Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1e/0dca9619dbd736d6981f12f946a497ec21a0ea27262f563bca5729662d4d/greenlet-3.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:d0cb7d47199001de7658c213419358aa8937df767936506db0db7ce1a71f4a2f", size = 294991, upload-time = "2025-05-09T15:05:56.847Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9f/a47e19261747b562ce88219e5ed8c859d42c6e01e73da6fbfa3f08a7be13/greenlet-3.2.2-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:dcb9cebbf3f62cb1e5afacae90761ccce0effb3adaa32339a0670fe7805d8068", size = 268635, upload-time = "2025-05-09T14:50:39.007Z" }, + { url = "https://files.pythonhosted.org/packages/11/80/a0042b91b66975f82a914d515e81c1944a3023f2ce1ed7a9b22e10b46919/greenlet-3.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf3fc9145141250907730886b031681dfcc0de1c158f3cc51c092223c0f381ce", size = 628786, upload-time = "2025-05-09T15:24:00.692Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/8336bf1e691013f72a6ebab55da04db81a11f68e82bb691f434909fa1327/greenlet-3.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:efcdfb9df109e8a3b475c016f60438fcd4be68cd13a365d42b35914cdab4bb2b", size = 640866, upload-time = "2025-05-09T15:24:48.153Z" }, + { url = "https://files.pythonhosted.org/packages/f8/7e/f2a3a13e424670a5d08826dab7468fa5e403e0fbe0b5f951ff1bc4425b45/greenlet-3.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd139e4943547ce3a56ef4b8b1b9479f9e40bb47e72cc906f0f66b9d0d5cab3", size = 636752, upload-time = "2025-05-09T15:29:23.182Z" }, + { url = "https://files.pythonhosted.org/packages/fd/5d/ce4a03a36d956dcc29b761283f084eb4a3863401c7cb505f113f73af8774/greenlet-3.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71566302219b17ca354eb274dfd29b8da3c268e41b646f330e324e3967546a74", size = 636028, upload-time = "2025-05-09T14:53:32.854Z" }, + { url = "https://files.pythonhosted.org/packages/4b/29/b130946b57e3ceb039238413790dd3793c5e7b8e14a54968de1fe449a7cf/greenlet-3.2.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3091bc45e6b0c73f225374fefa1536cd91b1e987377b12ef5b19129b07d93ebe", size = 583869, upload-time = "2025-05-09T14:53:43.614Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/9f538dfe7f87b90ecc75e589d20cbd71635531a617a336c386d775725a8b/greenlet-3.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:44671c29da26539a5f142257eaba5110f71887c24d40df3ac87f1117df589e0e", size = 1112886, upload-time = "2025-05-09T15:27:01.304Z" }, + { url = "https://files.pythonhosted.org/packages/be/92/4b7deeb1a1e9c32c1b59fdca1cac3175731c23311ddca2ea28a8b6ada91c/greenlet-3.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c23ea227847c9dbe0b3910f5c0dd95658b607137614eb821e6cbaecd60d81cc6", size = 1138355, upload-time = "2025-05-09T14:53:58.011Z" }, + { url = "https://files.pythonhosted.org/packages/c5/eb/7551c751a2ea6498907b2fcbe31d7a54b602ba5e8eb9550a9695ca25d25c/greenlet-3.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:0a16fb934fcabfdfacf21d79e6fed81809d8cd97bc1be9d9c89f0e4567143d7b", size = 295437, upload-time = "2025-05-09T15:00:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a1/88fdc6ce0df6ad361a30ed78d24c86ea32acb2b563f33e39e927b1da9ea0/greenlet-3.2.2-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:df4d1509efd4977e6a844ac96d8be0b9e5aa5d5c77aa27ca9f4d3f92d3fcf330", size = 270413, upload-time = "2025-05-09T14:51:32.455Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/6c1caffd65490c68cd9bcec8cb7feb8ac7b27d38ba1fea121fdc1f2331dc/greenlet-3.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da956d534a6d1b9841f95ad0f18ace637668f680b1339ca4dcfb2c1837880a0b", size = 637242, upload-time = "2025-05-09T15:24:02.63Z" }, + { url = "https://files.pythonhosted.org/packages/98/28/088af2cedf8823b6b7ab029a5626302af4ca1037cf8b998bed3a8d3cb9e2/greenlet-3.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9c7b15fb9b88d9ee07e076f5a683027bc3befd5bb5d25954bb633c385d8b737e", size = 651444, upload-time = "2025-05-09T15:24:49.856Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0116ab876bb0bc7a81eadc21c3f02cd6100dcd25a1cf2a085a130a63a26a/greenlet-3.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:752f0e79785e11180ebd2e726c8a88109ded3e2301d40abced2543aa5d164275", size = 646067, upload-time = "2025-05-09T15:29:24.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/17/bb8f9c9580e28a94a9575da847c257953d5eb6e39ca888239183320c1c28/greenlet-3.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae572c996ae4b5e122331e12bbb971ea49c08cc7c232d1bd43150800a2d6c65", size = 648153, upload-time = "2025-05-09T14:53:34.716Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ee/7f31b6f7021b8df6f7203b53b9cc741b939a2591dcc6d899d8042fcf66f2/greenlet-3.2.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02f5972ff02c9cf615357c17ab713737cccfd0eaf69b951084a9fd43f39833d3", size = 603865, upload-time = "2025-05-09T14:53:45.738Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2d/759fa59323b521c6f223276a4fc3d3719475dc9ae4c44c2fe7fc750f8de0/greenlet-3.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4fefc7aa68b34b9224490dfda2e70ccf2131368493add64b4ef2d372955c207e", size = 1119575, upload-time = "2025-05-09T15:27:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/30/05/356813470060bce0e81c3df63ab8cd1967c1ff6f5189760c1a4734d405ba/greenlet-3.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a31ead8411a027c2c4759113cf2bd473690517494f3d6e4bf67064589afcd3c5", size = 1147460, upload-time = "2025-05-09T14:54:00.315Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/b2a26a309a04fb844c7406a4501331b9400e1dd7dd64d3450472fd47d2e1/greenlet-3.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b24c7844c0a0afc3ccbeb0b807adeefb7eff2b5599229ecedddcfeb0ef333bec", size = 296239, upload-time = "2025-05-09T14:57:17.633Z" }, + { url = "https://files.pythonhosted.org/packages/89/30/97b49779fff8601af20972a62cc4af0c497c1504dfbb3e93be218e093f21/greenlet-3.2.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3ab7194ee290302ca15449f601036007873028712e92ca15fc76597a0aeb4c59", size = 269150, upload-time = "2025-05-09T14:50:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/21/30/877245def4220f684bc2e01df1c2e782c164e84b32e07373992f14a2d107/greenlet-3.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dc5c43bb65ec3669452af0ab10729e8fdc17f87a1f2ad7ec65d4aaaefabf6bf", size = 637381, upload-time = "2025-05-09T15:24:12.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/16/adf937908e1f913856b5371c1d8bdaef5f58f251d714085abeea73ecc471/greenlet-3.2.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:decb0658ec19e5c1f519faa9a160c0fc85a41a7e6654b3ce1b44b939f8bf1325", size = 651427, upload-time = "2025-05-09T15:24:51.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/49/6d79f58fa695b618654adac64e56aff2eeb13344dc28259af8f505662bb1/greenlet-3.2.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fadd183186db360b61cb34e81117a096bff91c072929cd1b529eb20dd46e6c5", size = 645795, upload-time = "2025-05-09T15:29:26.673Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e6/28ed5cb929c6b2f001e96b1d0698c622976cd8f1e41fe7ebc047fa7c6dd4/greenlet-3.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1919cbdc1c53ef739c94cf2985056bcc0838c1f217b57647cbf4578576c63825", size = 648398, upload-time = "2025-05-09T14:53:36.61Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/b200194e25ae86bc57077f695b6cc47ee3118becf54130c5514456cf8dac/greenlet-3.2.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3885f85b61798f4192d544aac7b25a04ece5fe2704670b4ab73c2d2c14ab740d", size = 606795, upload-time = "2025-05-09T14:53:47.039Z" }, + { url = "https://files.pythonhosted.org/packages/f8/c8/ba1def67513a941154ed8f9477ae6e5a03f645be6b507d3930f72ed508d3/greenlet-3.2.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:85f3e248507125bf4af607a26fd6cb8578776197bd4b66e35229cdf5acf1dfbf", size = 1117976, upload-time = "2025-05-09T15:27:06.542Z" }, + { url = "https://files.pythonhosted.org/packages/c3/30/d0e88c1cfcc1b3331d63c2b54a0a3a4a950ef202fb8b92e772ca714a9221/greenlet-3.2.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1e76106b6fc55fa3d6fe1c527f95ee65e324a13b62e243f77b48317346559708", size = 1145509, upload-time = "2025-05-09T14:54:02.223Z" }, + { url = "https://files.pythonhosted.org/packages/90/2e/59d6491834b6e289051b252cf4776d16da51c7c6ca6a87ff97e3a50aa0cd/greenlet-3.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:fe46d4f8e94e637634d54477b0cfabcf93c53f29eedcbdeecaf2af32029b4421", size = 296023, upload-time = "2025-05-09T14:53:24.157Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/8a73aace5a5335a1cba56d0da71b7bd93e450f17d372c5b7c5fa547557e9/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba30e88607fb6990544d84caf3c706c4b48f629e18853fc6a646f82db9629418", size = 629911, upload-time = "2025-05-09T15:24:22.376Z" }, + { url = "https://files.pythonhosted.org/packages/48/08/c8b8ebac4e0c95dcc68ec99198842e7db53eda4ab3fb0a4e785690883991/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:055916fafad3e3388d27dd68517478933a97edc2fc54ae79d3bec827de2c64c4", size = 635251, upload-time = "2025-05-09T15:24:52.205Z" }, + { url = "https://files.pythonhosted.org/packages/37/26/7db30868f73e86b9125264d2959acabea132b444b88185ba5c462cb8e571/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2593283bf81ca37d27d110956b79e8723f9aa50c4bcdc29d3c0543d4743d2763", size = 632620, upload-time = "2025-05-09T15:29:28.051Z" }, + { url = "https://files.pythonhosted.org/packages/10/ec/718a3bd56249e729016b0b69bee4adea0dfccf6ca43d147ef3b21edbca16/greenlet-3.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89c69e9a10670eb7a66b8cef6354c24671ba241f46152dd3eed447f79c29fb5b", size = 628851, upload-time = "2025-05-09T14:53:38.472Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1c79286a76bc62ccdc1387291464af16a4204ea717f24e77b0acd623b99/greenlet-3.2.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a98600899ca1ca5d3a2590974c9e3ec259503b2d6ba6527605fcd74e08e207", size = 593718, upload-time = "2025-05-09T14:53:48.313Z" }, + { url = "https://files.pythonhosted.org/packages/cd/41/96ba2bf948f67b245784cd294b84e3d17933597dffd3acdb367a210d1949/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b50a8c5c162469c3209e5ec92ee4f95c8231b11db6a04db09bbe338176723bb8", size = 1105752, upload-time = "2025-05-09T15:27:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/68/3b/3b97f9d33c1f2eb081759da62bd6162159db260f602f048bc2f36b4c453e/greenlet-3.2.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:45f9f4853fb4cc46783085261c9ec4706628f3b57de3e68bae03e8f8b3c0de51", size = 1125170, upload-time = "2025-05-09T14:54:04.082Z" }, + { url = "https://files.pythonhosted.org/packages/31/df/b7d17d66c8d0f578d2885a3d8f565e9e4725eacc9d3fdc946d0031c055c4/greenlet-3.2.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:9ea5231428af34226c05f927e16fc7f6fa5e39e3ad3cd24ffa48ba53a47f4240", size = 269899, upload-time = "2025-05-09T14:54:01.581Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hatch" +version = "1.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "hatchling" }, + { name = "httpx" }, + { name = "hyperlink" }, + { name = "keyring" }, + { name = "packaging" }, + { name = "pexpect" }, + { name = "platformdirs" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "tomli-w" }, + { name = "tomlkit" }, + { name = "userpath" }, + { name = "uv" }, + { name = "virtualenv" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/43/c0b37db0e857a44ce5ffdb7e8a9b8fa6425d0b74dea698fafcd9bddb50d1/hatch-1.14.1.tar.gz", hash = "sha256:ca1aff788f8596b0dd1f8f8dfe776443d2724a86b1976fabaf087406ba3d0713", size = 5188180, upload-time = "2025-04-07T04:16:04.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/40/19c0935bf9f25808541a0e3144ac459de696c5b6b6d4511a98d456c69604/hatch-1.14.1-py3-none-any.whl", hash = "sha256:39cdaa59e47ce0c5505d88a951f4324a9c5aafa17e4a877e2fde79b36ab66c21", size = 125770, upload-time = "2025-04-07T04:16:02.525Z" }, +] + +[[package]] +name = "hatchling" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pathspec" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "trove-classifiers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/8a/cc1debe3514da292094f1c3a700e4ca25442489731ef7c0814358816bb03/hatchling-1.27.0.tar.gz", hash = "sha256:971c296d9819abb3811112fc52c7a9751c8d381898f36533bb16f9791e941fd6", size = 54983, upload-time = "2024-12-15T17:08:11.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/e7/ae38d7a6dfba0533684e0b2136817d667588ae3ec984c1a4e5df5eb88482/hatchling-1.27.0-py3-none-any.whl", hash = "sha256:d3a2f3567c4f926ea39849cdf924c7e99e6686c9c8e288ae1037c8fa2a5d937b", size = 75794, upload-time = "2024-12-15T17:08:10.364Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "hyperlink" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/51/1947bd81d75af87e3bb9e34593a4cf118115a8feb451ce7a69044ef1412e/hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b", size = 140743, upload-time = "2021-01-08T05:51:20.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/aa/8caf6a0a3e62863cbb9dab27135660acba46903b703e224f14f447e57934/hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4", size = 74638, upload-time = "2021-01-08T05:51:22.906Z" }, +] + +[[package]] +name = "identify" +version = "2.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-tarfile", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912, upload-time = "2024-08-20T03:39:27.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825, upload-time = "2024-08-20T03:39:25.966Z" }, +] + +[[package]] +name = "jaraco-functools" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", size = 19159, upload-time = "2024-09-27T19:47:09.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", size = 10187, upload-time = "2024-09-27T19:47:07.14Z" }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, +] + +[[package]] +name = "keyring" +version = "25.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.12'" }, + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750, upload-time = "2024-12-25T15:26:45.782Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085, upload-time = "2024-12-25T15:26:44.377Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/a0/834b0cebabbfc7e311f30b46c8188790a37f89fc8d756660346fe5abfd09/more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3", size = 127671, upload-time = "2025-04-22T14:17:41.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710, upload-time = "2025-05-12T14:41:58.025Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158, upload-time = "2025-05-12T14:41:56.217Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest-timeout" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739, upload-time = "2022-08-13T16:22:46.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221, upload-time = "2022-08-13T16:22:44.457Z" }, +] + +[[package]] +name = "setuptools" +version = "80.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/d2/ec1acaaff45caed5c2dedb33b67055ba9d4e96b091094df90762e60135fe/setuptools-80.8.0.tar.gz", hash = "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257", size = 1319720, upload-time = "2025-05-20T14:02:53.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0", size = 1201470, upload-time = "2025-05-20T14:02:51.348Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tomli-w" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz", hash = "sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021", size = 7184, upload-time = "2025-01-15T12:07:24.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885, upload-time = "2024-08-14T08:19:41.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955, upload-time = "2024-08-14T08:19:40.05Z" }, +] + +[[package]] +name = "tox" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/dcec0c00321a107f7f697fd00754c5112572ea6dcacb40b16d8c3eea7c37/tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca", size = 197260, upload-time = "2025-05-13T15:04:28.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/14/f58b4087cf248b18c795b5c838c7a8d1428dfb07cb468dad3ec7f54041ab/tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224", size = 172761, upload-time = "2025-05-13T15:04:26.207Z" }, +] + +[[package]] +name = "trove-classifiers" +version = "2025.5.9.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/04/1cd43f72c241fedcf0d9a18d0783953ee301eac9e5d9db1df0f0f089d9af/trove_classifiers-2025.5.9.12.tar.gz", hash = "sha256:7ca7c8a7a76e2cd314468c677c69d12cc2357711fcab4a60f87994c1589e5cb5", size = 16940, upload-time = "2025-05-09T12:04:48.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/ef/c6deb083748be3bcad6f471b6ae983950c161890bf5ae1b2af80cc56c530/trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce", size = 14119, upload-time = "2025-05-09T12:04:46.38Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "userpath" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/b7/30753098208505d7ff9be5b3a32112fb8a4cb3ddfccbbb7ba9973f2e29ff/userpath-1.9.2.tar.gz", hash = "sha256:6c52288dab069257cc831846d15d48133522455d4677ee69a9781f11dbefd815", size = 11140, upload-time = "2024-02-29T21:39:08.742Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/99/3ec6335ded5b88c2f7ed25c56ffd952546f7ed007ffb1e1539dc3b57015a/userpath-1.9.2-py3-none-any.whl", hash = "sha256:2cbf01a23d655a1ff8fc166dfb78da1b641d1ceabf0fe5f970767d380b14e89d", size = 9065, upload-time = "2024-02-29T21:39:07.551Z" }, +] + +[[package]] +name = "uv" +version = "0.7.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/4f/c26b354fc791fb716a990f6b0147c0b5d69351400030654827fb920fd79b/uv-0.7.8.tar.gz", hash = "sha256:a59d6749587946d63d371170d8f69d168ca8f4eade5cf880ad3be2793ea29c77", size = 3258494, upload-time = "2025-05-24T00:28:18.241Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/48/dd73c6a9b7b18dc1784b243cd5a93c14db34876c5a5cbb215e00be285e05/uv-0.7.8-py3-none-linux_armv6l.whl", hash = "sha256:ff1b7e4bc8a1d260062782ad34d12ce0df068df01d4a0f61d0ddc20aba1a5688", size = 16741809, upload-time = "2025-05-24T00:27:20.873Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bd/0bc26f1f4f476cff93c8ce2d258819b10b9a4e41a9825405788ef25a2300/uv-0.7.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b83866be6a69f680f3d2e36b3befd2661b5596e59e575e266e7446b28efa8319", size = 16836506, upload-time = "2025-05-24T00:27:25.229Z" }, + { url = "https://files.pythonhosted.org/packages/26/28/1573e22b5f109f7779ddf64cb11e8e475ac05cf94e6b79ad3a4494c8c39c/uv-0.7.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f749b58a5c348c455083781c92910e49b4ddba85c591eb67e97a8b84db03ef9b", size = 15642479, upload-time = "2025-05-24T00:27:28.866Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f1/3d403896ea1edeea9109cab924e6a724ed7f5fbdabe8e5e9f3e3aa2be95a/uv-0.7.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c058ee0f8c20b0942bd9f5c83a67b46577fa79f5691df8867b8e0f2d74cbadb1", size = 16043352, upload-time = "2025-05-24T00:27:31.911Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/a914e491af320be503db26ff57f1b328738d1d7419cdb690e6e31d87ae16/uv-0.7.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a07bdf9d6aadef40dd4edbe209bca698a3d3244df5285d40d2125f82455519c", size = 16413446, upload-time = "2025-05-24T00:27:35.363Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cc/a396870530db7661eac080d276eba25df1b6c930f50c721f8402370acd12/uv-0.7.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13af6b94563f25bdca6bb73e294648af9c0b165af5bb60f0c913ab125ec45e06", size = 17188599, upload-time = "2025-05-24T00:27:38.979Z" }, + { url = "https://files.pythonhosted.org/packages/d0/96/299bd3895d630e28593dcc54f4c4dbd72e12b557288c6d153987bbd62f34/uv-0.7.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4acc09c06d6cf7a27e0f1de4edb8c1698b8a3ffe34f322b10f4c145989e434b9", size = 18105049, upload-time = "2025-05-24T00:27:42.194Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a4/9fa0b6a4540950fe7fa66d37c44228d6ad7bb6d42f66e16f4f96e20fd50c/uv-0.7.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9221a9679f2ffd031b71b735b84f58d5a2f1adf9bfa59c8e82a5201dad7db466", size = 17777603, upload-time = "2025-05-24T00:27:45.695Z" }, + { url = "https://files.pythonhosted.org/packages/d7/62/988cca0f1723406ff22edd6a9fb5e3e1d4dd0af103d8c3a64effadc685fd/uv-0.7.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:409cee21edcaf4a7c714893656ab4dd0814a15659cb4b81c6929cbb75cd2d378", size = 22222113, upload-time = "2025-05-24T00:27:49.172Z" }, + { url = "https://files.pythonhosted.org/packages/06/36/0e7943d9415560aa9fdd775d0bb4b9c06b69c543f0647210e5b84776658b/uv-0.7.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81ac0bb371979f48d1293f9c1bee691680ea6a724f16880c8f76718f5ff50049", size = 17454597, upload-time = "2025-05-24T00:27:52.478Z" }, + { url = "https://files.pythonhosted.org/packages/bb/70/666be8dbc6a49e1a096f4577d69c4e6f78b3d9228fa2844d1bece21f5cd0/uv-0.7.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3c620cecd6f3cdab59b316f41c2b1c4d1b709d9d5226cadeec370cfeed56f80c", size = 16335744, upload-time = "2025-05-24T00:27:55.657Z" }, + { url = "https://files.pythonhosted.org/packages/24/a5/c1fbffc8b62121c0d07aa66e7e5135065ff881ebb85ba307664125f4c51c/uv-0.7.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0c691090ff631dde788c8f4f1b1ea20f9deb9d805289796dcf10bc4a144a817e", size = 16439468, upload-time = "2025-05-24T00:27:58.599Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/a079658721b88d483c97a1765f9fd4f1b8b4fa601f2889d86824244861f2/uv-0.7.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4a117fe3806ba4ebb9c68fdbf91507e515a883dfab73fa863df9bc617d6de7a3", size = 16740156, upload-time = "2025-05-24T00:28:01.657Z" }, + { url = "https://files.pythonhosted.org/packages/14/69/a2d110786c4cf093d788cfcde9e99c634af087555f0bf9ceafc009d051ed/uv-0.7.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:91d022235b39e59bab4bce7c4b634dc67e16fa89725cdfb2149a6ef7eaf6d784", size = 17569652, upload-time = "2025-05-24T00:28:04.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/56/db6db0dc20114b76eb48dbd5167a26a2ebe51e8b604b4e84c5ef84ef4103/uv-0.7.8-py3-none-win32.whl", hash = "sha256:6ebe252f34c50b09b7f641f8e603d7b627f579c76f181680c757012b808be456", size = 16958006, upload-time = "2025-05-24T00:28:07.996Z" }, + { url = "https://files.pythonhosted.org/packages/4b/80/5c78a9adc50fa3b7cca3a0c1245dff8c74d906ab53c3503b1f8133243930/uv-0.7.8-py3-none-win_amd64.whl", hash = "sha256:b5b62ca8a1bea5fdbf8a6372eabb03376dffddb5d139688bbb488c0719fa52fc", size = 18457129, upload-time = "2025-05-24T00:28:11.844Z" }, + { url = "https://files.pythonhosted.org/packages/15/52/fd76b44942ac308e1dbbebea8b23de67a0f891a54d5e51346c3c3564dd9b/uv-0.7.8-py3-none-win_arm64.whl", hash = "sha256:ad79388b0c6eff5383b963d8d5ddcb7fbb24b0b82bf5d0c8b1bdbfbe445cb868", size = 17177058, upload-time = "2025-05-24T00:28:15.561Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" }, +] + +[[package]] +name = "zope-event" +version = "5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/c2/427f1867bb96555d1d34342f1dd97f8c420966ab564d58d18469a1db8736/zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd", size = 17350, upload-time = "2023-06-23T06:28:35.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/42/f8dbc2b9ad59e927940325a22d6d3931d630c3644dae7e2369ef5d9ba230/zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", size = 6824, upload-time = "2023-06-23T06:28:32.652Z" }, +] + +[[package]] +name = "zope-interface" +version = "7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/93/9210e7606be57a2dfc6277ac97dcc864fd8d39f142ca194fdc186d596fda/zope.interface-7.2.tar.gz", hash = "sha256:8b49f1a3d1ee4cdaf5b32d2e738362c7f5e40ac8b46dd7d1a65e82a4872728fe", size = 252960, upload-time = "2024-11-28T08:45:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/71/e6177f390e8daa7e75378505c5ab974e0bf59c1d3b19155638c7afbf4b2d/zope.interface-7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce290e62229964715f1011c3dbeab7a4a1e4971fd6f31324c4519464473ef9f2", size = 208243, upload-time = "2024-11-28T08:47:29.781Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/7e5f4226bef540f6d55acfd95cd105782bc6ee044d9b5587ce2c95558a5e/zope.interface-7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:05b910a5afe03256b58ab2ba6288960a2892dfeef01336dc4be6f1b9ed02ab0a", size = 208759, upload-time = "2024-11-28T08:47:31.908Z" }, + { url = "https://files.pythonhosted.org/packages/28/ea/fdd9813c1eafd333ad92464d57a4e3a82b37ae57c19497bcffa42df673e4/zope.interface-7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550f1c6588ecc368c9ce13c44a49b8d6b6f3ca7588873c679bd8fd88a1b557b6", size = 254922, upload-time = "2024-11-28T09:18:11.795Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d3/0000a4d497ef9fbf4f66bb6828b8d0a235e690d57c333be877bec763722f/zope.interface-7.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ef9e2f865721553c6f22a9ff97da0f0216c074bd02b25cf0d3af60ea4d6931d", size = 249367, upload-time = "2024-11-28T08:48:24.238Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e5/0b359e99084f033d413419eff23ee9c2bd33bca2ca9f4e83d11856f22d10/zope.interface-7.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27f926f0dcb058211a3bb3e0e501c69759613b17a553788b2caeb991bed3b61d", size = 254488, upload-time = "2024-11-28T08:48:28.816Z" }, + { url = "https://files.pythonhosted.org/packages/7b/90/12d50b95f40e3b2fc0ba7f7782104093b9fd62806b13b98ef4e580f2ca61/zope.interface-7.2-cp310-cp310-win_amd64.whl", hash = "sha256:144964649eba4c5e4410bb0ee290d338e78f179cdbfd15813de1a664e7649b3b", size = 211947, upload-time = "2024-11-28T08:48:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/98/7d/2e8daf0abea7798d16a58f2f3a2bf7588872eee54ac119f99393fdd47b65/zope.interface-7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1909f52a00c8c3dcab6c4fad5d13de2285a4b3c7be063b239b8dc15ddfb73bd2", size = 208776, upload-time = "2024-11-28T08:47:53.009Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/0c03c7170fe61d0d371e4c7ea5b62b8cb79b095b3d630ca16719bf8b7b18/zope.interface-7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:80ecf2451596f19fd607bb09953f426588fc1e79e93f5968ecf3367550396b22", size = 209296, upload-time = "2024-11-28T08:47:57.993Z" }, + { url = "https://files.pythonhosted.org/packages/49/b4/451f19448772b4a1159519033a5f72672221e623b0a1bd2b896b653943d8/zope.interface-7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:033b3923b63474800b04cba480b70f6e6243a62208071fc148354f3f89cc01b7", size = 260997, upload-time = "2024-11-28T09:18:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/5aa4461c10718062c8f8711161faf3249d6d3679c24a0b81dd6fc8ba1dd3/zope.interface-7.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a102424e28c6b47c67923a1f337ede4a4c2bba3965b01cf707978a801fc7442c", size = 255038, upload-time = "2024-11-28T08:48:26.381Z" }, + { url = "https://files.pythonhosted.org/packages/9f/aa/1a28c02815fe1ca282b54f6705b9ddba20328fabdc37b8cf73fc06b172f0/zope.interface-7.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25e6a61dcb184453bb00eafa733169ab6d903e46f5c2ace4ad275386f9ab327a", size = 259806, upload-time = "2024-11-28T08:48:30.78Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2c/82028f121d27c7e68632347fe04f4a6e0466e77bb36e104c8b074f3d7d7b/zope.interface-7.2-cp311-cp311-win_amd64.whl", hash = "sha256:3f6771d1647b1fc543d37640b45c06b34832a943c80d1db214a37c31161a93f1", size = 212305, upload-time = "2024-11-28T08:49:14.525Z" }, + { url = "https://files.pythonhosted.org/packages/68/0b/c7516bc3bad144c2496f355e35bd699443b82e9437aa02d9867653203b4a/zope.interface-7.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:086ee2f51eaef1e4a52bd7d3111a0404081dadae87f84c0ad4ce2649d4f708b7", size = 208959, upload-time = "2024-11-28T08:47:47.788Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e9/1463036df1f78ff8c45a02642a7bf6931ae4a38a4acd6a8e07c128e387a7/zope.interface-7.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21328fcc9d5b80768bf051faa35ab98fb979080c18e6f84ab3f27ce703bce465", size = 209357, upload-time = "2024-11-28T08:47:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/07/a8/106ca4c2add440728e382f1b16c7d886563602487bdd90004788d45eb310/zope.interface-7.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6dd02ec01f4468da0f234da9d9c8545c5412fef80bc590cc51d8dd084138a89", size = 264235, upload-time = "2024-11-28T09:18:15.56Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ca/57286866285f4b8a4634c12ca1957c24bdac06eae28fd4a3a578e30cf906/zope.interface-7.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e7da17f53e25d1a3bde5da4601e026adc9e8071f9f6f936d0fe3fe84ace6d54", size = 259253, upload-time = "2024-11-28T08:48:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/2103587ebc989b455cf05e858e7fbdfeedfc3373358320e9c513428290b1/zope.interface-7.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cab15ff4832580aa440dc9790b8a6128abd0b88b7ee4dd56abacbc52f212209d", size = 264702, upload-time = "2024-11-28T08:48:37.363Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c7/3c67562e03b3752ba4ab6b23355f15a58ac2d023a6ef763caaca430f91f2/zope.interface-7.2-cp312-cp312-win_amd64.whl", hash = "sha256:29caad142a2355ce7cfea48725aa8bcf0067e2b5cc63fcf5cd9f97ad12d6afb5", size = 212466, upload-time = "2024-11-28T08:49:14.397Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3b/e309d731712c1a1866d61b5356a069dd44e5b01e394b6cb49848fa2efbff/zope.interface-7.2-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:3e0350b51e88658d5ad126c6a57502b19d5f559f6cb0a628e3dc90442b53dd98", size = 208961, upload-time = "2024-11-28T08:48:29.865Z" }, + { url = "https://files.pythonhosted.org/packages/49/65/78e7cebca6be07c8fc4032bfbb123e500d60efdf7b86727bb8a071992108/zope.interface-7.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15398c000c094b8855d7d74f4fdc9e73aa02d4d0d5c775acdef98cdb1119768d", size = 209356, upload-time = "2024-11-28T08:48:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/627384b745310d082d29e3695db5f5a9188186676912c14b61a78bbc6afe/zope.interface-7.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:802176a9f99bd8cc276dcd3b8512808716492f6f557c11196d42e26c01a69a4c", size = 264196, upload-time = "2024-11-28T09:18:17.584Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/54548df6dc73e30ac6c8a7ff1da73ac9007ba38f866397091d5a82237bd3/zope.interface-7.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb23f58a446a7f09db85eda09521a498e109f137b85fb278edb2e34841055398", size = 259237, upload-time = "2024-11-28T08:48:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, + { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/55/bd0487e86679db1823fc9ee0d8c9c78ae2413d34c0b461193b5f4c31d22f/zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9", size = 788701, upload-time = "2024-07-15T00:13:27.351Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8a/ccb516b684f3ad987dfee27570d635822e3038645b1a950c5e8022df1145/zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880", size = 633678, upload-time = "2024-07-15T00:13:30.24Z" }, + { url = "https://files.pythonhosted.org/packages/12/89/75e633d0611c028e0d9af6df199423bf43f54bea5007e6718ab7132e234c/zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc", size = 4941098, upload-time = "2024-07-15T00:13:32.526Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7a/bd7f6a21802de358b63f1ee636ab823711c25ce043a3e9f043b4fcb5ba32/zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573", size = 5308798, upload-time = "2024-07-15T00:13:34.925Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/775f851a4a65013e88ca559c8ae42ac1352db6fcd96b028d0df4d7d1d7b4/zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391", size = 5341840, upload-time = "2024-07-15T00:13:37.376Z" }, + { url = "https://files.pythonhosted.org/packages/09/4f/0cc49570141dd72d4d95dd6fcf09328d1b702c47a6ec12fbed3b8aed18a5/zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e", size = 5440337, upload-time = "2024-07-15T00:13:39.772Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7c/aaa7cd27148bae2dc095191529c0570d16058c54c4597a7d118de4b21676/zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd", size = 4861182, upload-time = "2024-07-15T00:13:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/ac/eb/4b58b5c071d177f7dc027129d20bd2a44161faca6592a67f8fcb0b88b3ae/zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4", size = 4932936, upload-time = "2024-07-15T00:13:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/44/f9/21a5fb9bb7c9a274b05ad700a82ad22ce82f7ef0f485980a1e98ed6e8c5f/zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea", size = 5464705, upload-time = "2024-07-15T00:13:46.822Z" }, + { url = "https://files.pythonhosted.org/packages/49/74/b7b3e61db3f88632776b78b1db597af3f44c91ce17d533e14a25ce6a2816/zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2", size = 4857882, upload-time = "2024-07-15T00:13:49.297Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7f/d8eb1cb123d8e4c541d4465167080bec88481ab54cd0b31eb4013ba04b95/zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9", size = 4697672, upload-time = "2024-07-15T00:13:51.447Z" }, + { url = "https://files.pythonhosted.org/packages/5e/05/f7dccdf3d121309b60342da454d3e706453a31073e2c4dac8e1581861e44/zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a", size = 5206043, upload-time = "2024-07-15T00:13:53.587Z" }, + { url = "https://files.pythonhosted.org/packages/86/9d/3677a02e172dccd8dd3a941307621c0cbd7691d77cb435ac3c75ab6a3105/zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0", size = 5667390, upload-time = "2024-07-15T00:13:56.137Z" }, + { url = "https://files.pythonhosted.org/packages/41/7e/0012a02458e74a7ba122cd9cafe491facc602c9a17f590367da369929498/zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c", size = 5198901, upload-time = "2024-07-15T00:13:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/65/3a/8f715b97bd7bcfc7342d8adcd99a026cb2fb550e44866a3b6c348e1b0f02/zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813", size = 430596, upload-time = "2024-07-15T00:14:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/19/b7/b2b9eca5e5a01111e4fe8a8ffb56bdcdf56b12448a24effe6cfe4a252034/zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4", size = 495498, upload-time = "2024-07-15T00:14:02.741Z" }, + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, +] From b1bb46696b3f5d1b72bca4c2b391a5fbb8297376 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 21 Jul 2026 17:40:37 +0200 Subject: [PATCH 268/275] Fix overeager SIM cleanups and restore testing extra Keep OSError suppression in Channel.__del__, simplify _no_longer_opened without a dummy callback, ignore SIM105/SIM115, and keep the published testing extra for pip install. Co-authored-by: Cursor AI Co-authored-by: Cursor Grok 4.5 --- pyproject.toml | 11 +++++++-- src/execnet/gateway_base.py | 20 ++++++++--------- src/execnet/script/socketserver.py | 2 +- src/execnet/script/socketserverservice.py | 2 +- uv.lock | 27 ++++++++++++++--------- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a5454de8..fcd2b4b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ "Topic :: System :: Networking", ] -[dependency-groups] +[project.optional-dependencies] testing = [ "pre-commit", "pytest>8.0", @@ -45,6 +45,11 @@ testing = [ "gevent", ] +[dependency-groups] +testing = [ + "execnet[testing]", +] + [project.urls] Homepage = "https://execnet.readthedocs.io/en/latest/" @@ -80,7 +85,9 @@ ignore = [ "PLW2901", # for loop variable overwritten by assignment target "PLR5501", # Use `elif` instead of `else` then `if` "UP031", # Use format specifiers instead of percent format - + # flake8-simplify: overeager for this codebase + "SIM105", # try-except-pass -> suppress (bad with dynamic exc types / shutdown) + "SIM115", # open() without context manager (intentional long-lived handles) ] [tool.ruff.lint.isort] diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 4fb6af0c..53dbf38c 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -491,7 +491,7 @@ def trace(*msg: object) -> None: fn = os.path.join(tempfile.gettempdir(), "execnet-debug-%d" % pid) # sys.stderr.write("execnet-debug at %r" % (fn,)) - debugfile = open(fn, "w") # noqa: SIM115 + debugfile = open(fn, "w") def trace(*msg: object) -> None: try: @@ -499,7 +499,7 @@ def trace(*msg: object) -> None: debugfile.write(line + "\n") debugfile.flush() except Exception as exc: - try: # noqa: SIM105 + try: sys.stderr.write(f"[{pid}] exception during tracing: {exc!r}\n") except Exception: pass # nothing we can do, likely interpreter-shutdown @@ -788,7 +788,7 @@ def __del__(self) -> None: msgcode = Message.CHANNEL_LAST_MESSAGE else: msgcode = Message.CHANNEL_CLOSE - with suppress(KeyError, ValueError): # ignore problems with sending + with suppress(OSError, ValueError): # ignore problems with sending self.gateway._send(msgcode, self.id) def _getremoteerror(self): @@ -1001,11 +1001,11 @@ def channels(self) -> list[Channel]: # def _no_longer_opened(self, id: int) -> None: self._channels.pop(id, None) - callback, endmarker, _strconfig = self._callbacks.pop( - id, (lambda x: None, NO_ENDMARKER_WANTED, None) - ) - if endmarker is not NO_ENDMARKER_WANTED: - callback(endmarker) + item = self._callbacks.pop(id, None) + if item is not None: + callback, endmarker, _strconfig = item + if endmarker is not NO_ENDMARKER_WANTED: + callback(endmarker) def _local_close(self, id: int, remoteerror=None, sendonly: bool = False) -> None: channel = self._channels.get(id) @@ -1761,8 +1761,8 @@ def init_popen_io(execmodel: ExecModel) -> Popen2IO: io = Popen2IO(sys.stdout, sys.stdin, execmodel) import tempfile - sys.stdin = tempfile.TemporaryFile("r") # noqa: SIM115 - sys.stdout = tempfile.TemporaryFile("w") # noqa: SIM115 + sys.stdin = tempfile.TemporaryFile("r") + sys.stdout = tempfile.TemporaryFile("w") else: try: devnull = os.devnull diff --git a/src/execnet/script/socketserver.py b/src/execnet/script/socketserver.py index 1a06ab29..fa98743c 100644 --- a/src/execnet/script/socketserver.py +++ b/src/execnet/script/socketserver.py @@ -33,7 +33,7 @@ debug = 0 if debug: # and not os.isatty(sys.stdin.fileno()) - f = open("/tmp/execnet-socket-pyout.log", "w") # noqa: SIM115 + f = open("/tmp/execnet-socket-pyout.log", "w") old = sys.stdout, sys.stderr sys.stdout = sys.stderr = f diff --git a/src/execnet/script/socketserverservice.py b/src/execnet/script/socketserverservice.py index 1dbc4140..18e375c4 100644 --- a/src/execnet/script/socketserverservice.py +++ b/src/execnet/script/socketserverservice.py @@ -51,7 +51,7 @@ def SvcDoRun(self) -> None: # Redirect stdout and stderr to prevent "IOError: [Errno 9] # Bad file descriptor". Windows services don't have functional # output streams. - sys.stdout = sys.stderr = open("nul", "w") # noqa: SIM115 + sys.stdout = sys.stderr = open("nul", "w") # Write a 'started' event to the event log... win32evtlogutil.ReportEvent( diff --git a/uv.lock b/uv.lock index 7bc44d78..a2efaa64 100644 --- a/uv.lock +++ b/uv.lock @@ -202,7 +202,7 @@ wheels = [ name = "execnet" source = { editable = "." } -[package.dev-dependencies] +[package.optional-dependencies] testing = [ { name = "gevent" }, { name = "hatch" }, @@ -213,18 +213,25 @@ testing = [ { name = "uv" }, ] +[package.dev-dependencies] +testing = [ + { name = "execnet", extra = ["testing"] }, +] + [package.metadata] +requires-dist = [ + { name = "gevent", marker = "extra == 'testing'" }, + { name = "hatch", marker = "extra == 'testing'" }, + { name = "pre-commit", marker = "extra == 'testing'" }, + { name = "pytest", marker = "extra == 'testing'", specifier = ">8.0" }, + { name = "pytest-timeout", marker = "extra == 'testing'" }, + { name = "tox", marker = "extra == 'testing'" }, + { name = "uv", marker = "extra == 'testing'" }, +] +provides-extras = ["testing"] [package.metadata.requires-dev] -testing = [ - { name = "gevent" }, - { name = "hatch" }, - { name = "pre-commit" }, - { name = "pytest", specifier = ">8.0" }, - { name = "pytest-timeout" }, - { name = "tox" }, - { name = "uv" }, -] +testing = [{ name = "execnet", extras = ["testing"] }] [[package]] name = "filelock" From 652baafd223379c947a863dc7213fabb667dcfc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:40:50 +0000 Subject: [PATCH 269/275] build(deps): bump pygments from 2.19.1 to 2.20.0 Bumps [pygments](https://github.com/pygments/pygments) from 2.19.1 to 2.20.0. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.19.1...2.20.0) --- updated-dependencies: - dependency-name: pygments dependency-version: 2.20.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index a2efaa64..69aa3b13 100644 --- a/uv.lock +++ b/uv.lock @@ -659,11 +659,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.1" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] From 8d3926b3f9d1e15e735d6f16aa920dd5a48078b1 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 22 Jul 2026 07:52:29 +0200 Subject: [PATCH 270/275] ci: group Dependabot uv and GitHub Actions updates Stop Dependabot opening one PR per dependency by grouping uv and GitHub Actions updates into fewer PRs per ecosystem. Co-authored-by: Cursor AI Co-authored-by: Cursor Grok 4.5 --- .github/dependabot.yaml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 5ace4600..edd0bf1e 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -1,6 +1,20 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: github-actions + directory: / schedule: - interval: "weekly" + interval: weekly + groups: + github-actions: + patterns: ["*"] + + - package-ecosystem: uv + directory: / + schedule: + interval: weekly + groups: + python: + patterns: ["*"] + python-security: + applies-to: security-updates + patterns: ["*"] From 868ad7e608048f244d78c51ee3d5d940d62ef1ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 05:57:17 +0000 Subject: [PATCH 271/275] build(deps): bump the python group with 6 updates Bumps the python group with 6 updates: | Package | From | To | | --- | --- | --- | | [pre-commit](https://github.com/pre-commit/pre-commit) | `4.2.0` | `4.6.1` | | [pytest](https://github.com/pytest-dev/pytest) | `8.3.5` | `9.1.1` | | [tox](https://github.com/tox-dev/tox) | `4.26.0` | `4.58.0` | | [hatch](https://github.com/pypa/hatch) | `1.14.1` | `1.17.1` | | [uv](https://github.com/astral-sh/uv) | `0.7.8` | `0.11.31` | | [gevent](https://github.com/gevent/gevent) | `25.5.1` | `26.5.0` | Updates `pre-commit` from 4.2.0 to 4.6.1 - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v4.2.0...v4.6.1) Updates `pytest` from 8.3.5 to 9.1.1 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...9.1.1) Updates `tox` from 4.26.0 to 4.58.0 - [Release notes](https://github.com/tox-dev/tox/releases) - [Changelog](https://github.com/tox-dev/tox/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/tox/compare/4.26.0...4.58.0) Updates `hatch` from 1.14.1 to 1.17.1 - [Release notes](https://github.com/pypa/hatch/releases) - [Commits](https://github.com/pypa/hatch/compare/hatch-v1.14.1...hatch-v1.17.1) Updates `uv` from 0.7.8 to 0.11.31 - [Release notes](https://github.com/astral-sh/uv/releases) - [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/uv/compare/0.7.8...0.11.31) Updates `gevent` from 25.5.1 to 26.5.0 - [Release notes](https://github.com/gevent/gevent/releases) - [Changelog](https://github.com/gevent/gevent/blob/master/docs/changelog_pre.rst) - [Commits](https://github.com/gevent/gevent/compare/25.5.1...26.5.0) --- updated-dependencies: - dependency-name: pre-commit dependency-version: 4.6.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python - dependency-name: pytest dependency-version: 9.1.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: python - dependency-name: tox dependency-version: 4.58.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python - dependency-name: hatch dependency-version: 1.17.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python - dependency-name: uv dependency-version: 0.11.31 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: python - dependency-name: gevent dependency-version: 26.5.0 dependency-type: direct:production update-type: version-update:semver-major dependency-group: python ... Signed-off-by: dependabot[bot] --- uv.lock | 566 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 312 insertions(+), 254 deletions(-) diff --git a/uv.lock b/uv.lock index a2efaa64..54a47860 100644 --- a/uv.lock +++ b/uv.lock @@ -4,17 +4,16 @@ requires-python = ">=3.10" [[package]] name = "anyio" -version = "4.9.0" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -27,21 +26,105 @@ wheels = [ ] [[package]] -name = "cachetools" -version = "6.0.0" +name = "backports-zstd" +version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c0/b0/f539a1ddff36644c28a61490056e5bae43bd7386d9f9c69beae2d7e7d6d1/cachetools-6.0.0.tar.gz", hash = "sha256:f225782b84438f828328fc2ad74346522f27e5b1440f4e9fd18b20ebfd1aa2cf", size = 30160, upload-time = "2025-05-23T20:01:13.076Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/c3/8bb087c903c95a570015ce84e0c23ae1d79f528c349cbc141b5c4e250293/cachetools-6.0.0-py3-none-any.whl", hash = "sha256:82e73ba88f7b30228b5507dce1a1f878498fc669d972aef2dde4f3a3c24f103e", size = 10964, upload-time = "2025-05-23T20:01:11.323Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/8e/b5/5a873da082bd08acd6a497f7aae224e94a7c27fa8f24488089cc50a16c84/backports_zstd-1.6.0.tar.gz", hash = "sha256:80a7859ffe70bf239d7a2ce15293bdeb5b4280ff7dc326ffab312b0e254dbb24", size = 1000009, upload-time = "2026-06-14T10:50:58.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/8d/3f8e7a0fd319b3c0dbf0c4f751336309bb50a873b9185c2f5d228ff0d21b/backports_zstd-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:73000459db113a658c4fb0510100ef0e79137b5828bf957b7709aacae4eb1b87", size = 437068, upload-time = "2026-06-14T10:49:05.528Z" }, + { url = "https://files.pythonhosted.org/packages/db/14/4700047713a60131efcb3977a9892fab60bc9dd6634272550b8f1c5a427d/backports_zstd-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6e78d5e28f812b39f92397806ecddd4a6f3bf35531a8c039a1f187abc931af8", size = 363456, upload-time = "2026-06-14T10:49:07.154Z" }, + { url = "https://files.pythonhosted.org/packages/c4/34/92de2f1bd5ee29b24302c871b9f3c19155bf9478cd3af5a0dfd70fa2f483/backports_zstd-1.6.0-cp310-cp310-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:32f04d54ec1fdf3aa648b24a10b1c9234ed2046cc4af7a8850cbc236c05d42f3", size = 507392, upload-time = "2026-06-14T10:49:08.63Z" }, + { url = "https://files.pythonhosted.org/packages/5a/95/ed5b8b026c6df1a59681a73396f63cfd10e17ccfbc6315974745a8b7d834/backports_zstd-1.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83415af3c64550a56cc20b4cce59bbaa81f21d28466d7adf98feff011ecbc66d", size = 476957, upload-time = "2026-06-14T10:49:09.894Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/1a82ede48d9df99c8245cb38622cd1a9b388b34f89e1cb7b6650913b493d/backports_zstd-1.6.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3c17e6a267d13de9cbf14bf2ebfa87e03d26692456fc67d2dbed9da4f479b18", size = 582618, upload-time = "2026-06-14T10:49:11.097Z" }, + { url = "https://files.pythonhosted.org/packages/6b/70/441ed36e230b0f66d7d49382c58249b540139c5b1aa096ace1ff00bd7873/backports_zstd-1.6.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:75578c71644b031118ce938855a53530708db7f4af6e83e2f8840d5a1de990f8", size = 642278, upload-time = "2026-06-14T10:49:12.545Z" }, + { url = "https://files.pythonhosted.org/packages/58/a3/8f5737bdb02576577a018c10a4c345a5b4b2e63cc3811baeecc054f71c00/backports_zstd-1.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4ae7ed5a6d813450cc2d818284ea3db9721edcef50a56aae42ea06feec38c6e", size = 492492, upload-time = "2026-06-14T10:49:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/c086507f535c2466a25bf83a876666c9ccde07b17ce81680217ac17355fe/backports_zstd-1.6.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5e9a8370c8ed873083d5de956d6b2e60adbad31e52d7a11111c96ef01d1910ae", size = 566440, upload-time = "2026-06-14T10:49:15.483Z" }, + { url = "https://files.pythonhosted.org/packages/89/bb/778aaccb58c4d2fba3482438c0d33c6a3a413710ecdb2ee8559ff28632fc/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2d1ccfe088e8279d605011a3575619a74526c261be357695b3258c0f636115a", size = 482894, upload-time = "2026-06-14T10:49:16.982Z" }, + { url = "https://files.pythonhosted.org/packages/3a/88/87ad188ce971c15bce933e13c4dc2939e741a9cb06a8bd692ef8614c3ed4/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e73a550dbeb84e8fa50f8385f7735e9a4735b465851ef617d02f80ab10e44e7e", size = 510822, upload-time = "2026-06-14T10:49:18.274Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8c/01714884a14b836abfdb1d80339acbb39515b0e92e615c4b65caf0eec257/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84f92e5a60a78c72ccda79d0417d311a1f6da18f446423ed411726d545bf7b56", size = 586941, upload-time = "2026-06-14T10:49:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/63/43/e2cb44bbe3f7485d6ad8493211f0c8fffdb7e02fb94203fd968751d9fad7/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0eb4281f402b94d397b7482f6d9efd04c28274e4ed6eb57eb1f87bdd091a6a87", size = 564255, upload-time = "2026-06-14T10:49:20.968Z" }, + { url = "https://files.pythonhosted.org/packages/36/eb/eb0f00f6f7778db3710f757d77f5699c325548037dd975b52b186394125e/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d6b9b06323e3ba947c0003b2d70e02f33c90c36bc6262a92eb8201afc4a1aa08", size = 632836, upload-time = "2026-06-14T10:49:22.177Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b8/5434897431de92e79ebfb2c02e1ab3cd228e92853b7ff981b2cffcce7355/backports_zstd-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8872a0e9f1af975966b5be6af7eebd3dc4046f15e470b719316516dc3d137cd6", size = 496501, upload-time = "2026-06-14T10:49:23.444Z" }, + { url = "https://files.pythonhosted.org/packages/db/76/754939c3914e9724e20a50b75b17fdc27aeb24d697eb61c3e93438a42920/backports_zstd-1.6.0-cp310-cp310-win32.whl", hash = "sha256:c14fa5dc39a804f1b92d63506f450eca5c59647a18d197d1a564b89dac1be1ce", size = 291527, upload-time = "2026-06-14T10:49:24.568Z" }, + { url = "https://files.pythonhosted.org/packages/84/f1/adcebdc2caa2e3d3d496ac2501f0ada49ad2942ee36e5f88a944bca9ca92/backports_zstd-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:8219d6fceae6b39535c4ac323dba0923d10f781d59962ff3504e693fdcafa92c", size = 329024, upload-time = "2026-06-14T10:49:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/74/10/12edc0b401a08aba157b9d331748ac0f0e9890af0a58a9c72425063d1450/backports_zstd-1.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:b7bc9a0b66097f03820a54316d2fdd0beb38859cf98f10d63e94c55450ed8920", size = 291597, upload-time = "2026-06-14T10:49:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/428dd82228b1b6d62d5a1bf312c29e6c125af6a182fcfd82768ca179dcc7/backports_zstd-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4fc41b2df5529cad5ceb230319e82728096d4b353ce8d4df68a2ec37e291bb8", size = 437067, upload-time = "2026-06-14T10:49:28.335Z" }, + { url = "https://files.pythonhosted.org/packages/ef/48/768edf21fe33bae8d874470b1be136681d4d32eb820a32e1c98262ebe39b/backports_zstd-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:83391ef5935cc0f329b1abca414ae20ffe40d335fc21a4b5e664f08a74317d5f", size = 363454, upload-time = "2026-06-14T10:49:29.784Z" }, + { url = "https://files.pythonhosted.org/packages/29/8a/d462c2e5071eb573378f0d26760f6590613086fdf59c2d3c66bdfffb9f41/backports_zstd-1.6.0-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:7d3f64c503af7b60115b97c16feaf75bd191ef2c978d5c0c7725a6682bef63c5", size = 507393, upload-time = "2026-06-14T10:49:31.077Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/af58363b0dd0b497282ecef1fa99789b03cc1885a01a41394cad42ceeff6/backports_zstd-1.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0308990ffc998df3c7ed35276bde049728b5c3956203cae40d80893576a41459", size = 476957, upload-time = "2026-06-14T10:49:32.53Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fd/5fbdf2275cefae95c4b3509f6db2dc372d0587ebafea342d28781d51d932/backports_zstd-1.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8c298785e2fadeab82342040f2d9ce764ce500e6da6a6d99a2de514e63580b5a", size = 582618, upload-time = "2026-06-14T10:49:33.723Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/7dd45c53c907ea67f635c3900b58bb3347c01dc2ded441402028aae0ef9c/backports_zstd-1.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae106fe16e36efc60ab098d02478d30aa0e31e1420eb4ecf0116459253bc6361", size = 642279, upload-time = "2026-06-14T10:49:34.938Z" }, + { url = "https://files.pythonhosted.org/packages/4d/25/a9e37dd035027565fa0b7e367da50e88a6ab26e7fd413269aa118e25258b/backports_zstd-1.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7293fefe15f0e5852bdb4ad1e0e26f3cbd4d3e61c19f751ecc4ff34bc1eb237d", size = 492486, upload-time = "2026-06-14T10:49:36.06Z" }, + { url = "https://files.pythonhosted.org/packages/a1/52/659686bf8f7c53ea279e1c44038504b82a6901cee2f5ae83c30bbf581301/backports_zstd-1.6.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ece8e7288db5b827ef8c64b2f78519f1a173a8991a625978fce02eccd7654fe9", size = 566440, upload-time = "2026-06-14T10:49:37.536Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1a/c7ea5a0ff607a1a6066bb7c7cb65ae20e2f85da6adc69ab77fd8943e180c/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:28eef3881164f3c23ce58ed59e4684103bdd279583eb2d299858c9e9b72fde9a", size = 482899, upload-time = "2026-06-14T10:49:38.805Z" }, + { url = "https://files.pythonhosted.org/packages/83/48/bd2b91100ee4fe6bb4d816e3659cbbb0cda5dd32760d2379c54d1752ec25/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:481a1e9bd8f419fdc625307aa20234687f99368c75df511ef589693c5fea4c6f", size = 510826, upload-time = "2026-06-14T10:49:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/25/fe/fa28509d7ce2ad59404e7ce738a2fd858e12dfd9a896629f10330222a7fb/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b6713371f8987a1178df93cb36f29eef191f224021e2d656b2f11ce60d26816", size = 586941, upload-time = "2026-06-14T10:49:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/45/28/757daf2399aa71bb37f9f7f48b42ab03fc51c340eccfad2fec92a23f6aa3/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b0ddbcd2866b8ff1a2836e4b0e4d44788f5b992d83fac75a38cda8f9a2bee079", size = 564261, upload-time = "2026-06-14T10:49:42.49Z" }, + { url = "https://files.pythonhosted.org/packages/4e/53/9b9db30cb2c148a69c40ad7647aa787338041f3dc81c5b22113286e590e9/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2914abea516704bdafb2090acd3f15b5f9debecfabd15b8dd8285b2ad3b92209", size = 632869, upload-time = "2026-06-14T10:49:43.981Z" }, + { url = "https://files.pythonhosted.org/packages/81/a4/1692fbb88af8aaf900a53619fcc95c9e45d9ff162223a47fd672a9893c8d/backports_zstd-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd085eafa2aac6f883afd28210a3231f717f25409a1e44a39bb7b04c8c5b5646", size = 496496, upload-time = "2026-06-14T10:49:45.118Z" }, + { url = "https://files.pythonhosted.org/packages/93/42/c5a66c47320bd12ce84a7341330ea582d67069bdb70214bca0b6bf394cfd/backports_zstd-1.6.0-cp311-cp311-win32.whl", hash = "sha256:b81b4cf3d6e0ad7ac92bef248f49fafc954262c5fb0f7e19d6aac497e5a856b2", size = 291613, upload-time = "2026-06-14T10:49:46.473Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f2/f22c19b4cdde429805ff5ac8dd77a95569a7c4cb8991741b2ff0d538f220/backports_zstd-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:10b61850c4112952e05aa6e6cce8c9a5936fbeadb321e154216705cc76a14afa", size = 329078, upload-time = "2026-06-14T10:49:47.71Z" }, + { url = "https://files.pythonhosted.org/packages/ef/dc/e902a3f1eb92c4907b5f47f90cb3c2734ee315c4ff67179fc111343b45ba/backports_zstd-1.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:068ef3d8c18815a2e3a752f766313e19910e7c50939b956923748d9c04ebcb1b", size = 291727, upload-time = "2026-06-14T10:49:48.929Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/009af3a9532d4cc66d5385391c512210fae32ab2442605f26aca1d8d2957/backports_zstd-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0466b14723f3b7697669c00ee66fe16e30e25636b286b0a923fa86fa3d8a753c", size = 437407, upload-time = "2026-06-14T10:49:50.155Z" }, + { url = "https://files.pythonhosted.org/packages/0c/76/f7c02efde81ebb9993586f9e435d2fd1191a6f806f640e4eeb8d004493ed/backports_zstd-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1d146926e997d2d3de8212bdcbf4985344a2622ca3bec458d8908000a84fd883", size = 363519, upload-time = "2026-06-14T10:49:51.383Z" }, + { url = "https://files.pythonhosted.org/packages/2e/5e/0cf66f12472fe3e082cc4134395a7e0b8746cfb30aabd74251ce8fafa9a7/backports_zstd-1.6.0-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:460fd6b3f338c659507ae36cfd6b58ac9942a2ff233c5cf574416dfec0451a84", size = 507756, upload-time = "2026-06-14T10:49:52.497Z" }, + { url = "https://files.pythonhosted.org/packages/03/95/7ed25c90369360f96f8bfa961540845e063377c32a43b775201af66a588c/backports_zstd-1.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c2b1f4a640c51130caa92cef5bf72bd3c3dbbcfbf814c37403aa0601b1811b0", size = 477578, upload-time = "2026-06-14T10:49:53.887Z" }, + { url = "https://files.pythonhosted.org/packages/e3/75/f16b1d3e33ca396525847c81d96e3de7bc74d2c6f9ca2ddee76b0c450697/backports_zstd-1.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:beb43e9885202c8d4f3762319ed4d5e98e197622afbff8439fbbdd81d08938b9", size = 583029, upload-time = "2026-06-14T10:49:55.132Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2b/a17b111b631e1c79a0e570881c1a266c661b936585afa395435a458b1991/backports_zstd-1.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fbb746522ebfc11155f1cd688e2c48ef3d74125e38b63eabdaab068a055c3e88", size = 641741, upload-time = "2026-06-14T10:49:56.42Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b2/d17b2722c636d64b4e77ddc68d8d0625719d39f94021be8719a218af4c0a/backports_zstd-1.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a99710fbb225d459d66def4dc2bb2cd4a9a0bdc8b799fc0621cfdd863be9c93", size = 495554, upload-time = "2026-06-14T10:49:57.652Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/2853e8b6c03f03795b6548ea61f82cc104d4f7ff2523a04bc69f46984663/backports_zstd-1.6.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f69365ee2b836939137de024a302395a1cb8654fb6dc5ffef6381105259c8f87", size = 570027, upload-time = "2026-06-14T10:49:59.003Z" }, + { url = "https://files.pythonhosted.org/packages/18/aa/83f37b81f3b8c6ea035bf260ec374648bd59372894c02323dc9de3cbdf77/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:66cf8038893c7708ec345ffb3ac63c775d10f430f323ac2f0334fdb6a397c57c", size = 483594, upload-time = "2026-06-14T10:50:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6a/d77f8cd2ff642d3b3652c1ccab5b6583114dbf10f8cb0143531357c83998/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e514c71ca72f3b56bd8fbda1a6a5b7d1100a2764b42a3c74a38841f25f9b00ab", size = 511206, upload-time = "2026-06-14T10:50:01.86Z" }, + { url = "https://files.pythonhosted.org/packages/56/b2/99a60fe4d1aac8053769d2463271d5df37a7c11c387072fdbb0b16aed7f7/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7741e44f7938ec94f9a52678c8d19b7bc548522ffdc39c9e4481af8db545fa9a", size = 587416, upload-time = "2026-06-14T10:50:03.236Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1e/a9c003fe4d14bd4bf671598d4c7dcc1cef51e3513d9d7111ba1d07b6f07b/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97e8a9674652496c7612b528085dd5a296c052a2edc466ca1bfb7b0b27820413", size = 567615, upload-time = "2026-06-14T10:50:04.524Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b9/955bd604f692c550c7cb66d00bd7691ead5c86df8ebd23d7254eeaa90789/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:23a793f2fed4dbf0517319759a2cded0b0dd8e8d3797fe30badd5693e320c175", size = 632269, upload-time = "2026-06-14T10:50:05.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/9f61f612f8a4193484c78a1f26db82a50141234189885113ef0085a8a961/backports_zstd-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b951113113ed4b8d173418a4f155c14b739dace626b3fa3f82be1831958d39e4", size = 500066, upload-time = "2026-06-14T10:50:07.446Z" }, + { url = "https://files.pythonhosted.org/packages/81/a3/19fb8c48d94139481c5ccaf2fb54c31b543fa635fd7bd7399aadd15752ac/backports_zstd-1.6.0-cp312-cp312-win32.whl", hash = "sha256:6430b34a2ae6fcc604672f4f913102563473d9a015bdca1ce8c95041cc1f2677", size = 291825, upload-time = "2026-06-14T10:50:08.762Z" }, + { url = "https://files.pythonhosted.org/packages/58/38/40ba081c6c71f0f22c64d3d54b912ad75a4e6812caa1397cbb15b5693b12/backports_zstd-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:08793876172551a930ce4d65c712cd516184d1a97070d4a1193e05bf0cf7040d", size = 329201, upload-time = "2026-06-14T10:50:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/2b/6c/f7116dd2edc6f960545f0d8616939eae3a20031b3b6669697d4f9fd83b2e/backports_zstd-1.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:03b7c59c71f7a597e2bcb3f8368371e9a660a1bdf1c37afc1f1ad1496a013c19", size = 291901, upload-time = "2026-06-14T10:50:11.198Z" }, + { url = "https://files.pythonhosted.org/packages/38/06/c430537d59c55d49bcd15ecf4b1aa965453219caad810a4f2b484816f4be/backports_zstd-1.6.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:2ace939e4d620e119423606f2d3d7115f8707733bf57f279ad9a9383f875986f", size = 400327, upload-time = "2026-06-14T10:50:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/36/48/2f8323bb0e3ebba88b54877a2979afeb83983fb2ca572f09ad61aae2d3a0/backports_zstd-1.6.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:4c68a9ed2df0cca51d774c521e68a34d2e3d9ebfc687ef8096adfd4f345b551d", size = 454276, upload-time = "2026-06-14T10:50:13.667Z" }, + { url = "https://files.pythonhosted.org/packages/7c/39/87a665244a65f5b87a06b848c29a8cce07e91d59c5988ee2a32c0293a21c/backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:30576f49b82328ec8af16c11100efe52ca88526f71bbe100ef6b4e707dc13bf2", size = 357457, upload-time = "2026-06-14T10:50:14.906Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8b/854d4a47bb8b7a48bfb2ed381c7b03a70efb4fc49f0e4a1509b38a2e1727/backports_zstd-1.6.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b4bddfcfb6679215d6f4dc5f79a1f9301af339480d70527a14b57a1f2e6b6cbf", size = 366139, upload-time = "2026-06-14T10:50:16.399Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/c3af43eb8df6f2581e157e18a3e0121eadb826055b2fde3f91ec188689cb/backports_zstd-1.6.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:65048ed08c5124f05ff9f355ab9703014bb2dbe7f8d9948ce193685b1775f442", size = 446683, upload-time = "2026-06-14T10:50:17.633Z" }, + { url = "https://files.pythonhosted.org/packages/5c/39/87cf3d883d386c10ac52f5322604fb9afdd204229f4c47d4a820a839b8ff/backports_zstd-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5918fc6b31437208721276964323933cd86077b8d5b469c59c1b3fd2c8220a05", size = 436869, upload-time = "2026-06-14T10:50:19.113Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b6/9479e6f0f18824ad38e8d7dd85161ab0842a198be669421232925bb30960/backports_zstd-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b6c8b02ab0ccb2431bb7bc238be91d158b308915e7b07937388e540466fe7e7", size = 363090, upload-time = "2026-06-14T10:50:20.302Z" }, + { url = "https://files.pythonhosted.org/packages/d9/74/a5e98fe108e17c91d9bc590a19e77f5d47d579e34d3f5bc098a949d6c27c/backports_zstd-1.6.0-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:711e6b98f8924e8b4a61ff97ab6321f33de024e1ed6a32f5123763aeda8459be", size = 507070, upload-time = "2026-06-14T10:50:21.536Z" }, + { url = "https://files.pythonhosted.org/packages/69/f5/392bb7dce7363b77bc5403060f418fad438b9cfdd3edd10d65cee7d8fd11/backports_zstd-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ba9ac10fc393e5123a08802e0e895a107cb4a66b9973d2844dbd8a343111e59", size = 477200, upload-time = "2026-06-14T10:50:22.91Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4d/dfb665806ba4f74bc48071d32006843b53568c4a17ff627a3061de5eaa09/backports_zstd-1.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f723219335387d7546412d8141e0303590600949b4184a1391a0c6a3c756058", size = 582724, upload-time = "2026-06-14T10:50:24.28Z" }, + { url = "https://files.pythonhosted.org/packages/57/b2/beeca7393a8310debd82ee2f0ce5c1801e8d7cb673f7f226f4a0866ca238/backports_zstd-1.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64b94d7a836568926a3309ff510c7f8261b881b341fd4992cabf4f0998878f8a", size = 643493, upload-time = "2026-06-14T10:50:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/38/26/ce90e9eed6f25aaa4a4fa305a2aaf2d2ad81fd69de8eb248ddd91c80d1e0/backports_zstd-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e39258a09b1c7ca70b5e94a5c5ccfe4700b4250b8077cfeab31d0f79565d4c9b", size = 492190, upload-time = "2026-06-14T10:50:27.205Z" }, + { url = "https://files.pythonhosted.org/packages/17/9b/37b9b146df1f5452419a96071a7017cbac212ec9b137d7a88ca46dc2aa9e/backports_zstd-1.6.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:15b1aae0f64cd742df4bba1d989d0a09a6ec619202543fdba684640454541fd3", size = 567432, upload-time = "2026-06-14T10:50:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/06/66/81b30991be83237529f36335ac3682bce26409064b906ac6122874575196/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:25b5ddc789480072551af571a746e9500356b2aff0499861cf2ca07ea7431e68", size = 483021, upload-time = "2026-06-14T10:50:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/49/2a/792c65dcc1e45eb0c1bdc012ee94b84867186bfe27a860d0813bd216f03b/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a13cfa3410a75e4cb87abdb669aaf79da861cb79299159054ff8f77b9671bc40", size = 510596, upload-time = "2026-06-14T10:50:31.657Z" }, + { url = "https://files.pythonhosted.org/packages/1d/22/01b92a600505620e4cb5f20429e181f30458b7207ca8b52ca5ca6068c35f/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2ddab55a5f54dec8acfad68ef70f1c704fd21919990ddc238afbd6f496e61c6a", size = 587143, upload-time = "2026-06-14T10:50:32.868Z" }, + { url = "https://files.pythonhosted.org/packages/d8/60/4672f5110b9eb01388cc6225a739e3a5fcd749a63a9c4c1450a04fa27113/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fa305a84087e10d7a85e8a8a3dcba8cdbda4868f2180173b264b7b488fd37c55", size = 565238, upload-time = "2026-06-14T10:50:34.173Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3b/19928d60ea7d25820bf12ef88de74534ca85b56ff7cf13c1b0e74e3a3d7c/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:df27b57d214a3124fbe4e933ef5a903d4567f154260d9aece8c797a987f2a205", size = 633970, upload-time = "2026-06-14T10:50:35.506Z" }, + { url = "https://files.pythonhosted.org/packages/df/97/c4cecb3e0ff53563ef9819f0395d919ceaae9c5147392ac23bac7afdb20f/backports_zstd-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28fecd73459d74910ae1987ab84b7bef690d3dd860948430dd5555108b006daf", size = 496539, upload-time = "2026-06-14T10:50:37.015Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f4/46b2f29d2938a80e56e61a19f11ab093f531a9f8cd0ec8eeaac1246bcd99/backports_zstd-1.6.0-cp313-cp313-win32.whl", hash = "sha256:3e689af303df287142770abe3a48bbefd24dab4a09da5807d0e1fa8c75bab026", size = 291451, upload-time = "2026-06-14T10:50:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/b529f92166da61f496621345f95d2dc583c8ca5ac553c084a4ef6c12cd71/backports_zstd-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:b067b1ef9c8e41fb0882c828aa37829938b5c0dab067eca72b23fc24c563b9da", size = 329023, upload-time = "2026-06-14T10:50:39.742Z" }, + { url = "https://files.pythonhosted.org/packages/30/d8/6be904d20345fbebec583ca83676e01f30c76118b283eb666d8ec8291ca1/backports_zstd-1.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:a838296f5b84c920172fb579cac894d255c1fc25457c7234613ddcfa385e49b7", size = 291636, upload-time = "2026-06-14T10:50:41.004Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/9ed88f528b9484f3847f07b9d1d014b496e048d391b4bc04cb0117bd71a5/backports_zstd-1.6.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6c73ae37dbf9207727ac095dedef864c05d836eaec962a47b3b64eaadaf1c6b6", size = 411126, upload-time = "2026-06-14T10:50:42.253Z" }, + { url = "https://files.pythonhosted.org/packages/fe/26/bf8093d117cb6c36202ee7a2127f672c7b0c81f0c104ce28124534b75efa/backports_zstd-1.6.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:839faf90a7eb525a401978dc925df8c44bd12526e8ba1529b9f8a7106e729637", size = 340643, upload-time = "2026-06-14T10:50:43.571Z" }, + { url = "https://files.pythonhosted.org/packages/17/10/55f0860ed359d290e0eadd410da47ae720a1acf0d8362149e22acbe63223/backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f8f5c1c7c69a4b00889e52d9304a918a5b49010f9645768eb5fd0ad404f790ba", size = 421696, upload-time = "2026-06-14T10:50:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3e/8dd9cc6f3e697e4721e53d6b9ca8c95c9d51ad2e759e7fcee6885e5b71db/backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e80bceebc9b58e959bede9b26cafe15b5b9526f3533a6dd06330c5da73cb9329", size = 395239, upload-time = "2026-06-14T10:50:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/ab92f8599749cb6063416dd9f46e084004c4e8db68e2eb768283012a6d27/backports_zstd-1.6.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79284c1dd702f4f24ed1a36e51555c907dd237b6c0d829595978f4089a2aeea9", size = 415202, upload-time = "2026-06-14T10:50:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f4/dd3ef995f6b22e23da145ac3ecc91e1f1fc4cb572b7f95e6b2b11de16782/backports_zstd-1.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1e20b3ecd0a711be82e964aca28554eabbc31ee69a20e5e7b8fd42268af46212", size = 315722, upload-time = "2026-06-14T10:50:49Z" }, + { url = "https://files.pythonhosted.org/packages/e8/09/898fe2f8196fa7ab825f5fed786c68581fdac7d23a8e20baa0cc01cb2f0b/backports_zstd-1.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:aeef8563b82ed4af328f98e5041c1b4800d86f68f857ffd1577d4d47dc9aa6cd", size = 411023, upload-time = "2026-06-14T10:50:50.286Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ad/6ad9af1596ab5f284bb53954be41396e13d23c81cdfe3d945402e8ee0215/backports_zstd-1.6.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9cb75e33131946fabd6319061df3b8b1d588fe0963183280e9b5f49f7772fc09", size = 340554, upload-time = "2026-06-14T10:50:51.523Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/f083d7c8a4ee5d0bb21b4d3144e76de9f655ca4dd0bffcb95baa5bc47a62/backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:ef132cfb638e9a86bd5dc07fb4e1cb895bc55bce6bb5e759366e8b160d0747e2", size = 421694, upload-time = "2026-06-14T10:50:52.917Z" }, + { url = "https://files.pythonhosted.org/packages/41/d7/693b20f3ccae2e05d166f98fe55b1657451170b72c804ed9f6b98df520be/backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab70eace272d6f122b121c057e436709b50a28abf30d97aab28433c08f4a4095", size = 395237, upload-time = "2026-06-14T10:50:54.448Z" }, + { url = "https://files.pythonhosted.org/packages/53/a1/484e0f9ec994bd2285d6747e7c8028350f1a177e9210bc57637898042d3b/backports_zstd-1.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17efb3d11137de5166dd51eedab9c36ad633402acba386eee8d715213ea47e49", size = 415201, upload-time = "2026-06-14T10:50:55.854Z" }, + { url = "https://files.pythonhosted.org/packages/3c/56/70860ece85cd49b564305cbc22bf6c4183975427ff6dfe2097e855f5dd5e/backports_zstd-1.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:994167ff6551b9c1ce226e0aab16295b98c94507b5701aa60d2c32b7d50796b1", size = 315721, upload-time = "2026-06-14T10:50:57.074Z" }, ] [[package]] -name = "certifi" -version = "2025.4.26" +name = "cachetools" +version = "7.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/8b/0d3945a13955303b81272f759a0331e54c5c793da455e6f5706b89d2639c/cachetools-7.1.4.tar.gz", hash = "sha256:437f55a4e0c1b01a4f3077cc470e6991d47430970e36fbcb77e2be0df4fc1cd6", size = 40085, upload-time = "2026-05-21T22:40:43.376Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7b/1fc1c09cc0756cf25861a3be10565915953876da48bb228fb9a672b20a42/cachetools-7.1.4-py3-none-any.whl", hash = "sha256:323dc4127934744db5b54eb4924482d7edafbf9554e820d1531c2e08c0e4ef54", size = 16761, upload-time = "2026-05-21T22:40:41.845Z" }, ] [[package]] @@ -53,8 +136,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, @@ -65,8 +146,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264, upload-time = "2024-09-04T20:43:51.124Z" }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651, upload-time = "2024-09-04T20:43:52.872Z" }, { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, @@ -77,8 +156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178, upload-time = "2024-09-04T20:44:12.232Z" }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840, upload-time = "2024-09-04T20:44:13.739Z" }, { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, @@ -88,8 +165,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, @@ -110,15 +185,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] -[[package]] -name = "chardet" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" }, -] - [[package]] name = "click" version = "8.2.1" @@ -186,6 +252,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, ] +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -235,16 +310,16 @@ testing = [{ name = "execnet", extras = ["testing"] }] [[package]] name = "filelock" -version = "3.18.0" +version = "3.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, ] [[package]] name = "gevent" -version = "25.5.1" +version = "26.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation == 'CPython' and sys_platform == 'win32'" }, @@ -252,42 +327,54 @@ dependencies = [ { name = "zope-event" }, { name = "zope-interface" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/58/267e8160aea00ab00acd2de97197eecfe307064a376fb5c892870a8a6159/gevent-25.5.1.tar.gz", hash = "sha256:582c948fa9a23188b890d0bc130734a506d039a2e5ad87dae276a456cc683e61", size = 6388207, upload-time = "2025-05-12T12:57:59.833Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/a7/438568c37fb255f80e710318bfcad04731b92ce764bc16adee278fdc6b4d/gevent-25.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8e5a0fab5e245b15ec1005b3666b0a2e867c26f411c8fe66ae1afe07174a30e9", size = 2922800, upload-time = "2025-05-12T11:11:46.728Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b3/b44d8b1c4a4d01097a7f82ffbc582d054007365c27b28867f0b2d4241d73/gevent-25.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7b80a37f2fb45ee4a8f7e64b77dd8a842d364384046e394227b974a4e9c9a52", size = 1812954, upload-time = "2025-05-12T11:52:27.059Z" }, - { url = "https://files.pythonhosted.org/packages/1e/c6/935b4c973ad827c9ec49c354d68d047da1d23e3018bda63d3723cce43178/gevent-25.5.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29ab729d50ae85077a68e0385f129f5b01052d01a0ae6d7fdc1824f5337905e4", size = 1900169, upload-time = "2025-05-12T11:54:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/38/8a/b745bddfec35fb723cafb036f191e5e0a0013f1698bf0ba4fa2cb8e01879/gevent-25.5.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80d20592aeabcc4e294fd441fd43d45cb537437fd642c374ea9d964622fad229", size = 1849786, upload-time = "2025-05-12T12:00:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b3/7aa7b09d91207bebe7608699558bbadd34f63e32904351867c29f8be25de/gevent-25.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8ba0257542ccbb72a8229dc34d00844ccdfba110417e4b7b34599548d0e20e9", size = 2139021, upload-time = "2025-05-12T11:32:58.961Z" }, - { url = "https://files.pythonhosted.org/packages/74/da/cf52ae0c84361f4164a04f3338508b1234331ce79719db103e50dbc5598c/gevent-25.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cad0821dff998c7c60dd238f92cd61380342c47fb9e92e1a8705d9b5ac7c16e8", size = 1830758, upload-time = "2025-05-12T11:59:55.666Z" }, - { url = "https://files.pythonhosted.org/packages/93/93/73a49b896d78eec27f0895ce3008f9825db748a5aacbca47404d1014da4b/gevent-25.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:017a7384c0cd1a5907751c991535a0699596e89725468a7fc39228312e10efa1", size = 2199993, upload-time = "2025-05-12T11:40:50.845Z" }, - { url = "https://files.pythonhosted.org/packages/df/c7/34680b7d2a75492fa032fa8ecaacc03c1940767a35125f6740954a0132a3/gevent-25.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:469c86d02fccad7e2a3d82fe22237e47ecb376fbf4710bc18747b49c50716817", size = 1652665, upload-time = "2025-05-12T12:35:58.105Z" }, - { url = "https://files.pythonhosted.org/packages/c6/eb/015e93f16a718e2f836ecebecae9bcd7b4d2a5695d1c8bd5bba2d5d91548/gevent-25.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:12380aba5c316e9ff53cc21d8ab80f4a91c0df3ada58f65d4f5eb2cf693db00e", size = 2877441, upload-time = "2025-05-12T11:14:57.735Z" }, - { url = "https://files.pythonhosted.org/packages/7b/86/42d191a6f6672ca59d6d79b4cd9b89d4a15f59c843fbbad42f2b749f8ea9/gevent-25.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f0694daab1a041b69a53f53c2141c12994892b2503870515cabe6a5dbd2a928", size = 1774873, upload-time = "2025-05-12T11:52:29.015Z" }, - { url = "https://files.pythonhosted.org/packages/f5/9f/42dd255849c9ca2e814f5cbe180980594007ba19044a132cf674069e38bf/gevent-25.5.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2797885e9aeffdc98e1846723e5aa212e7ce53007dbef40d6fd2add264235c41", size = 1857911, upload-time = "2025-05-12T11:54:19.523Z" }, - { url = "https://files.pythonhosted.org/packages/3e/fc/8e799a733be48f6114bfc531b94e28812741664d8af89872dd90e117f8a4/gevent-25.5.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cde6aaac36b54332e10ea2a5bc0de6a8aba6c205c92603fe4396e3777c88e05d", size = 1812751, upload-time = "2025-05-12T12:00:03.719Z" }, - { url = "https://files.pythonhosted.org/packages/52/4f/a3f3acd961887da10cb0b49c3d915201973d59ce6bf49e2922eaf2058d5f/gevent-25.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24484f80f14befb8822bf29554cfb3a26a26cb69cd1e5a8be9e23b4bd7a96e25", size = 2087115, upload-time = "2025-05-12T11:33:01.128Z" }, - { url = "https://files.pythonhosted.org/packages/b6/27/bb38e005106a53787c13ad1f9f73ed990e403e462108acae6320ab11d442/gevent-25.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc7446895fa184890d8ca5ea61e502691114f9db55c9b76adc33f3086c4368", size = 1793549, upload-time = "2025-05-12T11:59:57.854Z" }, - { url = "https://files.pythonhosted.org/packages/ee/56/da817bc69e1f0ae8438f12f2cd150656b09a8c3576c6d12f992dc9ca64ef/gevent-25.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5b6106e2414b1797133786258fa1962a5e836480e4d5e861577f9fc63b673a5a", size = 2145899, upload-time = "2025-05-12T11:40:53.275Z" }, - { url = "https://files.pythonhosted.org/packages/b8/42/989403abbdbb1346a1507083c02018bee3fedaef3f9648940c767d8c0958/gevent-25.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:bc899212d90f311784c58938a9c09c59802fb6dc287a35fabdc36d180f57f575", size = 1635771, upload-time = "2025-05-12T12:26:47.644Z" }, - { url = "https://files.pythonhosted.org/packages/58/c5/cf71423666a0b83db3d7e3f85788bc47d573fca5fe62b798fe2c4273de7c/gevent-25.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:d87c0a1bd809d8f70f96b9b229779ec6647339830b8888a192beed33ac8d129f", size = 2909333, upload-time = "2025-05-12T11:11:34.883Z" }, - { url = "https://files.pythonhosted.org/packages/26/7e/d2f174ee8bec6eb85d961ca203bc599d059c857b8412e367b8fa206603a5/gevent-25.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87a4b66edb3808d4d07bbdb0deed5a710cf3d3c531e082759afd283758bb649", size = 1788420, upload-time = "2025-05-12T11:52:30.306Z" }, - { url = "https://files.pythonhosted.org/packages/fe/f3/3aba8c147b9108e62ba348c726fe38ae69735a233db425565227336e8ce6/gevent-25.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f076779050029a82feb0cb1462021d3404d22f80fa76a181b1a7889cd4d6b519", size = 1868854, upload-time = "2025-05-12T11:54:21.564Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b1/11a5453f8fcebe90a456471fad48bd154c6a62fcb96e3475a5e408d05fc8/gevent-25.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bb673eb291c19370f69295f7a881a536451408481e2e3deec3f41dedb7c281ec", size = 1833946, upload-time = "2025-05-12T12:00:05.514Z" }, - { url = "https://files.pythonhosted.org/packages/70/1c/37d4a62303f86e6af67660a8df38c1171b7290df61b358e618c6fea79567/gevent-25.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1325ed44225c8309c0dd188bdbbbee79e1df8c11ceccac226b861c7d52e4837", size = 2070583, upload-time = "2025-05-12T11:33:02.803Z" }, - { url = "https://files.pythonhosted.org/packages/4b/8f/3b14929ff28263aba1d268ea97bcf104be1a86ba6f6bb4633838e7a1905e/gevent-25.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcd5bcad3102bde686d0adcc341fade6245186050ce14386d547ccab4bd54310", size = 1808341, upload-time = "2025-05-12T11:59:59.154Z" }, - { url = "https://files.pythonhosted.org/packages/2f/fc/674ec819fb8a96e482e4d21f8baa43d34602dba09dfce7bbdc8700899d1b/gevent-25.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1a93062609e8fa67ec97cd5fb9206886774b2a09b24887f40148c9c37e6fb71c", size = 2137974, upload-time = "2025-05-12T11:40:54.78Z" }, - { url = "https://files.pythonhosted.org/packages/05/9a/048b7f5e28c54e4595ad4a8ad3c338fa89560e558db2bbe8273f44f030de/gevent-25.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:2534c23dc32bed62b659ed4fd9e198906179e68b26c9276a897e04163bdde806", size = 1638344, upload-time = "2025-05-12T12:08:31.776Z" }, - { url = "https://files.pythonhosted.org/packages/10/25/2162b38d7b48e08865db6772d632bd1648136ce2bb50e340565e45607cad/gevent-25.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a022a9de9275ce0b390b7315595454258c525dc8287a03f1a6cacc5878ab7cbc", size = 2928044, upload-time = "2025-05-12T11:11:36.33Z" }, - { url = "https://files.pythonhosted.org/packages/1b/e0/dbd597a964ed00176da122ea759bf2a6c1504f1e9f08e185379f92dc355f/gevent-25.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fae8533f9d0ef3348a1f503edcfb531ef7a0236b57da1e24339aceb0ce52922", size = 1788751, upload-time = "2025-05-12T11:52:32.643Z" }, - { url = "https://files.pythonhosted.org/packages/f1/74/960cc4cf4c9c90eafbe0efc238cdf588862e8e278d0b8c0d15a0da4ed480/gevent-25.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c7b32d9c3b5294b39ea9060e20c582e49e1ec81edbfeae6cf05f8ad0829cb13d", size = 1869766, upload-time = "2025-05-12T11:54:23.903Z" }, - { url = "https://files.pythonhosted.org/packages/56/78/fa84b1c7db79b156929685db09a7c18c3127361dca18a09e998e98118506/gevent-25.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b95815fe44f318ebbfd733b6428b4cb18cc5e68f1c40e8501dd69cc1f42a83d", size = 1835358, upload-time = "2025-05-12T12:00:06.794Z" }, - { url = "https://files.pythonhosted.org/packages/00/5c/bfefe3822bbca5b83bfad256c82251b3f5be13d52d14e17a786847b9b625/gevent-25.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d316529b70d325b183b2f3f5cde958911ff7be12eb2b532b5c301f915dbbf1e", size = 2073071, upload-time = "2025-05-12T11:33:04.2Z" }, - { url = "https://files.pythonhosted.org/packages/20/e4/08a77a3839a37db96393dea952e992d5846a881b887986dde62ead6b48a1/gevent-25.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f6ba33c13db91ffdbb489a4f3d177a261ea1843923e1d68a5636c53fe98fa5ce", size = 1809805, upload-time = "2025-05-12T12:00:00.537Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ac/28848348f790c1283df74b0fc0a554271d0606676470f848eccf84eae42a/gevent-25.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:37ee34b77c7553777c0b8379915f75934c3f9c8cd32f7cd098ea43c9323c2276", size = 2138305, upload-time = "2025-05-12T11:40:56.566Z" }, - { url = "https://files.pythonhosted.org/packages/52/9e/0e9e40facd2d714bfb00f71fc6dacaacc82c24c1c2e097bf6461e00dec9f/gevent-25.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fa6aa0da224ed807d3b76cdb4ee8b54d4d4d5e018aed2478098e685baae7896", size = 1637444, upload-time = "2025-05-12T12:17:45.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/16/b71171e97ec7b4ded8669542f4369d88d5a289e2704efbbde51e858e062a/gevent-25.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:0bacf89a65489d26c7087669af89938d5bfd9f7afb12a07b57855b9fad6ccbd0", size = 2937113, upload-time = "2025-05-12T11:12:03.191Z" }, - { url = "https://files.pythonhosted.org/packages/11/81/834da3c1ea5e71e4dc1a78a034a15f2813d9760d135464aae5d1f058a8c6/gevent-25.5.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:60ad4ca9ca2c4cc8201b607c229cd17af749831e371d006d8a91303bb5568eb1", size = 1291540, upload-time = "2025-05-12T11:11:55.456Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/c4/cb/98aa3a299e2fc4a2372b5d124863e02965b64579ffc29fe54d0641e65b2f/gevent-26.5.0.tar.gz", hash = "sha256:1655eb04c1e20d71b2aa4a3c7528162dd58ff6cc46a037af1f01f534c80fefba", size = 6712354, upload-time = "2026-05-20T21:22:45.132Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/b7/01a5880e01702f39fb09e3616c624054a0dc9a82561a865f3b1eff4bfc80/gevent-26.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:2ba673dcbf7747513b58fa64ca7e9d6a828bc5c604d1552d23db89006d7911df", size = 2181491, upload-time = "2026-05-20T20:35:19.326Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fe/035ec5fa58a886740a744380118f03a90ac2da3f6c9cba248f28074ce40a/gevent-26.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:271b1474d81bb33036631adb16a35e5a1ee9dc414b05c999d6b01dc839a89975", size = 2212161, upload-time = "2026-05-20T20:43:25.678Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/ea87c08931c9e4c6c40bb05a2cb19c2d6f93fe6e0052f9152ea5ade6d037/gevent-26.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:cd3dc60581687e2618286108f8e2f820d8446be4b34131065011c066e911d39c", size = 1768295, upload-time = "2026-05-20T21:17:29.438Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1d0e7287ae55700a8d25153ac736896bd9bcc3f85a12d374ef398db4b33c/gevent-26.5.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:dc7fa28b2d627f8e87595f39043b6dec71e8e7fb97e685e5506c47cf3ff8cb2e", size = 1862627, upload-time = "2026-05-20T21:15:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/3a/4c/7f5ed67e52dfdef4ff91ae1a6fb28186d52e2496962edc8f17bdea9ab2c0/gevent-26.5.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:68c5fc21cef80268cdff88a4ae6c025fabb019b071f6f8ee4d20a7bccbddb873", size = 1804690, upload-time = "2026-05-20T21:30:51.713Z" }, + { url = "https://files.pythonhosted.org/packages/4c/75/0f5da6ca045f8a052203e1810058029f4b682507a789b413cac7d28bae28/gevent-26.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d325502eb0695708ef8c899f605573ed6847f3961f8159627dba267fbf3ce457", size = 2119054, upload-time = "2026-05-20T20:35:22.678Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f1/fcff7f7fad2bb33f3742db6b2145825a2191c0cd31d75789b0741fd28faf/gevent-26.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a11daf3a588b932c8bf965fb18444c69aff48badec88435e988cf8d67137075a", size = 1778784, upload-time = "2026-05-20T21:16:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/98/57/151314f00bdc6ba77333febb3e9dc97fdf94d79426559b4fa8332f0c2b6e/gevent-26.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1101b5ef82a3fb178550cfd80f32293dc8dd2f3d0828292223ebba29d6f76e33", size = 2145373, upload-time = "2026-05-20T20:43:27.255Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b5/7a02f711db62cbed1c1a00e1f9ff50eef95ccc78d4c04a0f93636655d1b7/gevent-26.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:5233109ad4f3af16393ba9888f238919a05ce15ce68d6831ac8a0da8dfb750ae", size = 1696576, upload-time = "2026-05-20T20:15:49.62Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/5022adc310697ef25c6fb22eb9bf0ebcad3427b51776e882709de9a8b6d7/gevent-26.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:3be804565168ffacebeb21af9f1cd689831a89f0f12fc0c3f423c730c3c9eb31", size = 1552095, upload-time = "2026-05-20T20:16:54.81Z" }, + { url = "https://files.pythonhosted.org/packages/37/0b/1a530b2db55c97cc0cf44116201f538f3033c04c1d2aca143979b412f4be/gevent-26.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:e80ad2a8a1e8bdaa5605e3bf4929e0cebf9ea7b8237c83362f7257698bb14280", size = 2929714, upload-time = "2026-05-20T20:13:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/b9/df/32fe851ed5f68493f354e09b19bdebae0de1185be4db0b2988e71e737fd3/gevent-26.5.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fe42c037253580a3386fce275f8a2a845e540f5a729916934a732f13d42e72cc", size = 1784838, upload-time = "2026-05-20T21:17:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9a/21332674f9a10e8cdf13b41b52e9d663647a1c6e1dc3c62b07c0aeefd360/gevent-26.5.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:9f463c7d6f69d13b6fe8e3b832a6175a6e95328a940f38495d25496d1ae8ad88", size = 1880440, upload-time = "2026-05-20T21:16:00.881Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b1/5f8a4196113cf7f3fdd987b483f7e6b10c28ea3930c4727e31ba8cce51b6/gevent-26.5.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:96d5e96b1b14a4c1023dcfcc114533217f13febc3b6169254f23fc18d19fee29", size = 1831592, upload-time = "2026-05-20T21:30:53.832Z" }, + { url = "https://files.pythonhosted.org/packages/4e/69/1559b1f6b5107a9118fccd300240879bd581b6d87b03d568d0d155ea702c/gevent-26.5.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:bccff69c462e3650a0fd1d4e9cfc8b6effe15f3e9b1cad20a7bb5ce14b057efd", size = 2114915, upload-time = "2026-05-20T20:35:25.041Z" }, + { url = "https://files.pythonhosted.org/packages/e4/32/602c499d54472f64e5cdf6013aeab5ce6aa6fed005387e8b4f2d22f5dc8d/gevent-26.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f519139354d5ca7625df9ddb1b2ffada885c14abc5b4dbae3682e967ddf79669", size = 1796906, upload-time = "2026-05-20T21:16:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3c/2fe77ee6e3d381b3c50c0b7d6c4c08c08b8ff5e8c0d9dd51a3b426d61eec/gevent-26.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0bf57df54f1c66273bf3601c2a1e41b12138fe848933718369663bc54f177ca2", size = 2140806, upload-time = "2026-05-20T20:43:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/22/d5/4620797bbd9c88f4541188efc138b0d615f9834db540da36a2249ee929c5/gevent-26.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:e49ce0de007dfd7412edbc2b5d41cce33b049bb1b7086f50be5a09e601bde603", size = 1699995, upload-time = "2026-05-20T20:15:39.311Z" }, + { url = "https://files.pythonhosted.org/packages/cb/83/ac3477dfc0f9fd80c88110102c73cefc35dcded2b248544f45a8fa5412df/gevent-26.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:5c5ff29495a2eed2a244de8150f21893d6c1b15d8b4b5719ab4bbfa06db1e28f", size = 1547433, upload-time = "2026-05-20T20:15:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/5b992ab9c8037633cfd0fe698a97a878f59d8eb53c381e91e9a1a76fd215/gevent-26.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:9b4d3f34c913d1a6bec6d030365a517f3b527a9773b12e58cf56c3339bbe96e6", size = 2952523, upload-time = "2026-05-20T20:13:04.698Z" }, + { url = "https://files.pythonhosted.org/packages/74/11/c7dfc773eb43331a682efed610b49df6e976331f1b0e1c592a0c35d29872/gevent-26.5.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1d8da4e799431feeb4c9e441ac7431f0baabb9106976790d884289d08ac08359", size = 1787044, upload-time = "2026-05-20T21:17:32.845Z" }, + { url = "https://files.pythonhosted.org/packages/ae/28/9812933dac93560f46910a9e834805fe76f822c408bd1c20cdf299d7c311/gevent-26.5.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:51becdb4c30a8f45c1c028ad7a97bf5a1ed141f74b159a31aa9cc6aa1e6263a6", size = 1882342, upload-time = "2026-05-20T21:16:02.645Z" }, + { url = "https://files.pythonhosted.org/packages/96/4b/514f248f69b2230b69b0bb17f4158b0b05dd4b2cb469a60ab206e9fe7496/gevent-26.5.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:c42bbcd3d453b08ad8915fd3feaf3d44a3562cdf1c7b208f9837149711e16d9d", size = 1834136, upload-time = "2026-05-20T21:30:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/53/67/f5f30716efca99b6200ae89a9303a7e94dae085b7de6f6d0033c52a37f4b/gevent-26.5.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bd3445e4fbeeb46690ed8efe94b8d1d46b14aa04af8866ae7a8da5997828d1c6", size = 2115349, upload-time = "2026-05-20T20:35:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/09/d8/60e8809bde7986e6c4e6d106080b3603fa09b3bb0255fed1a4d8282e3ca2/gevent-26.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b573d5b2826edc705f31f07da6889ad483a6a0d64944ebd8d32205f7c5bf46fb", size = 1799443, upload-time = "2026-05-20T21:16:41.928Z" }, + { url = "https://files.pythonhosted.org/packages/f8/41/b388b2b1f0a026ea30687e51ddf81dbb783dfb55fac0a16708d2821d99e5/gevent-26.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d53b1b28f2082a151bded2850b53f6baed02f742d2a1584029e8bd42d457fb4", size = 2141117, upload-time = "2026-05-20T20:43:30.694Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f3/ac9a4b0de487e390c5d53a908a9347c0df0102de2bbf3e8603087769191d/gevent-26.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:23569ce0c254eb821fc3dcfe250843dde8b3180b09bae9e222e41aa3fa4885b7", size = 1699862, upload-time = "2026-05-20T20:15:33.642Z" }, + { url = "https://files.pythonhosted.org/packages/2a/cf/1ef1fc9b390563c0f97702f94a557d1649b7bbb5724f9b86c2122747e92f/gevent-26.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:40cdcdb2e404b6c82b82a4576bdb33958f23fc2deb0d933e9e022b362001e647", size = 1545341, upload-time = "2026-05-20T20:16:26.229Z" }, + { url = "https://files.pythonhosted.org/packages/17/55/7d98d3888e7bb9ad4656420dec69232ecbbea48792aff9295d0ad7cf8435/gevent-26.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:75a0050e4b87f08ddee7e56f59e6014cd7fcdc3153046c09a847940515d12c85", size = 2968223, upload-time = "2026-05-20T20:13:17.223Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b4/e8e116fcbcb9dc0bf3acc50037f86e1204c217c8ed5defde68be11b3aab6/gevent-26.5.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:fd1a0b83a04e19378d9466ae0ee2b5937cf1d7fbfdcb916b2aea82179a208574", size = 1793926, upload-time = "2026-05-20T21:17:34.321Z" }, + { url = "https://files.pythonhosted.org/packages/28/07/7b267e9754b661defb93542e97731a4df21f8a40dc0f6c853faa717cf124/gevent-26.5.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:4c964c15076e76391d523ec24202f579a2535f7e301a40efb1656ae046d3eb69", size = 1887632, upload-time = "2026-05-20T21:16:04.158Z" }, + { url = "https://files.pythonhosted.org/packages/5c/50/b47d29e99449bd13b557ffa451401dc13d397a9923f562ef90a4e8514502/gevent-26.5.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:45d5438d1c84da5df7e832434627624709543630977332bb4e2d05ecca362cc9", size = 1838688, upload-time = "2026-05-20T21:30:57.979Z" }, + { url = "https://files.pythonhosted.org/packages/8b/eb/5b54ccff11bc7d7bebd40a24571ccc115d5cdae4f6c32ab457b43b436e42/gevent-26.5.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:354f35924113abc954819216c2a6ee16751958c615681e0490946e31b437bd2f", size = 2120351, upload-time = "2026-05-20T20:35:32.699Z" }, + { url = "https://files.pythonhosted.org/packages/9c/70/30fd325c30e04b1e5174c61945e17421d53ddb2450366cc52cef234f8c4b/gevent-26.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a47cd2d32f6404212d374ad8014a3491d7477dcf0cc09c5a2308ad6d325fd663", size = 1806684, upload-time = "2026-05-20T21:16:43.87Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e8/fbf911ac3f9524ecfaed174d100fde671904ab8db92ceaf07faaebd13386/gevent-26.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:032157cebdedb84f2f52cdd980f2f5f2623eed6a8f083aadf44b44c47f628642", size = 2146606, upload-time = "2026-05-20T20:43:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4d/284fcbbfde66fd978c2980c1fbe0eabd586af6e4b728649e9cf459e8b38f/gevent-26.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:9c414935ba5fc88359110968851d3616f119082c937390d00a1c0f4f59be814f", size = 1722497, upload-time = "2026-05-20T20:16:44.274Z" }, + { url = "https://files.pythonhosted.org/packages/15/d2/9f66eb53434704402be0ba733bf3320bf589671a4b76fac52a7d6077e972/gevent-26.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:2a0f5993a04b95a35b3a118b1a58ba272833f9b547b774001dea29f90620882f", size = 1574249, upload-time = "2026-05-20T20:15:50.873Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d5/b4c50adb761878e3c96642b9f79bf44cee3120f3df55cd40876f51d89866/gevent-26.5.0-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2e117df896a2660c9ebd4e2b5afc02dfd6e2ddf9b495e787e67c72d105432b09", size = 2971993, upload-time = "2026-05-20T20:12:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/03/83/71c2a945e80198422d1d93dbe67355f249fb456b451bf9201199d3ef6a1a/gevent-26.5.0-cp315-cp315-manylinux_2_28_aarch64.whl", hash = "sha256:af5ffe9c11ffb8a39b6bef2e8b722aa2043ae4980977915c6aa8c68b4bc26e46", size = 1796658, upload-time = "2026-05-20T21:17:35.968Z" }, + { url = "https://files.pythonhosted.org/packages/42/96/548ca77aed5cb9a44e855a6c23ebceeb3554a0ea9ca0c01c311878899a3e/gevent-26.5.0-cp315-cp315-manylinux_2_28_ppc64le.whl", hash = "sha256:7da34aef7e87c43dd3662e5785e79ed505c01399a7cb42876d2d8925969fd75f", size = 1891473, upload-time = "2026-05-20T21:16:05.657Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4f/f48bd47d5287afb0fbcc56165f3ed47583f1803bad401653fe27e71ade2d/gevent-26.5.0-cp315-cp315-manylinux_2_28_s390x.whl", hash = "sha256:1c6293a7046bcc6f3d8972a74b19cd7a4cfd02d3881edf0fcf827aa514bd247b", size = 1841429, upload-time = "2026-05-20T21:30:59.907Z" }, + { url = "https://files.pythonhosted.org/packages/a0/72/1925215fc720d2561fa3ec8d4af5f098f8d0cbfa76a45fafed6e5ade7718/gevent-26.5.0-cp315-cp315-manylinux_2_28_x86_64.whl", hash = "sha256:d3bde0f140a275b2fa88e4b6516bda85551930e10bc2fd95e18c1b7d11cb780c", size = 2123895, upload-time = "2026-05-20T20:35:34.964Z" }, + { url = "https://files.pythonhosted.org/packages/83/59/0f584f6b1170c9a6abd9b70ccf5e9cc5ead34eabafabc0e21876ef0fe6f7/gevent-26.5.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e29fb4b17d9958ec8cb7f6339a111b29bc23f2c2efbef86189d1248bb4862d17", size = 1809047, upload-time = "2026-05-20T21:16:45.977Z" }, + { url = "https://files.pythonhosted.org/packages/82/88/61e854bfd98ac22eac78a97fc6db10de0f9ace46514072b435c217168729/gevent-26.5.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:b2239df2f7570efa03736678f3f053bb1bdd22a8a16cd28a2feb7d32ea5f533f", size = 2150764, upload-time = "2026-05-20T20:43:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f5/af048b97433d7f9a7df7f5510b2c46918b7d073dcfb3bf6d0ef0e5a83dcc/gevent-26.5.0-cp315-cp315-win_amd64.whl", hash = "sha256:aae214952fd38d27a42dc416bb70193962ec932384b63445d29bbb5817a1c042", size = 1722600, upload-time = "2026-05-20T20:19:56.81Z" }, + { url = "https://files.pythonhosted.org/packages/11/95/fb74a2299c6a2d78d9de12deaaac640ab5d2ef96a8e0f97a3ff84b9ca84b/gevent-26.5.0-cp315-cp315-win_arm64.whl", hash = "sha256:f7067564f139e33bf26a31ee3b13d168d76eb99a44b85ced626652b158baa80c", size = 1574406, upload-time = "2026-05-20T20:17:12.125Z" }, ] [[package]] @@ -353,17 +440,21 @@ wheels = [ [[package]] name = "hatch" -version = "1.14.1" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "backports-zstd", marker = "python_full_version < '3.14'" }, { name = "click" }, + { name = "distro", marker = "sys_platform == 'linux'" }, { name = "hatchling" }, - { name = "httpx" }, + { name = "httpx2" }, { name = "hyperlink" }, { name = "keyring" }, { name = "packaging" }, { name = "pexpect" }, { name = "platformdirs" }, + { name = "pyproject-hooks" }, + { name = "python-discovery" }, { name = "rich" }, { name = "shellingham" }, { name = "tomli-w" }, @@ -371,11 +462,10 @@ dependencies = [ { name = "userpath" }, { name = "uv" }, { name = "virtualenv" }, - { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/43/c0b37db0e857a44ce5ffdb7e8a9b8fa6425d0b74dea698fafcd9bddb50d1/hatch-1.14.1.tar.gz", hash = "sha256:ca1aff788f8596b0dd1f8f8dfe776443d2724a86b1976fabaf087406ba3d0713", size = 5188180, upload-time = "2025-04-07T04:16:04.522Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/5a/7cacce8d33a93e81cda9b4f8623ebf67b73859950af0062e1f2c527f4b34/hatch-1.17.1.tar.gz", hash = "sha256:e5f2f389ececd7e86cd86863e3f921a9024cd04ce6ef6b36d07cd9a6dc37c9cd", size = 5252686, upload-time = "2026-07-08T01:54:37.327Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/40/19c0935bf9f25808541a0e3144ac459de696c5b6b6d4511a98d456c69604/hatch-1.14.1-py3-none-any.whl", hash = "sha256:39cdaa59e47ce0c5505d88a951f4324a9c5aafa17e4a877e2fde79b36ab66c21", size = 125770, upload-time = "2025-04-07T04:16:02.525Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0a/2b4e653186fc85061f0dfde43d602e7e93c08c0d75b23fa3577f9b3f83fd/hatch-1.17.1-py3-none-any.whl", hash = "sha256:cc6c06d302cfa785c35586ab4285c8c28a24b1744addb66344192302f6958083", size = 162405, upload-time = "2026-07-08T01:54:35.452Z" }, ] [[package]] @@ -395,31 +485,32 @@ wheels = [ ] [[package]] -name = "httpcore" -version = "1.0.9" +name = "httpcore2" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "certifi" }, { name = "h11" }, + { name = "truststore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/fe/6a3f9f1a8bb8733326140737446aaf72fddb8b54b8f202302f5c84960613/httpcore2-2.7.0.tar.gz", hash = "sha256:6dc0fedf329a52a990930a5579edfebaea81118ea700ea0dd7de2b5e5be49efc", size = 65593, upload-time = "2026-07-14T20:40:01.111Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6c/62e2e279e63fc4f7a5ee841ef13175a8bbc613f258e9dcc186e9de803a42/httpcore2-2.7.0-py3-none-any.whl", hash = "sha256:1452f589fe23f55b44546cd884294c41a29330af902bc0b71a761fd52d18f92b", size = 81506, upload-time = "2026-07-14T20:39:58.053Z" }, ] [[package]] -name = "httpx" -version = "0.28.1" +name = "httpx2" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, + { name = "httpcore2" }, { name = "idna" }, + { name = "truststore" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/4a/129b2e21b90ac2985d3928d96792bccc39bc6dfe796c5eee2d8ec06d4105/httpx2-2.7.0.tar.gz", hash = "sha256:8b30709aed5c8465b0dd3b95c09ce301c8f79e7e7a2d00ab0af551e0d0375b07", size = 94487, upload-time = "2026-07-14T20:40:02.318Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b8/c341bba6411bdfda786020343c47a75ef472f6085caf82391b142b1a3ad9/httpx2-2.7.0-py3-none-any.whl", hash = "sha256:ed2a2719c696789e09493bd8e2bec3d8bd925cc6e26b68389ec25ade132f7bf4", size = 90234, upload-time = "2026-07-14T20:39:59.531Z" }, ] [[package]] @@ -445,11 +536,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -577,11 +668,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -607,11 +698,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, ] [[package]] @@ -625,7 +716,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -634,9 +725,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, ] [[package]] @@ -668,20 +759,29 @@ wheels = [ [[package]] name = "pyproject-api" -version = "1.9.1" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710, upload-time = "2025-05-12T14:41:58.025Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/bd/2f985c12bf33fdd8637e3a8f9418d6806f177601dee7c4924d0b2bb28650/pyproject_api-1.11.0.tar.gz", hash = "sha256:b8807d85a293e6c9f133e6575946fed45f1d42b22d58c780b33aa2421a799549", size = 23787, upload-time = "2026-07-21T13:09:33.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158, upload-time = "2025-05-12T14:41:56.217Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/2822ee12bafe87af8f6cc620f7d1f126e77f82ce56ba888b94a9af5a979c/pyproject_api-1.11.0-py3-none-any.whl", hash = "sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8", size = 13275, upload-time = "2026-07-21T13:09:32.559Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, ] [[package]] name = "pytest" -version = "8.3.5" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -689,11 +789,12 @@ dependencies = [ { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, + { name = "pygments" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] @@ -708,6 +809,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] +[[package]] +name = "python-discovery" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" @@ -806,52 +920,58 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, -] - [[package]] name = "tomli" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" }, - { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" }, - { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" }, - { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" }, - { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" }, - { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" }, - { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" }, - { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" }, - { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" }, - { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" }, - { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" }, - { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" }, - { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" }, - { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" }, - { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" }, - { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" }, - { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" }, - { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, ] [[package]] @@ -874,24 +994,25 @@ wheels = [ [[package]] name = "tox" -version = "4.26.0" +version = "4.58.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cachetools" }, - { name = "chardet" }, { name = "colorama" }, { name = "filelock" }, { name = "packaging" }, { name = "platformdirs" }, { name = "pluggy" }, { name = "pyproject-api" }, + { name = "python-discovery" }, { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tomli-w" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/3c/dcec0c00321a107f7f697fd00754c5112572ea6dcacb40b16d8c3eea7c37/tox-4.26.0.tar.gz", hash = "sha256:a83b3b67b0159fa58e44e646505079e35a43317a62d2ae94725e0586266faeca", size = 197260, upload-time = "2025-05-13T15:04:28.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/8e/4d2b1b2a81f4de1cd4e54fa40df1ab5f9bb88fe2e37461fc44aba8f9d302/tox-4.58.0.tar.gz", hash = "sha256:ab0b126a04dd56bc18e6d216386db09335247f2289b54cf534deb5c4ae3a8d2e", size = 296926, upload-time = "2026-07-21T13:10:36.622Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/14/f58b4087cf248b18c795b5c838c7a8d1428dfb07cb468dad3ec7f54041ab/tox-4.26.0-py3-none-any.whl", hash = "sha256:75f17aaf09face9b97bd41645028d9f722301e912be8b4c65a3f938024560224", size = 172761, upload-time = "2025-05-13T15:04:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/7ba55871e9d794d40b6c8424f2e5d1b267ea5d9a4bd2175e08b57960ba13/tox-4.58.0-py3-none-any.whl", hash = "sha256:dcae21f5f015f3a67658e35644cce0d1aa0dedcd06f3927f95d84e1717f6cea5", size = 223298, upload-time = "2026-07-21T13:10:34.731Z" }, ] [[package]] @@ -903,13 +1024,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/ef/c6deb083748be3bcad6f471b6ae983950c161890bf5ae1b2af80cc56c530/trove_classifiers-2025.5.9.12-py3-none-any.whl", hash = "sha256:e381c05537adac78881c8fa345fd0e9970159f4e4a04fcc42cfd3129cca640ce", size = 14119, upload-time = "2025-05-09T12:04:46.38Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" -version = "4.13.2" +version = "4.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, ] [[package]] @@ -926,41 +1056,44 @@ wheels = [ [[package]] name = "uv" -version = "0.7.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/4f/c26b354fc791fb716a990f6b0147c0b5d69351400030654827fb920fd79b/uv-0.7.8.tar.gz", hash = "sha256:a59d6749587946d63d371170d8f69d168ca8f4eade5cf880ad3be2793ea29c77", size = 3258494, upload-time = "2025-05-24T00:28:18.241Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/48/dd73c6a9b7b18dc1784b243cd5a93c14db34876c5a5cbb215e00be285e05/uv-0.7.8-py3-none-linux_armv6l.whl", hash = "sha256:ff1b7e4bc8a1d260062782ad34d12ce0df068df01d4a0f61d0ddc20aba1a5688", size = 16741809, upload-time = "2025-05-24T00:27:20.873Z" }, - { url = "https://files.pythonhosted.org/packages/b4/bd/0bc26f1f4f476cff93c8ce2d258819b10b9a4e41a9825405788ef25a2300/uv-0.7.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b83866be6a69f680f3d2e36b3befd2661b5596e59e575e266e7446b28efa8319", size = 16836506, upload-time = "2025-05-24T00:27:25.229Z" }, - { url = "https://files.pythonhosted.org/packages/26/28/1573e22b5f109f7779ddf64cb11e8e475ac05cf94e6b79ad3a4494c8c39c/uv-0.7.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f749b58a5c348c455083781c92910e49b4ddba85c591eb67e97a8b84db03ef9b", size = 15642479, upload-time = "2025-05-24T00:27:28.866Z" }, - { url = "https://files.pythonhosted.org/packages/ad/f1/3d403896ea1edeea9109cab924e6a724ed7f5fbdabe8e5e9f3e3aa2be95a/uv-0.7.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c058ee0f8c20b0942bd9f5c83a67b46577fa79f5691df8867b8e0f2d74cbadb1", size = 16043352, upload-time = "2025-05-24T00:27:31.911Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2e/a914e491af320be503db26ff57f1b328738d1d7419cdb690e6e31d87ae16/uv-0.7.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2a07bdf9d6aadef40dd4edbe209bca698a3d3244df5285d40d2125f82455519c", size = 16413446, upload-time = "2025-05-24T00:27:35.363Z" }, - { url = "https://files.pythonhosted.org/packages/c3/cc/a396870530db7661eac080d276eba25df1b6c930f50c721f8402370acd12/uv-0.7.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13af6b94563f25bdca6bb73e294648af9c0b165af5bb60f0c913ab125ec45e06", size = 17188599, upload-time = "2025-05-24T00:27:38.979Z" }, - { url = "https://files.pythonhosted.org/packages/d0/96/299bd3895d630e28593dcc54f4c4dbd72e12b557288c6d153987bbd62f34/uv-0.7.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4acc09c06d6cf7a27e0f1de4edb8c1698b8a3ffe34f322b10f4c145989e434b9", size = 18105049, upload-time = "2025-05-24T00:27:42.194Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a4/9fa0b6a4540950fe7fa66d37c44228d6ad7bb6d42f66e16f4f96e20fd50c/uv-0.7.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9221a9679f2ffd031b71b735b84f58d5a2f1adf9bfa59c8e82a5201dad7db466", size = 17777603, upload-time = "2025-05-24T00:27:45.695Z" }, - { url = "https://files.pythonhosted.org/packages/d7/62/988cca0f1723406ff22edd6a9fb5e3e1d4dd0af103d8c3a64effadc685fd/uv-0.7.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:409cee21edcaf4a7c714893656ab4dd0814a15659cb4b81c6929cbb75cd2d378", size = 22222113, upload-time = "2025-05-24T00:27:49.172Z" }, - { url = "https://files.pythonhosted.org/packages/06/36/0e7943d9415560aa9fdd775d0bb4b9c06b69c543f0647210e5b84776658b/uv-0.7.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81ac0bb371979f48d1293f9c1bee691680ea6a724f16880c8f76718f5ff50049", size = 17454597, upload-time = "2025-05-24T00:27:52.478Z" }, - { url = "https://files.pythonhosted.org/packages/bb/70/666be8dbc6a49e1a096f4577d69c4e6f78b3d9228fa2844d1bece21f5cd0/uv-0.7.8-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:3c620cecd6f3cdab59b316f41c2b1c4d1b709d9d5226cadeec370cfeed56f80c", size = 16335744, upload-time = "2025-05-24T00:27:55.657Z" }, - { url = "https://files.pythonhosted.org/packages/24/a5/c1fbffc8b62121c0d07aa66e7e5135065ff881ebb85ba307664125f4c51c/uv-0.7.8-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:0c691090ff631dde788c8f4f1b1ea20f9deb9d805289796dcf10bc4a144a817e", size = 16439468, upload-time = "2025-05-24T00:27:58.599Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/a079658721b88d483c97a1765f9fd4f1b8b4fa601f2889d86824244861f2/uv-0.7.8-py3-none-musllinux_1_1_i686.whl", hash = "sha256:4a117fe3806ba4ebb9c68fdbf91507e515a883dfab73fa863df9bc617d6de7a3", size = 16740156, upload-time = "2025-05-24T00:28:01.657Z" }, - { url = "https://files.pythonhosted.org/packages/14/69/a2d110786c4cf093d788cfcde9e99c634af087555f0bf9ceafc009d051ed/uv-0.7.8-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:91d022235b39e59bab4bce7c4b634dc67e16fa89725cdfb2149a6ef7eaf6d784", size = 17569652, upload-time = "2025-05-24T00:28:04.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/56/db6db0dc20114b76eb48dbd5167a26a2ebe51e8b604b4e84c5ef84ef4103/uv-0.7.8-py3-none-win32.whl", hash = "sha256:6ebe252f34c50b09b7f641f8e603d7b627f579c76f181680c757012b808be456", size = 16958006, upload-time = "2025-05-24T00:28:07.996Z" }, - { url = "https://files.pythonhosted.org/packages/4b/80/5c78a9adc50fa3b7cca3a0c1245dff8c74d906ab53c3503b1f8133243930/uv-0.7.8-py3-none-win_amd64.whl", hash = "sha256:b5b62ca8a1bea5fdbf8a6372eabb03376dffddb5d139688bbb488c0719fa52fc", size = 18457129, upload-time = "2025-05-24T00:28:11.844Z" }, - { url = "https://files.pythonhosted.org/packages/15/52/fd76b44942ac308e1dbbebea8b23de67a0f891a54d5e51346c3c3564dd9b/uv-0.7.8-py3-none-win_arm64.whl", hash = "sha256:ad79388b0c6eff5383b963d8d5ddcb7fbb24b0b82bf5d0c8b1bdbfbe445cb868", size = 17177058, upload-time = "2025-05-24T00:28:15.561Z" }, +version = "0.11.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/f0/501fe8a234ac96ea8869e84cb47b3bd77e39a0e80ee01950713e24fe1c4a/uv-0.11.31.tar.gz", hash = "sha256:763609d59721af5b8522e16deac6cffe8055f82bb837740c708917506f305185", size = 6045932, upload-time = "2026-07-22T01:48:45.407Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/6a/065e1e7feaf375eee8d1bb05e5276185708149dd48c27a230f320a0fc8bf/uv-0.11.31-py3-none-linux_armv6l.whl", hash = "sha256:6adaaf151f53fef04dec685f0816d304c09a091b2b609746f86ee7c55ada6bcd", size = 25838313, upload-time = "2026-07-22T01:47:21.787Z" }, + { url = "https://files.pythonhosted.org/packages/e1/15/529b573723a36badbda1e13a432c3b21a7554b8ddef3b20a2200037051c2/uv-0.11.31-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2d84b6dd6b1eaf42fc923203d21a5efd052e1982e4f961eccecc2a6905ffbecd", size = 24795386, upload-time = "2026-07-22T01:47:26.882Z" }, + { url = "https://files.pythonhosted.org/packages/52/be/a809b3fe20c3d37bc667de33f38475c4c94f860979d07049ccddb6d91801/uv-0.11.31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:335f3262c4350c004cf6e3b7061200148d670e579bcee7ba0e31c7535f125018", size = 23410594, upload-time = "2026-07-22T01:47:31.43Z" }, + { url = "https://files.pythonhosted.org/packages/c9/9a/ebaacd8b7713fd755d23623e0e8de78dfd001f6abc818034f2e9058035c7/uv-0.11.31-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:e1cf5803c39221387b2fe8be2b522b0529ac732831a2e52a92330e053539995e", size = 25358933, upload-time = "2026-07-22T01:47:36.544Z" }, + { url = "https://files.pythonhosted.org/packages/81/34/c30568a0f9e556be766c341106bf6ca2ef5c8067be6c11665a53df0549f1/uv-0.11.31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:68ae6974ffbd04703e138654e83220a16e7b0b679271a8f209f928928dd399f8", size = 25346175, upload-time = "2026-07-22T01:47:41.132Z" }, + { url = "https://files.pythonhosted.org/packages/b5/63/18467b66f578dc121ec6d4af78074a0db06b27627b072fc433226a99a384/uv-0.11.31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:48f7ec906eaebf9717a01ba0f7635cd0cac648ff5c8fff3a57b8805e6bd49078", size = 25381240, upload-time = "2026-07-22T01:47:45.659Z" }, + { url = "https://files.pythonhosted.org/packages/b8/43/b51d6b8ad1307f51dd75154d623d6a527c6de600086bb0446251047d2e5e/uv-0.11.31-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a2cfd1638420f9a2a7dbca71c808edaf3929b6d8f4ec2ceac2f27014150d0e3", size = 26661822, upload-time = "2026-07-22T01:47:50.42Z" }, + { url = "https://files.pythonhosted.org/packages/30/9f/008c859ea3fc0d25d6ac32e1293a0795c737b0a472a8603b5e511b56659c/uv-0.11.31-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aec65d8f54403e60f32c50e44d98b6420de55211ad22a340927efc5db6ef4205", size = 27594901, upload-time = "2026-07-22T01:47:55.444Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ca/65a2856e79a208f8a1ece0ac077fbee531db7455608c06ab677b2513cbc4/uv-0.11.31-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5610fea306dc6ce5021482d272e6372f0c3dfd1e24ec061f90b1b9287263ac58", size = 26708620, upload-time = "2026-07-22T01:48:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c3/019ecbf3564d909c55fcf065592aff90b8b386d679e379caf356de4473f9/uv-0.11.31-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac79fca5807122676701279a1f36d7917a922f25a0ab5c5cf58a252f666e7e", size = 26894006, upload-time = "2026-07-22T01:48:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/f6/9b/b67aa8736f9f82a9f99cec93c28d66d77ff42126914784a3f680bc737b56/uv-0.11.31-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:c4d4b34264017dc9047d0d49f09363a5e20b388481cddc39d5c44b16b3c2a57c", size = 25504398, upload-time = "2026-07-22T01:48:09.859Z" }, + { url = "https://files.pythonhosted.org/packages/44/d1/37e3a30f55e1c623fca484efbb80b6e157b922ee79f5cb7b1c0ff5005f0f/uv-0.11.31-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:9ce168c7323aee61ef07220c815f1b3e3a1b74241acb9f56c0b7fc4794dad600", size = 26307040, upload-time = "2026-07-22T01:48:14.555Z" }, + { url = "https://files.pythonhosted.org/packages/00/cc/f607ba28a93100c55b3e048838f85481f8b55a24e3a338e42c151f5884ae/uv-0.11.31-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f3f8f58030ba4f711542d581b5fc3cde54db75a773fc873178f7b353f68f8711", size = 26425088, upload-time = "2026-07-22T01:48:19.204Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ae/dd865e1d680799f05ff32895689700a23f37e905aac9807c93521fc76d8c/uv-0.11.31-py3-none-musllinux_1_1_i686.whl", hash = "sha256:b1384887f8a4a0b0dfb8c6c81b2f819d1771015a96c70f89ef12559df8206b28", size = 25920399, upload-time = "2026-07-22T01:48:23.866Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0a/45ebfd783235a7a39ae1e99dc0bf26c083ea24a37584e530c7fbb6e38a21/uv-0.11.31-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:c6e052de498086b2014020536829b7e2b6f173ba95b07e55e9e0f85ac00a3927", size = 27126383, upload-time = "2026-07-22T01:48:28.376Z" }, + { url = "https://files.pythonhosted.org/packages/d9/c7/4cf78823c123efd3bdac50eb26f4b8fc2c222962d47918a7bb2b465b6522/uv-0.11.31-py3-none-win32.whl", hash = "sha256:03e18e463ecf0e1c347f901f9a8739059d07e2e2ebce72c0f8f1b9328a349c6f", size = 24644301, upload-time = "2026-07-22T01:48:33.094Z" }, + { url = "https://files.pythonhosted.org/packages/e1/4f/f2c3d0993ebab255a2dd7c476678c0307da03d890fb98761e8221d7bb043/uv-0.11.31-py3-none-win_amd64.whl", hash = "sha256:1a4bb0030d9070a4831a4f3115c5489998da7ca936e569a72696c90af469177a", size = 27699662, upload-time = "2026-07-22T01:48:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8b/259e12b510c655f743f9a0e3171e6e9276dfd35a058d04d6aeef1fc4a897/uv-0.11.31-py3-none-win_arm64.whl", hash = "sha256:88ab5fdbeff4ab10ac890ab2dd01b7ad62b92251665423e4f68b1cf977fbe635", size = 25849721, upload-time = "2026-07-22T01:48:42.513Z" }, ] [[package]] name = "virtualenv" -version = "20.31.2" +version = "21.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, ] [[package]] @@ -1018,78 +1151,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b6/66/ac05b741c2129fdf668b85631d2268421c5cd1a9ff99be1674371139d665/zope.interface-7.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a71a5b541078d0ebe373a81a3b7e71432c61d12e660f1d67896ca62d9628045b", size = 264696, upload-time = "2024-11-28T08:48:41.161Z" }, { url = "https://files.pythonhosted.org/packages/0a/2f/1bccc6f4cc882662162a1158cda1a7f616add2ffe322b28c99cb031b4ffc/zope.interface-7.2-cp313-cp313-win_amd64.whl", hash = "sha256:4893395d5dd2ba655c38ceb13014fd65667740f09fa5bb01caa1e6284e48c0cd", size = 212472, upload-time = "2024-11-28T08:49:56.587Z" }, ] - -[[package]] -name = "zstandard" -version = "0.23.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/55/bd0487e86679db1823fc9ee0d8c9c78ae2413d34c0b461193b5f4c31d22f/zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9", size = 788701, upload-time = "2024-07-15T00:13:27.351Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8a/ccb516b684f3ad987dfee27570d635822e3038645b1a950c5e8022df1145/zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880", size = 633678, upload-time = "2024-07-15T00:13:30.24Z" }, - { url = "https://files.pythonhosted.org/packages/12/89/75e633d0611c028e0d9af6df199423bf43f54bea5007e6718ab7132e234c/zstandard-0.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77da4c6bfa20dd5ea25cbf12c76f181a8e8cd7ea231c673828d0386b1740b8dc", size = 4941098, upload-time = "2024-07-15T00:13:32.526Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7a/bd7f6a21802de358b63f1ee636ab823711c25ce043a3e9f043b4fcb5ba32/zstandard-0.23.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2170c7e0367dde86a2647ed5b6f57394ea7f53545746104c6b09fc1f4223573", size = 5308798, upload-time = "2024-07-15T00:13:34.925Z" }, - { url = "https://files.pythonhosted.org/packages/79/3b/775f851a4a65013e88ca559c8ae42ac1352db6fcd96b028d0df4d7d1d7b4/zstandard-0.23.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c16842b846a8d2a145223f520b7e18b57c8f476924bda92aeee3a88d11cfc391", size = 5341840, upload-time = "2024-07-15T00:13:37.376Z" }, - { url = "https://files.pythonhosted.org/packages/09/4f/0cc49570141dd72d4d95dd6fcf09328d1b702c47a6ec12fbed3b8aed18a5/zstandard-0.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:157e89ceb4054029a289fb504c98c6a9fe8010f1680de0201b3eb5dc20aa6d9e", size = 5440337, upload-time = "2024-07-15T00:13:39.772Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7c/aaa7cd27148bae2dc095191529c0570d16058c54c4597a7d118de4b21676/zstandard-0.23.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:203d236f4c94cd8379d1ea61db2fce20730b4c38d7f1c34506a31b34edc87bdd", size = 4861182, upload-time = "2024-07-15T00:13:42.495Z" }, - { url = "https://files.pythonhosted.org/packages/ac/eb/4b58b5c071d177f7dc027129d20bd2a44161faca6592a67f8fcb0b88b3ae/zstandard-0.23.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dc5d1a49d3f8262be192589a4b72f0d03b72dcf46c51ad5852a4fdc67be7b9e4", size = 4932936, upload-time = "2024-07-15T00:13:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/44/f9/21a5fb9bb7c9a274b05ad700a82ad22ce82f7ef0f485980a1e98ed6e8c5f/zstandard-0.23.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:752bf8a74412b9892f4e5b58f2f890a039f57037f52c89a740757ebd807f33ea", size = 5464705, upload-time = "2024-07-15T00:13:46.822Z" }, - { url = "https://files.pythonhosted.org/packages/49/74/b7b3e61db3f88632776b78b1db597af3f44c91ce17d533e14a25ce6a2816/zstandard-0.23.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80080816b4f52a9d886e67f1f96912891074903238fe54f2de8b786f86baded2", size = 4857882, upload-time = "2024-07-15T00:13:49.297Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7f/d8eb1cb123d8e4c541d4465167080bec88481ab54cd0b31eb4013ba04b95/zstandard-0.23.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:84433dddea68571a6d6bd4fbf8ff398236031149116a7fff6f777ff95cad3df9", size = 4697672, upload-time = "2024-07-15T00:13:51.447Z" }, - { url = "https://files.pythonhosted.org/packages/5e/05/f7dccdf3d121309b60342da454d3e706453a31073e2c4dac8e1581861e44/zstandard-0.23.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19a2d91963ed9e42b4e8d77cd847ae8381576585bad79dbd0a8837a9f6620a", size = 5206043, upload-time = "2024-07-15T00:13:53.587Z" }, - { url = "https://files.pythonhosted.org/packages/86/9d/3677a02e172dccd8dd3a941307621c0cbd7691d77cb435ac3c75ab6a3105/zstandard-0.23.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:59556bf80a7094d0cfb9f5e50bb2db27fefb75d5138bb16fb052b61b0e0eeeb0", size = 5667390, upload-time = "2024-07-15T00:13:56.137Z" }, - { url = "https://files.pythonhosted.org/packages/41/7e/0012a02458e74a7ba122cd9cafe491facc602c9a17f590367da369929498/zstandard-0.23.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:27d3ef2252d2e62476389ca8f9b0cf2bbafb082a3b6bfe9d90cbcbb5529ecf7c", size = 5198901, upload-time = "2024-07-15T00:13:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/65/3a/8f715b97bd7bcfc7342d8adcd99a026cb2fb550e44866a3b6c348e1b0f02/zstandard-0.23.0-cp310-cp310-win32.whl", hash = "sha256:5d41d5e025f1e0bccae4928981e71b2334c60f580bdc8345f824e7c0a4c2a813", size = 430596, upload-time = "2024-07-15T00:14:00.693Z" }, - { url = "https://files.pythonhosted.org/packages/19/b7/b2b9eca5e5a01111e4fe8a8ffb56bdcdf56b12448a24effe6cfe4a252034/zstandard-0.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:519fbf169dfac1222a76ba8861ef4ac7f0530c35dd79ba5727014613f91613d4", size = 495498, upload-time = "2024-07-15T00:14:02.741Z" }, - { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, - { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, - { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, - { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, - { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, - { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, - { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, - { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, - { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, - { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, - { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, - { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, - { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, - { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, - { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, - { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, - { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, - { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, - { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, -] From 009fd88c712c11549c00c7fa07f3a5c9160b4693 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 06:07:31 +0000 Subject: [PATCH 272/275] build(deps): bump cryptography Bumps the python-security group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography). Updates `cryptography` from 45.0.2 to 48.0.1 - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/45.0.2...48.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-version: 48.0.1 dependency-type: indirect dependency-group: python-security ... Signed-off-by: dependabot[bot] --- uv.lock | 201 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 72 deletions(-) diff --git a/uv.lock b/uv.lock index 54a47860..b644ba40 100644 --- a/uv.lock +++ b/uv.lock @@ -129,51 +129,96 @@ wheels = [ [[package]] name = "cffi" -version = "1.17.1" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259, upload-time = "2024-09-04T20:43:56.123Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200, upload-time = "2024-09-04T20:43:57.891Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235, upload-time = "2024-09-04T20:44:00.18Z" }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721, upload-time = "2024-09-04T20:44:01.585Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242, upload-time = "2024-09-04T20:44:03.467Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999, upload-time = "2024-09-04T20:44:05.023Z" }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242, upload-time = "2024-09-04T20:44:06.444Z" }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604, upload-time = "2024-09-04T20:44:08.206Z" }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727, upload-time = "2024-09-04T20:44:09.481Z" }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400, upload-time = "2024-09-04T20:44:10.873Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803, upload-time = "2024-09-04T20:44:15.231Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850, upload-time = "2024-09-04T20:44:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729, upload-time = "2024-09-04T20:44:18.688Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256, upload-time = "2024-09-04T20:44:20.248Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424, upload-time = "2024-09-04T20:44:21.673Z" }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568, upload-time = "2024-09-04T20:44:23.245Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736, upload-time = "2024-09-04T20:44:24.757Z" }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448, upload-time = "2024-09-04T20:44:26.208Z" }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976, upload-time = "2024-09-04T20:44:27.578Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, + { url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" }, + { url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" }, + { url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" }, + { url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" }, + { url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" }, + { url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" }, + { url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" }, + { url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" }, + { url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" }, + { url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" }, + { url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" }, + { url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" }, + { url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" }, + { url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" }, + { url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" }, + { url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" }, + { url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" }, + { url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" }, + { url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" }, + { url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" }, + { url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" }, + { url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" }, ] [[package]] @@ -208,39 +253,51 @@ wheels = [ [[package]] name = "cryptography" -version = "45.0.2" +version = "48.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f6/47/92a8914716f2405f33f1814b97353e3cfa223cd94a77104075d42de3099e/cryptography-45.0.2.tar.gz", hash = "sha256:d784d57b958ffd07e9e226d17272f9af0c41572557604ca7554214def32c26bf", size = 743865, upload-time = "2025-05-18T02:46:34.986Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/52/49e6c86278e1b5ec226e96b62322538ccc466306517bf9aad8854116a088/cryptography-45.0.2-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cc31c66411e14dd70e2f384a9204a859dc25b05e1f303df0f5326691061b839", size = 4201098, upload-time = "2025-05-18T02:45:15.178Z" }, - { url = "https://files.pythonhosted.org/packages/7b/3a/201272539ac5b66b4cb1af89021e423fc0bfacb73498950280c51695fb78/cryptography-45.0.2-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:463096533acd5097f8751115bc600b0b64620c4aafcac10c6d0041e6e68f88fe", size = 4429839, upload-time = "2025-05-18T02:45:17.614Z" }, - { url = "https://files.pythonhosted.org/packages/99/89/fa1a84832b8f8f3917875cb15324bba98def5a70175a889df7d21a45dc75/cryptography-45.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:cdafb86eb673c3211accffbffdb3cdffa3aaafacd14819e0898d23696d18e4d3", size = 4205154, upload-time = "2025-05-18T02:45:19.874Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c5/5225d5230d538ab461725711cf5220560a813d1eb68bafcfb00131b8f631/cryptography-45.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05c2385b1f5c89a17df19900cfb1345115a77168f5ed44bdf6fd3de1ce5cc65b", size = 3897145, upload-time = "2025-05-18T02:45:22.209Z" }, - { url = "https://files.pythonhosted.org/packages/fe/24/f19aae32526cc55ae17d473bc4588b1234af2979483d99cbfc57e55ffea6/cryptography-45.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:e9e4bdcd70216b08801e267c0b563316b787f957a46e215249921f99288456f9", size = 4462192, upload-time = "2025-05-18T02:45:24.773Z" }, - { url = "https://files.pythonhosted.org/packages/19/18/4a69ac95b0b3f03355970baa6c3f9502bbfc54e7df81fdb179654a00f48e/cryptography-45.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b2de529027579e43b6dc1f805f467b102fb7d13c1e54c334f1403ee2b37d0059", size = 4208093, upload-time = "2025-05-18T02:45:27.028Z" }, - { url = "https://files.pythonhosted.org/packages/7c/54/2dea55ccc9558b8fa14f67156250b6ee231e31765601524e4757d0b5db6b/cryptography-45.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10d68763892a7b19c22508ab57799c4423c7c8cd61d7eee4c5a6a55a46511949", size = 4461819, upload-time = "2025-05-18T02:45:29.39Z" }, - { url = "https://files.pythonhosted.org/packages/37/f1/1b220fcd5ef4b1f0ff3e59e733b61597505e47f945606cc877adab2c1a17/cryptography-45.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d2a90ce2f0f5b695e4785ac07c19a58244092f3c85d57db6d8eb1a2b26d2aad6", size = 4329202, upload-time = "2025-05-18T02:45:31.925Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e0/51d1dc4f96f819a56db70f0b4039b4185055bbb8616135884c3c3acc4c6d/cryptography-45.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:59c0c8f043dd376bbd9d4f636223836aed50431af4c5a467ed9bf61520294627", size = 4570412, upload-time = "2025-05-18T02:45:34.348Z" }, - { url = "https://files.pythonhosted.org/packages/31/a3/a3e4a298d3db4a04085728f5ae6c8cda157e49c5bb784886d463b9fbff70/cryptography-45.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e328357b6bbf79928363dbf13f4635b7aac0306afb7e5ad24d21d0c5761c3253", size = 4189148, upload-time = "2025-05-18T02:45:42.538Z" }, - { url = "https://files.pythonhosted.org/packages/53/90/100dfadd4663b389cb56972541ec1103490a19ebad0132af284114ba0868/cryptography-45.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49af56491473231159c98c2c26f1a8f3799a60e5cf0e872d00745b858ddac9d2", size = 4424113, upload-time = "2025-05-18T02:45:44.316Z" }, - { url = "https://files.pythonhosted.org/packages/0d/40/e2b9177dbed6f3fcbbf1942e1acea2fd15b17007204b79d675540dd053af/cryptography-45.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f169469d04a23282de9d0be349499cb6683b6ff1b68901210faacac9b0c24b7d", size = 4189696, upload-time = "2025-05-18T02:45:46.622Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/ec29c79f481e1767c2ff916424ba36f3cf7774de93bbd60428a3c52d1357/cryptography-45.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9cfd1399064b13043082c660ddd97a0358e41c8b0dc7b77c1243e013d305c344", size = 3881498, upload-time = "2025-05-18T02:45:48.884Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4a/72937090e5637a232b2f73801c9361cd08404a2d4e620ca4ec58c7ea4b70/cryptography-45.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f8084b7ca3ce1b8d38bdfe33c48116edf9a08b4d056ef4a96dceaa36d8d965", size = 4451678, upload-time = "2025-05-18T02:45:50.706Z" }, - { url = "https://files.pythonhosted.org/packages/d3/fa/1377fced81fd67a4a27514248261bb0d45c3c1e02169411fe231583088c8/cryptography-45.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2cb03a944a1a412724d15a7c051d50e63a868031f26b6a312f2016965b661942", size = 4192296, upload-time = "2025-05-18T02:45:52.422Z" }, - { url = "https://files.pythonhosted.org/packages/d1/cf/b6fe837c83a08b9df81e63299d75fc5b3c6d82cf24b3e1e0e331050e9e5c/cryptography-45.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a9727a21957d3327cf6b7eb5ffc9e4b663909a25fea158e3fcbc49d4cdd7881b", size = 4451749, upload-time = "2025-05-18T02:45:55.025Z" }, - { url = "https://files.pythonhosted.org/packages/af/d8/5a655675cc635c7190bfc8cffb84bcdc44fc62ce945ad1d844adaa884252/cryptography-45.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ddb8d01aa900b741d6b7cc585a97aff787175f160ab975e21f880e89d810781a", size = 4317601, upload-time = "2025-05-18T02:45:56.911Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d4/75d2375a20d80aa262a8adee77bf56950e9292929e394b9fae2481803f11/cryptography-45.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c0c000c1a09f069632d8a9eb3b610ac029fcc682f1d69b758e625d6ee713f4ed", size = 4560535, upload-time = "2025-05-18T02:45:59.33Z" }, - { url = "https://files.pythonhosted.org/packages/ac/7b/00e18d24f08bc642e4018e0066a6f872d85c744e3265910c3beabb1f4d73/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:965611880c3fa8e504b7458484c0697e00ae6e937279cd6734fdaa2bc954dc49", size = 4135515, upload-time = "2025-05-18T02:46:07.241Z" }, - { url = "https://files.pythonhosted.org/packages/29/9f/ea7ad5239c33c36f0e2cbdf631a0e3b7633466e87e55923f5b5ea1b0b92d/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d891942592789fa0ab71b502550bbadb12f540d7413d7d7c4cef4b02af0f5bc6", size = 4378133, upload-time = "2025-05-18T02:46:09.035Z" }, - { url = "https://files.pythonhosted.org/packages/47/f8/b4e29d87fbc4d2cf46b36e01fcb98305bf76699f34de6b877cddd8bc3a64/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:b19f4b28dd2ef2e6d600307fee656c00825a2980c4356a7080bd758d633c3a6f", size = 4136787, upload-time = "2025-05-18T02:46:10.772Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7c/ac19bbf24d261667a67aac712d8aa3bb740f94bc2391f06ccc90e783f3ff/cryptography-45.0.2-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7c73968fbb7698a4c5d6160859db560d3aac160edde89c751edd5a8bc6560c88", size = 4377741, upload-time = "2025-05-18T02:46:13.215Z" }, - { url = "https://files.pythonhosted.org/packages/8a/18/57bc98fa5d93e74c2c2b16a3c5383f7ec218f957aa44559c0008a46c3629/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc7693573f16535428183de8fd27f0ca1ca37a51baa0b41dc5ed7b3d68fe80e2", size = 4143347, upload-time = "2025-05-18T02:46:19.934Z" }, - { url = "https://files.pythonhosted.org/packages/84/6f/d015e7e7bd7f3a6c538973005de5a780d93b68138c2d88c804422cf46b1c/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:614bca7c6ed0d8ad1dce683a6289afae1f880675b4090878a0136c3da16bc693", size = 4387414, upload-time = "2025-05-18T02:46:21.944Z" }, - { url = "https://files.pythonhosted.org/packages/de/9e/fa5ec89cce7e4b86e430438da4d66b79113bdf321d0a00167d34b61daf19/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:4142e20c29224cec63e9e32eb1e6014fb285fe39b7be66b3564ca978a3a8afe9", size = 4145849, upload-time = "2025-05-18T02:46:24.327Z" }, - { url = "https://files.pythonhosted.org/packages/7c/09/5887d4fcc6f9c6fb19920789d094c4e25c2f604cc1b10b7e69d6f56187fe/cryptography-45.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:9a900036b42f7324df7c7ad9569eb92ba0b613cf699160dd9c2154b24fd02f8e", size = 4387449, upload-time = "2025-05-18T02:46:26.144Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" }, + { url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" }, + { url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" }, + { url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" }, + { url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" }, + { url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" }, + { url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" }, + { url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" }, + { url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" }, + { url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" }, + { url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" }, + { url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" }, + { url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" }, + { url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" }, + { url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" }, + { url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/3f43451b4f858bfceaaaffc649e6e787e8d4fb332a1d443af39ab02cc8f1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:735824ec41b7f74a7c45fb1591349333e4c696cb6c044e5f46356e560143e4cd", size = 4641226, upload-time = "2026-06-09T22:31:02.532Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/855584c2c23b09e4ce2d3b9c30e983e679cd60b068c513c6bbdb91e11782/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:92a46e1d638daa264ba2971c0b0489c9409787943efae4d60ffda3d091ef832c", size = 4668958, upload-time = "2026-06-09T22:32:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/42/3b/d35750e41d803d1e516fd6d6011f065424924da7af1748cef4cc9cb3ede1/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:7e234ac052af99f2700826a5c29ea99d9c1b1f80341cde62d11c8154dc8e0bd9", size = 4640793, upload-time = "2026-06-09T22:32:26.331Z" }, + { url = "https://files.pythonhosted.org/packages/ca/aa/cdb7181fe865285e87e96825aaab239400f1de0c3bfba9bd9769b79f1a92/cryptography-48.0.1-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:33842cf0888951cef5bc7ac724ab844a42044c1727b967b7f8997289a0464f92", size = 4668505, upload-time = "2026-06-09T22:31:27.534Z" }, ] [[package]] From f700163b5d938175d516d237e61a10b2b5613833 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 22 Jul 2026 14:30:03 +0200 Subject: [PATCH 273/275] fix: bound safe_terminate waits and gevent stdio exit Harden safe_terminate so a stuck kill cannot hang Group.terminate forever (supersedes #221 / residual of #43), and switch gevent fdopen to FileObject so init_popen_io children can exit cleanly. Co-authored-by: Cursor AI Co-authored-by: Cursor Grok 4.5 --- src/execnet/gateway_base.py | 6 ++++-- src/execnet/multi.py | 36 +++++++++++++++++++++++++++-------- testing/test_multi.py | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/execnet/gateway_base.py b/src/execnet/gateway_base.py index 53dbf38c..73dc7175 100644 --- a/src/execnet/gateway_base.py +++ b/src/execnet/gateway_base.py @@ -273,10 +273,12 @@ def start(self, func, args=()) -> None: gevent.spawn(func, *args) def fdopen(self, fd, mode, bufsize=1, closefd=True): - # XXX import gevent.fileobject - return gevent.fileobject.FileObjectThread(fd, mode, bufsize, closefd=closefd) + # Prefer FileObject (FileObjectPosix on Unix). FileObjectThread keeps a + # native threadpool alive and can prevent interpreter shutdown, which + # hangs tests/scripts that open stdio via init_popen_io and then exit. + return gevent.fileobject.FileObject(fd, mode, bufsize, closefd=closefd) def Lock(self): import gevent.lock diff --git a/src/execnet/multi.py b/src/execnet/multi.py index 1ea07eee..4dbf8b89 100644 --- a/src/execnet/multi.py +++ b/src/execnet/multi.py @@ -17,6 +17,7 @@ from typing import TYPE_CHECKING from typing import Any from typing import Literal +from typing import TypeAlias from typing import overload from . import gateway_bootstrap @@ -328,25 +329,44 @@ def waitclose(self) -> None: raise first +TermKillFunc: TypeAlias = Callable[[], object] +TermKillPair: TypeAlias = tuple[TermKillFunc, TermKillFunc] + + def safe_terminate( - execmodel: ExecModel, timeout: float | None, list_of_paired_functions + execmodel: ExecModel, + timeout: float | None, + list_of_paired_functions: Sequence[TermKillPair], ) -> None: + """Run terminate/kill pairs in parallel with a hard wait bound. + + Each termfunc is given ``timeout``. If it does not finish, killfunc runs. + Waiting for the worker pool is also bounded so a stuck kill cannot hang + the caller forever (see issues #43 / #221). + """ workerpool = WorkerPool(execmodel) - def termkill(termfunc, killfunc) -> None: + def termkill(termfunc: TermKillFunc, killfunc: TermKillFunc) -> None: termreply = workerpool.spawn(termfunc) try: termreply.get(timeout=timeout) except OSError: killfunc() - replylist = [] - for termfunc, killfunc in list_of_paired_functions: - reply = workerpool.spawn(termkill, termfunc, killfunc) - replylist.append(reply) + replylist = [ + workerpool.spawn(termkill, termfunc, killfunc) + for termfunc, killfunc in list_of_paired_functions + ] + # Allow term timeout plus a kill attempt; never block indefinitely. + wait_timeout = None if timeout is None else timeout * 2 for reply in replylist: - reply.get() - workerpool.waitall(timeout=timeout) + try: + reply.waitfinish(timeout=wait_timeout) + except OSError: + # termkill still running (typically stuck in killfunc). + continue + reply.get() # propagate worker exceptions, if any + workerpool.waitall(timeout=wait_timeout) default_group = Group() diff --git a/testing/test_multi.py b/testing/test_multi.py index 81e4525b..12e0ed3d 100644 --- a/testing/test_multi.py +++ b/testing/test_multi.py @@ -278,3 +278,41 @@ def kill() -> None: sleep(0.1) gc.collect() assert threading.active_count() == active + + +@pytest.mark.timeout(5) +def test_safe_terminate_does_not_hang_when_kill_blocks( + execmodel: ExecModel, +) -> None: + """Regression for #43/#221: a stuck kill must not hang terminate forever. + + Before the fix, reply.get() waited without a timeout after termfunc timed + out, so a blocking killfunc made Group.terminate() hang indefinitely + (seen via pytest-xdist teardown). + """ + kill_started = execmodel.Event() + release_kill = execmodel.Event() + other_killed: list[int] = [] + + def term_slow() -> None: + execmodel.sleep(10) + + def kill_hang() -> None: + kill_started.set() + release_kill.wait() + + def kill_ok() -> None: + other_killed.append(1) + + safe_terminate( + execmodel, + 0.2, + [ + (term_slow, kill_hang), + (term_slow, kill_ok), + ], + ) + + assert kill_started.is_set() + assert other_killed == [1] + release_kill.set() From 65c52a8442761c7599f5d29f21bca3dac4174ceb Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 22 Jul 2026 16:57:06 +0200 Subject: [PATCH 274/275] docs/ci: follow default-branch rename to main MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update CI triggers, release docs, and the socketserver raw URL after the master → main rename. Also point the docs link at the current src/ layout. Closes #420 Co-authored-by: Cursor AI Co-authored-by: Cursor Grok 4.5 --- .github/workflows/test.yml | 4 ++-- RELEASING.rst | 6 +++--- doc/example/test_info.rst | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d8936a8..ad25a355 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,12 +3,12 @@ name: test on: push: branches: - - "master" + - "main" - "test-me-*" pull_request: branches: - - "master" + - "main" # Cancel running jobs for the same workflow and branch. concurrency: diff --git a/RELEASING.rst b/RELEASING.rst index bc64a19a..a10991ec 100644 --- a/RELEASING.rst +++ b/RELEASING.rst @@ -7,7 +7,7 @@ This document describes the steps to make a new ``execnet`` release. Version ------- -``master`` should always be green and a potential release candidate. ``execnet`` follows +``main`` should always be green and a potential release candidate. ``execnet`` follows semantic versioning, so given that the current version is ``X.Y.Z``, to find the next version number one needs to look at the ``CHANGELOG.rst`` file: @@ -22,7 +22,7 @@ Steps To publish a new release ``X.Y.Z``, the steps are as follows: -#. Create a new branch named ``release-X.Y.Z`` from the latest ``master``. +#. Create a new branch named ``release-X.Y.Z`` from the latest ``main``. #. Update the ``CHANGELOG.rst`` file with the new release information. @@ -30,4 +30,4 @@ To publish a new release ``X.Y.Z``, the steps are as follows: #. Once the PR is **green** and **approved**, start the ``deploy`` workflow manually from the branch ``release-VERSION``, passing ``VERSION`` as parameter. -#. Merge the release PR to ``master``. +#. Merge the release PR to ``main``. diff --git a/doc/example/test_info.rst b/doc/example/test_info.rst index 0777dd7b..eefe91b3 100644 --- a/doc/example/test_info.rst +++ b/doc/example/test_info.rst @@ -172,7 +172,7 @@ sends back the results. Instantiate gateways through sockets ----------------------------------------------------- -.. _`socketserver.py`: https://raw.githubusercontent.com/pytest-dev/execnet/master/execnet/script/socketserver.py +.. _`socketserver.py`: https://raw.githubusercontent.com/pytest-dev/execnet/main/src/execnet/script/socketserver.py In cases where you do not have SSH-access to a machine you need to download a small version-independent standalone From 86fd2ff34c6070c84a20de0669d3bcf309505d0f Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:14:45 +0300 Subject: [PATCH 275/275] Add support for Python 3.15 --- .github/workflows/test.yml | 3 ++- pyproject.toml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad25a355..0efbdcc1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: os: [ windows-latest, ubuntu-latest ] - python: [ "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11" ] + python: [ "3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "pypy-3.11" ] steps: - uses: actions/checkout@v7 @@ -51,6 +51,7 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Install dependencies run: pip install tox twine diff --git a/pyproject.toml b/pyproject.toml index fcd2b4b5..97245491 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries",