I have these two tables named User and UserRole.
import enum
from typing import List
from sqlmodel import Column, Enum, Field, Relationship, SQLModel
class UserRoleType(str, enum.Enum):
admin = 'admin'
client = 'client'
class UserRole(SQLModel, table=True):
__tablename__ = 'user_role'
id: int | None = Field(default=None, primary_key=True)
type: UserRoleType = Field(
default=UserRoleType.client,
sa_column=Column(Enum(UserRoleType)),
)
write_access: bool = Field(default=False)
read_access: bool = Field(default=False)
users: List['User'] = Relationship(back_populates='user_role')
class User(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
username: str = Field(..., index=True)
user_role_id: int = Field(..., foreign_key='user_role.id')
user_role: 'UserRole' = Relationship(back_populates='users')
I can easily insert entries into the DB like so:
async with get_session() as session:
role = UserRole(description=UserRoleType.client)
session.add(role)
await session.commit()
user = User(username='test', user_role_id=role.id)
session.add(user)
await session.commit()
await session.refresh(user)
And access the committed data with:
results = await session.execute(select(User).where(User.id == 1)).one()
Output: (User(user_role_id=1, username='test', id=1),)
Notice that there's an user_role_id, but where's the user_role object?
In fact, if I try to access it, it raises an error:
AttributeError: Could not locate column in row for column 'user_role'
I also tried to pass the role instead of the user_role_id at the insertion of the User:
...
user = User(username='test', user_role=role)
But I got the following error:
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 2 - probably unsupported type.
A few things first.
User.user_role_idandUser.user_rolefields to be "pydantically" optional. This allows you to create user instances without passing the role to the constructor, giving you the option to do so after initialization or for example by appendingUserobjects to theUserRole.userslist instead. To enforce that a user must have a role on the database level, you simply definenullable=Falseon theUser.user_role_idfield. That way, if you try to commit to the DB without having defined a user role for a user in any of the possible ways, you will get an error.role = UserRole(description=UserRoleType.client). I assume thedescriptionis from older code and you meant to writerole = UserRole(type=UserRoleType.client).UserRole.typeto be not nullable on the database side. You can do so by passingnullable=Falseto theColumnconstructor (not theFieldconstructor).This is how I would suggest you define the models:
Here is a little test function to show that it works as expected:
This passes without errors. The SQL output generated by the engine is as follows:
PS: I know the question was posted a while ago, but maybe this still helps or helps someone else.