Package pyamf :: Package adapters :: Module _sqlalchemy

[hide private]
[frames] | no frames]

# Source Code for Module pyamf.adapters._sqlalchemy

      1  # Copyright (c) 2007-2009 The PyAMF Project.
      2  # See LICENSE for details.
      3
      4  """
      5  SQLAlchemy adapter module.
      6
      7  @see: U{SQLAlchemy homepage (external)<http://www.sqlalchemy.org>}
      8
      9  @since: 0.4
     10  """
     11
     12  import sqlalchemy
     13  from sqlalchemy.orm import collections
     14
     15  try:
     16      from sqlalchemy.orm import class_mapper, object_mapper
     17  except ImportError:
     18      from sqlalchemy.orm.util import class_mapper, object_mapper
     19
     20  import pyamf
     21  from pyamf.adapters import util
     22
     23  UnmappedInstanceError = None
     24
     25  try:
     26      class_mapper(dict)
     27  except Exception, e:
     28      UnmappedInstanceError = e.__class__
     29
     30 -class SaMappedClassAlias(pyamf.ClassAlias):
     31      KEY_ATTR = 'sa_key'
     32      LAZY_ATTR = 'sa_lazy'
     33      EXCLUDED_ATTRS = [
     34          '_sa_instance_state', '_sa_session_id', '_state',
     35          '_entity_name', '_instance_key', '_sa_class_manager',
     36          '_sa_adapter', '_sa_appender', '_sa_instrumented',
     37          '_sa_iterator', '_sa_remover', '_sa_initiator',
     38      ]
     39
     40 -    def _getMapper(self, obj):
     41          """
     42          Returns C{sqlalchemy.orm.mapper.Mapper} object.
     43          """
     44          if hasattr(self, 'primary_mapper'):
     45              return self.primary_mapper
     46
     47          try:
     48              self.primary_mapper = object_mapper(obj)
     49          except UnmappedInstanceError:
     50              self.primary_mapper = None
     51
     52          return self.primary_mapper
     53
     54 -    def getAttrs(self, obj, *args, **kwargs):
     55          """
     56          Returns a C{tuple} containing 2 lists. The 1st is a list of allowed
     57          static attribute names, and the 2nd is a list of allowed dynamic
     58          attribute names.
     59          """
     60          mapper = self._getMapper(obj)
     61
     62          if mapper is None:
     63              return pyamf.ClassAlias.getAttrs(self, obj, *args, **kwargs)
     64
     65          if not hasattr(self, 'static_attrs'):
     66              self.static_attrs = [self.KEY_ATTR, self.LAZY_ATTR]
     67              self.properties = []
     68
     69              for prop in mapper.iterate_properties:
     70                  self.static_attrs.append(prop.key)
     71
     72              for key, prop in self.klass.__dict__.iteritems():
     73                  if isinstance(prop, property):
     74                      self.properties.append(key)
     75                      self.static_attrs.append(key)
     76
     77          dynamic_attrs = []
     78
     79          for key in obj.__dict__.keys():
     80              if key in self.EXCLUDED_ATTRS:
     81                  continue
     82
     83              if key not in self.static_attrs:
     84                  dynamic_attrs.append(key)
     85
     86          return self.static_attrs, dynamic_attrs
     87
     88 -    def getAttributes(self, obj, *args, **kwargs):
     89          """
     90          Returns a C{tuple} containing a dict of static and dynamic attributes
     91          for C{obj}.
     92          """
     93          mapper = self._getMapper(obj)
     94
     95          if mapper is None:
     96              return pyamf.ClassAlias.getAttributes(self, obj, *args, **kwargs)
     97
     98          static_attrs = {}
     99          dynamic_attrs = {}
    100          lazy_attrs = []
    101
    102          static_attr_names, dynamic_attr_names = self.getAttrs(obj)
    103
    104          # primary_key_from_instance actually changes obj.__dict__ if
    105          # primary key properties do not already exist in obj.__dict__
    106          static_attrs[self.KEY_ATTR] = mapper.primary_key_from_instance(obj)
    107
    108          for attr in static_attr_names:
    109              if attr in obj.__dict__ or attr in self.properties:
    110                  static_attrs[attr] = getattr(obj, attr)
    111
    112                  continue
    113
    114              if attr in [self.KEY_ATTR, self.LAZY_ATTR]:
    115                  continue
    116
    117              # attrs here are lazy but have not been loaded from the db yet ..
    118              lazy_attrs.append(attr)
    119              static_attrs[attr] = pyamf.Undefined
    120
    121          for attr in dynamic_attr_names:
    122              if attr in obj.__dict__:
    123                   dynamic_attrs[attr] = getattr(obj, attr)
    124
    125          static_attrs[self.LAZY_ATTR] = lazy_attrs
    126
    127          return static_attrs, dynamic_attrs
    128
    129 -    def applyAttributes(self, obj, attrs, *args, **kwargs):
    130          """
    131          Add decoded attributes to instance.
    132          """
    133          mapper = self._getMapper(obj)
    134
    135          if mapper is None:
    136              pyamf.ClassAlias.applyAttributes(self, obj, attrs, *args, **kwargs)
    137
    138              return
    139
    140          # Delete lazy-loaded attrs.
    141          #
    142          # Doing it this way ensures that lazy-loaded attributes are not
    143          # attached to the object, even if there is a default value specified
    144          # in the __init__ method.
    145          #
    146          # This is the correct behavior, because SQLAlchemy ignores __init__.
    147          # So, an object retreived from a DB with SQLAlchemy will not have a
    148          # lazy-loaded value, even if __init__ specifies a default value.
    149          if self.LAZY_ATTR in attrs:
    150              obj_state = None
    151
    152              if hasattr(sqlalchemy.orm.attributes, 'instance_state'):
    153                  obj_state = sqlalchemy.orm.attributes.instance_state(obj)
    154
    155              for lazy_attr in attrs[self.LAZY_ATTR]:
    156                  if lazy_attr in obj.__dict__:
    157                      # Delete directly from the dict, so
    158                      # SA callbacks are not triggered.
    159                      del obj.__dict__[lazy_attr]
    160
    161                  # Delete from committed_state so
    162                  # SA thinks this attribute was never modified.
    163                  #
    164                  # If the attribute was set in the __init__ method,
    165                  # SA will think it is modified and will try to update
    166                  # it in the database.
    167                  if obj_state is not None:
    168                      if lazy_attr in obj_state.committed_state:
    169                          del obj_state.committed_state[lazy_attr]
    170                      if lazy_attr in obj_state.dict:
    171                          del obj_state.dict[lazy_attr]
    172
    173                  if lazy_attr in attrs:
    174                      del attrs[lazy_attr]
    175
    176              del attrs[self.LAZY_ATTR]
    177
    178          if self.KEY_ATTR in attrs:
    179              del attrs[self.KEY_ATTR]
    180
    181          for key, prop in self.klass.__dict__.iteritems():
    182              if isinstance(prop, property) and key in attrs.keys():
    183                  if prop.fset is None:
    184                      del attrs[key]
    185
    186          pyamf.util.set_attrs(obj, attrs)
    187
    188 -def is_class_sa_mapped(klass):
    189      """
    190      @rtype: C{bool}
    191      """
    192      if not isinstance(klass, type):
    193          klass = type(klass)
    194
    195      try:
    196          class_mapper(klass)
    197      except UnmappedInstanceError:
    198          return False
    199
    200      return True
    201
    202  pyamf.register_alias_type(SaMappedClassAlias, is_class_sa_mapped)
    203
    204  pyamf.add_type(collections.InstrumentedList, util.to_list)
    205  pyamf.add_type(collections.InstrumentedDict, util.to_dict)
    206  pyamf.add_type(collections.InstrumentedSet, util.to_set)
    207