diff --git a/.github/workflows/ci-code.yml b/.github/workflows/ci-code.yml index cb889ca160..d2145c86d3 100644 --- a/.github/workflows/ci-code.yml +++ b/.github/workflows/ci-code.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9', '3.13'] + python-version: ['3.9', '3.14'] database-backend: [psql] services: @@ -137,7 +137,7 @@ jobs: - name: Install aiida-core uses: ./.github/actions/install-aiida-core with: - python-version: '3.13' + python-version: '3.14' - name: Setup SSH on localhost run: .github/workflows/setup_ssh.sh @@ -159,7 +159,7 @@ jobs: - name: Install aiida-core uses: ./.github/actions/install-aiida-core with: - python-version: '3.13' + python-version: '3.14' from-lock: 'true' extras: '' diff --git a/.github/workflows/test-install.yml b/.github/workflows/test-install.yml index d879091fd5..fc642f6e94 100644 --- a/.github/workflows/test-install.yml +++ b/.github/workflows/test-install.yml @@ -111,7 +111,7 @@ jobs: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] # Not being able to install with conda on a specific Python version is # not sufficient to fail the run, but something we want to be aware of. diff --git a/environment.yml b/environment.yml index 62aa69f83f..374cac7f57 100644 --- a/environment.yml +++ b/environment.yml @@ -8,7 +8,7 @@ dependencies: - python~=3.9 - alembic~=1.8 - archive-path~=0.4.2 -- asyncssh~=2.19.0 +- asyncssh~=2.21.0 - circus~=0.19.0 - click-spinner~=0.1.8 - click<8.3,>=8.1.0 @@ -20,7 +20,7 @@ dependencies: - ipython>=7.6 - jedi<0.19 - jinja2~=3.0 -- kiwipy[rmq]~=0.8.4 +- kiwipy[rmq]>=0.8.4 - importlib-metadata~=6.0 - numpy<3,>=1.21 - paramiko~=3.0 @@ -38,3 +38,4 @@ dependencies: - upf_to_json~=0.9.2 - wrapt~=1.11 - chardet~=5.2.0 +- nest-asyncio diff --git a/pyproject.toml b/pyproject.toml index 5c9d205c87..0b5ddd06e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ classifiers = [ dependencies = [ 'alembic~=1.8', 'archive-path~=0.4.2', - "asyncssh~=2.19.0", + "asyncssh~=2.21.0", 'circus~=0.19.0', 'click-spinner~=0.1.8', 'click>=8.1.0,<8.3', @@ -39,11 +39,11 @@ dependencies = [ 'docstring-parser', 'get-annotations~=0.1;python_version<"3.10"', 'graphviz~=0.19', - 'plumpy~=0.25.0', + "plumpy~=0.25.0", 'ipython>=7.6', 'jedi<0.19', 'jinja2~=3.0', - 'kiwipy[rmq]~=0.8.4', + "kiwipy[rmq]>=0.8.4", 'importlib-metadata~=6.0', 'numpy>=1.21,<3', 'paramiko~=3.0', @@ -60,7 +60,8 @@ dependencies = [ 'typing-extensions~=4.1', 'upf_to_json~=0.9.2', 'wrapt~=1.11', - 'chardet~=5.2.0;platform_system=="Windows"' + 'chardet~=5.2.0;platform_system=="Windows"', + "nest-asyncio" ] description = 'AiiDA is a workflow manager for computational science with a strong focus on provenance, performance and extensibility.' dynamic = ['version'] # read from aiida/__init__.py @@ -280,7 +281,7 @@ tests = [ 'pytest-asyncio~=0.12,<0.17', 'pytest-timeout~=2.0', 'pytest-cov~=7.0', - 'pytest-rerunfailures~=12.0', + 'pytest-rerunfailures~=16.0', 'pytest-benchmark~=4.0', 'pytest-regressions~=2.2', 'pytest-xdist~=3.6', @@ -411,8 +412,9 @@ filterwarnings = [ 'ignore:The `aiida.orm.nodes.data.upf` module is deprecated.*:aiida.common.warnings.AiidaDeprecationWarning', 'ignore:The `Code` class is deprecated.*:aiida.common.warnings.AiidaDeprecationWarning', # https://github.com/aiidateam/plumpy/issues/283 - 'ignore:There is no current event loop:DeprecationWarning:plumpy', - 'ignore:There is no current event loop:DeprecationWarning:nest_asyncio', + "ignore:'asyncio.[sg]et_event_loop_policy' is deprecated:DeprecationWarning", + "ignore:'asyncio.DefaultEventLoopPolicy' is deprecated:DeprecationWarning", + "ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning:pytest_asyncio", # spglib deprecation 'ignore:dict interface is deprecated:DeprecationWarning', # https://github.com/aiidateam/archive-path/issues/21 @@ -579,3 +581,7 @@ commands = molecule {posargs:test} # .github/actions/install-aiida-core/action.yml # .readthedocs.yml required-version = ">=0.8.4" + +[tool.uv.sources] +nest-asyncio = {git = "https://github.com/danielhollas/nest_asyncio", branch = "python-3.14"} +plumpy = {git = "https://github.com/danielhollas/plumpy", rev = "06d6f3c32ee4e7f7dafcba0bd507401f349184c6"} diff --git a/src/aiida/engine/processes/futures.py b/src/aiida/engine/processes/futures.py index 096c11b277..0f512cb512 100644 --- a/src/aiida/engine/processes/futures.py +++ b/src/aiida/engine/processes/futures.py @@ -43,7 +43,12 @@ def __init__( from .process import ProcessState # create future in specified event loop - loop = loop if loop is not None else asyncio.get_event_loop() + if not loop: + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + super().__init__(loop=loop) assert not (poll_interval is None and communicator is None), 'Must poll or have a communicator to use' @@ -70,7 +75,10 @@ def _subscriber(*args, **kwargs): # Start polling if poll_interval is not None: - loop.create_task(self._poll_process(node, poll_interval)) + # TODO: Write a test with gc.collect to see if the assignment is needed? + # (per RUF006) + # self._task = loop.create_task(self._poll_process(node, poll_interval)) + loop.create_task(self._poll_process(node, poll_interval)) # noqa: RUF006 def cleanup(self) -> None: """Clean up the future by removing broadcast subscribers from the communicator if it still exists.""" diff --git a/src/aiida/engine/runners.py b/src/aiida/engine/runners.py index b19821b2e7..9851d8f89d 100644 --- a/src/aiida/engine/runners.py +++ b/src/aiida/engine/runners.py @@ -82,7 +82,14 @@ def __init__( ), 'Must supply a persister if you want to submit using communicator' set_event_loop_policy() - self._loop = loop if loop is not None else asyncio.get_event_loop() + if loop is not None: + self._loop = loop + else: + try: + self._loop = asyncio.get_event_loop() + except RuntimeError: + self._loop = asyncio.new_event_loop() + self._poll_interval = poll_interval self._broker_submit = broker_submit self._transport = transports.TransportQueue(self._loop) diff --git a/src/aiida/engine/transports.py b/src/aiida/engine/transports.py index 9b7e14fe83..cc9ae65b0e 100644 --- a/src/aiida/engine/transports.py +++ b/src/aiida/engine/transports.py @@ -45,7 +45,14 @@ class TransportQueue: def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None): """:param loop: An asyncio event, will use `asyncio.get_event_loop()` if not supplied""" - self._loop = loop if loop is not None else asyncio.get_event_loop() + + if loop is not None: + self._loop = loop + else: + try: + self._loop = asyncio.get_event_loop() + except RuntimeError: + self._loop = asyncio.new_event_loop() self._transport_requests: Dict[Hashable, TransportRequest] = {} @property diff --git a/src/aiida/engine/utils.py b/src/aiida/engine/utils.py index 5fc4335aa4..af9c8836f4 100644 --- a/src/aiida/engine/utils.py +++ b/src/aiida/engine/utils.py @@ -128,7 +128,11 @@ def interruptable_task( :param loop: the event loop in which to run the coroutine, by default uses asyncio.get_event_loop() :return: an InterruptableFuture """ - loop = loop or asyncio.get_event_loop() + try: + loop = loop or asyncio.get_event_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + future = InterruptableFuture() async def execute_coroutine(): @@ -151,7 +155,9 @@ async def execute_coroutine(): if not future.done(): future.set_result(result) - loop.create_task(execute_coroutine()) + # TODO: Store the task somewhere? + # See `ruff rule RUF006` + loop.create_task(execute_coroutine()) # noqa: RUF006 return future @@ -164,7 +170,7 @@ def ensure_coroutine(fct: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: :param fct: the function :returns: the coroutine """ - if asyncio.iscoroutinefunction(fct): + if inspect.iscoroutinefunction(fct): return fct async def wrapper(*args, **kwargs): @@ -252,7 +258,10 @@ def loop_scope(loop) -> Iterator[None]: :param loop: The event loop to make current for the duration of the scope """ - current = asyncio.get_event_loop() + try: + current = asyncio.get_event_loop() + except RuntimeError: + current = None try: asyncio.set_event_loop(loop) diff --git a/tests/conftest.py b/tests/conftest.py index b93feaf99f..156e97b02e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -510,6 +510,7 @@ def event_loop(manager, aiida_profile_clean): This is automatically called as a fixture for any test marked with ``@pytest.mark.asyncio``. """ + assert manager.get_runner().loop is not None yield manager.get_runner().loop diff --git a/tests/engine/test_manager.py b/tests/engine/test_manager.py index c4ffdab225..0eb35440e0 100644 --- a/tests/engine/test_manager.py +++ b/tests/engine/test_manager.py @@ -24,7 +24,10 @@ class TestJobManager: @pytest.fixture(autouse=True) def init_profile(self, aiida_localhost): """Initialize the profile.""" - self.loop = asyncio.get_event_loop() + try: + self.loop = asyncio.get_event_loop() + except RuntimeError: + self.loop = asyncio.new_event_loop() self.transport_queue = TransportQueue(self.loop) self.user = User.collection.get_default() self.computer = aiida_localhost @@ -39,7 +42,8 @@ def test_get_jobs_list(self): # Calling the method again, should return the exact same instance of `JobsList` assert self.manager.get_jobs_list(self.auth_info) == jobs_list - def test_request_job_info_update(self): + @pytest.mark.asyncio + async def test_request_job_info_update(self): """Test the `JobManager.request_job_info_update` method.""" with self.manager.request_job_info_update(self.auth_info, job_id=1) as request: assert isinstance(request, asyncio.Future) @@ -54,7 +58,10 @@ class TestJobsList: @pytest.fixture(autouse=True) def init_profile(self, aiida_localhost): """Initialize the profile.""" - self.loop = asyncio.get_event_loop() + try: + self.loop = asyncio.get_event_loop() + except RuntimeError: + self.loop = asyncio.new_event_loop() self.transport_queue = TransportQueue(self.loop) self.user = User.collection.get_default() self.computer = aiida_localhost diff --git a/tests/engine/test_process_function.py b/tests/engine/test_process_function.py index 33581868aa..47ce7605ec 100644 --- a/tests/engine/test_process_function.py +++ b/tests/engine/test_process_function.py @@ -36,8 +36,6 @@ CUSTOM_LABEL = 'Custom label' CUSTOM_DESCRIPTION = 'Custom description' -pytest.mark.requires_rmq - @workfunction def function_return_input(data): diff --git a/tests/engine/test_runners.py b/tests/engine/test_runners.py index 05a452c18e..766269a0a7 100644 --- a/tests/engine/test_runners.py +++ b/tests/engine/test_runners.py @@ -25,7 +25,11 @@ def runner(): """Construct and return a `Runner`.""" loop = asyncio.new_event_loop() - return get_manager().create_runner(poll_interval=0.5, loop=loop) + runner = get_manager().create_runner(poll_interval=0.5, loop=loop) + yield runner + if not runner.is_closed(): + runner.close() + loop.close() class Proc(Process): @@ -46,7 +50,6 @@ def the_hans_klok_comeback(loop): loop.stop() -@pytest.mark.requires_rmq def test_call_on_process_finish(runner): """Test call on calculation finish.""" loop = runner.loop diff --git a/tests/engine/test_transport.py b/tests/engine/test_transport.py index 2d11eafc6b..ef00468a87 100644 --- a/tests/engine/test_transport.py +++ b/tests/engine/test_transport.py @@ -55,6 +55,7 @@ async def nested(queue, authinfo): loop.run_until_complete(nested(transport_queue, self.authinfo)) + @pytest.mark.asyncio def test_get_transport_interleaved(self): """Test interleaved calls to get the same transport.""" transport_queue = TransportQueue() diff --git a/tests/engine/test_utils.py b/tests/engine/test_utils.py index fe18020445..1e8e0883cc 100644 --- a/tests/engine/test_utils.py +++ b/tests/engine/test_utils.py @@ -32,18 +32,18 @@ class TestExponentialBackoffRetry: """Tests for the exponential backoff retry coroutine.""" - @pytest.fixture(autouse=True) - def init_profile(self, aiida_localhost): - """Initialize the profile.""" - self.computer = aiida_localhost - self.authinfo = self.computer.get_authinfo(orm.User.collection.get_default()) - - @staticmethod - def test_exp_backoff_success(): + # @pytest.fixture(autouse=True) + # def init_profile(self, aiida_localhost): + # """Initialize the profile.""" + # self.computer = aiida_localhost + # self.authinfo = self.computer.get_authinfo(orm.User.collection.get_default()) + + # @staticmethod + @pytest.mark.asyncio + async def test_exp_backoff_success(self): """Test that exponential backoff will successfully catch exceptions as long as max_attempts is not exceeded.""" global ITERATION # noqa: PLW0603 ITERATION = 0 - loop = asyncio.get_event_loop() async def coro(): """A function that will raise RuntimeError as long as ITERATION is smaller than MAX_ITERATIONS.""" @@ -53,13 +53,13 @@ async def coro(): raise RuntimeError max_attempts = MAX_ITERATIONS + 1 - loop.run_until_complete(exponential_backoff_retry(coro, initial_interval=0.1, max_attempts=max_attempts)) + await exponential_backoff_retry(coro, initial_interval=0.1, max_attempts=max_attempts) - def test_exp_backoff_max_attempts_exceeded(self): + @pytest.mark.asyncio + async def test_exp_backoff_max_attempts_exceeded(self): """Test that exponential backoff will finally raise if max_attempts is exceeded""" global ITERATION # noqa: PLW0603 ITERATION = 0 - loop = asyncio.get_event_loop() def coro(): """A function that will raise RuntimeError as long as ITERATION is smaller than MAX_ITERATIONS.""" @@ -70,7 +70,7 @@ def coro(): max_attempts = MAX_ITERATIONS - 1 with pytest.raises(RuntimeError): - loop.run_until_complete(exponential_backoff_retry(coro, initial_interval=0.1, max_attempts=max_attempts)) + await exponential_backoff_retry(coro, initial_interval=0.1, max_attempts=max_attempts) def test_instantiate_process_invalid(manager): @@ -101,28 +101,28 @@ def work_function(): class TestInterruptable: """Tests for InterruptableFuture and interruptable_task.""" - def test_normal_future(self): + @pytest.mark.asyncio + async def test_normal_future(self): """Test interrupt future not being interrupted""" - loop = asyncio.get_event_loop() - interruptable = InterruptableFuture() fut = asyncio.Future() async def task(): fut.set_result('I am done') - loop.run_until_complete(interruptable.with_interrupt(task())) + await interruptable.with_interrupt(task()) assert not interruptable.done() assert fut.result() == 'I am done' - def test_interrupt(self): + @pytest.mark.asyncio + async def test_interrupt(self): """Test interrupt future being interrupted""" - loop = asyncio.get_event_loop() + loop = asyncio.get_running_loop() interruptable = InterruptableFuture() loop.call_soon(interruptable.interrupt, RuntimeError('STOP')) try: - loop.run_until_complete(interruptable.with_interrupt(asyncio.sleep(10.0))) + await interruptable.with_interrupt(asyncio.sleep(10.0)) except RuntimeError as err: assert str(err) == 'STOP' else: @@ -130,6 +130,7 @@ def test_interrupt(self): assert interruptable.done() + @pytest.mark.asyncio def test_inside_interrupted(self): """Test interrupt future being interrupted from inside of coroutine""" loop = asyncio.get_event_loop() @@ -152,6 +153,7 @@ async def task(): assert interruptable.done() assert fut.result() == 'I got set.' + @pytest.mark.asyncio def test_interruptable_future_set(self): """Test interrupt future being set before coroutine is done""" loop = asyncio.get_event_loop() diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000000..3628ac942f --- /dev/null +++ b/tests/test.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.mark.asyncio +def test_event_loop(): + pass diff --git a/uv.lock b/uv.lock index ff82b1d777..f90a5110d0 100644 --- a/uv.lock +++ b/uv.lock @@ -49,6 +49,7 @@ dependencies = [ { name = "jedi" }, { name = "jinja2" }, { name = "kiwipy", extra = ["rmq"] }, + { name = "nest-asyncio" }, { name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -125,7 +126,8 @@ pre-commit = [ { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-regressions" }, - { name = "pytest-rerunfailures" }, + { name = "pytest-rerunfailures", version = "16.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest-rerunfailures", version = "16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "python-memcached" }, @@ -178,7 +180,8 @@ tests = [ { name = "pytest-benchmark" }, { name = "pytest-cov" }, { name = "pytest-regressions" }, - { name = "pytest-rerunfailures" }, + { name = "pytest-rerunfailures", version = "16.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest-rerunfailures", version = "16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "python-memcached" }, @@ -203,7 +206,7 @@ requires-dist = [ { name = "alembic", specifier = "~=1.8" }, { name = "archive-path", specifier = "~=0.4.2" }, { name = "ase", marker = "extra == 'atomic-tools'", specifier = "~=3.21" }, - { name = "asyncssh", specifier = "~=2.19.0" }, + { name = "asyncssh", specifier = "~=2.21.0" }, { name = "bpython", marker = "extra == 'bpython'", specifier = "~=0.20" }, { name = "chardet", marker = "sys_platform == 'win32'", specifier = "~=5.2.0" }, { name = "circus", specifier = "~=0.19.0" }, @@ -224,10 +227,11 @@ requires-dist = [ { name = "jinja2", specifier = "~=3.0" }, { name = "jupyter", marker = "extra == 'notebook'", specifier = "~=1.0" }, { name = "jupyter-client", marker = "extra == 'notebook'", specifier = "~=8.0" }, - { name = "kiwipy", extras = ["rmq"], specifier = "~=0.8.4" }, + { name = "kiwipy", extras = ["rmq"], specifier = ">=0.8.4" }, { name = "matplotlib", marker = "extra == 'atomic-tools'", specifier = "~=3.3,>=3.3.4" }, { name = "mypy", marker = "extra == 'pre-commit'", specifier = "~=1.19.0" }, { name = "myst-nb", marker = "extra == 'docs'", specifier = "~=1.0.0" }, + { name = "nest-asyncio", git = "https://github.com/danielhollas/nest_asyncio?branch=python-3.14" }, { name = "notebook", marker = "extra == 'notebook'", specifier = "~=6.1,>=6.1.5" }, { name = "numpy", specifier = ">=1.21,<3" }, { name = "packaging", marker = "extra == 'pre-commit'", specifier = "~=23.0" }, @@ -235,7 +239,7 @@ requires-dist = [ { name = "pg8000", marker = "extra == 'tests'", specifier = "~=1.13" }, { name = "pgsu", specifier = "~=0.3.0" }, { name = "pgtest", marker = "extra == 'tests'", specifier = "~=1.3,>=1.3.1" }, - { name = "plumpy", specifier = "~=0.25.0" }, + { name = "plumpy", git = "https://github.com/danielhollas/plumpy?rev=06d6f3c32ee4e7f7dafcba0bd507401f349184c6" }, { name = "pre-commit", marker = "extra == 'pre-commit'", specifier = "~=3.5" }, { name = "psutil", specifier = "~=7.0" }, { name = "psycopg", extras = ["binary"], specifier = ">=3.0.2,<4" }, @@ -252,7 +256,7 @@ requires-dist = [ { name = "pytest-benchmark", marker = "extra == 'tests'", specifier = "~=4.0" }, { name = "pytest-cov", marker = "extra == 'tests'", specifier = "~=7.0" }, { name = "pytest-regressions", marker = "extra == 'tests'", specifier = "~=2.2" }, - { name = "pytest-rerunfailures", marker = "extra == 'tests'", specifier = "~=12.0" }, + { name = "pytest-rerunfailures", marker = "extra == 'tests'", specifier = "~=16.0" }, { name = "pytest-timeout", marker = "extra == 'tests'", specifier = "~=2.0" }, { name = "pytest-xdist", marker = "extra == 'tests'", specifier = "~=3.6" }, { name = "python-memcached", marker = "extra == 'rest'", specifier = "~=1.59" }, @@ -302,15 +306,44 @@ sdist = { url = "https://files.pythonhosted.org/packages/ea/90/1c9c13f2fdfa96602 [[package]] name = "aio-pika" -version = "9.4.3" +version = "9.5.6" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "aiormq" }, - { name = "yarl" }, + { name = "aiormq", marker = "python_full_version < '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "yarl", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/52/fe35c898bce5cc8af839ba786b38f7db8932aac48a67ba8ca7de3b074e07/aio_pika-9.5.6.tar.gz", hash = "sha256:5013f429e1235e1ce8df054a821e0eea140ea9afc94a09725b96590ea2dad001", size = 47308, upload-time = "2025-08-05T14:18:35.949Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/fb/c1cfb7cb98ccd2abdc91e170e7ba0e1e3088b6a9d051e4f2899d3249a231/aio_pika-9.5.6-py3-none-any.whl", hash = "sha256:47b532419185cf1105ae18daa45a5052ff98064915c5e080b2433431fe808193", size = 54303, upload-time = "2025-08-05T14:18:34.62Z" }, +] + +[[package]] +name = "aio-pika" +version = "9.5.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'win32'", + "python_full_version == '3.10.*' and sys_platform == 'win32'", + "python_full_version == '3.10.*' and sys_platform != 'win32'", ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/69/8649bdb97fa1521af3dafe23dbc5debadd4b01abb2850a4d193dae9b0451/aio_pika-9.4.3.tar.gz", hash = "sha256:fd2b1fce25f6ed5203ef1dd554dc03b90c9a46a64aaf758d032d78dc31e5295d", size = 47693, upload-time = "2024-08-13T06:49:09.619Z" } +dependencies = [ + { name = "aiormq", marker = "python_full_version >= '3.10'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "yarl", marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/73/8d1020683970de5532b3b01732d75c8bf922a6505fcdad1a9c7c6405242a/aio_pika-9.5.8.tar.gz", hash = "sha256:7c36874115f522bbe7486c46d8dd711a4dbedd67c4e8a8c47efe593d01862c62", size = 47408, upload-time = "2025-11-12T10:37:10.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/66/cad391d83b7266a667c85c826bb6c0d7f68519a0eed7634098c12fb39a4b/aio_pika-9.4.3-py3-none-any.whl", hash = "sha256:f1423d2d5a8b7315d144efe1773763bf687ac17aa1535385982687e9e5ed49bb", size = 53240, upload-time = "2024-08-13T06:49:07.276Z" }, + { url = "https://files.pythonhosted.org/packages/7c/91/513971861d845d28160ecb205ae2cfaf618b16918a9cd4e0b832b5360ce7/aio_pika-9.5.8-py3-none-any.whl", hash = "sha256:f4c6cb8a6c5176d00f39fd7431e9702e638449bc6e86d1769ad7548b2a506a8d", size = 54397, upload-time = "2025-11-12T10:37:08.374Z" }, ] [[package]] @@ -550,15 +583,15 @@ wheels = [ [[package]] name = "asyncssh" -version = "2.19.0" +version = "2.21.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/85/c723aa7dd69570a31b6638c1405e659712f18f569f280da2da27989445d3/asyncssh-2.19.0.tar.gz", hash = "sha256:723dead4d068b558708dc66a4ca7e7a93a813aa9416036eccb9af4c03ae2cf30", size = 533702, upload-time = "2024-12-13T00:52:01.966Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/b8/065c20bb5c9b8991648c0f25b13e445b4f51556cc3fdd0ad13ce4787c156/asyncssh-2.21.1.tar.gz", hash = "sha256:9943802955e2131536c2b1e71aacc68f56973a399937ed0b725086d7461c990c", size = 540515, upload-time = "2025-09-28T16:36:19.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/8f/f0749af566fa39204f6380473e6f9632b804daeb0ecb24cc7de1fc9f2717/asyncssh-2.19.0-py3-none-any.whl", hash = "sha256:bb82ac30ff0cb4393fbaf1114e606ad7a4f13d6c4bdaed423c033ee26b455228", size = 372704, upload-time = "2024-12-13T00:51:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/0e/89/4a9a61bc120ca68bce92b0ea176ddc0e550e58c60ab820603bd5246e7261/asyncssh-2.21.1-py3-none-any.whl", hash = "sha256:f218f9f303c78df6627d0646835e04039a156d15e174ad63c058d62de61e1968", size = 375529, upload-time = "2025-09-28T16:36:17.68Z" }, ] [[package]] @@ -2923,21 +2956,22 @@ wheels = [ [[package]] name = "kiwipy" -version = "0.8.5" +version = "0.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "deprecation" }, { name = "pyyaml" }, { name = "shortuuid" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/c9/60f4597b2f7ce9f1ce9f202c1ddc70b857716597d828fc5baa123a2fa17e/kiwipy-0.8.5.tar.gz", hash = "sha256:23b746f60577120764d662673335cea40cf34083d15f1ee8ab4fa6155b50d60f", size = 41087, upload-time = "2024-12-02T08:19:59.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/d1/a56aea1ee27c9aa73c7c6c785f4eb0539799392566b758fc920397afea91/kiwipy-0.9.0.tar.gz", hash = "sha256:3dc5a2cbe4bf7127da2c8a6c20476ddad30849b32fa12b495c622059c633db4f", size = 242121, upload-time = "2025-10-21T05:58:10.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/50/2d180b54272d467a3e5eb4d7e64df80a8bb11d483e908404d71905a2801b/kiwipy-0.8.5-py3-none-any.whl", hash = "sha256:b6acf17ba69fdfc9ce246673efd35e1db06a27b2c624ba1735d2159f8e665a1b", size = 41820, upload-time = "2024-12-02T08:19:58.573Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c0/2ed83a3b88048db2504ddd67a419148e23a1cf2bc64ee0bbaacb46c80bbb/kiwipy-0.9.0-py3-none-any.whl", hash = "sha256:8d861310d64dc15de50667c1c7b7295f2dba50a5561284e8a889e3a6c2b197f9", size = 41865, upload-time = "2025-10-21T05:58:09.033Z" }, ] [package.optional-dependencies] rmq = [ - { name = "aio-pika" }, + { name = "aio-pika", version = "9.5.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "aio-pika", version = "9.5.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pamqp" }, { name = "pytray" }, ] @@ -4019,12 +4053,8 @@ wheels = [ [[package]] name = "nest-asyncio" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, -] +version = "1.6.0.dev0" +source = { git = "https://github.com/danielhollas/nest_asyncio?branch=python-3.14#71d8eba682b8f91c82f1a95d4fada56470a30779" } [[package]] name = "networkx" @@ -4899,17 +4929,13 @@ wheels = [ [[package]] name = "plumpy" -version = "0.25.0" -source = { registry = "https://pypi.org/simple" } +version = "0.25.1" +source = { git = "https://github.com/danielhollas/plumpy?rev=06d6f3c32ee4e7f7dafcba0bd507401f349184c6#06d6f3c32ee4e7f7dafcba0bd507401f349184c6" } dependencies = [ { name = "kiwipy", extra = ["rmq"] }, { name = "nest-asyncio" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/b1/7c8141f04fb9060e9de7fd4fafed7ce429a16c1a903675a093d390f14b16/plumpy-0.25.0.tar.gz", hash = "sha256:5eccca0f11757db652b15bfb0bb95dc010a9a5fa000df5f9db51cf6a4d1e682f", size = 198187, upload-time = "2025-05-01T07:48:10.931Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/9a/e939c60e6376bdcc84ed9ae92ed96312c13781044c08b1b96be036d4b11b/plumpy-0.25.0-py3-none-any.whl", hash = "sha256:f15e7b471185265e6bbd0cfd37b9cd4b5bf51a9019fdd74247d9cfe28c5da617", size = 75249, upload-time = "2025-05-01T07:48:09.428Z" }, -] [[package]] name = "ply" @@ -5703,15 +5729,47 @@ wheels = [ [[package]] name = "pytest-rerunfailures" -version = "12.0" +version = "16.0.1" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] dependencies = [ - { name = "packaging" }, - { name = "pytest" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pytest", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/53/a543a76f922a5337d10df22441af8bf68f1b421cadf9aedf8a77943b81f6/pytest_rerunfailures-16.0.1.tar.gz", hash = "sha256:ed4b3a6e7badb0a720ddd93f9de1e124ba99a0cb13bc88561b3c168c16062559", size = 27612, upload-time = "2025-09-02T06:48:25.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/73/67dc14cda1942914e70fbb117fceaf11e259362c517bdadd76b0dd752524/pytest_rerunfailures-16.0.1-py3-none-any.whl", hash = "sha256:0bccc0e3b0e3388275c25a100f7077081318196569a121217688ed05e58984b9", size = 13610, upload-time = "2025-09-02T06:48:23.615Z" }, +] + +[[package]] +name = "pytest-rerunfailures" +version = "16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform == 'win32'", + "python_full_version >= '3.14' and platform_python_implementation == 'PyPy' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_python_implementation == 'PyPy' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform == 'win32'", + "python_full_version >= '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_python_implementation != 'PyPy' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and platform_python_implementation != 'PyPy' and sys_platform != 'win32'", + "python_full_version >= '3.14' and platform_python_implementation == 'PyPy' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_python_implementation == 'PyPy' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and platform_python_implementation == 'PyPy' and sys_platform != 'win32'", + "python_full_version == '3.10.*' and sys_platform == 'win32'", + "python_full_version == '3.10.*' and sys_platform != 'win32'", +] +dependencies = [ + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pytest", marker = "python_full_version >= '3.10'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/66/40f778791860c5234c5c677026d45c1a8708873b3dba8111de672bceac4f/pytest-rerunfailures-12.0.tar.gz", hash = "sha256:784f462fa87fe9bdf781d0027d856b47a4bfe6c12af108f6bd887057a917b48e", size = 21154, upload-time = "2023-07-05T05:53:46.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/04/71e9520551fc8fe2cf5c1a1842e4e600265b0815f2016b7c27ec85688682/pytest_rerunfailures-16.1.tar.gz", hash = "sha256:c38b266db8a808953ebd71ac25c381cb1981a78ff9340a14bcb9f1b9bff1899e", size = 30889, upload-time = "2025-10-10T07:06:01.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/14/e02206388902a828cc26894996dfc68eec50f7583bcddc4b5605d0c18b51/pytest_rerunfailures-12.0-py3-none-any.whl", hash = "sha256:9a1afd04e21b8177faf08a9bbbf44de7a0fe3fc29f8ddbe83b9684bd5f8f92a9", size = 12977, upload-time = "2023-07-05T05:53:43.909Z" }, + { url = "https://files.pythonhosted.org/packages/77/54/60eabb34445e3db3d3d874dc1dfa72751bfec3265bd611cb13c8b290adea/pytest_rerunfailures-16.1-py3-none-any.whl", hash = "sha256:5d11b12c0ca9a1665b5054052fcc1084f8deadd9328962745ef6b04e26382e86", size = 14093, upload-time = "2025-10-10T07:06:00.019Z" }, ] [[package]]