FastAPI + SQLAlchemy - 使用日期时间字段将条目发布到 table 时出现 InvalidRequestError

FastAPI + SQLAlchemy - InvalidRequestError when posting entry to table with datetime field

我刚刚关注并完成了 FastAPI 文档的整个 SQLAlchemy 部分 here,我选择了本地 MariaDB 作为数据库。

我的代码看起来与末尾列出的最终文件相同。我已经测试了一切,一切正常。

当我尝试创建自己的名为 DHT(数字湿度和温度)的数据库 table 时出现了一个问题。 它的目的是在一天中的特定时间保存有关我房间的湿度和温度的信息。当我测试用于写入 table 的端点时,我收到错误消息。但是数据确实会保存到 table 中,如下所示。

这是 SQLAlchemy ORM 模型的样子:

class DHT(Base):
    __tablename__ = "dht"

    datetime = Column(DateTime, primary_key=True, default=datetime.now)
    humidity = Column(Float(8), index=True)
    temp = Column(Float(8), index=True)

以下是用于数据验证的 Pydantic 模式:

class DHTBase(BaseModel):
    temp: float
    humidity: float

class DHTCreate(DHTBase):
    pass

class DHT(DHTBase):
    """
    In the Pydantic models for reading, we add an internal Config class.
    """
    date: datetime

    class Config:
        orm_mode = True

一切都从端点开始,我尽可能地遵循了其他 POST 端点示例。

@app.post("/dht/", response_model=schemas.DHT)
def create_dht(dht: schemas.DHTCreate,
               db: Session = Depends(get_db)):
    return crud.create_dht(db=db, dht_data=dht)

这是端点调用的 crud 操作:

def create_dht(db: Session, dht_data: schemas.DHTCreate):

    dht = models.DHT(humidity=dht_data.humidity,
                     temp=dht_data.temp)
    db.add(dht)
    db.commit()
    db.refresh(dht)
    return dht

当我使用 curl 测试端点时,示例如下:

curl -X 'POST' \
  'http://127.0.0.1:8000/dht/' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "temp": 23.34,
  "humidity": 55.35
}'

...FastAPI 记录此错误:

  File "/home/sydney/.cache/pypoetry/virtualenvs/app-sfi98_Li-py3.10/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2328, in refresh
    raise sa_exc.InvalidRequestError(
sqlalchemy.exc.InvalidRequestError: Could not refresh instance '<DHT at 0x7f09f52c5420>'

但是当我查看数据库时,实际上已经写入了一个条目。

MariaDB [blogfolio]> select * from dht;
+---------------------+----------+-------+
| datetime            | humidity | temp  |
+---------------------+----------+-------+
| 2022-03-26 10:00:11 |    55.35 | 23.34 |
+---------------------+----------+-------+

我期望从 FastAPI 得到的答复是 JSON 这种形式。

{
  "temp": 23.34,
  "humidity": 55.35,
  "date": "2022-03-26T17:00:11.815Z"
}

相反,我得到“内部服务器错误”。

我制作了 DHT table 的虚拟版本,其中日期时间列只是替换为字符串列并填充了字符串数据,一切正常。所以在这一点上,我唯一确定的是它来自 SQLAlchemy,既不喜欢我的 ORM 模型的日期时间字段,也不喜欢我将日期时间数据写入其中的方式。

我的 ORM 模型/CRUD 操作有什么问题?

mariadb datetime 的文档指出 0 微秒是默认的日期时间精度。这在您的数据库输出中似乎是这样,即。 2022-03-26 10:00:11。此外,从同一文档中可以看出,时区取决于会话。

我能够通过使用 DATETIME(fsp=6) 重新创建此异常以及 half-fix。这就是似乎发生的事情:

  1. DHT 是使用 python 的 datetime.now() 作为应用程序中的主键创建的
  2. DHT 被写入数据库,微秒可能只是被 mariadb 忽略,因为该列默认没有微秒
  3. 刷新被调用,SQLAlchemy 查询数据库以查找 datetime = 2022-03-26T17:00:11.815 的记录(注意这里有微秒)
  4. 它找不到一个,因为数据库中的 datetime 将它们修剪为 2022-03-26T17:00:11,因此刷新失败并出现异常

在这里我想说最安全的策略是为主键使用整数 id 但是...

要首先完成这项工作,您可以将列上的精度提高到 6 位,持续微秒。您必须使用方言特定 DATETIMEfrom sqlalchemy.dialect.mysql import DATETIMEfsp=6.

然后是时区...您应该确保数据库会话时区与您在应用程序中用于制作日期时间的时区相匹配,因为 mariadb 不进行转换。 UTC 可能是这里最安全的,但我不确定 mariadb 的具体细节。

您可能必须自行更改服务器的默认设置。这可能适用于会话,session.execute(text("SET time_zone = 'UTC'"))

在 python 中,您可以通过 import datetime 执行 default = lambda: datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)。这将获得 UTC 的当前时间,然后删除时区以使 dt 天真。