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