summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2013-11-29 18:53:53 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2013-11-29 18:53:53 -0500
commit58fab1be984a42400e67bdf7f15acd5a6d7c4b3c (patch)
treea31000a83412c349db1ac31bc9cd122c57a961a7
parentc368034ca058fad9454cd79f9912cda284cb50e6 (diff)
downloadsqlalchemy-58fab1be984a42400e67bdf7f15acd5a6d7c4b3c.tar.gz
- start reworking examples to include more code from the wiki.
- add the other versioning examples from the wiki - modernize the dictlike examples
-rw-r--r--doc/build/orm/examples.rst115
-rw-r--r--examples/versioned_history/__init__.py (renamed from examples/versioning/__init__.py)4
-rw-r--r--examples/versioned_history/history_meta.py (renamed from examples/versioning/history_meta.py)0
-rw-r--r--examples/versioned_history/test_versioning.py (renamed from examples/versioning/test_versioning.py)0
-rw-r--r--examples/vertical/dictlike-polymorphic.py214
-rw-r--r--examples/vertical/dictlike.py191
6 files changed, 224 insertions, 300 deletions
diff --git a/doc/build/orm/examples.rst b/doc/build/orm/examples.rst
index 60d075a36..484865e7e 100644
--- a/doc/build/orm/examples.rst
+++ b/doc/build/orm/examples.rst
@@ -1,18 +1,21 @@
.. _examples_toplevel:
-Examples
-========
+============
+ORM Examples
+============
The SQLAlchemy distribution includes a variety of code examples illustrating
a select set of patterns, some typical and some not so typical. All are
runnable and can be found in the ``/examples`` directory of the
-distribution. Each example contains a README in its ``__init__.py`` file,
-each of which are listed below.
+distribution. Descriptions and source code for all can be found here.
Additional SQLAlchemy examples, some user contributed, are available on the
wiki at `<http://www.sqlalchemy.org/trac/wiki/UsageRecipes>`_.
+Mapping Recipes
+===============
+
.. _examples_adjacencylist:
Adjacency List
@@ -27,80 +30,98 @@ Associations
.. automodule:: examples.association
-
-.. _examples_instrumentation:
-
-Attribute Instrumentation
--------------------------
-
-.. automodule:: examples.custom_attributes
-
-.. _examples_caching:
-
-Dogpile Caching
----------------
-
-.. automodule:: examples.dogpile_caching
-
Directed Graphs
---------------
.. automodule:: examples.graphs
Dynamic Relations as Dictionaries
-----------------------------------
+------------------------------------
.. automodule:: examples.dynamic_dict
.. _examples_generic_associations:
Generic Associations
---------------------
+------------------------
.. automodule:: examples.generic_associations
-.. _examples_sharding:
-
-Horizontal Sharding
--------------------
-
-.. automodule:: examples.sharding
-
-Inheritance Mappings
---------------------
-
-.. automodule:: examples.inheritance
-
Large Collections
------------------
+------------------------
.. automodule:: examples.large_collection
Nested Sets
------------
+------------
.. automodule:: examples.nested_sets
-.. _examples_postgis:
+.. _examples_xmlpersistence:
-PostGIS Integration
--------------------
+XML Persistence
+------------------------
-.. automodule:: examples.postgis
+.. automodule:: examples.elementtree
+
+Versioning Objects
+------------------------
+
+Versioning with a History Table
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. automodule:: examples.versioned_history
-Versioned Objects
------------------
+Versioning using Temporal Rows
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-.. automodule:: examples.versioning
+.. automodule:: examples.versioned_rows
Vertical Attribute Mapping
---------------------------
+------------------------------------
.. automodule:: examples.vertical
-.. _examples_xmlpersistence:
-XML Persistence
----------------
+Inheritance Mapping Recipes
+============================
+
+Basic Inheritance Mappings
+----------------------------------
+
+.. automodule:: examples.inheritance
+
+Special APIs
+============
+
+.. _examples_instrumentation:
+
+Attribute Instrumentation
+------------------------------------
+
+.. automodule:: examples.custom_attributes
+
+.. _examples_sharding:
+
+Horizontal Sharding
+------------------------
+
+.. automodule:: examples.sharding
+
+Extending the ORM
+=================
+
+.. _examples_caching:
+
+Dogpile Caching
+------------------------
+
+.. automodule:: examples.dogpile_caching
+
+.. _examples_postgis:
+
+PostGIS Integration
+------------------------
+
+.. automodule:: examples.postgis
-.. automodule:: examples.elementtree
diff --git a/examples/versioning/__init__.py b/examples/versioned_history/__init__.py
index a2a6a1813..d630b994b 100644
--- a/examples/versioning/__init__.py
+++ b/examples/versioned_history/__init__.py
@@ -1,8 +1,6 @@
"""
Illustrates an extension which creates version tables for entities and stores
-records for each change. The same idea as Elixir's versioned extension, but
-more efficient (uses attribute API to get history) and handles class
-inheritance. The given extensions generate an anonymous "history" class which
+records for each change. The given extensions generate an anonymous "history" class which
represents historical versions of the target object.
Usage is illustrated via a unit test module ``test_versioning.py``, which can
diff --git a/examples/versioning/history_meta.py b/examples/versioned_history/history_meta.py
index 8cb523434..8cb523434 100644
--- a/examples/versioning/history_meta.py
+++ b/examples/versioned_history/history_meta.py
diff --git a/examples/versioning/test_versioning.py b/examples/versioned_history/test_versioning.py
index 906280555..906280555 100644
--- a/examples/versioning/test_versioning.py
+++ b/examples/versioned_history/test_versioning.py
diff --git a/examples/vertical/dictlike-polymorphic.py b/examples/vertical/dictlike-polymorphic.py
index 872a7c52e..e3d5ba578 100644
--- a/examples/vertical/dictlike-polymorphic.py
+++ b/examples/vertical/dictlike-polymorphic.py
@@ -1,10 +1,7 @@
"""Mapping a polymorphic-valued vertical table as a dictionary.
-This example illustrates accessing and modifying a "vertical" (or
-"properties", or pivoted) table via a dict-like interface. The 'dictlike.py'
-example explains the basics of vertical tables and the general approach. This
-example adds a twist- the vertical table holds several "value" columns, one
-for each type of data that can be stored. For example::
+Builds upon the dictlike.py example to also add differently typed
+columns to the "fact" table, e.g.::
Table('properties', metadata
Column('owner_id', Integer, ForeignKey('owner.id'),
@@ -25,80 +22,43 @@ we'll use a @hybrid_property to build a smart '.value' attribute that wraps up
reading and writing those various '_value' columns and keeps the '.type' up to
date.
-Class decorators are used, so Python 2.6 or greater is required.
"""
from sqlalchemy.orm.interfaces import PropComparator
-from sqlalchemy.orm import comparable_property
from sqlalchemy.ext.hybrid import hybrid_property
-
-# Using the VerticalPropertyDictMixin from the base example
-from .dictlike import VerticalPropertyDictMixin
+from sqlalchemy import event
+from sqlalchemy import literal_column
+from .dictlike import ProxiedDictMixin
class PolymorphicVerticalProperty(object):
"""A key/value pair with polymorphic value storage.
- Supplies a smart 'value' attribute that provides convenient read/write
- access to the row's current value without the caller needing to worry
- about the 'type' attribute or multiple columns.
-
- The 'value' attribute can also be used for basic comparisons in queries,
- allowing the row's logical value to be compared without foreknowledge of
- which column it might be in. This is not going to be a very efficient
- operation on the database side, but it is possible. If you're mapping to
- an existing database and you have some rows with a value of str('1') and
- others of int(1), then this could be useful.
-
- Subclasses must provide a 'type_map' class attribute with the following
- form::
-
- type_map = {
- <python type> : ('type column value', 'column name'),
- # ...
- }
-
- For example,::
-
- type_map = {
- int: ('integer', 'integer_value'),
- str: ('varchar', 'varchar_value'),
- }
+ The class which is mapped should indicate typing information
+ within the "info" dictionary of mapped Column objects; see
+ the AnimalFact mapping below for an example.
- Would indicate that a Python int value should be stored in the
- 'integer_value' column and the .type set to 'integer'. Conversely, if the
- value of '.type' is 'integer, then the 'integer_value' column is consulted
- for the current value.
"""
- type_map = {
- type(None): (None, None),
- }
-
def __init__(self, key, value=None):
self.key = key
self.value = value
@hybrid_property
def value(self):
- for discriminator, field in self.type_map.values():
- if self.type == discriminator:
- return getattr(self, field)
- return None
+ fieldname, discriminator = self.type_map[self.type]
+ if fieldname is None:
+ return None
+ else:
+ return getattr(self, fieldname)
@value.setter
def value(self, value):
py_type = type(value)
- if py_type not in self.type_map:
- raise TypeError(py_type)
-
- for field_type in self.type_map:
- discriminator, field = self.type_map[field_type]
- field_value = None
- if py_type == field_type:
- self.type = discriminator
- field_value = value
- if field is not None:
- setattr(self, field, field_value)
+ fieldname, discriminator = self.type_map[py_type]
+
+ self.type = discriminator
+ if fieldname is not None:
+ setattr(self, fieldname, value)
@value.deleter
def value(self):
@@ -113,9 +73,14 @@ class PolymorphicVerticalProperty(object):
self.cls = cls
def _case(self):
- whens = [(text("'%s'" % p[0]), cast(getattr(self.cls, p[1]), String))
- for p in self.cls.type_map.values()
- if p[1] is not None]
+ pairs = set(self.cls.type_map.values())
+ whens = [
+ (
+ literal_column("'%s'" % discriminator),
+ cast(getattr(self.cls, attribute), String)
+ ) for attribute, discriminator in pairs
+ if attribute is not None
+ ]
return case(whens, self.cls.type, null())
def __eq__(self, other):
return self._case() == cast(other, String)
@@ -125,69 +90,78 @@ class PolymorphicVerticalProperty(object):
def __repr__(self):
return '<%s %r=%r>' % (self.__class__.__name__, self.key, self.value)
+@event.listens_for(PolymorphicVerticalProperty, "mapper_configured", propagate=True)
+def on_new_class(mapper, cls_):
+ """Look for Column objects with type info in them, and work up
+ a lookup table."""
+
+ info_dict = {}
+ info_dict[type(None)] = (None, 'none')
+ info_dict['none'] = (None, 'none')
+
+ for k in mapper.c.keys():
+ col = mapper.c[k]
+ if 'type' in col.info:
+ python_type, discriminator = col.info['type']
+ info_dict[python_type] = (k, discriminator)
+ info_dict[discriminator] = (k, discriminator)
+ cls_.type_map = info_dict
if __name__ == '__main__':
- from sqlalchemy import (MetaData, Table, Column, Integer, Unicode,
- ForeignKey, UnicodeText, and_, not_, or_, String, Boolean, cast, text,
+ from sqlalchemy import (Column, Integer, Unicode,
+ ForeignKey, UnicodeText, and_, or_, String, Boolean, cast,
null, case, create_engine)
- from sqlalchemy.orm import mapper, relationship, Session
+ 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
+
+ Base = declarative_base()
- metadata = MetaData()
-
- animals = Table('animal', metadata,
- Column('id', Integer, primary_key=True),
- Column('name', Unicode(100)))
-
- chars = Table('facts', metadata,
- Column('animal_id', Integer, ForeignKey('animal.id'),
- primary_key=True),
- Column('key', Unicode(64), primary_key=True),
- Column('type', Unicode(16), default=None),
- Column('int_value', Integer, default=None),
- Column('char_value', UnicodeText, default=None),
- Column('boolean_value', Boolean, default=None))
-
- class AnimalFact(PolymorphicVerticalProperty):
- type_map = {
- int: ('integer', 'int_value'),
- str: ('char', 'char_value'),
- bool: ('boolean', 'boolean_value'),
- type(None): (None, None),
- }
-
- class Animal(VerticalPropertyDictMixin):
- """An animal.
-
- Animal facts are available via the 'facts' property or by using
- dict-like accessors on an Animal instance::
-
- cat['color'] = 'calico'
- # or, equivalently:
- cat.facts['color'] = AnimalFact('color', 'calico')
- """
- _property_type = AnimalFact
- _property_mapping = 'facts'
+ class AnimalFact(PolymorphicVerticalProperty, Base):
+ """A fact about an animal."""
+
+ __tablename__ = 'animal_fact'
+
+ animal_id = Column(ForeignKey('animal.id'), primary_key=True)
+ key = Column(Unicode(64), primary_key=True)
+ type = Column(Unicode(16))
+
+ # add information about storage for different types
+ # in the info dictionary of Columns
+ int_value = Column(Integer, info={'type': (int, 'integer')})
+ char_value = Column(UnicodeText, info={'type': (str, 'string')})
+ boolean_value = Column(Boolean, info={'type': (bool, 'boolean')})
+
+ class Animal(ProxiedDictMixin._base_class(Base)):
+ """an Animal"""
+
+ __tablename__ = 'animal'
+
+ id = Column(Integer, primary_key=True)
+ name = Column(Unicode(100))
+
+ 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, chars)
+ @classmethod
+ def with_characteristic(self, key, value):
+ return self.facts.any(key=key, value=value)
engine = create_engine('sqlite://', echo=True)
- metadata.create_all(engine)
+ Base.metadata.create_all(engine)
session = Session(engine)
stoat = Animal('stoat')
@@ -227,30 +201,24 @@ if __name__ == '__main__':
AnimalFact.value == True))))
print('weasel-like 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('weasel-like', True))))
+ filter(Animal.with_characteristic('weasel-like', True)))
print('weasel-like animals again', q.all())
q = (session.query(Animal).
- filter(Animal.facts.any(with_characteristic('poisonous', False))))
+ filter(Animal.with_characteristic('poisonous', False)))
print('animals with poisonous=False', q.all())
q = (session.query(Animal).
- filter(or_(Animal.facts.any(
- with_characteristic('poisonous', False)),
- not_(Animal.facts.any(AnimalFact.key == 'poisonous')))))
+ filter(or_(
+ Animal.with_characteristic('poisonous', False),
+ ~Animal.facts.any(AnimalFact.key == 'poisonous')
+ )
+ )
+ )
print('non-poisonous animals', q.all())
q = (session.query(Animal).
filter(Animal.facts.any(AnimalFact.value == 5)))
print('any animal with a .value of 5', q.all())
- # Facts can be queried as well.
- q = (session.query(AnimalFact).
- filter(with_characteristic('cuteness', 'very cute')))
- print(q.all())
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())