简体   繁体   中英

pydantic custom data type for phone number : value_error.missing

mysql schema for user

user_id     binary(16) 
first_name  varchar(32) 
last_name   varchar(32) 
email       varchar(255)
phone       varchar(32) 
role        enum('member','admin','operator')
created_on  datetime
updated_on  datetime
id   varchar(32)

my pydantic model is

class UserPatchEntity(BaseModel):
    user_id: Optional[UUID]
    first_name: Optional[str] = Field(min_length=1, max_length=32)
    last_name: Optional[str] = Field(min_length=1, max_length=32)
    email: Optional[EmailStr]
    phone: Optional[Phone]---------------> HERE
    role:  Optional[RoleType]

i want to create a custom datatype for phone number...so i can use it at multiple places....i am trying like below

class Phone(BaseModel):
    phone: str
    @validator('phone')
    def phone_validation(cls, v):
        phone = v.phone.get("phone")
        logger.debug(f"phone in 2 validator:{v}")
        regex = r"^(\+)[1-9][0-9\-\(\)\.]{9,15}$"
        if v and not re.search(regex, v, re.I):
            raise ValueError("Phone Number Invalid.")
        return v
    class Config:
        orm_mode = True
        use_enum_values = True

via postman, i am sending

{
    "phone": "+917777777777"
}

i see 400 error in postman

[
    {
        "loc": [
            "phone",
            "phone"
        ],
        "msg": "field required",
        "type": "value_error.missing"
    }
]

Any inputs on what i am doing wrong above

This doesn't work because you are passing in:

{
    "phone": "+917777777777"
}

while the model has the structure UserPatchEntity.Phone.phone .

In order to make this work, I see two options:

1. Adapt the JSON Body (and fix the validator )

Fixing the validator :

class Phone(BaseModel):
    phone: str
    @validator("phone")
    def phone_validation(cls, v):
        # phone = v.phone.get("phone") #  <-- This line needs to be removed.
        logger.debug(f"phone in 2 validator:{v}")
        regex = r"^(\+)[1-9][0-9\-\(\)\.]{9,15}$"
        if v and not re.search(regex, v, re.I):
            raise ValueError("Phone Number Invalid.")
        return v

    class Config:
        orm_mode = True
        use_enum_values = True

Send this instead:

{
  "phone": {
    "phone": "+917777777777"
  }
}

It should work now.

2. Move the validator to the Parent Model

I would assume that the following is more in line what you want:

class UserPatchEntity(BaseModel):
    user_id: Optional[UUID]
    first_name: Optional[str] = Field(min_length=1, max_length=32)
    last_name: Optional[str] = Field(min_length=1, max_length=32)
    email: Optional[EmailStr]
    phone: Optional[str]  # <-- This is a str now, everything else stays the same
    role:  Optional[RoleType]

    @validator("phone")
    def phone_validation(cls, v):
        logger.debug(f"phone in 2 validator:{v}")
        regex = r"^(\+)[1-9][0-9\-\(\)\.]{9,15}$"
        if v and not re.search(regex, v, re.I):
            raise ValueError("Phone Number Invalid.")
        return v

    class Config:
        orm_mode = True
        use_enum_values = True

With that you can pass in phone in the body as you would expect:

{
    "phone": "+917777777777"
}

Assuming you're using FastAPI, in both cases, you'd get the following error if the phone number is too short, too long, or contains invalid characters:

{
  "detail": [
    {
      "loc": [
        "body",
        "phone"
      ],
      "msg": "Phone Number Invalid.",
      "type": "value_error"
    }
  ]
}

NOTES/HINTS:

  • Pydantic has a constr (Constrained String) type, which allows you to define the minimum and maximum length, and a few other things. You could use the following instead of str for the phone field, eg:

     class UserPatchEntity(BaseModel): ... phone: Optional[ constr( strip_whitespace=True, min_length=9, max_length=15, ) ]

    It also allows you to directly specify a regex string, eg:

     class UserPatchEntity(BaseModel): ... phone: Optional[ constr( strip_whitespace=True, regex=r"^(\+)[1-9][0-9\-\(\)\.]{9,15}$", ) ]

    Which would save you writing the explicit validator .

  • If you continue using regular expressions "directly", I would suggest to compile() them to make them a bit faster.

  • Assuming you are using FastAPI, you can navigate to http://localhost:8000/docs , when the app is running, where you'll find a full OpenAPI interface. You can send payloads directly from there and it also shows you the signature for each endpoint. In the first case, if you defined an endpoint like this:

     @router.post("/user/") async def post_user(user_patch_entity: UserPatchEntity): return user_patch_entity.phone

    it would show the documentation like that: 在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM