Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b88cd77
Deprecate get_event_loop by construct from new and force policy
unkcpz Jan 13, 2025
b179feb
Test with Python 3.14
danielhollas Oct 23, 2025
25e1a07
Run pre-commit on Python 3.14
danielhollas Oct 23, 2025
85db155
Fix Process constructor
danielhollas Oct 23, 2025
6f2577a
FORCE_COLOR
danielhollas Oct 23, 2025
58906c7
Omit needless dependencies
danielhollas Oct 23, 2025
0d72a39
session fixture
danielhollas Oct 23, 2025
dddda45
Remove deprecation warnings
danielhollas Oct 23, 2025
413d300
Ignore deprecation warnings for 3.16
danielhollas Oct 23, 2025
1d20683
More get_event_loop fixes
danielhollas Oct 23, 2025
2630f64
Remove ipykernel as a direct dependnecy
danielhollas Oct 23, 2025
d32ab2d
bump pytest-cov
danielhollas Oct 23, 2025
8a4d63a
bump shortuuid
danielhollas Oct 23, 2025
a350267
Revert "Remove ipykernel as a direct dependnecy"
danielhollas Oct 23, 2025
b9c6939
Bump ipykernel
danielhollas Oct 23, 2025
b86eb6e
Upgrade uv.lock
danielhollas Oct 23, 2025
8199166
Disable nest_asyncio
danielhollas Oct 23, 2025
dd5a2c9
Use nest_asyncio from fork
danielhollas Oct 23, 2025
0a7f666
Revert "Disable nest_asyncio"
danielhollas Oct 23, 2025
3f634cb
Use uv lockfile in CI
danielhollas Oct 23, 2025
86eb539
Tweak pytest config
danielhollas Oct 23, 2025
0ba4bd1
Ignore one more deprec warning
danielhollas Oct 23, 2025
73cc8af
Set correct python version
danielhollas Oct 23, 2025
d04b5d1
Catch warnings in the testing notebook
danielhollas Oct 23, 2025
7228b3e
Don't run coverage by hand
danielhollas Oct 23, 2025
45b8073
CI: Run uv sync explicitly
danielhollas Oct 23, 2025
797b039
Remove added fixtures from test_processes.py
danielhollas Oct 23, 2025
65c9e5e
Remove added fixtures from test_communications.py
danielhollas Oct 23, 2025
ac1aab2
revert changes to conftest.py
danielhollas Oct 23, 2025
53a33ba
Remove custom_event_loop_policy fixture from rmq/test_process_comms.py
danielhollas Oct 23, 2025
06d6f3c
Fix reset_event_loop_policy
danielhollas Oct 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ name: ci

on: [push, pull_request]

env:
FORCE_COLOR: 1

jobs:
pre-commit:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v5

- name: Set up Python 3.13
- name: Set up Python 3.14
uses: actions/setup-python@v5
with:
python-version: '3.13'
python-version: '3.14'

- name: Install Python dependencies
run: pip install -e .[pre-commit]
Expand All @@ -25,7 +28,7 @@ jobs:

strategy:
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']
fail-fast: false

services:
Expand All @@ -42,14 +45,18 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Install python dependencies
run: pip install .[tests]
- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python-version }}
version: 0.9.4
activate-environment: true

- name: Run pytest
run: pytest -s --cov=plumpy tests/
- name: Install dependencies
run: uv sync

- name: Create xml coverage
run: coverage xml
- name: Run pytest
run: pytest --cov=plumpy tests/

- name: Upload coverage to Codecov
if: github.repository == 'aiidateam/plumpy'
Expand Down
21 changes: 14 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ classifiers = [
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.14',
]
keywords = ['workflow', 'multithreaded', 'rabbitmq']
requires-python = '>=3.9'
Expand Down Expand Up @@ -54,21 +55,19 @@ docs = [
'myst-nb~=1.2.0',
'sphinx~=7.2.0',
'sphinx-book-theme~=1.1.4',
'importlib-metadata~=4.12.0',
]
pre-commit = [
'mypy==1.18.2',
'pre-commit~=3.6',
'types-pyyaml~=6.0'
]
tests = [
'ipykernel==6.12.1',
'ipykernel~=6.31.0',
'pytest~=8.4',
'pytest-asyncio~=0.12,<0.17',
'pytest-cov~=4.1',
'pytest-cov~=7.0',
'pytest-notebook>=0.8.0',
'shortuuid==1.0.8',
'importlib-resources~=5.2',
'shortuuid==1.0.13',
]

[tool.flit.module]
Expand Down Expand Up @@ -138,11 +137,16 @@ module = [
ignore_missing_imports = true

[tool.pytest.ini_options]
minversion = '6.0'
addopts = '--strict-config --strict-markers -ra --cov-report xml --cov-append'
minversion = '7.0'
testpaths = [
'test',
]
filterwarnings = []
filterwarnings = [
"ignore:'asyncio.get_event_loop_policy' is deprecated:DeprecationWarning::",
"ignore:'asyncio.set_event_loop_policy' is deprecated:DeprecationWarning::",
"ignore:'asyncio.DefaultEventLoopPolicy' is deprecated:DeprecationWarning::",
]

[tool.tox]
legacy_tox_ini = """
Expand Down Expand Up @@ -183,3 +187,6 @@ commands =
--port 0 --open-browser \
-n -b {posargs:html} docs/source/ docs/_build/{posargs:html}
"""

[tool.uv.sources]
nest-asyncio = { git = "https://github.com/danielhollas/nest_asyncio", branch = "python-3.14" }
12 changes: 9 additions & 3 deletions src/plumpy/communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import kiwipy

from . import futures
from . import events, futures
from .utils import ensure_coroutine

__all__ = [
Expand Down Expand Up @@ -124,7 +124,7 @@ def wrap_communicator(
return LoopCommunicator(communicator, loop)


class LoopCommunicator(kiwipy.Communicator): # type: ignore
class LoopCommunicator(kiwipy.Communicator): # type: ignore[misc]
"""Wrapper around a `kiwipy.Communicator` that schedules any subscriber messages on a given event loop."""

def __init__(self, communicator: kiwipy.Communicator, loop: Optional[asyncio.AbstractEventLoop] = None):
Expand All @@ -136,7 +136,13 @@ def __init__(self, communicator: kiwipy.Communicator, loop: Optional[asyncio.Abs
assert communicator is not None

self._communicator = communicator
self._loop: asyncio.AbstractEventLoop = loop or asyncio.get_event_loop()
if loop:
self._loop = loop
else:
try:
self._loop = asyncio.get_event_loop()
except RuntimeError:
self._loop = events.create_running_loop()

def loop(self) -> asyncio.AbstractEventLoop:
return self._loop
Expand Down
38 changes: 26 additions & 12 deletions src/plumpy/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
get_event_loop = asyncio.get_event_loop


def create_running_loop() -> asyncio.AbstractEventLoop:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

return loop


def set_event_loop(*args: Any, **kwargs: Any) -> None:
raise NotImplementedError('this method is not implemented because `plumpy` uses a single reentrant loop')

Expand All @@ -29,21 +36,23 @@ def new_event_loop(*args: Any, **kwargs: Any) -> asyncio.AbstractEventLoop:
raise NotImplementedError('this method is not implemented because `plumpy` uses a single reentrant loop')


class PlumpyEventLoopPolicy(asyncio.DefaultEventLoopPolicy):
class PlumpyEventLoopPolicy(asyncio.DefaultEventLoopPolicy): # type: ignore[name-defined,misc]
"""Custom event policy that always returns the same event loop that is made reentrant by ``nest_asyncio``."""

_loop: Optional[asyncio.AbstractEventLoop] = None

def get_event_loop(self) -> asyncio.AbstractEventLoop:
"""Return the patched event loop."""
def new_event_loop(self) -> asyncio.AbstractEventLoop:
import nest_asyncio

if self._loop is None:
self._loop = super().get_event_loop()
nest_asyncio.apply(self._loop)
self._loop = super().new_event_loop()
nest_asyncio.apply(self._loop)

return self._loop

def get_event_loop(self) -> asyncio.AbstractEventLoop:
"""Return the patched event loop."""
return self._loop or self.new_event_loop()


def set_event_loop_policy() -> None:
"""Enable plumpy's event loop policy that will make event loop's reentrant."""
Expand All @@ -55,18 +64,23 @@ def set_event_loop_policy() -> None:

def reset_event_loop_policy() -> None:
"""Reset the event loop policy to the default."""
loop = get_event_loop()

cls = loop.__class__

del cls._check_running # type: ignore
del cls._nest_patched # type: ignore
try:
# TODO: I think we should not be calling get_event_loop
# but maybe get_running_loop?
loop = asyncio.get_event_loop()
except RuntimeError:
pass
else:
cls = loop.__class__
del cls._check_running # type: ignore
del cls._nest_patched # type: ignore

asyncio.set_event_loop_policy(None)


def run_until_complete(future: asyncio.Future, loop: Optional[asyncio.AbstractEventLoop] = None) -> Any:
loop = loop or get_event_loop()
loop = loop or asyncio.get_event_loop()
return loop.run_until_complete(future)


Expand Down
8 changes: 7 additions & 1 deletion src/plumpy/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import kiwipy

from . import events

__all__ = ['CancelledError', 'Future', 'chain', 'copy_future', 'create_task', 'gather']

CancelledError = kiwipy.CancelledError
Expand Down Expand Up @@ -65,7 +67,11 @@ def create_task(coro: Callable[[], Awaitable[Any]], loop: Optional[asyncio.Abstr
:return: the future representing the outcome of the coroutine

"""
loop = loop or asyncio.get_event_loop()
if not loop:
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = events.create_running_loop()

future = loop.create_future()

Expand Down
8 changes: 7 additions & 1 deletion src/plumpy/processes.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,13 @@ def __init__(
# Don't allow the spec to be changed anymore
self.spec().seal()

self._loop = loop if loop is not None else asyncio.get_event_loop()
if loop:
self._loop = loop
else:
try:
self._loop = asyncio.get_event_loop()
except RuntimeError:
self._loop = events.create_running_loop()

self._setup_event_hooks()

Expand Down
5 changes: 2 additions & 3 deletions src/plumpy/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import asyncio
import functools
import importlib
import inspect
Expand Down Expand Up @@ -195,10 +194,10 @@ def ensure_coroutine(coro_or_fn: Any) -> Callable[..., Awaitable[Any]]:
:param fct: the function
:returns: the coroutine
"""
if asyncio.iscoroutinefunction(coro_or_fn):
if inspect.iscoroutinefunction(coro_or_fn):
return coro_or_fn

if asyncio.iscoroutinefunction(coro_or_fn.__call__):
if inspect.iscoroutinefunction(coro_or_fn.__call__):
return coro_or_fn

if callable(coro_or_fn):
Expand Down
6 changes: 4 additions & 2 deletions tests/notebooks/get_event_loop.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"outputs": [],
"source": [
"import asyncio\n",
"import warnings\n",
"\n",
"from plumpy import PlumpyEventLoopPolicy, set_event_loop_policy\n",
"\n",
"set_event_loop_policy()\n",
"assert isinstance(asyncio.get_event_loop_policy(), PlumpyEventLoopPolicy)\n",
"with warnings.catch_warnings(record=True):\n",
" set_event_loop_policy()\n",
" assert isinstance(asyncio.get_event_loop_policy(), PlumpyEventLoopPolicy)\n",
"assert hasattr(asyncio.get_event_loop(), '_nest_patched')"
]
}
Expand Down
1 change: 0 additions & 1 deletion tests/test_communications.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ def subscriber():
"""Return an instance of `Subscriber`."""
return Subscriber()


def test_add_rpc_subscriber(loop_communicator, subscriber):
"""Test the `LoopCommunicator.add_rpc_subscriber` method."""
assert loop_communicator.add_rpc_subscriber(subscriber) is not None
Expand Down
Loading
Loading