diff --git a/docs/source/nitpick-exceptions b/docs/source/nitpick-exceptions index 033cccffb6..30c2820f81 100644 --- a/docs/source/nitpick-exceptions +++ b/docs/source/nitpick-exceptions @@ -26,6 +26,7 @@ py:class json.encoder.JSONEncoder py:class EXPOSED_TYPE py:class EVENT_CALLBACK_TYPE py:class datetime +py:class UUID py:class types.LambdaType py:meth tempfile.TemporaryDirectory @@ -68,6 +69,7 @@ py:class aiida.orm.groups.SelfType py:class aiida.orm.implementation.entitites.EntityType py:class aiida.engine.processes.functions.FunctionType py:class aiida.engine.processes.workchains.workchain.MethodType +py:class aiida.orm.entities.EntityInputModel py:class aiida.orm.entities.EntityType py:class aiida.orm.entities.BackendEntityType py:class aiida.orm.entities.CollectionType diff --git a/src/aiida/cmdline/commands/cmd_code.py b/src/aiida/cmdline/commands/cmd_code.py index f463b8755c..5df47e3f01 100644 --- a/src/aiida/cmdline/commands/cmd_code.py +++ b/src/aiida/cmdline/commands/cmd_code.py @@ -8,11 +8,13 @@ ########################################################################### """`verdi code` command.""" +from __future__ import annotations + import pathlib import warnings from collections import defaultdict from functools import partial -from typing import Any +from typing import TYPE_CHECKING, Any import click @@ -25,6 +27,10 @@ from aiida.cmdline.utils.common import validate_output_filename from aiida.cmdline.utils.decorators import with_dbenv from aiida.common import exceptions +from aiida.common.pydantic import get_metadata + +if TYPE_CHECKING: + from aiida.orm import Code @verdi.group('code') @@ -32,10 +38,11 @@ def verdi_code(): """Setup and manage codes.""" -def create_code(ctx: click.Context, cls, **kwargs) -> None: +def create_code(ctx: click.Context, cls: Code, **kwargs) -> None: """Create a new `Code` instance.""" try: - instance = cls._from_model(cls.Model(**kwargs)) + model = cls.CreateModel(**kwargs) + instance = cls.from_model(model) except (TypeError, ValueError) as exception: echo.echo_critical(f'Failed to create instance `{cls}`: {exception}') @@ -223,50 +230,27 @@ def code_duplicate(ctx, code, non_interactive, **kwargs): @verdi_code.command() @arguments.CODE() @with_dbenv() -def show(code): +def show(code: Code): """Display detailed information for a code.""" from aiida.cmdline import is_verbose - from aiida.common.pydantic import get_metadata table = [] # These are excluded from the CLI, so we add them manually table.append(['PK', code.pk]) table.append(['UUID', code.uuid]) - table.append(['Type', code.entry_point.name]) - - for field_name, field_info in code.Model.model_fields.items(): - # Skip fields excluded from CLI - if get_metadata( - field_info, - key='exclude_from_cli', - default=False, - ): - continue - - # Skip fields that are not stored in the attributes column - # NOTE: this also catches e.g., filepath_files for PortableCode, which is actually a "misuse" - # of the is_attribute metadata flag, as there it is flagging that the field is not stored at all! - # TODO (edan-bainglass) consider improving this by introducing a new metadata flag or reworking PortableCode - # TODO see also Dict and InstalledCode for other potential misuses of is_attribute - if not get_metadata( - field_info, - key='is_attribute', - default=True, - ): - continue - - value = getattr(code, field_name) + table.append(['Type', code.entry_point.name if code.entry_point else None]) + table.append(['Label', code.label]) + table.append(['Description', code.description or '']) - # Special handling for computer field to show additional info - if field_name == 'computer': - value = f'{value.label} ({value.hostname}), pk: {value.pk}' + if code.computer is not None: + table.append(['Computer', f'{code.computer.label} ({code.computer.hostname}), pk: {code.computer.pk}']) - # Use the field's title as display name. - # This allows for custom titles (class-cased by default from Pydantic). - display_name = field_info.title - - table.append([display_name, value]) + for key, field in code.AttributesModel.model_fields.items(): + if key == 'source' or get_metadata(field, 'write_only'): + continue + value = getattr(code, key) + table.append([field.title, value]) if is_verbose(): table.append(['Calculations', len(code.base.links.get_outgoing().all())]) diff --git a/src/aiida/cmdline/groups/dynamic.py b/src/aiida/cmdline/groups/dynamic.py index c7949a9065..9e01d9e7f3 100644 --- a/src/aiida/cmdline/groups/dynamic.py +++ b/src/aiida/cmdline/groups/dynamic.py @@ -9,6 +9,7 @@ import click from aiida.common import exceptions +from aiida.common.pydantic import OrmModel from aiida.plugins.entry_point import ENTRY_POINT_GROUP_FACTORY_MAPPING, get_entry_point_names from aiida.plugins.factories import BaseFactory @@ -96,9 +97,15 @@ def call_command(self, ctx: click.Context, cls: t.Any, non_interactive: bool, ** from pydantic import ValidationError if hasattr(cls, 'Model'): - # The plugin defines a pydantic model: use it to validate the provided arguments try: - cls.Model(**kwargs) + Model = getattr(cls, 'CreateModel', cls.Model) # noqa: N806 + if 'attributes' in Model.model_fields: + attr_field = Model.model_fields['attributes'] + AttributesModel = t.cast(OrmModel, attr_field.annotation) # noqa: N806 + kwargs['attributes'] = { + key: kwargs.pop(key) for key in AttributesModel.model_fields.keys() if key in kwargs + } + Model(**kwargs) except ValidationError as exception: param_hint = [ f'--{loc.replace("_", "-")}' # type: ignore[union-attr] @@ -153,8 +160,6 @@ def list_options(self, entry_point: str) -> list[t.Callable[[FC], FC]]: """ from pydantic_core import PydanticUndefined - from aiida.common.pydantic import get_metadata - cls = self.factory(entry_point) if not hasattr(cls, 'Model'): @@ -168,10 +173,18 @@ def list_options(self, entry_point: str) -> list[t.Callable[[FC], FC]]: options_spec = self.factory(entry_point).get_cli_options() # type: ignore[union-attr] return [self.create_option(*item) for item in options_spec] + Model = getattr(cls, 'CreateModel', cls.Model) # noqa: N806 + options_spec = {} - for key, field_info in cls.Model.model_fields.items(): - if get_metadata(field_info, 'exclude_from_cli'): + fields = dict(Model.model_fields) + attr_field = fields.get('attributes') + if attr_field is not None: + AttributesModel = t.cast(OrmModel, attr_field.annotation) # noqa: N806 + fields |= AttributesModel.model_fields + + for key, field_info in fields.items(): + if key in ('extras', 'attributes'): continue default = field_info.default_factory if field_info.default is PydanticUndefined else field_info.default diff --git a/src/aiida/common/datastructures.py b/src/aiida/common/datastructures.py index 58198f051a..94658c09a2 100644 --- a/src/aiida/common/datastructures.py +++ b/src/aiida/common/datastructures.py @@ -167,8 +167,8 @@ class CalcInfo(DefaultFieldsAttributeDict): max_wallclock_seconds: None | int max_memory_kb: None | int rerunnable: bool - retrieve_list: None | list[str | tuple[str, str, str]] - retrieve_temporary_list: None | list[str | tuple[str, str, str]] + retrieve_list: None | list[str | tuple[str, str, int]] + retrieve_temporary_list: None | list[str | tuple[str, str, int]] local_copy_list: None | list[tuple[str, str, str]] remote_copy_list: None | list[tuple[str, str, str]] remote_symlink_list: None | list[tuple[str, str, str]] diff --git a/src/aiida/common/pydantic.py b/src/aiida/common/pydantic.py index 60a3913aad..3e9d86e70e 100644 --- a/src/aiida/common/pydantic.py +++ b/src/aiida/common/pydantic.py @@ -3,14 +3,11 @@ from __future__ import annotations import typing as t -from pathlib import Path -from pydantic import Field +from pydantic import BaseModel, ConfigDict, Field, create_model from pydantic_core import PydanticUndefined if t.TYPE_CHECKING: - from pydantic import BaseModel - from aiida.orm import Entity @@ -23,11 +20,85 @@ def get_metadata(field_info: t.Any, key: str, default: t.Any | None = None) -> t :returns: The metadata if defined, otherwise the default. """ for element in field_info.metadata: - if key in element: + if isinstance(element, dict) and key in element: return element[key] return default +class OrmModel(BaseModel, defer_build=True): + """Base class for all ORM entity models.""" + + model_config = ConfigDict(extra='forbid') + + @classmethod + def __pydantic_init_subclass__(cls, **kwargs: t.Any) -> None: + """Sets the JSON schema title of the model. + + The qualified name of the class is used, with dots removed. For example, `Node.Model` becomes `NodeModel` + in the JSON schema. + """ + super().__pydantic_init_subclass__(**kwargs) + cls.model_config['title'] = cls.__qualname__.replace('.', '') + + @classmethod + def _as_create_model(cls: t.Type[OrmModel]) -> t.Type[OrmModel]: + """Return a derived creation model class with read-only fields removed. + + This also removes any serializers/validators defined on those fields. + + :return: The derived creation model class. + """ + + new_name = cls.__qualname__.replace('Model', 'CreateModel') + CreateModel = create_model( # noqa: N806 + new_name, + __base__=cls, + __module__=cls.__module__, + ) + CreateModel.__qualname__ = new_name + CreateModel.model_config['extra'] = 'ignore' + CreateModel.model_config['json_schema_extra'] = { + **CreateModel.model_config.get('json_schema_extra', {}), # type: ignore[dict-item] + 'additionalProperties': False, + } + + readonly_fields: t.List[str] = [] + for key, field in CreateModel.model_fields.items(): + annotation = field.annotation + if get_metadata(field, 'read_only'): + readonly_fields.append(key) + elif isinstance(annotation, type) and issubclass(annotation, OrmModel): + sub_create_model = annotation._as_create_model() + field.annotation = sub_create_model + if any(f.is_required() for f in sub_create_model.model_fields.values()): + field.default_factory = None + + # Remove read-only fields + for key in readonly_fields: + CreateModel.model_fields.pop(key, None) + if hasattr(CreateModel, key): + delattr(CreateModel, key) + + # Prune field validators/serializers referring to read-only fields + decorators = CreateModel.__pydantic_decorators__ + + def prune_field_decorators(field_decorators: dict[str, t.Any]) -> dict[str, t.Any]: + return { + key: decorator + for key, decorator in field_decorators.items() + if all(field not in readonly_fields for field in decorator.info.fields) + } + + decorators.field_validators = prune_field_decorators(decorators.field_validators) + decorators.field_serializers = prune_field_decorators(decorators.field_serializers) + + # If called on CreateModel, return self + CreateModel._as_create_model = lambda: CreateModel # type: ignore[method-assign] + + CreateModel.model_rebuild(force=True) + return CreateModel + + def MetadataField( # noqa: N802 default: t.Any = PydanticUndefined, *, @@ -35,19 +106,18 @@ def MetadataField( # noqa: N802 short_name: str | None = None, option_cls: t.Any | None = None, orm_class: type[Entity[t.Any, t.Any]] | str | None = None, - orm_to_model: t.Callable[[Entity[t.Any, t.Any], Path], t.Any] | None = None, - model_to_orm: t.Callable[['BaseModel'], t.Any] | None = None, - exclude_to_orm: bool = False, - exclude_from_cli: bool = False, - is_attribute: bool = True, - is_subscriptable: bool = False, + orm_to_model: t.Callable[[Entity[t.Any, t.Any], dict[str, t.Any]], t.Any] | None = None, + model_to_orm: t.Callable[[OrmModel], t.Any] | None = None, + read_only: bool = False, + write_only: bool = False, + may_be_large: bool = False, **kwargs: t.Any, ) -> t.Any: """Return a :class:`pydantic.fields.Field` instance with additional metadata. .. code-block:: python - class Model(BaseModel): + class Model(OrmModel): attribute: MetadataField('default', priority=1000, short_name='-A') @@ -66,20 +136,34 @@ class Model(BaseModel): :param short_name: Optional short name to use for an option on a command line interface. :param option_cls: The :class:`click.Option` class to use to construct the option. :param orm_class: The class, or entry point name thereof, to which the field should be converted. If this field is - defined, the value of this field should acccept an integer which will automatically be converted to an instance + defined, the value of this field should accept an integer which will automatically be converted to an instance of said ORM class using ``orm_class.collection.get(id={field_value})``. This is useful, for example, where a field represents an instance of a different entity, such as an instance of ``User``. The serialized data would store the ``pk`` of the user, but the ORM entity instance would receive the actual ``User`` instance with that primary key. :param orm_to_model: Optional callable to convert the value of a field from an ORM instance to a model instance. :param model_to_orm: Optional callable to convert the value of a field from a model instance to an ORM instance. - :param exclude_to_orm: When set to ``True``, this field value will not be passed to the ORM entity constructor + :param read_only: When set to ``True``, this field value will not be passed to the ORM entity constructor through ``Entity.from_model``. - :param exclude_from_cli: When set to ``True``, this field value will not be exposed on the CLI command that is - dynamically generated to create a new instance. - :param is_attribute: Whether the field is stored as an attribute. - :param is_subscriptable: Whether the field can be indexed like a list or dictionary. + :param write_only: When set to ``True``, this field value will not be populated when constructing the model from an + ORM entity through ``Entity.to_model``. + :param may_be_large: Whether the field value may be large. This is used to determine whether to include the field + when serializing the entity for various purposes, such as exporting or logging. """ + + extra = kwargs.pop('json_schema_extra', {}) + + if read_only and write_only: + raise ValueError('A field cannot be both read-only and write-only.') + + if read_only: + extra.update({'readOnly': True}) + + if write_only: + extra.update({'writeOnly': True}) + + kwargs['json_schema_extra'] = extra + field_info = Field(default, **kwargs) for key, value in ( @@ -89,10 +173,9 @@ class Model(BaseModel): ('orm_class', orm_class), ('orm_to_model', orm_to_model), ('model_to_orm', model_to_orm), - ('exclude_to_orm', exclude_to_orm), - ('exclude_from_cli', exclude_from_cli), - ('is_attribute', is_attribute), - ('is_subscriptable', is_subscriptable), + ('read_only', read_only), + ('write_only', write_only), + ('may_be_large', may_be_large), ): if value is not None: field_info.metadata.append({key: value}) diff --git a/src/aiida/orm/authinfos.py b/src/aiida/orm/authinfos.py index 8282e944fb..65ae2ff8d0 100644 --- a/src/aiida/orm/authinfos.py +++ b/src/aiida/orm/authinfos.py @@ -10,7 +10,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Optional, Type +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Type, cast from aiida.common import exceptions from aiida.common.pydantic import MetadataField @@ -32,8 +32,10 @@ class AuthInfoCollection(entities.Collection['AuthInfo']): """The collection of `AuthInfo` entries.""" + collection_type: ClassVar[str] = 'authinfos' + @staticmethod - def _entity_base_cls() -> Type['AuthInfo']: + def _entity_base_cls() -> Type[AuthInfo]: return AuthInfo def delete(self, pk: int) -> None: @@ -53,30 +55,25 @@ class AuthInfo(entities.Entity['BackendAuthInfo', AuthInfoCollection]): class Model(entities.Entity.Model): computer: int = MetadataField( description='The PK of the computer', - is_attribute=False, orm_class=Computer, - orm_to_model=lambda auth_info, _: auth_info.computer.pk, # type: ignore[attr-defined] + orm_to_model=lambda auth_info, _: cast(AuthInfo, auth_info).computer.pk, ) user: int = MetadataField( description='The PK of the user', - is_attribute=False, orm_class=User, - orm_to_model=lambda auth_info, _: auth_info.user.pk, # type: ignore[attr-defined] + orm_to_model=lambda auth_info, _: cast(AuthInfo, auth_info).user.pk, ) enabled: bool = MetadataField( True, description='Whether the instance is enabled', - is_attribute=False, ) auth_params: Dict[str, Any] = MetadataField( default_factory=dict, description='Dictionary of authentication parameters', - is_attribute=False, ) metadata: Dict[str, Any] = MetadataField( default_factory=dict, description='Dictionary of metadata', - is_attribute=False, ) def __init__( diff --git a/src/aiida/orm/comments.py b/src/aiida/orm/comments.py index 8d6614de38..1ad7de9bda 100644 --- a/src/aiida/orm/comments.py +++ b/src/aiida/orm/comments.py @@ -8,8 +8,13 @@ ########################################################################### """Comment objects and functions""" +from __future__ import annotations + from datetime import datetime -from typing import TYPE_CHECKING, List, Optional, Type, cast +from typing import TYPE_CHECKING, ClassVar, List, Optional, Type, cast +from uuid import UUID + +from pydantic import field_serializer from aiida.common.pydantic import MetadataField from aiida.manage import get_manager @@ -28,8 +33,10 @@ class CommentCollection(entities.Collection['Comment']): """The collection of Comment entries.""" + collection_type: ClassVar[str] = 'comments' + @staticmethod - def _entity_base_cls() -> Type['Comment']: + def _entity_base_cls() -> Type[Comment]: return Comment def delete(self, pk: int) -> None: @@ -67,29 +74,45 @@ class Comment(entities.Entity['BackendComment', CommentCollection]): _CLS_COLLECTION = CommentCollection + identity_field: ClassVar[str] = 'uuid' + class Model(entities.Entity.Model): - uuid: Optional[str] = MetadataField( - description='The UUID of the comment', is_attribute=False, exclude_to_orm=True + uuid: UUID = MetadataField( + description='The UUID of the comment', + read_only=True, + examples=['123e4567-e89b-12d3-a456-426614174000'], ) - ctime: Optional[datetime] = MetadataField( - description='Creation time of the comment', is_attribute=False, exclude_to_orm=True + ctime: datetime = MetadataField( + description='Creation time of the comment', + read_only=True, + examples=['2024-01-01T12:00:00+00:00'], ) - mtime: Optional[datetime] = MetadataField( - description='Modified time of the comment', is_attribute=False, exclude_to_orm=True + mtime: datetime = MetadataField( + description='Modified time of the comment', + read_only=True, + examples=['2024-01-02T12:00:00+00:00'], ) node: int = MetadataField( description='Node PK that the comment is attached to', - is_attribute=False, orm_class='core.node', - orm_to_model=lambda comment, _: cast('Comment', comment).node.pk, + orm_to_model=lambda comment, _: cast(Comment, comment).node.pk, + examples=[42], ) user: int = MetadataField( description='User PK that created the comment', - is_attribute=False, orm_class='core.user', - orm_to_model=lambda comment, _: cast('Comment', comment).user.pk, + orm_to_model=lambda comment, _: cast(Comment, comment).user.pk, + examples=[7], + ) + content: str = MetadataField( + description='Content of the comment', + examples=['This is a comment.'], ) - content: str = MetadataField(description='Content of the comment', is_attribute=False) + + @field_serializer('uuid') + def serialize_uuid(self, value: UUID) -> str: + """Serialize UUID to string.""" + return str(value) def __init__( self, node: 'Node', user: 'User', content: Optional[str] = None, backend: Optional['StorageBackend'] = None diff --git a/src/aiida/orm/computers.py b/src/aiida/orm/computers.py index e8afea0b65..85024da175 100644 --- a/src/aiida/orm/computers.py +++ b/src/aiida/orm/computers.py @@ -8,8 +8,13 @@ ########################################################################### """Module for Computer entities""" +from __future__ import annotations + import os -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple, Type, Union +from uuid import UUID + +from pydantic import field_serializer from aiida.common import exceptions from aiida.common.log import AIIDA_LOGGER, AiidaLoggerType @@ -31,11 +36,13 @@ class ComputerCollection(entities.Collection['Computer']): """The collection of Computer entries.""" + collection_type: ClassVar[str] = 'computers' + @staticmethod - def _entity_base_cls() -> Type['Computer']: + def _entity_base_cls() -> Type[Computer]: return Computer - def get_or_create(self, label: str, **kwargs: Any) -> Tuple[bool, 'Computer']: + def get_or_create(self, label: str, **kwargs: Any) -> Tuple[bool, Computer]: """Try to retrieve a Computer from the DB with the given arguments; create (and store) a new Computer if such a Computer was not present yet. @@ -74,13 +81,43 @@ class Computer(entities.Entity['BackendComputer', ComputerCollection]): _CLS_COLLECTION = ComputerCollection class Model(entities.Entity.Model): - uuid: str = MetadataField(description='The UUID of the computer', is_attribute=False, exclude_to_orm=True) - label: str = MetadataField(description='Label for the computer', is_attribute=False) - description: str = MetadataField(description='Description of the computer', is_attribute=False) - hostname: str = MetadataField(description='Hostname of the computer', is_attribute=False) - transport_type: str = MetadataField(description='Transport type of the computer', is_attribute=False) - scheduler_type: str = MetadataField(description='Scheduler type of the computer', is_attribute=False) - metadata: Dict[str, Any] = MetadataField(description='Metadata of the computer', is_attribute=False) + uuid: UUID = MetadataField( + description='The UUID of the computer', + read_only=True, + examples=['123e4567-e89b-12d3-a456-426614174000'], + ) + label: str = MetadataField( + description='Label for the computer', + examples=['localhost'], + ) + description: str = MetadataField( + '', + description='Description of the computer', + examples=['My local machine'], + ) + hostname: str = MetadataField( + description='Hostname of the computer', + examples=['localhost'], + ) + transport_type: str = MetadataField( + description='Transport type of the computer', + examples=['core.local'], + ) + scheduler_type: str = MetadataField( + description='Scheduler type of the computer', + examples=['core.direct'], + ) + metadata: Dict[str, Any] = MetadataField( + default_factory=dict, + description='Metadata of the computer', + may_be_large=True, + examples=[{'key': 'value'}], + ) + + @field_serializer('uuid') + def serialize_uuid(self, value: UUID) -> str: + """Serialize UUID to string.""" + return str(value) def __init__( self, @@ -261,11 +298,11 @@ def default_memory_per_machine_validator(cls, def_memory_per_machine: Optional[i f'Invalid value for def_memory_per_machine, must be a positive int, got: {def_memory_per_machine}' ) - def copy(self) -> 'Computer': + def copy(self) -> Computer: """Return a copy of the current object to work with, not stored yet.""" return entities.from_backend_entity(Computer, self._backend_entity.copy()) - def store(self) -> 'Computer': + def store(self) -> Computer: """Store the computer in the DB. Differently from Nodes, a computer can be re-stored if its properties diff --git a/src/aiida/orm/entities.py b/src/aiida/orm/entities.py index 172f982c15..3183f08f9d 100644 --- a/src/aiida/orm/entities.py +++ b/src/aiida/orm/entities.py @@ -14,17 +14,15 @@ import pathlib from enum import Enum from functools import lru_cache -from typing import TYPE_CHECKING, Any, Generic, List, NoReturn, Optional, Type, TypeVar, Union +from typing import TYPE_CHECKING, Any, ClassVar, Generic, List, Literal, NoReturn, Optional, Type, TypeVar, Union from plumpy.base.utils import call_with_super_check, super_check -from pydantic import BaseModel -from pydantic.fields import FieldInfo from typing_extensions import Self from aiida.common import exceptions, log from aiida.common.exceptions import EntryPointError, InvalidOperation, NotExistent from aiida.common.lang import classproperty, type_check -from aiida.common.pydantic import MetadataField, get_metadata +from aiida.common.pydantic import MetadataField, OrmModel, get_metadata from aiida.common.warnings import warn_deprecation from aiida.manage import get_manager @@ -58,6 +56,8 @@ class EntityTypes(Enum): class Collection(abc.ABC, Generic[EntityType]): """Container class that represents the collection of objects of a particular entity type.""" + collection_type: ClassVar[str] = 'entities' + @staticmethod @abc.abstractmethod def _entity_base_cls() -> Type[EntityType]: @@ -184,79 +184,162 @@ class Entity(abc.ABC, Generic[BackendEntityType, CollectionType], metaclass=Enti _CLS_COLLECTION: Type[CollectionType] = Collection # type: ignore[assignment] _logger = log.AIIDA_LOGGER.getChild('orm.entities') - class Model(BaseModel, defer_build=True): - pk: Optional[int] = MetadataField( - None, - description='The primary key of the entity. Can be `None` if the entity is not yet stored.', - is_attribute=False, - exclude_to_orm=True, - exclude_from_cli=True, + identity_field: ClassVar[str] = 'pk' + + class Model(OrmModel): + pk: int = MetadataField( + description='The primary key of the entity', + read_only=True, + examples=[42], ) - @classmethod - def model_to_orm_fields(cls) -> dict[str, FieldInfo]: - return { - key: field for key, field in cls.Model.model_fields.items() if not get_metadata(field, 'exclude_to_orm') - } + @classproperty + def CreateModel(cls) -> Type[OrmModel]: # noqa: N802, N805 + """Return the creation version of the model class for this entity. + + :return: The creation model class, with read-only fields removed. + """ + return cls.Model._as_create_model() @classmethod - def model_to_orm_field_values(cls, model: Model) -> dict[str, Any]: + def model_to_orm_field_values(cls, valid_model: OrmModel) -> dict[str, Any]: + """Collect values for the ORM entity's fields from the given model instance. + + This method centralizes the mapping of Model -> ORM values, including handling of + `orm_class` metadata and nested models. The process is recursive, applying metadata + field rules to nested models as well. + + :param valid_model: An validated instance of the entity's model class. + :return: Mapping of model field name to validated value. + :raises EntryPointError: if an `orm_class` entry point could not be loaded. + :raises NotExistent: if a referenced ORM entity could not be found. + """ from aiida.plugins.factories import BaseFactory - fields = {} + def get_orm_field_values(model: OrmModel, schema: type[OrmModel] = cls.CreateModel) -> dict[str, Any]: + """Recursive helper function to collect field values from a model instance. + + :param model: The model instance to extract field values from. + :param schema: The model class. + :return: Mapping of model field name to validated value. + """ + + fields: dict[str, Any] = {} + for key, field in schema.model_fields.items(): + field_value = getattr(model, key, field.default) + + if field_value is None: + continue + + annotation = field.annotation + if isinstance(annotation, type) and issubclass(annotation, OrmModel): + fields[key] = get_orm_field_values(field_value, annotation) + elif orm_class := get_metadata(field, 'orm_class'): + if isinstance(orm_class, str): + try: + orm_class = BaseFactory('aiida.orm', orm_class) + except EntryPointError as exception: + raise EntryPointError( + f'The `orm_class` of `{cls.__name__}.Model.{key} is invalid: {exception}' + ) from exception + try: + fields[key] = orm_class.collection.get(id=field_value) + except NotExistent as exception: + raise NotExistent(f'No `{orm_class}` found with pk={field_value}') from exception + elif model_to_orm := get_metadata(field, 'model_to_orm'): + fields[key] = model_to_orm(model) + else: + fields[key] = field_value - for key, field in cls.model_to_orm_fields().items(): - field_value = getattr(model, key) + return fields - if field_value is None: - continue + return get_orm_field_values(valid_model) - if orm_class := get_metadata(field, 'orm_class'): - if isinstance(orm_class, str): - try: - orm_class = BaseFactory('aiida.orm', orm_class) - except EntryPointError as exception: - raise EntryPointError( - f'The `orm_class` of `{cls.__name__}.Model.{key} is invalid: {exception}' - ) from exception - try: - fields[key] = orm_class.collection.get(id=field_value) - except NotExistent as exception: - raise NotExistent(f'No `{orm_class}` found with pk={field_value}') from exception - elif model_to_orm := get_metadata(field, 'model_to_orm'): - fields[key] = model_to_orm(model) - else: - fields[key] = field_value - - return fields - - def _to_model(self, repository_path: pathlib.Path) -> Model: - """Return the entity instance as an instance of its model.""" - fields = {} - - for key, field in self.Model.model_fields.items(): - if orm_to_model := get_metadata(field, 'orm_to_model'): - fields[key] = orm_to_model(self, repository_path) - else: - fields[key] = getattr(self, key) - - return self.Model(**fields) + def orm_to_model_field_values( + self, + *, + repository_path: Optional[pathlib.Path] = None, + model: type[OrmModel] | None = None, + minimal: bool = False, + ) -> dict[str, Any]: + """Collect values for the ``Model``'s fields from this entity. + + Centralizes mapping of ORM -> Model values, including handling of ``orm_to_model`` + functions and optional filtering based on field metadata (e.g., excluding CLI-only fields). + The process is recursive, applying metadata field rules to nested models as well. + + :param repository_path: Optional path to use for repository-based fields. + :param minimal: Whether to exclude potentially large value fields. + :return: Mapping of ORM field name to value. + """ + + def get_model_field_values(model_cls: Type[OrmModel]) -> dict[str, Any]: + """Recursive helper function to collect field values for a model class. + + :param model_cls: The model class to extract field values for. + :return: Mapping of ORM field name to value. + """ + fields: dict[str, Any] = {} + + for key, field in model_cls.model_fields.items(): + if get_metadata(field, 'may_be_large') and minimal: + continue + + annotation = field.annotation + if isinstance(annotation, type) and issubclass(annotation, OrmModel): + fields[key] = get_model_field_values(annotation) + continue + + if orm_to_model := get_metadata(field, 'orm_to_model'): + kwargs = {'repository_path': repository_path} + fields[key] = orm_to_model(self, kwargs) + else: + fields[key] = getattr(self, key, field.default) + + return fields + + return get_model_field_values(model or self.Model) + + def to_model(self, *, repository_path: Optional[pathlib.Path] = None, minimal: bool = False) -> OrmModel: + """Return the entity instance as an instance of its model. + + :param repository_path: If the orm node has files in the repository, this path is used to read the repository + files from. If no path is specified a temporary path is created using the entities pk. + This field can be very large, so it is excluded by default. + :param minimal: Whether to exclude potentially large value fields. + :return: An instance of the entity's model class. + """ + fields = self.orm_to_model_field_values(repository_path=repository_path, minimal=minimal) + Model = self.Model if self.is_stored else self.CreateModel # noqa: N806 + return Model(**fields) @classmethod - def _from_model(cls, model: Model) -> Self: - """Return an entity instance from an instance of its model.""" + def from_model(cls, model: OrmModel) -> Self: + """Return an entity instance from an instance of its model. + + :param model: An instance of the entity's model class. + :return: An instance of the entity class. + """ fields = cls.model_to_orm_field_values(model) return cls(**fields) - def serialize(self, repository_path: Union[pathlib.Path, None] = None) -> dict[str, Any]: + def serialize( + self, + *, + repository_path: Optional[pathlib.Path] = None, + minimal: bool = False, + mode: Literal['json', 'python'] = 'json', + ) -> dict[str, Any]: """Serialize the entity instance to JSON. - :param repository_path: If the orm node has files in the repository, this path is used to dump the repostiory + :param repository_path: If the orm node has files in the repository, this path is used to dump the repository files to. If no path is specified a temporary path is created using the entities pk. + :param minimal: Whether to exclude potentially large value fields. + :param mode: The serialization mode, either 'json' or 'python'. The 'json' mode is the most strict and ensures + that the output is JSON serializable, whereas the 'python' mode allows for more complex Python types, such + as `datetime` objects. + :return: A dictionary that can be serialized to JSON. """ - self.logger.warning( - 'Serialization through pydantic is still an experimental feature and might break in future releases.' - ) if repository_path is None: import tempfile @@ -267,18 +350,27 @@ def serialize(self, repository_path: Union[pathlib.Path, None] = None) -> dict[s raise ValueError(f'The repository_path `{repository_path}` does not exist.') if not repository_path.is_dir(): raise ValueError(f'The repository_path `{repository_path}` is not a directory.') - return self._to_model(repository_path).model_dump() - @classmethod - def from_serialized(cls, **kwargs: dict[str, Any]) -> Self: - """Construct an entity instance from JSON serialized data.""" - cls._logger.warning( - 'Serialization through pydantic is still an experimental feature and might break in future releases.' + return self.to_model( + repository_path=repository_path, + minimal=minimal, + ).model_dump( + mode=mode, + exclude_none=True, + exclude_unset=minimal, ) - return cls._from_model(cls.Model(**kwargs)) + + @classmethod + def from_serialized(cls, serialized: dict[str, Any]) -> Self: + """Construct an entity instance from JSON serialized data. + + :param serialized: A dictionary representing the serialized entity. + :return: An instance of the entity class. + """ + return cls.from_model(cls.CreateModel(**serialized)) @classproperty - def objects(cls: EntityType) -> CollectionType: # noqa: N805 + def objects(cls) -> CollectionType: # noqa: N805 """Get a collection for objects of this type, with the default backend. .. deprecated:: This will be removed in v3, use ``collection`` instead. @@ -399,7 +491,7 @@ def backend_entity(self) -> BackendEntityType: return self._backend_entity -def from_backend_entity(cls: Type[EntityType], backend_entity: BackendEntityType) -> EntityType: +def from_backend_entity(cls: Type[EntityType], backend_entity: BackendEntity) -> EntityType: """Construct an entity from a backend entity instance :param backend_entity: the backend entity diff --git a/src/aiida/orm/fields.py b/src/aiida/orm/fields.py index f0e8ca390b..760002b534 100644 --- a/src/aiida/orm/fields.py +++ b/src/aiida/orm/fields.py @@ -15,10 +15,8 @@ from functools import singledispatchmethod from pprint import pformat -from pydantic import BaseModel - from aiida.common.lang import isidentifier -from aiida.common.pydantic import get_metadata +from aiida.common.pydantic import OrmModel, get_metadata __all__ = ( 'QbField', @@ -51,7 +49,6 @@ class QbField: '_doc', '_dtype', '_is_attribute', - '_is_subscriptable', '_key', ) @@ -63,7 +60,6 @@ def __init__( dtype: t.Optional[t.Any] = None, doc: str = '', is_attribute: bool = True, - is_subscriptable: bool = False, ) -> None: """Initialise a ORM entity field, accessible via the ``QueryBuilder`` @@ -72,14 +68,12 @@ def __init__( :param dtype: The data type of the field. If None, the field is of variable type. :param doc: A docstring for the field :param is_attribute: If True, the ``backend_key`` property will prepend "attributes." to field name - :param is_subscriptable: If True, a new field can be created by ``field["subkey"]`` """ self._key = key self._backend_key = alias if alias is not None else key self._doc = doc self._dtype = dtype self._is_attribute = is_attribute - self._is_subscriptable = is_subscriptable @property def key(self) -> str: @@ -91,42 +85,30 @@ def backend_key(self) -> str: return f'attributes.{self._backend_key}' return self._backend_key - @property - def doc(self) -> str: - return self._doc - @property def dtype(self) -> t.Optional[t.Any]: """Return the primitive root type.""" return extract_root_type(self._dtype) - @property - def annotation(self) -> t.Optional[t.Any]: - """Return the full type annotation.""" - return self._dtype - - @property - def is_attribute(self) -> bool: - return self._is_attribute - - @property - def is_subscriptable(self) -> bool: - return self._is_subscriptable + def in_(self, value: t.Iterable[t.Any]): + """Return a filter for only values in the list""" + try: + set(value) + except TypeError: + raise TypeError('in_ must be iterable') + return QbFieldFilters(((self, 'in', value),)) def __repr__(self) -> str: return ( - f'{self.__class__.__name__}({self.key!r}' + f'{self.__class__.__name__}({self.backend_key!r}' + (f', {self._backend_key!r}' if self._backend_key != self.key else '') + (f', dtype={self._dtype or ""}') - + (f', is_attribute={self.is_attribute}') - + (f', is_subscriptable={self.is_subscriptable!r}' if self.is_subscriptable else '') + + (f', doc={self._doc!r}' if self._doc else '') + ')' ) def __str__(self) -> str: - class_name = self.__class__.__name__ - field_name = f'{self.backend_key}{".*" if self.is_subscriptable else ""}' - return f'{class_name}({field_name}) -> {self._dtype}' + return f'{self.__class__.__name__}({self.backend_key}) -> {self._dtype}' def __hash__(self): return hash((self.key, self.backend_key)) @@ -137,13 +119,9 @@ def __eq__(self, value): def __ne__(self, value): return QbFieldFilters(((self, '!==', value),)) - def in_(self, value: t.Iterable[t.Any]): - """Return a filter for only values in the list""" - try: - set(value) - except TypeError: - raise TypeError('in_ must be iterable') - return QbFieldFilters(((self, 'in', value),)) + def __dir__(self) -> list[str]: + """Return the field properties for tab completion.""" + return sorted(set(super().__dir__()) - {'key', 'backend_key', 'dtype'}) if t.TYPE_CHECKING: @@ -223,18 +201,52 @@ def has_key(self, value): """Return a filter for only values with these keys""" return QbFieldFilters(((self, 'has_key', value),)) - def __getitem__(self, key: str) -> 'QbAttrField': + def __getitem__(self, key: str) -> 'QbAnyField': """Return a new `QbField` with a nested key.""" - if not self.is_subscriptable: - raise IndexError('This field is not subscriptable') - return QbAttrField( - f'{self.key}.{key}', - f'{self._backend_key}.{key}' if self.is_attribute else None, - is_attribute=self.is_attribute, + return QbAnyField( + key=f'{self.key}.{key}', + alias=f'{self._backend_key}.{key}' if self._is_attribute else None, + dtype=t.Any, + is_attribute=self._is_attribute, ) + def __str__(self) -> str: + return f'{self.__class__.__name__}({self.backend_key}[...]) -> {self._dtype}' + + +class QbAttributesField(QbDictField): + """The 'attributes' field of an ORM Node. + + Used specifically for `orm.Node.attributes`, where the inner keys + are defined by the node's `AttributesModel`. + """ + + _typed_children: dict[str, QbField] -class QbAttrField(QbNumericField, QbArrayField, QbStrField, QbDictField): + def __getattr__(self, key: str) -> 'QbField': + """Return a typed child field if known; otherwise raise AttributeError. + + This enables autocomplete on fields such as: + orm.Int.fields.attributes.value + orm.Data.fields.attributes.source + """ + if key.startswith('_'): + # normal attribute lookup + raise AttributeError(key) + + children = getattr(self, '_typed_children', None) or {} + if key in children: + return children[key] + + raise AttributeError(key) + + def __dir__(self) -> list[str]: + """Expose typed children for autocompletion.""" + children = getattr(self, '_typed_children', None) or {} + return sorted(set(super().__dir__()) | set(children.keys())) + + +class QbAnyField(QbNumericField, QbArrayField, QbStrField, QbDictField): """A generic flavor of `QbField` covering all operations.""" def of_type(self, value): @@ -354,8 +366,12 @@ class QbFields: def __init__(self, fields: t.Optional[t.Dict[str, QbField]] = None): self._fields = fields or {} + def keys(self) -> list[str]: + """Return the field keys, sorted, prefixed with 'attribute.' if field is an attribute.""" + return sorted([field.backend_key for field in self._fields.values()]) + def __repr__(self) -> str: - return pformat({key: str(value) for key, value in self._fields.items()}) + return pformat({key: repr(value) for key, value in self._fields.items()}, width=500) def __str__(self) -> str: return str({key: str(value) for key, value in self._fields.items()}) @@ -399,80 +415,91 @@ class EntityFieldMeta(ABCMeta): def __init__(cls, name, bases, classdict): super().__init__(name, bases, classdict) - # only allow an existing fields attribute if has been generated from a subclass + # Only allow an existing fields attribute if has been generated from a subclass current_fields = getattr(cls, 'fields', None) if current_fields is not None and not isinstance(current_fields, QbFields): raise ValueError(f"class '{cls}' already has a `fields` attribute set") - fields = {} - - # If the class has an attribute ``Model`` that is a subclass of :class:`pydantic.BaseModel`, parse the model - # fields to build up the ``fields`` class attribute, which is used to allow specifying ``QueryBuilder`` filters - # programmatically. - if hasattr(cls, 'Model') and issubclass(cls.Model, BaseModel): - # If the class itself directly specifies the ``Model`` attribute, check that it is valid. Here, the check - # ``cls.__dict__`` is used instead of ``hasattr`` as the former only returns true if the class itself - # defines the attribute and does not just inherit it from a base class. In that case, this check will - # already have been performed for that subclass. - - # When a class defines a ``Model``, the following check ensures that the model inherits from the same bases - # as the class containing the attribute itself. For example, if ``cls`` inherits from ``ClassA`` and - # ``ClassB`` that each define a ``Model``, the ``cls.Model`` class should inherit from both ``ClassA.Model`` - # and ``ClassBModel`` or it will be losing the attributes of some of the models. + fields: dict[str, t.Any] = {} + + if cls._has_model('Model'): if 'Model' in cls.__dict__: - # Get all the base classes in the MRO of this class that define a class attribute ``Model`` that is a - # subclass of pydantic's ``BaseModel`` and not the class itself - cls_bases_with_model = [ - base - for base in cls.__mro__ - if base is not cls and 'Model' in base.__dict__ and issubclass(base.Model, BaseModel) # type: ignore[attr-defined] - ] + cls._validate_model_inheritance('Model') - # Now get the "leaf" bases, i.e., those base classes in the subclass list that themselves do not have a - # subclass in the tree. This set should be the base classes for the class' ``Model`` attribute. - cls_bases_with_model_leaves = { - base - for base in cls_bases_with_model - if all( - not issubclass(b.Model, base.Model) # type: ignore[attr-defined] - for b in cls_bases_with_model - if b is not base - ) - } - - cls_model_bases = {base.Model for base in cls_bases_with_model_leaves} # type: ignore[attr-defined] - - # If the base class does not have a base that defines a model, it means the ``Model`` should simply have - # ``pydantic.BaseModel`` as its sole base. - if not cls_model_bases: - cls_model_bases = { - BaseModel, - } - - # Get the set of bases of ``cls.Model`` that are a subclass of :class:`pydantic.BaseModel`. - model_bases = {base for base in cls.Model.__bases__ if issubclass(base, BaseModel)} - - # For ``cls.Model`` to be valid, the bases that contain a model, should equal to the leaf bases of the - # ``cls`` itself that also define a model. - if model_bases != cls_model_bases and not getattr(cls, '_SKIP_MODEL_INHERITANCE_CHECK', False): - bases = [f'{e.__module__}.{e.__name__}.Model' for e in cls_bases_with_model_leaves] - raise RuntimeError( - f'`{cls.__name__}.Model` does not subclass all necessary base classes. It should be: ' - f'`class Model({", ".join(sorted(bases))}):`' - ) - - for key, field in cls.Model.model_fields.items(): + Model = cls.Model # type: ignore[attr-defined] # noqa N806 + for key, field in Model.model_fields.items(): fields[key] = add_field( key, - alias=get_metadata(field, 'alias', None), + alias=field.alias, + dtype=field.annotation, + doc=field.description, + ) + + # TODO should this be done more generally/recursively for all nested models? + if cls._has_model('AttributesModel'): + if 'AttributesModel' in cls.__dict__: + cls._validate_model_inheritance('AttributesModel') + + try: + container_field = fields['attributes'] + except KeyError: + raise KeyError(f"class '{cls}' was expected to have a `Model` with an 'attributes' field") + + container_field._typed_children = {} + + Model = cls.AttributesModel # type: ignore[attr-defined] # noqa N806 + for key, field in Model.model_fields.items(): + if get_metadata(field, 'write_only'): + continue + + typed_field = add_field( + key, + alias=field.alias, dtype=field.annotation, doc=field.description, - is_attribute=get_metadata(field, 'is_attribute', False), - is_subscriptable=get_metadata(field, 'is_subscriptable', False), + is_attribute=True, ) + container_field._typed_children[key] = typed_field + fields[key] = typed_field # BACKWARDS COMPATIBILITY + + # Finalize cls.fields = QbFields({key: fields[key] for key in sorted(fields)}) + def _has_model(cls, model_name: str = 'Model') -> bool: + """Return whether the class has a model defined.""" + return hasattr(cls, model_name) and issubclass(getattr(cls, model_name), OrmModel) + + def _validate_model_inheritance(cls, model_name: str = 'Model') -> None: + """Validate that model class inherits from all necessary base classes.""" + + cls_bases_with_model = [ + base + for base in cls.__mro__ + if base is not cls and model_name in base.__dict__ and issubclass(getattr(base, model_name), OrmModel) + ] + + cls_bases_with_model_leaves = { + base + for base in cls_bases_with_model + if all( + not issubclass(getattr(b, model_name), getattr(base, model_name)) + for b in cls_bases_with_model + if b is not base + ) + } + + cls_model_bases = {getattr(base, model_name) for base in cls_bases_with_model_leaves} or {OrmModel} + + model_bases = {base for base in getattr(cls, model_name).__bases__ if issubclass(base, OrmModel)} + + if model_bases != cls_model_bases and not getattr(cls, '_SKIP_MODEL_INHERITANCE_CHECK', False): + bases = [f'{e.__module__}.{e.__name__}.{model_name}' for e in cls_bases_with_model_leaves] + raise RuntimeError( + f'`{cls.__name__}.{model_name}` does not subclass all necessary base classes. It should be: ' + f'`class {model_name}({", ".join(sorted(bases))}):`' + ) + class QbFieldArguments(t.TypedDict): key: str @@ -480,7 +507,6 @@ class QbFieldArguments(t.TypedDict): dtype: t.Optional[t.Any] doc: str is_attribute: bool - is_subscriptable: bool def add_field( @@ -489,8 +515,7 @@ def add_field( *, dtype: t.Optional[t.Any] = None, doc: str = '', - is_attribute: bool = True, - is_subscriptable: bool = False, + is_attribute: bool = False, ) -> QbField: """Add a `dtype`-dependent `QbField` representation of a field. @@ -499,7 +524,6 @@ def add_field( :param dtype: The data type of the field. If None, the field is of variable type. :param doc: A docstring for the field :param is_attribute: If True, the ``backend_key`` property will prepend "attributes." to field name - :param is_subscriptable: If True, a new field can be created by ``field["subkey"]`` """ kwargs: QbFieldArguments = { 'key': key, @@ -507,15 +531,14 @@ def add_field( 'dtype': dtype, 'doc': doc, 'is_attribute': is_attribute, - 'is_subscriptable': is_subscriptable, } if not isidentifier(key): raise ValueError(f'{key} is not a valid python identifier') if not is_attribute and alias: raise ValueError('only attribute fields may be aliased') - if not dtype: - return QbField(**kwargs) - root_type = extract_root_type(dtype) + if key == 'attributes': + return QbAttributesField(**kwargs) + root_type = extract_root_type(dtype) if dtype else None if root_type in (int, float, datetime.datetime): return QbNumericField(**kwargs) elif root_type is list: @@ -525,4 +548,4 @@ def add_field( elif root_type is dict: return QbDictField(**kwargs) else: - return QbField(**kwargs) + return QbAnyField(**kwargs) diff --git a/src/aiida/orm/groups.py b/src/aiida/orm/groups.py index 8582ee56b9..a0f221e9c8 100644 --- a/src/aiida/orm/groups.py +++ b/src/aiida/orm/groups.py @@ -6,14 +6,18 @@ # For further information on the license, see the LICENSE.txt file # # For further information please visit http://www.aiida.net # ########################################################################### -"""AiiDA Group entites""" +"""AiiDA Group entities""" + +from __future__ import annotations import datetime import warnings from functools import cached_property from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Sequence, Tuple, Type, Union, cast +from uuid import UUID +from pydantic import field_serializer from typing_extensions import Self from aiida.common import exceptions @@ -34,7 +38,7 @@ __all__ = ('AutoGroup', 'Group', 'ImportGroup', 'UpfFamily') -def load_group_class(type_string: str) -> Type['Group']: +def load_group_class(type_string: str) -> Type[Group]: """Load the sub class of `Group` that corresponds to the given `type_string`. .. note:: will fall back on `aiida.orm.groups.Group` if `type_string` cannot be resolved to loadable entry point. @@ -58,11 +62,13 @@ def load_group_class(type_string: str) -> Type['Group']: class GroupCollection(entities.Collection['Group']): """Collection of Groups""" + collection_type: ClassVar[str] = 'groups' + @staticmethod - def _entity_base_cls() -> Type['Group']: + def _entity_base_cls() -> Type[Group]: return Group - def get_or_create(self, label: Optional[str] = None, **kwargs) -> Tuple['Group', bool]: + def get_or_create(self, label: Optional[str] = None, **kwargs) -> Tuple[Group, bool]: """Try to retrieve a group from the DB with the given arguments; create (and store) a new group if such a group was not present yet. @@ -95,9 +101,9 @@ def delete(self, pk: int) -> None: class GroupBase: """A namespace for group related functionality, that is not directly related to its user-facing properties.""" - def __init__(self, group: 'Group') -> None: + def __init__(self, group: Group) -> None: """Construct a new instance of the base namespace.""" - self._group: 'Group' = group + self._group: Group = group @cached_property def extras(self) -> extras.EntityExtras: @@ -110,27 +116,53 @@ class Group(entities.Entity['BackendGroup', GroupCollection]): __type_string: ClassVar[Optional[str]] + identity_field: ClassVar[str] = 'uuid' + class Model(entities.Entity.Model): - uuid: str = MetadataField(description='The UUID of the group', is_attribute=False, exclude_to_orm=True) - type_string: str = MetadataField(description='The type of the group', is_attribute=False, exclude_to_orm=True) + uuid: UUID = MetadataField( + description='The UUID of the group', + read_only=True, + examples=['123e4567-e89b-12d3-a456-426614174000'], + ) + type_string: str = MetadataField( + description='The type of the group', + read_only=True, + examples=['my_custom_group_type'], + ) user: int = MetadataField( - description='The group owner', - is_attribute=False, + description='The PK of the group owner', orm_class='core.user', - orm_to_model=lambda group, _: group.user.pk, # type: ignore[attr-defined] + orm_to_model=lambda group, _: cast(Group, group).user.pk, + read_only=True, + examples=[1], + ) + time: datetime.datetime = MetadataField( + description='The creation time of the node, defaults to now (timezone-aware)', + read_only=True, + examples=['2024-01-01T12:00:00+00:00'], ) - time: Optional[datetime.datetime] = MetadataField( - description='The creation time of the node', is_attribute=False + label: str = MetadataField( + description='The group label', + examples=['my_group_label'], ) - label: str = MetadataField(description='The group label', is_attribute=False) - description: Optional[str] = MetadataField(description='The group description', is_attribute=False) - extras: Optional[Dict[str, Any]] = MetadataField( + description: str = MetadataField( + '', + description='The group description', + examples=['This is my group description.'], + ) + extras: Dict[str, Any] = MetadataField( + default_factory=dict, description='The group extras', - is_attribute=False, - is_subscriptable=True, - orm_to_model=lambda group, _: group.base.extras.all, # type: ignore[attr-defined] + orm_to_model=lambda group, _: cast(Group, group).base.extras.all, + may_be_large=True, + examples=[{'key': 'value'}], ) + @field_serializer('uuid') + def serialize_uuid(self, value: UUID) -> str: + """Serialize UUID to string.""" + return str(value) + _CLS_COLLECTION = GroupCollection def __init__( diff --git a/src/aiida/orm/logs.py b/src/aiida/orm/logs.py index ed07deb847..bc39560839 100644 --- a/src/aiida/orm/logs.py +++ b/src/aiida/orm/logs.py @@ -8,9 +8,14 @@ ########################################################################### """Module for orm logging abstract classes""" +from __future__ import annotations + import logging from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type +from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Type, cast +from uuid import UUID + +from pydantic import field_serializer from aiida.common import timezone from aiida.common.pydantic import MetadataField @@ -39,11 +44,13 @@ class LogCollection(entities.Collection['Log']): and retrieve logs. """ + collection_type: ClassVar[str] = 'logs' + @staticmethod - def _entity_base_cls() -> Type['Log']: + def _entity_base_cls() -> Type[Log]: return Log - def create_entry_from_record(self, record: logging.LogRecord) -> Optional['Log']: + def create_entry_from_record(self, record: logging.LogRecord) -> Optional[Log]: """Helper function to create a log entry from a record created as by the python logging library :param record: The record created by the logging module @@ -81,7 +88,7 @@ def create_entry_from_record(self, record: logging.LogRecord) -> Optional['Log'] backend=self.backend, ) - def get_logs_for(self, entity: 'Node', order_by: Optional['OrderByType'] = None) -> List['Log']: + def get_logs_for(self, entity: 'Node', order_by: Optional['OrderByType'] = None) -> List[Log]: """Get all the log messages for a given node and optionally sort :param entity: the entity to get logs for @@ -129,24 +136,57 @@ class Log(entities.Entity['BackendLog', LogCollection]): _CLS_COLLECTION = LogCollection + identity_field: ClassVar[str] = 'uuid' + class Model(entities.Entity.Model): - uuid: str = MetadataField(description='The UUID of the node', is_attribute=False, exclude_to_orm=True) - loggername: str = MetadataField(description='The name of the logger', is_attribute=False) - levelname: str = MetadataField(description='The name of the log level', is_attribute=False) - message: str = MetadataField(description='The message of the log', is_attribute=False) - time: datetime = MetadataField(description='The time at which the log was created', is_attribute=False) - metadata: Dict[str, Any] = MetadataField(description='The metadata of the log', is_attribute=False) - dbnode_id: int = MetadataField(description='Associated node', is_attribute=False) + uuid: UUID = MetadataField( + description='The UUID of the node', + read_only=True, + examples=['123e4567-e89b-12d3-a456-426614174000'], + ) + loggername: str = MetadataField( + description='The name of the logger', + examples=['aiida.node'], + ) + levelname: str = MetadataField( + description='The name of the log level', + examples=['INFO', 'ERROR'], + ) + message: str = MetadataField( + description='The message of the log', + examples=['This is a log message.'], + ) + time: datetime = MetadataField( + description='The time at which the log was created', + examples=['2024-01-01T12:00:00+00:00'], + ) + metadata: Dict[str, Any] = MetadataField( + default_factory=dict, + description='The metadata of the log', + examples=[{'key': 'value'}], + ) + node: int = MetadataField( + description='Associated node', + orm_class='core.node', + orm_to_model=lambda log, _: cast(Log, log).dbnode_id, + examples=[42], + ) + + @field_serializer('uuid') + def serialize_uuid(self, value: UUID) -> str: + """Serialize UUID to string.""" + return str(value) def __init__( self, time: datetime, loggername: str, levelname: str, - dbnode_id: int, + dbnode_id: Optional[int] = None, message: str = '', metadata: Optional[Dict[str, Any]] = None, backend: Optional['StorageBackend'] = None, + node: Optional[Node] = None, ): """Construct a new log @@ -166,6 +206,10 @@ def __init__( if not loggername or not levelname: raise exceptions.ValidationError('The loggername and levelname cannot be empty') + dbnode_id = dbnode_id or (node.pk if node is not None else None) + if dbnode_id is None: + raise exceptions.ValidationError('Either dbnode_id or node must be provided to create a Log entry') + backend = backend or get_manager().get_profile_storage() model = backend.logs.create( time=time, diff --git a/src/aiida/orm/nodes/data/array/array.py b/src/aiida/orm/nodes/data/array/array.py index 9118187b0c..4bcaf1a82a 100644 --- a/src/aiida/orm/nodes/data/array/array.py +++ b/src/aiida/orm/nodes/data/array/array.py @@ -10,12 +10,12 @@ from __future__ import annotations -import base64 import io -from typing import Any, Iterator, Optional +from collections.abc import Iterable, Iterator, Sequence +from typing import Any, Optional, Union, cast import numpy as np -from pydantic import ConfigDict +from pydantic import ConfigDict, field_validator from aiida.common.pydantic import MetadataField @@ -47,62 +47,75 @@ class ArrayData(Data): """ - class Model(Data.Model): - model_config = ConfigDict(arbitrary_types_allowed=True) - arrays: Optional[dict[str, bytes]] = MetadataField( + class AttributesModel(Data.AttributesModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + json_schema_extra={ + 'patternProperties': { + r'^array\|[A-Za-z0-9_]+$': { + 'type': 'array', + 'items': {'type': 'integer'}, + 'minItems': 1, + 'description': 'Shape of an array stored in the repository', + 'readOnly': True, + } + } + }, + ) + + arrays: Optional[Union[Sequence, dict[str, Sequence]]] = MetadataField( None, - description='The dictionary of numpy arrays.', - orm_to_model=lambda node, _: ArrayData.save_arrays(node.arrays), # type: ignore[attr-defined] - model_to_orm=lambda model: ArrayData.load_arrays(model.arrays), # type: ignore[attr-defined] + description='A single (or dictionary of) array(s) to store', + orm_to_model=lambda node, _: cast(ArrayData, node).arrays, + write_only=True, ) + @field_validator('arrays', mode='before') + @classmethod + def normalize_arrays( + cls, + value: Sequence | np.ndarray | dict[str, Sequence | np.ndarray] | None, + ) -> Sequence | dict[str, Sequence] | None: + if value is None: + return value + if isinstance(value, Sequence): + return value + if isinstance(value, np.ndarray): + return value.tolist() + elif isinstance(value, dict): + arrays: dict[str, Sequence] = {} + for key, array in value.items(): + if isinstance(array, Sequence): + arrays[key] = array + elif isinstance(array, np.ndarray): + arrays[key] = array.tolist() + else: + raise TypeError(f'`arrays` should be an iterable or dictionary of iterables but got: {value}') + return arrays + else: + raise TypeError(f'`arrays` should be an iterable or dictionary of iterables but got: {value}') + array_prefix = 'array|' default_array_name = 'default' - def __init__(self, arrays: np.ndarray | dict[str, np.ndarray] | None = None, **kwargs): + def __init__(self, arrays: Iterable | dict[str, Iterable] | None = None, **kwargs): """Construct a new instance and set one or multiple numpy arrays. :param arrays: An optional single numpy array, or dictionary of numpy arrays to store. """ + arrays = arrays if arrays is not None else kwargs.get('attributes', {}).pop('arrays', {}) + super().__init__(**kwargs) self._cached_arrays: dict[str, np.ndarray] = {} - arrays = arrays if arrays is not None else {} - - if isinstance(arrays, np.ndarray): + if isinstance(arrays, (Sequence, np.ndarray)): arrays = {self.default_array_name: arrays} - if ( - not isinstance(arrays, dict) # type: ignore[redundant-expr] - or any(not isinstance(a, np.ndarray) for a in arrays.values()) - ): - raise TypeError(f'`arrays` should be a single numpy array or dictionary of numpy arrays but got: {arrays}') + if not isinstance(arrays, dict) or any(not isinstance(a, (Sequence, np.ndarray)) for a in arrays.values()): + raise TypeError(f'`arrays` should be a single sequence or dictionary of sequences but got: {arrays}') for key, value in arrays.items(): - self.set_array(key, value) - - @staticmethod - def save_arrays(arrays: dict[str, np.ndarray]) -> dict[str, bytes]: - results = {} - - for key, array in arrays.items(): - stream = io.BytesIO() - np.save(stream, array) - stream.seek(0) - results[key] = base64.encodebytes(stream.read()) - - return results - - @staticmethod - def load_arrays(arrays: dict[str, bytes]) -> dict[str, np.ndarray]: - results = {} - - for key, encoded in arrays.items(): - stream = io.BytesIO(base64.decodebytes(encoded)) - stream.seek(0) - results[key] = np.load(stream) - - return results + self.set_array(key, np.array(value)) @property def arrays(self) -> dict[str, np.ndarray]: @@ -218,18 +231,13 @@ def set_array(self, name: str, array: np.ndarray) -> None: :param name: The name of the array. :param array: The numpy array to store. """ - import re import tempfile if not isinstance(array, np.ndarray): raise TypeError('ArrayData can only store numpy arrays. Convert the object to an array first') # Check if the name is valid - if not name or re.sub('[0-9a-zA-Z_]', '', name): - raise ValueError( - 'The name assigned to the array ({}) is not valid,' - 'it can only contain digits, letters and underscores' - ) + self._validate_array_name(name) # Write the array to a temporary file, and then add it to the repository of the node with tempfile.NamedTemporaryFile() as handle: @@ -245,11 +253,29 @@ def set_array(self, name: str, array: np.ndarray) -> None: # Store the array name and shape for querying purposes self.base.attributes.set(f'{self.array_prefix}{name}', list(array.shape)) + def _validate_array_name(self, name: str) -> None: + """Validate the array name. + + :param name: The name of the array. + :raises ValueError: if the name is not valid. + """ + import re + + if not name or re.sub('[0-9a-zA-Z_]', '', name): + raise ValueError( + f'The name assigned to the array ({name}) is not valid. ' + 'It can only contain digits, letters and underscores' + ) + def _validate(self) -> bool: """Check if the list of .npy files stored inside the node and the list of properties match. Just a name check, no check on the size since this would require to reload all arrays and this may take time and memory. + + In case of mismatch, try to fix it by loading the arrays from files + and updating the properties accordingly. If no files are found, + raise a ValidationError. """ from aiida.common.exceptions import ValidationError @@ -257,9 +283,21 @@ def _validate(self) -> bool: properties = self._arraynames_from_properties() if set(files) != set(properties): - raise ValidationError( - f'Mismatch of files and properties for ArrayData node (pk= {self.pk}): {files} vs. {properties}' - ) + if not files: + raise ValidationError( + f'Mismatch of files and properties for ArrayData node (pk= {self.pk}): {files} vs. {properties}' + ) + for file in files: + self._validate_array_name(file) + try: + content = self.base.repository.get_object_content(f'{file}.npy', 'rb') + array = np.load(io.BytesIO(content)) + self.base.attributes.set(f'{self.array_prefix}{file}', list(array.shape)) + except Exception as exc: + raise ValidationError( + f'ArrayData node (pk= {self.pk}): could not load array `{file}` from file to fix mismatch.' + ) from exc + return super()._validate() def _get_array_entries(self) -> dict[str, Any]: diff --git a/src/aiida/orm/nodes/data/array/bands.py b/src/aiida/orm/nodes/data/array/bands.py index f104d9222e..c34274f068 100644 --- a/src/aiida/orm/nodes/data/array/bands.py +++ b/src/aiida/orm/nodes/data/array/bands.py @@ -10,6 +10,8 @@ in a Brillouin zone, and how to operate on them. """ +from __future__ import annotations + import json import typing as t from string import Template @@ -141,7 +143,7 @@ def nint(num): lumo = [_[0][_[1] + 1] for _ in zip(bands, homo_indexes)] except IndexError: raise ValueError( - 'To understand if it is a metal or insulator, ' 'need more bands than n_band=number_electrons' + 'To understand if it is a metal or insulator, need more bands than n_band=number_electrons' ) else: @@ -158,7 +160,7 @@ def nint(num): lumo = [i[number_electrons // number_electrons_per_band] for i in bands] # take the n+1th level except IndexError: raise ValueError( - 'To understand if it is a metal or insulator, ' 'need more bands than n_band=number_electrons' + 'To understand if it is a metal or insulator, need more bands than n_band=number_electrons' ) if number_electrons % 2 == 1 and len(stored_bands.shape) == 2: @@ -213,9 +215,26 @@ def nint(num): class BandsData(KpointsData): """Class to handle bands data""" - class Model(KpointsData.Model): - array_labels: t.Optional[t.List[str]] = MetadataField(description='Labels associated with the band arrays') - units: str = MetadataField(description='Units in which the data in bands were stored') + class AttributesModel(KpointsData.AttributesModel): + array_labels: t.Optional[t.List[str]] = MetadataField( + None, + description='Labels associated with the band arrays', + ) + units: t.Optional[str] = MetadataField( + None, + description='Units in which the data in bands were stored', + orm_to_model=lambda node, _: t.cast(BandsData, node).base.attributes.get('units', None), + ) + + def __init__( + self, + *, + array_labels: list[str] | None = None, + units: str | None = None, + **kwargs, + ): + super().__init__(**kwargs) + self.units = units def set_kpointsdata(self, kpointsdata): """Load the kpoints from a kpoint object. @@ -304,7 +323,7 @@ def _validate_bands_occupations(self, bands, occupations=None, labels=None): the_labels = [str(_) for _ in labels] else: raise ValidationError( - 'Band labels have an unrecognized type ({})' 'but should be a string or a list of strings'.format( + 'Band labels have an unrecognized type ({})but should be a string or a list of strings'.format( labels.__class__ ) ) diff --git a/src/aiida/orm/nodes/data/array/kpoints.py b/src/aiida/orm/nodes/data/array/kpoints.py index e7958970a4..83ba32f1fc 100644 --- a/src/aiida/orm/nodes/data/array/kpoints.py +++ b/src/aiida/orm/nodes/data/array/kpoints.py @@ -11,6 +11,8 @@ periodic crystal structure). """ +from __future__ import annotations + import typing as t import numpy @@ -38,15 +40,110 @@ class KpointsData(ArrayData): set_cell_from_structure methods. """ - class Model(ArrayData.Model): - labels: t.List[str] = MetadataField(description='Labels associated with the list of kpoints') - label_numbers: t.List[int] = MetadataField(description='Index of the labels in the list of kpoints') - mesh: t.List[int] = MetadataField(description='Mesh of kpoints') - offset: t.List[float] = MetadataField(description='Offset of kpoints') - cell: t.List[t.List[float]] = MetadataField(description='Unit cell of the crystal, in Angstroms') - pbc1: bool = MetadataField(description='True if the first lattice vector is periodic') - pbc2: bool = MetadataField(description='True if the second lattice vector is periodic') - pbc3: bool = MetadataField(description='True if the third lattice vector is periodic') + class AttributesModel(ArrayData.AttributesModel): + labels: t.Optional[t.List[str]] = MetadataField( + None, + description='Labels associated with the list of kpoints', + orm_to_model=lambda node, _: t.cast(KpointsData, node).base.attributes.get('labels', None), + ) + label_numbers: t.Optional[t.List[int]] = MetadataField( + None, + description='Index of the labels in the list of kpoints', + orm_to_model=lambda node, _: t.cast(KpointsData, node).base.attributes.get('label_numbers', None), + ) + cell: t.Optional[t.List[t.List[float]]] = MetadataField( + None, + description='Unit cell of the crystal, in Angstroms', + orm_to_model=lambda node, _: t.cast(KpointsData, node).base.attributes.get('cell', None), + ) + pbc1: t.Optional[bool] = MetadataField( + None, + description='Periodicity in the first lattice vector direction', + orm_to_model=lambda node, _: t.cast(KpointsData, node).pbc[0], + ) + pbc2: t.Optional[bool] = MetadataField( + None, + description='Periodicity in the second lattice vector direction', + orm_to_model=lambda node, _: t.cast(KpointsData, node).pbc[1], + ) + pbc3: t.Optional[bool] = MetadataField( + None, + description='Periodicity in the third lattice vector direction', + orm_to_model=lambda node, _: t.cast(KpointsData, node).pbc[2], + ) + mesh: t.Optional[t.List[int]] = MetadataField( + None, + description='Mesh of kpoints', + orm_to_model=lambda node, _: t.cast(KpointsData, node).base.attributes.get('mesh', None), + ) + offset: t.Optional[t.List[float]] = MetadataField( + None, + description='Offset of kpoints', + orm_to_model=lambda node, _: t.cast(KpointsData, node).base.attributes.get('offset', None), + ) + + def __init__( + self, + *, + labels: list[str] | None = None, + label_numbers: list[int] | None = None, + cell: list[list[float]] | None = None, + pbc1: bool | None = None, + pbc2: bool | None = None, + pbc3: bool | None = None, + mesh: list[int] | None = None, + offset: list[float] | None = None, + **kwargs, + ): + attributes = kwargs.get('attributes', {}) + labels = labels or attributes.pop('labels', None) + label_numbers = label_numbers or attributes.pop('label_numbers', None) + cell = cell or attributes.pop('cell', None) + pbc1 = pbc1 if pbc1 is not None else attributes.pop('pbc1', None) + pbc2 = pbc2 if pbc2 is not None else attributes.pop('pbc2', None) + pbc3 = pbc3 if pbc3 is not None else attributes.pop('pbc3', None) + mesh = mesh or attributes.pop('mesh', None) + offset = offset or attributes.pop('offset', None) + + arrays = kwargs.pop('arrays', None) + + super().__init__(**kwargs) + + if offset is not None and mesh is None: + raise ValueError('Cannot set an offset without a kpoints mesh') + + given_list_kwargs = any(kwarg is not None for kwarg in (labels, label_numbers, cell, pbc1, pbc2, pbc3)) + + if mesh is not None: + if arrays is not None or given_list_kwargs: + raise ValueError('When providing a kpoints mesh, only mesh and offset are allowed') + self.set_kpoints_mesh(mesh, offset=offset) + + if arrays is not None: + if labels is not None and label_numbers is not None: + if len(labels) != len(label_numbers): + raise ValueError('Labels and label numbers must have the same length') + label_list = list(zip(label_numbers, labels)) + else: + label_list = None + + pbc = (pbc1 or False, pbc2 or False, pbc3 or False) + + if cell is not None: + self.set_cell(cell, pbc) + else: + self.pbc = pbc + + if isinstance(arrays, dict): + kpoints = arrays.get('kpoints', None) + if kpoints is None: + raise ValueError("When providing a dict of arrays, it must contain the key 'kpoints'") + arrays = kpoints + + self.set_kpoints(arrays, labels=label_list) + + elif given_list_kwargs: + raise ValueError('Missing kpoints list') def get_description(self): """Returns a string with infos retrieved from kpoints node's properties. @@ -99,8 +196,11 @@ def pbc(self): :return: a tuple of three booleans, each one tells if there are periodic boundary conditions for the i-th real-space direction (i=1,2,3) """ - # return copy.deepcopy(self._pbc) - return (self.base.attributes.get('pbc1'), self.base.attributes.get('pbc2'), self.base.attributes.get('pbc3')) + return ( + self.base.attributes.get('pbc1', False), + self.base.attributes.get('pbc2', False), + self.base.attributes.get('pbc3', False), + ) @pbc.setter def pbc(self, value): @@ -194,7 +294,7 @@ def set_cell_from_structure(self, structuredata): if not isinstance(structuredata, StructureData): raise ValueError( - 'An instance of StructureData should be passed to ' 'the KpointsData, found instead {}'.format( + 'An instance of StructureData should be passed to the KpointsData, found instead {}'.format( structuredata.__class__ ) ) @@ -333,12 +433,8 @@ def _dimension(self): """Dimensionality of the structure, found from its pbc (i.e. 1 if it's a 1D structure, 2 if its 2D, 3 if it's 3D ...). :return dimensionality: 0, 1, 2 or 3 - :note: will return 3 if pbc has not been set beforehand """ - try: - return sum(self.pbc) - except AttributeError: - return 3 + return sum(self.pbc) def _validate_kpoints_weights(self, kpoints, weights): """Validate the list of kpoints and of weights before storage. @@ -355,9 +451,9 @@ def _validate_kpoints_weights(self, kpoints, weights): kpoints = numpy.array([[0.0, 0.0, 0.0]]) else: raise ValueError( - 'empty kpoints list is valid only in zero dimension' - '; instead here with have {} dimensions' - ''.format(self._dimension) + 'empty kpoints list is valid only in zero dimension; instead here with have {} dimensions'.format( + self._dimension + ) ) if len(kpoints.shape) <= 1: @@ -373,8 +469,9 @@ def _validate_kpoints_weights(self, kpoints, weights): if kpoints.shape[1] < self._dimension: raise ValueError( - 'In a system which has {0} dimensions, kpoint need' - 'more than {0} coordinates (found instead {1})'.format(self._dimension, kpoints.shape[1]) + 'In a system which has {0} dimensions, kpoint needmore than {0} coordinates (found instead {1})'.format( + self._dimension, kpoints.shape[1] + ) ) if weights is not None: diff --git a/src/aiida/orm/nodes/data/array/projection.py b/src/aiida/orm/nodes/data/array/projection.py index 881ac727c2..614810316a 100644 --- a/src/aiida/orm/nodes/data/array/projection.py +++ b/src/aiida/orm/nodes/data/array/projection.py @@ -30,6 +30,8 @@ class ProjectionData(OrbitalData, ArrayData): < orbital | Bloch wavefunction (s,n,k) > """ + _SKIP_MODEL_INHERITANCE_CHECK = True + def _check_projections_bands(self, projection_array): """Checks to make sure that a reference bandsdata is already set, and that projection_array is of the same shape of the bands data @@ -283,5 +285,5 @@ def set_orbitals(self, **kwargs): # type: ignore[override] If used will raise a NotImplementedError """ raise NotImplementedError( - 'You cannot set orbitals using this class!' ' This class is for setting orbitals and ' ' projections only!' + 'You cannot set orbitals using this class! This class is for setting orbitals and projections only!' ) diff --git a/src/aiida/orm/nodes/data/array/trajectory.py b/src/aiida/orm/nodes/data/array/trajectory.py index 3d6356ebd2..5b5c98eb5b 100644 --- a/src/aiida/orm/nodes/data/array/trajectory.py +++ b/src/aiida/orm/nodes/data/array/trajectory.py @@ -8,6 +8,8 @@ ########################################################################### """AiiDA class to deal with crystal structure trajectories.""" +from __future__ import annotations + import collections.abc from typing import List @@ -23,13 +25,12 @@ class TrajectoryData(ArrayData): possibly with velocities). """ - class Model(ArrayData.Model): - units_positions: str = MetadataField(alias='units|positions', description='Unit of positions') - units_times: str = MetadataField(alias='units|times', description='Unit of time') + class AttributesModel(ArrayData.AttributesModel): symbols: List[str] = MetadataField(description='List of symbols') def __init__(self, structurelist=None, **kwargs): super().__init__(**kwargs) + if structurelist is not None: self.set_structurelist(structurelist) @@ -70,7 +71,7 @@ def _internal_validate(self, stepids, cells, symbols, positions, times, velociti numatoms = len(symbols) if positions.shape != (numsteps, numatoms, 3): raise ValueError( - 'TrajectoryData.positions must have shape (s,n,3), ' 'with s=number of steps and n=number of symbols' + 'TrajectoryData.positions must have shape (s,n,3), with s=number of steps and n=number of symbols' ) if times is not None: if times.shape != (numsteps,): @@ -371,7 +372,7 @@ def get_step_structure(self, index, custom_kinds=None): for k in custom_kinds: if not isinstance(k, Kind): raise TypeError( - 'Each element of the custom_kinds list must ' 'be a aiida.orm.nodes.data.structure.Kind object' + 'Each element of the custom_kinds list must be a aiida.orm.nodes.data.structure.Kind object' ) kind_names.append(k.name) if len(kind_names) != len(set(kind_names)): diff --git a/src/aiida/orm/nodes/data/array/xy.py b/src/aiida/orm/nodes/data/array/xy.py index aa994bbd30..dcbd31d702 100644 --- a/src/aiida/orm/nodes/data/array/xy.py +++ b/src/aiida/orm/nodes/data/array/xy.py @@ -18,6 +18,7 @@ import numpy as np from aiida.common.exceptions import NotExistent +from aiida.common.pydantic import MetadataField from .array import ArrayData @@ -47,6 +48,20 @@ class XyData(ArrayData): Y arrays, which can be considered functions of X. """ + class AttributesModel(ArrayData.AttributesModel): + x_name: str = MetadataField( + description='The name of the x array', + ) + x_units: str = MetadataField( + description='The units of the x array', + ) + y_names: Sequence[str] = MetadataField( + description='The names of the y arrays', + ) + y_units: Sequence[str] = MetadataField( + description='The units of the y arrays', + ) + def __init__( self, x_array: 'ndarray' | None = None, @@ -69,10 +84,16 @@ def __init__( :param y_names: The names of the y arrays. :param y_units: The units of the y arrays. """ + attributes = kwargs.get('attributes', {}) + x_name = x_name or attributes.pop('x_name', None) + x_units = x_units or attributes.pop('x_units', None) + y_names = y_names or attributes.pop('y_names', None) + y_units = y_units or attributes.pop('y_units', None) + super().__init__(**kwargs) if x_array is not None: - self.set_x(x_array, x_name, x_units) # type: ignore[arg-type] + self.set_x(x_array, x_name, x_units) self.set_y(y_arrays, y_names, y_units) # type: ignore[arg-type] @staticmethod diff --git a/src/aiida/orm/nodes/data/base.py b/src/aiida/orm/nodes/data/base.py index adc0f3a98e..081a28bb00 100644 --- a/src/aiida/orm/nodes/data/base.py +++ b/src/aiida/orm/nodes/data/base.py @@ -8,6 +8,8 @@ ########################################################################### """`Data` sub class to be used as a base for data containers that represent base python data types.""" +from __future__ import annotations + import typing as t from functools import singledispatch @@ -27,10 +29,10 @@ def to_aiida_type(value): class BaseType(Data): """`Data` sub class to be used as a base for data containers that represent base python data types.""" - class Model(Data.Model): + class AttributesModel(Data.AttributesModel): value: t.Any = MetadataField( ..., - title='Data value.', + title='Data value', description='The value of the data', ) @@ -40,9 +42,11 @@ def __init__(self, value=None, **kwargs): except AttributeError: raise RuntimeError('Derived class must define the `_type` class member') + value = value if value is not None else kwargs.get('attributes', {}).pop('value', None) + super().__init__(**kwargs) - self.value = value or self._type() + self.value = value if value is not None else self._type() @property def value(self): diff --git a/src/aiida/orm/nodes/data/bool.py b/src/aiida/orm/nodes/data/bool.py index 61c8e61d5e..2b91e34b37 100644 --- a/src/aiida/orm/nodes/data/bool.py +++ b/src/aiida/orm/nodes/data/bool.py @@ -10,6 +10,8 @@ import numpy +from aiida.common.pydantic import MetadataField + from .base import BaseType, to_aiida_type __all__ = ('Bool',) @@ -20,6 +22,12 @@ class Bool(BaseType): _type = bool + class AttributesModel(BaseType.AttributesModel): + value: bool = MetadataField( + title='Boolean value', + description='The value of the boolean', + ) + def __int__(self): return int(bool(self)) diff --git a/src/aiida/orm/nodes/data/cif.py b/src/aiida/orm/nodes/data/cif.py index 8421a617eb..827751fa92 100644 --- a/src/aiida/orm/nodes/data/cif.py +++ b/src/aiida/orm/nodes/data/cif.py @@ -9,7 +9,7 @@ """Tools for handling Crystallographic Information Files (CIF)""" import re -import typing as t +from typing import List, Optional from aiida.common.pydantic import MetadataField from aiida.common.utils import Capturing @@ -250,15 +250,21 @@ class CifData(SinglefileData): _values = None _ase = None - class Model(SinglefileData.Model): - formulae: t.Optional[t.List[str]] = MetadataField( - None, description='List of formulae contained in the CIF file.', exclude_to_orm=True + class AttributesModel(SinglefileData.AttributesModel): + formulae: Optional[List[str]] = MetadataField( + None, + description='List of formulae contained in the CIF file', + read_only=True, ) - spacegroup_numbers: t.Optional[t.List[str]] = MetadataField( - None, description='List of space group numbers of the structure.', exclude_to_orm=True + spacegroup_numbers: Optional[List[str]] = MetadataField( + None, + description='List of space group numbers of the structure', + read_only=True, ) - md5: t.Optional[str] = MetadataField( - None, description='MD5 checksum of the file contents.', exclude_to_orm=True + md5: Optional[str] = MetadataField( + None, + description='MD5 checksum of the file contents', + read_only=True, ) def __init__(self, ase=None, file=None, filename=None, values=None, scan_type=None, parse_policy=None, **kwargs): @@ -372,7 +378,7 @@ def get_or_create(cls, filename, use_first=False, store_cif=True): return (cifs[0], False) raise ValueError( - 'More than one copy of a CIF file ' 'with the same MD5 has been found in ' 'the DB. pks={}'.format( + 'More than one copy of a CIF file with the same MD5 has been found in the DB. pks={}'.format( ','.join([str(i.pk) for i in cifs]) ) ) diff --git a/src/aiida/orm/nodes/data/code/abstract.py b/src/aiida/orm/nodes/data/code/abstract.py index eb72649b9c..a37259ca55 100644 --- a/src/aiida/orm/nodes/data/code/abstract.py +++ b/src/aiida/orm/nodes/data/code/abstract.py @@ -19,7 +19,7 @@ from aiida.common import exceptions from aiida.common.folders import Folder from aiida.common.lang import type_check -from aiida.common.pydantic import MetadataField, get_metadata +from aiida.common.pydantic import MetadataField from aiida.orm import Computer from aiida.plugins import CalculationFactory @@ -43,44 +43,32 @@ class AbstractCode(Data, metaclass=abc.ABCMeta): _KEY_ATTRIBUTE_WRAP_CMDLINE_PARAMS: str = 'wrap_cmdline_params' _KEY_EXTRA_IS_HIDDEN: str = 'hidden' # Should become ``is_hidden`` once ``Code`` is dropped - class Model(Data.Model, defer_build=True): + class AttributesModel(Data.AttributesModel): """Model describing required information to create an instance.""" - label: str = MetadataField( - ..., - title='Label', - description='A unique label to identify the code by.', - short_name='-L', - ) - description: str = MetadataField( - '', - title='Description', - description='Human-readable description, ideally including version and compilation environment.', - short_name='-D', - ) default_calc_job_plugin: t.Optional[str] = MetadataField( None, title='Default `CalcJob` plugin', - description='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`).', + description='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)', short_name='-P', ) use_double_quotes: bool = MetadataField( False, title='Escape using double quotes', description='Whether the executable and arguments of the code in the submission script should be escaped ' - 'with single or double quotes.', + 'with single or double quotes', ) with_mpi: t.Optional[bool] = MetadataField( None, title='Run with MPI', description='Whether the executable should be run as an MPI program. This option can be left unspecified ' 'in which case `None` will be set and it is left up to the calculation job plugin or inputs ' - 'whether to run with MPI.', + 'whether to run with MPI', ) prepend_text: str = MetadataField( '', title='Prepend script', - description='Bash commands that should be prepended to the run line in all submit scripts for this code.', + description='Bash commands that should be prepended to the run line in all submit scripts for this code', option_cls=functools.partial( TemplateInteractiveOption, extension='.bash', @@ -92,7 +80,7 @@ class Model(Data.Model, defer_build=True): append_text: str = MetadataField( '', title='Append script', - description='Bash commands that should be appended to the run line in all submit scripts for this code.', + description='Bash commands that should be appended to the run line in all submit scripts for this code', option_cls=functools.partial( TemplateInteractiveOption, extension='.bash', @@ -102,15 +90,29 @@ class Model(Data.Model, defer_build=True): ), ) + class Model(Data.Model): + label: str = MetadataField( + ..., + title='Label', + description='A unique label to identify the code by', + short_name='-L', + ) + description: str = MetadataField( + '', + title='Description', + description='Human-readable description, ideally including version and compilation environment', + short_name='-D', + ) + def __init__( self, default_calc_job_plugin: str | None = None, append_text: str = '', prepend_text: str = '', - use_double_quotes: bool = False, + use_double_quotes: bool | None = False, with_mpi: bool | None = None, - is_hidden: bool = False, - wrap_cmdline_params: bool = False, + is_hidden: bool | None = False, + wrap_cmdline_params: bool | None = False, **kwargs, ): """Construct a new instance. @@ -124,7 +126,32 @@ def __init__( form a single string. This is required to enable support for Docker with the ``ContainerizedCode``. :param is_hidden: Whether the code is hidden. """ + + attributes = kwargs.get('attributes', {}) + default_calc_job_plugin = default_calc_job_plugin or attributes.pop( + self._KEY_ATTRIBUTE_DEFAULT_CALC_JOB_PLUGIN, + attributes.pop('default_calc_job_plugin', None), + ) + append_text = append_text or attributes.pop(self._KEY_ATTRIBUTE_APPEND_TEXT, '') + prepend_text = prepend_text or attributes.pop(self._KEY_ATTRIBUTE_PREPEND_TEXT, '') + use_double_quotes = ( + use_double_quotes + if use_double_quotes is not None + else attributes.pop(self._KEY_ATTRIBUTE_USE_DOUBLE_QUOTES, False) + ) + with_mpi = with_mpi if with_mpi is not None else attributes.pop(self._KEY_ATTRIBUTE_WITH_MPI, None) + wrap_cmdline_params = wrap_cmdline_params or attributes.pop(self._KEY_ATTRIBUTE_WRAP_CMDLINE_PARAMS, False) + is_hidden = ( + is_hidden if is_hidden is not None else kwargs.get('extras', {}).pop(self._KEY_EXTRA_IS_HIDDEN, False) + ) + wrap_cmdline_params = ( + wrap_cmdline_params + if wrap_cmdline_params is not None + else attributes.pop(self._KEY_ATTRIBUTE_WRAP_CMDLINE_PARAMS, False) + ) + super().__init__(**kwargs) + self.default_calc_job_plugin = default_calc_job_plugin self.append_text = append_text self.prepend_text = prepend_text @@ -369,21 +396,23 @@ def _prepare_yaml(self, *args, **kwargs): """Export code to a YAML file.""" import yaml - code_data = {} - sort = kwargs.get('sort', False) - - for key, field in self.Model.model_fields.items(): - if get_metadata(field, 'exclude_from_cli'): - continue - elif (orm_to_model := get_metadata(field, 'orm_to_model')) is None: - value = getattr(self, key) - else: - value = orm_to_model(self, pathlib.Path.cwd() / f'{self.label}') - - # If the attribute is not set, for example ``with_mpi`` do not export it - # so that there are no null-values in the resulting YAML file - code_data[key] = value - return yaml.dump(code_data, sort_keys=sort, encoding='utf-8'), {} + code_data = ( + { + 'label': self.label, + 'description': self.description, + } + | ({'computer': self.computer.label} if self.computer else {}) + | { + key: value + for key, value in self.orm_to_model_field_values( + repository_path=pathlib.Path.cwd() / f'{self.label}', + model=self.AttributesModel, + ).items() + if value is not None + } + ) + + return yaml.dump(code_data, sort_keys=kwargs.get('sort', False), encoding='utf-8'), {} def _prepare_yml(self, *args, **kwargs): """Also allow for export as .yml""" diff --git a/src/aiida/orm/nodes/data/code/containerized.py b/src/aiida/orm/nodes/data/code/containerized.py index 85fd539cfa..534c966375 100644 --- a/src/aiida/orm/nodes/data/code/containerized.py +++ b/src/aiida/orm/nodes/data/code/containerized.py @@ -30,21 +30,21 @@ class ContainerizedCode(InstalledCode): _KEY_ATTRIBUTE_ENGINE_COMMAND: str = 'engine_command' _KEY_ATTRIBUTE_IMAGE_NAME: str = 'image_name' - class Model(InstalledCode.Model): + class AttributesModel(InstalledCode.AttributesModel): """Model describing required information to create an instance.""" engine_command: str = MetadataField( ..., title='Engine command', description='The command to run the container. It must contain the placeholder {image_name} that will be ' - 'replaced with the `image_name`.', + 'replaced with the `image_name`', short_name='-E', priority=3, ) image_name: str = MetadataField( ..., title='Image name', - description='Name of the image container in which to the run the executable.', + description='Name of the image container in which to the run the executable', short_name='-I', priority=2, ) @@ -52,12 +52,25 @@ class Model(InstalledCode.Model): False, title='Wrap command line parameters', description='Whether all command line parameters to be passed to the engine command should be wrapped in ' - 'a double quotes to form a single argument. This should be set to `True` for Docker.', + 'a double quotes to form a single argument. This should be set to `True` for Docker', priority=1, ) - def __init__(self, engine_command: str, image_name: str, **kwargs): + def __init__( + self, + engine_command: str | None = None, + image_name: str | None = None, + **kwargs, + ): + attributes = kwargs.get('attributes', {}) + engine_command = engine_command or attributes.pop(self._KEY_ATTRIBUTE_ENGINE_COMMAND, None) + image_name = image_name or attributes.pop(self._KEY_ATTRIBUTE_IMAGE_NAME, None) + + if engine_command is None or image_name is None: + raise ValueError('Both `engine_command` and `image_name` must be provided.') + super().__init__(**kwargs) + self.engine_command = engine_command self.image_name = image_name diff --git a/src/aiida/orm/nodes/data/code/installed.py b/src/aiida/orm/nodes/data/code/installed.py index 3401f43ed2..9b297eadeb 100644 --- a/src/aiida/orm/nodes/data/code/installed.py +++ b/src/aiida/orm/nodes/data/code/installed.py @@ -19,8 +19,6 @@ import pathlib from typing import cast -from pydantic import field_serializer, field_validator - from aiida.common import exceptions from aiida.common.lang import type_check from aiida.common.log import override_log_level @@ -28,6 +26,7 @@ from aiida.orm import Computer from aiida.orm.entities import from_backend_entity +from ....utils.loaders import load_computer from .abstract import AbstractCode from .legacy import Code @@ -41,50 +40,53 @@ class InstalledCode(Code): _KEY_ATTRIBUTE_FILEPATH_EXECUTABLE: str = 'filepath_executable' _SKIP_MODEL_INHERITANCE_CHECK: bool = True - class Model(AbstractCode.Model): - """Model describing required information to create an instance.""" - - computer: str = MetadataField( # type: ignore[assignment] - ..., - title='Computer', - description='The remote computer on which the executable resides.', - orm_to_model=lambda node, _: cast('InstalledCode', node).computer.label, - short_name='-Y', - priority=2, - ) + class AttributesModel(AbstractCode.AttributesModel): filepath_executable: str = MetadataField( - ..., title='Filepath executable', - description='Filepath of the executable on the remote computer.', - orm_to_model=lambda node, _: str(cast('InstalledCode', node).filepath_executable), + description='Filepath of the executable on the remote computer', + orm_to_model=lambda node, _: str(cast(InstalledCode, node).filepath_executable), short_name='-X', priority=1, ) + computer: str = MetadataField( + title='Computer', + description='The label of the remote computer on which the executable resides', + short_name='-Y', + priority=2, + write_only=True, + orm_to_model=lambda node, _: cast(InstalledCode, node).computer.label, + model_to_orm=lambda model: load_computer(cast(InstalledCode.AttributesModel, model).computer), + ) - @field_validator('computer') - @classmethod - def validate_computer(cls, value: str) -> Computer: - """Override the validator for the ``label`` of the base class since uniqueness is defined on full label.""" - from aiida.orm import load_computer - - try: - return load_computer(value) - except exceptions.NotExistent as exception: - raise ValueError(exception) from exception - - @field_serializer('computer') - def serialize_computer(self, computer: Computer, _info): - return computer.label - - def __init__(self, computer: Computer, filepath_executable: str, **kwargs): + def __init__( + self, + computer: Computer | str | None = None, + filepath_executable: str | None = None, + **kwargs, + ): """Construct a new instance. - :param computer: The remote computer on which the executable is located. + :param computer: The remote computer (instance or label) on which the executable is located. :param filepath_executable: The absolute filepath of the executable on the remote computer. """ + computer = computer or kwargs.get('attributes', {}).pop('computer', None) + filepath = filepath_executable or kwargs.get('attributes', {}).pop( + self._KEY_ATTRIBUTE_FILEPATH_EXECUTABLE, None + ) + + if computer is None: + raise ValueError('The `computer` parameter must be provided.') + + if isinstance(computer, str): + computer = load_computer(computer) + + if filepath is None: + raise ValueError('The `filepath_executable` parameter must be provided.') + super().__init__(**kwargs) + self.computer = computer - self.filepath_executable = filepath_executable + self.filepath_executable = filepath def _validate(self): """Validate the instance by checking that a computer has been defined. diff --git a/src/aiida/orm/nodes/data/code/legacy.py b/src/aiida/orm/nodes/data/code/legacy.py index b462d8ad7c..5db575898f 100644 --- a/src/aiida/orm/nodes/data/code/legacy.py +++ b/src/aiida/orm/nodes/data/code/legacy.py @@ -39,7 +39,7 @@ class Code(AbstractCode): for the code to be run). """ - class Model(AbstractCode.Model): + class AttributesModel(AbstractCode.AttributesModel): prepend_text: str = MetadataField( '', description='The code that will be put in the scheduler script before the execution of the code', @@ -51,11 +51,24 @@ class Model(AbstractCode.Model): input_plugin: t.Optional[str] = MetadataField( description='The name of the input plugin to be used for this code' ) - local_executable: t.Optional[str] = MetadataField(description='Path to a local executable') - remote_exec_path: t.Optional[str] = MetadataField(description='Remote path to executable') - is_local: t.Optional[bool] = MetadataField(description='Whether the code is local or remote') + local_executable: t.Optional[str] = MetadataField( + description='Path to a local executable', + ) + remote_exec_path: t.Optional[str] = MetadataField( + description='Remote path to executable', + ) + is_local: t.Optional[bool] = MetadataField( + description='Whether the code is local or remote', + ) - def __init__(self, remote_computer_exec=None, local_executable=None, input_plugin_name=None, files=None, **kwargs): + def __init__( + self, + remote_computer_exec: t.Optional[str] = None, + local_executable: t.Optional[str] = None, + input_plugin_name: t.Optional[str] = None, + files: t.Optional[t.List[str]] = None, + **kwargs, + ): super().__init__(**kwargs) # The ``_EMIT_CODE_DEPRECATION_WARNING`` attribute is set in subclasses to avoid the deprecation message below @@ -220,7 +233,7 @@ def get_code_helper(cls, label, machinename=None, backend=None): elif query.count() > 1: codes = query.all(flat=True) retstr = f"There are multiple codes with label '{label}', having IDs: " - retstr += f"{', '.join(sorted([str(c.pk) for c in codes]))}.\n" + retstr += f'{", ".join(sorted([str(c.pk) for c in codes]))}.\n' retstr += 'Relabel them (using their ID), or refer to them with their ID.' raise MultipleObjectsError(retstr) else: @@ -333,7 +346,7 @@ def _validate(self): if self.is_local(): if not self.get_local_executable(): raise exceptions.ValidationError( - 'You have to set which file is the local executable ' 'using the set_exec_filename() method' + 'You have to set which file is the local executable using the set_exec_filename() method' ) if self.get_local_executable() not in self.base.repository.list_object_names(): raise exceptions.ValidationError( diff --git a/src/aiida/orm/nodes/data/code/portable.py b/src/aiida/orm/nodes/data/code/portable.py index 911855318e..a8b4b19362 100644 --- a/src/aiida/orm/nodes/data/code/portable.py +++ b/src/aiida/orm/nodes/data/code/portable.py @@ -21,6 +21,7 @@ import logging import pathlib +from typing import cast from aiida.common import exceptions from aiida.common.folders import Folder @@ -36,7 +37,7 @@ _LOGGER = logging.getLogger(__name__) -def _export_filpath_files_from_repo(portable_code: PortableCode, repository_path: pathlib.Path) -> str: +def _export_filepath_files_from_repo(portable_code: PortableCode, repository_path: pathlib.Path) -> str: for root, _, filenames in portable_code.base.repository.walk(): for filename in filenames: rel_path = str(root / filename) @@ -54,31 +55,33 @@ class PortableCode(Code): _KEY_ATTRIBUTE_FILEPATH_EXECUTABLE: str = 'filepath_executable' _SKIP_MODEL_INHERITANCE_CHECK: bool = True - class Model(AbstractCode.Model): + class AttributesModel(AbstractCode.AttributesModel): """Model describing required information to create an instance.""" filepath_executable: str = MetadataField( ..., title='Filepath executable', - description='Relative filepath of executable with directory of code files.', + description='Relative filepath of executable with directory of code files', short_name='-X', priority=1, - orm_to_model=lambda node, _: str(node.filepath_executable), # type: ignore[attr-defined] + orm_to_model=lambda node, _: str(cast(PortableCode, node).filepath_executable), ) filepath_files: str = MetadataField( - ..., title='Code directory', - description='Filepath to directory containing code files.', + description='Filepath to directory containing code files', short_name='-F', - is_attribute=False, priority=2, - orm_to_model=_export_filpath_files_from_repo, # type: ignore[arg-type] + write_only=True, + orm_to_model=lambda node, kwargs: _export_filepath_files_from_repo( + cast(PortableCode, node), + kwargs.get('repository_path', pathlib.Path.cwd() / f'{cast(PortableCode, node).label}'), + ), ) def __init__( self, - filepath_executable: FilePath, - filepath_files: FilePath, + filepath_executable: FilePath | None = None, + filepath_files: FilePath | None = None, **kwargs, ): """Construct a new instance. @@ -96,11 +99,21 @@ def __init__( :param filepath_executable: The relative filepath of the executable within the directory of uploaded files. :param filepath_files: The filepath to the directory containing all the files of the code. """ + attributes = kwargs.get('attributes', {}) + filepath_executable = filepath_executable or attributes.pop(self._KEY_ATTRIBUTE_FILEPATH_EXECUTABLE, None) + filepath_files = filepath_files or attributes.pop('filepath_files', None) + + if filepath_files is None or filepath_executable is None: + raise ValueError('Both `filepath_files` and `filepath_executable` must be provided.') + super().__init__(**kwargs) + type_check(filepath_files, (pathlib.PurePath, str)) + filepath_files_path = pathlib.Path(filepath_files) if not filepath_files_path.exists(): raise ValueError(f'The filepath `{filepath_files}` does not exist.') + if not filepath_files_path.is_dir(): raise ValueError(f'The filepath `{filepath_files}` is not a directory.') @@ -201,7 +214,7 @@ def _prepare_yaml(self, *args, **kwargs): """Export code to a YAML file.""" result = super()._prepare_yaml(*args, **kwargs)[0] target = pathlib.Path().cwd() / f'{self.label}' - _export_filpath_files_from_repo(self, target) + _export_filepath_files_from_repo(self, target) _LOGGER.info(f'Repository files for PortableCode <{self.pk}> dumped to folder `{target}`.') return result, {} diff --git a/src/aiida/orm/nodes/data/data.py b/src/aiida/orm/nodes/data/data.py index 56b3dcbdbb..1baba3afe5 100644 --- a/src/aiida/orm/nodes/data/data.py +++ b/src/aiida/orm/nodes/data/data.py @@ -8,6 +8,8 @@ ########################################################################### """Module with `Node` sub class `Data` to be used as a base class for data structures.""" +from __future__ import annotations + from typing import Dict, Optional from aiida.common import exceptions @@ -46,14 +48,20 @@ class Data(Node): _storable = True _unstorable_message = 'storing for this node has been disabled' - class Model(Node.Model): + class AttributesModel(Node.AttributesModel): source: Optional[dict] = MetadataField( - None, description='Source of the data.', is_subscriptable=True, exclude_from_cli=True + None, + description='Source of the data', + read_only=True, ) def __init__(self, *args, source=None, **kwargs): """Construct a new instance, setting the ``source`` attribute if provided as a keyword argument.""" + + source = kwargs.get('attributes', {}).pop('source', source) + super().__init__(*args, **kwargs) + if source is not None: self.source = source @@ -113,7 +121,7 @@ def source(self, source): raise ValueError('Source must be supplied as a dictionary') unknown_attrs = tuple(set(source.keys()) - set(self._source_attributes)) if unknown_attrs: - raise KeyError(f"Unknown source parameters: {', '.join(unknown_attrs)}") + raise KeyError(f'Unknown source parameters: {", ".join(unknown_attrs)}') self.base.attributes.set('source', source) @@ -161,13 +169,13 @@ def _exportcontent(self, fileformat, main_file_name='', **kwargs): except KeyError: if exporters.keys(): raise ValueError( - 'The format {} is not implemented for {}. ' 'Currently implemented are: {}.'.format( + 'The format {} is not implemented for {}. Currently implemented are: {}.'.format( fileformat, self.__class__.__name__, ','.join(exporters.keys()) ) ) else: raise ValueError( - 'The format {} is not implemented for {}. ' 'No formats are implemented yet.'.format( + 'The format {} is not implemented for {}. No formats are implemented yet.'.format( fileformat, self.__class__.__name__ ) ) @@ -269,13 +277,13 @@ def importstring(self, inputstring, fileformat, **kwargs): except KeyError: if importers.keys(): raise ValueError( - 'The format {} is not implemented for {}. ' 'Currently implemented are: {}.'.format( + 'The format {} is not implemented for {}. Currently implemented are: {}.'.format( fileformat, self.__class__.__name__, ','.join(importers.keys()) ) ) else: raise ValueError( - 'The format {} is not implemented for {}. ' 'No formats are implemented yet.'.format( + 'The format {} is not implemented for {}. No formats are implemented yet.'.format( fileformat, self.__class__.__name__ ) ) @@ -326,13 +334,13 @@ def convert(self, object_format=None, *args): except KeyError: if converters.keys(): raise ValueError( - 'The format {} is not implemented for {}. ' 'Currently implemented are: {}.'.format( + 'The format {} is not implemented for {}. Currently implemented are: {}.'.format( object_format, self.__class__.__name__, ','.join(converters.keys()) ) ) else: raise ValueError( - 'The format {} is not implemented for {}. ' 'No formats are implemented yet.'.format( + 'The format {} is not implemented for {}. No formats are implemented yet.'.format( object_format, self.__class__.__name__ ) ) diff --git a/src/aiida/orm/nodes/data/dict.py b/src/aiida/orm/nodes/data/dict.py index 99673de6fd..25dd287411 100644 --- a/src/aiida/orm/nodes/data/dict.py +++ b/src/aiida/orm/nodes/data/dict.py @@ -13,6 +13,8 @@ import copy import typing as t +import pydantic as pdt + from aiida.common import exceptions from aiida.common.pydantic import MetadataField @@ -50,11 +52,17 @@ class Dict(Data): Finally, all dictionary mutations will be forbidden once the node is stored. """ - class Model(Data.Model): + class AttributesModel(Data.AttributesModel): + model_config = pdt.ConfigDict( + arbitrary_types_allowed=True, + json_schema_extra={ + 'additionalProperties': True, + }, + ) + value: t.Dict[str, t.Any] = MetadataField( - description='Dictionary content.', - is_attribute=False, - is_subscriptable=True, + description='The dictionary content', + write_only=True, ) def __init__(self, value=None, **kwargs): @@ -67,8 +75,10 @@ def __init__(self, value=None, **kwargs): :param value: dictionary to initialise the ``Dict`` node from """ - dictionary = value or kwargs.pop('dict', None) + dictionary = value or kwargs.pop('dict', None) or kwargs.get('attributes', {}).pop('value', None) + super().__init__(**kwargs) + if dictionary: self.set_dict(dictionary) diff --git a/src/aiida/orm/nodes/data/enum.py b/src/aiida/orm/nodes/data/enum.py index 1c6fb41c79..c39c9c8616 100644 --- a/src/aiida/orm/nodes/data/enum.py +++ b/src/aiida/orm/nodes/data/enum.py @@ -19,6 +19,7 @@ class Color(Enum): from enum import Enum from plumpy.loaders import get_object_loader +from pydantic import computed_field from aiida.common.lang import type_check from aiida.common.pydantic import MetadataField @@ -50,15 +51,41 @@ class of the enumeration) in the ``KEY_NAME``, ``KEY_VALUE`` and ``KEY_IDENTIFIE KEY_VALUE = 'value' KEY_IDENTIFIER = 'identifier' - class Model(Data.Model): - member: Enum = MetadataField( - description='The member name.', - orm_to_model=lambda node, _: node.get_member(), # type: ignore[attr-defined] + class AttributesModel(Data.AttributesModel): + member: t.Optional[Enum] = MetadataField( + None, + description='The member name', + orm_to_model=lambda node, _: t.cast(EnumData, node).get_member(), + write_only=True, ) - def __init__(self, member: Enum, *args, **kwargs): + @computed_field # type: ignore[prop-decorator] + @property + def name(self) -> str: + """Return the member name.""" + return self.member.name if self.member is not None else '' + + @computed_field # type: ignore[prop-decorator] + @property + def value(self) -> t.Optional[t.Any]: + """Return the member value.""" + return self.member.value if self.member is not None else None + + @computed_field # type: ignore[prop-decorator] + @property + def identifier(self) -> str: + """Return the member identifier.""" + return get_object_loader().identify_object(self.member.__class__) + + def __init__(self, member: t.Optional[Enum] = None, *args, **kwargs): """Construct the node for the to enum member that is to be wrapped.""" + + attributes: dict = kwargs.get('attributes', {}) + member = member or attributes.pop('member', None) + attributes.clear() # we only need the member to construct the node + type_check(member, Enum) + super().__init__(*args, **kwargs) data = { diff --git a/src/aiida/orm/nodes/data/float.py b/src/aiida/orm/nodes/data/float.py index 88e535e067..7c0fdf7b75 100644 --- a/src/aiida/orm/nodes/data/float.py +++ b/src/aiida/orm/nodes/data/float.py @@ -10,6 +10,8 @@ import numbers +from aiida.common.pydantic import MetadataField + from .base import to_aiida_type from .numeric import NumericType @@ -21,6 +23,12 @@ class Float(NumericType): _type = float + class AttributesModel(NumericType.AttributesModel): + value: float = MetadataField( + title='Float value', + description='The value of the float', + ) + @to_aiida_type.register(numbers.Real) def _(value): diff --git a/src/aiida/orm/nodes/data/int.py b/src/aiida/orm/nodes/data/int.py index 43ffa918c3..2e30bdcb17 100644 --- a/src/aiida/orm/nodes/data/int.py +++ b/src/aiida/orm/nodes/data/int.py @@ -8,9 +8,13 @@ ########################################################################### """`Data` sub class to represent an integer value.""" +from __future__ import annotations + import numbers -from .base import to_aiida_type +from aiida.common.pydantic import MetadataField + +from .base import BaseType, to_aiida_type from .numeric import NumericType __all__ = ('Int',) @@ -21,6 +25,12 @@ class Int(NumericType): _type = int + class AttributesModel(BaseType.AttributesModel): + value: int = MetadataField( + title='Integer value', + description='The value of the integer', + ) + @to_aiida_type.register(numbers.Integral) def _(value): diff --git a/src/aiida/orm/nodes/data/jsonable.py b/src/aiida/orm/nodes/data/jsonable.py index 24309abd72..1cd4e13579 100644 --- a/src/aiida/orm/nodes/data/jsonable.py +++ b/src/aiida/orm/nodes/data/jsonable.py @@ -4,7 +4,7 @@ import json import typing -from pydantic import ConfigDict +from pydantic import ConfigDict, WithJsonSchema, computed_field from aiida.common.pydantic import MetadataField @@ -50,12 +50,52 @@ class JsonableData(Data): environment, or an ``ImportError`` will be raised. """ - class Model(Data.Model): - model_config = ConfigDict(arbitrary_types_allowed=True) - obj: JsonSerializableProtocol = MetadataField(description='The JSON-serializable object.') - - def __init__(self, obj: JsonSerializableProtocol, *args, **kwargs): + class AttributesModel(Data.AttributesModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + json_schema_extra={ + 'additionalProperties': True, + }, + ) + + obj: typing.Annotated[ + JsonSerializableProtocol, + WithJsonSchema( + { + 'type': 'object', + 'title': 'JSON-serializable object', + 'description': 'The JSON-serializable object', + } + ), + MetadataField( + description='The JSON-serializable object', + orm_to_model=lambda node, _: typing.cast(JsonableData, node).obj, + write_only=True, + ), + ] + + @computed_field( # type: ignore[prop-decorator] + title='Class name', + alias='@class', + description='The class name of the wrapped object', + ) + @property + def the_class(self) -> str: + return self.obj.__class__.__name__ + + @computed_field( # type: ignore[prop-decorator] + title='Module name', + alias='@module', + description='The module name of the wrapped object', + ) + @property + def the_module(self) -> str: + return self.obj.__class__.__module__ + + def __init__(self, obj: typing.Optional[JsonSerializableProtocol] = None, *args, **kwargs): """Construct the node for the to be wrapped object.""" + obj = obj or kwargs.get('attributes', {}).pop('obj', None) + if obj is None: raise TypeError('the `obj` argument cannot be `None`.') diff --git a/src/aiida/orm/nodes/data/list.py b/src/aiida/orm/nodes/data/list.py index f36dc70a8e..09250d2d50 100644 --- a/src/aiida/orm/nodes/data/list.py +++ b/src/aiida/orm/nodes/data/list.py @@ -25,7 +25,7 @@ class List(Data, MutableSequence): _LIST_KEY = 'list' - class Model(Data.Model): + class AttributesModel(Data.AttributesModel): value: t.List[t.Any] = MetadataField( description='Content of the data', ) @@ -35,7 +35,7 @@ def __init__(self, value=None, **kwargs): :param value: list to initialise the ``List`` node from """ - data = value or kwargs.pop('list', []) + data = value or kwargs.pop('list', []) or kwargs.get('attributes', {}).pop('value', []) super().__init__(**kwargs) self.set_list(data) diff --git a/src/aiida/orm/nodes/data/remote/base.py b/src/aiida/orm/nodes/data/remote/base.py index 8bae0fed02..4623f61977 100644 --- a/src/aiida/orm/nodes/data/remote/base.py +++ b/src/aiida/orm/nodes/data/remote/base.py @@ -13,7 +13,7 @@ import logging import os from pathlib import Path -from typing import Union +from typing import Optional from aiida.common.pydantic import MetadataField from aiida.orm import AuthInfo @@ -34,14 +34,16 @@ class RemoteData(Data): KEY_EXTRA_CLEANED = 'cleaned' - class Model(Data.Model): - remote_path: Union[str, None] = MetadataField( + class AttributesModel(Data.AttributesModel): + remote_path: Optional[str] = MetadataField( + None, title='Remote path', - description='Filepath on the remote computer.', + description='Filepath on the remote computer', orm_to_model=lambda node, _: node.get_remote_path(), ) - def __init__(self, remote_path: Union[str, None] = None, **kwargs): + def __init__(self, remote_path: Optional[str] = None, **kwargs): + remote_path = remote_path or kwargs.get('attributes', {}).pop('remote_path', None) super().__init__(**kwargs) if remote_path is not None: self.set_remote_path(remote_path) diff --git a/src/aiida/orm/nodes/data/remote/stash/base.py b/src/aiida/orm/nodes/data/remote/stash/base.py index 06a59269d5..e97e31744f 100644 --- a/src/aiida/orm/nodes/data/remote/stash/base.py +++ b/src/aiida/orm/nodes/data/remote/stash/base.py @@ -8,6 +8,8 @@ ########################################################################### """Data plugin that models an archived folder on a remote computer.""" +from typing import Optional + from aiida.common.datastructures import StashMode from aiida.common.lang import type_check from aiida.common.pydantic import MetadataField @@ -36,15 +38,23 @@ class RemoteStashData(Data): _storable = False - class Model(Data.Model): - stash_mode: StashMode = MetadataField(description='The mode with which the data was stashed') + class AttributesModel(Data.AttributesModel): + stash_mode: StashMode = MetadataField( + description='The mode with which the data was stashed', + ) - def __init__(self, stash_mode: StashMode, **kwargs): + def __init__(self, stash_mode: Optional[StashMode] = None, **kwargs): """Construct a new instance :param stash_mode: the stashing mode with which the data was stashed on the remote. """ + stash_mode = stash_mode or kwargs.get('attributes', {}).pop('stash_mode', None) + + if stash_mode is None: + raise ValueError('the `stash_mode` parameter must be specified.') + super().__init__(**kwargs) + self.stash_mode = stash_mode @property diff --git a/src/aiida/orm/nodes/data/remote/stash/compress.py b/src/aiida/orm/nodes/data/remote/stash/compress.py index 70fadc3cc2..4eb6faead2 100644 --- a/src/aiida/orm/nodes/data/remote/stash/compress.py +++ b/src/aiida/orm/nodes/data/remote/stash/compress.py @@ -8,7 +8,7 @@ ########################################################################### """Data plugin that models a stashed folder on a remote computer.""" -from typing import List, Tuple, Union +from typing import List, Optional, Tuple, Union from aiida.common.datastructures import StashMode from aiida.common.lang import type_check @@ -24,7 +24,7 @@ class RemoteStashCompressedData(RemoteStashData): _storable = True - class Model(RemoteStashData.Model): + class AttributesModel(RemoteStashData.AttributesModel): target_basepath: str = MetadataField( description='The the target basepath', ) @@ -37,10 +37,10 @@ class Model(RemoteStashData.Model): def __init__( self, - stash_mode: StashMode, - target_basepath: str, - source_list: List, - dereference: bool, + stash_mode: Optional[StashMode] = None, + target_basepath: Optional[str] = None, + source_list: Optional[List] = None, + dereference: Optional[bool] = None, **kwargs, ): """Construct a new instance @@ -48,8 +48,26 @@ def __init__( :param stash_mode: the stashing mode with which the data was stashed on the remote. :param target_basepath: absolute path to place the compressed file (path+filename). :param source_list: the list of source files. + :param dereference: whether to dereference symlinks when compressing. """ + + attributes = kwargs.get('attributes', {}) + stash_mode = stash_mode or attributes.pop('stash_mode', None) + target_basepath = target_basepath or attributes.pop('target_basepath', None) + source_list = source_list or attributes.pop('source_list', None) + dereference = dereference if dereference is not None else attributes.pop('dereference', None) + + if stash_mode is None: + raise ValueError('the `stash_mode` parameter must be specified.') + if target_basepath is None: + raise ValueError('the `target_basepath` parameter must be specified.') + if source_list is None: + raise ValueError('the `source_list` parameter must be specified.') + if dereference is None: + raise ValueError('the `dereference` parameter must be specified.') + super().__init__(stash_mode, **kwargs) + self.target_basepath = target_basepath self.source_list = source_list self.dereference = dereference diff --git a/src/aiida/orm/nodes/data/remote/stash/custom.py b/src/aiida/orm/nodes/data/remote/stash/custom.py index d46cbc7ae1..ceb20dc257 100644 --- a/src/aiida/orm/nodes/data/remote/stash/custom.py +++ b/src/aiida/orm/nodes/data/remote/stash/custom.py @@ -8,7 +8,7 @@ ########################################################################### """Data plugin that models a stashed folder on a remote computer.""" -from typing import List, Tuple, Union +from typing import List, Optional, Tuple, Union from aiida.common.datastructures import StashMode from aiida.common.lang import type_check @@ -24,21 +24,40 @@ class RemoteStashCustomData(RemoteStashData): _storable = True - class Model(RemoteStashData.Model): - target_basepath: str = MetadataField(description='The the target basepath') - source_list: List[str] = MetadataField(description='The list of source files that were stashed') + class AttributesModel(RemoteStashData.AttributesModel): + target_basepath: str = MetadataField( + description='The the target basepath', + ) + source_list: List[str] = MetadataField( + description='The list of source files that were stashed', + ) def __init__( self, - stash_mode: StashMode, - target_basepath: str, - source_list: List, + stash_mode: Optional[StashMode] = None, + target_basepath: Optional[str] = None, + source_list: Optional[List] = None, **kwargs, ): """Construct a new instance :param stash_mode: the stashing mode with which the data was stashed on the remote. + :param target_basepath: the target basepath. + :param source_list: the list of source files. """ + + attributes = kwargs.get('attributes', {}) + stash_mode = stash_mode or attributes.pop('stash_mode', None) + target_basepath = target_basepath or attributes.pop('target_basepath', None) + source_list = source_list or attributes.pop('source_list', None) + + if stash_mode is None: + raise ValueError('the `stash_mode` parameter must be specified.') + if target_basepath is None: + raise ValueError('the `target_basepath` parameter must be specified.') + if source_list is None: + raise ValueError('the `source_list` parameter must be specified.') + super().__init__(stash_mode, **kwargs) self.target_basepath = target_basepath diff --git a/src/aiida/orm/nodes/data/remote/stash/folder.py b/src/aiida/orm/nodes/data/remote/stash/folder.py index 22afd57491..8c5131ed03 100644 --- a/src/aiida/orm/nodes/data/remote/stash/folder.py +++ b/src/aiida/orm/nodes/data/remote/stash/folder.py @@ -8,7 +8,7 @@ ########################################################################### """Data plugin that models a stashed folder on a remote computer.""" -from typing import List, Tuple, Union +from typing import List, Optional, Tuple, Union from aiida.common.datastructures import StashMode from aiida.common.lang import type_check @@ -27,18 +27,37 @@ class RemoteStashFolderData(RemoteStashData): _storable = True - class Model(RemoteStashData.Model): + class AttributesModel(RemoteStashData.AttributesModel): target_basepath: str = MetadataField(description='The the target basepath') source_list: List[str] = MetadataField(description='The list of source files that were stashed') - def __init__(self, stash_mode: StashMode, target_basepath: str, source_list: List, **kwargs): + def __init__( + self, + stash_mode: Optional[StashMode] = None, + target_basepath: Optional[str] = None, + source_list: Optional[List] = None, + **kwargs, + ): """Construct a new instance :param stash_mode: the stashing mode with which the data was stashed on the remote. :param target_basepath: the target basepath. :param source_list: the list of source files. """ + attributes = kwargs.get('attributes', {}) + stash_mode = stash_mode or attributes.pop('stash_mode', None) + target_basepath = target_basepath or attributes.pop('target_basepath', None) + source_list = source_list or attributes.pop('source_list', None) + + if stash_mode is None: + raise ValueError('the `stash_mode` parameter must be specified.') + if target_basepath is None: + raise ValueError('the `target_basepath` parameter must be specified.') + if source_list is None: + raise ValueError('the `source_list` parameter must be specified.') + super().__init__(stash_mode, **kwargs) + self.target_basepath = target_basepath self.source_list = source_list diff --git a/src/aiida/orm/nodes/data/singlefile.py b/src/aiida/orm/nodes/data/singlefile.py index 742a5702c6..9b169b0848 100644 --- a/src/aiida/orm/nodes/data/singlefile.py +++ b/src/aiida/orm/nodes/data/singlefile.py @@ -30,15 +30,29 @@ class SinglefileData(Data): DEFAULT_FILENAME = 'file.txt' - class Model(Data.Model): - content: bytes = MetadataField( - description='The file content.', - model_to_orm=lambda model: io.BytesIO(model.content), # type: ignore[attr-defined] + class AttributesModel(Data.AttributesModel): + filename: str = MetadataField( + 'file.txt', + description='The name of the stored file', + orm_to_model=lambda node, _: t.cast(SinglefileData, node).base.attributes.get('filename', 'file.txt'), ) - filename: t.Optional[str] = MetadataField(None, description='The filename. Defaults to `file.txt`.') + content: t.Optional[str] = MetadataField( + None, + description='The file content', + model_to_orm=lambda model: t.cast(SinglefileData.AttributesModel, model).content_as_bytes(), + write_only=True, + ) + + def content_as_bytes(self) -> t.IO | None: + """Return the content as bytes. + + :return: the content as bytes + :raises ValueError: if the content is not set + """ + return io.BytesIO(self.content.encode()) if self.content else None @classmethod - def from_string(cls, content: str, filename: str | pathlib.Path | None = None, **kwargs: t.Any) -> 'SinglefileData': + def from_string(cls, content: str, filename: str | pathlib.Path | None = None, **kwargs: t.Any) -> SinglefileData: """Construct a new instance and set ``content`` as its contents. :param content: The content as a string. @@ -47,9 +61,7 @@ def from_string(cls, content: str, filename: str | pathlib.Path | None = None, * return cls(io.StringIO(content), filename, **kwargs) @classmethod - def from_bytes( - cls, content: bytes, filename: str | pathlib.Path | None = None, **kwargs: t.Any - ) -> 'SinglefileData': + def from_bytes(cls, content: bytes, filename: str | pathlib.Path | None = None, **kwargs: t.Any) -> SinglefileData: """Construct a new instance and set ``content`` as its contents. :param content: The content as bytes. @@ -70,6 +82,11 @@ def __init__( Hint: Pass io.BytesIO(b"my string") to construct the SinglefileData directly from a string. :param filename: specify filename to use (defaults to name of provided file). """ + + attributes = kwargs.get('attributes', {}) + filename = filename or attributes.pop('filename', None) + content = content or attributes.pop('content', None) + super().__init__(**kwargs) if file is not None and content is not None: @@ -196,19 +213,34 @@ def set_file(self, file: str | pathlib.Path | t.IO, filename: str | pathlib.Path self.base.attributes.set('filename', key) def _validate(self) -> bool: - """Ensure that there is one object stored in the repository, whose key matches value set for `filename` attr.""" + """Validate the node before storing. + + This check ensures that there is one object stored in the repository, + whose key matches the value set for the `filename` attribute. If no + `filename` attribute is set, it will be set to the key of the stored + object. If there are no objects stored, a ValidationError is raised. + + :return: True if the node is valid + :raises ValidationError: if the node is not valid + """ super()._validate() + objects = self.base.repository.list_object_names() + try: filename = self.filename except AttributeError: - raise exceptions.ValidationError('the `filename` attribute is not set.') - - objects = self.base.repository.list_object_names() + if not objects: + raise exceptions.ValidationError('the `filename` attribute is not set.') + if len(objects) > 1: + raise exceptions.ValidationError(f'multiple repository files {objects} found.') + filename = objects[0] + if self.base.repository.get_object(filename).is_dir(): + raise exceptions.ValidationError(f'repository object `{filename}` is a directory.') if [filename] != objects: raise exceptions.ValidationError( - f'respository files {objects} do not match the `filename` attribute `{filename}`.' + f'repository files {objects} do not match the `filename` attribute `{filename}`.' ) return True diff --git a/src/aiida/orm/nodes/data/str.py b/src/aiida/orm/nodes/data/str.py index 66b631ef7c..f9029a915b 100644 --- a/src/aiida/orm/nodes/data/str.py +++ b/src/aiida/orm/nodes/data/str.py @@ -8,6 +8,8 @@ ########################################################################### """`Data` sub class to represent a string value.""" +from aiida.common.pydantic import MetadataField + from .base import BaseType, to_aiida_type __all__ = ('Str',) @@ -18,6 +20,12 @@ class Str(BaseType): _type = str + class AttributesModel(BaseType.AttributesModel): + value: str = MetadataField( + title='String value', + description='The value of the string', + ) + @to_aiida_type.register(str) def _(value): diff --git a/src/aiida/orm/nodes/data/structure.py b/src/aiida/orm/nodes/data/structure.py index 0e3e09ef5b..9dccedffb0 100644 --- a/src/aiida/orm/nodes/data/structure.py +++ b/src/aiida/orm/nodes/data/structure.py @@ -10,12 +10,16 @@ functions to operate on them. """ +from __future__ import annotations + import copy import functools import itertools import json import typing as t +from pydantic import field_validator + from aiida.common.constants import elements from aiida.common.exceptions import UnsupportedSpeciesError from aiida.common.pydantic import MetadataField @@ -503,7 +507,7 @@ def get_symbols_string(symbols, weights): pieces.append(f'{symbol}{weight:4.2f}') if has_vacancies(weights): pieces.append(f'X{1.0 - sum(weights):4.2f}') - return f"{{{''.join(sorted(pieces))}}}" + return f'{{{"".join(sorted(pieces))}}}' def has_vacancies(weights): @@ -684,13 +688,39 @@ class StructureData(Data): _dimensionality_label = {0: '', 1: 'length', 2: 'surface', 3: 'volume'} _internal_kind_tags = None - class Model(Data.Model): - pbc1: bool = MetadataField(description='Whether periodic in the a direction') - pbc2: bool = MetadataField(description='Whether periodic in the b direction') - pbc3: bool = MetadataField(description='Whether periodic in the c direction') - cell: t.List[t.List[float]] = MetadataField(description='The cell parameters') - kinds: t.Optional[t.List[dict]] = MetadataField(description='The kinds of atoms') - sites: t.Optional[t.List[dict]] = MetadataField(description='The atomic sites') + class AttributesModel(Data.AttributesModel): + pbc1: bool = MetadataField( + False, + description='Whether periodic in the a direction', + ) + pbc2: bool = MetadataField( + False, + description='Whether periodic in the b direction', + ) + pbc3: bool = MetadataField( + False, + description='Whether periodic in the c direction', + ) + cell: t.Optional[t.List[t.List[float]]] = MetadataField( + None, + description='The cell parameters', + ) + kinds: t.List[dict] = MetadataField( + description='The kinds of atoms', + ) + sites: t.List[dict] = MetadataField( + description='The atomic sites', + ) + + @field_validator('kinds', mode='before') + @classmethod + def _validate_kinds(cls, value: list[Kind | dict[str, t.Any]]) -> list[t.Dict]: + return [kind.get_raw() if isinstance(kind, Kind) else kind for kind in value] + + @field_validator('sites', mode='before') + @classmethod + def _validate_sites(cls, value: list[Site | dict[str, t.Any]]) -> list[t.Dict]: + return [site.get_raw() if isinstance(site, Site) else site for site in value] def __init__( self, @@ -700,15 +730,23 @@ def __init__( pymatgen=None, pymatgen_structure=None, pymatgen_molecule=None, - pbc1=None, - pbc2=None, - pbc3=None, - kinds=None, - sites=None, + pbc1: bool | None = None, + pbc2: bool | None = None, + pbc3: bool | None = None, + kinds: list[Kind | dict[str, t.Any]] | None = None, + sites: list[Site | dict[str, t.Any]] | None = None, **kwargs, ): - if pbc1 is not None and pbc2 is not None and pbc3 is not None: - pbc = [pbc1, pbc2, pbc3] + attributes = kwargs.get('attributes', {}) + pbc1 = pbc1 if pbc1 is not None else attributes.pop('pbc1', None) + pbc2 = pbc2 if pbc2 is not None else attributes.pop('pbc2', None) + pbc3 = pbc3 if pbc3 is not None else attributes.pop('pbc3', None) + cell = cell if cell is not None else attributes.pop('cell', None) + kinds = kinds or attributes.pop('kinds', None) + sites = sites or attributes.pop('sites', None) + + if pbc1 is not None or pbc2 is not None or pbc3 is not None: + pbc = [pbc1 or False, pbc2 or False, pbc3 or False] args = { 'cell': cell, @@ -748,10 +786,22 @@ def __init__( self.set_pbc(pbc) if kinds is not None: - self.base.attributes.set('kinds', kinds) + for kind in kinds: + if isinstance(kind, Kind): + self.append_kind(kind) + elif isinstance(kind, dict): + self.append_kind(Kind(**kind)) + else: + raise TypeError('Each kind must be either a Kind instance or a dictionary.') if sites is not None: - self.base.attributes.set('sites', sites) + for site in sites: + if isinstance(site, Site): + self.append_site(site) + elif isinstance(site, dict): + self.append_site(Site(**site)) + else: + raise TypeError('Each site must be either a Site instance or a dictionary.') def get_dimensionality(self): """Return the dimensionality of the structure and its length/surface/volume. @@ -1371,7 +1421,7 @@ def append_atom(self, **kwargs): if aseatom is not None: if kwargs: raise ValueError( - "If you pass 'ase' as a parameter to " 'append_atom, you cannot pass any further' 'parameter' + "If you pass 'ase' as a parameter to append_atom, you cannot pass any furtherparameter" ) position = aseatom.position kind = Kind(ase=aseatom) @@ -2127,9 +2177,7 @@ def weights(self, value): weights_tuple = _create_weights_tuple(value) if len(weights_tuple) != len(self._symbols): - raise ValueError( - 'Cannot change the number of weights. Use the ' 'set_symbols_and_weights function instead.' - ) + raise ValueError('Cannot change the number of weights. Use the set_symbols_and_weights function instead.') validate_weights_tuple(weights_tuple, _SUM_THRESHOLD) self._weights = weights_tuple @@ -2182,9 +2230,7 @@ def symbols(self, value): symbols_tuple = _create_symbols_tuple(value) if len(symbols_tuple) != len(self._weights): - raise ValueError( - 'Cannot change the number of symbols. Use the ' 'set_symbols_and_weights function instead.' - ) + raise ValueError('Cannot change the number of symbols. Use the set_symbols_and_weights function instead.') validate_symbols_tuple(symbols_tuple) self._symbols = symbols_tuple diff --git a/src/aiida/orm/nodes/node.py b/src/aiida/orm/nodes/node.py index 129a059d89..db092ace46 100644 --- a/src/aiida/orm/nodes/node.py +++ b/src/aiida/orm/nodes/node.py @@ -10,19 +10,34 @@ from __future__ import annotations -import base64 import datetime +from copy import deepcopy from functools import cached_property -from typing import TYPE_CHECKING, Any, ClassVar, Dict, Generic, Iterator, List, NoReturn, Optional, Tuple, Type, TypeVar +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Dict, + Generic, + Iterator, + List, + NoReturn, + Optional, + Tuple, + Type, + TypeVar, + cast, +) from uuid import UUID +from pydantic import field_serializer from typing_extensions import Self from aiida.common import exceptions from aiida.common.lang import classproperty, type_check from aiida.common.links import LinkType from aiida.common.log import AIIDA_LOGGER -from aiida.common.pydantic import MetadataField +from aiida.common.pydantic import MetadataField, OrmModel from aiida.common.warnings import warn_deprecation from aiida.manage import get_manager from aiida.orm.utils.node import ( @@ -51,7 +66,7 @@ from ..implementation.nodes import BackendNode from .repository import NodeRepository -__all__ = ('Node',) +__all__ = ('Node', 'NodeLinks') NodeType = TypeVar('NodeType', bound='Node') @@ -59,8 +74,10 @@ class NodeCollection(EntityCollection[NodeType], Generic[NodeType]): """The collection of nodes.""" + collection_type: ClassVar[str] = 'nodes' + @staticmethod - def _entity_base_cls() -> Type['Node']: # type: ignore[override] + def _entity_base_cls() -> Type[Node]: # type: ignore[override] return Node def delete(self, pk: int) -> None: @@ -105,39 +122,39 @@ def iter_repo_keys( class NodeBase: """A namespace for node related functionality, that is not directly related to its user-facing properties.""" - def __init__(self, node: 'Node') -> None: + def __init__(self, node: Node) -> None: """Construct a new instance of the base namespace.""" self._node = node @cached_property - def repository(self) -> 'NodeRepository': + def repository(self) -> NodeRepository: """Return the repository for this node.""" from .repository import NodeRepository return NodeRepository(self._node) @cached_property - def caching(self) -> 'NodeCaching': + def caching(self) -> NodeCaching: """Return an interface to interact with the caching of this node.""" return self._node._CLS_NODE_CACHING(self._node) @cached_property - def comments(self) -> 'NodeComments': + def comments(self) -> NodeComments: """Return an interface to interact with the comments of this node.""" return NodeComments(self._node) @cached_property - def attributes(self) -> 'NodeAttributes': + def attributes(self) -> NodeAttributes: """Return an interface to interact with the attributes of this node.""" return NodeAttributes(self._node) @cached_property - def extras(self) -> 'EntityExtras': + def extras(self) -> EntityExtras: """Return an interface to interact with the extras of this node.""" return EntityExtras(self._node) @cached_property - def links(self) -> 'NodeLinks': + def links(self) -> NodeLinks: """Return an interface to interact with the links of this node.""" return self._node._CLS_NODE_LINKS(self._node) @@ -195,101 +212,125 @@ def _query_type_string(cls) -> str: # noqa: N805 _storable = False _unstorable_message = 'only Data, WorkflowNode, CalculationNode or their subclasses can be stored' + identity_field: ClassVar[str] = 'uuid' + + class AttributesModel(OrmModel): + """The node attributes.""" + class Model(Entity.Model): - uuid: Optional[str] = MetadataField( - None, description='The UUID of the node', is_attribute=False, exclude_to_orm=True, exclude_from_cli=True + uuid: UUID = MetadataField( + description='The UUID of the node', + read_only=True, + examples=['123e4567-e89b-12d3-a456-426614174000'], ) - node_type: Optional[str] = MetadataField( - None, description='The type of the node', is_attribute=False, exclude_to_orm=True, exclude_from_cli=True + node_type: str = MetadataField( + description='The type of the node', + read_only=True, + examples=['process.calculation.calcjob.CalcJobNode.'], ) process_type: Optional[str] = MetadataField( None, description='The process type of the node', - is_attribute=False, - exclude_to_orm=True, - exclude_from_cli=True, + read_only=True, + examples=['aiida.calculations:arithmetic.add.'], ) - repository_metadata: Optional[Dict[str, Any]] = MetadataField( - None, - description='Virtual hierarchy of the file repository.', - is_attribute=False, - orm_to_model=lambda node, _: node.base.repository.metadata, # type: ignore[attr-defined] - exclude_to_orm=True, - exclude_from_cli=True, + repository_metadata: Dict[str, Any] = MetadataField( + default_factory=dict, + description='Virtual hierarchy of the file repository', + orm_to_model=lambda node, _: cast(Node, node).base.repository.metadata, + read_only=True, + may_be_large=True, + examples=[{'key': 'value'}], ) - ctime: Optional[datetime.datetime] = MetadataField( - None, + ctime: datetime.datetime = MetadataField( description='The creation time of the node', - is_attribute=False, - exclude_to_orm=True, - exclude_from_cli=True, + read_only=True, + examples=['2024-01-01T12:00:00+00:00'], ) - mtime: Optional[datetime.datetime] = MetadataField( - None, + mtime: datetime.datetime = MetadataField( description='The modification time of the node', - is_attribute=False, - exclude_to_orm=True, - exclude_from_cli=True, + read_only=True, + examples=['2024-01-02T12:00:00+00:00'], ) - label: Optional[str] = MetadataField( - None, description='The node label', is_attribute=False, exclude_from_cli=True + label: str = MetadataField( + '', + description='The node label', + examples=['my_node'], ) - description: Optional[str] = MetadataField( - None, description='The node description', is_attribute=False, exclude_from_cli=True + description: str = MetadataField( + '', + description='The node description', + examples=['This is my node description.'], ) - attributes: Optional[Dict[str, Any]] = MetadataField( - None, + attributes: Node.AttributesModel = MetadataField( + default_factory=lambda: Node.AttributesModel(), description='The node attributes', - is_attribute=False, - orm_to_model=lambda node, _: node.base.attributes.all, # type: ignore[attr-defined] - is_subscriptable=True, - exclude_from_cli=True, - exclude_to_orm=True, + orm_to_model=lambda node, _: cast(Node, node).base.attributes.all, + may_be_large=True, + examples=[{'attr_key': 'attr_value'}], ) - extras: Optional[Dict[str, Any]] = MetadataField( - None, + extras: Dict[str, Any] = MetadataField( + default_factory=dict, description='The node extras', - is_attribute=False, - orm_to_model=lambda node, _: node.base.extras.all, # type: ignore[attr-defined] - is_subscriptable=True, - exclude_from_cli=True, - exclude_to_orm=True, + orm_to_model=lambda node, _: cast(Node, node).base.extras.all, + may_be_large=True, + examples=[{'extra_key': 'extra_value'}], ) computer: Optional[int] = MetadataField( None, description='The PK of the computer', - is_attribute=False, - orm_to_model=lambda node, _: node.computer.pk if node.computer else None, # type: ignore[attr-defined] + orm_to_model=lambda node, _: cast(Node, node).get_computer_pk(), orm_class=Computer, - exclude_from_cli=True, + read_only=True, + examples=[42], ) - user: Optional[int] = MetadataField( - None, + user: int = MetadataField( description='The PK of the user who owns the node', - is_attribute=False, - orm_to_model=lambda node, _: node.user.pk, # type: ignore[attr-defined] + orm_to_model=lambda node, _: cast(Node, node).user.pk, orm_class=User, - exclude_from_cli=True, - ) - repository_content: Optional[dict[str, bytes]] = MetadataField( - None, - description='Dictionary of file repository content. Keys are relative filepaths and values are binary file ' - 'contents encoded as base64.', - is_attribute=False, - orm_to_model=lambda node, _: { - key: base64.encodebytes(content) - for key, content in node.base.repository.serialize_content().items() # type: ignore[attr-defined] - }, - exclude_from_cli=True, - exclude_to_orm=True, + read_only=True, + examples=[7], ) + @field_serializer('uuid') + def serialize_uuid(self, value: UUID) -> str: + """Serialize UUID to string.""" + return str(value) + + def __init_subclass__(cls, **kwargs) -> None: + super().__init_subclass__(**kwargs) + + Model = cast(type[Node.Model], getattr(cls, 'Model')) # noqa N806 + Attrs = cast(type[Node.AttributesModel], getattr(cls, 'AttributesModel')) # noqa N806 + + if 'Model' not in cls.__dict__: + parent_model = Model + Model = cast( # noqa N806 + type[Node.Model], + type( + 'Model', + (parent_model,), + { + '__module__': cls.__module__, + '__qualname__': f'{cls.__qualname__}.Model', + }, + ), + ) + cls.Model = Model # type: ignore[misc] + + base_field = Model.model_fields['attributes'] + new_field = deepcopy(base_field) + new_field.annotation = Attrs + Model.model_fields['attributes'] = new_field + Model.model_rebuild(force=True) + def __init__( self, backend: Optional['StorageBackend'] = None, user: Optional[User] = None, computer: Optional[Computer] = None, extras: Optional[Dict[str, Any]] = None, + attributes: Optional[Dict[str, Any]] = None, **kwargs: Any, ) -> None: backend = backend or get_manager().get_profile_storage() @@ -307,21 +348,12 @@ def __init__( node_type=self.class_node_type, user=user.backend_entity, computer=backend_computer, **kwargs ) super().__init__(backend_entity) - if extras is not None: - self.base.extras.set_many(extras) - - @classmethod - def _from_model(cls, model: Model) -> Self: # type: ignore[override] - """Return an entity instance from an instance of its model.""" - fields = cls.model_to_orm_field_values(model) - repository_content = fields.pop('repository_content', {}) - node = cls(**fields) + if attributes is not None: + self.base.attributes.set_many(attributes) - for filepath, encoded in repository_content.items(): - node.base.repository.put_object_from_bytes(base64.decodebytes(encoded), filepath) - - return node + if extras is not None: + self.base.extras.set_many(extras) @cached_property def base(self) -> NodeBase: @@ -346,7 +378,7 @@ def __eq__(self, other: Any) -> bool: def __hash__(self) -> int: """Python-Hash: Implementation that is compatible with __eq__""" - return UUID(self.uuid).int + return int(UUID(self.uuid)) def __repr__(self) -> str: return f'<{self.__class__.__name__}: {self!s}>' @@ -615,7 +647,7 @@ def _verify_are_parents_stored(self) -> None: f'Cannot store because source node of link triple {link_triple} is not stored' ) - def _store_from_cache(self, cache_node: 'Node') -> None: + def _store_from_cache(self, cache_node: Node) -> None: """Store this node from an existing cache node. .. note:: @@ -648,7 +680,7 @@ def _store_from_cache(self, cache_node: 'Node') -> None: self._add_outputs_from_cache(cache_node) self.base.extras.set(self.base.caching.CACHED_FROM_KEY, cache_node.uuid) - def _add_outputs_from_cache(self, cache_node: 'Node') -> None: + def _add_outputs_from_cache(self, cache_node: Node) -> None: """Replicate the output links and nodes from the cached node onto this node.""" for entry in cache_node.base.links.get_outgoing(link_type=LinkType.CREATE): new_node = entry.node.clone() @@ -662,6 +694,13 @@ def get_description(self) -> str: """ return '' + def get_computer_pk(self) -> Optional[int]: + """Get the pk of the computer of this node. + + :return: The computer pk or None if no computer is set. + """ + return self.computer.pk if self.computer else None + @property def is_valid_cache(self) -> bool: """Hook to exclude certain ``Node`` classes from being considered a valid cache. diff --git a/src/aiida/orm/nodes/process/calculation/calcjob.py b/src/aiida/orm/nodes/process/calculation/calcjob.py index a526fc3b9c..720e6f4267 100644 --- a/src/aiida/orm/nodes/process/calculation/calcjob.py +++ b/src/aiida/orm/nodes/process/calculation/calcjob.py @@ -8,8 +8,10 @@ ########################################################################### """Module with `Node` sub class for calculation job processes.""" +from __future__ import annotations + import datetime -from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Sequence, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, AnyStr, Dict, List, Optional, Sequence, Tuple, Type, Union, cast from aiida.common import exceptions from aiida.common.datastructures import CalcJobState @@ -64,42 +66,56 @@ class CalcJobNode(CalculationNode): SCHEDULER_LAST_JOB_INFO_KEY = 'last_job_info' SCHEDULER_DETAILED_JOB_INFO_KEY = 'detailed_job_info' - class Model(CalculationNode.Model): + class AttributesModel(CalculationNode.AttributesModel): scheduler_state: Optional[str] = MetadataField( - description='The state of the scheduler', orm_to_model=lambda node, _: node.get_scheduler_state() + None, + description='The state of the scheduler', + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_scheduler_state(), ) state: Optional[str] = MetadataField( - description='The active state of the calculation job', orm_to_model=lambda node, _: node.get_state() + None, + description='The active state of the calculation job', + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_state(), ) remote_workdir: Optional[str] = MetadataField( + None, description='The path to the remote (on cluster) scratch folder', - orm_to_model=lambda node, _: node.get_remote_workdir(), + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_remote_workdir(), ) job_id: Optional[str] = MetadataField( - description='The scheduler job id', orm_to_model=lambda node, _: node.get_job_id() + None, + description='The scheduler job id', + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_job_id(), ) scheduler_lastchecktime: Optional[datetime.datetime] = MetadataField( + None, description='The last time the scheduler was checked, in isoformat', - orm_to_model=lambda node, _: node.get_scheduler_lastchecktime(), + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_scheduler_lastchecktime(), ) last_job_info: Optional[dict] = MetadataField( + None, description='The last job info returned by the scheduler', - orm_to_model=lambda node, _: dict(node.get_last_job_info() or {}), + orm_to_model=lambda node, _: dict(cast(CalcJobNode, node).get_last_job_info() or {}), ) detailed_job_info: Optional[dict] = MetadataField( + None, description='The detailed job info returned by the scheduler', - orm_to_model=lambda node, _: node.get_detailed_job_info(), + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_detailed_job_info(), ) - retrieve_list: Optional[List[str]] = MetadataField( + retrieve_list: Optional[Sequence[Union[str, Tuple[str, str, int]]]] = MetadataField( + None, description='The list of files to retrieve from the remote cluster', - orm_to_model=lambda node, _: node.get_retrieve_list(), + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_retrieve_list(), ) - retrieve_temporary_list: Optional[List[str]] = MetadataField( + retrieve_temporary_list: Optional[Sequence[Union[str, Tuple[str, str, int]]]] = MetadataField( + None, description='The list of temporary files to retrieve from the remote cluster', - orm_to_model=lambda node, _: node.get_retrieve_temporary_list(), + orm_to_model=lambda node, _: cast(CalcJobNode, node).get_retrieve_temporary_list(), ) imported: Optional[bool] = MetadataField( - description='Whether the node has been migrated', orm_to_model=lambda node, _: node.is_imported + None, + description='Whether the node has been migrated', + orm_to_model=lambda node, _: cast(CalcJobNode, node).is_imported, ) # An optional entry point for a CalculationTools instance @@ -259,7 +275,7 @@ def get_remote_workdir(self) -> Optional[str]: return self.base.attributes.get(self.REMOTE_WORKDIR_KEY, None) @staticmethod - def _validate_retrieval_directive(directives: Sequence[Union[str, Tuple[str, str, str]]]) -> None: + def _validate_retrieval_directive(directives: Sequence[Union[str, Tuple[str, str, int]]]) -> None: """Validate a list or tuple of file retrieval directives. :param directives: a list or tuple of file retrieval directives @@ -286,7 +302,7 @@ def _validate_retrieval_directive(directives: Sequence[Union[str, Tuple[str, str if not isinstance(directive[2], (int, type(None))): raise ValueError('invalid directive, third element has to be an integer representing the depth') - def set_retrieve_list(self, retrieve_list: Sequence[Union[str, Tuple[str, str, str]]]) -> None: + def set_retrieve_list(self, retrieve_list: Sequence[Union[str, Tuple[str, str, int]]]) -> None: """Set the retrieve list. This list of directives will instruct the daemon what files to retrieve after the calculation has completed. @@ -297,14 +313,14 @@ def set_retrieve_list(self, retrieve_list: Sequence[Union[str, Tuple[str, str, s self._validate_retrieval_directive(retrieve_list) self.base.attributes.set(self.RETRIEVE_LIST_KEY, retrieve_list) - def get_retrieve_list(self) -> Optional[Sequence[Union[str, Tuple[str, str, str]]]]: + def get_retrieve_list(self) -> Optional[Sequence[Union[str, Tuple[str, str, int]]]]: """Return the list of files/directories to be retrieved on the cluster after the calculation has completed. :return: a list of file directives """ return self.base.attributes.get(self.RETRIEVE_LIST_KEY, None) - def set_retrieve_temporary_list(self, retrieve_temporary_list: Sequence[Union[str, Tuple[str, str, str]]]) -> None: + def set_retrieve_temporary_list(self, retrieve_temporary_list: Sequence[Union[str, Tuple[str, str, int]]]) -> None: """Set the retrieve temporary list. The retrieve temporary list stores files that are retrieved after completion and made available during parsing @@ -315,7 +331,7 @@ def set_retrieve_temporary_list(self, retrieve_temporary_list: Sequence[Union[st self._validate_retrieval_directive(retrieve_temporary_list) self.base.attributes.set(self.RETRIEVE_TEMPORARY_LIST_KEY, retrieve_temporary_list) - def get_retrieve_temporary_list(self) -> Optional[Sequence[Union[str, Tuple[str, str, str]]]]: + def get_retrieve_temporary_list(self) -> Optional[Sequence[Union[str, Tuple[str, str, int]]]]: """Return list of files to be retrieved from the cluster which will be available during parsing. :return: a list of file directives @@ -338,7 +354,7 @@ def get_job_id(self) -> Optional[str]: """ return self.base.attributes.get(self.SCHEDULER_JOB_ID_KEY, None) - def set_scheduler_state(self, state: 'JobState') -> None: + def set_scheduler_state(self, state: JobState) -> None: """Set the scheduler state. :param state: an instance of `JobState` @@ -423,7 +439,7 @@ def get_last_job_info(self) -> Optional['JobInfo']: return job_info - def get_authinfo(self) -> 'AuthInfo': + def get_authinfo(self) -> AuthInfo: """Return the `AuthInfo` that is configured for the `Computer` set for this node. :return: `AuthInfo` @@ -435,7 +451,7 @@ def get_authinfo(self) -> 'AuthInfo': return computer.get_authinfo(self.user) - def get_transport(self) -> 'Transport': + def get_transport(self) -> Transport: """Return the transport for this calculation. :return: Transport configured @@ -443,7 +459,7 @@ def get_transport(self) -> 'Transport': """ return self.get_authinfo().get_transport() - def get_parser_class(self) -> Optional[Type['Parser']]: + def get_parser_class(self) -> Optional[Type[Parser]]: """Return the output parser object for this calculation or None if no parser is set. :return: a `Parser` class. @@ -463,7 +479,7 @@ def link_label_retrieved(self) -> str: """Return the link label used for the retrieved FolderData node.""" return 'retrieved' - def get_retrieved_node(self) -> Optional['FolderData']: + def get_retrieved_node(self) -> Optional[FolderData]: """Return the retrieved data folder. :return: the retrieved FolderData node or None if not found @@ -480,7 +496,7 @@ def get_retrieved_node(self) -> Optional['FolderData']: return None @property - def res(self) -> 'CalcJobResultManager': + def res(self) -> CalcJobResultManager: """To be used to get direct access to the parsed parameters. :return: an instance of the CalcJobResultManager. diff --git a/src/aiida/orm/nodes/process/process.py b/src/aiida/orm/nodes/process/process.py index 37369d14f6..72cb6286a8 100644 --- a/src/aiida/orm/nodes/process/process.py +++ b/src/aiida/orm/nodes/process/process.py @@ -8,6 +8,8 @@ ########################################################################### """Module with `Node` sub class for processes.""" +from __future__ import annotations + import enum from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union @@ -188,14 +190,35 @@ def _updatable_attributes(cls) -> Tuple[str, ...]: # noqa: N805 cls.PROCESS_STATUS_KEY, ) - class Model(Node.Model, Sealable.Model): - process_label: Optional[str] = MetadataField(description='The process label') - process_state: Optional[str] = MetadataField(description='The process state enum') - process_status: Optional[str] = MetadataField(description='The process status is a generic status message') - exit_status: Optional[int] = MetadataField(description='The process exit status') - exit_message: Optional[str] = MetadataField(description='The process exit message') - exception: Optional[str] = MetadataField(description='The process exception message') - paused: bool = MetadataField(description='Whether the process is paused') + class AttributesModel(Node.AttributesModel, Sealable.AttributesModel): + process_label: Optional[str] = MetadataField( + None, + description='The process label', + ) + process_state: Optional[str] = MetadataField( + None, + description='The process state enum', + ) + process_status: Optional[str] = MetadataField( + None, + description='The process status is a generic status message', + ) + exit_status: Optional[int] = MetadataField( + None, + description='The process exit status', + ) + exit_message: Optional[str] = MetadataField( + None, + description='The process exit message', + ) + exception: Optional[str] = MetadataField( + None, + description='The process exception message', + ) + paused: Optional[bool] = MetadataField( + None, + description='Whether the process is paused', + ) def set_metadata_inputs(self, value: Dict[str, Any]) -> None: """Set the mapping of inputs corresponding to ``metadata`` ports that were passed to the process.""" @@ -237,7 +260,7 @@ def recursive_merge(cls, left: dict[Any, Any], right: dict[Any, Any]) -> None: else: left[key] = value - def get_builder_restart(self) -> 'ProcessBuilder': + def get_builder_restart(self) -> ProcessBuilder: """Return a `ProcessBuilder` that is ready to relaunch the process that created this node. The process class will be set based on the `process_type` of this node and the inputs of the builder will be @@ -253,7 +276,7 @@ def get_builder_restart(self) -> 'ProcessBuilder': return builder @property - def process_class(self) -> Type['Process']: + def process_class(self) -> Type[Process]: """Return the process class that was used to create this node. :return: `Process` class @@ -437,7 +460,7 @@ def is_failed(self) -> bool: return self.is_finished and self.exit_status != 0 @property - def exit_code(self) -> Optional['ExitCode']: + def exit_code(self) -> Optional[ExitCode]: """Return the exit code of the process. It is reconstituted from the ``exit_status`` and ``exit_message`` attributes if both of those are defined. @@ -462,10 +485,10 @@ def exit_status(self) -> Optional[int]: """ return self.base.attributes.get(self.EXIT_STATUS_KEY, None) - def set_exit_status(self, status: Union[None, enum.Enum, int]) -> None: + def set_exit_status(self, status: Optional[Union[enum.Enum, int]] = None) -> None: """Set the exit status of the process - :param state: an integer exit code or None, which will be interpreted as zero + :param status: the exit status, an integer exit code, or None """ if status is None: status = 0 @@ -572,7 +595,7 @@ def unpause(self) -> None: pass @property - def called(self) -> List['ProcessNode']: + def called(self) -> List[ProcessNode]: """Return a list of nodes that the process called :returns: list of process nodes called by this process @@ -580,7 +603,7 @@ def called(self) -> List['ProcessNode']: return self.base.links.get_outgoing(link_type=(LinkType.CALL_CALC, LinkType.CALL_WORK)).all_nodes() @property - def called_descendants(self) -> List['ProcessNode']: + def called_descendants(self) -> List[ProcessNode]: """Return a list of all nodes that have been called downstream of this process This will recursively find all the called processes for this process and its children. @@ -594,7 +617,7 @@ def called_descendants(self) -> List['ProcessNode']: return descendants @property - def caller(self) -> Optional['ProcessNode']: + def caller(self) -> Optional[ProcessNode]: """Return the process node that called this process node, or None if it does not have a caller :returns: process node that called this process node instance or None diff --git a/src/aiida/orm/users.py b/src/aiida/orm/users.py index bb091b9fa0..8e447dc659 100644 --- a/src/aiida/orm/users.py +++ b/src/aiida/orm/users.py @@ -8,7 +8,9 @@ ########################################################################### """Module for the ORM user class.""" -from typing import TYPE_CHECKING, Optional, Tuple, Type +from __future__ import annotations + +from typing import TYPE_CHECKING, ClassVar, Optional, Tuple, Type from aiida.common import exceptions from aiida.common.pydantic import MetadataField @@ -26,11 +28,13 @@ class UserCollection(entities.Collection['User']): """The collection of users stored in a backend.""" + collection_type: ClassVar[str] = 'users' + @staticmethod - def _entity_base_cls() -> Type['User']: + def _entity_base_cls() -> Type[User]: return User - def get_or_create(self, email: str, **kwargs) -> Tuple[bool, 'User']: + def get_or_create(self, email: str, **kwargs) -> Tuple[bool, User]: """Get the existing user with a given email address or create an unstored one :param kwargs: The properties of the user to get or create @@ -43,7 +47,7 @@ def get_or_create(self, email: str, **kwargs) -> Tuple[bool, 'User']: except exceptions.NotExistent: return True, User(backend=self.backend, email=email, **kwargs) - def get_default(self) -> Optional['User']: + def get_default(self) -> Optional[User]: """Get the current default user""" return self.backend.default_user @@ -54,10 +58,25 @@ class User(entities.Entity['BackendUser', UserCollection]): _CLS_COLLECTION = UserCollection class Model(entities.Entity.Model): - email: str = MetadataField(description='The user email', is_attribute=False) - first_name: str = MetadataField(description='The user first name', is_attribute=False) - last_name: str = MetadataField(description='The user last name', is_attribute=False) - institution: str = MetadataField(description='The user institution', is_attribute=False) + email: str = MetadataField( + description='The user email', + examples=['verdi@opera.net'], + ) + first_name: str = MetadataField( + '', + description='The user first name', + examples=['Giuseppe'], + ) + last_name: str = MetadataField( + '', + description='The user last name', + examples=['Verdi'], + ) + institution: str = MetadataField( + '', + description='The user institution', + examples=['Opera National de Paris'], + ) def __init__( self, diff --git a/src/aiida/orm/utils/mixins.py b/src/aiida/orm/utils/mixins.py index 4d8379079a..14e501ff64 100644 --- a/src/aiida/orm/utils/mixins.py +++ b/src/aiida/orm/utils/mixins.py @@ -12,11 +12,9 @@ import inspect -import pydantic - from aiida.common import exceptions from aiida.common.lang import classproperty, override, type_check -from aiida.common.pydantic import MetadataField +from aiida.common.pydantic import MetadataField, OrmModel from aiida.common.warnings import warn_deprecation @@ -183,8 +181,10 @@ class Sealable: SEALED_KEY = 'sealed' - class Model(pydantic.BaseModel, defer_build=True): - sealed: bool = MetadataField(description='Whether the node is sealed') + class AttributesModel(OrmModel): + sealed: bool = MetadataField( + description='Whether the node is sealed', + ) @classproperty def _updatable_attributes(cls) -> tuple[str, ...]: # noqa: N805 diff --git a/src/aiida/storage/psql_dos/orm/querybuilder/main.py b/src/aiida/storage/psql_dos/orm/querybuilder/main.py index eb5305b6c3..451e222dbe 100644 --- a/src/aiida/storage/psql_dos/orm/querybuilder/main.py +++ b/src/aiida/storage/psql_dos/orm/querybuilder/main.py @@ -52,32 +52,42 @@ PROJECT_MAP = { 'db_dbauthinfo': { 'pk': 'id', - 'computer_pk': 'dbcomputer_id', - 'user_pk': 'aiidauser_id', + 'computer': 'dbcomputer_id', + 'user': 'aiidauser_id', }, 'db_dbnode': { 'pk': 'id', - 'computer_pk': 'dbcomputer_id', - 'user_pk': 'user_id', + 'computer': 'dbcomputer_id', + 'user': 'user_id', + }, + 'db_dbuser': { + 'pk': 'id', }, 'db_dbcomputer': { 'pk': 'id', }, 'db_dbgroup': { 'pk': 'id', - 'user_pk': 'user_id', + 'user': 'user_id', }, 'db_dbcomment': { 'pk': 'id', - 'user_pk': 'user_id', - 'node_pk': 'dbnode_id', + 'user': 'user_id', + 'node': 'dbnode_id', }, 'db_dblog': { 'pk': 'id', - 'node_pk': 'dbnode_id', + 'node': 'dbnode_id', }, } +ALIAS_MAP = { + 'id': 'pk', + 'dbcomputer_id': 'computer', + 'user_id': 'user', + 'dbnode_id': 'node', +} + @dataclass class BuiltQuery: @@ -1008,10 +1018,14 @@ def get_column(colname: str, alias: AliasedClass) -> InstrumentedAttribute: try: return getattr(alias, colname) except AttributeError as exc: + keys = [] + for key in alias._sa_class_manager.mapper.c.keys(): + if colalias := ALIAS_MAP.get(key): + keys.append(f'{colalias} (alias for {key})') + else: + keys.append(key) raise ValueError( - '{} is not a column of {}\nValid columns are:\n{}'.format( - colname, alias, '\n'.join(alias._sa_class_manager.mapper.c.keys()) - ) + '{} is not a column of {}\nValid columns are:\n{}'.format(colname, alias, '\n'.join(keys)) ) from exc diff --git a/tests/cmdline/commands/test_code/test_code_export___no_sort_.yml b/tests/cmdline/commands/test_code/test_code_export___no_sort_.yml index db9c8dc4d4..e329449464 100644 --- a/tests/cmdline/commands/test_code/test_code_export___no_sort_.yml +++ b/tests/cmdline/commands/test_code/test_code_export___no_sort_.yml @@ -3,7 +3,6 @@ description: '' computer: localhost default_calc_job_plugin: core.arithmetic.add use_double_quotes: false -with_mpi: null prepend_text: "module load something\n some command" append_text: '' filepath_executable: /bin/cat diff --git a/tests/cmdline/commands/test_code/test_code_export___sort_.yml b/tests/cmdline/commands/test_code/test_code_export___sort_.yml index 3c0037409a..14f76a515c 100644 --- a/tests/cmdline/commands/test_code/test_code_export___sort_.yml +++ b/tests/cmdline/commands/test_code/test_code_export___sort_.yml @@ -6,4 +6,3 @@ filepath_executable: /bin/cat label: code prepend_text: "module load something\n some command" use_double_quotes: false -with_mpi: null diff --git a/tests/orm/data/code/test_abstract.py b/tests/orm/data/code/test_abstract.py index 791595c9f4..d4008156c0 100644 --- a/tests/orm/data/code/test_abstract.py +++ b/tests/orm/data/code/test_abstract.py @@ -72,4 +72,4 @@ def test_serialization(): label = 'some-label' code = MockCode(label=label) - MockCode.from_serialized(**code.serialize()) + MockCode.from_serialized(code.serialize()) diff --git a/tests/orm/data/code/test_containerized.py b/tests/orm/data/code/test_containerized.py index 68ae32b76d..e01c85bf23 100644 --- a/tests/orm/data/code/test_containerized.py +++ b/tests/orm/data/code/test_containerized.py @@ -17,16 +17,16 @@ def test_constructor_raises(aiida_localhost): """Test the constructor when it is supposed to raise.""" - with pytest.raises(TypeError, match=r'missing .* required positional arguments'): - ContainerizedCode() + with pytest.raises(ValueError, match=r'Both `engine_command` and `image_name` must be provided.'): + ContainerizedCode(engine_command='bash') + + with pytest.raises(ValueError, match=r'Both `engine_command` and `image_name` must be provided.'): + ContainerizedCode(image_name='img') with pytest.raises(TypeError, match=r'Got object of type .*'): path = pathlib.Path('bash') ContainerizedCode(computer=aiida_localhost, filepath_executable=path, engine_command='docker', image_name='img') - with pytest.raises(TypeError, match=r'Got object of type .*'): - ContainerizedCode(computer='computer', filepath_executable='bash', engine_command='docker', image_name='img') - with pytest.raises(ValueError, match="the '{image_name}' template field should be in engine command."): ContainerizedCode(computer=aiida_localhost, filepath_executable='ls', engine_command='docker', image_name='img') diff --git a/tests/orm/data/code/test_installed.py b/tests/orm/data/code/test_installed.py index eb474bfd1b..497c54e951 100644 --- a/tests/orm/data/code/test_installed.py +++ b/tests/orm/data/code/test_installed.py @@ -20,15 +20,12 @@ def test_constructor_raises(aiida_localhost, bash_path): """Test the constructor when it is supposed to raise.""" - with pytest.raises(TypeError, match=r'missing .* required positional arguments'): - InstalledCode() + with pytest.raises(ValueError, match=r'The `filepath_executable` parameter must be provided.'): + InstalledCode(computer=aiida_localhost) with pytest.raises(TypeError, match=r'Got object of type .*'): InstalledCode(computer=aiida_localhost, filepath_executable=bash_path) - with pytest.raises(TypeError, match=r'Got object of type .*'): - InstalledCode(computer='computer', filepath_executable='/usr/bin/bash') - def test_constructor(aiida_localhost, bash_path): """Test the constructor.""" @@ -152,4 +149,4 @@ def test_serialization(aiida_localhost, bash_path): """Test the deprecated :meth:`aiida.orm.nodes.data.code.installed.InstalledCode.get_execname` method.""" code = InstalledCode(label='some-label', computer=aiida_localhost, filepath_executable=str(bash_path.absolute())) - InstalledCode.from_serialized(**code.serialize()) + InstalledCode.from_serialized(code.serialize()) diff --git a/tests/orm/data/code/test_portable.py b/tests/orm/data/code/test_portable.py index 455804773e..01ed5e4588 100644 --- a/tests/orm/data/code/test_portable.py +++ b/tests/orm/data/code/test_portable.py @@ -20,8 +20,11 @@ def test_constructor_raises(tmp_path, bash_path): """Test the constructor when it is supposed to raise.""" - with pytest.raises(TypeError, match=r'missing .* required positional argument'): - PortableCode() + with pytest.raises(ValueError, match=r'Both `filepath_files` and `filepath_executable` must be provided.'): + PortableCode(filepath_files=tmp_path) + + with pytest.raises(ValueError, match=r'Both `filepath_files` and `filepath_executable` must be provided.'): + PortableCode(filepath_executable=bash_path) with pytest.raises(ValueError, match=r'The `filepath_executable` should not be absolute.'): PortableCode(filepath_executable=bash_path, filepath_files=tmp_path) @@ -150,9 +153,7 @@ def test_portablecode_extra_files(tmp_path, chdir_tmp_path): result, extra_args = code._prepare_yaml() ref_result = f"""label: some-label description: '' -default_calc_job_plugin: null use_double_quotes: false -with_mpi: null prepend_text: '' append_text: '' filepath_executable: bash @@ -176,4 +177,4 @@ def test_serialization(tmp_path, chdir_tmp_path): (filepath_files / 'subdir').mkdir() (filepath_files / 'subdir/test').write_text('test') code = PortableCode(label='some-label', filepath_executable='bash', filepath_files=filepath_files) - PortableCode.from_serialized(**code.serialize()) + PortableCode.from_serialized(code.serialize()) diff --git a/tests/orm/models/test_models.py b/tests/orm/models/test_models.py index 7209a8fdaf..e768fd32f9 100644 --- a/tests/orm/models/test_models.py +++ b/tests/orm/models/test_models.py @@ -14,14 +14,17 @@ ArrayData, Bool, CifData, + ContainerizedCode, Data, Dict, EnumData, Float, FolderData, + InstalledCode, Int, JsonableData, List, + PortableCode, RemoteData, RemoteStashCompressedData, RemoteStashData, @@ -41,14 +44,17 @@ ArrayData, Bool, CifData, + ContainerizedCode, Data, Dict, EnumData, Float, FolderData, + InstalledCode, Int, JsonableData, List, + PortableCode, SinglefileData, Str, StructureData, @@ -110,49 +116,88 @@ def required_arguments(request, default_user, aiida_localhost, tmp_path): if request.param is User: return User, {'email': 'test@localhost'} if request.param is ArrayData: - return ArrayData, {'arrays': np.array([1, 0, 0])} + return ArrayData, {'attributes': {'arrays': np.array([1, 0, 0])}} if request.param is Bool: - return Bool, {'value': True} + return Bool, {'attributes': {'value': True}} if request.param is CifData: return CifData, {'content': io.BytesIO(b'some-content')} + if request.param is ContainerizedCode: + return ContainerizedCode, { + 'label': 'containerized_echo', + 'attributes': { + 'computer': aiida_localhost.label, + 'filepath_executable': '/bin/echo', + 'image_name': 'docker://alpine:3', + 'engine_command': 'docker run {image_name}', + }, + } if request.param is Data: - return Data, {'source': {'uri': 'http://127.0.0.1'}} + return Data, {'attributes': {'source': {'uri': 'http://127.0.0.1'}}} if request.param is Dict: return Dict, {'value': {'a': 1}} if request.param is EnumData: - return EnumData, {'member': DummyEnum.OPTION_A} + return EnumData, {'attributes': {'member': DummyEnum.OPTION_A}} if request.param is Float: - return Float, {'value': 1.0} + return Float, {'attributes': {'value': 1.0}} if request.param is FolderData: dirpath = tmp_path / 'folder_data' dirpath.mkdir() (dirpath / 'binary_file').write_bytes(b'byte content') (dirpath / 'text_file').write_text('text content') return FolderData, {'tree': dirpath} + if request.param is InstalledCode: + return InstalledCode, { + 'label': 'echo', + 'attributes': { + 'computer': aiida_localhost.label, + 'filepath_executable': '/bin/echo', + }, + } if request.param is Int: - return Int, {'value': 1} + return Int, {'attributes': {'value': 1}} if request.param is JsonableData: - return JsonableData, {'obj': JsonableClass({'a': 1})} + return JsonableData, {'attributes': {'obj': JsonableClass({'a': 1})}} if request.param is List: - return List, {'value': [1.0]} + return List, {'attributes': {'value': [1.0]}} + if request.param is PortableCode: + code_path = tmp_path / 'portable_code' + code_path.mkdir() + (code_path / 'code.sh').write_text('#!/bin/bash\necho "$@"\n') + return PortableCode, { + 'label': 'portable_code', + 'attributes': { + 'filepath_executable': 'code.sh', + 'filepath_files': code_path, + }, + } if request.param is SinglefileData: - return SinglefileData, {'content': io.BytesIO(b'some-content')} + return SinglefileData, {'attributes': {'content': io.BytesIO(b'some-content')}} if request.param is Str: - return Str, {'value': 'string'} + return Str, {'attributes': {'value': 'string'}} if request.param is StructureData: - return StructureData, {'cell': [[1, 0, 0], [0, 1, 0], [0, 0, 1]]} + return StructureData, { + 'attributes': { + 'cell': [[1, 0, 0], [0, 1, 0], [0, 0, 1]], + 'pbc1': True, + 'pbc2': True, + 'pbc3': True, + 'sites': [{'kind_name': 'H', 'position': (0.0, 0.0, 0.0)}], + 'kinds': [{'name': 'H', 'mass': 1.0, 'symbols': ('H',), 'weights': (1.0,)}], + } + } if request.param is RemoteData: - return RemoteData, {'remote_path': '/some/path'} + return RemoteData, {'attributes': {'remote_path': '/some/path'}} if request.param is RemoteStashData: - return RemoteStashData, {'stash_mode': StashMode.COMPRESS_TAR} + return RemoteStashData, {'attributes': {'stash_mode': StashMode.COMPRESS_TAR}} if request.param is RemoteStashCompressedData: return RemoteStashCompressedData, { - 'stash_mode': StashMode.COMPRESS_TAR, - 'target_basepath': '/some/path', - 'source_list': ['/some/file'], - 'dereference': True, + 'attributes': { + 'stash_mode': StashMode.COMPRESS_TAR, + 'target_basepath': '/some/path', + 'source_list': ['/some/file'], + 'dereference': True, + } } - raise NotImplementedError() @@ -169,28 +214,28 @@ def test_roundtrip(required_arguments, tmp_path): assert isinstance(entity, cls) # Get the model instance from the entity instance - model = entity._to_model(tmp_path) + model = entity.to_model(repository_path=tmp_path) assert isinstance(model, BaseModel) # Reconstruct the entity instance from the model instance - roundtrip = cls._from_model(model) + roundtrip = cls.from_model(model) assert isinstance(roundtrip, cls) # Get the model instance again from the reconstructed entity and check that the fields that would be passed to the - # ORM entity constructor are identical of the original model. The ``model_to_orm_field_values`` excludes values of - # fields that define ``exclude_to_orm=True`` because these can change during roundtrips. This because these - # typically correspond to entity fields that have defaults set on the database level, e.g., UUIDs. - roundtrip_model = roundtrip._to_model(tmp_path) + # ORM entity constructor are identical of the original model. + roundtrip_model = roundtrip.to_model(repository_path=tmp_path) original_field_values = cls.model_to_orm_field_values(model) - for key, value in cls.model_to_orm_field_values(roundtrip_model).items(): + def _validate_value(value): + if isinstance(value, dict): + return {k: _validate_value(v) for k, v in value.items()} if isinstance(value, io.BytesIO): - assert value.read() == original_field_values[key].read() - elif cls is ArrayData and key == 'arrays': - for array_name, array in value.items(): - assert np.array_equal(array, original_field_values[key][array_name]) - else: - assert value == original_field_values[key] + value.seek(0) + return value.read() + return value + + for key, value in cls.model_to_orm_field_values(roundtrip_model).items(): + assert _validate_value(value) == _validate_value(original_field_values[key]) @pytest.mark.parametrize( @@ -206,5 +251,5 @@ def test_roundtrip_serialization(required_arguments, tmp_path): assert isinstance(entity, cls) # Get the model instance from the entity instance - serialized_entity = entity.serialize(tmp_path) - entity.from_serialized(**serialized_entity) + serialized_entity = entity.serialize(repository_path=tmp_path, mode='python') + entity.from_serialized(serialized_entity) diff --git a/tests/orm/test_fields.py b/tests/orm/test_fields.py index f22fdc3c73..cba5314b8b 100644 --- a/tests/orm/test_fields.py +++ b/tests/orm/test_fields.py @@ -54,10 +54,8 @@ def test_add_field(): """Test the `add_field` API.""" class NewNode(orm.Data): - class Model(orm.Data.Model): - key1: str = MetadataField( # type: ignore[annotation-unchecked] - is_subscriptable=False, - ) + class AttributesModel(orm.Data.AttributesModel): + key1: str = MetadataField() node = NewNode() @@ -65,7 +63,7 @@ class Model(orm.Data.Model): assert node.fields.key1.dtype is str assert isinstance(node.fields.key1, orm.fields.QbStrField) assert node.fields.key1.backend_key == 'attributes.key1' - assert not node.fields.key1.is_subscriptable + assert node.fields.key1 == node.fields.attributes.key1 @pytest.mark.parametrize('key', ('|', 'some.field', '1key')) @@ -75,16 +73,6 @@ def test_invalid_field_keys(key): _ = add_field(key) -def test_disallowed_alias_for_db_field(): - """Test for disallowed alias argument for database fields.""" - with pytest.raises(ValueError): - _ = add_field( - 'some_key', - 'alias_not_allowed_for_db_fields', - is_attribute=False, - ) - - @pytest.mark.usefixtures('aiida_profile_clean') def test_query_new_class(monkeypatch): """Test that fields are correctly registered on a new data class, @@ -102,7 +90,7 @@ def _dummy(*args, **kwargs): ) class NewNode(orm.Data): - class Model(orm.Data.Model): + class AttributesModel(orm.Data.AttributesModel): some_label: str = MetadataField() # type: ignore[annotation-unchecked] some_value: int = MetadataField() # type: ignore[annotation-unchecked] diff --git a/tests/orm/test_fields/fields_AuthInfo.yml b/tests/orm/test_fields/fields_AuthInfo.yml index 505c96da91..7c2946f587 100644 --- a/tests/orm/test_fields/fields_AuthInfo.yml +++ b/tests/orm/test_fields/fields_AuthInfo.yml @@ -1,6 +1,9 @@ -auth_params: QbDictField('auth_params', dtype=typing.Dict[str, typing.Any], is_attribute=False) -computer: QbNumericField('computer', dtype=, is_attribute=False) -enabled: QbField('enabled', dtype=, 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) -user: QbNumericField('user', dtype=, is_attribute=False) +auth_params: QbDictField('auth_params', dtype=typing.Dict[str, typing.Any], doc='Dictionary + of authentication parameters') +computer: QbNumericField('computer', dtype=, doc='The PK of the computer') +enabled: QbAnyField('enabled', dtype=, doc='Whether the instance is + enabled') +metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], doc='Dictionary + of metadata') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +user: QbNumericField('user', dtype=, doc='The PK of the user') diff --git a/tests/orm/test_fields/fields_Comment.yml b/tests/orm/test_fields/fields_Comment.yml index 30aedcef79..a69baa79ea 100644 --- a/tests/orm/test_fields/fields_Comment.yml +++ b/tests/orm/test_fields/fields_Comment.yml @@ -1,7 +1,10 @@ -content: QbStrField('content', dtype=, 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) -node: QbNumericField('node', dtype=, is_attribute=False) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -user: QbNumericField('user', dtype=, is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +content: QbStrField('content', dtype=, doc='Content of the comment') +ctime: QbNumericField('ctime', dtype=, doc='Creation time + of the comment') +mtime: QbNumericField('mtime', dtype=, doc='Modified time + of the comment') +node: QbNumericField('node', dtype=, doc='Node PK that the comment is + attached to') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +user: QbNumericField('user', dtype=, doc='User PK that created the comment') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the comment') diff --git a/tests/orm/test_fields/fields_Computer.yml b/tests/orm/test_fields/fields_Computer.yml index 7d4e37168b..cf34ed9dca 100644 --- a/tests/orm/test_fields/fields_Computer.yml +++ b/tests/orm/test_fields/fields_Computer.yml @@ -1,8 +1,12 @@ -description: QbStrField('description', dtype=, is_attribute=False) -hostname: QbStrField('hostname', dtype=, is_attribute=False) -label: QbStrField('label', dtype=, 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) -scheduler_type: QbStrField('scheduler_type', dtype=, is_attribute=False) -transport_type: QbStrField('transport_type', dtype=, is_attribute=False) -uuid: QbStrField('uuid', dtype=, is_attribute=False) +description: QbStrField('description', dtype=, doc='Description of the + computer') +hostname: QbStrField('hostname', dtype=, doc='Hostname of the computer') +label: QbStrField('label', dtype=, doc='Label for the computer') +metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], doc='Metadata + of the computer') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +scheduler_type: QbStrField('scheduler_type', dtype=, doc='Scheduler type + of the computer') +transport_type: QbStrField('transport_type', dtype=, doc='Transport type + of the computer') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the computer') diff --git a/tests/orm/test_fields/fields_Group.yml b/tests/orm/test_fields/fields_Group.yml index 537c76a11d..870385e5dc 100644 --- a/tests/orm/test_fields/fields_Group.yml +++ b/tests/orm/test_fields/fields_Group.yml @@ -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) -label: QbStrField('label', dtype=, is_attribute=False) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -time: QbNumericField('time', dtype=typing.Optional[datetime.datetime], is_attribute=False) -type_string: QbStrField('type_string', dtype=, is_attribute=False) -user: QbNumericField('user', dtype=, is_attribute=False) -uuid: QbStrField('uuid', dtype=, is_attribute=False) +description: QbStrField('description', dtype=, doc='The group description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The group extras') +label: QbStrField('label', dtype=, doc='The group label') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +time: QbNumericField('time', dtype=, doc='The creation + time of the node, defaults to now (timezone-aware)') +type_string: QbStrField('type_string', dtype=, doc='The type of the group') +user: QbNumericField('user', dtype=, doc='The PK of the group owner') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the group') diff --git a/tests/orm/test_fields/fields_Log.yml b/tests/orm/test_fields/fields_Log.yml index 90bf0a5b0b..639de974de 100644 --- a/tests/orm/test_fields/fields_Log.yml +++ b/tests/orm/test_fields/fields_Log.yml @@ -1,8 +1,10 @@ -dbnode_id: QbNumericField('dbnode_id', dtype=, is_attribute=False) -levelname: QbStrField('levelname', dtype=, is_attribute=False) -loggername: QbStrField('loggername', dtype=, is_attribute=False) -message: QbStrField('message', dtype=, 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) -time: QbNumericField('time', dtype=, is_attribute=False) -uuid: QbStrField('uuid', dtype=, is_attribute=False) +levelname: QbStrField('levelname', dtype=, doc='The name of the log level') +loggername: QbStrField('loggername', dtype=, doc='The name of the logger') +message: QbStrField('message', dtype=, doc='The message of the log') +metadata: QbDictField('metadata', dtype=typing.Dict[str, typing.Any], doc='The metadata + of the log') +node: QbNumericField('node', dtype=, doc='Associated node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +time: QbNumericField('time', dtype=, doc='The time at which + the log was created') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_User.yml b/tests/orm/test_fields/fields_User.yml index 06fb9cd05c..627110e84f 100644 --- a/tests/orm/test_fields/fields_User.yml +++ b/tests/orm/test_fields/fields_User.yml @@ -1,5 +1,5 @@ -email: QbStrField('email', dtype=, is_attribute=False) -first_name: QbStrField('first_name', dtype=, is_attribute=False) -institution: QbStrField('institution', dtype=, is_attribute=False) -last_name: QbStrField('last_name', dtype=, is_attribute=False) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) +email: QbStrField('email', dtype=, doc='The user email') +first_name: QbStrField('first_name', dtype=, doc='The user first name') +institution: QbStrField('institution', dtype=, doc='The user institution') +last_name: QbStrField('last_name', dtype=, doc='The user last name') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml index c31304e6f5..8f759483d2 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.ArrayData.yml @@ -1,20 +1,22 @@ -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.bands.BandsData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.bands.BandsData.yml index f0bca48c81..2a143d17cc 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.bands.BandsData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.bands.BandsData.yml @@ -1,31 +1,42 @@ -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) -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) -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) -offset: QbArrayField('offset', dtype=typing.List[float], is_attribute=True) -pbc1: QbField('pbc1', dtype=, is_attribute=True) -pbc2: QbField('pbc2', dtype=, is_attribute=True) -pbc3: QbField('pbc3', dtype=, 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) -units: QbStrField('units', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +array_labels: QbArrayField('attributes.array_labels', dtype=typing.Optional[typing.List[str]], + doc='Labels associated with the band arrays') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +cell: QbArrayField('attributes.cell', dtype=typing.Optional[typing.List[typing.List[float]]], + doc='Unit cell of the crystal, in Angstroms') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +label_numbers: QbArrayField('attributes.label_numbers', dtype=typing.Optional[typing.List[int]], + doc='Index of the labels in the list of kpoints') +labels: QbArrayField('attributes.labels', dtype=typing.Optional[typing.List[str]], + doc='Labels associated with the list of kpoints') +mesh: QbArrayField('attributes.mesh', dtype=typing.Optional[typing.List[int]], doc='Mesh + of kpoints') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +offset: QbArrayField('attributes.offset', dtype=typing.Optional[typing.List[float]], + doc='Offset of kpoints') +pbc1: QbAnyField('attributes.pbc1', dtype=typing.Optional[bool], doc='Periodicity + in the first lattice vector direction') +pbc2: QbAnyField('attributes.pbc2', dtype=typing.Optional[bool], doc='Periodicity + in the second lattice vector direction') +pbc3: QbAnyField('attributes.pbc3', dtype=typing.Optional[bool], doc='Periodicity + in the third lattice vector direction') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +units: QbStrField('attributes.units', dtype=typing.Optional[str], doc='Units in which + the data in bands were stored') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.kpoints.KpointsData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.kpoints.KpointsData.yml index 6d0aaa2b6f..f7f2b35762 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.kpoints.KpointsData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.kpoints.KpointsData.yml @@ -1,28 +1,38 @@ -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) -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) -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) -offset: QbArrayField('offset', dtype=typing.List[float], is_attribute=True) -pbc1: QbField('pbc1', dtype=, is_attribute=True) -pbc2: QbField('pbc2', dtype=, is_attribute=True) -pbc3: QbField('pbc3', dtype=, 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) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +cell: QbArrayField('attributes.cell', dtype=typing.Optional[typing.List[typing.List[float]]], + doc='Unit cell of the crystal, in Angstroms') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +label_numbers: QbArrayField('attributes.label_numbers', dtype=typing.Optional[typing.List[int]], + doc='Index of the labels in the list of kpoints') +labels: QbArrayField('attributes.labels', dtype=typing.Optional[typing.List[str]], + doc='Labels associated with the list of kpoints') +mesh: QbArrayField('attributes.mesh', dtype=typing.Optional[typing.List[int]], doc='Mesh + of kpoints') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +offset: QbArrayField('attributes.offset', dtype=typing.Optional[typing.List[float]], + doc='Offset of kpoints') +pbc1: QbAnyField('attributes.pbc1', dtype=typing.Optional[bool], doc='Periodicity + in the first lattice vector direction') +pbc2: QbAnyField('attributes.pbc2', dtype=typing.Optional[bool], doc='Periodicity + in the second lattice vector direction') +pbc3: QbAnyField('attributes.pbc3', dtype=typing.Optional[bool], doc='Periodicity + in the third lattice vector direction') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.projection.ProjectionData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.projection.ProjectionData.yml index c31304e6f5..8f759483d2 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.projection.ProjectionData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.projection.ProjectionData.yml @@ -1,20 +1,22 @@ -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.trajectory.TrajectoryData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.trajectory.TrajectoryData.yml index 87aaa30148..642f193284 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.trajectory.TrajectoryData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.trajectory.TrajectoryData.yml @@ -1,23 +1,23 @@ -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) -symbols: QbArrayField('symbols', dtype=typing.List[str], is_attribute=True) -units_positions: QbStrField('units_positions', dtype=, is_attribute=True) -units_times: QbStrField('units_times', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +symbols: QbArrayField('attributes.symbols', dtype=typing.List[str], doc='List of symbols') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.array.xy.XyData.yml b/tests/orm/test_fields/fields_aiida.data.core.array.xy.XyData.yml index c31304e6f5..62d1bdacb5 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.array.xy.XyData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.array.xy.XyData.yml @@ -1,20 +1,30 @@ -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +x_name: QbStrField('attributes.x_name', dtype=, doc='The name of the + x array') +x_units: QbStrField('attributes.x_units', dtype=, doc='The units of the + x array') +y_names: QbAnyField('attributes.y_names', dtype=typing.Sequence[str], doc='The names + of the y arrays') +y_units: QbAnyField('attributes.y_units', dtype=typing.Sequence[str], doc='The units + of the y arrays') diff --git a/tests/orm/test_fields/fields_aiida.data.core.base.BaseType.yml b/tests/orm/test_fields/fields_aiida.data.core.base.BaseType.yml index 457621f596..73afa7f998 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.base.BaseType.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.base.BaseType.yml @@ -1,20 +1,23 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbAnyField('attributes.value', dtype=typing.Any, doc='The value of the data') diff --git a/tests/orm/test_fields/fields_aiida.data.core.bool.Bool.yml b/tests/orm/test_fields/fields_aiida.data.core.bool.Bool.yml index 457621f596..8522486c4d 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.bool.Bool.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.bool.Bool.yml @@ -1,20 +1,24 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbAnyField('attributes.value', dtype=, doc='The value of the + boolean') diff --git a/tests/orm/test_fields/fields_aiida.data.core.cif.CifData.yml b/tests/orm/test_fields/fields_aiida.data.core.cif.CifData.yml index da4e1d40d9..1092119587 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.cif.CifData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.cif.CifData.yml @@ -1,25 +1,30 @@ -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) -content: QbField('content', dtype=, is_attribute=True) -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) -filename: QbStrField('filename', dtype=typing.Optional[str], is_attribute=True) -formulae: QbArrayField('formulae', dtype=typing.Optional[typing.List[str]], is_attribute=True) -label: QbStrField('label', dtype=typing.Optional[str], is_attribute=False) -md5: QbStrField('md5', dtype=typing.Optional[str], 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) -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) -spacegroup_numbers: QbArrayField('spacegroup_numbers', dtype=typing.Optional[typing.List[str]], - is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filename: QbStrField('attributes.filename', dtype=, doc='The name of + the stored file') +formulae: QbArrayField('attributes.formulae', dtype=typing.Optional[typing.List[str]], + doc='List of formulae contained in the CIF file') +label: QbStrField('label', dtype=, doc='The node label') +md5: QbStrField('attributes.md5', dtype=typing.Optional[str], doc='MD5 checksum of + the file contents') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +spacegroup_numbers: QbArrayField('attributes.spacegroup_numbers', dtype=typing.Optional[typing.List[str]], + doc='List of space group numbers of the structure') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.code.Code.yml b/tests/orm/test_fields/fields_aiida.data.core.code.Code.yml index 8ebaa0804d..78ce1f8128 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.code.Code.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.code.Code.yml @@ -1,29 +1,45 @@ -append_text: QbStrField('append_text', dtype=, 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) -default_calc_job_plugin: QbStrField('default_calc_job_plugin', dtype=typing.Optional[str], - is_attribute=True) -description: QbStrField('description', dtype=, is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -input_plugin: QbStrField('input_plugin', dtype=typing.Optional[str], is_attribute=True) -is_local: QbField('is_local', dtype=typing.Optional[bool], is_attribute=True) -label: QbStrField('label', dtype=, is_attribute=True) -local_executable: QbStrField('local_executable', dtype=typing.Optional[str], 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) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -prepend_text: QbStrField('prepend_text', dtype=, is_attribute=True) -process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -remote_exec_path: QbStrField('remote_exec_path', dtype=typing.Optional[str], is_attribute=True) -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) -use_double_quotes: QbField('use_double_quotes', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) -with_mpi: QbField('with_mpi', dtype=typing.Optional[bool], is_attribute=True) +append_text: QbStrField('attributes.append_text', dtype=, doc='The code + that will be put in the scheduler script after the execution of the code') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +default_calc_job_plugin: QbStrField('attributes.default_calc_job_plugin', dtype=typing.Optional[str], + doc='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)') +description: QbStrField('description', dtype=, doc='Human-readable description, + ideally including version and compilation environment') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +input_plugin: QbStrField('attributes.input_plugin', dtype=typing.Optional[str], doc='The + name of the input plugin to be used for this code') +is_local: QbAnyField('attributes.is_local', dtype=typing.Optional[bool], doc='Whether + the code is local or remote') +label: QbStrField('label', dtype=, doc='A unique label to identify the + code by') +local_executable: QbStrField('attributes.local_executable', dtype=typing.Optional[str], + doc='Path to a local executable') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +prepend_text: QbStrField('attributes.prepend_text', dtype=, doc='The + code that will be put in the scheduler script before the execution of the code') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +remote_exec_path: QbStrField('attributes.remote_exec_path', dtype=typing.Optional[str], + doc='Remote path to executable') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +use_double_quotes: QbAnyField('attributes.use_double_quotes', dtype=, + doc='Whether the executable and arguments of the code in the submission script should + be escaped with single or double quotes') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +with_mpi: QbAnyField('attributes.with_mpi', dtype=typing.Optional[bool], doc='Whether + the executable should be run as an MPI program. This option can be left unspecified + in which case `None` will be set and it is left up to the calculation job plugin + or inputs whether to run with MPI') diff --git a/tests/orm/test_fields/fields_aiida.data.core.code.abstract.AbstractCode.yml b/tests/orm/test_fields/fields_aiida.data.core.code.abstract.AbstractCode.yml index 4dc178d9fc..1736b796b1 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.code.abstract.AbstractCode.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.code.abstract.AbstractCode.yml @@ -1,25 +1,38 @@ -append_text: QbStrField('append_text', dtype=, 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) -default_calc_job_plugin: QbStrField('default_calc_job_plugin', dtype=typing.Optional[str], - is_attribute=True) -description: QbStrField('description', dtype=, is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -label: QbStrField('label', dtype=, 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) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -prepend_text: QbStrField('prepend_text', dtype=, is_attribute=True) -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) -use_double_quotes: QbField('use_double_quotes', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) -with_mpi: QbField('with_mpi', dtype=typing.Optional[bool], is_attribute=True) +append_text: QbStrField('attributes.append_text', dtype=, doc='Bash commands + that should be appended to the run line in all submit scripts for this code') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +default_calc_job_plugin: QbStrField('attributes.default_calc_job_plugin', dtype=typing.Optional[str], + doc='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)') +description: QbStrField('description', dtype=, doc='Human-readable description, + ideally including version and compilation environment') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='A unique label to identify the + code by') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +prepend_text: QbStrField('attributes.prepend_text', dtype=, doc='Bash + commands that should be prepended to the run line in all submit scripts for this + code') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +use_double_quotes: QbAnyField('attributes.use_double_quotes', dtype=, + doc='Whether the executable and arguments of the code in the submission script should + be escaped with single or double quotes') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +with_mpi: QbAnyField('attributes.with_mpi', dtype=typing.Optional[bool], doc='Whether + the executable should be run as an MPI program. This option can be left unspecified + in which case `None` will be set and it is left up to the calculation job plugin + or inputs whether to run with MPI') diff --git a/tests/orm/test_fields/fields_aiida.data.core.code.containerized.ContainerizedCode.yml b/tests/orm/test_fields/fields_aiida.data.core.code.containerized.ContainerizedCode.yml index e7f12a1a94..9b863561bf 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.code.containerized.ContainerizedCode.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.code.containerized.ContainerizedCode.yml @@ -1,29 +1,49 @@ -append_text: QbStrField('append_text', dtype=, is_attribute=True) -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -computer: QbStrField('computer', dtype=, is_attribute=True) -ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) -default_calc_job_plugin: QbStrField('default_calc_job_plugin', dtype=typing.Optional[str], - is_attribute=True) -description: QbStrField('description', dtype=, is_attribute=True) -engine_command: QbStrField('engine_command', dtype=, is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -filepath_executable: QbStrField('filepath_executable', dtype=, is_attribute=True) -image_name: QbStrField('image_name', dtype=, is_attribute=True) -label: QbStrField('label', dtype=, 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) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -prepend_text: QbStrField('prepend_text', dtype=, is_attribute=True) -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) -use_double_quotes: QbField('use_double_quotes', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) -with_mpi: QbField('with_mpi', dtype=typing.Optional[bool], is_attribute=True) -wrap_cmdline_params: QbField('wrap_cmdline_params', dtype=, is_attribute=True) +append_text: QbStrField('attributes.append_text', dtype=, doc='Bash commands + that should be appended to the run line in all submit scripts for this code') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +default_calc_job_plugin: QbStrField('attributes.default_calc_job_plugin', dtype=typing.Optional[str], + doc='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)') +description: QbStrField('description', dtype=, doc='Human-readable description, + ideally including version and compilation environment') +engine_command: QbStrField('attributes.engine_command', dtype=, doc='The + command to run the container. It must contain the placeholder {image_name} that + will be replaced with the `image_name`') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filepath_executable: QbStrField('attributes.filepath_executable', dtype=, + doc='Filepath of the executable on the remote computer') +image_name: QbStrField('attributes.image_name', dtype=, doc='Name of + the image container in which to the run the executable') +label: QbStrField('label', dtype=, doc='A unique label to identify the + code by') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +prepend_text: QbStrField('attributes.prepend_text', dtype=, doc='Bash + commands that should be prepended to the run line in all submit scripts for this + code') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +use_double_quotes: QbAnyField('attributes.use_double_quotes', dtype=, + doc='Whether the executable and arguments of the code in the submission script should + be escaped with single or double quotes') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +with_mpi: QbAnyField('attributes.with_mpi', dtype=typing.Optional[bool], doc='Whether + the executable should be run as an MPI program. This option can be left unspecified + in which case `None` will be set and it is left up to the calculation job plugin + or inputs whether to run with MPI') +wrap_cmdline_params: QbAnyField('attributes.wrap_cmdline_params', dtype=, + doc='Whether all command line parameters to be passed to the engine command should + be wrapped in a double quotes to form a single argument. This should be set to `True` + for Docker') diff --git a/tests/orm/test_fields/fields_aiida.data.core.code.installed.InstalledCode.yml b/tests/orm/test_fields/fields_aiida.data.core.code.installed.InstalledCode.yml index 15089b4a3d..f32ed7de84 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.code.installed.InstalledCode.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.code.installed.InstalledCode.yml @@ -1,26 +1,40 @@ -append_text: QbStrField('append_text', dtype=, is_attribute=True) -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -computer: QbStrField('computer', dtype=, is_attribute=True) -ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) -default_calc_job_plugin: QbStrField('default_calc_job_plugin', dtype=typing.Optional[str], - is_attribute=True) -description: QbStrField('description', dtype=, is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -filepath_executable: QbStrField('filepath_executable', dtype=, is_attribute=True) -label: QbStrField('label', dtype=, 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) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -prepend_text: QbStrField('prepend_text', dtype=, is_attribute=True) -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) -use_double_quotes: QbField('use_double_quotes', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) -with_mpi: QbField('with_mpi', dtype=typing.Optional[bool], is_attribute=True) +append_text: QbStrField('attributes.append_text', dtype=, doc='Bash commands + that should be appended to the run line in all submit scripts for this code') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +default_calc_job_plugin: QbStrField('attributes.default_calc_job_plugin', dtype=typing.Optional[str], + doc='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)') +description: QbStrField('description', dtype=, doc='Human-readable description, + ideally including version and compilation environment') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filepath_executable: QbStrField('attributes.filepath_executable', dtype=, + doc='Filepath of the executable on the remote computer') +label: QbStrField('label', dtype=, doc='A unique label to identify the + code by') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +prepend_text: QbStrField('attributes.prepend_text', dtype=, doc='Bash + commands that should be prepended to the run line in all submit scripts for this + code') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +use_double_quotes: QbAnyField('attributes.use_double_quotes', dtype=, + doc='Whether the executable and arguments of the code in the submission script should + be escaped with single or double quotes') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +with_mpi: QbAnyField('attributes.with_mpi', dtype=typing.Optional[bool], doc='Whether + the executable should be run as an MPI program. This option can be left unspecified + in which case `None` will be set and it is left up to the calculation job plugin + or inputs whether to run with MPI') diff --git a/tests/orm/test_fields/fields_aiida.data.core.code.portable.PortableCode.yml b/tests/orm/test_fields/fields_aiida.data.core.code.portable.PortableCode.yml index b874b26466..e395c23ee2 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.code.portable.PortableCode.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.code.portable.PortableCode.yml @@ -1,27 +1,40 @@ -append_text: QbStrField('append_text', dtype=, 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) -default_calc_job_plugin: QbStrField('default_calc_job_plugin', dtype=typing.Optional[str], - is_attribute=True) -description: QbStrField('description', dtype=, is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], - is_attribute=False, is_subscriptable=True) -filepath_executable: QbStrField('filepath_executable', dtype=, is_attribute=True) -filepath_files: QbStrField('filepath_files', dtype=, is_attribute=False) -label: QbStrField('label', dtype=, 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) -pk: QbNumericField('pk', dtype=typing.Optional[int], is_attribute=False) -prepend_text: QbStrField('prepend_text', dtype=, is_attribute=True) -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) -use_double_quotes: QbField('use_double_quotes', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) -with_mpi: QbField('with_mpi', dtype=typing.Optional[bool], is_attribute=True) +append_text: QbStrField('attributes.append_text', dtype=, doc='Bash commands + that should be appended to the run line in all submit scripts for this code') +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +default_calc_job_plugin: QbStrField('attributes.default_calc_job_plugin', dtype=typing.Optional[str], + doc='Entry point name of the default plugin (as listed in `verdi plugin list aiida.calculations`)') +description: QbStrField('description', dtype=, doc='Human-readable description, + ideally including version and compilation environment') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filepath_executable: QbStrField('attributes.filepath_executable', dtype=, + doc='Relative filepath of executable with directory of code files') +label: QbStrField('label', dtype=, doc='A unique label to identify the + code by') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +prepend_text: QbStrField('attributes.prepend_text', dtype=, doc='Bash + commands that should be prepended to the run line in all submit scripts for this + code') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +use_double_quotes: QbAnyField('attributes.use_double_quotes', dtype=, + doc='Whether the executable and arguments of the code in the submission script should + be escaped with single or double quotes') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +with_mpi: QbAnyField('attributes.with_mpi', dtype=typing.Optional[bool], doc='Whether + the executable should be run as an MPI program. This option can be left unspecified + in which case `None` will be set and it is left up to the calculation job plugin + or inputs whether to run with MPI') diff --git a/tests/orm/test_fields/fields_aiida.data.core.dict.Dict.yml b/tests/orm/test_fields/fields_aiida.data.core.dict.Dict.yml index 710d253b4d..a9604c1897 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.dict.Dict.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.dict.Dict.yml @@ -1,21 +1,22 @@ -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) -value: QbDictField('value', dtype=typing.Dict[str, typing.Any], is_attribute=False, - is_subscriptable=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.enum.EnumData.yml b/tests/orm/test_fields/fields_aiida.data.core.enum.EnumData.yml index cfc3976079..0fb1fab2a8 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.enum.EnumData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.enum.EnumData.yml @@ -1,20 +1,22 @@ -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) -member: QbField('member', dtype=, 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) -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.float.Float.yml b/tests/orm/test_fields/fields_aiida.data.core.float.Float.yml index 457621f596..495d1c77eb 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.float.Float.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.float.Float.yml @@ -1,20 +1,24 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbNumericField('attributes.value', dtype=, doc='The value of + the float') diff --git a/tests/orm/test_fields/fields_aiida.data.core.folder.FolderData.yml b/tests/orm/test_fields/fields_aiida.data.core.folder.FolderData.yml index 5bee2ef441..6017283582 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.folder.FolderData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.folder.FolderData.yml @@ -1,19 +1,22 @@ -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.int.Int.yml b/tests/orm/test_fields/fields_aiida.data.core.int.Int.yml index 457621f596..f2b3c02085 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.int.Int.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.int.Int.yml @@ -1,20 +1,24 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbNumericField('attributes.value', dtype=, doc='The value of the + integer') diff --git a/tests/orm/test_fields/fields_aiida.data.core.jsonable.JsonableData.yml b/tests/orm/test_fields/fields_aiida.data.core.jsonable.JsonableData.yml index 1166fbc570..746bbb6a42 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.jsonable.JsonableData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.jsonable.JsonableData.yml @@ -1,21 +1,22 @@ -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) -obj: QbField('obj', dtype=, - 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) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.list.List.yml b/tests/orm/test_fields/fields_aiida.data.core.list.List.yml index 4edd6d3380..f3f1efbc09 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.list.List.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.list.List.yml @@ -1,20 +1,24 @@ -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) -value: QbArrayField('value', dtype=typing.List[typing.Any], is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbArrayField('attributes.value', dtype=typing.List[typing.Any], doc='Content + of the data') diff --git a/tests/orm/test_fields/fields_aiida.data.core.numeric.NumericType.yml b/tests/orm/test_fields/fields_aiida.data.core.numeric.NumericType.yml index 457621f596..73afa7f998 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.numeric.NumericType.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.numeric.NumericType.yml @@ -1,20 +1,23 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbAnyField('attributes.value', dtype=typing.Any, doc='The value of the data') diff --git a/tests/orm/test_fields/fields_aiida.data.core.orbital.OrbitalData.yml b/tests/orm/test_fields/fields_aiida.data.core.orbital.OrbitalData.yml index 5bee2ef441..6017283582 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.orbital.OrbitalData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.orbital.OrbitalData.yml @@ -1,19 +1,22 @@ -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.remote.RemoteData.yml b/tests/orm/test_fields/fields_aiida.data.core.remote.RemoteData.yml index 5086780659..70997be4a9 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.remote.RemoteData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.remote.RemoteData.yml @@ -1,20 +1,24 @@ -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) -remote_path: QbStrField('remote_path', dtype=typing.Optional[str], is_attribute=True) -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +remote_path: QbStrField('attributes.remote_path', dtype=typing.Optional[str], doc='Filepath + on the remote computer') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.RemoteStashData.yml b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.RemoteStashData.yml index 35e6e4188e..caa0774d83 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.RemoteStashData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.RemoteStashData.yml @@ -1,20 +1,24 @@ -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) -stash_mode: QbField('stash_mode', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +stash_mode: QbAnyField('attributes.stash_mode', dtype=, doc='The + mode with which the data was stashed') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.compress.RemoteStashCompressedData.yml b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.compress.RemoteStashCompressedData.yml index 40e941f933..bb46519494 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.compress.RemoteStashCompressedData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.compress.RemoteStashCompressedData.yml @@ -1,23 +1,30 @@ -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) -dereference: QbField('dereference', dtype=, is_attribute=True) -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) -source_list: QbArrayField('source_list', dtype=typing.List[str], is_attribute=True) -stash_mode: QbField('stash_mode', dtype=, is_attribute=True) -target_basepath: QbStrField('target_basepath', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +dereference: QbAnyField('attributes.dereference', dtype=, doc='The format + of the compression used when stashed') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +source_list: QbArrayField('attributes.source_list', dtype=typing.List[str], doc='The + list of source files that were stashed') +stash_mode: QbAnyField('attributes.stash_mode', dtype=, doc='The + mode with which the data was stashed') +target_basepath: QbStrField('attributes.target_basepath', dtype=, doc='The + the target basepath') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.custom.RemoteStashCustomData.yml b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.custom.RemoteStashCustomData.yml index 82e177e738..a7a7b3d112 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.custom.RemoteStashCustomData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.custom.RemoteStashCustomData.yml @@ -1,22 +1,28 @@ -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) -source_list: QbArrayField('source_list', dtype=typing.List[str], is_attribute=True) -stash_mode: QbField('stash_mode', dtype=, is_attribute=True) -target_basepath: QbStrField('target_basepath', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +source_list: QbArrayField('attributes.source_list', dtype=typing.List[str], doc='The + list of source files that were stashed') +stash_mode: QbAnyField('attributes.stash_mode', dtype=, doc='The + mode with which the data was stashed') +target_basepath: QbStrField('attributes.target_basepath', dtype=, doc='The + the target basepath') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.folder.RemoteStashFolderData.yml b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.folder.RemoteStashFolderData.yml index 82e177e738..063a139617 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.remote.stash.folder.RemoteStashFolderData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.remote.stash.folder.RemoteStashFolderData.yml @@ -1,22 +1,28 @@ -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) -source_list: QbArrayField('source_list', dtype=typing.List[str], is_attribute=True) -stash_mode: QbField('stash_mode', dtype=, is_attribute=True) -target_basepath: QbStrField('target_basepath', dtype=, is_attribute=True) -user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) -uuid: QbStrField('uuid', dtype=typing.Optional[str], is_attribute=False) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +source_list: QbArrayField('attributes.source_list', dtype=typing.List[str], doc='The + list of source files that were stashed') +stash_mode: QbAnyField('attributes.stash_mode', dtype=, doc='The + mode with which the data was stashed') +target_basepath: QbStrField('attributes.target_basepath', dtype=, doc='The + the target basepath') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.singlefile.SinglefileData.yml b/tests/orm/test_fields/fields_aiida.data.core.singlefile.SinglefileData.yml index 71f35c46f7..89105762aa 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.singlefile.SinglefileData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.singlefile.SinglefileData.yml @@ -1,21 +1,24 @@ -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) -content: QbField('content', dtype=, is_attribute=True) -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) -filename: QbStrField('filename', dtype=typing.Optional[str], is_attribute=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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filename: QbStrField('attributes.filename', dtype=, doc='The name of + the stored file') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.str.Str.yml b/tests/orm/test_fields/fields_aiida.data.core.str.Str.yml index 457621f596..5838729f64 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.str.Str.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.str.Str.yml @@ -1,20 +1,23 @@ -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) -value: QbField('value', dtype=typing.Any, is_attribute=True) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') +value: QbStrField('attributes.value', dtype=, doc='The value of the string') diff --git a/tests/orm/test_fields/fields_aiida.data.core.structure.StructureData.yml b/tests/orm/test_fields/fields_aiida.data.core.structure.StructureData.yml index 8e94962d01..afca3b4fb2 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.structure.StructureData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.structure.StructureData.yml @@ -1,25 +1,33 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], - 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) -kinds: QbArrayField('kinds', dtype=typing.Optional[typing.List[dict]], is_attribute=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) -pbc1: QbField('pbc1', dtype=, is_attribute=True) -pbc2: QbField('pbc2', dtype=, is_attribute=True) -pbc3: QbField('pbc3', dtype=, 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) -sites: QbArrayField('sites', dtype=typing.Optional[typing.List[dict]], is_attribute=True) -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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +cell: QbArrayField('attributes.cell', dtype=typing.Optional[typing.List[typing.List[float]]], + doc='The cell parameters') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +kinds: QbArrayField('attributes.kinds', dtype=typing.List[dict], doc='The kinds of + atoms') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pbc1: QbAnyField('attributes.pbc1', dtype=, doc='Whether periodic in + the a direction') +pbc2: QbAnyField('attributes.pbc2', dtype=, doc='Whether periodic in + the b direction') +pbc3: QbAnyField('attributes.pbc3', dtype=, doc='Whether periodic in + the c direction') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +sites: QbArrayField('attributes.sites', dtype=typing.List[dict], doc='The atomic sites') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.data.core.upf.UpfData.yml b/tests/orm/test_fields/fields_aiida.data.core.upf.UpfData.yml index 71f35c46f7..89105762aa 100644 --- a/tests/orm/test_fields/fields_aiida.data.core.upf.UpfData.yml +++ b/tests/orm/test_fields/fields_aiida.data.core.upf.UpfData.yml @@ -1,21 +1,24 @@ -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) -content: QbField('content', dtype=, is_attribute=True) -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) -filename: QbStrField('filename', dtype=typing.Optional[str], is_attribute=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) +attributes: QbAttributesField('attributes', dtype=, + doc='The node attributes') +computer: QbNumericField('computer', dtype=typing.Optional[int], doc='The PK of the + computer') +ctime: QbNumericField('ctime', dtype=, doc='The creation + time of the node') +description: QbStrField('description', dtype=, doc='The node description') +extras: QbDictField('extras', dtype=typing.Dict[str, typing.Any], doc='The node extras') +filename: QbStrField('attributes.filename', dtype=, doc='The name of + the stored file') +label: QbStrField('label', dtype=, doc='The node label') +mtime: QbNumericField('mtime', dtype=, doc='The modification + time of the node') +node_type: QbStrField('node_type', dtype=, doc='The type of the node') +pk: QbNumericField('pk', dtype=, doc='The primary key of the entity') +process_type: QbStrField('process_type', dtype=typing.Optional[str], doc='The process + type of the node') +repository_metadata: QbDictField('repository_metadata', dtype=typing.Dict[str, typing.Any], + doc='Virtual hierarchy of the file repository') +source: QbDictField('attributes.source', dtype=typing.Optional[dict], doc='Source + of the data') +user: QbNumericField('user', dtype=, doc='The PK of the user who owns + the node') +uuid: QbAnyField('uuid', dtype=, doc='The UUID of the node') diff --git a/tests/orm/test_fields/fields_aiida.node.data.Data.yml b/tests/orm/test_fields/fields_aiida.node.data.Data.yml index 5bee2ef441..cf63271c5e 100644 --- a/tests/orm/test_fields/fields_aiida.node.data.Data.yml +++ b/tests/orm/test_fields/fields_aiida.node.data.Data.yml @@ -1,18 +1,22 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], 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]], +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, +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, +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) diff --git a/tests/orm/test_fields/fields_aiida.node.process.ProcessNode.yml b/tests/orm/test_fields/fields_aiida.node.process.ProcessNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.ProcessNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.ProcessNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) diff --git a/tests/orm/test_fields/fields_aiida.node.process.calculation.CalculationNode.yml b/tests/orm/test_fields/fields_aiida.node.process.calculation.CalculationNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.calculation.CalculationNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.calculation.CalculationNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) diff --git a/tests/orm/test_fields/fields_aiida.node.process.calculation.calcfunction.CalcFunctionNode.yml b/tests/orm/test_fields/fields_aiida.node.process.calculation.calcfunction.CalcFunctionNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.calculation.calcfunction.CalcFunctionNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.calculation.calcfunction.CalcFunctionNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) diff --git a/tests/orm/test_fields/fields_aiida.node.process.calculation.calcjob.CalcJobNode.yml b/tests/orm/test_fields/fields_aiida.node.process.calculation.calcjob.CalcJobNode.yml index 8da4b34cb8..fc0b31b968 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.calculation.calcjob.CalcJobNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.calculation.calcjob.CalcJobNode.yml @@ -1,13 +1,15 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) detailed_job_info: QbDictField('detailed_job_info', dtype=typing.Optional[dict], is_attribute=True) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +extras: + QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False, is_subscriptable=True) imported: QbField('imported', dtype=typing.Optional[bool], is_attribute=True) job_id: QbStrField('job_id', dtype=typing.Optional[str], is_attribute=True) @@ -22,15 +24,20 @@ process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attrib process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) remote_workdir: QbStrField('remote_workdir', dtype=typing.Optional[str], is_attribute=True) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) -retrieve_list: QbArrayField('retrieve_list', dtype=typing.Optional[typing.List[str]], +retrieve_list: + QbArrayField('retrieve_list', dtype=typing.Optional[typing.List[str]], is_attribute=True) -retrieve_temporary_list: QbArrayField('retrieve_temporary_list', dtype=typing.Optional[typing.List[str]], +retrieve_temporary_list: + QbArrayField('retrieve_temporary_list', dtype=typing.Optional[typing.List[str]], is_attribute=True) -scheduler_lastchecktime: QbStrField('scheduler_lastchecktime', dtype=typing.Optional[str], +scheduler_lastchecktime: + QbStrField('scheduler_lastchecktime', dtype=typing.Optional[str], is_attribute=True) scheduler_state: QbStrField('scheduler_state', dtype=typing.Optional[str], is_attribute=True) sealed: QbField('sealed', dtype=, is_attribute=True) diff --git a/tests/orm/test_fields/fields_aiida.node.process.workflow.WorkflowNode.yml b/tests/orm/test_fields/fields_aiida.node.process.workflow.WorkflowNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.workflow.WorkflowNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.workflow.WorkflowNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) diff --git a/tests/orm/test_fields/fields_aiida.node.process.workflow.workchain.WorkChainNode.yml b/tests/orm/test_fields/fields_aiida.node.process.workflow.workchain.WorkChainNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.workflow.workchain.WorkChainNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.workflow.workchain.WorkChainNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False) diff --git a/tests/orm/test_fields/fields_aiida.node.process.workflow.workfunction.WorkFunctionNode.yml b/tests/orm/test_fields/fields_aiida.node.process.workflow.workfunction.WorkFunctionNode.yml index d8928ee1a4..6639d94fba 100644 --- a/tests/orm/test_fields/fields_aiida.node.process.workflow.workfunction.WorkFunctionNode.yml +++ b/tests/orm/test_fields/fields_aiida.node.process.workflow.workfunction.WorkFunctionNode.yml @@ -1,12 +1,14 @@ -attributes: QbDictField('attributes', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) +computer: QbNumericField('computer', dtype=typing.Optional[str], is_attribute=False) ctime: QbNumericField('ctime', dtype=typing.Optional[datetime.datetime], is_attribute=False) description: QbStrField('description', dtype=typing.Optional[str], is_attribute=False) exception: QbStrField('exception', dtype=typing.Optional[str], is_attribute=True) exit_message: QbStrField('exit_message', dtype=typing.Optional[str], is_attribute=True) exit_status: QbNumericField('exit_status', dtype=typing.Optional[int], is_attribute=True) -extras: QbDictField('extras', dtype=typing.Optional[typing.Dict[str, typing.Any]], +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) @@ -17,9 +19,11 @@ process_label: QbStrField('process_label', dtype=typing.Optional[str], is_attrib process_state: QbStrField('process_state', dtype=typing.Optional[str], is_attribute=True) process_status: QbStrField('process_status', dtype=typing.Optional[str], is_attribute=True) process_type: QbStrField('process_type', dtype=typing.Optional[str], is_attribute=False) -repository_content: QbDictField('repository_content', dtype=typing.Optional[dict[str, +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, +repository_metadata: + QbDictField('repository_metadata', dtype=typing.Optional[typing.Dict[str, typing.Any]], is_attribute=False) sealed: QbField('sealed', dtype=, is_attribute=True) user: QbNumericField('user', dtype=typing.Optional[int], is_attribute=False)