Skip to content

ask for

You can use the ormar model instead of the pydantic model in the fastapi request body parameters.

You can of course mix ormar.Models with pydantic if you want.

One of the most common tasks in a request is to exclude certain fields that you do not want included in the payload sent to the API.

There are many ways to achieve this in ormar, so you can review your options below and choose the one that best suits your situation.

Fields in the exclusion request

optional field

Note that each optional field is not required, which means optional fields can be skipped in both responses and requests.

Fields are not required if (any/many/all) of the following occur:

  • Field marked as nullable=True
  • field has a default value or provided functionality, i.e. default="Test"
  • field has the server_default value set
  • Field is an autoincrement=Trueprimary_key field (note that ormar. Integrprimary_key defaults to autoincrement)

Examples:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
base_ormar_config = ormar.OrmarConfig(
    metadata=metadata
    database=database
)

class User(ormar.Model):
    ormar_config = base_ormar_config.copy()

    id: int = ormar.Integer(primary_key=True)
    email: str = ormar.String(max_length=255)
    password: str = ormar.String(max_length=255)
    first_name: str = ormar.String(max_length=255, nullable=True)
    last_name: str = ormar.String(max_length=255)
    category: str = ormar.String(max_length=255, default="User")

In the example above, the fields id (which is an autoincrement integer), first_name (with nullable=True), and category (with default values) are optional and can be skipped in the response, and the model will still validate.

If this field is nullable, it does not have to be included in the payload during creation and in the response, so based on the example above, you can:

!!! Warning Note that although you don't have to pass optional fields, you can still do so. If someone passes a value, it will be used later unless you take steps to prevent it.

1
2
3
4
# note that app is an FastApi app
@app.post("/users/", response_model=User) # here we use ormar.Model in response
async def create_user(user: User):  # here we use ormar.Model in request parameter
    return await user.save()

This means that if you do not pass ie first_name in the request, it will validate correctly (because the field is optional) and None will be saved in the database.

Generating pydantic models from ormar.Model

Since the task of excluding fields is very common, ormar has a special way to generate pydantic models from existing ormar.Models without having to re-enter all the fields.

This method is the get_pydantic() method available to all model classes.

1
2
3
4
5
6
# generate a tree of models without password on User and without priority on nested Category
RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=User) # here you can also use both ormar/pydantic
async def create_user3(user: RequestUser):  # use the generated model here
    # note how now user is pydantic and not ormar Model so you need to convert
    return await User(**user.model_dump()).save()

!!! Note To see more examples and read more, visit the get_pydantic section of the documentation.

Warning The get_pydantic method generates all models in the nested model tree based on algorithms that allow loops in the model to be avoided (the same algorithms used in model_dump(), select_all(), etc.)

1
2
3
That means that nested models won't have reference to parent model (by default ormar relation is bidirectional).

Note also that if given model exists in a tree more than once it will be doubled in pydantic models (each occurrence will have separate own model). That way you can exclude/include different fields on different leafs of the tree.

Mypy and Type Check

Note that assigning a function to python type passes at runtime (because it is unchecked), and static type checkers like mypy complain.

Although the result of a function call is always the same for a given model, dynamically created types are not allowed.

So you have two choices:

The first is to simply add # type:ignore to skip type checking

1
2
3
4
5
RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=User)
async def create_user3(user: RequestUser):  # type: ignore
    # note how now user is not ormar Model so you need to convert
    return await User(**user.model_dump()).save()

The second one is a bit more hacky, using fastapi to extract function parameters.

You can override__annotations__entries for a given parameter.

1
2
3
4
5
6
7
8
RequestUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
# do not use the app decorator
async def create_user3(user: User):  # use ormar model here
    return await User(**user.model_dump()).save()
# overwrite the function annotations entry for user param with generated model 
create_user3.__annotations__["user"] = RequestUser
# manually call app functions (app.get, app.post etc.) and pass your function reference
app.post("/categories/", response_model=User)(create_user3)

Note that this will cause mypy to "think" that the user is an ormar model, but since it doesn't matter in the request (you'll pass jsonized dict anyway and need to convert before saving).

This should still work fine because the generated model will be a subset of fields, so all required fields will be validated and all unused fields will fail at runtime.

Independent cantilever model

The final solution is to manually create a separate pydantic model. It works exactly the same as a regular fastapi application, so you can use different response and request models, etc.

Sample:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pydantic

class UserCreate(pydantic.BaseModel):
    model_config = pydantic.ConfigDict(from_attributes=True)

    email: str
    first_name: str
    last_name: str
    password: str


@app.post("/users3/", response_model=User) # use ormar model here (but of course you CAN use pydantic also here)
async def create_user3(user: UserCreate):  # use pydantic model here
    # note how now request param is a pydantic model and not the ormar one
    # so you need to parse/convert it to ormar before you can use database
    return await User(**user.model_dump()).save()