Skip to content

Instantly share code, notes, and snippets.

@shazow
Created November 29, 2010 06:10
Show Gist options
  • Save shazow/719648 to your computer and use it in GitHub Desktop.
Save shazow/719648 to your computer and use it in GitHub Desktop.
AppEngine Expando model implemented in SQLAlchemy
"""
Usage:
class MyTable(ExpandoModel):
__tablename__ = 'my_table'
id = Column(types.Integer, primary_key=True)
>>> t = MyTable(id=1)
>>> Session.add(t)
>>> Session.commit()
>>> t.random_property = 42
>>> t.foo = "bar"
>>> Session.commit()
>>> t = Session.query(MyTable).get(1)
>>> t.foo, t.random_property
('bar', 42)
"""
from sqlalchemy import MetaData, types, Column
from sqlalchemy.ext.declarative import declarative_base
metadata = MetaData()
class _ExpandoBase(object):
"""
Based on:
https://github.com/shazow/everything/blob/master/idea/arbitrarily-structured-data-in-rdbms.md
http://code.google.com/appengine/docs/python/datastore/expandoclass.html
"""
_dynamic_properties = Column(types.PickleType(mutable=False))
def __getattribute__(self, key):
# __getattribute__ is necessary to avoid infinite recursion on setattr.
if not key.startswith('_'):
dynamic_properties = self._dynamic_properties
if dynamic_properties is not None and key in dynamic_properties:
return self.__getattr__(key)
return super(_ExpandoBase, self).__getattribute__(key)
def __getattr__(self, key):
# _dynamic_properties dereferencing is necessary to avoid unnecessary __getattribute__ lookups.
_dynamic_properties = self._dynamic_properties
if _dynamic_properties is not None and key in _dynamic_properties:
return _dynamic_properties[key]
return getattr(super(_ExpandoBase, self), key)
def __setattr__(self, key, value):
if key.startswith('_') or key in self.__table__.c:
return super(_ExpandoBase, self).__setattr__(key, value)
_dynamic_properties = self._dynamic_properties
if _dynamic_properties is None:
_dynamic_properties = {}
else:
_dynamic_properties = _dynamic_properties.copy()
_dynamic_properties[key] = value
# Pickles aren't mutable, so we shove a fresh instance of the variable in
self._dynamic_properties = _dynamic_properties
def __delattr__(self, key):
_dynamic_properties = self._dynamic_properties
if _dynamic_properties and key in _dynamic_properties:
_dynamic_properties = _dynamic_properties.copy()
del _dynamic_properties[key]
# Pickles aren't mutable, so we shove a fresh instance of the variable in
self._dynamic_properties = _dynamic_properties
return delattr(super(_ExpandoBase, self), key)
ExpandoModel = declarative_base(metadata=metadata, cls=_ExpandoBase)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment