Created
November 29, 2010 06:10
-
-
Save shazow/719648 to your computer and use it in GitHub Desktop.
AppEngine Expando model implemented in SQLAlchemy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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