Search code examples
python-3.xpydantic

How to define a pydantic.BaseModel that allows both fixed words and a pattern?


I have the following code

from pydantic import BaseModel


class Product(BaseModel):
    name: str
    tags: list[str]  # Accepted tags: "mean", "std", "extra_<float with 1 decimal point>".

The tags is a list of str. But it only accepts certain words.

  1. fixed words: mean and std.
  2. flexible words: it must start with extra_ and be followed by a float with 1 decimal point. For example extra_1.0, extra_2.5. But extra_3.14 is not allowed.

Can you please show me how to implement it?

Thanks.


Solution

  • EDIT: Thanks to Yurii Motov

    1st solution
    You can use Union, Literal, Annotated and StringConstraints
    (not constr, due to: Pydantic docs, constr will be deleted in 3.0 version)

    from typing_extensions import Annotated, Union, Literal 
    from pydantic import BaseModel, StringConstraints
    
    class Product(BaseModel):
        name: str
        tags: list[Union[Literal['mean', 'std'],
                      Annotated[str, StringConstraints(pattern=r'exp\_[0-9]\.[0-9]')]]]
    

    if input string doesn't match expectations this error will raise:

    Input should be 'mean' or 'std' [type=literal_error,
    input_value='template', input_type=str]...
    String should match pattern 'exp\_[0-9]\.[0-9]'
    [type=string_pattern_mismatch, input_value='template', input_type=str]
    

    or a similar one

    2nd solution
    Also you can use Annotated and Function validation (docs: Pydantic docs), see here:
    My simple example:

    def handler(s: str):
        #write the desired handler
        if(not s == 'template'):
            raise TypeError('Not valid string')
        return s
    
    MyStr = Annotated[str, BeforeValidator(handler)]
    
    class Product(BaseModel):
        name: str
        tags: list[MyStr]
    
    # this will raise TypeError, because of int input
    # product = Product(name = 'name', tags = [1])
    
    # this will also raise TypeError, that inside handler
    # product = Product(name = 'name', tags = ['1','template'])
    
    # this is totally fine
    product = Product(name = 'name', tags = ['template','template'])
    

    but you should rewrite handler