12

I have the following Pydantic model:

class Report(BaseModel):
    id: int
    name: str
    grade: float = None
    proportion: float = None

    @validator('*', pre=True)
    def blank_strings(cls, v):
        print(v)
        if v == "":
            return None
        return v

My goal here is to be able to ignore empty strings as null values, but it doesn't seem to work.

Report(id=5,name="Steve",grade=0.5) creates an instance where proportion=None but... Report(id=5,name="Steve",grade=0.5,proportion="") throws the error value is not a valid float (type=type_error.float). How can I get it to give the same result as the first case?

pyjamas
  • 4,608
  • 5
  • 38
  • 70
  • 2
    I don't get the error when running your exact code with pydantic==1.3. Which pydantic version are you running? – Nicola Jan 04 '20 at 18:09
  • @Nicola I'm using 0.32.2 which is a requirement for usage with the fastapi package... I tried 1.3 and it worked as you say. There's an open issue on fastapi to support Pydantic v1.0, but I'm not sure what to do in the meantime. Anyone have any workaround suggestion to get my above code working on Pydantic 0.32.2? – pyjamas Jan 08 '20 at 18:40
  • 1
    Fastapi supports Pydantic v.1 since release 0.44.0: https://fastapi.tiangolo.com/release-notes/#0440 – Nicola Jan 09 '20 at 11:16
  • 1
    @Nicola Indeed it does, thanks! Solved my problem – pyjamas Jan 09 '20 at 15:07

3 Answers3

16

pydantic==1.9.0

Best way

from pydantic import BaseModel, Field

"""
The first argument of Field is 'default', refers to default value of attribute, 
if is '...' it means is 'required', if no value is assigned, it defaults to 'undefined' 
""" 

class Report(BaseModel):
    id: int = Field(..., gt=0)  # ... is required, and gt is 'greater than'
    name: str = Field(..., min_length=1)
    grade: float = Field(0.0, gt=0)
    proportion: float = Field(0.0, gt=0)

Another way (I do not recommend it but you will probably see it somewhere else.)

from decimal import Decimal
from pydantic import BaseModel, constr, condecimal, conint
        
class Report(BaseModel):
    id: conint(gt=0)
    name: constr(min_length=1)
    grade: condecimal(gt=Decimal(0.0)) = 0.0
    proportion: condecimal(gt=Decimal(0.0)) = 0.0

Check: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types

Edit: if you use MyPy, or you want to use the same validators, you must create a class that inherits from ConstrainedStr, ConstrainedInt, etc...

from decimal import Decimal
from pydantic import ConstrainedStr, ConstrainedInt, ConstrainedDecimal
        
class IdValidator(ConstrainedInt):
    gt=0

class NameValidator(ConstrainedStr):
    min_length=1

class DecimalValidator(ConstrainedDecimal):
    gt=Decimal(0.0)

class Report(BaseModel):
    id: IdValidator
    name: NameValidator
    grade: DecimalValidator = None
    proportion: DecimalValidator = None
Facundo Padilla
  • 190
  • 1
  • 4
8

This works for me with pydantic==1.6.1:

class Report(BaseModel):
    id: int
    name: str
    grade: Optional[float]
    proportion: Optional[float]

    @validator('proportion', pre=True)
    def blank_string(value, field):
        if value == "":
            return None
        return value

The Optional creates type constraint equivalent to Union[float, None].

Converting the blank string to None satisfies the type constraint.

Sean Summers
  • 2,514
  • 19
  • 26
0

You can set configuration settings to ignore blank strings. Thought it is also good practice to explicitly remove empty strings:

class Report(BaseModel):
    id: int
    name: str
    grade: float = None
    proportion: float = None

     class Config:
        # Will remove whitespace from string and byte fields
        anystr_strip_whitespace = True

    @validator('proportion', pre=True)
    def remove_blank_strings(cls, v):
        """Removes whitespace characters and return None if empty"""
        if isinstance(v, str):
            v = v.strip()
        if v == "":
            return None
        return v
Yaakov Bressler
  • 9,056
  • 2
  • 45
  • 69