On Wed, Sep 9, 2020, at 2:36 PM, Bobby Rullo wrote:
> Hi there, 
> 
> I'm trying to create a relationship for a Mxin that is agnostic to what the 
> primary key of the mixed object is.
> 
> Basically we have this:
> 
> class TransitionBase(SurrogatePK, Model):
>     __abstract__ = True
> 
>     obj_id = Column(String, nullable=False, index=True)
>     state = Column(String, nullable=False, index=True)
>     ...(more stuff)...
> 
> class HasStateMixin:
>     @staticmethod
>     def get_state_class() -> Type[TransitionBase]:
>         raise NotImplementedError()
> 
>     @declared_attr
>     def transitions(cls) -> List[TransitionBase]:
>         state_cls = cls.get_state_class()
>         return relationship(
>             state_cls,
>             primaryjoin=foreign(state_cls.obj_id) == remote(cls.id),  # type: 
> ignore
>             order_by=state_cls.id.desc(),
>         )
> 
> And it works well, as long as the `id` property is a normal Column. Eg:
> 
> class Transition(TransitionBase):
>     __tablename__ = ''transitions'
> 
> 
> class Obj(Model, HasStateMachineMixin):
>     __tablename__ = 'obj'
>     __table_args__ = {'schema': test_schema}
> 
>     id = Column(String, primary_key=True)
> 
>     @staticmethod
>     def get_state_class() -> Type[TransitionBase]:
>         return Transition
> 
> But now I have a case where the primary key of the mixed class is three 
> columns. My first instinct was to use a hybrid property like this:
> 
> class ThreePrimaryKeys(HasStateMachineMixin):
>     a = Column(String, primary_key=True)
>     b = Column(String, primary_key=True)
>     c = Column(String, primary_key=True)
> 
>     @hybrid_property
>     def id(self):
>         return f'/{self.a}/{self.b}/{self.c}'
> 
>     @id.expression
>     def id(cls):
>         return func.concat(
>             '/', cls.a, '/', cls.b, '/', cls.c,
>         )
> 
> But that doesn't work: when I create a ThreePrimaryKeys() and then call 
> obj.transitions.append(MyTransition(state='foo')) it doesn't properly persist 
> the full concatenated ID - weirdly, it just uses column 'c' (or the 
> equivalent - type names have been changed to protect the innocent)
> 
> My question is: should this work? And if so, what am I doing wrong? If not, 
> do you have an alternate approach?

I'm not really sure why it doesn't work but the correct approach here is to 
have a composite ForeignKeyConstraint on the class that refers to 
ThreePrimaryKeys, or at least a join condition that links the three columns 
separately to three columns on the referencing object (but there should really 
be a FK constraint, as it looks like you are trying to do reasonable relational 
schema design).   you can get information on what kind of primary key a mapped 
class has by using inspect(class).primary_key .   your TransitionBase thing 
would need to dynamically have multiple "obj_id" columns added based on what 
kind of target it is hitting.

There's a lot of ways to do this, including in your transitions() method, you'd 
need to look at the class you're linking to, get the list of primary key 
columns, then add that many FK columns to the immediate class, set up a 
ForeignKeyConstraint for them and add that to the Table also (like 
cls.__table__.append_constraint()) , then the relationship() would just work.   
or you could build up the primary join using and_(c1 == fk1, c2 == fk2, ..)

a bit of a handwavy answer but that's the general idea.    making a 
concatenated string like that is not good relational design, it's denormalized 
and can't be properly indexed.



> 
> Thanks in advance,
> 
> Bobby
> 
> 

> --
> SQLAlchemy - 
> The Python SQL Toolkit and Object Relational Mapper
>  
> http://www.sqlalchemy.org/
>  
> To post example code, please provide an MCVE: Minimal, Complete, and 
> Verifiable Example. See http://stackoverflow.com/help/mcve for a full 
> description.
> --- 
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to [email protected].
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/0083c5d4-cd66-4346-8108-f85a8909d69an%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy/0083c5d4-cd66-4346-8108-f85a8909d69an%40googlegroups.com?utm_medium=email&utm_source=footer>.

-- 
SQLAlchemy - 
The Python SQL Toolkit and Object Relational Mapper

http://www.sqlalchemy.org/

To post example code, please provide an MCVE: Minimal, Complete, and Verifiable 
Example.  See  http://stackoverflow.com/help/mcve for a full description.
--- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/491ffccb-bd66-4dc2-a29f-2a73943f8c22%40www.fastmail.com.

Reply via email to