Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
e88d38c
Pin pydantic to `~=2.12`
edan-bainglass Jan 30, 2026
0c6eaad
Fix ORM pydantic schemas
edan-bainglass Oct 8, 2025
c8255e4
Add Kpoints constructor
edan-bainglass Oct 8, 2025
4112f5f
Fix `Node.from_model`
edan-bainglass Oct 8, 2025
a81f967
Missed cast
edan-bainglass Oct 8, 2025
81ffee8
Discard direct `orm` import from `cmdline` package
edan-bainglass Oct 8, 2025
c98257b
Allow unstored entity (de)serialization
edan-bainglass Oct 8, 2025
182312c
Update tests
edan-bainglass Oct 8, 2025
b801dc9
Fix computer type
edan-bainglass Oct 8, 2025
4ca7c08
Fix some typing issues
edan-bainglass Oct 9, 2025
73d2c80
Allow (de)serialization of unstored nodes
edan-bainglass Oct 10, 2025
9a85565
Add guard for unhandled attributes at parent `Data` constructor
edan-bainglass Oct 10, 2025
3592a75
Implement constructors for `KpointsData` and `BandsData`
edan-bainglass Oct 10, 2025
11cf1b2
Fix tests
edan-bainglass Oct 10, 2025
3f7c5ae
Serialize/validate arrays as numpy arrays, not bytes
edan-bainglass Oct 10, 2025
a443088
Fix typing
edan-bainglass Oct 10, 2025
595467e
Fix unhandled attributes check
edan-bainglass Oct 10, 2025
48f9978
Add `array_labels` back to `BandsData.Model` without constructor hand…
edan-bainglass Oct 10, 2025
33abe49
Nitpick some classes
edan-bainglass Oct 10, 2025
af96bc7
Fix docstring
edan-bainglass Oct 10, 2025
cb25d65
Remove user/ctime/mtime default factories from read-only fields
edan-bainglass Oct 11, 2025
481e155
Include attributes in node input model
edan-bainglass Oct 11, 2025
f357c64
Remove some unrelated changes
edan-bainglass Oct 12, 2025
81ed275
Make repo content serialization opt-in
edan-bainglass Nov 4, 2025
9384512
Restore array base64 serialization
edan-bainglass Nov 4, 2025
900d57c
Discard explicit quotation for annotations
edan-bainglass Nov 4, 2025
23f91d9
Add field validator to transform computer pk to label
edan-bainglass Nov 4, 2025
675d39c
Fix `orm_to_model` type
edan-bainglass Nov 4, 2025
a3f1d5e
Fix types
edan-bainglass Nov 4, 2025
1b49dbc
Mark `InstalledCode.Model.computer` as an attribute
edan-bainglass Nov 5, 2025
bab33ff
Fix `_prepare_yaml`
edan-bainglass Nov 5, 2025
8967476
Update regression tests
edan-bainglass Nov 5, 2025
d3411f3
Centralize model field collection
edan-bainglass Nov 5, 2025
73b664e
Move unhandled node attributes gate to `Node` class
edan-bainglass Nov 6, 2025
2e6d9a4
Add `attributes` to `Node` constructor
edan-bainglass Nov 6, 2025
1a4b201
Fix code fields
edan-bainglass Nov 6, 2025
d930552
Fix centralized model field collection
edan-bainglass Nov 6, 2025
5e8c3fc
Fix tests
edan-bainglass Nov 6, 2025
07a62f0
Enable `ArrayNode` numpy array POST payloads via model validator
edan-bainglass Nov 6, 2025
2da8f1e
Allow arguments in `orm_to_model`
edan-bainglass Nov 6, 2025
483eda7
Fix formatting
edan-bainglass Nov 6, 2025
0b4e86b
Fix `repository_path` default in portable code
edan-bainglass Nov 6, 2025
f5c2f72
Fix iterable array posting
edan-bainglass Nov 7, 2025
b9a2a06
Fix model inheritance
edan-bainglass Nov 7, 2025
036d335
Fix `from_serialized`
edan-bainglass Nov 7, 2025
0cf52df
Rename `InputModel` and `as_input_model` to `CreateModel` and `as_cre…
edan-bainglass Nov 8, 2025
876b439
Discard `exclude_from_cli`
edan-bainglass Nov 11, 2025
bfae063
Exclude `attributes` from ORM
edan-bainglass Nov 11, 2025
c8df44c
Discard `unstored` from `from_serialized`
edan-bainglass Nov 12, 2025
aacb542
Discard `unstored` serialization parameter
edan-bainglass Nov 12, 2025
2326315
Always pass `kwargs` to `orm_to_model`
edan-bainglass Nov 12, 2025
87678d6
Discard redundant class method in favor of correct model selection
edan-bainglass Nov 12, 2025
a6ca204
Use `is_stored` for model selection
edan-bainglass Nov 12, 2025
6239904
Restore serialization of node `extras`
edan-bainglass Nov 19, 2025
41912fd
Avoid using JSON Schema fields in `CreateModel` construction
edan-bainglass Nov 19, 2025
587af36
Fix `readOnly` logic in `MetadataField`
edan-bainglass Nov 19, 2025
ea6872f
Cleanup `CreateModel` definition
edan-bainglass Nov 30, 2025
5f48fac
Add serialization for `JsonSerializableProtocol` model field type
edan-bainglass Nov 30, 2025
c31ab3c
Use pydantic built-in base64 (de)serialization
edan-bainglass Dec 5, 2025
f2acabf
Update QB projection map aliasing
edan-bainglass Dec 6, 2025
5004f48
Add `exclude` parameter to `to_model`
edan-bainglass Dec 6, 2025
91f230e
Fix type hint
edan-bainglass Dec 6, 2025
d0d2c60
Fix QB warning
edan-bainglass Dec 6, 2025
7f2e74f
Move node attributes to `AttributesModel`
edan-bainglass Dec 9, 2025
ca11332
Draft
edan-bainglass Dec 10, 2025
1469b21
Discard `is_subscriptable`
edan-bainglass Dec 11, 2025
087663c
Discard `is_attribute`
edan-bainglass Dec 11, 2025
6939abd
Remove last period from orm model field descriptions
edan-bainglass Dec 12, 2025
eda9190
Discard left over `skip_read_only` parameter
edan-bainglass Dec 12, 2025
09a7aac
Discard write only parameter on `Node.Model` `attributes` and `extras`
edan-bainglass Dec 12, 2025
cec7142
FIX: Change CifData Model to AttributesModel
edan-bainglass Dec 12, 2025
cef96b1
Implement `OrmModel` as a root for `Entity.Model` and `Node.Attribute…
edan-bainglass Dec 13, 2025
c66e828
Sort of working
edan-bainglass Dec 14, 2025
55b7653
Add opt-in mechanism for hiding large fields
edan-bainglass Dec 14, 2025
7a0b533
Fix model tests
edan-bainglass Dec 16, 2025
e112b68
Make array and singlefile write-only fields optional
edan-bainglass Dec 16, 2025
ba739fb
Remove private prefix from nested helper functions
edan-bainglass Dec 16, 2025
2e84d86
Update array/singlefile handling
edan-bainglass Dec 17, 2025
d0856f6
Make `CreateModel` derivation private and idempotent
edan-bainglass Dec 17, 2025
26a1e02
Update tests
edan-bainglass Dec 17, 2025
b88ebd8
Fix mypy
edan-bainglass Dec 17, 2025
e3cb6ea
Update tests
edan-bainglass Dec 17, 2025
086042c
Fix typing
edan-bainglass Dec 17, 2025
4b5b9c0
Fix code export
edan-bainglass Dec 18, 2025
2f42449
Fix portable orm-to-model mapping
edan-bainglass Dec 18, 2025
c248162
Fix code show
edan-bainglass Dec 18, 2025
0c1f9bb
Fix test
edan-bainglass Dec 18, 2025
680d825
Fix default value fetching in model-to-orm conversion
edan-bainglass Dec 18, 2025
63285c0
Minor post-self-review updates
edan-bainglass Dec 18, 2025
b56fd34
Make `attributes` optional if `AttributesModel` has no required fields
edan-bainglass Dec 19, 2025
9b789b6
Fix `StructureData` model and constructor
edan-bainglass Dec 19, 2025
307ab65
Fix `Node` subclass model naming
edan-bainglass Dec 19, 2025
ca3799e
Also fix default value fetching in orm-to-model conversion
edan-bainglass Dec 19, 2025
e439bec
Fix `attributes` optional-status mutation
edan-bainglass Dec 19, 2025
95c88f3
Fix singlefile validation
edan-bainglass Dec 19, 2025
4e0b410
Remove serialization warning
edan-bainglass Dec 21, 2025
c996650
Discard `EntityModelType`
edan-bainglass Dec 22, 2025
fec8eec
Mark `Group.Model.extras` with `may_be_large=True`
edan-bainglass Dec 22, 2025
2b2b8af
Update node computer field types
edan-bainglass Dec 23, 2025
e82b46f
Change `Log.Model.dbnode_id` to `node` and provide constructor logic
edan-bainglass Dec 23, 2025
02d0c68
Update tests
edan-bainglass Dec 23, 2025
cd948d4
Discard serialization warning on creation
edan-bainglass Dec 25, 2025
5d8218a
Fix typing
edan-bainglass Dec 25, 2025
2a3bf0d
Use `ConfigDict` in `Dict.AttributesModel`
edan-bainglass Dec 25, 2025
b970db4
Correct `AbstractCode` constructor boolean field handling
edan-bainglass Dec 25, 2025
28fa87e
Move installed code computer field to attributes model and mark as wr…
edan-bainglass Dec 25, 2025
885dd45
Update tests
edan-bainglass Dec 25, 2025
2c6b86a
Fix tests
edan-bainglass Dec 25, 2025
31c8853
Discard ad hoc computer loaders and use utility
edan-bainglass Dec 25, 2025
a22b650
Add `identity_field` to entities
edan-bainglass Dec 27, 2025
ba44c00
Exclude null values when serializing
edan-bainglass Dec 30, 2025
9b0ef7a
Add `collection_type` on entity collections
edan-bainglass Dec 31, 2025
cfdc17a
Make UUID serialization explicit
edan-bainglass Jan 1, 2026
25f8cbf
Add examples to entity models
edan-bainglass Jan 3, 2026
968cbce
Fix `Int.AttributesModel` inheritance
edan-bainglass Feb 2, 2026
9544ffb
Fix dynamic model assignment
edan-bainglass Feb 2, 2026
264d4e9
Update tests
edan-bainglass Feb 2, 2026
f386fc4
Fix mypy
edan-bainglass Feb 2, 2026
b3dc49e
Fix mypy
edan-bainglass Feb 3, 2026
e3769eb
Fix tests
edan-bainglass Feb 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/source/nitpick-exceptions
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies:
- pgsu~=0.3.0
- psutil~=7.0
- psycopg[binary]<4,>=3.0.2
- pydantic~=2.6
- pydantic~=2.12
- pytz~=2021.1
- pyyaml~=6.0
- requests~=2.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies = [
'pgsu~=0.3.0',
'psutil~=7.0',
'psycopg[binary]>=3.0.2,<4',
'pydantic~=2.6',
'pydantic~=2.12',
'pytz~=2021.1',
'pyyaml~=6.0',
'requests~=2.0',
Expand Down
58 changes: 21 additions & 37 deletions src/aiida/cmdline/commands/cmd_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,17 +27,22 @@
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')
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}')

Expand Down Expand Up @@ -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())])
Expand Down
25 changes: 19 additions & 6 deletions src/aiida/cmdline/groups/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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'):
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/aiida/common/datastructures.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
135 changes: 112 additions & 23 deletions src/aiida/common/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
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.fields import FieldInfo
from pydantic_core import PydanticUndefined

if t.TYPE_CHECKING:
from pydantic import BaseModel

from aiida.orm import Entity


def get_metadata(field_info: t.Any, key: str, default: t.Any | None = None) -> t.Any:
def get_metadata(field_info: FieldInfo, key: str, default: t.Any | None = None) -> t.Any:
"""Return a the metadata of the given field for a particular key.

:param field_info: The field from which to retrieve the metadata.
Expand All @@ -23,31 +21,109 @@ 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.
"""

cached = cls.__dict__.get('_AIIDA_CREATE_MODEL')
if isinstance(cached, type) and issubclass(cached, OrmModel):
return cached

CreateModel = create_model( # noqa: N806
'CreateModel',
__base__=cls,
__module__=cls.__module__,
__qualname__=cls.__qualname__.replace('Model', 'CreateModel'),
)
CreateModel.model_config['extra'] = 'ignore'
CreateModel.model_config['json_schema_extra'] = {
**CreateModel.model_config.get('json_schema_extra', {}), # type: ignore[dict-item]
'additionalProperties': False,
}

CreateModel.model_rebuild(force=True)

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)

# Make subsequent calls idempotent for this specific class
cls._AIIDA_CREATE_MODEL = CreateModel # type: ignore[attr-defined]

CreateModel.model_rebuild(force=True)
return CreateModel


def MetadataField( # noqa: N802
default: t.Any = PydanticUndefined,
*,
priority: int = 0,
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')

Expand All @@ -66,20 +142,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 (
Expand All @@ -89,10 +179,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})
Expand Down
Loading
Loading