Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove pydantic-yaml as dependency #110

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ dependencies = [
"numpy",
"isoduration",
"pydantic",
"pydantic-yaml",
"ruamel.yaml",
"aiida-core>=2.5",
"aiida-workgraph==0.4.10",
"termcolor",
Expand Down
45 changes: 33 additions & 12 deletions src/sirocco/parsing/_yaml_data_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import typing
from dataclasses import dataclass, field
from datetime import datetime
from io import StringIO
from pathlib import Path
from typing import Annotated, Any, ClassVar, Literal

Expand All @@ -18,9 +19,11 @@
Discriminator,
Field,
Tag,
TypeAdapter,
field_validator,
model_validator,
)
from ruamel.yaml import YAML

from sirocco.parsing._utils import TimeUtils

Expand All @@ -41,16 +44,16 @@ class _NamedBaseModel(BaseModel):
>>> _NamedBaseModel(foo={})
_NamedBaseModel(name='foo')

>>> import pydantic_yaml, textwrap
>>> pydantic_yaml.parse_yaml_raw_as(
>>> import textwrap
>>> validate_yaml_content(
... _NamedBaseModel,
... textwrap.dedent('''
... foo:
... '''),
... )
_NamedBaseModel(name='foo')

>>> pydantic_yaml.parse_yaml_raw_as(
>>> validate_yaml_content(
... _NamedBaseModel,
... textwrap.dedent('''
... name: foo
Expand Down Expand Up @@ -479,15 +482,14 @@ class ConfigBaseData(_NamedBaseModel, ConfigBaseDataSpecs):
yaml snippet:

>>> import textwrap
>>> import pydantic_yaml
>>> snippet = textwrap.dedent(
... '''
... foo:
... type: "file"
... src: "foo.txt"
... '''
... )
>>> pydantic_yaml.parse_yaml_raw_as(ConfigBaseData, snippet)
>>> validate_yaml_content(ConfigBaseData, snippet)
ConfigBaseData(type=<DataType.FILE: 'file'>, src='foo.txt', format=None, computer=None, name='foo', parameters=[])


Expand Down Expand Up @@ -533,7 +535,6 @@ class ConfigData(BaseModel):
yaml snippet:

>>> import textwrap
>>> import pydantic_yaml
>>> snippet = textwrap.dedent(
... '''
... available:
Expand All @@ -546,7 +547,7 @@ class ConfigData(BaseModel):
... src: "bar.txt"
... '''
... )
>>> data = pydantic_yaml.parse_yaml_raw_as(ConfigData, snippet)
>>> data = validate_yaml_content(ConfigData, snippet)
>>> assert data.available[0].name == "foo"
>>> assert data.generated[0].name == "bar"

Expand Down Expand Up @@ -592,7 +593,6 @@ class ConfigWorkflow(BaseModel):
minimal yaml to generate:

>>> import textwrap
>>> import pydantic_yaml
>>> config = textwrap.dedent(
... '''
... cycles:
Expand All @@ -613,7 +613,7 @@ class ConfigWorkflow(BaseModel):
... src: some_task_output
... '''
... )
>>> wf = pydantic_yaml.parse_yaml_raw_as(ConfigWorkflow, config)
>>> wf = validate_yaml_content(ConfigWorkflow, config)

minimum programmatically created instance

Expand Down Expand Up @@ -698,13 +698,11 @@ def load_workflow_config(workflow_config: str) -> CanonicalWorkflow:

:param workflow_config: the string to the config yaml file containing the workflow definition
"""
from pydantic_yaml import parse_yaml_raw_as

config_path = Path(workflow_config)

content = config_path.read_text()

parsed_workflow = parse_yaml_raw_as(ConfigWorkflow, content)
parsed_workflow = validate_yaml_content(ConfigWorkflow, content)

# If name was not specified, then we use filename without file extension
if parsed_workflow.name is None:
Expand All @@ -713,3 +711,26 @@ def load_workflow_config(workflow_config: str) -> CanonicalWorkflow:
rootdir = config_path.resolve().parent

return canonicalize_workflow(config_workflow=parsed_workflow, rootdir=rootdir)


OBJECT_T = typing.TypeVar("OBJECT_T")


def validate_yaml_content(cls: type[OBJECT_T], content: str) -> OBJECT_T:
"""Parses the YAML content into a python object using generic types and subsequently validates it with pydantic.

Args:
cls (type[OBJECT_T]): The class type to which the parsed yaml content should
be validated. It must be compatible with pydantic validation.
content (str): The yaml content as a string.

Returns:
OBJECT_T: An instance of the specified class type with data parsed and
validated from the YAML content.

Raises:
pydantic.ValidationError: If the YAML content cannot be validated
against the specified class type.
ruamel.yaml.YAMLError: If there is an error in parsing the YAML content.
"""
return TypeAdapter(cls).validate_python(YAML(typ="safe", pure=True).load(StringIO(content)))