question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] Why not introduce pydantic @validator in docs?

See original GitHub issue

Description

I am new to fastapi and web APIs in general, but found it astonishing simple and fast to create a first working api. In the process, I found using pydantic @validators very useful for custom class validation. I was wondering why this is not introduced in the docs. Perhaps the use of @validators is discouraged and there’s a better way?

Here’s a minimum working example to discuss. If this is the right way to do it, I can create a pull request to add it to the docs. Otherwise, I would be grateful for any hint towards a better approach to do this:

from fastapi import FastAPI
from pydantic import BaseModel, validator, BaseConfig
from geoalchemy2 import WKTElement

app = FastAPI()


class Coordinates(BaseModel):
    lat: float = 0
    lng: float = 0

    @validator('lat')
    def lat_within_range(cls, v):
        if not -90 <= v <= 90:
            raise ValueError('Latitude outside allowed range')
        return v
        
    @validator('lng')
    def lng_within_range(cls, v):
        if not -180 <= v <= 180:
            raise ValueError('Longitude outside allowed range')
        return v

class UserIn(BaseModel):
    username: str
    coordinates: Coordinates


class UserOut(BaseModel):
    username: str


class UserInDB(BaseModel):
    username: str
    coordinates_geom: WKTElement

    class Config(BaseConfig):
        arbitrary_types_allowed = True

def get_geom_from_coordinates(coordinates: Coordinates):
    geom_wkte = WKTElement(
        f"Point ({coordinates.lng} {coordinates.lat})",
        srid=4326, extended=True)
    return geom_wkte


def fake_save_user(user_in: UserIn):
    coordinates_geom = get_geom_from_coordinates(user_in.coordinates)
    user_in_db = UserInDB(**user_in.dict(), coordinates_geom=coordinates_geom)
    print("User saved! ..not really")
    return user_in_db
    
@app.post("/user/", response_model=UserOut)
async def create_user(*, user_in: UserIn):
    user_saved = fake_save_user(user_in)
    return user_saved

Explanation In the example above, there are two steps where I make use of pydantic, validation (1) and conversion (2).

First (1), I need to validate coordinates, which are usually submitted from the frontend using Latitude and Longitude as floats. Then (2), I need to store these in the database in a special custom format, for which no validator is available from pydantic (e.g. as WKTElement from geoalchemy2, which is then stored as PostGis Geometry in the db). To summarize the use of pydantic:

  • Validation (1): I need a check for input lat/lng range, that’s where I make use of the @validator above
  • Conversion (2): I need conversion of input lat/lng values to the custom format WKTElement used to store values in the db, that’s the function get_geom_from_coordinates, which constructs a WellKnownText (WKT) representation of lat/lng - this requires making use of pydantic class Config to enable arbitrary_types_allowed

Here is a gist with a complete example that also includes loading of geometry to coordinates conversion.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:2
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

4reactions
tiangolocommented, Jun 20, 2019

Thanks for the insight @Sieboldianus !

For range validations you can use Schema, it’s a bit less verbose and also generates the corresponding JSON Schema (used by OpenAPI). That way, even the Swagger UI will complain if the values provided are not in range.

You could do:

from pydantic import BaseModel, Schema

class Coordinates(BaseModel):
    lat: float = Schema(0, gte=-90, lte=90)
    lng: float = Schema(0, gte=-180, lte=180)

Nevertheless, there are other more complex scenarios that benefit from @validator. Although I consider them more of a “power user” feature.

3reactions
dmontagucommented, Jun 14, 2019

Perhaps the use of @validators is discouraged and there’s a better way?

I think @validator is a great way to do data validation, especially with FastAPI – you get clear error responses for free! It’s probably worth a section in the documentation.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How we validate input data using pydantic
Pydantic raises a ValidationError when the validation of the model fails, stating which field, i.e. attribute, raised the error and why. In this ......
Read more >
8 Reasons to Start Using Pydantic to Improve Data Parsing ...
In this post, we'll introduce data validation and why you should think about it when developing Python applications. Then, we'll introduce ...
Read more >
Conflict between pydantic constr and mypy checking
Mypy accepts this happily and pydantic does correct validation. The type of data.regex[i] is Regex , but as pydantic.ConstrainedStr itself ...
Read more >
Why I use attrs instead of pydantic - The Three of Wands
This post is an account of why I prefer using the attrs library over Pydantic. I'm writing it since I am often asked...
Read more >
Parsing and validating data in Python using Pydantic
Pydantic not only does type checking and validation, it can be used to add constraints to properties and create custom validations for ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found