Ticket #277: pyamf-new-sqlalchemy-adapter.patch

File pyamf-new-sqlalchemy-adapter.patch, 4.4 KB (added by papagr, 2 months ago)

A patch to the adapter by Dave Thompson and some improvements by Nikos Papagrigoriou

  • _sqlalchemy.py

    old new  
    1 # Copyright (c) 2007-2008 The PyAMF Project. 
    2 # See LICENSE for details. 
    3  
    41""" 
    52SQLAlchemy adapter (experimental!) 
    63 
     4By Dave Thompson and some improvements by Nikos Papagrigoriou 
     5 
    76@since: 0.4.0 
    87""" 
    9 import copy 
    10  
    118import sqlalchemy 
    12 import sqlalchemy.orm.session 
    139 
    1410import pyamf 
    1511 
    16 def writeSAObject(obj): 
    17     """ 
    18     Return a copy of the given sqlalchemy orm object. 
    19     """ 
    20     base_obj = obj 
    21      
    22     # Create a copy of the object 
    23     obj = copy.copy(obj) 
    24      
    25     # Collect properties which are not 'known' 
    26     properties = [property for property in obj.__dict__ 
    27                     if property not in getattr(obj, 'c', []) and 
    28                        property != "_sa_session_id"] 
    29      
    30     # Iterate over the properties which are not columns of the orm mapped table 
    31     for property in properties: 
    32         # Delete 'private' properties 
    33         if property.startswith('_'): 
    34             del obj.__dict__[property] 
     12sa_properties = ('_state', '_sa_instance_state', '_sa_class_manager') 
     13 
     14def get_SA_attrs(obj): 
     15    """Get attributes to be encoded for a SA object.""" 
     16    if not is_SA_object(obj): 
     17        return pyamf.util.native_get_attrs(obj) 
     18 
     19    attrs = {} 
     20    mapper = obj._sa_class_manager.mapper 
     21    for property in mapper.iterate_properties: 
     22        if property.key in sa_properties: 
    3523            continue 
    36          
    37         # one-to-many references are stored in the InstrumentedList type 
    38         # convert it to a list and search properties of each item to update the 
    39         # backrefence of the object to the copied object 
    40         value = obj.__dict__[property] 
    41         if isinstance(value, sqlalchemy.orm.collections.InstrumentedList): 
    42             value = list(value) 
    43  
    44             # Update backreferences to the copied object 
    45             for item in value: 
    46                 for item_property in item.__dict__: 
    47                     if item.__dict__[item_property] == base_obj: 
    48                         item.__dict__[item_property] = obj 
    49              
    50             obj.__dict__[property] = value 
    51     return obj 
    52  
    53 def isSqlAlchemyObject(data): 
    54     """ Detect if this object is registered in a sqlalchemy session. 
    55         sqlalchemy 0.4 created a private variable '_state' in the object to 
    56         store state information. in sqlalchemy 0.5 this is '_sa_instance_state' 
    57     """ 
    58     if not isinstance(data, object): 
     24 
     25        if property.key.startswith('__'): 
     26            continue 
     27 
     28        attrs[property.key] = getattr(obj, property.key) 
     29    return attrs 
     30 
     31def write_SA_collection(obj): 
     32    """Convert a SQLAlchemy collection to a native type.""" 
     33    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedList): 
     34        new_obj = [] 
     35        new_obj.extend(obj) 
     36        return new_obj 
     37    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedDict): 
     38        new_obj = {} 
     39        new_obj.update(obj) 
     40        return new_obj 
     41    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedSet): 
     42        new_obj = Set() 
     43        new_obj.union(obj) 
     44        return new_obj 
     45 
     46    raise "Not a known SQLAlchemy collection type." 
     47 
     48def is_SA_object(obj): 
     49    """Detect if this object is a SQLAlchemy instance.""" 
     50    if is_SA_collection(obj): 
    5951        return False 
    60      
    61     # sqlalchemy 0.4 detection     
    62     state = getattr(data, '_state', None) 
    63      
    64     # sqlalchemy 0.5 ? 
    65     if not state: 
    66         state = getattr(data, '_sa_instance_state', None) 
    67          
    68     return isinstance(state, sqlalchemy.orm.attributes.InstanceState) 
    6952 
    70 pyamf.add_type(isSqlAlchemyObject, writeSAObject) 
     53    for property in sa_properties: 
     54        if hasattr(obj, property): 
     55            return True 
     56 
     57    return False 
     58 
     59def is_SA_collection(obj): 
     60    """Detect if this object is a SQLAlchemy collection.""" 
     61    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedList): 
     62        return True 
     63    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedSet): 
     64        return True 
     65    if isinstance(obj, sqlalchemy.orm.collections.InstrumentedDict): 
     66        return True 
     67    return False 
     68 
     69# Replace this method with the adapter's 
     70pyamf.util.native_get_attrs = pyamf.util.get_attrs 
     71pyamf.util.get_attrs = get_SA_attrs 
     72# turn collections into native objects 
     73pyamf.add_type(is_SA_collection, write_SA_collection)