1

I have a fastapi project using SQLModel that has JWT auth active. I have a registration endpoint and am trying to return a subset of the user record when a post to the registration endpoint is successful. I seem to be able to return the 'User' model which is tied to an actual table but I don't want to return the password hash so I created a new model called 'SensitiveUser' not tied to any table with the password removed. However, when I try to use this model and return the data from the endpoint I get the error "TypeError: 'SQLModelMetaclass' object is not iterable". Any insight on why this is happening is appreciated. I'm struggling to find this exact issue elsewhere online. Thanks.

Repo:

# Return all users.
def select_all_users():
    with Session(rms_engine) as session:
        statement = select(User)
        res = session.exec(statement).all()
        return res

# Returns a specific user record.
def find_user(name):
    with Session(rms_engine) as session:
        statement = select(User).where(User.username == name)
        return session.exec(statement).first()

# Returns a specific user record withou password information.
def find_sensitive_user(name):
    with Session(rms_engine) as session:
        statement = select(SensitiveUser).where(SensitiveUser.username == name)
        return session.exec(statement).first()

Models:

class User(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True)
    username: str
    password: str = Field(max_length=256, min_length=6)
    email: EmailStr
    created_at: datetime.datetime = datetime.datetime.now()
    is_admin: bool = False

class SensitiveUser(SQLModel):
    id: int
    username: str
    email: str
    created_at: datetime.datetime

The code below does work but returns more info than I want, namely the password:

@user_router.post('/register', status_code=201, tags=['users'], description='Register a new user')
def register(user: UserInput):
    users = select_all_users()
    if any(x.username == user.username for x in users):
        raise HTTPException(status_code=400, detail='Username is taken')
    hashed_pwd = auth_handler.get_password_hash(user.password)
    u = User(username=user.username, password=hashed_pwd, email=user.email, is_admin=user.is_admin)
    rms_session.add(u)
    rms_session.commit()
    stored_user = find_user(user.username) 
    return stored_user

The code below returns the "TypeError: 'SQLModelMetaclass' object is not iterable" error:

@user_router.post('/register', status_code=201, tags=['users'], description='Register a new user')
def register(user: UserInput):
    users = select_all_users()
    if any(x.username == user.username for x in users):
        raise HTTPException(status_code=400, detail='Username is taken')
    hashed_pwd = auth_handler.get_password_hash(user.password)
    u = User(username=user.username, password=hashed_pwd, email=user.email, is_admin=user.is_admin)
    rms_session.add(u)
    rms_session.commit()
    stored_user = find_sensitive_user(user.username) # <--- Error Occurs Here
    return stored_user
1
  • Probably the JSON encoder needs to iterate over the object that you're returning (a SQLModelMetaclass), which causes the error. Try transforming the result into a dict Commented Aug 17, 2022 at 20:11

1 Answer 1

2

The thing is that you cannot select an object that has not been mapped to the database's table, and SensitiveUser's objects are not! You should extend your User class to produce SensitiveUsers, like this:

class User(SQLModel, table=True):
    id: Optional[int] = Field(primary_key=True)
    username: str
    password: str = Field(max_length=256, min_length=6)
    email: str
    created_at: datetime.datetime = datetime.datetime.now()
    is_admin: bool = False

    def to_sensitive_user(self) -> SensitiveUser:
        return SensitiveUser(id=self.id, username=self.username, 
                             email=self.email, created_at=self.created_at)

And then, you can use find_user function extended with the call to to_sensitive_user(), like this:

stored_user = find_user(user.username).to_sensitive_user()

Of course, here needs to be some is-not-None checking and SensitiveUser needs to be defined before User, as well.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot. I had a fundamental misunderstanding of how it worked and this cleared it up. Appreciate it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.