| # Copyright (c) The PyAMF Project. |
| # See LICENSE for details. |
| |
| """ |
| SQLAlchemy adapter module. |
| |
| @see: U{SQLAlchemy homepage<http://www.sqlalchemy.org>} |
| |
| @since: 0.4 |
| """ |
| |
| from sqlalchemy import orm, __version__ |
| |
| try: |
| from sqlalchemy.orm import class_mapper |
| except ImportError: |
| from sqlalchemy.orm.util import class_mapper |
| |
| import pyamf |
| |
| UnmappedInstanceError = None |
| |
| try: |
| class_mapper(dict) |
| except Exception, e: |
| UnmappedInstanceError = e.__class__ |
| |
| |
| class_checkers = [] |
| |
| |
| class SaMappedClassAlias(pyamf.ClassAlias): |
| KEY_ATTR = 'sa_key' |
| LAZY_ATTR = 'sa_lazy' |
| EXCLUDED_ATTRS = [ |
| '_entity_name', '_instance_key', '_sa_adapter', '_sa_appender', |
| '_sa_class_manager', '_sa_initiator', '_sa_instance_state', |
| '_sa_instrumented', '_sa_iterator', '_sa_remover', '_sa_session_id', |
| '_state' |
| ] |
| |
| STATE_ATTR = '_sa_instance_state' |
| |
| if __version__.startswith('0.4'): |
| STATE_ATTR = '_state' |
| |
| def getCustomProperties(self): |
| self.mapper = class_mapper(self.klass) |
| self.exclude_attrs.update(self.EXCLUDED_ATTRS) |
| |
| self.properties = [] |
| |
| for prop in self.mapper.iterate_properties: |
| self.properties.append(prop.key) |
| |
| self.encodable_properties.update(self.properties) |
| self.decodable_properties.update(self.properties) |
| |
| self.exclude_sa_key = self.KEY_ATTR in self.exclude_attrs |
| self.exclude_sa_lazy = self.LAZY_ATTR in self.exclude_attrs |
| |
| def getEncodableAttributes(self, obj, **kwargs): |
| """ |
| Returns a C{tuple} containing a dict of static and dynamic attributes |
| for C{obj}. |
| """ |
| attrs = pyamf.ClassAlias.getEncodableAttributes(self, obj, **kwargs) |
| |
| if not self.exclude_sa_key: |
| # primary_key_from_instance actually changes obj.__dict__ if |
| # primary key properties do not already exist in obj.__dict__ |
| attrs[self.KEY_ATTR] = self.mapper.primary_key_from_instance(obj) |
| |
| if not self.exclude_sa_lazy: |
| lazy_attrs = [] |
| |
| for attr in self.properties: |
| if attr not in obj.__dict__: |
| lazy_attrs.append(attr) |
| |
| attrs[self.LAZY_ATTR] = lazy_attrs |
| |
| return attrs |
| |
| def getDecodableAttributes(self, obj, attrs, **kwargs): |
| """ |
| """ |
| attrs = pyamf.ClassAlias.getDecodableAttributes(self, obj, attrs, **kwargs) |
| |
| # Delete lazy-loaded attrs. |
| # |
| # Doing it this way ensures that lazy-loaded attributes are not |
| # attached to the object, even if there is a default value specified |
| # in the __init__ method. |
| # |
| # This is the correct behavior, because SQLAlchemy ignores __init__. |
| # So, an object retreived from a DB with SQLAlchemy will not have a |
| # lazy-loaded value, even if __init__ specifies a default value. |
| if self.LAZY_ATTR in attrs: |
| obj_state = None |
| |
| if hasattr(orm.attributes, 'instance_state'): |
| obj_state = orm.attributes.instance_state(obj) |
| |
| for lazy_attr in attrs[self.LAZY_ATTR]: |
| if lazy_attr in obj.__dict__: |
| # Delete directly from the dict, so |
| # SA callbacks are not triggered. |
| del obj.__dict__[lazy_attr] |
| |
| # Delete from committed_state so SA thinks this attribute was |
| # never modified. |
| # |
| # If the attribute was set in the __init__ method, |
| # SA will think it is modified and will try to update |
| # it in the database. |
| if obj_state is not None: |
| if lazy_attr in obj_state.committed_state: |
| del obj_state.committed_state[lazy_attr] |
| if lazy_attr in obj_state.dict: |
| del obj_state.dict[lazy_attr] |
| |
| if lazy_attr in attrs: |
| del attrs[lazy_attr] |
| |
| del attrs[self.LAZY_ATTR] |
| |
| if self.KEY_ATTR in attrs: |
| del attrs[self.KEY_ATTR] |
| |
| return attrs |
| |
| def createInstance(self, *args, **kwargs): |
| self.compile() |
| |
| return self.mapper.class_manager.new_instance() |
| |
| |
| def is_class_sa_mapped(klass): |
| """ |
| @rtype: C{bool} |
| """ |
| if not isinstance(klass, type): |
| klass = type(klass) |
| |
| for c in class_checkers: |
| if c(klass): |
| return False |
| |
| try: |
| class_mapper(klass) |
| except UnmappedInstanceError: |
| return False |
| |
| return True |
| |
| pyamf.register_alias_type(SaMappedClassAlias, is_class_sa_mapped) |