I'm creating a database User
model which can belong to several Trait
s. The below script reproduces a strange behavior that I don't understand -- after committing the new rows, the relationship attaches the Trait
s to the User
s in an unpredictable way.
I want to be able to attach 1+ traits to a user, and I want users to be able to share traits.
But in this example, when a trait is shared between users, the Trait sometimes is attached to user 1, and othertimes user 2. How can I make it so that the users can share traits?
import enum
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Enum, UniqueConstraint, ForeignKey
from sqlalchemy.orm import backref, relationship, validates
Base = declarative_base()
class TraitName(enum.Enum):
happy = 0
mad = 1
full = 2
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(80), unique=True, nullable=False)
traits = relationship("Trait")
def __repr__(self):
return self.username + str(traits)
class Trait(Base):
__tablename__ = 'traits'
id = Column(Integer, primary_key=True)
name = Column(Enum(TraitName), nullable=False)
user_id = Column(Integer, ForeignKey('users.id'))
__table_args__ = (UniqueConstraint('name', 'user_id', name='_user_trait'),)
def __repr__(self):
return str(self.name)
When I run this script several times,
from sqlalchemy import create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
engine = create_engine('postgresql://postgres@localhost:5420/test_db')
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
session = Session(engine)
user1 = User(username="bob")
user2 = User(username="ann")
happy_trait = Trait(name=TraitName.happy)
full_trait = Trait(name=TraitName.full)
user1.traits.extend([full_trait, happy_trait])
user2.traits.append(happy_trait)
session.add_all([user1, user2])
session.commit()
print([t.traits for t in session.query(User).all()])
print(session.query(Trait.user_id).all())
print(session.query(Trait).filter(Trait.user_id.in_((1,))).all())
session.close()
different results:
either:
[[TraitName.full, TraitName.happy], []] # Users' traits
[(1,), (1,)] # Traits' user_ids
[TraitName.full, TraitName.happy] # Traits with uers_id 1
or
[[TraitName.full], [TraitName.happy]] # Users' traits
[(1,), (2,)] # Traits' user_ids
[TraitName.full] # Traits with uers_id 1
So I would like to understand why the Trait
will be assigned unpredictably. And how I can model my User and Traits to avoid this -- maybe this isn't the appropraite pattern for attaching traits to users?
Also, if it's possible (I suppose everything is possible), I'd like to have two fields on User
: inactive_traits
and active_traits
fields. But in anycase I first have to figure out how to get one list of traits working.
You named your classes badly, which caused you to use them wrong.
Your TraitName is actually your Trait, which should be shared and your Trait is actually a UserTrait, which can't be shared between users, but you try to share the instance between them.
I'd do the following instead:
import enum
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm, Enum, ForeignKey, create_engine
Base = declarative_base()
class Trait(enum.Enum):
happy = 0
mad = 1
full = 2
class User(Base):
__tablename__ = 'users'
user_id = Column(Integer, primary_key=True)
username = Column(String(80), unique=True, nullable=False)
traits = orm.relationship("UserTrait")
def __repr__(self):
return self.username + str(self.traits)
class UserTrait(Base):
__tablename__ = 'usertraits'
user_id = Column(Integer, ForeignKey('users.user_id'), primary_key=True)
trait = Column(Enum(Trait), primary_key=True)
def __repr__(self):
return str([self.user_id, self.trait])
def __main__():
engine = create_engine('postgresql:///?host=/var/run/postgresql/', echo=False)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
session = orm.Session(engine)
user1 = User(username="bob")
user2 = User(username="ann")
user1.traits.extend([UserTrait(trait=Trait.full), UserTrait(trait=Trait.happy)])
user2.traits.append(UserTrait(trait=Trait.happy))
session.add_all([user1, user2])
session.commit()
print([t.traits for t in session.query(User).all()])
print(session.query(UserTrait.user_id).all())
print(session.query(UserTrait).filter(UserTrait.user_id.in_((1,))).all())
session.close()
if __name__ == "__main__":
__main__()