如何在 FastAPI 响应模式中使用箭头类型?
How to use Arrow type in FastAPI response schema?
我想在 FastAPI
响应中使用 Arrow
类型,因为我已经在 SQLAlchemy
模型中使用它(感谢 sqlalchemy_utils
)。
我准备了一个小型独立示例,其中包含一个最小的 FastAPI 应用程序。我希望这个应用程序 return product1
来自数据库的数据。
不幸的是,下面的代码给出了异常:
Exception has occurred: FastAPIError
Invalid args for response field! Hint: check that <class 'arrow.arrow.Arrow'> is a valid pydantic field type
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
requirements.txt:
sqlalchemy==1.4.23
sqlalchemy_utils==0.37.8
arrow==1.1.1
fastapi==0.68.1
uvicorn==0.15.0
这个错误已经在那些 FastAPI 问题中讨论过:
一种可能的解决方法是添加此代码 (source):
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
放在@app.get('/'...
正上方就可以了,甚至可以放在app = FastAPI()
之前
此解决方案的问题是 GET 端点的输出将是:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": {
"_datetime": "2021-08-25T21:38:01+00:00"
}
}
而不是期望的:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2021-08-25T21:38:01+00:00"
}
使用 @validator
装饰器添加自定义函数,returns 所需的 _datetime
对象:
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@validator("created_at")
def format_datetime(cls, value):
return value._datetime
在本地测试,似乎有效:
$ curl -s localhost:8000 | jq
{
"id": 1,
"name": "ice cream",
"created_at": "2021-12-02T08:25:10+00:00"
}
解决方案是使用 monkeypatch pydantic 的 ENCODERS_BY_TYPE
,这样它就知道如何转换 Arrow 对象,以便它可以被 json 格式接受:
from arrow import Arrow
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
设置BaseConfig.arbitrary_types_allowed = True
也是必要的。
结果:
// 20220514022717
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2022-05-14T00:20:11+00:00"
}
完整代码:
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
我想在 FastAPI
响应中使用 Arrow
类型,因为我已经在 SQLAlchemy
模型中使用它(感谢 sqlalchemy_utils
)。
我准备了一个小型独立示例,其中包含一个最小的 FastAPI 应用程序。我希望这个应用程序 return product1
来自数据库的数据。
不幸的是,下面的代码给出了异常:
Exception has occurred: FastAPIError
Invalid args for response field! Hint: check that <class 'arrow.arrow.Arrow'> is a valid pydantic field type
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)
requirements.txt:
sqlalchemy==1.4.23
sqlalchemy_utils==0.37.8
arrow==1.1.1
fastapi==0.68.1
uvicorn==0.15.0
这个错误已经在那些 FastAPI 问题中讨论过:
一种可能的解决方法是添加此代码 (source):
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
放在@app.get('/'...
正上方就可以了,甚至可以放在app = FastAPI()
此解决方案的问题是 GET 端点的输出将是:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": {
"_datetime": "2021-08-25T21:38:01+00:00"
}
}
而不是期望的:
// 20210826001330
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2021-08-25T21:38:01+00:00"
}
使用 @validator
装饰器添加自定义函数,returns 所需的 _datetime
对象:
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@validator("created_at")
def format_datetime(cls, value):
return value._datetime
在本地测试,似乎有效:
$ curl -s localhost:8000 | jq
{
"id": 1,
"name": "ice cream",
"created_at": "2021-12-02T08:25:10+00:00"
}
解决方案是使用 monkeypatch pydantic 的 ENCODERS_BY_TYPE
,这样它就知道如何转换 Arrow 对象,以便它可以被 json 格式接受:
from arrow import Arrow
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
设置BaseConfig.arbitrary_types_allowed = True
也是必要的。
结果:
// 20220514022717
// http://localhost:8000/
{
"id": 1,
"name": "ice cream",
"created_at": "2022-05-14T00:20:11+00:00"
}
完整代码:
import sqlalchemy
import uvicorn
from arrow import Arrow
from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import Column, Integer, Text, func
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy_utils import ArrowType
from pydantic.json import ENCODERS_BY_TYPE
ENCODERS_BY_TYPE |= {Arrow: str}
from pydantic import BaseConfig
BaseConfig.arbitrary_types_allowed = True
app = FastAPI()
engine = sqlalchemy.create_engine('sqlite:///db.db')
Base = declarative_base()
class Product(Base):
__tablename__ = "product"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True)
created_at = Column(ArrowType(timezone=True), nullable=False, server_default=func.now())
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
product1 = Product(name="ice cream")
product2 = Product(name="donut")
product3 = Product(name="apple pie")
session.add_all([product1, product2, product3])
session.commit()
class ProductResponse(BaseModel):
id: int
name: str
created_at: Arrow
class Config:
orm_mode = True
arbitrary_types_allowed = True
@app.get('/', response_model=ProductResponse)
async def return_product():
product = session.query(Product).filter(Product.id == 1).first()
return product
if __name__ == "__main__":
uvicorn.run(app, host="localhost", port=8000)