(Newbie) Using a custom collection extending from a dict(). Is that doable?

275 views
Skip to first unread message

Hector Blanco

unread,
Nov 8, 2010, 6:36:57 PM11/8/10
to sqlal...@googlegroups.com
Hello everyone...

I'm trying to use a custom collection to "connect" (or relate) two
classes but I haven't been able to do it. Maybe I got the whole
concept of the custom collections wrong, but let me explain what I am
doing (and see if someone can give me a hint, or something)

I have a Parent class (which some of you will remember from other
questions) with a couple of children. One of the children fields
stores children whose type is "VR" and the other children with a "CC"
type.

I don't really need persistence for the collection used to store the
children, but I need it to be of an special class so it will have some
methods that I have implemented and that need to be there. That would
be the "ZepConnector" (and, for purposes of the example, it's method
foo() it's the one I need to use). As you can see in the following
lines, I randomly test its availability in the addChild1() method of
the Parent.

--------------------- Parent.py -----------------

from megrok import rdb
from sqlalchemy import Column
from sqlalchemy import and_
from sqlalchemy.orm import relationship
from sqlalchemy.types import Integer
from sqlalchemy.types import String
from mylibraries.database.tests.Child import Child
from mylibraries.database.tests.Tables import testMetadata
from mylibraries.database.tests.ZepConnector import ZepConnector

class Parent(rdb.Model):
rdb.metadata(testMetadata)
rdb.tablename("parents_table")
rdb.tableargs(schema='test2', useexisting=False)

id = Column("id", Integer, primary_key=True, nullable=False, unique=True)
_whateverField1 = Column("whatever_field1", String(16)) #Irrelevant
_whateverField2 = Column("whatever_field2", String(16)) #Irrelevant

child1 = relationship(
"Child",
uselist=True,
primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type
== "VR")),
collection_class=ZepConnector("VR")
)

child2 = relationship(
"Child",
uselist=True,
primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type
== "CC")),
collection_class=ZepConnector("CC")
)

def __init__(self):
print "Parent __init__"
self._whateverField1 = "Whatever1"
self._whateverField2 = "Whatever2"
self.child1 = ZepConnector("VR")
self.child2 = ZepConnector("CC")

def addChild1(self, child):
if isinstance(child, Child):
print("::addChild1 > Testing .foo method: " + str(self.child1.foo()))
# The line above doesn't really makes much but testing the
accessibility of the .foo() method.
# As I explain later, it doesn't work
self.child1.append(child)

def addChild2(self, child):
if isinstance(child, Child):
self.child2.append(child)

----------------------------------------------------

Please note that I'm using megrok. For those who are not familiar with
it, allow me to explain that it is just a tool that writes the mappers
itself and makes it a little bit "programmer friendly".

I guess The mapping of the Parent() class in regular SqlAlchemy would
be something like:

mapper(Parent, parents_table, properties={
id = Column("id", Integer, primary_key=True, nullable=False, unique=True)
_whateverField1 = Column("whatever_field1", String(16)) #Irrelevant
_whateverField2 = Column("whatever_field2", String(16)) #Irrelevant
child1 = relationship( # etc, etc, etc
})
#

but I'm 100%... erm... 90% certain that using that tool is not what
lead me to ask what I'm going to ask here (I mean: I don't think is
interfering with the Collections thing)


A child is a very simple class:

--------------- Child.py --------------------------

import random

from megrok import rdb
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy.types import Integer
from sqlalchemy.types import String
from mylibraries.database.tests.Tables import testMetadata

class Child(rdb.Model):
rdb.metadata(testMetadata)
rdb.tablename("children_table")
rdb.tableargs(schema='test2', useexisting=False)

parent_id = Column("parent_id", Integer,
ForeignKey("test2.parents_table.id"), primary_key=True)
type = Column("type", String(2), nullable=True, primary_key=True)
hasher = Column("hasher", String(5))

def __init__(self):
self.type = None
self.hasher = self.generateHasher()

def setType(self, typeParameter):
if typeParameter in set(["VR", "CC"]):
self.type = typeParameter

@staticmethod
def generateHasher():
retval = str()
for i in random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
5):
retval += i
return retval
---------------------------------------------------------

Let's say every Child instance will have a unique "hasher" field that
can be used as a key in a dictionary (the example above is far away
from the reality, but it illustrates a little bit how the Child will
work)

And now my custom connector. I want it to behave as a list or a set
(more like a set, although I don't mind much) but it's a class that
inherits from dict.

-------------------- ZepConnector.py --------------------

from sqlalchemy.orm.collections import collection

class ZepConnector(dict):
__emulates__ = list

def __init__(self, type):
self.type = type

def foo(self):
return True

@collection.appender
def append(self, item):
#Appends a child to itself
if self.foo():
item.setType(self.type)
self[item.hasher] = item

@collection.remover
def remove(self, item):
try:
del self[item.hasher]
except ValueError, e:
print("::remove > Got exception when trying to remove entry=" +
str(item.hasher) + ". The exception is: " + str(e))

def extend(self, items):
pass
---------------------------------------------------------

But I don't know why, the "ZepConnector" instances in the Parent class
don't seem to be of a "ZepConnector" type but of an
"InstrumentedList":

When in the addChild1 method of Parent() I try to test the .foo()
method (which should just return True) I get this error:
AttributeError: 'InstrumentedList' object has no attribute 'foo'

It's strange... The __init__ method of the ZepConnector is properly
executed... but when I try to use it, it is not a ZepConnector...

In a second try I wrote:

class ZepConnector(dict):
__emulates__ = set

but this even makes things worse, because I get:
TypeError: Incompatible collection type: ZepConnector is not list-like

In a third (or second point second) try, I though... "well... if it's
saying that ZepConnector is not a list, maybe telling the Parent() not
to use a list in the relationship may help... Maybe stating that the
collection_class is a ZepConnector makes unnecessary the uselist
parameter in the relationship..."

And so I wrote:

child1 = relationship(
"Child",
uselist = False,
primaryjoin=lambda: and_((Parent.id == Child.parent_id),
(Child.type == "VR")),
collection_class=ZepConnector("VR")
)

But that threw a creepy exception talking about a field which I
shouldn't see and that I don't want to see... ever...
:-D
AttributeError: 'ZepConnector' object has no attribute '_sa_instance_state'

If someone has any ideas, guidance, counseling... whatever... I'd
really appreciate you sharing it with me... erm... us...

Thank you in advance!

(if you have reached this line, you certainly deserve a "thank you"
just for your patience reading this huge email)

Michael Bayer

unread,
Nov 9, 2010, 5:13:23 PM11/9/10
to sqlal...@googlegroups.com

On Nov 8, 2010, at 6:36 PM, Hector Blanco wrote:

> methods that I have implemented and that need to be there. That would
> be the "ZepConnector" (and, for purposes of the example, it's method
> foo() it's the one I need to use). As you can see in the following
> lines, I randomly test its availability in the addChild1() method of
> the Parent.
>

> child1 = relationship(
> "Child",
> uselist=True,
> primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type
> == "VR")),
> collection_class=ZepConnector("VR")
> )

So this is incorrect - collection_class takes a class or other callable as an argument that will produce an instance of your collection. The ZepConnector source you have below indicates that ZepConnector("VR") is an instance of the collection. You need to use a lambda: there. The other errors you're getting would appear to extend from that (and is also why __init__ is called on ZepConnector - you're calling it yourself).


Hector Blanco

unread,
Nov 9, 2010, 6:05:18 PM11/9/10
to sqlal...@googlegroups.com
Shoot!! It works!! :D :D

---------------------- Parent.py (extract) ----------------------
# . . .


child1 = relationship(
"Child",
uselist=True,
primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type
== "VR")),

collection_class=lambda: ZepConnector("VR")
)

child2 = relationship(


"Child",
uselist=True,
primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type

== "CC")),
collection_class=lambda: ZepConnector("CC")
)

-----------------------
Do you know any documentation where I can understand "why"? Conor
already mentioned
(http://groups.google.com/group/sqlalchemy/msg/d8fbbbff6d961332) the
importance of using lambdas, strings... to avoid mistakes,

------ Quote: ---------
In SQLAlchemy you get around circular dependencies by:

* Using strings as the target of ForeignKey()
* Using class name strings as the target of a relation (declarative
only)
* Using strings or callables as primaryjoin/secondaryjoin arguments
in a relationship()
---------------------------------

but I'd like to understand a little bit more how does it work (what's
going on internally) so I won't make similar errors in the future.

Thank you so much...

2010/11/9 Michael Bayer <mik...@zzzcomputing.com>:

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

Reply all
Reply to author
Forward
0 new messages