Skip to content

reply

You can use the ormar model instead of the pydantic model in fastapi response_model.

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

One of the most common tasks in a response is to exclude certain fields that you do not want to include in the response data.

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.

Excluding fields in response

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:

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), save in the database and return saved records without this field (which will also validate).

!!!Note Note that although you didn't pass the field value, the field itself still exists in the response_model, meaning it will appear in the response data and be set to None.

1
If you want to fully exclude the field from the result read on.

FastApi Response_Model_Exclusion

Fastapi has a response_model_exclude that accepts a group (or list) of field names.

This has its limitations because ormar and pydantic also accept dictionaries where you can set exclusion/inclusion columns as well as on nested models (see more below).

Warning Note that response_model cannot be used to exclude required fields because it fails during validation.

1
2
3
@app.post("/users/", response_model=User, response_model_exclude={"password"})
async def create_user(user: User):
    return await user.save()

The endpoint above can be queried as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from starlette.testclient import TestClient

client = TestClient(app)

with client as client:
        # note there is no pk
        user = {
            "email": "test@domain.com",
            "password": "^*^%A*DA*IAAA",
            "first_name": "John",
            "last_name": "Doe",
        }
        response = client.post("/users/", json=user)
        # note that the excluded field is fully gone from response
        assert "password" not in response.json()
        # read the response and initialize model out of it
        created_user = User(**response.json())
        # note pk is populated by autoincrement
        assert created_user.pk is not None
        # note that password is missing in initialized model too
        assert created_user.password is None

Notice how the password field completely disappears from the response data in the example above.

1
Note that you can use this method only for non-required fields.

nested model exclusion

Although fastapi allows only one set of field names to be passed, so simply exclude, ormar is smarter when using response_model_exclude.

In ormar, you can use two types of symbols to exclude nested models.

The first is a dictionary with nested fields representing the model tree structure, and the second is a path of field names separated by double underscores.

Suppose our user category is a separate model:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
base_ormar_config = ormar.OrmarConfig(
    metadata=metadata
    database=database
)

class Category(ormar.Model):
    ormar_config = base_ormar_config.copy(tablename="categories")

    id: int = ormar.Integer(primary_key=True)
    name: str = ormar.String(max_length=255)    
    priority: int = ormar.Integer(nullable=True)


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: Optional[Category] = ormar.ForeignKey(Category, related_name="categories")

If you want to exclude priority from categories in the response, you can still use the fastapi parameter.

1
2
3
@app.post("/users/", response_model=User, response_model_exclude={"category__priority"})
async def create_user(user: User):
    return await user.save()

Note that you can double underline deeper models, and if you want to exclude multiple fields from nested models, you need to precede them with full paths. In the example response_model_exclude={"category__priority", "category__other_field", category__nested_model__nested_model_field} ETC.

!!!Note To read more about possible exclusions and how to build an exclusion dictionary or setting, visit the Fields section of the documentation.

Note that in addition to the response_model_exclude parameter, fastapi also supports other parameters inherited from pydantic. All of these also apply to ormar, but there may be some nuances, so it's best to read the dict section of the document.

Excluding in Model.model_dump()

Alternatively, you can just return a dictionary from ormar.Model and use it.

Like this, you can also set exclude/include to dictionary and exclude fields on nested models.

!!!Warning Not using response_model will result in API documents with no response examples and patterns, because responses can theoretically have any format.

1
2
3
4
5
@app.post("/users2/", response_model=User)
async def create_user2(user: User):
    user = await user.save()
    return user.model_dump(exclude={'password'})
    # could be also something like return user.model_dump(exclude={'category': {'priority'}}) to exclude category priority

!!!Note that even if you pass the password field in the request, the example above invalidates the password field, but the field will still exist because it is part of the response schema and the value will be set to None.

If you want to completely exclude the field in this way, simply do not use response_model and exclude it in the model_dump() of the model

Alternatively, you can just return a dictionary from the ormar model. Like this, you can also set exclude/include to dictionary and exclude fields on nested models.

!!!Note that theoretically, you lose response validation here, but since you are operating on ormar.Models, the response data is already validated after database queries (because ormar models are pydantic models).

So if you skip response_model altogether, you can do the following:

1
2
3
4
@app.post("/users4/") # note no response_model
async def create_user4(user: User):
    user = await user.save()
    return user.model_dump(exclude={'last_name'})

!!!Note that when you skip response_model, you can now also exclude required fields because the response is no longer validated after return.

1
The cost of this solution is that you loose also api documentation as response schema in unknown from fastapi perspective.

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
# generate a tree of models without password on User and without priority on nested Category
ResponseUser = User.get_pydantic(exclude={"password": ..., "category": {"priority"}})
@app.post("/users3/", response_model=ResponseUser) # use the generated model here
async def create_user3(user: User):
    return await user.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.

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
import pydantic

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

    email: str
    first_name: str
    last_name: str


@app.post("/users3/", response_model=UserBase) # use pydantic model here
async def create_user3(user: User): #use ormar model here (but of course you CAN use pydantic also here)
    return await user.save()