Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/ci-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -139,7 +139,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
Expand All @@ -161,7 +161,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: ''

Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ repos:
hooks:
- id: check-merge-conflict
- id: check-added-large-files
exclude: uv.lock
- id: check-yaml
- id: double-quote-string-fixer
- id: end-of-file-fixer
Expand Down
5 changes: 3 additions & 2 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dependencies:
- python~=3.9
- alembic~=1.8
- archive-path~=0.4.2
- asyncssh~=2.19.0
- asyncssh~=2.21.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bump is needed as 2.20.0 version had some Python 3.14 fixes

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- circus~=0.19.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened a PR with tests for 3.14
circus-tent/circus#1233

- click-spinner~=0.1.8
- click<8.3,>=8.1.0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: get-annotations dependency can be dropped once we drop support for Python 3.9

https://pypi.org/project/get-annotations/

Expand All @@ -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
Copy link
Collaborator Author

@danielhollas danielhollas Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw: importlib-metadata can be dropped once we drop 3.9 in favour of stdlib importlib.metadata, see comment in: aiida/plugins/entry_point.py

- numpy<3,>=1.21
- paramiko~=3.0
Expand All @@ -38,3 +38,4 @@ dependencies:
- upf_to_json~=0.9.2
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tabulate seems to work with 3.14
astanin/python-tabulate#372

- wrapt~=1.11
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrapt just had a 2.0 release: https://wrapt.readthedocs.io/en/latest/changes.html#version-2-0-0

Looking at the changelog it seems that it should be safe to upgrade (we're only using wrapt.decorator).
We could also choose to support both 1.x and 2.x, as there might be aiida plugins that depend on wrapt as well.

Possibly the biggest reason to update is that the package is now typed. :-)

- chardet~=5.2.0
- nest-asyncio
20 changes: 13 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,19 @@ 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',
'disk-objectstore~=1.4.0',
'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',
Expand All @@ -60,7 +60,8 @@ dependencies = [
'typing-extensions~=4.1;python_version<"3.11"',
'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
Expand Down Expand Up @@ -280,7 +281,7 @@ tests = [
'pytest-asyncio~=0.12,<0.17',
'pytest-timeout~=2.0',
'pytest-cov~=4.1',
'pytest-rerunfailures~=12.0',
'pytest-rerunfailures~=16.0',
'pytest-benchmark~=4.0',
'pytest-regressions~=2.2',
'pytest-xdist~=3.6',
Expand Down Expand Up @@ -407,8 +408,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
Expand Down Expand Up @@ -571,3 +573,7 @@ commands = molecule {posargs:test}
# .github/actions/install-aiida-core/action.yml
# .readthedocs.yml
required-version = ">=0.7.0"

[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"}
2 changes: 1 addition & 1 deletion src/aiida/calculations/unstash.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def traverse(node_):
else: # UnstashTargetMode.NewRemoteData.value
computer = self.inputs.metadata.get('computer')
with computer.get_transport() as transport:
remote_user = transport.whoami_async()
remote_user = transport.whoami()
remote_working_directory = computer.get_workdir().format(username=remote_user)

# The following line is set at calcjob::presubmit, but we need it here
Expand Down
12 changes: 10 additions & 2 deletions src/aiida/engine/processes/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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."""
Expand Down
9 changes: 8 additions & 1 deletion src/aiida/engine/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 8 additions & 1 deletion src/aiida/engine/transports.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions src/aiida/engine/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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

Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 1 addition & 20 deletions src/aiida/orm/nodes/data/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,6 @@ def has_pymatgen():
return True


def get_pymatgen_version():
""":return: string with pymatgen version, None if can not import."""
if not has_pymatgen():
return None
try:
from pymatgen import __version__
except ImportError:
# this was changed in version 2022.0.3
from pymatgen.core import __version__
return __version__


def has_spglib():
""":return: True if the spglib module can be imported, False otherwise."""
try:
Expand Down Expand Up @@ -1827,14 +1815,7 @@ def _get_object_pymatgen_structure(self, **kwargs):
if len(kind.symbols) != 1 or (len(kind.weights) != 1 or sum(kind.weights) < 1.0):
raise ValueError('Cannot set partial occupancies and spins at the same time')
spin = -1 if kind.name.endswith('1') else 1 if kind.name.endswith('2') else 0
try:
specie = Specie(kind.symbols[0], oxidation_state, properties={'spin': spin})
except TypeError:
# As of v2023.9.2, the ``properties`` argument is removed and the ``spin`` argument should be used.
# See: https://github.com/materialsproject/pymatgen/commit/118c245d6082fe0b13e19d348fc1db9c0d512019
# The ``spin`` argument was introduced in v2023.6.28.
# See: https://github.com/materialsproject/pymatgen/commit/9f2b3939af45d5129e0778d371d814811924aeb6
specie = Specie(kind.symbols[0], oxidation_state, spin=spin)
specie = Specie(kind.symbols[0], oxidation_state, spin=spin)
species.append(specie)
else:
# case when no spin are defined
Expand Down
5 changes: 0 additions & 5 deletions tests/calculations/arithmetic/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
###########################################################################
"""Tests for the `ArithmeticAddCalculation` plugin."""

import pytest

from aiida import orm
from aiida.calculations.arithmetic.add import ArithmeticAddCalculation
from aiida.common import datastructures


@pytest.mark.requires_rmq
def test_add_default(fixture_sandbox, aiida_localhost, generate_calc_job):
"""Test a default `ArithmeticAddCalculation`."""
inputs = {
Expand Down Expand Up @@ -46,7 +43,6 @@ def test_add_default(fixture_sandbox, aiida_localhost, generate_calc_job):
assert input_written == f"echo $(({inputs['x'].value} + {inputs['y'].value}))\n"


@pytest.mark.requires_rmq
def test_add_custom_filenames(fixture_sandbox, aiida_localhost, generate_calc_job):
"""Test an `ArithmeticAddCalculation` with non-default input and output filenames."""
input_filename = 'custom.in'
Expand All @@ -71,7 +67,6 @@ def test_add_custom_filenames(fixture_sandbox, aiida_localhost, generate_calc_jo
assert calc_info.retrieve_list == [output_filename]


@pytest.mark.requires_rmq
def test_sleep(fixture_sandbox, aiida_localhost, generate_calc_job):
"""Test the ``metadata.options.sleep`` input."""
sleep = 5
Expand Down
3 changes: 0 additions & 3 deletions tests/calculations/test_stash.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from aiida.plugins import CalculationFactory


@pytest.mark.requires_rmq
@pytest.mark.parametrize(
'operation_type,entry_point_name',
[
Expand Down Expand Up @@ -215,7 +214,6 @@ def create_unstash_source_node():
generate_calc_job(fixture_sandbox, 'core.unstash', unstash_input)


@pytest.mark.requires_rmq
@pytest.mark.parametrize('unstash_target_mode', [UnstashTargetMode.NewRemoteData, UnstashTargetMode.OriginalPlace])
def test_submit_custom_code(fixture_sandbox, aiida_localhost, generate_calc_job, tmp_path, unstash_target_mode):
"""Test the full functionality of the `StashCalculation` and `UnstashCalculation` with a custom code submission."""
Expand Down Expand Up @@ -369,7 +367,6 @@ def test_submit_custom_code(fixture_sandbox, aiida_localhost, generate_calc_job,


@pytest.mark.usefixtures('aiida_profile_clean')
@pytest.mark.requires_rmq
@pytest.mark.parametrize(
'unstash_target_mode', [UnstashTargetMode.OriginalPlace.value, UnstashTargetMode.NewRemoteData.value]
)
Expand Down
4 changes: 0 additions & 4 deletions tests/calculations/test_templatereplacer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@

import io

import pytest

from aiida import orm
from aiida.common import datastructures


@pytest.mark.requires_rmq
def test_base_template(fixture_sandbox, aiida_localhost, generate_calc_job):
"""Test a base template that emulates the arithmetic add."""
entry_point_name = 'core.templatereplacer'
Expand Down Expand Up @@ -57,7 +54,6 @@ def test_base_template(fixture_sandbox, aiida_localhost, generate_calc_job):
assert input_written == f"echo $(({inputs['parameters']['x']} + {inputs['parameters']['y']}))"


@pytest.mark.requires_rmq
def test_file_usage(fixture_sandbox, aiida_localhost, generate_calc_job):
"""Test a base template that uses two files."""
file1_node = orm.SinglefileData(io.BytesIO(b'Content of file 1'))
Expand Down
5 changes: 0 additions & 5 deletions tests/calculations/test_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@

import os

import pytest

from aiida import orm
from aiida.common import datastructures


@pytest.mark.requires_rmq
def test_get_transfer(fixture_sandbox, aiida_localhost, generate_calc_job, tmp_path):
"""Test a default `TransferCalculation`."""
file1 = tmp_path / 'file1.txt'
Expand Down Expand Up @@ -64,7 +61,6 @@ def test_get_transfer(fixture_sandbox, aiida_localhost, generate_calc_job, tmp_p
assert sorted(calc_info.retrieve_list) == sorted(retrieve_list)


@pytest.mark.requires_rmq
def test_put_transfer(fixture_sandbox, aiida_localhost, generate_calc_job, tmp_path):
"""Test a default `TransferCalculation`."""
file1 = tmp_path / 'file1.txt'
Expand Down Expand Up @@ -197,7 +193,6 @@ def test_validate_transfer_inputs(aiida_localhost, tmp_path):
assert result == expected


@pytest.mark.requires_rmq
def test_integration_transfer(aiida_localhost, tmp_path):
"""Test a default `TransferCalculation`."""
from aiida.calculations.transfer import TransferCalculation
Expand Down
1 change: 0 additions & 1 deletion tests/cmdline/commands/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,6 @@ def test_arrayshow(self):
assert res.exit_code == 0, 'The command did not finish correctly'


@pytest.mark.requires_rmq
class TestVerdiDataBands(DummyVerdiDataListable):
"""Testing verdi data core.bands."""

Expand Down
Loading