21

I would like to create pydantic model to validate users form. one of my model values should be validated from a list of names. I succeed to create the model using enum as follow:

from enum import Enum
class Fruit(str, Enum):
    APPLE = 'apple'
    BANANA = 'banana'
    MELON = 'melon'

from pydantic import BaseModel
class UserForm(BaseModel):
    fruit: Fruit
    name: str

Now I would like to switch the enum to a list of values in my code:

fruit = ['apple','banana','melon']

How can I manage to do so?

tnx

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
OrFeldman
  • 283
  • 1
  • 3
  • 6

4 Answers4

17

I am proposing an elegant solution.

from pydantic import BaseModel
from typing import List
from enum import Enum


class Fruit(str, Enum):
    APPLE = 'apple'
    BANANA = 'banana'
    MELON = 'melon'


class UserForm(BaseModel):
    fruits: List[Fruit]
    name: str

And that's it.

  • you don't need to write your own validator
  • just tell pydantic you need a list of Fruit object, and it will do it for you

Check the above code:

put the above code in a file main.py.

Run

python -i main.py
>>> uf = UserForm(fruits=['apple','banana'],name='hello')
>>> uf
UserForm(fruits=[<Fruit.APPLE: 'apple'>, <Fruit.BANANA: 'banana'>], name='hello')


>>> af = UserForm(fruits=['monkey','apple'],name='hello')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for UserForm
fruits -> 0
  value is not a valid enumeration member; permitted: 'apple', 'banana', 'melon' (type=type_error.enum; enum_values=[<Fruit.APPLE: 'apple'>, <Fruit.BANANA: 'banana'>, <Fruit.MELON: 'melon'>])
>>> 

pydantic will raise an error, as monkey is not in fruits.

aahnik
  • 1,661
  • 1
  • 11
  • 29
15

You could do this also by means of a list of Literal type. Like so:

import pydantic
from typing import Literal, List

class M(pydantic.BaseModel):
    fruits: List[Literal["apple", "orange"]]

print(M.parse_obj({"fruits":["apple", "orange"]}))  # OK fruits=['apple', 'orange']
print(M.parse_obj({"fruits":["apple", "orange", "potato"]}))  # Error unexpected value potato
alex_noname
  • 26,459
  • 5
  • 69
  • 86
6

You can use validator in the following way:

 from pydantic import BaseModel, ValidationError, validator
 class UserForm(BaseModel):
    fruit: str
    name: str
    @validator('fruit')
    def fruit_must_be_in_fruits(cls,fruit):
      fruits=['apple','banana','melon']
      if fruit not in fruits:
        raise ValueError(f'must be in {fruits}')
      return fruit
 try:
    UserForm(fruit="apple",name="apple")
 except ValidationError as e:
    print(e)

It will raise a validation error if it doesn't match the criteria.

Walid
  • 718
  • 5
  • 13
  • Hi, this is great thank you. But it still not working as I would like. I am using this UserForm in fastapi and that means that while swagger/docs will automatically suggest all the values for the user when I am using the enum solution it will not suggest them in this solution. – OrFeldman Dec 27 '20 at 13:42
1

You can get informations about the enum by its .__members__ dictionary - here you can simply iterate it keys though:

from enum import Enum
class Fruit(str, Enum):
    APPLE = 'apple'
    BANANA = 'banana'
    MELON = 'melon'

# only need __members__ if you need more infos about it

print(Fruit.__members__)

# you do not need the __members__ if you just want the keys
print([name.lower() for name in Fruit])

Output:

# enums __members__ dictionary
{'APPLE': <Fruit.APPLE: 'apple'>, 
 'BANANA': <Fruit.BANANA: 'banana'>, 
 'MELON': <Fruit.MELON: 'melon'>} 

# lower keys
['apple', 'banana', 'melon']
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69