On Jan 30, 2012, at 9:49 AM, Martijn Moeling wrote:

> sorry I have to get back on this. I renamed all Columns in my application 
> definitions to MyColumn a while back. and everything worked.
> Now that I'm starting to use functionality of MyColumn. (The reason I needed 
> this) I run into some trouble.


so we can skip the easy "_constructor" thing and implement the rest of Column.  
If you read the first error message you get with a straight subclass:

TypeError: Could not create a copy of this <class '__main__.MySpecialColumn'> 
object.  Ensure the class includes a _constructor() attribute or method which 
accepts the standard Column constructor arguments, or references the Column 
class itself. Original error: __init__() takes exactly 2 arguments (8 given)


So Column goes through a lot of trouble to try to diagnose what's going on.    
It's telling us to create a method called _constructor(), that accepts the 
standard arguments that Column does.  The return value is our modified column:

class MySpecialColumn(Column):
    x = 0
    y = 0
    def __init__(self, type_, **options):
        filtered_options = {}
        for name, option in options.items():
            if hasattr(self, name):
                setattr(self, name, option)
            else:
                filtered_options[name] = option

        Column.__init__(self, type_, **filtered_options)

    def _constructor(self, name, type_, **kw):
        kw['x'] = self.x
        kw['y'] = self.y
        col = MySpecialColumn(type_, **kw)
        col.name = name
        return col


> 
> At the other end, I might need to introspect the objects, I have tried but I 
> have trouble in relating to X or Y since the Name Column is an 
> InstrumentedAttribute.

Yeah to get at them directly you'd need to say:

assert MyClass.value.property.columns[0].x == 5

if you want MyClass.value.x == 5, you need to tack that on when it gets 
instrumented:

@event.listens_for(Base, 'attribute_instrument')
def configure_listener(class_, key, inst):
    if isinstance(inst.property, ColumnProperty) and \
        isinstance(inst.property.columns[0], MySpecialColumn):
        inst.x = inst.property.columns[0].x
        inst.y = inst.property.columns[0].y


> 
> 
> One other thing. I can get Columns by iterating over self.__table__.Columns. 
> I can get Foreign keys using:
> inspector.get_foreign_keys(SomeClass.__tablename__)

I'd note those are not at all equivalent operations, in that inspector is going 
to go out to the database in order to get the FK information.   Assuming your 
table metadata has it configured, you can get it locally by iterating through 
SomeClass.__table__.foreign_keys:

for fk in MyClass.__table__.foreign_keys:
    print fk.parent, fk.column


if you want the full constraint object, which I'd recommend if you have 
composite FKs in use:

for const in MyClass.__table__.constraints:
    if isinstance(const, ForeignKeyConstraint):
        for element in const.elements:
            print element.parent, element.column


> 
> But Now I need to access the relation objects defined in my classes and 
> Introspect them.

relationship is not the same as a foreign key, see below...

There's a ticket for 0.8 that would attempt to provide more accessors for these 
things, including things like mapper.relationships and stuff like that.   Feel 
free to add in things we should consider on 
http://www.sqlalchemy.org/trac/ticket/2208.

an example with everything happening, including in a tricky from_self() query:

from sqlalchemy import create_engine, Column, Integer, ForeignKey, 
ForeignKeyConstraint
from sqlalchemy.orm import Session, configure_mappers, relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
from sqlalchemy.orm.properties import ColumnProperty, RelationshipProperty

class MySpecialColumn(Column):
    x = 0
    y = 0
    def __init__(self, type_, **options):
        filtered_options = {}
        for name, option in options.items():
            if hasattr(self, name):
                setattr(self, name, option)
            else:
                filtered_options[name] = option

        Column.__init__(self, type_, **filtered_options)

    def _constructor(self, name, type_, **kw):
        kw['x'] = self.x
        kw['y'] = self.y
        col = MySpecialColumn(type_, **kw)
        col.name = name
        return col

Base= declarative_base()

@event.listens_for(Base, 'attribute_instrument')
def configure_listener(class_, key, inst):
    if isinstance(inst.property, ColumnProperty) and \
        isinstance(inst.property.columns[0], MySpecialColumn):
        inst.x = inst.property.columns[0].x
        inst.y = inst.property.columns[0].y

class MyClass(Base):
    __tablename__ = "sometable"

    id = Column(Integer, primary_key=True)
    foo_id = Column(Integer, ForeignKey('foo.id'))
    value = MySpecialColumn(Integer, nullable=False, x=5, y=12)
    myfoo = relationship("MyFoo")

class MyFoo(Base):
    __tablename__ = 'foo'
    id = Column(Integer, primary_key=True)


assert MyClass.value.property.columns[0].x == 5

# configure mappers now to fire off
# the instrumentation event ahead
# of time, just so we can see value.x
configure_mappers()
assert MyClass.value.x == 5

# foreign keys

for fk in MyClass.__table__.foreign_keys:
    print fk.parent, fk.column

for const in MyClass.__table__.constraints:
    if isinstance(const, ForeignKeyConstraint):
        for element in const.elements:
            print element.parent, element.column

# relationships
for prop in MyClass.__mapper__.iterate_properties:
    if isinstance(prop, RelationshipProperty):
        print "relationship:", prop.key, prop.mapper.class_, 

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

s = Session(e)
s.add_all([
    MyClass(value=12), MyClass(value=18)
])
s.commit()

# from_self() here creates a subquery and exercises the 
# column copying
print s.query(MyClass).filter(
            MyClass.value.between(MyClass.value.x, MyClass.value.y)
        ).from_self().all()

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to sqlalchemy@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.

Reply via email to