无法通过 SQLModel 将 FastAPI List[]、Set() 映射到 Postgres

Jua*_*ejo 3 sqlalchemy pydantic fastapi sqlmodel

我正在使用FastAPI制作我的个人投资组合 API ,并决定尝试SQLModel。它感觉非常直观,到目前为止我很喜欢它,但我遇到了一个问题,我已经挣扎了好几天,试图了解如何解决它。

\n

我有一个项目模型:

\n
from datetime import datetime\nfrom typing import List, Optional, Set\nfrom sqlmodel import SQLModel, Field\n\n\nclass ProjectBase(SQLModel):\n    name: Optional[str]\n    summary: Optional[str]\n    description: Optional[str]\n    category: Set[str] = ()\n    award: Optional[str] = None\n    url: Optional[str] = None\n    published: datetime = datetime.utcnow()\n    image: str = "placeholderMainImage"\n    images: List[str] = []\n    learning: Optional[str]\n    tech: Optional[str]\n    tools: Optional[str]\n\n\n\nclass Project(ProjectBase, table=True):\n    id: int = Field(default=None, primary_key=True)\n    created_at: datetime = Field(default_factory=datetime.utcnow)\n    updated_at: datetime = Field(default_factory=datetime.utcnow)\n
Run Code Online (Sandbox Code Playgroud)\n

FastAPI 工作正常,我检查了 localhost:8000/docs并正确进行了类型验证:

\n
    {\n  "name": "string",\n  "summary": "string",\n  "description": "string",\n  "category": [],\n  "award": "string",\n  "url": "string",\n  "published": "2021-10-04T20:43:26.472364",\n  "image": "placeholderMainImage",\n  "images": [],\n  "learning": "string",\n  "tech": "string",\n  "tools": "string",\n  "id": 0,\n  "created_at": "2021-10-04T21:01:30.048Z",\n  "updated_at": "2021-10-04T21:01:30.048Z"\n}\n
Run Code Online (Sandbox Code Playgroud)\n

当我使用上述查询发出 POST 请求时,出现内部服务器错误

\n
invalid input for query argument $4: (expected str, got set)\n
Run Code Online (Sandbox Code Playgroud)\n

不幸的是,SQLModel 在设计上会在创建表时将任何奇怪的类型转换为 VARCHAR,从而无法使用 List 或 Set 功能:

\n
   CREATE TABLE project (\n         name VARCHAR, \n         summary VARCHAR, \n         description VARCHAR, \n         category VARCHAR, \n         award VARCHAR, \n         url VARCHAR, \n         published TIMESTAMP WITHOUT TIME ZONE, \n         image VARCHAR, \n         images VARCHAR, \n         learning VARCHAR, \n         tech VARCHAR, \n         tools VARCHAR, \n         id SERIAL, \n        created_at TIMESTAMP WITHOUT TIME ZONE, \n        updated_at TIMESTAMP WITHOUT TIME ZONE, \n        PRIMARY KEY (id)\n )\n
Run Code Online (Sandbox Code Playgroud)\n

我知道 postgres 有一些数组类型,例如:integer[]text[],它们可以处理类别和图像字段的这种情况。\n尝试手动更改表列类型,结果相同。

\n

尝试将类别和图像发布为 str:

\n
    {\n  "detail": [\n    {\n      "loc": [\n        "body",\n        "category"\n      ],\n      "msg": "value is not a valid set",\n      "type": "type_error.set"\n    },\n    {\n      "loc": [\n        "body",\n        "images"\n      ],\n      "msg": "value is not a valid list",\n      "type": "type_error.list"\n    }\n  ]\n}\n
Run Code Online (Sandbox Code Playgroud)\n

如果无法使用如此出色的功能来清理我收到的数据,那将是非常遗憾的。我在互联网上查找,但找不到任何相关内容或使用 List 和 Set 与 SQLModel 的示例

\n

\xc2\xbf我可以做什么来支持这种情况?

\n

PD:我也在使用 asyncpg

\n

Dje*_*eth 6

我让它适用于列表,但不适用于 postgres 数据库上的集合。问题在于结果何时映射回您的项目类。这是我的完整代码。

from datetime import datetime
from typing import List, Optional, Set
from sqlalchemy.sql.schema import Column
from sqlmodel import SQLModel, Field, create_engine, Session, select, String, ARRAY 
from fastapi import FastAPI

class ProjectBase(SQLModel):
    name: Optional[str]
    category: Set[str] = Field(default=None, sa_column=Column(ARRAY(String())))
    images: List[str] = Field(default=None, sa_column=Column(ARRAY(String())))


class Project(ProjectBase, table=True):
    id: int = Field(default=None, primary_key=True)

engine = create_engine("postgresql://postgres:mysecretpassword@localhost:5432/testdb")

new_proj = Project(
    name=f"{str(datetime.time(datetime.utcnow()))}", 
    category=("hi", "hello", "cat3"), 
    images=["img1", "img2"] 
)

app = FastAPI()

@app.on_event("startup")
def on_startup():
    SQLModel.metadata.create_all(engine)
    print("BEFORE REFRESH:", new_proj)
    with Session(engine) as session:
        session.add(new_proj)
        session.commit()
        session.refresh(new_proj)
    print("AFTER REFRESH:", new_proj)

@app.get("/", response_model=List[Project])
def home():
    with Session(engine) as session:
        projects = session.exec(select(Project)).all()
        return projects
Run Code Online (Sandbox Code Playgroud)

我对你的Project课程进行了一些简化。启动 uvicorn 运行此程序后,我得到以下输出(参考@app.on_event("startup")):

BEFORE REFRESH: name='17:17:38.090595' category={'hoi', 'cat3', 'hello'} images=['img1', 'img2'] id=None
AFTER REFRESH: id=7 category=['hoi', 'cat3', 'hello'] images=['img1', 'img2'] name='17:17:38.090595'
Run Code Online (Sandbox Code Playgroud)

注意category值的区别,从数据库刷新对象之前以 { 开头(表示集合),从数据库刷新对象之后以 [ 开头。

我尝试了各种方法但没有效果。我唯一能想到的(但找不到任何相关内容)是某种自定义映射规则sqlalchemy。尽管我对该软件包了解不够,但无法真正理解我在寻找什么。希望这至少有帮助!无论哪种方式我都学到了很多:)