pythonpython-3.xpydantic

Specifying a different input type for a Pydantic model field (comma-separated string input as a list of strings)


Using Pydantic, how can I specify an attribute that has an input type different from its actual type?

For example I have a systems field that contains a list of systems (so a list of strings) and the user can provide this systems list as a comma separated string (e.g. "system1,system2"); then I use a validator to split this string into a list of strings.

The code below is doing that and it's working but the type hinting is wrong as the systems field is actually a list of strings, not a string; the validator is splitting the original string into a list of strings.

How can I fix this?

import typing

from pydantic import BaseSettings, Field, validator


class Config(BaseSettings):
    systems: str = Field([], description="list of systems as a comma separated list (e.g. 'sys1,sys2')")

    @validator("systems")
    def set_systems(cls, v) -> typing.List[str]:
        if v == "":
            return []
        systems = list(filter(None, v.split(",")))
        return systems


if __name__ == "__main__":
    c = Config(**{"systems": "foo,bar"})
    print(c)

Solution

  • Always annotate model fields with the types you actually want in your schema!

    If you want the field systems to be a list of strings, then annotate it accordingly. A comma-separated string is the exception after all. To allow it, use a mode='before' validator to intercept that string before the default field validators get to it (and raise an error). Then you can split it and return the list as you wish:

    from pydantic import BaseSettings, Field, field_validator
    
    
    class Config(BaseSettings):
        systems: list[str] = Field(default_factory=list)
    
        @field_validator("systems", mode="before")
        @classmethod
        def split_comma_separated(cls, v: object) -> object:
            if isinstance(v, str):
                v = v.strip()
                return [] if v == "" else v.split(",")
            return v
    
    
    if __name__ == "__main__":
        print(Config.parse_obj({"systems": "foo,bar"}))
        print(Config.parse_obj({"systems": ""}))
        print(Config())
    

    Output:

    systems=['foo', 'bar']
    systems=[]
    systems=[]