Hi Hector,

On Fri, Nov 12, 2010 at 7:46 AM, Hector Blanco <white.li...@gmail.com> wrote:
> Hello everyone.
>
> I was wondering if it's possible to inherit a custom collection to
> create another custom collection.
>
> A few days ago I was trying to use my own class as a custom_collection
> (http://groups.google.com/group/sqlalchemy/msg/ba1c64c3d227f586).
> Thanks to Michael Bayer I was able to do it, but now I would like to
> go one step further, and inherit my custom collection to create
> another custom collection.
>
> To simplify a little what I asked in the other message, let's say I have a:
>
> def ClassA(declarativeBase):
>        __tablename__ = "aes"
>        id = Column("id", Integer, primary_key=True)
>        _whatever = Column("type", String(64))
>        def __init__(self):
>                self._whatever = "whatever"
>
> Then I have my custom collection for instances of "ClassA":
>
> def ContainerOfA(dict):
>        __emulates__ = set
>        def __init__(self):
>                self._field = "I'm a great... awesom! container"
>
>        #I also defined the appender, remover and iterator
>       �...@collection.iterator
>        def __iter__(self):
>                return self.itervalues()
>
>       �...@collection.appender
>        def append(self, item):
>                self[item.getUniqueHash()] = item
>
>       �...@collection.remover
>        def remove(self, item):
>                if item.getUniqueHash() in self.keys():
>                        del self[item.getUniqueHash()]
>
> And then I was happily able to use it in any relationships:
>
> def YetAnotherClass(declarativeBase):
>        id = Column("id", Integer, primary_key=True)
>        classesA = relationship("ClassA",
>                uselist=True,
>                secondary="intermediate_table",
>                collection_class=lambda: ContainerOfA(),
>                cascade="all, delete, delete-orphan",
>                single_parent=True
>        )
>
> Now I needed to extend "ClassA" in a "Class B" and "ContainerOfA" in
> "ContainerOfB". I added the polymorphic stuff to "ClassA" and "ClassB"
> to create a joined table inheritance, as detailed in
> http://www.sqlalchemy.org/docs/orm/inheritance.html#joined-table-inheritance
> . (it seems to be working fine, that's why I am not completely
> detailing it here)
>
> def ClassB(ClassA):
>        __tablename__ = "bs" #Sorry for that
>        __mapper_args__ = {'polymorphic_identity': 'ClassB'}
>        id = Column("id", Integer, ForeignKey('aes.id'), primary_key=True)
>        def __init__(self):
>                self._anotherWhatever = "another whatever"
>
> def ContainerOfB(ContainerOfA):
>        def __init__(self):
>                super(ContainerOfB, self).__init__()
>        def anotherMethodOnlyForBInstances(self):
>                # do interesting stuff for B classes
>
> Then I tried to use it in a relationship:
>
> def YetYetAnotherClass(declarativeBase):
>        id = Column("id", Integer, primary_key=True)
>        classesB = relationship("ClassB",
>                uselist=True,
>                secondary="another_intermediate_table",
>                collection_class=lambda: ContainerOfB(),
>                cascade="all, delete, delete-orphan",
>                single_parent=True
>        )
>
> But when I tried to append a "ClassB" instance through the
> relationship detailed above, I got this exception:
>
>>> Type ContainerOfB must elect an appender method to be a collection class

I haven't been able to replicate this behavior.  When testing your
code I did notice that you are using 'def' to declare your classes,
which won't actually create the type.  I make that same typo myself
periodically and it can be quite tricky to track down the one "def'd"
class that's causing seemingly unrelated errors.

Anyhow, I've attached the working test case I put together.  If you
can modify this to replicate your behavior, we can track down any bugs
that might be present in the collection API's appender metadata
bookkeeping.  You definitely should not have to re-declare an
@appender on a subclass- the collection mechanics should be sweeping
over your inherited class and transparently picking up the methods.
This is definitely working for the cases in the SQLA unit tests, but
it's definitely possible you've found some corner case with that dict
that's declared to be emulating a set.

Cheers,
Jason

> I thought... "ok, ok... let's just explicitly add the 'appender' to
> the ContainerOfB class...  The only thing I need to do is calling the
> appender of the super class, anyway... no biggie" and so I did:
>
> def ContainerOfB(ContainerOfA):
>        # [ . . . ] #
>       �...@collection.appender
>        def append(self, classBInstance):
>                return super(ContainerOfB, self).append(classBInstance)
>
> But then... another exception when I tried to add an instance of ClassB():
>
>>> InvalidRequestError: Instance <ClassB at 0xba9726c> is already associated 
>>> with an instance of <class 'mylibraries.classesTest.YetYetAnotherClass'> 
>>> via its YetYetAnotherClass.classesB attribute, and is only allowed a single 
>>> parent.
>
> Well... I need the cascade to properly delete the items
> (http://www.sqlalchemy.org/docs/orm/session.html#deleting) and in
> order to use that, I need the single_parent = True.
>
> Then funny thing is that if I totally rewrite the appender method in
> ContainerOfB:
>
> def ContainerOfB(ContainerOfA):
>        # [ . . . ] #
>       �...@collection.appender
>        def append(self, classBInstance):
>                # write here the exact same code than ContainerOfA changing
>                # the reference to the "item" parameter by "classBInstance"
>                # (that's the only difference)
>
> then everything is working fine. I have made some more tests, and the
> inheritance ClassA <- ClassB seems to be working fine. In said tests I
> removed the "cascade" and the "single_parent" parameters of the
> "classesB" relationship. By doing that, I was able to insert instances
> of ClassB in the classesB container and all the information was
> properly stored in the database (the polymorphic identity was added
> properly, the foreign key of the ClassB() instance was properly
> pointing to its "parent" class "ClassA"'s id, all the fields were
> there... perfect!). The problem seems to come when I try to use the
> "single_parent = True" with my inherited ContainerOfB class.
>
> As far as I can tell (and I may be totally wrong) it seems to me that
> somehow, calling the "super - appender" appends the instance twice. I
> thought that because rewriting the whole "append" method in my
> ContainerOfB fixed the problem. But that is not "perfect". In regular
> OOP I don't need to do that. Is there any way to fix that? Am I
> missing something? (sorry... let me rephrase it: "What am I missing?",
> because, newbie as I am, I'm sure I'm doing something wrong)
>
> Reading the custom collections chapter
> (http://www.sqlalchemy.org/docs/orm/collections.html#instrumentation-and-custom-types),
> I found:
>
> """
> However, it is important to note that the instrumentation process
> _will_ modify the type, adding decorators around methods
> automatically.
> """
>
> I don't know if this is what is affecting my code, but if so, I
> haven't been able to find a better workaround than rewriting the
> "append" method.
>
> Any hint will be appreciated! Thank you in advance!
>
> --
> You received this message because you are subscribed to the Google Groups 
> "sqlalchemy" group.
> To post to this group, send email to sqlalch...@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> For more options, visit this group at 
> http://groups.google.com/group/sqlalchemy?hl=en.
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Attachment: appender.py
Description: Binary data

Reply via email to