From d90219b03ef7956df2a7716988a5193f18bc67ab Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Wed, 9 Jul 2025 14:04:45 -0700 Subject: [PATCH 1/2] Line of Dict of Elements Overwrite validation and model dump. --- schema/BaseElement.py | 6 ++--- schema/Line.py | 56 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/schema/BaseElement.py b/schema/BaseElement.py index 825635e..43b63b6 100644 --- a/schema/BaseElement.py +++ b/schema/BaseElement.py @@ -1,5 +1,5 @@ from pydantic import BaseModel, ConfigDict -from typing import Literal, Optional +from typing import Literal class BaseElement(BaseModel): @@ -12,5 +12,5 @@ class BaseElement(BaseModel): # not only when an instance of BaseElement is created model_config = ConfigDict(validate_assignment=True) - # Unique element name - name: Optional[str] = None + # element name + name: str diff --git a/schema/Line.py b/schema/Line.py index e1ad070..48c1580 100644 --- a/schema/Line.py +++ b/schema/Line.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, field_validator from typing import Annotated, List, Literal, Union from schema.BaseElement import BaseElement @@ -29,6 +29,60 @@ class Line(BaseModel): ] ] + @field_validator("line", mode="before") + @classmethod + def parse_list_of_dicts(cls, value): + """This method inserts the key of the one-key dictionary into + the name attribute of the elements""" + if not isinstance(value, list): + raise TypeError("line must be a list") + + if value and isinstance(value[0], BaseModel): + # Already a list of models; nothing to do + return value + + # we expect a list of dicts or strings + elements = [] + for item_dict in value: + # an element is either a reference string to another element or a dict + if isinstance(item_dict, str): + raise RuntimeError("Reference/alias elements not yet implemented") + + elif isinstance(item_dict, dict): + if not (isinstance(item_dict, dict) and len(item_dict) == 1): + raise ValueError( + f"Each line element must be a dict with exactly one key, the name of the element, but we got: {item_dict!r}" + ) + [(name, fields)] = item_dict.items() + + if not isinstance(fields, dict): + raise ValueError( + f"Value for element key '{name}' must be a dict (got {fields!r})" + ) + + # Insert the name into the fields dict + fields["name"] = name + elements.append(fields) + return elements + + def model_dump(self, *args, **kwargs): + """This makes sure the element name property is moved out and up to a one-key dictionary""" + # Use default dump for non-line fields + data = super().model_dump(*args, **kwargs) + + # Reformat 'line' field as list of single-key dicts + new_line = [] + for elem in self.line: + # The element's name is the dict key; dump all other fields except 'name' + elem_dict = elem.model_dump(exclude={"name"}, **kwargs) + name = getattr(elem, "name", None) + if name is None: + raise ValueError("Element missing 'name' attribute") + new_line.append({name: elem_dict}) + + data["line"] = new_line + return data + # Avoid circular import issues Line.model_rebuild() From fd931a1320385344ca4c307d122a61f93519db3c Mon Sep 17 00:00:00 2001 From: Axel Huebl Date: Wed, 16 Jul 2025 08:16:44 -0700 Subject: [PATCH 2/2] Fix BaseElement (de)serialization Co-authored-by: Edoardo Zoni --- schema/BaseElement.py | 9 +++++++++ schema/Line.py | 9 +++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/schema/BaseElement.py b/schema/BaseElement.py index 43b63b6..9b361b2 100644 --- a/schema/BaseElement.py +++ b/schema/BaseElement.py @@ -14,3 +14,12 @@ class BaseElement(BaseModel): # element name name: str + + def model_dump(self, *args, **kwargs): + """This makes sure the element name property is moved out and up to a one-key dictionary""" + elem_dict = super().model_dump(*args, **kwargs) + name = elem_dict.pop("name", None) + if name is None: + raise ValueError("Element missing 'name' attribute") + data = [{name: elem_dict}] + return data diff --git a/schema/Line.py b/schema/Line.py index 48c1580..b1e0b13 100644 --- a/schema/Line.py +++ b/schema/Line.py @@ -73,12 +73,9 @@ def model_dump(self, *args, **kwargs): # Reformat 'line' field as list of single-key dicts new_line = [] for elem in self.line: - # The element's name is the dict key; dump all other fields except 'name' - elem_dict = elem.model_dump(exclude={"name"}, **kwargs) - name = getattr(elem, "name", None) - if name is None: - raise ValueError("Element missing 'name' attribute") - new_line.append({name: elem_dict}) + # Use custom dump for each line element + elem_dict = elem.model_dump(**kwargs)[0] + new_line.append(elem_dict) data["line"] = new_line return data