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_id
andUser.user_role
fields 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 appendingUser
objects to theUserRole.users
list instead. To enforce that a user must have a role on the database level, you simply definenullable=False
on theUser.user_role_id
field. 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 thedescription
is from older code and you meant to writerole = UserRole(type=UserRoleType.client)
.UserRole.type
to be not nullable on the database side. You can do so by passingnullable=False
to theColumn
constructor (not theField
constructor).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.