1

The objectives are:

  • Having data stored in the simplified list form or verbose dictionary/objects form.
  • Having model.Schema() defining both formats.
  • Use data within application in verbose objects form.

According to this question I have tried to define the root_validator on Hobbies:

#!/usr/bin/env python3.10

from pydantic import BaseModel, root_validator


H = {
"name": "Mark",
"hobbies": [
        "Hobby1",
        "Hobby2|Description of hobby 2"
    ]
}


class Hobby(BaseModel):
    name: str
    description: str | None


class Hobbies(BaseModel):
    __root__: list[Hobby | str]

    @root_validator(pre=True)
    def convert_string_to_dict(cls, values):
        """
        Converts strings to a dictionary, with an optional fields, divided by
        '|' character.
        """
        for idx, value in values:
            if not isinstance(value, str):
                continue
            value = dict(zip(Hobby.__fields__, value.split("|", maxsplit=1)))
            values[idx] = value
        return values


class Hobbyst(BaseModel):
    name: str
    hobbies: Hobbies

hobbyst = Hobbyst(**H)

print(hobbyst.dict())

I axpect the script to print:

{'name': 'Mark', hobbies: [{'name': 'Hobby1', 'description': None}, {'name': 'Hobby2', 'description': 'Description of hobby 2'}]}

however I have got the following error:

Traceback (most recent call last):
  File "./playground.py", line 41, in <module>
    hobbyst = Hobbyst(**H)
  File "pydantic/main.py", line 342, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for Hobbyst
hobbies -> __root__
  too many values to unpack (expected 2) (type=value_error)

[EDIT]

I have sligtly modified the accepted answer:

    @root_validator(pre=True)
    def convert_string_to_dict(cls, values: dict):
        """
        Converts strings to a dictionary, with an optional fields, divided by
        '|' character.
        """
        for idx, value in enumerate(values["__root__"]):
            if not isinstance(value, str):
                continue
            value = dict(zip(Hobby.__fields__, value.split("|", maxsplit=1)))
            values["__root__"][idx] = value
        return values

I have forgotten about enumerate and didn't know, that a dictionary instead of list will be passed to a root validator.

  • 1
    This is not a [minimal reproducible example](https://stackoverflow.com/help/mcve). I suggest you 1) get rid of the irrelevant/distracting YAML context, 2) present us with code that can be copied and pasted to reproduce the error you showed, and 3) show the output you want. – Daniil Fajnberg Jan 02 '23 at 16:42
  • Thank you for pointing this out. I have changed my question accordingly. – Piotr Sawicki Jan 03 '23 at 08:27

1 Answers1

1

How about doing this?

import typing

from pprint import pformat

from pydantic import BaseModel
from pydantic import root_validator


H = {
    "name": "Mark",
    "hobbies": [
        "Hobby1",
        "Hobby2|Description of hobby 2",
        {"name": "Hobby3", "description": "Description of hobby 3"},
    ],
}


class Hobby(BaseModel):
    name: str
    description: typing.Union[str, None]


class Hobbies(BaseModel):
    __root__: typing.Union[typing.List[str], typing.List[Hobby]]

    @root_validator(pre=True)
    @classmethod
    def convert_string_to_dict(cls, values: typing.Dict):
        _hobbies = values.get("__root__", [])
        hobbies = []
        if _hobbies:
            for hobbies_obj in _hobbies:
                if not isinstance(hobbies_obj, str):
                    hobbies.append(hobbies_obj)
                    continue
                name, *desc = hobbies_obj.split("|", maxsplit=1)
                hobbies.append({"name": name, "description": desc[0] if desc else None})
            values.update({"__root__": hobbies})
        return values


class Hobbyst(BaseModel):
    name: str
    hobbies: Hobbies


hobbyst = Hobbyst(**H)
print(f"dhobbyst=\n{pformat(hobbyst.dict())}")
print(f"schema=\n{pformat(hobbyst.schema())}")

output:

dhobbyst=
{'hobbies': [{'description': None, 'name': 'Hobby1'},
             {'description': 'Description of hobby 2', 'name': 'Hobby2'},
             {'description': 'Description of hobby 3', 'name': 'Hobby3'}],
 'name': 'Mark'}
schema=
{'definitions': {'Hobbies': {'anyOf': [{'items': {'type': 'string'},
                                        'type': 'array'},
                                       {'items': {'$ref': '#/definitions/Hobby'},
                                        'type': 'array'}],
                             'title': 'Hobbies'},
                 'Hobby': {'properties': {'description': {'title': 'Description',
                                                          'type': 'string'},
                                          'name': {'title': 'Name',
                                                   'type': 'string'}},
                           'required': ['name'],
                           'title': 'Hobby',
                           'type': 'object'}},
 'properties': {'hobbies': {'$ref': '#/definitions/Hobbies'},
                'name': {'title': 'Name', 'type': 'string'}},
 'required': ['name', 'hobbies'],
 'title': 'Hobbyst',
 'type': 'object'}
  • 1
    Regarding the aswer: *usage of typing* - I prefer to use new, python 3.10 types annotations - this part was correct. *usage of @classmethod - this is redundant as `root_validator` already is a classmethod. *building dict* - the `zip` method covers length checks. My error was really in lack of `enumerate` and accessing the `values["__root__"] element. – Piotr Sawicki Jan 03 '23 at 10:47