summaryrefslogtreecommitdiff
path: root/examples/vertical/dictlike.py
diff options
context:
space:
mode:
Diffstat (limited to 'examples/vertical/dictlike.py')
-rw-r--r--examples/vertical/dictlike.py191
1 files changed, 64 insertions, 127 deletions
diff --git a/examples/vertical/dictlike.py b/examples/vertical/dictlike.py
index f17d1acc8..1a3631354 100644
--- a/examples/vertical/dictlike.py
+++ b/examples/vertical/dictlike.py
@@ -30,150 +30,101 @@ accessing them like a Python dict can be very convenient. The example below
can be used with many common vertical schemas as-is or with minor adaptations.
"""
+from __future__ import unicode_literals
+from sqlalchemy.util.compat import with_metaclass
+from collections import MutableMapping
+from abc import ABCMeta
+from sqlalchemy.ext.declarative import DeclarativeMeta
-class VerticalProperty(object):
- """A key/value pair.
- This class models rows in the vertical table.
- """
-
- def __init__(self, key, value):
- self.key = key
- self.value = value
-
- def __repr__(self):
- return '<%s %r=%r>' % (self.__class__.__name__, self.key, self.value)
-
-
-class VerticalPropertyDictMixin(object):
+class ProxiedDictMixin(MutableMapping):
"""Adds obj[key] access to a mapped class.
- This is a mixin class. It can be inherited from directly, or included
- with multiple inheritence.
-
- Classes using this mixin must define two class properties::
-
- _property_type:
- The mapped type of the vertical key/value pair instances. Will be
- invoked with two positional arugments: key, value
-
- _property_mapping:
- A string, the name of the Python attribute holding a dict-based
- relationship of _property_type instances.
-
- Using the VerticalProperty class above as an example,::
+ This class basically proxies dictionary access to an attribute
+ called ``_proxied``. The class which inherits this class
+ should have an attribute called ``_proxied`` which points to a dictionary.
- class MyObj(VerticalPropertyDictMixin):
- _property_type = VerticalProperty
- _property_mapping = 'props'
-
- mapper(MyObj, sometable, properties={
- 'props': relationship(VerticalProperty,
- collection_class=attribute_mapped_collection('key'))})
-
- Dict-like access to MyObj is proxied through to the 'props' relationship::
-
- myobj['key'] = 'value'
- # ...is shorthand for:
- myobj.props['key'] = VerticalProperty('key', 'value')
-
- myobj['key'] = 'updated value']
- # ...is shorthand for:
- myobj.props['key'].value = 'updated value'
-
- print myobj['key']
- # ...is shorthand for:
- print myobj.props['key'].value
+ The use of this class is optional, as it requires some
+ elaborate metaclass arithmetic in order to use it with declarative.
"""
- _property_type = VerticalProperty
- _property_mapping = None
+ def __len__(self):
+ return len(self._proxied)
- __map = property(lambda self: getattr(self, self._property_mapping))
+ def __iter__(self):
+ return iter(self._proxied)
def __getitem__(self, key):
- return self.__map[key].value
-
- def __setitem__(self, key, value):
- property = self.__map.get(key, None)
- if property is None:
- self.__map[key] = self._property_type(key, value)
- else:
- property.value = value
-
- def __delitem__(self, key):
- del self.__map[key]
+ return self._proxied[key]
def __contains__(self, key):
- return key in self.__map
-
- # Implement other dict methods to taste. Here are some examples:
- def keys(self):
- return self.__map.keys()
+ return key in self._proxied
- def values(self):
- return [prop.value for prop in self.__map.values()]
+ def __setitem__(self, key, value):
+ self._proxied[key] = value
- def items(self):
- return [(key, prop.value) for key, prop in self.__map.items()]
+ def __delitem__(self, key):
+ del self._proxied[key]
- def __iter__(self):
- return iter(self.keys())
+ @classmethod
+ def _base_class(cls, base):
+ """Perform the requisite metaclass trickery in order
+ to get DeclarativeMeta and ABCMeta to play nicely together,
+ while also remaining Python 2/3 agnostic.
+ """
+ return with_metaclass(
+ type(str("MutableBase"), (ABCMeta, DeclarativeMeta), {}),
+ base, cls)
if __name__ == '__main__':
- from sqlalchemy import (MetaData, Table, Column, Integer, Unicode,
- ForeignKey, UnicodeText, and_, not_, create_engine)
- from sqlalchemy.orm import mapper, relationship, Session
+ from sqlalchemy import (Column, Integer, Unicode,
+ ForeignKey, UnicodeText, and_, create_engine)
+ from sqlalchemy.orm import relationship, Session
from sqlalchemy.orm.collections import attribute_mapped_collection
+ from sqlalchemy.ext.declarative import declarative_base
+ from sqlalchemy.ext.associationproxy import association_proxy
- metadata = MetaData()
+ Base = declarative_base()
- # Here we have named animals, and a collection of facts about them.
- animals = Table('animal', metadata,
- Column('id', Integer, primary_key=True),
- Column('name', Unicode(100)))
+ class AnimalFact(Base):
+ """A fact about an animal."""
- facts = Table('facts', metadata,
- Column('animal_id', Integer, ForeignKey('animal.id'),
- primary_key=True),
- Column('key', Unicode(64), primary_key=True),
- Column('value', UnicodeText, default=None),)
+ __tablename__ = 'animal_fact'
- class AnimalFact(VerticalProperty):
- """A fact about an animal."""
+ animal_id = Column(ForeignKey('animal.id'), primary_key=True)
+ key = Column(Unicode(64), primary_key=True)
+ value = Column(UnicodeText)
- class Animal(VerticalPropertyDictMixin):
- """An animal.
+ class Animal(ProxiedDictMixin._base_class(Base)):
+ """an Animal"""
- Animal facts are available via the 'facts' property or by using
- dict-like accessors on an Animal instance::
+ __tablename__ = 'animal'
- cat['color'] = 'calico'
- # or, equivalently:
- cat.facts['color'] = AnimalFact('color', 'calico')
- """
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode(100))
- _property_type = AnimalFact
- _property_mapping = 'facts'
+ facts = relationship("AnimalFact",
+ collection_class=attribute_mapped_collection('key'))
+
+ _proxied = association_proxy("facts", "value",
+ creator=
+ lambda key, value: AnimalFact(key=key, value=value))
def __init__(self, name):
self.name = name
def __repr__(self):
- return '<%s %r>' % (self.__class__.__name__, self.name)
-
+ return "Animal(%r)" % self.name
- mapper(Animal, animals, properties={
- 'facts': relationship(
- AnimalFact, backref='animal',
- collection_class=attribute_mapped_collection('key')),
- })
- mapper(AnimalFact, facts)
+ @classmethod
+ def with_characteristic(self, key, value):
+ return self.facts.any(key=key, value=value)
engine = create_engine("sqlite://")
- metadata.create_all(engine)
+ Base.metadata.create_all(engine)
+
session = Session(bind=engine)
stoat = Animal('stoat')
@@ -194,9 +145,6 @@ if __name__ == '__main__':
critter['cuteness'] = 'very'
print('changing cuteness:')
- engine.echo = True
- session.commit()
- engine.echo = False
marten = Animal('marten')
marten['color'] = 'brown'
@@ -212,7 +160,6 @@ if __name__ == '__main__':
loris['cuteness'] = 'fairly'
loris['poisonous-part'] = 'elbows'
session.add(loris)
- session.commit()
q = (session.query(Animal).
filter(Animal.facts.any(
@@ -220,27 +167,17 @@ if __name__ == '__main__':
AnimalFact.value == 'reddish'))))
print('reddish animals', q.all())
- # Save some typing by wrapping that up in a function:
- with_characteristic = lambda key, value: and_(AnimalFact.key == key,
- AnimalFact.value == value)
-
- q = (session.query(Animal).
- filter(Animal.facts.any(
- with_characteristic('color', 'brown'))))
+ q = session.query(Animal).\
+ filter(Animal.with_characteristic("color", 'brown'))
print('brown animals', q.all())
- q = (session.query(Animal).
- filter(not_(Animal.facts.any(
- with_characteristic('poisonous-part', 'elbows')))))
+ q = session.query(Animal).\
+ filter(~Animal.with_characteristic("poisonous-part", 'elbows'))
print('animals without poisonous-part == elbows', q.all())
q = (session.query(Animal).
- filter(Animal.facts.any(AnimalFact.value == 'somewhat')))
+ filter(Animal.facts.any(value='somewhat')))
print('any animal with any .value of "somewhat"', q.all())
- # Facts can be queried as well.
- q = (session.query(AnimalFact).
- filter(with_characteristic('cuteness', 'very')))
- print('just the facts', q.all())