text: # 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 as 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 = []
              local_attrs = obj._sa_class_manager.local_attrs
  
              for attr in self.properties:
                  if attr in local_attrs:
                      lazy = getattr(local_attrs[attr].property, 'lazy', False)
                      if lazy not in (False, 'joined'):
                          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)
  