Skip to content
Closed
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
47 changes: 44 additions & 3 deletions tests/orm/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,66 @@
###########################################################################
"""Test for entity fields"""

import typing as t
from importlib.metadata import entry_points

import pytest

from aiida import orm
from aiida.orm.fields import add_field
from aiida.orm.fields import QbField, add_field
from aiida.orm.pydantic import OrmMetadataField
from aiida.plugins import load_entry_point

EPS = entry_points()


def format_dtype(dtype: t.Any) -> str | None:
"""Return a stable, human-readable rendering of a field dtype.

Plain classes are rendered by qualified name, everything else by ``str`` with the ``typing.`` prefix stripped.
The ``get_origin`` guard is required: on Python 3.10 ``isinstance(dict[str, Any], type)`` is ``True``, so without
it a parametrized generic would render as a bare ``dict`` there and as ``dict[str, Any]`` from 3.11 on.
"""
if dtype is None:
return None
if t.get_origin(dtype) is None and isinstance(dtype, type):
return dtype.__qualname__
return str(dtype).replace('typing.', '')


def field_spec(field: QbField) -> str:
"""Return a one-line description of a field, for comparison against a reference file.

The field key is not included: it is the mapping key in the reference file. Everything else is only emitted when
it carries information, to keep the files readable:

* ``backend_key`` only when it differs from the key, which is exactly when ``is_attribute`` is set, since a
non-attribute field may not be aliased and so always stores under its own key;
* ``root``, the ``extract_root_type`` of the dtype, only when it differs from the dtype itself. It selects the
``QbField`` subclass and is otherwise only covered implicitly, via that choice.
"""
parts = []
if field.backend_key != field.key:
parts.append(f'backend_key={field.backend_key}')
if field._is_attribute:
parts.append('is_attribute=True')
dtype = format_dtype(field._dtype)
root = format_dtype(field.dtype)
parts.append(f'dtype={dtype}')
if root != dtype:
parts.append(f'root={root}')
if field._doc:
parts.append(f'doc={field._doc!r}')
return f'{type(field).__name__}({", ".join(parts)})'


@pytest.mark.parametrize(
'entity_cls',
(orm.AuthInfo, orm.Comment, orm.Computer, orm.Group, orm.Log, orm.User),
)
def test_all_entity_fields(entity_cls, data_regression):
data_regression.check(
{key: repr(value) for key, value in entity_cls.fields._dict.items()},
{key: field_spec(value) for key, value in entity_cls.fields._dict.items()},
basename=f'fields_{entity_cls.__name__}',
)

Expand All @@ -46,7 +87,7 @@ def test_all_node_fields(node_and_data_entry_points: list[tuple[str, str]], data
for group, name in node_and_data_entry_points:
node_cls = load_entry_point(group, name)
data_regression.check(
{key: repr(value) for key, value in node_cls.fields._dict.items()},
{key: field_spec(value) for key, value in node_cls.fields._dict.items()},
basename=f'fields_{group}.{name}.{node_cls.__name__}',
)

Expand Down
16 changes: 7 additions & 9 deletions tests/orm/test_fields/fields_AuthInfo.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
auth_params: QbDictField('auth_params', dtype=dict[str, typing.Any], doc='Dictionary
of authentication parameters')
computer: QbNumericField('computer', dtype=<class 'int'>, doc='The PK of the computer')
enabled: QbAnyField('enabled', dtype=<class 'bool'>, doc='Whether the instance is
enabled')
metadata: QbDictField('metadata', dtype=dict[str, typing.Any], doc='Dictionary of
metadata')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
user: QbNumericField('user', dtype=<class 'int'>, doc='The PK of the user')
auth_params: QbDictField(dtype=dict[str, Any], root=dict, doc='Dictionary of authentication
parameters')
computer: QbNumericField(dtype=int, doc='The PK of the computer')
enabled: QbAnyField(dtype=bool, doc='Whether the instance is enabled')
metadata: QbDictField(dtype=dict[str, Any], root=dict, doc='Dictionary of metadata')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
user: QbNumericField(dtype=int, doc='The PK of the user')
17 changes: 7 additions & 10 deletions tests/orm/test_fields/fields_Comment.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
content: QbStrField('content', dtype=<class 'str'>, doc='Content of the comment')
ctime: QbNumericField('ctime', dtype=<class 'datetime.datetime'>, doc='Creation time
of the comment')
mtime: QbNumericField('mtime', dtype=<class 'datetime.datetime'>, doc='Modified time
of the comment')
node: QbNumericField('node', dtype=<class 'int'>, doc='Node PK that the comment is
attached to')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
user: QbNumericField('user', dtype=<class 'int'>, doc='User PK that created the comment')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the comment')
content: QbStrField(dtype=str, doc='Content of the comment')
ctime: QbNumericField(dtype=datetime, doc='Creation time of the comment')
mtime: QbNumericField(dtype=datetime, doc='Modified time of the comment')
node: QbNumericField(dtype=int, doc='Node PK that the comment is attached to')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
user: QbNumericField(dtype=int, doc='User PK that created the comment')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the comment')
20 changes: 8 additions & 12 deletions tests/orm/test_fields/fields_Computer.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
description: QbStrField('description', dtype=<class 'str'>, doc='Description of the
computer')
hostname: QbStrField('hostname', dtype=<class 'str'>, doc='Hostname of the computer')
label: QbStrField('label', dtype=<class 'str'>, doc='Label for the computer')
metadata: QbDictField('metadata', dtype=dict[str, typing.Any], doc='Metadata of the
computer')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
scheduler_type: QbStrField('scheduler_type', dtype=<class 'str'>, doc='Scheduler type
of the computer')
transport_type: QbStrField('transport_type', dtype=<class 'str'>, doc='Transport type
of the computer')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the computer')
description: QbStrField(dtype=str, doc='Description of the computer')
hostname: QbStrField(dtype=str, doc='Hostname of the computer')
label: QbStrField(dtype=str, doc='Label for the computer')
metadata: QbDictField(dtype=dict[str, Any], root=dict, doc='Metadata of the computer')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
scheduler_type: QbStrField(dtype=str, doc='Scheduler type of the computer')
transport_type: QbStrField(dtype=str, doc='Transport type of the computer')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the computer')
18 changes: 9 additions & 9 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=<class 'str'>, doc='The group description')
extras: QbDictField('extras', dtype=dict[str, typing.Any], doc='The group extras')
label: QbStrField('label', dtype=<class 'str'>, doc='The group label')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
time: QbNumericField('time', dtype=<class 'datetime.datetime'>, doc='The creation
time of the node, defaults to now (timezone-aware)')
type_string: QbStrField('type_string', dtype=<class 'str'>, doc='The type of the group')
user: QbNumericField('user', dtype=<class 'int'>, doc='The PK of the group owner')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the group')
description: QbStrField(dtype=str, doc='The group description')
extras: QbDictField(dtype=dict[str, Any], root=dict, doc='The group extras')
label: QbStrField(dtype=str, doc='The group label')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
time: QbNumericField(dtype=datetime, doc='The creation time of the node, defaults
to now (timezone-aware)')
type_string: QbStrField(dtype=str, doc='The type of the group')
user: QbNumericField(dtype=int, doc='The PK of the group owner')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the group')
18 changes: 8 additions & 10 deletions tests/orm/test_fields/fields_Log.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
levelname: QbStrField('levelname', dtype=<class 'str'>, doc='The name of the log level')
loggername: QbStrField('loggername', dtype=<class 'str'>, doc='The name of the logger')
message: QbStrField('message', dtype=<class 'str'>, doc='The message of the log')
metadata: QbDictField('metadata', dtype=dict[str, typing.Any], doc='The metadata of
the log')
node: QbNumericField('node', dtype=<class 'int'>, doc='Associated node')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
time: QbNumericField('time', dtype=<class 'datetime.datetime'>, doc='The time at which
the log was created')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the node')
levelname: QbStrField(dtype=str, doc='The name of the log level')
loggername: QbStrField(dtype=str, doc='The name of the logger')
message: QbStrField(dtype=str, doc='The message of the log')
metadata: QbDictField(dtype=dict[str, Any], root=dict, doc='The metadata of the log')
node: QbNumericField(dtype=int, doc='Associated node')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
time: QbNumericField(dtype=datetime, doc='The time at which the log was created')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the node')
10 changes: 5 additions & 5 deletions tests/orm/test_fields/fields_User.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
email: QbStrField('email', dtype=<class 'str'>, doc='The user email')
first_name: QbStrField('first_name', dtype=<class 'str'>, doc='The user first name')
institution: QbStrField('institution', dtype=<class 'str'>, doc='The user institution')
last_name: QbStrField('last_name', dtype=<class 'str'>, doc='The user last name')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
email: QbStrField(dtype=str, doc='The user email')
first_name: QbStrField(dtype=str, doc='The user first name')
institution: QbStrField(dtype=str, doc='The user institution')
last_name: QbStrField(dtype=str, doc='The user last name')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
39 changes: 18 additions & 21 deletions tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
attributes: QbAttributesField('attributes', dtype=<class 'aiida.orm.nodes.data.array.array.ArrayData.AttributesModel'>,
doc='The node attributes')
computer: QbNumericField('computer', dtype=int | None, doc='The PK of the computer')
ctime: QbNumericField('ctime', dtype=<class 'datetime.datetime'>, doc='The creation
time of the node')
description: QbStrField('description', dtype=<class 'str'>, doc='The node description')
extras: QbDictField('extras', dtype=dict[str, typing.Any], doc='The node extras')
label: QbStrField('label', dtype=<class 'str'>, doc='The node label')
mtime: QbNumericField('mtime', dtype=<class 'datetime.datetime'>, doc='The modification
time of the node')
node_type: QbStrField('node_type', dtype=typing.Literal['data.core.array.ArrayData.'],
doc='The type of the node.')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
process_type: QbStrField('process_type', dtype=str | None, doc='The process type of
the node')
repository_metadata: QbDictField('repository_metadata', dtype=dict[str, typing.Any],
doc='Virtual hierarchy of the file repository')
source: QbDictField('attributes.source', dtype=dict | None, doc='Source of the data')
user: QbNumericField('user', dtype=<class 'int'>, doc='The PK of the user who owns
the node')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the node')
attributes: QbAttributesField(dtype=ArrayData.AttributesModel, doc='The node attributes')
computer: QbNumericField(dtype=int | None, root=int, doc='The PK of the computer')
ctime: QbNumericField(dtype=datetime, doc='The creation time of the node')
description: QbStrField(dtype=str, doc='The node description')
extras: QbDictField(dtype=dict[str, Any], root=dict, doc='The node extras')
label: QbStrField(dtype=str, doc='The node label')
mtime: QbNumericField(dtype=datetime, doc='The modification time of the node')
node_type: QbStrField(dtype=Literal['data.core.array.ArrayData.'], root=Literal, doc='The
type of the node.')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
process_type: QbStrField(dtype=str | None, root=str, doc='The process type of the
node')
repository_metadata: QbDictField(dtype=dict[str, Any], root=dict, doc='Virtual hierarchy
of the file repository')
source: QbDictField(backend_key=attributes.source, is_attribute=True, dtype=dict |
None, root=dict, doc='Source of the data')
user: QbNumericField(dtype=int, doc='The PK of the user who owns the node')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the node')
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
array_labels: QbArrayField('attributes.array_labels', dtype=list[str] | None, doc='Labels
associated with the band arrays')
attributes: QbAttributesField('attributes', dtype=<class 'aiida.orm.nodes.data.array.bands.BandsData.AttributesModel'>,
doc='The node attributes')
cell: QbArrayField('attributes.cell', dtype=list[list[float]] | None, doc='Unit cell
of the crystal, in Angstroms')
computer: QbNumericField('computer', dtype=int | None, doc='The PK of the computer')
ctime: QbNumericField('ctime', dtype=<class 'datetime.datetime'>, doc='The creation
time of the node')
description: QbStrField('description', dtype=<class 'str'>, doc='The node description')
extras: QbDictField('extras', dtype=dict[str, typing.Any], doc='The node extras')
label: QbStrField('label', dtype=<class 'str'>, doc='The node label')
label_numbers: QbArrayField('attributes.label_numbers', dtype=list[int] | None, doc='Index
of the labels in the list of kpoints')
labels: QbArrayField('attributes.labels', dtype=list[str] | None, doc='Labels associated
with the list of kpoints')
mesh: QbArrayField('attributes.mesh', dtype=list[int] | None, doc='Mesh of kpoints')
mtime: QbNumericField('mtime', dtype=<class 'datetime.datetime'>, doc='The modification
time of the node')
node_type: QbStrField('node_type', dtype=typing.Literal['data.core.array.bands.BandsData.'],
array_labels: QbArrayField(backend_key=attributes.array_labels, is_attribute=True,
dtype=list[str] | None, root=list, doc='Labels associated with the band arrays')
attributes: QbAttributesField(dtype=BandsData.AttributesModel, doc='The node attributes')
cell: QbArrayField(backend_key=attributes.cell, is_attribute=True, dtype=list[list[float]]
| None, root=list, doc='Unit cell of the crystal, in Angstroms')
computer: QbNumericField(dtype=int | None, root=int, doc='The PK of the computer')
ctime: QbNumericField(dtype=datetime, doc='The creation time of the node')
description: QbStrField(dtype=str, doc='The node description')
extras: QbDictField(dtype=dict[str, Any], root=dict, doc='The node extras')
label: QbStrField(dtype=str, doc='The node label')
label_numbers: QbArrayField(backend_key=attributes.label_numbers, is_attribute=True,
dtype=list[int] | None, root=list, doc='Index of the labels in the list of kpoints')
labels: QbArrayField(backend_key=attributes.labels, is_attribute=True, dtype=list[str]
| None, root=list, doc='Labels associated with the list of kpoints')
mesh: QbArrayField(backend_key=attributes.mesh, is_attribute=True, dtype=list[int]
| None, root=list, doc='Mesh of kpoints')
mtime: QbNumericField(dtype=datetime, doc='The modification time of the node')
node_type: QbStrField(dtype=Literal['data.core.array.bands.BandsData.'], root=Literal,
doc='The type of the node.')
offset: QbArrayField('attributes.offset', dtype=list[float] | None, doc='Offset of
kpoints')
pbc1: QbAnyField('attributes.pbc1', dtype=bool | None, doc='Periodicity in the first
lattice vector direction')
pbc2: QbAnyField('attributes.pbc2', dtype=bool | None, doc='Periodicity in the second
lattice vector direction')
pbc3: QbAnyField('attributes.pbc3', dtype=bool | None, doc='Periodicity in the third
lattice vector direction')
pk: QbNumericField('pk', dtype=<class 'int'>, doc='The primary key of the entity')
process_type: QbStrField('process_type', dtype=str | None, doc='The process type of
the node')
repository_metadata: QbDictField('repository_metadata', dtype=dict[str, typing.Any],
doc='Virtual hierarchy of the file repository')
source: QbDictField('attributes.source', dtype=dict | None, doc='Source of the data')
units: QbStrField('attributes.units', dtype=str | None, doc='Units in which the data
in bands were stored')
user: QbNumericField('user', dtype=<class 'int'>, doc='The PK of the user who owns
the node')
uuid: QbAnyField('uuid', dtype=<class 'uuid.UUID'>, doc='The UUID of the node')
offset: QbArrayField(backend_key=attributes.offset, is_attribute=True, dtype=list[float]
| None, root=list, doc='Offset of kpoints')
pbc1: QbAnyField(backend_key=attributes.pbc1, is_attribute=True, dtype=bool | None,
root=bool, doc='Periodicity in the first lattice vector direction')
pbc2: QbAnyField(backend_key=attributes.pbc2, is_attribute=True, dtype=bool | None,
root=bool, doc='Periodicity in the second lattice vector direction')
pbc3: QbAnyField(backend_key=attributes.pbc3, is_attribute=True, dtype=bool | None,
root=bool, doc='Periodicity in the third lattice vector direction')
pk: QbNumericField(dtype=int, doc='The primary key of the entity')
process_type: QbStrField(dtype=str | None, root=str, doc='The process type of the
node')
repository_metadata: QbDictField(dtype=dict[str, Any], root=dict, doc='Virtual hierarchy
of the file repository')
source: QbDictField(backend_key=attributes.source, is_attribute=True, dtype=dict |
None, root=dict, doc='Source of the data')
units: QbStrField(backend_key=attributes.units, is_attribute=True, dtype=str | None,
root=str, doc='Units in which the data in bands were stored')
user: QbNumericField(dtype=int, doc='The PK of the user who owns the node')
uuid: QbAnyField(dtype=UUID, doc='The UUID of the node')
Loading
Loading