-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ORM: Switch to
pydantic
for code schema definition
The `verdi code create` command dynamically generates a subcommand for each registered entry point that is a subclass of the `AbstractCode` data plugin base class. The options for each subcommand are generated automatically for each plugin using the `DynamicEntryPointCommandGroup`. When first developed, this `click.Group` subclass would rely on the plugin defining the `get_cli_options` method to return a dictionary with a spec for each of the options. This specification used an ad-hoc custom schema making it not very useful for any other applications. Recently, the class added support for using `pydantic` models to define the specification instead. This was already used for plugins of storage backends. Here, the `AbstractCode` and its subclasses are also migrated to use `pydantic` instead to define their model. Most of the data that is required to create `click` options from the pydantic model can be communicated using the default properties of pydantic's `Field` class. However, there was the need for a few additional metadata properties: * `priority`: To control the order in which options are prompted for. This used to be controlled by the `_get_cli_options` of each plugin. It could define the options in the order required and could also determine whether they came before or after the options that could potentially be inherited from a base class. The way the pydantic models work, the fields of a subclass will always come _after_ those of the base class and there is no way to control this. * `short_name`: The short form of the option name. The option name is derived from the `title` attribute of the `Field`. In addition to a full name, options often want to provide a short form option. Since there is no algorithmic method of deducing this from the title, a dedicated metadata keyword is added. * `option_cls`: To customize the class to be used to create the option. This can be used by options that should use a different subclass of the `click.Option` base class. The `aiida.common.pydantic.MetadataField` utility function is added which provides a transparent way to define these metadata arguments when declaring a field in the model. The alternative is to use `Annotated` but this quickly makes the model difficult to read if multiple metadata are provided. The changes introduce _almost_ no difference in behavior of the `verdi code create` command. There is one exception and that is that the callbacks of the options are now replaced by the validators of the models. The downside is that the validators are only called once all options are specified, whereas the callbacks would be called immediately once the respective option was defined. This is not really a problem except for the `label` of the `InstalledCode`. The callback would be called immediately and so if an invalid label was provided during an interactive session, the user would be immediately prompted to provide a new label. It is not clear how this behavior can be reproduced using the pydantic validators.
- Loading branch information
Showing
8 changed files
with
316 additions
and
243 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""Utilities related to ``pydantic``.""" | ||
from __future__ import annotations | ||
|
||
import typing as t | ||
|
||
from pydantic import Field | ||
|
||
|
||
def MetadataField( # noqa: N802 | ||
default: t.Any | None = None, | ||
*, | ||
priority: int = 0, | ||
short_name: str | None = None, | ||
option_cls: t.Any | None = None, | ||
**kwargs, | ||
): | ||
"""Return a :class:`pydantic.fields.Field` instance with additional metadata. | ||
.. code-block:: python | ||
class Model(BaseModel): | ||
attribute: MetadataField('default', priority=1000, short_name='-A') | ||
This is a utility function that constructs a ``Field`` instance with an easy interface to add additional metadata. | ||
It is possible to add metadata using ``Annotated``:: | ||
class Model(BaseModel): | ||
attribute: Annotated[str, {'metadata': 'value'}] = Field(...) | ||
However, when requiring multiple metadata, this notation can make the model difficult to read. Since this utility | ||
is only used to automatically build command line interfaces from the model definition, it is possible to restrict | ||
which metadata are accepted. | ||
:param priority: Used to order the list of all fields in the model. Ordering is done from small to large priority. | ||
: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. | ||
""" | ||
field_info = Field(default, **kwargs) | ||
|
||
for key, value in (('priority', priority), ('short_name', short_name), ('option_cls', option_cls)): | ||
if value is not None: | ||
field_info.metadata.append({key: value}) | ||
|
||
return field_info |
Oops, something went wrong.