DEV Community

Cover image for Pydantic for FastAPI
Amal Shaji
Amal Shaji

Posted on • Edited on • Originally published at blog.amalshaji.com

Pydantic for FastAPI

FastAPI is a modern async framework for Python. According to Python developers survey 2020, FastAPI is the 3rd most popular web framework for python.

What makes FastAPI so popular?

  • Async
  • Fast
  • Easy to Code and fast learning curve.
  • Data validation via Pydantic
  • Automatic docs

Take a look at all the FastAPI features.

Pydantic is a python library for data parsing and validation using Python type hints. It is the fastest python data validation library.

Pydantic for FastAPI

1. BaseSettings for reading environment variables

This requires python-dotenv installed.



pip install python-dotenv


Enter fullscreen mode Exit fullscreen mode

Dummy .env file:



DB_NAME=postgres
DB_PASS=postgres
DB_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres

DEBUG=True
SEED=42


Enter fullscreen mode Exit fullscreen mode


# env.py

from pydantic import BaseSettings, PostgresDsn


class Settings(BaseSettings):
    db_name: str
    db_pass: str
    db_url: PostgresDsn
    debug: bool
    seed: int

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"


settings = Settings()
print(settings)


Enter fullscreen mode Exit fullscreen mode

Output:



db_name='postgres' db_pass='postgres' db_url=PostgresDsn('postgresql://postgres:postgres@127.0.0.1:5432/postgres', scheme='postgresql', user='postgres', password='postgres', host='127.0.0.1', host_type='ipv4', port='5432', path='/postgres') debug=True seed=42


Enter fullscreen mode Exit fullscreen mode
  • The environment variables are automatically loaded by name.
  • They are converted to the types specified by type hints.

By default, all variables are case-insensitive.

You can also use an alias for loading env values. For example, your env variable is DATABASE_URL, but you need to load it as db_url.



from pydantic import BaseSettings, Field, PostgresDsn

class Settings(BaseSettings):
    db_url: PostgresDsn = Field(..., env="DATABASE_URL")


Enter fullscreen mode Exit fullscreen mode

Reading the env file is only required if the values are not in the system environment. This process is costly, especially when read for each request. So cache the values using lru_cache.



from functools import lru_cache
# other imports


class Settings(BaseSettings):
    # All fields, yadayadayada

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"


@lru_cache
def get_settings() -> Settings:
    return Settings()


settings = get_settings()


Enter fullscreen mode Exit fullscreen mode

Every time the get_settings is called, the cached values are returned.

2. Validators for custom data validation

Validators are applied to BaseModel to perform custom validation.

Basic BaseModel + FastAPI usage:



from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class User(BaseModel):
    email: str  # use EmailStr from pydantic
    username: str
    password: str


@app.post("/")
async def register(user: User):
    return user


Enter fullscreen mode Exit fullscreen mode

Test it out using httpie:



➜ http POST 127.0.0.1:8000 email=test@test.com username=amal password=amal
HTTP/1.1 200 OK
content-length: 61
content-type: application/json
date: Wed, 19 May 2021 12:20:06 GMT
server: uvicorn

{
    "email": "test@test.com",
    "password": "amal",
    "username": "amal"
}


Enter fullscreen mode Exit fullscreen mode

Let's use @validator from pydantic to perform some operation on the BaseModel before the view, register, receives it.



from pydantic import BaseModel, validator

class User(BaseModel):
    email: str  # use EmailStr from pydantic
    username: str
    password: str

    @validator("password")
    def hash_the_password(cls, password: str):
        return f"Fast-Insecure-Hash-{password}"


Enter fullscreen mode Exit fullscreen mode


➜ http POST 127.0.0.1:8000 email=test@test.com username=amal password=amal
HTTP/1.1 200 OK
content-length: 80
content-type: application/json
date: Wed, 19 May 2021 12:39:06 GMT
server: uvicorn

{
    "email": "test@test.com",
    "password": "Fast-Insecure-Hash-amal",
    "username": "amal"
}


Enter fullscreen mode Exit fullscreen mode

You can also validate a particular field with the rest of the fields.



@validator("password")
def hash_the_password(cls, password, values, **kwargs):
    print(values.keys())
    return password


Enter fullscreen mode Exit fullscreen mode

This prints dict_keys(['email', 'username']) to stdout. The pydantic fields are validated in sequence, and the values dict carries the already validated fields.

In this case, since we are validating the password field, all the above fields are available to use.

You can use Root Validator to use the entire model's data. By default, the root validator gets data after all the fields are validated(i.e., the default validation).



@root_validator
def test_root_validator(cls, values):
    print(values.keys())
    return values


Enter fullscreen mode Exit fullscreen mode

Output:



dict_keys(['email', 'username', 'password'])


Enter fullscreen mode Exit fullscreen mode

3. schema_extra for providing examples in docs

With the previous example running, if you navigate to http://127.0.0.1:8000/docs, you could see the interactive docs by FastAPI.

Interactive API docs by FastAPI

Instead of string for all values, let's write some custom data for the examples.



class User(BaseModel):
    email: str  # use EmailStr from pydantic
    username: str
    password: str

    class Config:
        schema_extra = {
            "example": {
                "email": "test@test.com",
                "username": "amal",
                "password": "amal",
            }
        }


Enter fullscreen mode Exit fullscreen mode

Interactive API docs with custom data

You can also use fake data libraries like Faker to generate random data.

Setting the example data can also be achieved like so:



class User(BaseModel):
email: str = Field(..., example="test@test.com")
username: str = Field(..., example="amal")
password: str = Field(..., example="amal")

Enter fullscreen mode Exit fullscreen mode




Conclusion

Pydantic is an amazing tool for data validation. FastAPI uses pydantic to help build better APIs. For more amazing features of pydantic, read the official documentation.

Top comments (1)

Collapse
 
sm0ke profile image
Sm0ke

Nice ...