Hi guys and girls,

I've recently discovered the joys of using sqlalchemy and would love
to using it together with Traits.  A few months back there was an
attempt to integrate sqlalchemy into traits, though it wasn't really
comprehensive in exploiting all of sqlalchemy's potential.

So I'm trying to work on that and combine ext.Declarative with traits.
The basic idea is to use the DeclarativeMeta type to generate Columns
from Traits and pass those on for the Declarative extension to do its
magic.  This would allow mixing of sqlalchemy attributes and trait
attributes in a single class so that we could still make use of all
the relational setup sqlalchemy does in any case.

Reading through several threads and looking at Elixir's SA integration
helped me a bit though I couldn't find any documentation on how to
implement the InstrumentationManager interface.  I'm assuming this
would be essential for letting Traits and SQLAlchemy play well
together.

There's still a lot of work to do, and I'm not really sure what needs
to be done for everything to work properly.  Would really appreciate
it if someone could help me out.


Here's an example of how it's working at the moment, I'll add the
actual implementation at the end.

#####################################
class User(HasTraitsORM):
    __tablename__ = 'users'

    id = Column('id', Integer, primary_key=True)
    name = Str(sqldb=True)

    def _name_changed(self, old, new):
        print 'Changed name from %s to %s.' % (old, new)

    def __repr__(self):
        return '<User(%s, %s)>' % (self.id, self.name)

people = ['John', 'Charls','Steve','Smith','Jane']

for per in people:
    obj = User(name=per)
    sess = sqlservice.Session()
    sess.add(obj)
    sess.commit()
    sess.close()
    print obj

session = sqlservice.Session()
print '\nQuery all users\n'
for user in session.query(User).order_by(User.name).all():
    print user

session.close()


############################

Which spits out:
###############################
Changed name from  to John.
<User(users.id, )>
Changed name from  to Charls.
<User(users.id, )>
Changed name from  to Steve.
<User(users.id, Steve)>
Changed name from  to Smith.
<User(users.id, Smith)>
Changed name from  to Jane.
<User(users.id, Jane)>

Query all users

<User(2, Charls)>
<User(1, John)>

##############################

Which is really strange behaviour.  There's obviously something wrong
in my implementation of HasTraitsORM but why the different results
within the same loop???  Why add only two instances?

Totally baffles me.

Here's the rest of my code, hope somehow can help me out.  It's very
messy, I've been hacking at it like crazy with no success :-)

Hope you're all having a great weekend.
-Chris


##############################

# Standard library imports.
import logging

# Enthought library imports
from enthought.preferences.api import Preferences
from enthought.traits.api import \
    HasTraits, MetaHasTraits, Int, Str, Bool, Float, Any,\
    String, Enum, Python, \
    on_trait_change, TraitListObject

# Package imports
import sqlalchemy
from sqlalchemy import Column, Integer
from sqlalchemy.schema import MetaData
from sqlalchemy.orm.interfaces import MapperProperty,
InstrumentationManager
from sqlalchemy.orm.attributes import get_attribute, set_attribute,
is_instrumented
from sqlalchemy.orm.collections import InstrumentedList,
collection_adapter
from sqlalchemy.ext.declarative import _as_declarative


# Setup a logger for this module.
logger = logging.getLogger(__name__)


TRAIT_MAPPING = {
         Int : 'sqlalchemy.Integer',
         Str : 'sqlalchemy.Text',
         Enum : 'sqlalchemy.Text',
         String : 'sqlalchemy.Text',
         Float : 'sqlalchemy.Float',
         Bool : 'sqlalchemy.Boolean',
         }


class HasTraitsORMState(InstrumentationManager):
    def __init__(self, cls):
        self.states = {}

    def instrument_attribute(self, class_, key, attr):
        pass

    def install_descriptor(self, class_, key, attr):
        pass

    def uninstall_descriptor(self, class_, key, attr):
        pass

    def instrument_collection_class(self, class_, key,
collection_class):
        return ObjectCollection

    def get_instance_dict(self, class_, instance):
        return instance.__dict__

    def initialize_instance_dict(self, class_, instance):
        instance.reset_traits()

    def initialize_collection(self, key, state, factory):
        data = factory()
        return ObjectCollectionAdapter(key, state, data), data

    def install_state(self, class_, instance, state):
        self.states[id(instance)] = state

    def state_getter(self, class_):
        def find(instance):
            return self.states[id(instance)]
        return find


class ObjectCollectionAdapter(object):
    """
        An adapter for SQLAlchemy for TraitsListObject which is the
collection
        we use for storing instances of classes within attributes of
other
        classes.

        TODO:  Think of a way to get this to behave like a normal
traited
        attribute of objects.
        At the moment collection attributes are set through the mapper
by SA
        when using relations or backref.

    """
    def __init__(self, key, state, collection):
        self.key = key
        self.state = state
        self.collection = collection
        setattr(collection, '_sa_adapter', self)

    def unlink(self, data):
        setattr(data, '_sa_adapter', None)

    def adapt_like_to_iterable(self, obj):
        return iter(obj)

    def append_with_event(self, item, initiator=None):
        self.collection.append(item, emit=initiator)

    def append_without_event(self, item):
        self.collection.append(item, emit=False)

    def remove_with_event(self, item, initiator=None):
        self.collection.remove(item, emit=initiator)

    def remove_without_event(self, item):
        self.collection.remove(item, emit=False)

    def clear_with_event(self, initiator=None):
        for item in list(self):
            self.remove_with_event(item, initiator)

    def clear_without_event(self):
        for item in list(self):
            self.remove_without_event(item)

    def __iter__(self):
        return iter(self.collection)

    def fire_append_event(self, item, initiator=None):
        if initiator is not False and item is not None:
            self.state.get_impl(self.key).fire_append_event
(self.state, item,
                                                            initiator)

    def fire_remove_event(self, item, initiator=None):
        if initiator is not False and item is not None:
            self.state.get_impl(self.key).fire_remove_event
(self.state, item,
                                                            initiator)

    def fire_pre_remove_event(self, initiator=None):
        self.state.get_impl(self.key).fire_pre_remove_event
(self.state,
                                                            initiator)

class ObjectCollection(TraitListObject):
    __emulates__ = list

    def __init__( self, trait, object, name, value ):
        pass

    def __init__(self):
        self.members = list()
    def append(self, object, emit=None):
        self.members.append(object)
        #collection_adapter(self).fire_append_event(object, emit)
    def remove(self, object, emit=None):
        #collection_adapter(self).fire_pre_remove_event(object)
        self.members.remove(object)
        #collection_adapter(self).fire_remove_event(object, emit)
    def __getitem__(self, index):
        return self.members[index]
    def __iter__(self):
        return iter(self.members)
    def __len__(self):
        return len(self.members)
    def __repr__(self):
        if len(self.members) == 0:
            return '[empty collection]'
        s = '['
        for item in self.members[0:max(5, len(self.members))]:
            s += '%s,' % item
        s += '...]'
        return s


class DeclarativeMetaTraits(MetaHasTraits):
    pass

#    def __new__(cls, classname, bases, dict_):
#        return MetaHasTraits.__new__(cls, classname, bases, dict_)

    def __init__(cls, classname, bases, dict_):

        if '_decl_class_registry' in cls.__dict__:
            super(DeclarativeMetaTraits, cls).__init__(cls, classname,
bases, dict_)
            return

        if '__tablename__' not in dict_:
            setattr(cls, '__tablename__', cls.__name__)

        # create sql columns from flagged traits
        if '__class_traits__' in cls.__dict__:
            traits = cls.__dict__['__class_traits__']
            for key, trait in traits.iteritems():
                if getattr( trait, 'sqldb' ):
                    args = getattr( trait, 'col_args' ) or ()
                    kwargs = getattr( trait, 'col_kwargs' ) or {}
                    if 'name' not in kwargs:
                        kwargs['name'] = key
                    if 'type_' not in kwargs:
                        kwargs['type_'] = eval(TRAIT_MAPPING[type
(trait.trait_type)])

                    if getattr(trait, 'sqlpk'):
                        kwargs['primary_key'] = True

                    c = Column(*args, **kwargs)
                    dict_[key] = c
                    setattr(cls, key, c)

        # Pass this along to SA's declarative mapper stuff
        _as_declarative(cls, classname, dict_)

        super(DeclarativeMetaTraits, cls).__init__(cls, classname,
bases, dict_)


class HasTraitsORM(HasTraits):
    __metaclass__ = DeclarativeMetaTraits

    # some SA hacking
    __sa_instrumentation_manager__ = HasTraitsORMState

    _decl_class_registry = dict()
    metadata = MetaData()

    # Any implicit traits added by SQLAlchemy are transient and should
not be
    # copied through .clone_traits(), copy.copy(), or pickling.
    _ = Python(transient=True)


    # For some reason we need to override these, our superclass screws
things up

    def __getattr__(self, key):
        if is_instrumented(self, key):
            return get_attribute(self, key)
        else:
            try:
                return getattr(self, key)
            except:
                pass

    def __setattr__(self, key, value):
        if is_instrumented(self, key):
            set_attribute(self, key, value)
        else:
            setattr(self, key, value)


#################################################


--~--~---------~--~----~------------~-------~--~----~
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