0

I am using fast API and pydantic model to build APIs. I am doing request body validation and as per the documentation request body schema and its validations are written in the same class (same file). Is there a way to separate the validations part into a separate file? The below snippet shows a sample of the same:

class post(BaseModel):
  emp_name: str
  emp_id: int
  blood_group: str
  contact_number: int
  email_id: str
  job_title: str
  address: str
  team_id: str  
  disposition: str

  @validators('blood_group')   
  def bg(cls,v):
    x=["o+ve","a+ve","o-ve","a-ve","ab+ve","ab-ve","b+ve","b-ve"]  
    if v not in x :
        raise ValueError("please enter valid blood group")
    return v
  • 3
    Unrelated, but you can use `Literal` to avoid writing this specific validator. `from typing import Literal; blood_group: Literal["o+ve", "o-ve", ..]`. To answer your question, you might be able to separate your main class into certain subclasses, and write validators for each one of them. – Ionut-Alexandru Baltariu Sep 06 '22 at 11:02
  • 2
    Another option is to use an Enum instead of Literal: https://pydantic-docs.helpmanual.io/usage/types/#enums-and-choices - since those usually represents a distinct set of choices for a value – MatsLindh Sep 06 '22 at 11:40

1 Answers1

0

As mentioned in one of the comments, if you have a limited number of possible choices for a field, Pydantic suggests using Enums.

In your case this could look like the following (see explanation below):

from enum import Enum, unique
from pydantic import BaseModel

@unique
class BloodGroup(str, Enum):

    AB_N_VE = "ab-ve"
    AB_P_VE = "ab+ve"
    A_N_VE = "a-ve"
    A_P_VE = "a+ve"
    B_N_VE = "b-ve"
    B_P_VE = "b+ve"
    O_N_VE = "o-ve"
    O_P_VE = "o+ve"

class post(BaseModel):
    emp_name: str
    bg: BloodGroup
    # ...
    # omitting other fields to keep it short
  

p1 = post.parse_obj({"emp_name": "Some Name", "bg": "ab-ve"})
print(p1)
# p1: emp_name='Some Name' bg=<BloodGroup.AB_N_VE: 'ab-ve'>

p2 = post.parse_obj({"emp_name": "Some Name", "bg": BloodGroup.AB_N_VE})
print("p2:", p2)
# p2: emp_name='Some Name' bg=<BloodGroup.AB_N_VE: 'ab-ve'>

p3 = post.parse_obj({"emp_name": "Some Name", "bg": "xyz"})
# Raises exception:
# Traceback (most recent call last):
#   File "pydantic_emums.py", line 44, in <module>
#     p2 = post.parse_obj({"emp_name": "Some Name", "bg": "xyz"})
#   File "pydantic/main.py", line 572, in pydantic.main.BaseModel.parse_obj
#   File "pydantic/main.py", line 400, in pydantic.main.BaseModel.__init__
# pydantic.error_wrappers.ValidationError: 1 validation error for post
# bg
#   value is not a valid enumeration member; permitted: 'ab-ve', 'ab+ve', 'a-ve', 'a+ve', 'b-ve', 'b+ve', 'o-ve', 'o+ve' (type=type_error.enum; enum_values=[<BloodGroup.AB_N_VE: 'ab-ve'>, <BloodGroup.AB_P_VE: 'ab+ve'>, <BloodGroup.A_N_VE: 'a-ve'>, <BloodGroup.A_P_VE: 'a+ve'>, <BloodGroup.B_N_VE: 'b-ve'>, <BloodGroup.B_P_VE: 'b+ve'>, <BloodGroup.O_N_VE: 'o-ve'>, <BloodGroup.O_P_VE: 'o+ve'>])

The unique decorator ensures that all values are unique and you don't end up with duplicates by accident (which is what you typically want to avoid).

Since + and - are not allowed as variable names in Python, I have used P (positive) and N (negative) instead but you could call them whatever you want.

Inheriting from str in addition to Enum makes it possible to use comparison operators on the Enum fields, i.e.:

BloodGroup.A_N_VE == "a-ve"
# True

Keep in mind though, that str must come before Enum (see here).

When instantiating the model (post in your case), you can either use the string representing (p1) of the blood group or the Enum field (p2). Both will result in the same instance.

When anything other than the strings defined by the Enum class is passed into the model, it will raise a ValidationError with a detailed description of what is wrong.

Another advantage of using Enums like this is that you can import and use them in other modules and parts of your code without having to rely on the string representation. This helps to avoid typos and can give you auto-completion if your editor is configured accordingly.

And if you're writing the results to a database and you're using SQLAlchemy, you can also reuse the same Enums in the database field definitions.

Paul P
  • 3,346
  • 2
  • 12
  • 26