dahlia / sqlalchemy-imageattach

SQLAlchemy extension for attaching images to entities.
https://sqlalchemy-imageattach.readthedocs.io/
MIT License
116 stars 25 forks source link

I do not understand how this should work with (pydantic model) validation #46

Open lazarillo opened 4 years ago

lazarillo commented 4 years ago

I am trying to use imageattach, and I do not really understand how it should work, how dis/similar it is to just a normal relationship.

Below is a snippet of code, which is mimicking something like a StackOverflow question board, where users could post questions and the questions could have images attached to them. I am following a style quite similar to FastAPI's SQLAlchemy tutorial.

I am able to get users, create users, and get questions. But when I try to create a question (POST a question), I get the following pydantic validation error:

pydantic.error_wrappers.ValidationError: 1 validation error for Question
response -> pictures
  value is not a valid list (type=type_error.list)

I am fairly sure this is an issue with pydantic having trouble understanding the relationship that sqlalchemy-imageattach creates, which is why I am posting here.


schemas.py (Pydantic models)

from typing import List, Optional
from pydantic import BaseModel

class QuestionPictureBase(BaseModel):
    pass

class QuestionPicture(QuestionPictureBase):
    id: int
    question_id: int
    class Config:
        orm_mode = True

class QuestionBase(BaseModel):
    header: str
    details: str

class QuestionCreate(QuestionBase):
    pass

class Question(QuestionBase):
    id: int
    creator_id: int
    pictures: List[QuestionPicture] = []

    class Config:
        orm_mode = True

class UserBase(BaseModel):
    email: Optional[str] = None
    name: str  # This is the nickname they want to go by
    username: str  # This is the unique user name

class UserCreate(UserBase):
    password: str

class User(UserBase):
    id: int
    is_active: bool
    questions: List[Question] = []

    class Config:
        orm_mode = True

Then the SQL ORM models:

models.py

from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Text
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy_imageattach.entity import Image, image_attachment

from .database import Base

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String)
    username = Column(String, unique=True, index=True)
    email = Column(String)
    hashed_password = Column(String)
    is_active = Column(Boolean, default=True)
    questions = relationship("Question", back_populates="creator", uselist=True)

class Question(Base):
    __tablename__ = "questions"
    id = Column(Integer, primary_key=True, index=True)
    header = Column(String, index=True)
    details = Column(String, index=True)
    creator_id = Column(Integer, ForeignKey("users.id"))
    pictures = image_attachment("QuestionPicture", uselist=True)
    creator = relationship("User", back_populates="questions")

class QuestionPicture(Base, Image):
    """Model for pictures associated with the question."""
    __tablename__ = "question_pictures"
    id = Column(Integer, primary_key=True)
    question_id = Column(Integer, ForeignKey("questions.id"))
    question = relationship("Question", back_populates="pictures")

Then the CRUD module (edited down to just the function of interest):

crud.py

from sqlalchemy.orm import Session
from . import models, schemas

def create_user_question(db: Session, question: schemas.QuestionCreate, username: str):
    user = get_user_by_username(db=db, username=username)
    db_item = models.Question(**question.dict(), creator_id=user.id)
    db.add(db_item)
    db.commit()
    db.refresh(db_item)
    return db_item

And finally, the main.py module, again edited just to focus upon question creation.

from typing import List
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from . import crud, models, schemas
from .database import SessionLocal, engine

app = FastAPI()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.post("/users/{username}/questions/", response_model=schemas.Question)
def create_question_for_user(
    username: str, question: schemas.QuestionCreate, db: Session = Depends(get_db)
):
    return crud.create_user_question(db=db, question=question, username=username)