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.

[BUG] Schema is generated using field alias

See original GitHub issue

Describe the bug

Schema that is generated in FasAPI /docs endpoint is generated with alias rather than the model field when in the route configuration it is specified the route will reply with the model fields instead of the model fields’ alias. Route for endpoint is using response_model to define the response schema. I tried setting response_model_by_alias to False which correctly uses the model field’s instead of mode field aliases in the response however the schema is wrong in this case.

To Reproduce

Steps to reproduce the behavior with a minimum self-contained file.

Replace each part with your own scenario:

  1. Create a file with:
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Transaction(BaseModel):
    id: ObjectIdStr = Field(..., alias='_id')
    loan_id: ObjectIdStr = Field(..., alias='loanId')

@app.get("/", response_model=Transaction, response_model_by_alias=False)
def read_root():
    return {"Hello": "World"}
  1. Open the browser and call the endpoint /docs.
  2. Check schema response vs the actual response and notice schema generated is inconsistent with the response.

Expected behavior

Schema should use the model fields instead of using model fields’ alias when response_model_by_alias=False is set.

Environment

  • OS: Linux / Windows / macOS
  • FastAPI Version: 0.44
  • Python version: 3.8

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:14
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

5reactions
tiangolocommented, May 10, 2021

Thanks for the discussion everyone.

response_model_by_alias is mainly a hack/workaround for quick experiments, mainly because some people were asking for that, but I’m inclined to deprecating and removing it at some point. Its behavior is actually quite strange, and there’s a much better way to solve the same problem.

More background on that here: https://github.com/tiangolo/fastapi/pull/1642 and here: https://github.com/tiangolo/fastapi/pull/264

For your specific use case, your code example doesn’t really run, I modified it to what I imagine is your intention:

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Transaction(BaseModel):
    id: str = Field(..., alias="_id")
    loan_id: str = Field(..., alias="loanId")


@app.get("/", response_model=Transaction, response_model_by_alias=False)
def read_root():
    return {"_id": "foo", "loanId": "bar"}

And when you call that endpoint, you would receive:

{
  "id": "foo",
  "loan_id": "bar"
}

and then, you would want to have the JSON Schema inside OpenAPI declare the model with id and loan_id.

Now, notice that you return this in the function:

{"_id": "foo", "loanId": "bar"}

but you received this in the HTTP JSON response:

{
  "id": "foo",
  "loan_id": "bar"
}

Note: if you wanted to be able to return data using the field names and not the aliases you would need to use allow_population_by_field_name.

How to do it

Assuming something like that is what you want to achieve while having proper OpenAPI and JSON Schema support, this is how you would implement it:

from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Transaction(BaseModel):
    id: str = Field(..., alias="_id")
    loan_id: str = Field(..., alias="loanId")
    name: str  # to show that you can reuse fields with inheritance


class TransactionResponse(Transaction):
    id: str
    loan_id: str


@app.get("/", response_model=TransactionResponse)
def read_root():
    return {"id": "foo", "loan_id": "bar", "name": "baz"}

With this, you would receive in the JSON Response:

{
  "id": "foo",
  "loan_id": "bar",
  "name": "baz"
}

And the JSON Schema would have id and loan_id:

Selection_428

4reactions
danmichaelocommented, May 16, 2021

Thanks for providing the “How to do it” example. Can confirm that it works, but it does make the code more convoluted. It becomes even more convoluted when bringing SQL Alchemy and orm_mode into the mix. Building on the same example, I could not get something like this to work:

@app.get("/", response_model=TransactionResponse)
def read_root():
    return db.query(TransactionModel).first()

One way I got it to work was like this:

@app.get("/", response_model=TransactionResponse)
def read_root():
    transaction = db.query(TransactionModel).first()
    return TransactionResponse(**Transaction.from_orm(transaction).dict())

Please let me know if there is a less messy way!

Here’s a complete self-contained example:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, Field
from fastapi import FastAPI, Depends


Base = declarative_base()


class TransactionModel(Base):
    __tablename__ = "transactions"
    _id = Column(Integer, primary_key=True)
    loanId = Column(String)
    name = Column(String)



class Transaction(BaseModel):
    id: str = Field(..., alias="_id")
    loan_id: str = Field(..., alias="loanId")
    name: str  # to show that you can reuse fields with inheritance

    class Config:
        orm_mode = True
        allow_population_by_field_name = True


class TransactionResponse(Transaction):
    id: str
    loan_id: str


app = FastAPI()

@app.get("/", response_model=TransactionResponse)
def read_root():
    transaction = TransactionModel(_id=1, loanId='ABC', name='Hello')

    return transaction  # <-- this does not work
    # return TransactionResponse(**Transaction.from_orm(transaction).dict())  # <-- this works, but messy

(Btw. In my case, the issue is that I need to return a field called “metadata”, but “metadata” is reserved on SQL Alchemy models, so I use the workaround described here)

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using aliases - Apollo GraphQL Docs
Using aliases. When generating code, Apollo Kotlin uses the name of your fields to generate matching typesafe models using a flat hierarchy. The...
Read more >
Using aliases in GraphQL - LogRocket Blog
Aliases allow us to rename the data that is returned in a query's results. Aliases don't change the original schema, instead, they manipulate ......
Read more >
[#PIG-3144] Erroneous map entry alias resolution leading to ...
I only encountered this bug when using aliases for map fields. ... b:chararray); D1 = FOREACH DATA GENERATE a#'name' as name, a#'age' as...
Read more >
Feature and table schemas for task parameters—Documentation
A field alias is an alternative name for a field that is more descriptive and user-friendly than the actual name. Clients can use...
Read more >
return pydantic model with field names instead of alias as ...
Ok thanks. Do you know how to make the docs show the the same fields? At the moment they show the alias as...
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