On Tue Jun 10 15:47:00 2014, Noah Davis wrote:

some_model.py
---------------------------------------
[SQLA setup of Base class here]
class Alice(Base):
   __tablename__ = 'alice'
   id = Column(Integer, primary_key=True)
   value = Column(String)

class Bob(Base):
   __tablename__ = 'bob'
   id = Column(Integer, primary_key=True)
   subval = Column(String)
   alice_id = Colum(Integer, ForeignKey('alice.id'))
   alice = relationship('Alice', backref='bobs')
----------------------------------------

some_app.py
----------------------------------------
import some_model

class MyAlice(some_model.Alice):
   def myfunc(self):
       do_nothing_sql_related_here()

class MyBob(some_model.Bob):
   def otherfunc(self):
      again_something_unrelated()
-----------------------------------------

This actually works okay out of the box if I select on the subclasses:
DBSession.query(MyAlice).filter(MyAlice.id==5).first() -> MyAlice(...)

The problem, of course, is relations:
a = DBSession.query(MyAlice).filter(MyAlice.id=1).first()
a.bobs -> [Bob(...), Bob(...), Bob(...)]
instead of
a.bobs -> [MyBob(...), MyBob(...), MyBob(...)]

I suspect there's some way to tell the ORM to Do The Right Thing here,

Well IMHO it is doing the Right Thing right now, Alice has a relationship that points to Bob. So it's going to give you Bob objects. I don't know how any system could be devised such that it would know you want to go to "MyBob" instead. Especially if you have MyBobOne, MyBobTwo, etc.

if you wanted MyBob you'd need to tell it that. SQLA isn't really expecting this kind of thing but you can make it "work", with warnings, like this:

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Alice(Base):
   __tablename__ = 'alice'
   id = Column(Integer, primary_key=True)
   bobs = relationship("Bob", back_populates="alice")

class Bob(Base):
   __tablename__ = 'bob'
   id = Column(Integer, primary_key=True)
   alice_id = Column(Integer, ForeignKey('alice.id'))
   alice = relationship('Alice', back_populates="bobs")


class MyAlice(Alice):
   def myfunc(self):
      print "myfunc"

   bobs = relationship("MyBob", back_populates="alice")

class MyBob(Bob):
   def otherfunc(self):
       print "otherfunc"

   alice = relationship('MyAlice', back_populates="bobs")

e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add_all([
   MyAlice(
       bobs = [
           MyBob(),
           MyBob()
       ]
   )
])
s.commit()
s.close()

a1 = s.query(MyAlice).first()
print a1.bobs

this isn't really a great approach though.

but I have no idea what it might be. I'd like the particular
applications to be as unaware of the underlying table information as
possible. I guess in essence I'm trying to separate business logic
from the DB logic as much as possible. Maybe I'm heading down a
dead-end... I'm open to better suggestions.

well if your really want it that way, you can define Table objects separately. Relationships you can get in there using declarative mixin patterns perhaps. But if you want a custom-defined MyAlice to point to a custom-defined MyBob you'd need some system that knows how to figure that out.

Here's a goofy way to do it by name, you might want to get into something more comprehensive but I'm hoping this is inspiration...

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, declared_attr

class DynamicHierarchy(object):

   @classmethod
   def hierarchy(cls, name):
       target_hierarchy = cls.hierarchy_name
       return cls._decl_class_registry[target_hierarchy + name]

Base = declarative_base(cls=DynamicHierarchy)

class Alice(Base):
   __abstract__ = True

   @declared_attr
   def __table__(cls):
       return Table("alice",
                   cls.metadata,
                   Column('id', Integer, primary_key=True),
                   useexisting=True
           )

   @declared_attr
   def bobs(cls):
return relationship(lambda: cls.hierarchy("Bob"), back_populates="alice")

class Bob(Base):
   __abstract__ = True

   @declared_attr
   def __table__(cls):
       return Table("bob",
                       cls.metadata,
                       Column('id', Integer, primary_key=True),
                       Column("alice_id", ForeignKey('alice.id')),
                       useexisting=True
                   )
   @declared_attr
   def alice(cls):
return relationship(lambda: cls.hierarchy('Alice'), back_populates="bobs")


class MyAlice(Alice):
   hierarchy_name = "My"

   def myfunc(self):
      print "myfunc"


class MyBob(Bob):
   hierarchy_name = "My"

   def otherfunc(self):
       print "otherfunc"


e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)

s = Session(e)
s.add_all([
   MyAlice(
       bobs = [
           MyBob(),
           MyBob()
       ]
   )
])
s.commit()
s.close()

a1 = s.query(MyAlice).first()
print a1.bobs




Thanks,
   Noah

--
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 sqlalchemy+unsubscr...@googlegroups.com
<mailto:sqlalchemy+unsubscr...@googlegroups.com>.
To post to this group, send email to sqlalchemy@googlegroups.com
<mailto:sqlalchemy@googlegroups.com>.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

--
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 sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to