Skip to content
Merged
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
14 changes: 7 additions & 7 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 @@ -72,19 +72,19 @@ jobs:
AIIDA_TEST_PROFILE: test_aiida
AIIDA_WARN_v3: 1
run: |
pytest -n auto --db-backend ${{ matrix.database-backend }} -m 'not nightly' tests/ ${{ matrix.python-version == '3.13' && '--cov aiida' || '' }}
pytest -n auto --db-backend ${{ matrix.database-backend }} -m 'not nightly' tests/ ${{ matrix.python-version == '3.14' && '--cov aiida' || '' }}

- name: Upload coverage report
if: matrix.python-version == 3.13 && github.repository == 'aiidateam/aiida-core'
if: matrix.python-version == 3.14 && github.repository == 'aiidateam/aiida-core'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: aiida-pytests-py3.13
name: aiida-pytests-py3.14
files: ./coverage.xml
fail_ci_if_error: false # don't fail job, if coverage upload fails

- name: Upload coverage artifact
if: matrix.python-version == '3.13'
if: matrix.python-version == '3.14'
uses: actions/upload-artifact@v6
with:
name: coverage
Expand Down Expand Up @@ -148,7 +148,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 @@ -170,7 +170,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
4 changes: 2 additions & 2 deletions .github/workflows/test-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -165,7 +165,7 @@ jobs:
strategy:
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']

services:
postgres:
Expand Down
2 changes: 1 addition & 1 deletion 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
Comment thread
GeigerJ2 marked this conversation as resolved.
- circus~=0.19.0
- click-spinner~=0.1.8
- click<8.3,>=8.1.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ classifiers = [
dependencies = [
'alembic~=1.8',
'archive-path~=0.4.2',
"asyncssh~=2.19.0",
"asyncssh~=2.21.0",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'd go for 2.22.0
where they have fix an internal race condition

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

ok I see. 2.22.0 has dropped 3.9..

'circus~=0.19.0',
'click-spinner~=0.1.8',
'click>=8.1.0,<8.3',
Expand Down
2 changes: 2 additions & 0 deletions src/aiida/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .links import *
from .log import *
from .progress_reporter import *
from .utils import *

__all__ = (
'AIIDA_LOGGER',
Expand Down Expand Up @@ -87,6 +88,7 @@
'override_log_level',
'set_progress_bar_tqdm',
'set_progress_reporter',
'url2pathname',
'validate_link_label',
)

Expand Down
24 changes: 24 additions & 0 deletions src/aiida/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

from __future__ import annotations

__all__ = ('url2pathname',)

import filecmp
import inspect
import io
Expand Down Expand Up @@ -659,3 +661,25 @@ def batch_iter(
# balance between memory efficiency and database round-trip overhead. Setting it too low increases
# the number of database queries needed, while setting it too high increases memory consumption.
DEFAULT_BATCH_SIZE: int = 1000

# To obtain pre py3.14 behavior of url2pathname we copied the code to here
# https://github.com/python/cpython/blob/1a2b0fb3e5eac4e767e6bbb0b2c3cedaedafc07b/Lib/urllib/request.py#L1664-L1679
# Only minor changes were applied to conform with mypy
if os.name == 'nt':
from nturl2path import url2pathname

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This still has the same behavior as before?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

So this is a 1to1 copy from as from py3.13, where this function seemed to still have worked in profile code. The if case is probably not needed because it is for Window NT but i just kept it to be sure to have the same behavior.

else:
from urllib.parse import unquote

def url2pathname(url: str) -> str:
"""OS-specific conversion from a relative URL of the 'file' scheme
to a file system path; not recommended for general use."""
if url[:3] == '///':
# URL has an empty authority section, so the path begins on the
# third character.
url = url[2:]
elif url[:12] == '//localhost/':
# Skip past 'localhost' authority.
url = url[11:]
encoding = sys.getfilesystemencoding()
errors = sys.getfilesystemencodeerrors()
return unquote(url, encoding=encoding, errors=errors)
2 changes: 1 addition & 1 deletion src/aiida/manage/configuration/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ def repository_path(self) -> pathlib.Path:
:return: absolute filepath of the profile's file repository
"""
from urllib.parse import urlparse
from urllib.request import url2pathname

from aiida.common.utils import url2pathname
from aiida.common.warnings import warn_deprecation

warn_deprecation('This method has been deprecated', version=3)
Expand Down
3 changes: 2 additions & 1 deletion src/aiida/storage/psql_dos/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@
def get_filepath_container(profile: Profile) -> pathlib.Path:
"""Return the filepath of the disk-object store container."""
from urllib.parse import urlparse
from urllib.request import url2pathname

from aiida.common.utils import url2pathname

try:
parts = urlparse(profile.storage_config['repository_uri'])
Expand Down
12 changes: 12 additions & 0 deletions tests/orm/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
###########################################################################
"""Test for entity fields"""

import sys

import pytest
from importlib_metadata import entry_points

Expand All @@ -18,7 +20,16 @@

EPS = entry_points()

# These regression tests compare ``repr()`` output of field objects against YAML reference
# files. Since ``repr()`` of ``typing`` generics is not stable across Python versions
# (e.g. Python 3.14 renders ``typing.Dict`` as ``dict`` and ``typing.Optional[X]`` as
# ``X | None``), the reference files are only valid for the Python version they were
# generated with. Rather than maintaining two sets of reference files, we skip on
# Python versions that don't match.
skip_below_py314 = pytest.mark.skipif(sys.version_info < (3, 14), reason='typing repr fixtures require Python >=3.14.0')


@skip_below_py314
@pytest.mark.parametrize(
'entity_cls',
(orm.AuthInfo, orm.Comment, orm.Computer, orm.Group, orm.Log, orm.User),
Expand All @@ -40,6 +51,7 @@ def node_and_data_entry_points() -> list[tuple[str, str]]:
return _eps


@skip_below_py314
def test_all_node_fields(node_and_data_entry_points: list[tuple[str, str]], data_regression):
"""Test that all the node fields are correctly registered."""
for group, name in node_and_data_entry_points:
Expand Down
2 changes: 1 addition & 1 deletion tests/orm/test_fields/fields_AuthInfo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ auth_params: QbDictField('auth_params', dtype=typing.Dict[str, typing.Any], is_a
computer: QbNumericField('computer', dtype=<class 'int'>, is_attribute=False)
enabled: QbField('enabled', dtype=<class 'bool'>, is_attribute=False)
metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
user: QbNumericField('user', dtype=<class 'int'>, is_attribute=False)
8 changes: 4 additions & 4 deletions tests/orm/test_fields/fields_Comment.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
content: QbStrField('content', dtype=<class 'str'>, is_attribute=False)
ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
mtime: QbNumericField('mtime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
ctime: QbNumericField('ctime', dtype=datetime.datetime | None, is_attribute=False)
mtime: QbNumericField('mtime', dtype=datetime.datetime | None, is_attribute=False)
node: QbNumericField('node', dtype=<class 'int'>, is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
user: QbNumericField('user', dtype=<class 'int'>, is_attribute=False)
uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False)
uuid: QbStrField('uuid', dtype=str | None, is_attribute=False)
2 changes: 1 addition & 1 deletion tests/orm/test_fields/fields_Computer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ description: QbStrField('description', dtype=<class 'str'>, is_attribute=False)
hostname: QbStrField('hostname', dtype=<class 'str'>, is_attribute=False)
label: QbStrField('label', dtype=<class 'str'>, is_attribute=False)
metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
scheduler_type: QbStrField('scheduler_type', dtype=<class 'str'>, is_attribute=False)
transport_type: QbStrField('transport_type', dtype=<class 'str'>, is_attribute=False)
uuid: QbStrField('uuid', dtype=<class 'str'>, is_attribute=False)
10 changes: 5 additions & 5 deletions tests/orm/test_fields/fields_Group.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False)
extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]],
is_attribute=False, is_subscriptable=True)
description: QbStrField('description', dtype=str | None, is_attribute=False)
extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any] | None, is_attribute=False,
is_subscriptable=True)
label: QbStrField('label', dtype=<class 'str'>, is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
time: QbNumericField('time', dtype=typing.Optional[datetime.datetime], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
time: QbNumericField('time', dtype=datetime.datetime | None, is_attribute=False)
type_string: QbStrField('type_string', dtype=<class 'str'>, is_attribute=False)
user: QbNumericField('user', dtype=<class 'int'>, is_attribute=False)
uuid: QbStrField('uuid', dtype=<class 'str'>, is_attribute=False)
2 changes: 1 addition & 1 deletion tests/orm/test_fields/fields_Log.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ levelname: QbStrField('levelname', dtype=<class 'str'>, is_attribute=False)
loggername: QbStrField('loggername', dtype=<class 'str'>, is_attribute=False)
message: QbStrField('message', dtype=<class 'str'>, is_attribute=False)
metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
time: QbNumericField('time', dtype=<class 'datetime.datetime'>, is_attribute=False)
uuid: QbStrField('uuid', dtype=<class 'str'>, is_attribute=False)
2 changes: 1 addition & 1 deletion tests/orm/test_fields/fields_User.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ email: QbStrField('email', dtype=<class 'str'>, is_attribute=False)
first_name: QbStrField('first_name', dtype=<class 'str'>, is_attribute=False)
institution: QbStrField('institution', dtype=<class 'str'>, is_attribute=False)
last_name: QbStrField('last_name', dtype=<class 'str'>, is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
40 changes: 20 additions & 20 deletions tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
arrays: QbDictField('arrays', dtype=typing.Optional[dict[str, bytes]], is_attribute=True)
attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]],
is_attribute=False, is_subscriptable=True)
computer: QbNumericField('computer', dtype=typing.Optional[int], is_attribute=False)
ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False)
extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]],
is_attribute=False, is_subscriptable=True)
label: QbStrField('label', dtype=typing.Optional[str], is_attribute=False)
mtime: QbNumericField('mtime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
node_type: QbStrField('node_type', dtype=typing.Optional[str], is_attribute=False)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False)
repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str,
bytes]], is_attribute=False)
repository_metadata: QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str,
typing.Any]], is_attribute=False)
source: QbDictField('source', dtype=typing.Optional[dict], is_attribute=True, is_subscriptable=True)
user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False)
uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False)
arrays: QbDictField('arrays', dtype=dict[str, bytes] | None, is_attribute=True)
attributes: QbDictField('attributes', dtype=typing.Dict[str, typing.Any] | None, is_attribute=False,
is_subscriptable=True)
computer: QbNumericField('computer', dtype=int | None, is_attribute=False)
ctime: QbNumericField('ctime', dtype=datetime.datetime | None, is_attribute=False)
description: QbStrField('description', dtype=str | None, is_attribute=False)
extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any] | None, is_attribute=False,
is_subscriptable=True)
label: QbStrField('label', dtype=str | None, is_attribute=False)
mtime: QbNumericField('mtime', dtype=datetime.datetime | None, is_attribute=False)
node_type: QbStrField('node_type', dtype=str | None, is_attribute=False)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
process_type: QbStrField('process_type', dtype=str | None, is_attribute=False)
repository_content: QbDictField('repository_content', dtype=dict[str, bytes] | None,
is_attribute=False)
repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any]
| None, is_attribute=False)
source: QbDictField('source', dtype=dict | None, is_attribute=True, is_subscriptable=True)
user: QbNumericField('user', dtype=int | None, is_attribute=False)
uuid: QbStrField('uuid', dtype=str | None, is_attribute=False)
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
array_labels: QbArrayField('array_labels', dtype=typing.Optional[typing.List[str]],
is_attribute=True)
arrays: QbDictField('arrays', dtype=typing.Optional[dict[str, bytes]], is_attribute=True)
attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]],
is_attribute=False, is_subscriptable=True)
array_labels: QbArrayField('array_labels', dtype=typing.List[str] | None, is_attribute=True)
arrays: QbDictField('arrays', dtype=dict[str, bytes] | None, is_attribute=True)
attributes: QbDictField('attributes', dtype=typing.Dict[str, typing.Any] | None, is_attribute=False,
is_subscriptable=True)
cell: QbArrayField('cell', dtype=typing.List[typing.List[float]], is_attribute=True)
computer: QbNumericField('computer', dtype=typing.Optional[int], is_attribute=False)
ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False)
extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]],
is_attribute=False, is_subscriptable=True)
label: QbStrField('label', dtype=typing.Optional[str], is_attribute=False)
computer: QbNumericField('computer', dtype=int | None, is_attribute=False)
ctime: QbNumericField('ctime', dtype=datetime.datetime | None, is_attribute=False)
description: QbStrField('description', dtype=str | None, is_attribute=False)
extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any] | None, is_attribute=False,
is_subscriptable=True)
label: QbStrField('label', dtype=str | None, is_attribute=False)
label_numbers: QbArrayField('label_numbers', dtype=typing.List[int], is_attribute=True)
labels: QbArrayField('labels', dtype=typing.List[str], is_attribute=True)
mesh: QbArrayField('mesh', dtype=typing.List[int], is_attribute=True)
mtime: QbNumericField('mtime', dtype=typing.Optional[datetime.datetime], is_attribute=False)
node_type: QbStrField('node_type', dtype=typing.Optional[str], is_attribute=False)
mtime: QbNumericField('mtime', dtype=datetime.datetime | None, is_attribute=False)
node_type: QbStrField('node_type', dtype=str | None, is_attribute=False)
offset: QbArrayField('offset', dtype=typing.List[float], is_attribute=True)
pbc1: QbField('pbc1', dtype=<class 'bool'>, is_attribute=True)
pbc2: QbField('pbc2', dtype=<class 'bool'>, is_attribute=True)
pbc3: QbField('pbc3', dtype=<class 'bool'>, is_attribute=True)
pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False)
process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False)
repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str,
bytes]], is_attribute=False)
repository_metadata: QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str,
typing.Any]], is_attribute=False)
source: QbDictField('source', dtype=typing.Optional[dict], is_attribute=True, is_subscriptable=True)
pk: QbNumericField('pk', dtype=int | None, is_attribute=False)
process_type: QbStrField('process_type', dtype=str | None, is_attribute=False)
repository_content: QbDictField('repository_content', dtype=dict[str, bytes] | None,
is_attribute=False)
repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any]
| None, is_attribute=False)
source: QbDictField('source', dtype=dict | None, is_attribute=True, is_subscriptable=True)
units: QbStrField('units', dtype=<class 'str'>, is_attribute=True)
user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False)
uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False)
user: QbNumericField('user', dtype=int | None, is_attribute=False)
uuid: QbStrField('uuid', dtype=str | None, is_attribute=False)
Loading
Loading