Remove submodule, just put Dependencies in ./libs
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# ext/__init__.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
@@ -0,0 +1,884 @@
|
||||
# ext/associationproxy.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Contain the ``AssociationProxy`` class.
|
||||
|
||||
The ``AssociationProxy`` is a Python property object which provides
|
||||
transparent proxied access to the endpoint of an association object.
|
||||
|
||||
See the example ``examples/association/proxied_association.py``.
|
||||
|
||||
"""
|
||||
import itertools
|
||||
import operator
|
||||
import weakref
|
||||
from sqlalchemy import exceptions
|
||||
from sqlalchemy import orm
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.orm import collections
|
||||
from sqlalchemy.sql import not_
|
||||
|
||||
|
||||
def association_proxy(target_collection, attr, **kw):
|
||||
"""Return a Python property implementing a view of *attr* over a collection.
|
||||
|
||||
Implements a read/write view over an instance's *target_collection*,
|
||||
extracting *attr* from each member of the collection. The property acts
|
||||
somewhat like this list comprehension::
|
||||
|
||||
[getattr(member, *attr*)
|
||||
for member in getattr(instance, *target_collection*)]
|
||||
|
||||
Unlike the list comprehension, the collection returned by the property is
|
||||
always in sync with *target_collection*, and mutations made to either
|
||||
collection will be reflected in both.
|
||||
|
||||
Implements a Python property representing a relationship as a collection of
|
||||
simpler values. The proxied property will mimic the collection type of
|
||||
the target (list, dict or set), or, in the case of a one to one relationship,
|
||||
a simple scalar value.
|
||||
|
||||
:param target_collection: Name of the relationship attribute we'll proxy to,
|
||||
usually created with :func:`~sqlalchemy.orm.relationship`.
|
||||
|
||||
:param attr: Attribute on the associated instances we'll proxy for.
|
||||
|
||||
For example, given a target collection of [obj1, obj2], a list created
|
||||
by this proxy property would look like [getattr(obj1, *attr*),
|
||||
getattr(obj2, *attr*)]
|
||||
|
||||
If the relationship is one-to-one or otherwise uselist=False, then simply:
|
||||
getattr(obj, *attr*)
|
||||
|
||||
:param creator: optional.
|
||||
|
||||
When new items are added to this proxied collection, new instances of
|
||||
the class collected by the target collection will be created. For list
|
||||
and set collections, the target class constructor will be called with
|
||||
the 'value' for the new instance. For dict types, two arguments are
|
||||
passed: key and value.
|
||||
|
||||
If you want to construct instances differently, supply a *creator*
|
||||
function that takes arguments as above and returns instances.
|
||||
|
||||
For scalar relationships, creator() will be called if the target is None.
|
||||
If the target is present, set operations are proxied to setattr() on the
|
||||
associated object.
|
||||
|
||||
If you have an associated object with multiple attributes, you may set
|
||||
up multiple association proxies mapping to different attributes. See
|
||||
the unit tests for examples, and for examples of how creator() functions
|
||||
can be used to construct the scalar relationship on-demand in this
|
||||
situation.
|
||||
|
||||
:param \*\*kw: Passes along any other keyword arguments to
|
||||
:class:`AssociationProxy`.
|
||||
|
||||
"""
|
||||
return AssociationProxy(target_collection, attr, **kw)
|
||||
|
||||
|
||||
class AssociationProxy(object):
|
||||
"""A descriptor that presents a read/write view of an object attribute."""
|
||||
|
||||
def __init__(self, target_collection, attr, creator=None,
|
||||
getset_factory=None, proxy_factory=None, proxy_bulk_set=None):
|
||||
"""Arguments are:
|
||||
|
||||
target_collection
|
||||
Name of the collection we'll proxy to, usually created with
|
||||
'relationship()' in a mapper setup.
|
||||
|
||||
attr
|
||||
Attribute on the collected instances we'll proxy for. For example,
|
||||
given a target collection of [obj1, obj2], a list created by this
|
||||
proxy property would look like [getattr(obj1, attr), getattr(obj2,
|
||||
attr)]
|
||||
|
||||
creator
|
||||
Optional. When new items are added to this proxied collection, new
|
||||
instances of the class collected by the target collection will be
|
||||
created. For list and set collections, the target class constructor
|
||||
will be called with the 'value' for the new instance. For dict
|
||||
types, two arguments are passed: key and value.
|
||||
|
||||
If you want to construct instances differently, supply a 'creator'
|
||||
function that takes arguments as above and returns instances.
|
||||
|
||||
getset_factory
|
||||
Optional. Proxied attribute access is automatically handled by
|
||||
routines that get and set values based on the `attr` argument for
|
||||
this proxy.
|
||||
|
||||
If you would like to customize this behavior, you may supply a
|
||||
`getset_factory` callable that produces a tuple of `getter` and
|
||||
`setter` functions. The factory is called with two arguments, the
|
||||
abstract type of the underlying collection and this proxy instance.
|
||||
|
||||
proxy_factory
|
||||
Optional. The type of collection to emulate is determined by
|
||||
sniffing the target collection. If your collection type can't be
|
||||
determined by duck typing or you'd like to use a different
|
||||
collection implementation, you may supply a factory function to
|
||||
produce those collections. Only applicable to non-scalar relationships.
|
||||
|
||||
proxy_bulk_set
|
||||
Optional, use with proxy_factory. See the _set() method for
|
||||
details.
|
||||
|
||||
"""
|
||||
self.target_collection = target_collection
|
||||
self.value_attr = attr
|
||||
self.creator = creator
|
||||
self.getset_factory = getset_factory
|
||||
self.proxy_factory = proxy_factory
|
||||
self.proxy_bulk_set = proxy_bulk_set
|
||||
|
||||
self.scalar = None
|
||||
self.owning_class = None
|
||||
self.key = '_%s_%s_%s' % (
|
||||
type(self).__name__, target_collection, id(self))
|
||||
self.collection_class = None
|
||||
|
||||
def _get_property(self):
|
||||
return (orm.class_mapper(self.owning_class).
|
||||
get_property(self.target_collection))
|
||||
|
||||
@property
|
||||
def target_class(self):
|
||||
"""The class the proxy is attached to."""
|
||||
return self._get_property().mapper.class_
|
||||
|
||||
def _target_is_scalar(self):
|
||||
return not self._get_property().uselist
|
||||
|
||||
def __get__(self, obj, class_):
|
||||
if self.owning_class is None:
|
||||
self.owning_class = class_ and class_ or type(obj)
|
||||
if obj is None:
|
||||
return self
|
||||
elif self.scalar is None:
|
||||
self.scalar = self._target_is_scalar()
|
||||
if self.scalar:
|
||||
self._initialize_scalar_accessors()
|
||||
|
||||
if self.scalar:
|
||||
return self._scalar_get(getattr(obj, self.target_collection))
|
||||
else:
|
||||
try:
|
||||
# If the owning instance is reborn (orm session resurrect,
|
||||
# etc.), refresh the proxy cache.
|
||||
creator_id, proxy = getattr(obj, self.key)
|
||||
if id(obj) == creator_id:
|
||||
return proxy
|
||||
except AttributeError:
|
||||
pass
|
||||
proxy = self._new(_lazy_collection(obj, self.target_collection))
|
||||
setattr(obj, self.key, (id(obj), proxy))
|
||||
return proxy
|
||||
|
||||
def __set__(self, obj, values):
|
||||
if self.owning_class is None:
|
||||
self.owning_class = type(obj)
|
||||
if self.scalar is None:
|
||||
self.scalar = self._target_is_scalar()
|
||||
if self.scalar:
|
||||
self._initialize_scalar_accessors()
|
||||
|
||||
if self.scalar:
|
||||
creator = self.creator and self.creator or self.target_class
|
||||
target = getattr(obj, self.target_collection)
|
||||
if target is None:
|
||||
setattr(obj, self.target_collection, creator(values))
|
||||
else:
|
||||
self._scalar_set(target, values)
|
||||
else:
|
||||
proxy = self.__get__(obj, None)
|
||||
if proxy is not values:
|
||||
proxy.clear()
|
||||
self._set(proxy, values)
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.owning_class is None:
|
||||
self.owning_class = type(obj)
|
||||
delattr(obj, self.key)
|
||||
|
||||
def _initialize_scalar_accessors(self):
|
||||
if self.getset_factory:
|
||||
get, set = self.getset_factory(None, self)
|
||||
else:
|
||||
get, set = self._default_getset(None)
|
||||
self._scalar_get, self._scalar_set = get, set
|
||||
|
||||
def _default_getset(self, collection_class):
|
||||
attr = self.value_attr
|
||||
getter = operator.attrgetter(attr)
|
||||
if collection_class is dict:
|
||||
setter = lambda o, k, v: setattr(o, attr, v)
|
||||
else:
|
||||
setter = lambda o, v: setattr(o, attr, v)
|
||||
return getter, setter
|
||||
|
||||
def _new(self, lazy_collection):
|
||||
creator = self.creator and self.creator or self.target_class
|
||||
self.collection_class = util.duck_type_collection(lazy_collection())
|
||||
|
||||
if self.proxy_factory:
|
||||
return self.proxy_factory(lazy_collection, creator, self.value_attr, self)
|
||||
|
||||
if self.getset_factory:
|
||||
getter, setter = self.getset_factory(self.collection_class, self)
|
||||
else:
|
||||
getter, setter = self._default_getset(self.collection_class)
|
||||
|
||||
if self.collection_class is list:
|
||||
return _AssociationList(lazy_collection, creator, getter, setter, self)
|
||||
elif self.collection_class is dict:
|
||||
return _AssociationDict(lazy_collection, creator, getter, setter, self)
|
||||
elif self.collection_class is set:
|
||||
return _AssociationSet(lazy_collection, creator, getter, setter, self)
|
||||
else:
|
||||
raise exceptions.ArgumentError(
|
||||
'could not guess which interface to use for '
|
||||
'collection_class "%s" backing "%s"; specify a '
|
||||
'proxy_factory and proxy_bulk_set manually' %
|
||||
(self.collection_class.__name__, self.target_collection))
|
||||
|
||||
def _inflate(self, proxy):
|
||||
creator = self.creator and self.creator or self.target_class
|
||||
|
||||
if self.getset_factory:
|
||||
getter, setter = self.getset_factory(self.collection_class, self)
|
||||
else:
|
||||
getter, setter = self._default_getset(self.collection_class)
|
||||
|
||||
proxy.creator = creator
|
||||
proxy.getter = getter
|
||||
proxy.setter = setter
|
||||
|
||||
def _set(self, proxy, values):
|
||||
if self.proxy_bulk_set:
|
||||
self.proxy_bulk_set(proxy, values)
|
||||
elif self.collection_class is list:
|
||||
proxy.extend(values)
|
||||
elif self.collection_class is dict:
|
||||
proxy.update(values)
|
||||
elif self.collection_class is set:
|
||||
proxy.update(values)
|
||||
else:
|
||||
raise exceptions.ArgumentError(
|
||||
'no proxy_bulk_set supplied for custom '
|
||||
'collection_class implementation')
|
||||
|
||||
@property
|
||||
def _comparator(self):
|
||||
return self._get_property().comparator
|
||||
|
||||
def any(self, criterion=None, **kwargs):
|
||||
return self._comparator.any(getattr(self.target_class, self.value_attr).has(criterion, **kwargs))
|
||||
|
||||
def has(self, criterion=None, **kwargs):
|
||||
return self._comparator.has(getattr(self.target_class, self.value_attr).has(criterion, **kwargs))
|
||||
|
||||
def contains(self, obj):
|
||||
return self._comparator.any(**{self.value_attr: obj})
|
||||
|
||||
def __eq__(self, obj):
|
||||
return self._comparator.has(**{self.value_attr: obj})
|
||||
|
||||
def __ne__(self, obj):
|
||||
return not_(self.__eq__(obj))
|
||||
|
||||
|
||||
class _lazy_collection(object):
|
||||
def __init__(self, obj, target):
|
||||
self.ref = weakref.ref(obj)
|
||||
self.target = target
|
||||
|
||||
def __call__(self):
|
||||
obj = self.ref()
|
||||
if obj is None:
|
||||
raise exceptions.InvalidRequestError(
|
||||
"stale association proxy, parent object has gone out of "
|
||||
"scope")
|
||||
return getattr(obj, self.target)
|
||||
|
||||
def __getstate__(self):
|
||||
return {'obj':self.ref(), 'target':self.target}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.ref = weakref.ref(state['obj'])
|
||||
self.target = state['target']
|
||||
|
||||
class _AssociationCollection(object):
|
||||
def __init__(self, lazy_collection, creator, getter, setter, parent):
|
||||
"""Constructs an _AssociationCollection.
|
||||
|
||||
This will always be a subclass of either _AssociationList,
|
||||
_AssociationSet, or _AssociationDict.
|
||||
|
||||
lazy_collection
|
||||
A callable returning a list-based collection of entities (usually an
|
||||
object attribute managed by a SQLAlchemy relationship())
|
||||
|
||||
creator
|
||||
A function that creates new target entities. Given one parameter:
|
||||
value. This assertion is assumed::
|
||||
|
||||
obj = creator(somevalue)
|
||||
assert getter(obj) == somevalue
|
||||
|
||||
getter
|
||||
A function. Given an associated object, return the 'value'.
|
||||
|
||||
setter
|
||||
A function. Given an associated object and a value, store that
|
||||
value on the object.
|
||||
|
||||
"""
|
||||
self.lazy_collection = lazy_collection
|
||||
self.creator = creator
|
||||
self.getter = getter
|
||||
self.setter = setter
|
||||
self.parent = parent
|
||||
|
||||
col = property(lambda self: self.lazy_collection())
|
||||
|
||||
def __len__(self):
|
||||
return len(self.col)
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.col)
|
||||
|
||||
def __getstate__(self):
|
||||
return {'parent':self.parent, 'lazy_collection':self.lazy_collection}
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.parent = state['parent']
|
||||
self.lazy_collection = state['lazy_collection']
|
||||
self.parent._inflate(self)
|
||||
|
||||
class _AssociationList(_AssociationCollection):
|
||||
"""Generic, converting, list-to-list proxy."""
|
||||
|
||||
def _create(self, value):
|
||||
return self.creator(value)
|
||||
|
||||
def _get(self, object):
|
||||
return self.getter(object)
|
||||
|
||||
def _set(self, object, value):
|
||||
return self.setter(object, value)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self._get(self.col[index])
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
if not isinstance(index, slice):
|
||||
self._set(self.col[index], value)
|
||||
else:
|
||||
if index.stop is None:
|
||||
stop = len(self)
|
||||
elif index.stop < 0:
|
||||
stop = len(self) + index.stop
|
||||
else:
|
||||
stop = index.stop
|
||||
step = index.step or 1
|
||||
|
||||
rng = range(index.start or 0, stop, step)
|
||||
if step == 1:
|
||||
for i in rng:
|
||||
del self[index.start]
|
||||
i = index.start
|
||||
for item in value:
|
||||
self.insert(i, item)
|
||||
i += 1
|
||||
else:
|
||||
if len(value) != len(rng):
|
||||
raise ValueError(
|
||||
"attempt to assign sequence of size %s to "
|
||||
"extended slice of size %s" % (len(value),
|
||||
len(rng)))
|
||||
for i, item in zip(rng, value):
|
||||
self._set(self.col[i], item)
|
||||
|
||||
def __delitem__(self, index):
|
||||
del self.col[index]
|
||||
|
||||
def __contains__(self, value):
|
||||
for member in self.col:
|
||||
# testlib.pragma exempt:__eq__
|
||||
if self._get(member) == value:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __getslice__(self, start, end):
|
||||
return [self._get(member) for member in self.col[start:end]]
|
||||
|
||||
def __setslice__(self, start, end, values):
|
||||
members = [self._create(v) for v in values]
|
||||
self.col[start:end] = members
|
||||
|
||||
def __delslice__(self, start, end):
|
||||
del self.col[start:end]
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over proxied values.
|
||||
|
||||
For the actual domain objects, iterate over .col instead or
|
||||
just use the underlying collection directly from its property
|
||||
on the parent.
|
||||
"""
|
||||
|
||||
for member in self.col:
|
||||
yield self._get(member)
|
||||
raise StopIteration
|
||||
|
||||
def append(self, value):
|
||||
item = self._create(value)
|
||||
self.col.append(item)
|
||||
|
||||
def count(self, value):
|
||||
return sum([1 for _ in
|
||||
itertools.ifilter(lambda v: v == value, iter(self))])
|
||||
|
||||
def extend(self, values):
|
||||
for v in values:
|
||||
self.append(v)
|
||||
|
||||
def insert(self, index, value):
|
||||
self.col[index:index] = [self._create(value)]
|
||||
|
||||
def pop(self, index=-1):
|
||||
return self.getter(self.col.pop(index))
|
||||
|
||||
def remove(self, value):
|
||||
for i, val in enumerate(self):
|
||||
if val == value:
|
||||
del self.col[i]
|
||||
return
|
||||
raise ValueError("value not in list")
|
||||
|
||||
def reverse(self):
|
||||
"""Not supported, use reversed(mylist)"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def sort(self):
|
||||
"""Not supported, use sorted(mylist)"""
|
||||
|
||||
raise NotImplementedError
|
||||
|
||||
def clear(self):
|
||||
del self.col[0:len(self.col)]
|
||||
|
||||
def __eq__(self, other):
|
||||
return list(self) == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return list(self) != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return list(self) < other
|
||||
|
||||
def __le__(self, other):
|
||||
return list(self) <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return list(self) > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return list(self) >= other
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(list(self), other)
|
||||
|
||||
def __add__(self, iterable):
|
||||
try:
|
||||
other = list(iterable)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
return list(self) + other
|
||||
|
||||
def __radd__(self, iterable):
|
||||
try:
|
||||
other = list(iterable)
|
||||
except TypeError:
|
||||
return NotImplemented
|
||||
return other + list(self)
|
||||
|
||||
def __mul__(self, n):
|
||||
if not isinstance(n, int):
|
||||
return NotImplemented
|
||||
return list(self) * n
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __iadd__(self, iterable):
|
||||
self.extend(iterable)
|
||||
return self
|
||||
|
||||
def __imul__(self, n):
|
||||
# unlike a regular list *=, proxied __imul__ will generate unique
|
||||
# backing objects for each copy. *= on proxied lists is a bit of
|
||||
# a stretch anyhow, and this interpretation of the __imul__ contract
|
||||
# is more plausibly useful than copying the backing objects.
|
||||
if not isinstance(n, int):
|
||||
return NotImplemented
|
||||
if n == 0:
|
||||
self.clear()
|
||||
elif n > 1:
|
||||
self.extend(list(self) * (n - 1))
|
||||
return self
|
||||
|
||||
def copy(self):
|
||||
return list(self)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(list(self))
|
||||
|
||||
def __hash__(self):
|
||||
raise TypeError("%s objects are unhashable" % type(self).__name__)
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(list, func_name)):
|
||||
func.__doc__ = getattr(list, func_name).__doc__
|
||||
del func_name, func
|
||||
|
||||
|
||||
_NotProvided = util.symbol('_NotProvided')
|
||||
class _AssociationDict(_AssociationCollection):
|
||||
"""Generic, converting, dict-to-dict proxy."""
|
||||
|
||||
def _create(self, key, value):
|
||||
return self.creator(key, value)
|
||||
|
||||
def _get(self, object):
|
||||
return self.getter(object)
|
||||
|
||||
def _set(self, object, key, value):
|
||||
return self.setter(object, key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._get(self.col[key])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key in self.col:
|
||||
self._set(self.col[key], key, value)
|
||||
else:
|
||||
self.col[key] = self._create(key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.col[key]
|
||||
|
||||
def __contains__(self, key):
|
||||
# testlib.pragma exempt:__hash__
|
||||
return key in self.col
|
||||
|
||||
def has_key(self, key):
|
||||
# testlib.pragma exempt:__hash__
|
||||
return key in self.col
|
||||
|
||||
def __iter__(self):
|
||||
return self.col.iterkeys()
|
||||
|
||||
def clear(self):
|
||||
self.col.clear()
|
||||
|
||||
def __eq__(self, other):
|
||||
return dict(self) == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return dict(self) != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return dict(self) < other
|
||||
|
||||
def __le__(self, other):
|
||||
return dict(self) <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return dict(self) > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return dict(self) >= other
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(dict(self), other)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(dict(self.items()))
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def setdefault(self, key, default=None):
|
||||
if key not in self.col:
|
||||
self.col[key] = self._create(key, default)
|
||||
return default
|
||||
else:
|
||||
return self[key]
|
||||
|
||||
def keys(self):
|
||||
return self.col.keys()
|
||||
|
||||
def iterkeys(self):
|
||||
return self.col.iterkeys()
|
||||
|
||||
def values(self):
|
||||
return [ self._get(member) for member in self.col.values() ]
|
||||
|
||||
def itervalues(self):
|
||||
for key in self.col:
|
||||
yield self._get(self.col[key])
|
||||
raise StopIteration
|
||||
|
||||
def items(self):
|
||||
return [(k, self._get(self.col[k])) for k in self]
|
||||
|
||||
def iteritems(self):
|
||||
for key in self.col:
|
||||
yield (key, self._get(self.col[key]))
|
||||
raise StopIteration
|
||||
|
||||
def pop(self, key, default=_NotProvided):
|
||||
if default is _NotProvided:
|
||||
member = self.col.pop(key)
|
||||
else:
|
||||
member = self.col.pop(key, default)
|
||||
return self._get(member)
|
||||
|
||||
def popitem(self):
|
||||
item = self.col.popitem()
|
||||
return (item[0], self._get(item[1]))
|
||||
|
||||
def update(self, *a, **kw):
|
||||
if len(a) > 1:
|
||||
raise TypeError('update expected at most 1 arguments, got %i' %
|
||||
len(a))
|
||||
elif len(a) == 1:
|
||||
seq_or_map = a[0]
|
||||
for item in seq_or_map:
|
||||
if isinstance(item, tuple):
|
||||
self[item[0]] = item[1]
|
||||
else:
|
||||
self[item] = seq_or_map[item]
|
||||
|
||||
for key, value in kw:
|
||||
self[key] = value
|
||||
|
||||
def copy(self):
|
||||
return dict(self.items())
|
||||
|
||||
def __hash__(self):
|
||||
raise TypeError("%s objects are unhashable" % type(self).__name__)
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(dict, func_name)):
|
||||
func.__doc__ = getattr(dict, func_name).__doc__
|
||||
del func_name, func
|
||||
|
||||
|
||||
class _AssociationSet(_AssociationCollection):
|
||||
"""Generic, converting, set-to-set proxy."""
|
||||
|
||||
def _create(self, value):
|
||||
return self.creator(value)
|
||||
|
||||
def _get(self, object):
|
||||
return self.getter(object)
|
||||
|
||||
def _set(self, object, value):
|
||||
return self.setter(object, value)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.col)
|
||||
|
||||
def __nonzero__(self):
|
||||
if self.col:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def __contains__(self, value):
|
||||
for member in self.col:
|
||||
# testlib.pragma exempt:__eq__
|
||||
if self._get(member) == value:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over proxied values.
|
||||
|
||||
For the actual domain objects, iterate over .col instead or just use
|
||||
the underlying collection directly from its property on the parent.
|
||||
|
||||
"""
|
||||
for member in self.col:
|
||||
yield self._get(member)
|
||||
raise StopIteration
|
||||
|
||||
def add(self, value):
|
||||
if value not in self:
|
||||
self.col.add(self._create(value))
|
||||
|
||||
# for discard and remove, choosing a more expensive check strategy rather
|
||||
# than call self.creator()
|
||||
def discard(self, value):
|
||||
for member in self.col:
|
||||
if self._get(member) == value:
|
||||
self.col.discard(member)
|
||||
break
|
||||
|
||||
def remove(self, value):
|
||||
for member in self.col:
|
||||
if self._get(member) == value:
|
||||
self.col.discard(member)
|
||||
return
|
||||
raise KeyError(value)
|
||||
|
||||
def pop(self):
|
||||
if not self.col:
|
||||
raise KeyError('pop from an empty set')
|
||||
member = self.col.pop()
|
||||
return self._get(member)
|
||||
|
||||
def update(self, other):
|
||||
for value in other:
|
||||
self.add(value)
|
||||
|
||||
def __ior__(self, other):
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
for value in other:
|
||||
self.add(value)
|
||||
return self
|
||||
|
||||
def _set(self):
|
||||
return set(iter(self))
|
||||
|
||||
def union(self, other):
|
||||
return set(self).union(other)
|
||||
|
||||
__or__ = union
|
||||
|
||||
def difference(self, other):
|
||||
return set(self).difference(other)
|
||||
|
||||
__sub__ = difference
|
||||
|
||||
def difference_update(self, other):
|
||||
for value in other:
|
||||
self.discard(value)
|
||||
|
||||
def __isub__(self, other):
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
for value in other:
|
||||
self.discard(value)
|
||||
return self
|
||||
|
||||
def intersection(self, other):
|
||||
return set(self).intersection(other)
|
||||
|
||||
__and__ = intersection
|
||||
|
||||
def intersection_update(self, other):
|
||||
want, have = self.intersection(other), set(self)
|
||||
|
||||
remove, add = have - want, want - have
|
||||
|
||||
for value in remove:
|
||||
self.remove(value)
|
||||
for value in add:
|
||||
self.add(value)
|
||||
|
||||
def __iand__(self, other):
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.intersection(other), set(self)
|
||||
|
||||
remove, add = have - want, want - have
|
||||
|
||||
for value in remove:
|
||||
self.remove(value)
|
||||
for value in add:
|
||||
self.add(value)
|
||||
return self
|
||||
|
||||
def symmetric_difference(self, other):
|
||||
return set(self).symmetric_difference(other)
|
||||
|
||||
__xor__ = symmetric_difference
|
||||
|
||||
def symmetric_difference_update(self, other):
|
||||
want, have = self.symmetric_difference(other), set(self)
|
||||
|
||||
remove, add = have - want, want - have
|
||||
|
||||
for value in remove:
|
||||
self.remove(value)
|
||||
for value in add:
|
||||
self.add(value)
|
||||
|
||||
def __ixor__(self, other):
|
||||
if not collections._set_binops_check_strict(self, other):
|
||||
return NotImplemented
|
||||
want, have = self.symmetric_difference(other), set(self)
|
||||
|
||||
remove, add = have - want, want - have
|
||||
|
||||
for value in remove:
|
||||
self.remove(value)
|
||||
for value in add:
|
||||
self.add(value)
|
||||
return self
|
||||
|
||||
def issubset(self, other):
|
||||
return set(self).issubset(other)
|
||||
|
||||
def issuperset(self, other):
|
||||
return set(self).issuperset(other)
|
||||
|
||||
def clear(self):
|
||||
self.col.clear()
|
||||
|
||||
def copy(self):
|
||||
return set(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return set(self) == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return set(self) != other
|
||||
|
||||
def __lt__(self, other):
|
||||
return set(self) < other
|
||||
|
||||
def __le__(self, other):
|
||||
return set(self) <= other
|
||||
|
||||
def __gt__(self, other):
|
||||
return set(self) > other
|
||||
|
||||
def __ge__(self, other):
|
||||
return set(self) >= other
|
||||
|
||||
def __repr__(self):
|
||||
return repr(set(self))
|
||||
|
||||
def __hash__(self):
|
||||
raise TypeError("%s objects are unhashable" % type(self).__name__)
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(set, func_name)):
|
||||
func.__doc__ = getattr(set, func_name).__doc__
|
||||
del func_name, func
|
||||
@@ -0,0 +1,239 @@
|
||||
# ext/compiler.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Provides an API for creation of custom ClauseElements and compilers.
|
||||
|
||||
Synopsis
|
||||
========
|
||||
|
||||
Usage involves the creation of one or more :class:`~sqlalchemy.sql.expression.ClauseElement`
|
||||
subclasses and one or more callables defining its compilation::
|
||||
|
||||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import ColumnClause
|
||||
|
||||
class MyColumn(ColumnClause):
|
||||
pass
|
||||
|
||||
@compiles(MyColumn)
|
||||
def compile_mycolumn(element, compiler, **kw):
|
||||
return "[%s]" % element.name
|
||||
|
||||
Above, ``MyColumn`` extends :class:`~sqlalchemy.sql.expression.ColumnClause`,
|
||||
the base expression element for named column objects. The ``compiles``
|
||||
decorator registers itself with the ``MyColumn`` class so that it is invoked
|
||||
when the object is compiled to a string::
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
s = select([MyColumn('x'), MyColumn('y')])
|
||||
print str(s)
|
||||
|
||||
Produces::
|
||||
|
||||
SELECT [x], [y]
|
||||
|
||||
Dialect-specific compilation rules
|
||||
==================================
|
||||
|
||||
Compilers can also be made dialect-specific. The appropriate compiler will be
|
||||
invoked for the dialect in use::
|
||||
|
||||
from sqlalchemy.schema import DDLElement
|
||||
|
||||
class AlterColumn(DDLElement):
|
||||
|
||||
def __init__(self, column, cmd):
|
||||
self.column = column
|
||||
self.cmd = cmd
|
||||
|
||||
@compiles(AlterColumn)
|
||||
def visit_alter_column(element, compiler, **kw):
|
||||
return "ALTER COLUMN %s ..." % element.column.name
|
||||
|
||||
@compiles(AlterColumn, 'postgresql')
|
||||
def visit_alter_column(element, compiler, **kw):
|
||||
return "ALTER TABLE %s ALTER COLUMN %s ..." % (element.table.name, element.column.name)
|
||||
|
||||
The second ``visit_alter_table`` will be invoked when any ``postgresql`` dialect is used.
|
||||
|
||||
Compiling sub-elements of a custom expression construct
|
||||
=======================================================
|
||||
|
||||
The ``compiler`` argument is the :class:`~sqlalchemy.engine.base.Compiled`
|
||||
object in use. This object can be inspected for any information about the
|
||||
in-progress compilation, including ``compiler.dialect``,
|
||||
``compiler.statement`` etc. The :class:`~sqlalchemy.sql.compiler.SQLCompiler`
|
||||
and :class:`~sqlalchemy.sql.compiler.DDLCompiler` both include a ``process()``
|
||||
method which can be used for compilation of embedded attributes::
|
||||
|
||||
from sqlalchemy.sql.expression import Executable, ClauseElement
|
||||
|
||||
class InsertFromSelect(Executable, ClauseElement):
|
||||
def __init__(self, table, select):
|
||||
self.table = table
|
||||
self.select = select
|
||||
|
||||
@compiles(InsertFromSelect)
|
||||
def visit_insert_from_select(element, compiler, **kw):
|
||||
return "INSERT INTO %s (%s)" % (
|
||||
compiler.process(element.table, asfrom=True),
|
||||
compiler.process(element.select)
|
||||
)
|
||||
|
||||
insert = InsertFromSelect(t1, select([t1]).where(t1.c.x>5))
|
||||
print insert
|
||||
|
||||
Produces::
|
||||
|
||||
"INSERT INTO mytable (SELECT mytable.x, mytable.y, mytable.z FROM mytable WHERE mytable.x > :x_1)"
|
||||
|
||||
Cross Compiling between SQL and DDL compilers
|
||||
---------------------------------------------
|
||||
|
||||
SQL and DDL constructs are each compiled using different base compilers - ``SQLCompiler``
|
||||
and ``DDLCompiler``. A common need is to access the compilation rules of SQL expressions
|
||||
from within a DDL expression. The ``DDLCompiler`` includes an accessor ``sql_compiler`` for this reason, such as below where we generate a CHECK
|
||||
constraint that embeds a SQL expression::
|
||||
|
||||
@compiles(MyConstraint)
|
||||
def compile_my_constraint(constraint, ddlcompiler, **kw):
|
||||
return "CONSTRAINT %s CHECK (%s)" % (
|
||||
constraint.name,
|
||||
ddlcompiler.sql_compiler.process(constraint.expression)
|
||||
)
|
||||
|
||||
Changing the default compilation of existing constructs
|
||||
=======================================================
|
||||
|
||||
The compiler extension applies just as well to the existing constructs. When overriding
|
||||
the compilation of a built in SQL construct, the @compiles decorator is invoked upon
|
||||
the appropriate class (be sure to use the class, i.e. ``Insert`` or ``Select``, instead of the creation function such as ``insert()`` or ``select()``).
|
||||
|
||||
Within the new compilation function, to get at the "original" compilation routine,
|
||||
use the appropriate visit_XXX method - this because compiler.process() will call upon the
|
||||
overriding routine and cause an endless loop. Such as, to add "prefix" to all insert statements::
|
||||
|
||||
from sqlalchemy.sql.expression import Insert
|
||||
|
||||
@compiles(Insert)
|
||||
def prefix_inserts(insert, compiler, **kw):
|
||||
return compiler.visit_insert(insert.prefix_with("some prefix"), **kw)
|
||||
|
||||
The above compiler will prefix all INSERT statements with "some prefix" when compiled.
|
||||
|
||||
.. _type_compilation_extension:
|
||||
|
||||
Changing Compilation of Types
|
||||
=============================
|
||||
|
||||
``compiler`` works for types, too, such as below where we implement the MS-SQL specific 'max' keyword for ``String``/``VARCHAR``::
|
||||
|
||||
@compiles(String, 'mssql')
|
||||
@compiles(VARCHAR, 'mssql')
|
||||
def compile_varchar(element, compiler, **kw):
|
||||
if element.length == 'max':
|
||||
return "VARCHAR('max')"
|
||||
else:
|
||||
return compiler.visit_VARCHAR(element, **kw)
|
||||
|
||||
foo = Table('foo', metadata,
|
||||
Column('data', VARCHAR('max'))
|
||||
)
|
||||
|
||||
Subclassing Guidelines
|
||||
======================
|
||||
|
||||
A big part of using the compiler extension is subclassing SQLAlchemy expression constructs. To make this easier, the expression and schema packages feature a set of "bases" intended for common tasks. A synopsis is as follows:
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.ClauseElement` - This is the root
|
||||
expression class. Any SQL expression can be derived from this base, and is
|
||||
probably the best choice for longer constructs such as specialized INSERT
|
||||
statements.
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.ColumnElement` - The root of all
|
||||
"column-like" elements. Anything that you'd place in the "columns" clause of
|
||||
a SELECT statement (as well as order by and group by) can derive from this -
|
||||
the object will automatically have Python "comparison" behavior.
|
||||
|
||||
:class:`~sqlalchemy.sql.expression.ColumnElement` classes want to have a
|
||||
``type`` member which is expression's return type. This can be established
|
||||
at the instance level in the constructor, or at the class level if its
|
||||
generally constant::
|
||||
|
||||
class timestamp(ColumnElement):
|
||||
type = TIMESTAMP()
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.FunctionElement` - This is a hybrid of a
|
||||
``ColumnElement`` and a "from clause" like object, and represents a SQL
|
||||
function or stored procedure type of call. Since most databases support
|
||||
statements along the line of "SELECT FROM <some function>"
|
||||
``FunctionElement`` adds in the ability to be used in the FROM clause of a
|
||||
``select()`` construct::
|
||||
|
||||
from sqlalchemy.sql.expression import FunctionElement
|
||||
|
||||
class coalesce(FunctionElement):
|
||||
name = 'coalesce'
|
||||
|
||||
@compiles(coalesce)
|
||||
def compile(element, compiler, **kw):
|
||||
return "coalesce(%s)" % compiler.process(element.clauses)
|
||||
|
||||
@compiles(coalesce, 'oracle')
|
||||
def compile(element, compiler, **kw):
|
||||
if len(element.clauses) > 2:
|
||||
raise TypeError("coalesce only supports two arguments on Oracle")
|
||||
return "nvl(%s)" % compiler.process(element.clauses)
|
||||
|
||||
* :class:`~sqlalchemy.schema.DDLElement` - The root of all DDL expressions,
|
||||
like CREATE TABLE, ALTER TABLE, etc. Compilation of ``DDLElement``
|
||||
subclasses is issued by a ``DDLCompiler`` instead of a ``SQLCompiler``.
|
||||
``DDLElement`` also features ``Table`` and ``MetaData`` event hooks via the
|
||||
``execute_at()`` method, allowing the construct to be invoked during CREATE
|
||||
TABLE and DROP TABLE sequences.
|
||||
|
||||
* :class:`~sqlalchemy.sql.expression.Executable` - This is a mixin which should be
|
||||
used with any expression class that represents a "standalone" SQL statement that
|
||||
can be passed directly to an ``execute()`` method. It is already implicit
|
||||
within ``DDLElement`` and ``FunctionElement``.
|
||||
|
||||
"""
|
||||
|
||||
def compiles(class_, *specs):
|
||||
def decorate(fn):
|
||||
existing = class_.__dict__.get('_compiler_dispatcher', None)
|
||||
existing_dispatch = class_.__dict__.get('_compiler_dispatch')
|
||||
if not existing:
|
||||
existing = _dispatcher()
|
||||
|
||||
if existing_dispatch:
|
||||
existing.specs['default'] = existing_dispatch
|
||||
|
||||
# TODO: why is the lambda needed ?
|
||||
setattr(class_, '_compiler_dispatch', lambda *arg, **kw: existing(*arg, **kw))
|
||||
setattr(class_, '_compiler_dispatcher', existing)
|
||||
|
||||
if specs:
|
||||
for s in specs:
|
||||
existing.specs[s] = fn
|
||||
|
||||
else:
|
||||
existing.specs['default'] = fn
|
||||
return fn
|
||||
return decorate
|
||||
|
||||
class _dispatcher(object):
|
||||
def __init__(self):
|
||||
self.specs = {}
|
||||
|
||||
def __call__(self, element, compiler, **kw):
|
||||
# TODO: yes, this could also switch off of DBAPI in use.
|
||||
fn = self.specs.get(compiler.dialect.name, None)
|
||||
if not fn:
|
||||
fn = self.specs['default']
|
||||
return fn(element, compiler, **kw)
|
||||
|
||||
Executable
+1451
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,125 @@
|
||||
# ext/horizontal_shard.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Horizontal sharding support.
|
||||
|
||||
Defines a rudimental 'horizontal sharding' system which allows a Session to
|
||||
distribute queries and persistence operations across multiple databases.
|
||||
|
||||
For a usage example, see the :ref:`examples_sharding` example included in
|
||||
the source distrbution.
|
||||
|
||||
"""
|
||||
|
||||
import sqlalchemy.exceptions as sa_exc
|
||||
from sqlalchemy import util
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.orm.query import Query
|
||||
|
||||
__all__ = ['ShardedSession', 'ShardedQuery']
|
||||
|
||||
|
||||
class ShardedSession(Session):
|
||||
def __init__(self, shard_chooser, id_chooser, query_chooser, shards=None, **kwargs):
|
||||
"""Construct a ShardedSession.
|
||||
|
||||
:param shard_chooser: A callable which, passed a Mapper, a mapped instance, and possibly a
|
||||
SQL clause, returns a shard ID. This id may be based off of the
|
||||
attributes present within the object, or on some round-robin
|
||||
scheme. If the scheme is based on a selection, it should set
|
||||
whatever state on the instance to mark it in the future as
|
||||
participating in that shard.
|
||||
|
||||
:param id_chooser: A callable, passed a query and a tuple of identity values, which
|
||||
should return a list of shard ids where the ID might reside. The
|
||||
databases will be queried in the order of this listing.
|
||||
|
||||
:param query_chooser: For a given Query, returns the list of shard_ids where the query
|
||||
should be issued. Results from all shards returned will be combined
|
||||
together into a single listing.
|
||||
|
||||
:param shards: A dictionary of string shard names to :class:`~sqlalchemy.engine.base.Engine`
|
||||
objects.
|
||||
|
||||
"""
|
||||
super(ShardedSession, self).__init__(**kwargs)
|
||||
self.shard_chooser = shard_chooser
|
||||
self.id_chooser = id_chooser
|
||||
self.query_chooser = query_chooser
|
||||
self.__binds = {}
|
||||
self._mapper_flush_opts = {'connection_callable':self.connection}
|
||||
self._query_cls = ShardedQuery
|
||||
if shards is not None:
|
||||
for k in shards:
|
||||
self.bind_shard(k, shards[k])
|
||||
|
||||
def connection(self, mapper=None, instance=None, shard_id=None, **kwargs):
|
||||
if shard_id is None:
|
||||
shard_id = self.shard_chooser(mapper, instance)
|
||||
|
||||
if self.transaction is not None:
|
||||
return self.transaction.connection(mapper, shard_id=shard_id)
|
||||
else:
|
||||
return self.get_bind(mapper,
|
||||
shard_id=shard_id,
|
||||
instance=instance).contextual_connect(**kwargs)
|
||||
|
||||
def get_bind(self, mapper, shard_id=None, instance=None, clause=None, **kw):
|
||||
if shard_id is None:
|
||||
shard_id = self.shard_chooser(mapper, instance, clause=clause)
|
||||
return self.__binds[shard_id]
|
||||
|
||||
def bind_shard(self, shard_id, bind):
|
||||
self.__binds[shard_id] = bind
|
||||
|
||||
class ShardedQuery(Query):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ShardedQuery, self).__init__(*args, **kwargs)
|
||||
self.id_chooser = self.session.id_chooser
|
||||
self.query_chooser = self.session.query_chooser
|
||||
self._shard_id = None
|
||||
|
||||
def set_shard(self, shard_id):
|
||||
"""return a new query, limited to a single shard ID.
|
||||
|
||||
all subsequent operations with the returned query will
|
||||
be against the single shard regardless of other state.
|
||||
"""
|
||||
|
||||
q = self._clone()
|
||||
q._shard_id = shard_id
|
||||
return q
|
||||
|
||||
def _execute_and_instances(self, context):
|
||||
if self._shard_id is not None:
|
||||
result = self.session.connection(
|
||||
mapper=self._mapper_zero(),
|
||||
shard_id=self._shard_id).execute(context.statement, self._params)
|
||||
return self.instances(result, context)
|
||||
else:
|
||||
partial = []
|
||||
for shard_id in self.query_chooser(self):
|
||||
result = self.session.connection(
|
||||
mapper=self._mapper_zero(),
|
||||
shard_id=shard_id).execute(context.statement, self._params)
|
||||
partial = partial + list(self.instances(result, context))
|
||||
|
||||
# if some kind of in memory 'sorting'
|
||||
# were done, this is where it would happen
|
||||
return iter(partial)
|
||||
|
||||
def get(self, ident, **kwargs):
|
||||
if self._shard_id is not None:
|
||||
return super(ShardedQuery, self).get(ident)
|
||||
else:
|
||||
ident = util.to_list(ident)
|
||||
for shard_id in self.id_chooser(self, ident):
|
||||
o = self.set_shard(shard_id).get(ident, **kwargs)
|
||||
if o is not None:
|
||||
return o
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
# ext/orderinglist.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""A custom list that manages index/position information for its children.
|
||||
|
||||
:author: Jason Kirtland
|
||||
|
||||
``orderinglist`` is a helper for mutable ordered relationships. It will intercept
|
||||
list operations performed on a relationship collection and automatically
|
||||
synchronize changes in list position with an attribute on the related objects.
|
||||
(See :ref:`advdatamapping_entitycollections` for more information on the general pattern.)
|
||||
|
||||
Example: Two tables that store slides in a presentation. Each slide
|
||||
has a number of bullet points, displayed in order by the 'position'
|
||||
column on the bullets table. These bullets can be inserted and re-ordered
|
||||
by your end users, and you need to update the 'position' column of all
|
||||
affected rows when changes are made.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
slides_table = Table('Slides', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('name', String))
|
||||
|
||||
bullets_table = Table('Bullets', metadata,
|
||||
Column('id', Integer, primary_key=True),
|
||||
Column('slide_id', Integer, ForeignKey('Slides.id')),
|
||||
Column('position', Integer),
|
||||
Column('text', String))
|
||||
|
||||
class Slide(object):
|
||||
pass
|
||||
class Bullet(object):
|
||||
pass
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet, order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
The standard relationship mapping will produce a list-like attribute on each Slide
|
||||
containing all related Bullets, but coping with changes in ordering is totally
|
||||
your responsibility. If you insert a Bullet into that list, there is no
|
||||
magic- it won't have a position attribute unless you assign it it one, and
|
||||
you'll need to manually renumber all the subsequent Bullets in the list to
|
||||
accommodate the insert.
|
||||
|
||||
An ``orderinglist`` can automate this and manage the 'position' attribute on all
|
||||
related bullets for you.
|
||||
|
||||
.. sourcecode:: python+sql
|
||||
|
||||
mapper(Slide, slides_table, properties={
|
||||
'bullets': relationship(Bullet,
|
||||
collection_class=ordering_list('position'),
|
||||
order_by=[bullets_table.c.position])
|
||||
})
|
||||
mapper(Bullet, bullets_table)
|
||||
|
||||
s = Slide()
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets.append(Bullet())
|
||||
s.bullets[1].position
|
||||
>>> 1
|
||||
s.bullets.insert(1, Bullet())
|
||||
s.bullets[2].position
|
||||
>>> 2
|
||||
|
||||
Use the ``ordering_list`` function to set up the ``collection_class`` on relationships
|
||||
(as in the mapper example above). This implementation depends on the list
|
||||
starting in the proper order, so be SURE to put an order_by on your relationship.
|
||||
|
||||
.. warning:: ``ordering_list`` only provides limited functionality when a primary
|
||||
key column or unique column is the target of the sort. Since changing the order of
|
||||
entries often means that two rows must trade values, this is not possible when
|
||||
the value is constrained by a primary key or unique constraint, since one of the rows
|
||||
would temporarily have to point to a third available value so that the other row
|
||||
could take its old value. ``ordering_list`` doesn't do any of this for you,
|
||||
nor does SQLAlchemy itself.
|
||||
|
||||
``ordering_list`` takes the name of the related object's ordering attribute as
|
||||
an argument. By default, the zero-based integer index of the object's
|
||||
position in the ``ordering_list`` is synchronized with the ordering attribute:
|
||||
index 0 will get position 0, index 1 position 1, etc. To start numbering at 1
|
||||
or some other integer, provide ``count_from=1``.
|
||||
|
||||
Ordering values are not limited to incrementing integers. Almost any scheme
|
||||
can implemented by supplying a custom ``ordering_func`` that maps a Python list
|
||||
index to any value you require.
|
||||
|
||||
|
||||
|
||||
|
||||
"""
|
||||
from sqlalchemy.orm.collections import collection
|
||||
from sqlalchemy import util
|
||||
|
||||
__all__ = [ 'ordering_list' ]
|
||||
|
||||
|
||||
def ordering_list(attr, count_from=None, **kw):
|
||||
"""Prepares an OrderingList factory for use in mapper definitions.
|
||||
|
||||
Returns an object suitable for use as an argument to a Mapper relationship's
|
||||
``collection_class`` option. Arguments are:
|
||||
|
||||
attr
|
||||
Name of the mapped attribute to use for storage and retrieval of
|
||||
ordering information
|
||||
|
||||
count_from (optional)
|
||||
Set up an integer-based ordering, starting at ``count_from``. For
|
||||
example, ``ordering_list('pos', count_from=1)`` would create a 1-based
|
||||
list in SQL, storing the value in the 'pos' column. Ignored if
|
||||
``ordering_func`` is supplied.
|
||||
|
||||
Passes along any keyword arguments to ``OrderingList`` constructor.
|
||||
"""
|
||||
|
||||
kw = _unsugar_count_from(count_from=count_from, **kw)
|
||||
return lambda: OrderingList(attr, **kw)
|
||||
|
||||
# Ordering utility functions
|
||||
def count_from_0(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 0."""
|
||||
|
||||
return index
|
||||
|
||||
def count_from_1(index, collection):
|
||||
"""Numbering function: consecutive integers starting at 1."""
|
||||
|
||||
return index + 1
|
||||
|
||||
def count_from_n_factory(start):
|
||||
"""Numbering function: consecutive integers starting at arbitrary start."""
|
||||
|
||||
def f(index, collection):
|
||||
return index + start
|
||||
try:
|
||||
f.__name__ = 'count_from_%i' % start
|
||||
except TypeError:
|
||||
pass
|
||||
return f
|
||||
|
||||
def _unsugar_count_from(**kw):
|
||||
"""Builds counting functions from keywrod arguments.
|
||||
|
||||
Keyword argument filter, prepares a simple ``ordering_func`` from a
|
||||
``count_from`` argument, otherwise passes ``ordering_func`` on unchanged.
|
||||
"""
|
||||
|
||||
count_from = kw.pop('count_from', None)
|
||||
if kw.get('ordering_func', None) is None and count_from is not None:
|
||||
if count_from == 0:
|
||||
kw['ordering_func'] = count_from_0
|
||||
elif count_from == 1:
|
||||
kw['ordering_func'] = count_from_1
|
||||
else:
|
||||
kw['ordering_func'] = count_from_n_factory(count_from)
|
||||
return kw
|
||||
|
||||
class OrderingList(list):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
See the module and __init__ documentation for more details. The
|
||||
``ordering_list`` factory function is used to configure ``OrderingList``
|
||||
collections in ``mapper`` relationship definitions.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, ordering_attr=None, ordering_func=None,
|
||||
reorder_on_append=False):
|
||||
"""A custom list that manages position information for its children.
|
||||
|
||||
``OrderingList`` is a ``collection_class`` list implementation that
|
||||
syncs position in a Python list with a position attribute on the
|
||||
mapped objects.
|
||||
|
||||
This implementation relies on the list starting in the proper order,
|
||||
so be **sure** to put an ``order_by`` on your relationship.
|
||||
|
||||
ordering_attr
|
||||
Name of the attribute that stores the object's order in the
|
||||
relationship.
|
||||
|
||||
ordering_func
|
||||
Optional. A function that maps the position in the Python list to a
|
||||
value to store in the ``ordering_attr``. Values returned are
|
||||
usually (but need not be!) integers.
|
||||
|
||||
An ``ordering_func`` is called with two positional parameters: the
|
||||
index of the element in the list, and the list itself.
|
||||
|
||||
If omitted, Python list indexes are used for the attribute values.
|
||||
Two basic pre-built numbering functions are provided in this module:
|
||||
``count_from_0`` and ``count_from_1``. For more exotic examples
|
||||
like stepped numbering, alphabetical and Fibonacci numbering, see
|
||||
the unit tests.
|
||||
|
||||
reorder_on_append
|
||||
Default False. When appending an object with an existing (non-None)
|
||||
ordering value, that value will be left untouched unless
|
||||
``reorder_on_append`` is true. This is an optimization to avoid a
|
||||
variety of dangerous unexpected database writes.
|
||||
|
||||
SQLAlchemy will add instances to the list via append() when your
|
||||
object loads. If for some reason the result set from the database
|
||||
skips a step in the ordering (say, row '1' is missing but you get
|
||||
'2', '3', and '4'), reorder_on_append=True would immediately
|
||||
renumber the items to '1', '2', '3'. If you have multiple sessions
|
||||
making changes, any of whom happen to load this collection even in
|
||||
passing, all of the sessions would try to "clean up" the numbering
|
||||
in their commits, possibly causing all but one to fail with a
|
||||
concurrent modification error. Spooky action at a distance.
|
||||
|
||||
Recommend leaving this with the default of False, and just call
|
||||
``reorder()`` if you're doing ``append()`` operations with
|
||||
previously ordered instances or when doing some housekeeping after
|
||||
manual sql operations.
|
||||
|
||||
"""
|
||||
self.ordering_attr = ordering_attr
|
||||
if ordering_func is None:
|
||||
ordering_func = count_from_0
|
||||
self.ordering_func = ordering_func
|
||||
self.reorder_on_append = reorder_on_append
|
||||
|
||||
# More complex serialization schemes (multi column, e.g.) are possible by
|
||||
# subclassing and reimplementing these two methods.
|
||||
def _get_order_value(self, entity):
|
||||
return getattr(entity, self.ordering_attr)
|
||||
|
||||
def _set_order_value(self, entity, value):
|
||||
setattr(entity, self.ordering_attr, value)
|
||||
|
||||
def reorder(self):
|
||||
"""Synchronize ordering for the entire collection.
|
||||
|
||||
Sweeps through the list and ensures that each object has accurate
|
||||
ordering information set.
|
||||
|
||||
"""
|
||||
for index, entity in enumerate(self):
|
||||
self._order_entity(index, entity, True)
|
||||
|
||||
# As of 0.5, _reorder is no longer semi-private
|
||||
_reorder = reorder
|
||||
|
||||
def _order_entity(self, index, entity, reorder=True):
|
||||
have = self._get_order_value(entity)
|
||||
|
||||
# Don't disturb existing ordering if reorder is False
|
||||
if have is not None and not reorder:
|
||||
return
|
||||
|
||||
should_be = self.ordering_func(index, self)
|
||||
if have != should_be:
|
||||
self._set_order_value(entity, should_be)
|
||||
|
||||
def append(self, entity):
|
||||
super(OrderingList, self).append(entity)
|
||||
self._order_entity(len(self) - 1, entity, self.reorder_on_append)
|
||||
|
||||
def _raw_append(self, entity):
|
||||
"""Append without any ordering behavior."""
|
||||
|
||||
super(OrderingList, self).append(entity)
|
||||
_raw_append = collection.adds(1)(_raw_append)
|
||||
|
||||
def insert(self, index, entity):
|
||||
super(OrderingList, self).insert(index, entity)
|
||||
self._reorder()
|
||||
|
||||
def remove(self, entity):
|
||||
super(OrderingList, self).remove(entity)
|
||||
self._reorder()
|
||||
|
||||
def pop(self, index=-1):
|
||||
entity = super(OrderingList, self).pop(index)
|
||||
self._reorder()
|
||||
return entity
|
||||
|
||||
def __setitem__(self, index, entity):
|
||||
if isinstance(index, slice):
|
||||
step = index.step or 1
|
||||
start = index.start or 0
|
||||
if start < 0:
|
||||
start += len(self)
|
||||
stop = index.stop or len(self)
|
||||
if stop < 0:
|
||||
stop += len(self)
|
||||
|
||||
for i in xrange(start, stop, step):
|
||||
self.__setitem__(i, entity[i])
|
||||
else:
|
||||
self._order_entity(index, entity, True)
|
||||
super(OrderingList, self).__setitem__(index, entity)
|
||||
|
||||
def __delitem__(self, index):
|
||||
super(OrderingList, self).__delitem__(index)
|
||||
self._reorder()
|
||||
|
||||
# Py2K
|
||||
def __setslice__(self, start, end, values):
|
||||
super(OrderingList, self).__setslice__(start, end, values)
|
||||
self._reorder()
|
||||
|
||||
def __delslice__(self, start, end):
|
||||
super(OrderingList, self).__delslice__(start, end)
|
||||
self._reorder()
|
||||
# end Py2K
|
||||
|
||||
for func_name, func in locals().items():
|
||||
if (util.callable(func) and func.func_name == func_name and
|
||||
not func.__doc__ and hasattr(list, func_name)):
|
||||
func.__doc__ = getattr(list, func_name).__doc__
|
||||
del func_name, func
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# ext/serializer.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""Serializer/Deserializer objects for usage with SQLAlchemy query structures,
|
||||
allowing "contextual" deserialization.
|
||||
|
||||
Any SQLAlchemy query structure, either based on sqlalchemy.sql.*
|
||||
or sqlalchemy.orm.* can be used. The mappers, Tables, Columns, Session
|
||||
etc. which are referenced by the structure are not persisted in serialized
|
||||
form, but are instead re-associated with the query structure
|
||||
when it is deserialized.
|
||||
|
||||
Usage is nearly the same as that of the standard Python pickle module::
|
||||
|
||||
from sqlalchemy.ext.serializer import loads, dumps
|
||||
metadata = MetaData(bind=some_engine)
|
||||
Session = scoped_session(sessionmaker())
|
||||
|
||||
# ... define mappers
|
||||
|
||||
query = Session.query(MyClass).filter(MyClass.somedata=='foo').order_by(MyClass.sortkey)
|
||||
|
||||
# pickle the query
|
||||
serialized = dumps(query)
|
||||
|
||||
# unpickle. Pass in metadata + scoped_session
|
||||
query2 = loads(serialized, metadata, Session)
|
||||
|
||||
print query2.all()
|
||||
|
||||
Similar restrictions as when using raw pickle apply; mapped classes must be
|
||||
themselves be pickleable, meaning they are importable from a module-level
|
||||
namespace.
|
||||
|
||||
The serializer module is only appropriate for query structures. It is not
|
||||
needed for:
|
||||
|
||||
* instances of user-defined classes. These contain no references to engines,
|
||||
sessions or expression constructs in the typical case and can be serialized directly.
|
||||
|
||||
* Table metadata that is to be loaded entirely from the serialized structure (i.e. is
|
||||
not already declared in the application). Regular pickle.loads()/dumps() can
|
||||
be used to fully dump any ``MetaData`` object, typically one which was reflected
|
||||
from an existing database at some previous point in time. The serializer module
|
||||
is specifically for the opposite case, where the Table metadata is already present
|
||||
in memory.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy.orm import class_mapper, Query
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.orm.mapper import Mapper
|
||||
from sqlalchemy.orm.attributes import QueryableAttribute
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.util import pickle
|
||||
import re
|
||||
import base64
|
||||
# Py3K
|
||||
#from io import BytesIO as byte_buffer
|
||||
# Py2K
|
||||
from cStringIO import StringIO as byte_buffer
|
||||
# end Py2K
|
||||
|
||||
# Py3K
|
||||
#def b64encode(x):
|
||||
# return base64.b64encode(x).decode('ascii')
|
||||
#def b64decode(x):
|
||||
# return base64.b64decode(x.encode('ascii'))
|
||||
# Py2K
|
||||
b64encode = base64.b64encode
|
||||
b64decode = base64.b64decode
|
||||
# end Py2K
|
||||
|
||||
__all__ = ['Serializer', 'Deserializer', 'dumps', 'loads']
|
||||
|
||||
|
||||
|
||||
def Serializer(*args, **kw):
|
||||
pickler = pickle.Pickler(*args, **kw)
|
||||
|
||||
def persistent_id(obj):
|
||||
#print "serializing:", repr(obj)
|
||||
if isinstance(obj, QueryableAttribute):
|
||||
cls = obj.impl.class_
|
||||
key = obj.impl.key
|
||||
id = "attribute:" + key + ":" + b64encode(pickle.dumps(cls))
|
||||
elif isinstance(obj, Mapper) and not obj.non_primary:
|
||||
id = "mapper:" + b64encode(pickle.dumps(obj.class_))
|
||||
elif isinstance(obj, Table):
|
||||
id = "table:" + str(obj)
|
||||
elif isinstance(obj, Column) and isinstance(obj.table, Table):
|
||||
id = "column:" + str(obj.table) + ":" + obj.key
|
||||
elif isinstance(obj, Session):
|
||||
id = "session:"
|
||||
elif isinstance(obj, Engine):
|
||||
id = "engine:"
|
||||
else:
|
||||
return None
|
||||
return id
|
||||
|
||||
pickler.persistent_id = persistent_id
|
||||
return pickler
|
||||
|
||||
our_ids = re.compile(r'(mapper|table|column|session|attribute|engine):(.*)')
|
||||
|
||||
def Deserializer(file, metadata=None, scoped_session=None, engine=None):
|
||||
unpickler = pickle.Unpickler(file)
|
||||
|
||||
def get_engine():
|
||||
if engine:
|
||||
return engine
|
||||
elif scoped_session and scoped_session().bind:
|
||||
return scoped_session().bind
|
||||
elif metadata and metadata.bind:
|
||||
return metadata.bind
|
||||
else:
|
||||
return None
|
||||
|
||||
def persistent_load(id):
|
||||
m = our_ids.match(id)
|
||||
if not m:
|
||||
return None
|
||||
else:
|
||||
type_, args = m.group(1, 2)
|
||||
if type_ == 'attribute':
|
||||
key, clsarg = args.split(":")
|
||||
cls = pickle.loads(b64decode(clsarg))
|
||||
return getattr(cls, key)
|
||||
elif type_ == "mapper":
|
||||
cls = pickle.loads(b64decode(args))
|
||||
return class_mapper(cls)
|
||||
elif type_ == "table":
|
||||
return metadata.tables[args]
|
||||
elif type_ == "column":
|
||||
table, colname = args.split(':')
|
||||
return metadata.tables[table].c[colname]
|
||||
elif type_ == "session":
|
||||
return scoped_session()
|
||||
elif type_ == "engine":
|
||||
return get_engine()
|
||||
else:
|
||||
raise Exception("Unknown token: %s" % type_)
|
||||
unpickler.persistent_load = persistent_load
|
||||
return unpickler
|
||||
|
||||
def dumps(obj, protocol=0):
|
||||
buf = byte_buffer()
|
||||
pickler = Serializer(buf, protocol)
|
||||
pickler.dump(obj)
|
||||
return buf.getvalue()
|
||||
|
||||
def loads(data, metadata=None, scoped_session=None, engine=None):
|
||||
buf = byte_buffer(data)
|
||||
unpickler = Deserializer(buf, metadata, scoped_session, engine)
|
||||
return unpickler.load()
|
||||
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
# ext/sqlsoup.py
|
||||
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
#
|
||||
# This module is part of SQLAlchemy and is released under
|
||||
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
"""
|
||||
Introduction
|
||||
============
|
||||
|
||||
SqlSoup provides a convenient way to access existing database
|
||||
tables without having to declare table or mapper classes ahead
|
||||
of time. It is built on top of the SQLAlchemy ORM and provides a
|
||||
super-minimalistic interface to an existing database.
|
||||
|
||||
SqlSoup effectively provides a coarse grained, alternative
|
||||
interface to working with the SQLAlchemy ORM, providing a "self
|
||||
configuring" interface for extremely rudimental operations. It's
|
||||
somewhat akin to a "super novice mode" version of the ORM. While
|
||||
SqlSoup can be very handy, users are strongly encouraged to use
|
||||
the full ORM for non-trivial applications.
|
||||
|
||||
Suppose we have a database with users, books, and loans tables
|
||||
(corresponding to the PyWebOff dataset, if you're curious).
|
||||
|
||||
Creating a SqlSoup gateway is just like creating an SQLAlchemy
|
||||
engine::
|
||||
|
||||
>>> from sqlalchemy.ext.sqlsoup import SqlSoup
|
||||
>>> db = SqlSoup('sqlite:///:memory:')
|
||||
|
||||
or, you can re-use an existing engine::
|
||||
|
||||
>>> db = SqlSoup(engine)
|
||||
|
||||
You can optionally specify a schema within the database for your
|
||||
SqlSoup::
|
||||
|
||||
>>> db.schema = myschemaname
|
||||
|
||||
Loading objects
|
||||
===============
|
||||
|
||||
Loading objects is as easy as this::
|
||||
|
||||
>>> users = db.users.all()
|
||||
>>> users.sort()
|
||||
>>> users
|
||||
[
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0),
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
]
|
||||
|
||||
Of course, letting the database do the sort is better::
|
||||
|
||||
>>> db.users.order_by(db.users.name).all()
|
||||
[
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1),
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0)
|
||||
]
|
||||
|
||||
Field access is intuitive::
|
||||
|
||||
>>> users[0].email
|
||||
u'student@example.edu'
|
||||
|
||||
Of course, you don't want to load all users very often. Let's
|
||||
add a WHERE clause. Let's also switch the order_by to DESC while
|
||||
we're at it::
|
||||
|
||||
>>> from sqlalchemy import or_, and_, desc
|
||||
>>> where = or_(db.users.name=='Bhargan Basepair', db.users.email=='student@example.edu')
|
||||
>>> db.users.filter(where).order_by(desc(db.users.name)).all()
|
||||
[
|
||||
MappedUsers(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0),
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
]
|
||||
|
||||
You can also use .first() (to retrieve only the first object
|
||||
from a query) or .one() (like .first when you expect exactly one
|
||||
user -- it will raise an exception if more were returned)::
|
||||
|
||||
>>> db.users.filter(db.users.name=='Bhargan Basepair').one()
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
|
||||
Since name is the primary key, this is equivalent to
|
||||
|
||||
>>> db.users.get('Bhargan Basepair')
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
|
||||
This is also equivalent to
|
||||
|
||||
>>> db.users.filter_by(name='Bhargan Basepair').one()
|
||||
MappedUsers(name=u'Bhargan Basepair',email=u'basepair@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)
|
||||
|
||||
filter_by is like filter, but takes kwargs instead of full
|
||||
clause expressions. This makes it more concise for simple
|
||||
queries like this, but you can't do complex queries like the
|
||||
or\_ above or non-equality based comparisons this way.
|
||||
|
||||
Full query documentation
|
||||
------------------------
|
||||
|
||||
Get, filter, filter_by, order_by, limit, and the rest of the
|
||||
query methods are explained in detail in
|
||||
:ref:`ormtutorial_querying`.
|
||||
|
||||
Modifying objects
|
||||
=================
|
||||
|
||||
Modifying objects is intuitive::
|
||||
|
||||
>>> user = _
|
||||
>>> user.email = 'basepair+nospam@example.edu'
|
||||
>>> db.commit()
|
||||
|
||||
(SqlSoup leverages the sophisticated SQLAlchemy unit-of-work
|
||||
code, so multiple updates to a single object will be turned into
|
||||
a single ``UPDATE`` statement when you commit.)
|
||||
|
||||
To finish covering the basics, let's insert a new loan, then
|
||||
delete it::
|
||||
|
||||
>>> book_id = db.books.filter_by(title='Regional Variation in Moss').first().id
|
||||
>>> db.loans.insert(book_id=book_id, user_name=user.name)
|
||||
MappedLoans(book_id=2,user_name=u'Bhargan Basepair',loan_date=None)
|
||||
|
||||
>>> loan = db.loans.filter_by(book_id=2, user_name='Bhargan Basepair').one()
|
||||
>>> db.delete(loan)
|
||||
>>> db.commit()
|
||||
|
||||
You can also delete rows that have not been loaded as objects.
|
||||
Let's do our insert/delete cycle once more, this time using the
|
||||
loans table's delete method. (For SQLAlchemy experts: note that
|
||||
no flush() call is required since this delete acts at the SQL
|
||||
level, not at the Mapper level.) The same where-clause
|
||||
construction rules apply here as to the select methods::
|
||||
|
||||
>>> db.loans.insert(book_id=book_id, user_name=user.name)
|
||||
MappedLoans(book_id=2,user_name=u'Bhargan Basepair',loan_date=None)
|
||||
>>> db.loans.delete(db.loans.book_id==2)
|
||||
|
||||
You can similarly update multiple rows at once. This will change the
|
||||
book_id to 1 in all loans whose book_id is 2::
|
||||
|
||||
>>> db.loans.update(db.loans.book_id==2, book_id=1)
|
||||
>>> db.loans.filter_by(book_id=1).all()
|
||||
[MappedLoans(book_id=1,user_name=u'Joe Student',
|
||||
loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
|
||||
|
||||
|
||||
Joins
|
||||
=====
|
||||
|
||||
Occasionally, you will want to pull out a lot of data from related
|
||||
tables all at once. In this situation, it is far more efficient to
|
||||
have the database perform the necessary join. (Here we do not have *a
|
||||
lot of data* but hopefully the concept is still clear.) SQLAlchemy is
|
||||
smart enough to recognize that loans has a foreign key to users, and
|
||||
uses that as the join condition automatically::
|
||||
|
||||
>>> join1 = db.join(db.users, db.loans, isouter=True)
|
||||
>>> join1.filter_by(name='Joe Student').all()
|
||||
[
|
||||
MappedJoin(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0,book_id=1,
|
||||
user_name=u'Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0))
|
||||
]
|
||||
|
||||
If you're unfortunate enough to be using MySQL with the default MyISAM
|
||||
storage engine, you'll have to specify the join condition manually,
|
||||
since MyISAM does not store foreign keys. Here's the same join again,
|
||||
with the join condition explicitly specified::
|
||||
|
||||
>>> db.join(db.users, db.loans, db.users.name==db.loans.user_name, isouter=True)
|
||||
<class 'sqlalchemy.ext.sqlsoup.MappedJoin'>
|
||||
|
||||
You can compose arbitrarily complex joins by combining Join objects
|
||||
with tables or other joins. Here we combine our first join with the
|
||||
books table::
|
||||
|
||||
>>> join2 = db.join(join1, db.books)
|
||||
>>> join2.all()
|
||||
[
|
||||
MappedJoin(name=u'Joe Student',email=u'student@example.edu',
|
||||
password=u'student',classname=None,admin=0,book_id=1,
|
||||
user_name=u'Joe Student',loan_date=datetime.datetime(2006, 7, 12, 0, 0),
|
||||
id=1,title=u'Mustards I Have Known',published_year=u'1989',
|
||||
authors=u'Jones')
|
||||
]
|
||||
|
||||
If you join tables that have an identical column name, wrap your join
|
||||
with `with_labels`, to disambiguate columns with their table name
|
||||
(.c is short for .columns)::
|
||||
|
||||
>>> db.with_labels(join1).c.keys()
|
||||
[u'users_name', u'users_email', u'users_password',
|
||||
u'users_classname', u'users_admin', u'loans_book_id',
|
||||
u'loans_user_name', u'loans_loan_date']
|
||||
|
||||
You can also join directly to a labeled object::
|
||||
|
||||
>>> labeled_loans = db.with_labels(db.loans)
|
||||
>>> db.join(db.users, labeled_loans, isouter=True).c.keys()
|
||||
[u'name', u'email', u'password', u'classname',
|
||||
u'admin', u'loans_book_id', u'loans_user_name', u'loans_loan_date']
|
||||
|
||||
|
||||
Relationships
|
||||
=============
|
||||
|
||||
You can define relationships on SqlSoup classes:
|
||||
|
||||
>>> db.users.relate('loans', db.loans)
|
||||
|
||||
These can then be used like a normal SA property:
|
||||
|
||||
>>> db.users.get('Joe Student').loans
|
||||
[MappedLoans(book_id=1,user_name=u'Joe Student',
|
||||
loan_date=datetime.datetime(2006, 7, 12, 0, 0))]
|
||||
|
||||
>>> db.users.filter(~db.users.loans.any()).all()
|
||||
[MappedUsers(name=u'Bhargan Basepair',
|
||||
email='basepair+nospam@example.edu',
|
||||
password=u'basepair',classname=None,admin=1)]
|
||||
|
||||
relate can take any options that the relationship function
|
||||
accepts in normal mapper definition:
|
||||
|
||||
>>> del db._cache['users']
|
||||
>>> db.users.relate('loans', db.loans, order_by=db.loans.loan_date, cascade='all, delete-orphan')
|
||||
|
||||
Advanced Use
|
||||
============
|
||||
|
||||
Sessions, Transations and Application Integration
|
||||
-------------------------------------------------
|
||||
|
||||
**Note:** please read and understand this section thoroughly
|
||||
before using SqlSoup in any web application.
|
||||
|
||||
SqlSoup uses a ScopedSession to provide thread-local sessions.
|
||||
You can get a reference to the current one like this::
|
||||
|
||||
>>> session = db.session
|
||||
|
||||
The default session is available at the module level in SQLSoup,
|
||||
via::
|
||||
|
||||
>>> from sqlalchemy.ext.sqlsoup import Session
|
||||
|
||||
The configuration of this session is ``autoflush=True``,
|
||||
``autocommit=False``. This means when you work with the SqlSoup
|
||||
object, you need to call ``db.commit()`` in order to have
|
||||
changes persisted. You may also call ``db.rollback()`` to roll
|
||||
things back.
|
||||
|
||||
Since the SqlSoup object's Session automatically enters into a
|
||||
transaction as soon as it's used, it is *essential* that you
|
||||
call ``commit()`` or ``rollback()`` on it when the work within a
|
||||
thread completes. This means all the guidelines for web
|
||||
application integration at :ref:`session_lifespan` must be
|
||||
followed.
|
||||
|
||||
The SqlSoup object can have any session or scoped session
|
||||
configured onto it. This is of key importance when integrating
|
||||
with existing code or frameworks such as Pylons. If your
|
||||
application already has a ``Session`` configured, pass it to
|
||||
your SqlSoup object::
|
||||
|
||||
>>> from myapplication import Session
|
||||
>>> db = SqlSoup(session=Session)
|
||||
|
||||
If the ``Session`` is configured with ``autocommit=True``, use
|
||||
``flush()`` instead of ``commit()`` to persist changes - in this
|
||||
case, the ``Session`` closes out its transaction immediately and
|
||||
no external management is needed. ``rollback()`` is also not
|
||||
available. Configuring a new SQLSoup object in "autocommit" mode
|
||||
looks like::
|
||||
|
||||
>>> from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
>>> db = SqlSoup('sqlite://', session=scoped_session(sessionmaker(autoflush=False, expire_on_commit=False, autocommit=True)))
|
||||
|
||||
|
||||
Mapping arbitrary Selectables
|
||||
-----------------------------
|
||||
|
||||
SqlSoup can map any SQLAlchemy :class:`.Selectable` with the map
|
||||
method. Let's map an :func:`.expression.select` object that uses an aggregate
|
||||
function; we'll use the SQLAlchemy :class:`.Table` that SqlSoup
|
||||
introspected as the basis. (Since we're not mapping to a simple
|
||||
table or join, we need to tell SQLAlchemy how to find the
|
||||
*primary key* which just needs to be unique within the select,
|
||||
and not necessarily correspond to a *real* PK in the database.)::
|
||||
|
||||
>>> from sqlalchemy import select, func
|
||||
>>> b = db.books._table
|
||||
>>> s = select([b.c.published_year, func.count('*').label('n')], from_obj=[b], group_by=[b.c.published_year])
|
||||
>>> s = s.alias('years_with_count')
|
||||
>>> years_with_count = db.map(s, primary_key=[s.c.published_year])
|
||||
>>> years_with_count.filter_by(published_year='1989').all()
|
||||
[MappedBooks(published_year=u'1989',n=1)]
|
||||
|
||||
Obviously if we just wanted to get a list of counts associated with
|
||||
book years once, raw SQL is going to be less work. The advantage of
|
||||
mapping a Select is reusability, both standalone and in Joins. (And if
|
||||
you go to full SQLAlchemy, you can perform mappings like this directly
|
||||
to your object models.)
|
||||
|
||||
An easy way to save mapped selectables like this is to just hang them on
|
||||
your db object::
|
||||
|
||||
>>> db.years_with_count = years_with_count
|
||||
|
||||
Python is flexible like that!
|
||||
|
||||
Raw SQL
|
||||
-------
|
||||
|
||||
SqlSoup works fine with SQLAlchemy's text construct, described
|
||||
in :ref:`sqlexpression_text`. You can also execute textual SQL
|
||||
directly using the `execute()` method, which corresponds to the
|
||||
`execute()` method on the underlying `Session`. Expressions here
|
||||
are expressed like ``text()`` constructs, using named parameters
|
||||
with colons::
|
||||
|
||||
>>> rp = db.execute('select name, email from users where name like :name order by name', name='%Bhargan%')
|
||||
>>> for name, email in rp.fetchall(): print name, email
|
||||
Bhargan Basepair basepair+nospam@example.edu
|
||||
|
||||
Or you can get at the current transaction's connection using
|
||||
`connection()`. This is the raw connection object which can
|
||||
accept any sort of SQL expression or raw SQL string passed to
|
||||
the database::
|
||||
|
||||
>>> conn = db.connection()
|
||||
>>> conn.execute("'select name, email from users where name like ? order by name'", '%Bhargan%')
|
||||
|
||||
Dynamic table names
|
||||
-------------------
|
||||
|
||||
You can load a table whose name is specified at runtime with the
|
||||
entity() method:
|
||||
|
||||
>>> tablename = 'loans'
|
||||
>>> db.entity(tablename) == db.loans
|
||||
True
|
||||
|
||||
entity() also takes an optional schema argument. If none is
|
||||
specified, the default schema is used.
|
||||
|
||||
"""
|
||||
|
||||
from sqlalchemy import Table, MetaData, join
|
||||
from sqlalchemy import schema, sql, util
|
||||
from sqlalchemy.engine.base import Engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, mapper, \
|
||||
class_mapper, relationship, session,\
|
||||
object_session
|
||||
from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE
|
||||
from sqlalchemy.exceptions import SQLAlchemyError, InvalidRequestError, ArgumentError
|
||||
from sqlalchemy.sql import expression
|
||||
|
||||
|
||||
__all__ = ['PKNotFoundError', 'SqlSoup']
|
||||
|
||||
Session = scoped_session(sessionmaker(autoflush=True, autocommit=False))
|
||||
|
||||
class AutoAdd(MapperExtension):
|
||||
def __init__(self, scoped_session):
|
||||
self.scoped_session = scoped_session
|
||||
|
||||
def instrument_class(self, mapper, class_):
|
||||
class_.__init__ = self._default__init__(mapper)
|
||||
|
||||
def _default__init__(ext, mapper):
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.iteritems():
|
||||
setattr(self, key, value)
|
||||
return __init__
|
||||
|
||||
def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
session = self.scoped_session()
|
||||
session._save_without_cascade(instance)
|
||||
return EXT_CONTINUE
|
||||
|
||||
def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
sess = object_session(instance)
|
||||
if sess:
|
||||
sess.expunge(instance)
|
||||
return EXT_CONTINUE
|
||||
|
||||
class PKNotFoundError(SQLAlchemyError):
|
||||
pass
|
||||
|
||||
def _ddl_error(cls):
|
||||
msg = 'SQLSoup can only modify mapped Tables (found: %s)' \
|
||||
% cls._table.__class__.__name__
|
||||
raise InvalidRequestError(msg)
|
||||
|
||||
# metaclass is necessary to expose class methods with getattr, e.g.
|
||||
# we want to pass db.users.select through to users._mapper.select
|
||||
class SelectableClassType(type):
|
||||
def insert(cls, **kwargs):
|
||||
_ddl_error(cls)
|
||||
|
||||
def __clause_element__(cls):
|
||||
return cls._table
|
||||
|
||||
def __getattr__(cls, attr):
|
||||
if attr == '_query':
|
||||
# called during mapper init
|
||||
raise AttributeError()
|
||||
return getattr(cls._query, attr)
|
||||
|
||||
class TableClassType(SelectableClassType):
|
||||
def insert(cls, **kwargs):
|
||||
o = cls()
|
||||
o.__dict__.update(kwargs)
|
||||
return o
|
||||
|
||||
def relate(cls, propname, *args, **kwargs):
|
||||
class_mapper(cls)._configure_property(propname, relationship(*args, **kwargs))
|
||||
|
||||
def _is_outer_join(selectable):
|
||||
if not isinstance(selectable, sql.Join):
|
||||
return False
|
||||
if selectable.isouter:
|
||||
return True
|
||||
return _is_outer_join(selectable.left) or _is_outer_join(selectable.right)
|
||||
|
||||
def _selectable_name(selectable):
|
||||
if isinstance(selectable, sql.Alias):
|
||||
return _selectable_name(selectable.element)
|
||||
elif isinstance(selectable, sql.Select):
|
||||
return ''.join(_selectable_name(s) for s in selectable.froms)
|
||||
elif isinstance(selectable, schema.Table):
|
||||
return selectable.name.capitalize()
|
||||
else:
|
||||
x = selectable.__class__.__name__
|
||||
if x[0] == '_':
|
||||
x = x[1:]
|
||||
return x
|
||||
|
||||
def _class_for_table(session, engine, selectable, base_cls, mapper_kwargs):
|
||||
selectable = expression._clause_element_as_expr(selectable)
|
||||
mapname = 'Mapped' + _selectable_name(selectable)
|
||||
# Py2K
|
||||
if isinstance(mapname, unicode):
|
||||
engine_encoding = engine.dialect.encoding
|
||||
mapname = mapname.encode(engine_encoding)
|
||||
# end Py2K
|
||||
|
||||
if isinstance(selectable, Table):
|
||||
klass = TableClassType(mapname, (base_cls,), {})
|
||||
else:
|
||||
klass = SelectableClassType(mapname, (base_cls,), {})
|
||||
|
||||
def _compare(self, o):
|
||||
L = list(self.__class__.c.keys())
|
||||
L.sort()
|
||||
t1 = [getattr(self, k) for k in L]
|
||||
try:
|
||||
t2 = [getattr(o, k) for k in L]
|
||||
except AttributeError:
|
||||
raise TypeError('unable to compare with %s' % o.__class__)
|
||||
return t1, t2
|
||||
|
||||
# python2/python3 compatible system of
|
||||
# __cmp__ - __lt__ + __eq__
|
||||
|
||||
def __lt__(self, o):
|
||||
t1, t2 = _compare(self, o)
|
||||
return t1 < t2
|
||||
|
||||
def __eq__(self, o):
|
||||
t1, t2 = _compare(self, o)
|
||||
return t1 == t2
|
||||
|
||||
def __repr__(self):
|
||||
L = ["%s=%r" % (key, getattr(self, key, ''))
|
||||
for key in self.__class__.c.keys()]
|
||||
return '%s(%s)' % (self.__class__.__name__, ','.join(L))
|
||||
|
||||
for m in ['__eq__', '__repr__', '__lt__']:
|
||||
setattr(klass, m, eval(m))
|
||||
klass._table = selectable
|
||||
klass.c = expression.ColumnCollection()
|
||||
mappr = mapper(klass,
|
||||
selectable,
|
||||
extension=AutoAdd(session),
|
||||
**mapper_kwargs)
|
||||
|
||||
for k in mappr.iterate_properties:
|
||||
klass.c[k.key] = k.columns[0]
|
||||
|
||||
klass._query = session.query_property()
|
||||
return klass
|
||||
|
||||
class SqlSoup(object):
|
||||
"""Represent an ORM-wrapped database resource."""
|
||||
|
||||
def __init__(self, engine_or_metadata, base=object, session=None):
|
||||
"""Initialize a new :class:`.SqlSoup`.
|
||||
|
||||
:param engine_or_metadata: a string database URL, :class:`.Engine`
|
||||
or :class:`.MetaData` object to associate with. If the
|
||||
argument is a :class:`.MetaData`, it should be *bound*
|
||||
to an :class:`.Engine`.
|
||||
:param base: a class which will serve as the default class for
|
||||
returned mapped classes. Defaults to ``object``.
|
||||
:param session: a :class:`.ScopedSession` or :class:`.Session` with
|
||||
which to associate ORM operations for this :class:`.SqlSoup` instance.
|
||||
If ``None``, a :class:`.ScopedSession` that's local to this
|
||||
module is used.
|
||||
|
||||
"""
|
||||
|
||||
self.session = session or Session
|
||||
self.base=base
|
||||
|
||||
if isinstance(engine_or_metadata, MetaData):
|
||||
self._metadata = engine_or_metadata
|
||||
elif isinstance(engine_or_metadata, (basestring, Engine)):
|
||||
self._metadata = MetaData(engine_or_metadata)
|
||||
else:
|
||||
raise ArgumentError("invalid engine or metadata argument %r" %
|
||||
engine_or_metadata)
|
||||
|
||||
self._cache = {}
|
||||
self.schema = None
|
||||
|
||||
@property
|
||||
def bind(self):
|
||||
"""The :class:`.Engine` associated with this :class:`.SqlSoup`."""
|
||||
return self._metadata.bind
|
||||
|
||||
engine = bind
|
||||
|
||||
def delete(self, instance):
|
||||
"""Mark an instance as deleted."""
|
||||
|
||||
self.session.delete(instance)
|
||||
|
||||
def execute(self, stmt, **params):
|
||||
"""Execute a SQL statement.
|
||||
|
||||
The statement may be a string SQL string,
|
||||
an :func:`.expression.select` construct, or an :func:`.expression.text`
|
||||
construct.
|
||||
|
||||
"""
|
||||
return self.session.execute(sql.text(stmt, bind=self.bind), **params)
|
||||
|
||||
@property
|
||||
def _underlying_session(self):
|
||||
if isinstance(self.session, session.Session):
|
||||
return self.session
|
||||
else:
|
||||
return self.session()
|
||||
|
||||
def connection(self):
|
||||
"""Return the current :class:`.Connection` in use by the current transaction."""
|
||||
|
||||
return self._underlying_session._connection_for_bind(self.bind)
|
||||
|
||||
def flush(self):
|
||||
"""Flush pending changes to the database.
|
||||
|
||||
See :meth:`.Session.flush`.
|
||||
|
||||
"""
|
||||
self.session.flush()
|
||||
|
||||
def rollback(self):
|
||||
"""Rollback the current transction.
|
||||
|
||||
See :meth:`.Session.rollback`.
|
||||
|
||||
"""
|
||||
self.session.rollback()
|
||||
|
||||
def commit(self):
|
||||
"""Commit the current transaction.
|
||||
|
||||
See :meth:`.Session.commit`.
|
||||
|
||||
"""
|
||||
self.session.commit()
|
||||
|
||||
def clear(self):
|
||||
"""Synonym for :meth:`.SqlSoup.expunge_all`."""
|
||||
|
||||
self.session.expunge_all()
|
||||
|
||||
def expunge(self, instance):
|
||||
"""Remove an instance from the :class:`.Session`.
|
||||
|
||||
See :meth:`.Session.expunge`.
|
||||
|
||||
"""
|
||||
self.session.expunge(instance)
|
||||
|
||||
def expunge_all(self):
|
||||
"""Clear all objects from the current :class:`.Session`.
|
||||
|
||||
See :meth:`.Session.expunge_all`.
|
||||
|
||||
"""
|
||||
self.session.expunge_all()
|
||||
|
||||
def map_to(self, attrname, tablename=None, selectable=None,
|
||||
schema=None, base=None, mapper_args=util.frozendict()):
|
||||
"""Configure a mapping to the given attrname.
|
||||
|
||||
This is the "master" method that can be used to create any
|
||||
configuration.
|
||||
|
||||
(new in 0.6.6)
|
||||
|
||||
:param attrname: String attribute name which will be
|
||||
established as an attribute on this :class:.`.SqlSoup`
|
||||
instance.
|
||||
:param base: a Python class which will be used as the
|
||||
base for the mapped class. If ``None``, the "base"
|
||||
argument specified by this :class:`.SqlSoup`
|
||||
instance's constructor will be used, which defaults to
|
||||
``object``.
|
||||
:param mapper_args: Dictionary of arguments which will
|
||||
be passed directly to :func:`.orm.mapper`.
|
||||
:param tablename: String name of a :class:`.Table` to be
|
||||
reflected. If a :class:`.Table` is already available,
|
||||
use the ``selectable`` argument. This argument is
|
||||
mutually exclusive versus the ``selectable`` argument.
|
||||
:param selectable: a :class:`.Table`, :class:`.Join`, or
|
||||
:class:`.Select` object which will be mapped. This
|
||||
argument is mutually exclusive versus the ``tablename``
|
||||
argument.
|
||||
:param schema: String schema name to use if the
|
||||
``tablename`` argument is present.
|
||||
|
||||
|
||||
"""
|
||||
if attrname in self._cache:
|
||||
raise InvalidRequestError(
|
||||
"Attribute '%s' is already mapped to '%s'" % (
|
||||
attrname,
|
||||
class_mapper(self._cache[attrname]).mapped_table
|
||||
))
|
||||
|
||||
if tablename is not None:
|
||||
if not isinstance(tablename, basestring):
|
||||
raise ArgumentError("'tablename' argument must be a string."
|
||||
)
|
||||
if selectable is not None:
|
||||
raise ArgumentError("'tablename' and 'selectable' "
|
||||
"arguments are mutually exclusive")
|
||||
|
||||
selectable = Table(tablename,
|
||||
self._metadata,
|
||||
autoload=True,
|
||||
autoload_with=self.bind,
|
||||
schema=schema or self.schema)
|
||||
elif schema:
|
||||
raise ArgumentError("'tablename' argument is required when "
|
||||
"using 'schema'.")
|
||||
elif selectable is not None:
|
||||
if not isinstance(selectable, expression.FromClause):
|
||||
raise ArgumentError("'selectable' argument must be a "
|
||||
"table, select, join, or other "
|
||||
"selectable construct.")
|
||||
else:
|
||||
raise ArgumentError("'tablename' or 'selectable' argument is "
|
||||
"required.")
|
||||
|
||||
if not selectable.primary_key.columns:
|
||||
if tablename:
|
||||
raise PKNotFoundError(
|
||||
"table '%s' does not have a primary "
|
||||
"key defined" % tablename)
|
||||
else:
|
||||
raise PKNotFoundError(
|
||||
"selectable '%s' does not have a primary "
|
||||
"key defined" % selectable)
|
||||
|
||||
mapped_cls = _class_for_table(
|
||||
self.session,
|
||||
self.engine,
|
||||
selectable,
|
||||
base or self.base,
|
||||
mapper_args
|
||||
)
|
||||
self._cache[attrname] = mapped_cls
|
||||
return mapped_cls
|
||||
|
||||
|
||||
def map(self, selectable, base=None, **mapper_args):
|
||||
"""Map a selectable directly.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
base for the mapped class. If ``None``, the "base"
|
||||
argument specified by this :class:`.SqlSoup`
|
||||
instance's constructor will be used, which defaults to
|
||||
``object``.
|
||||
:param mapper_args: Dictionary of arguments which will
|
||||
be passed directly to :func:`.orm.mapper`.
|
||||
|
||||
"""
|
||||
|
||||
return _class_for_table(
|
||||
self.session,
|
||||
self.engine,
|
||||
selectable,
|
||||
base or self.base,
|
||||
mapper_args
|
||||
)
|
||||
|
||||
def with_labels(self, selectable, base=None, **mapper_args):
|
||||
"""Map a selectable directly, wrapping the
|
||||
selectable in a subquery with labels.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
|
||||
:param selectable: an :func:`.expression.select` construct.
|
||||
:param base: a Python class which will be used as the
|
||||
base for the mapped class. If ``None``, the "base"
|
||||
argument specified by this :class:`.SqlSoup`
|
||||
instance's constructor will be used, which defaults to
|
||||
``object``.
|
||||
:param mapper_args: Dictionary of arguments which will
|
||||
be passed directly to :func:`.orm.mapper`.
|
||||
|
||||
"""
|
||||
|
||||
# TODO give meaningful aliases
|
||||
return self.map(
|
||||
expression._clause_element_as_expr(selectable).
|
||||
select(use_labels=True).
|
||||
alias('foo'), base=base, **mapper_args)
|
||||
|
||||
def join(self, left, right, onclause=None, isouter=False,
|
||||
base=None, **mapper_args):
|
||||
"""Create an :func:`.expression.join` and map to it.
|
||||
|
||||
The class and its mapping are not cached and will
|
||||
be discarded once dereferenced (as of 0.6.6).
|
||||
|
||||
:param left: a mapped class or table object.
|
||||
:param right: a mapped class or table object.
|
||||
:param onclause: optional "ON" clause construct..
|
||||
:param isouter: if True, the join will be an OUTER join.
|
||||
:param base: a Python class which will be used as the
|
||||
base for the mapped class. If ``None``, the "base"
|
||||
argument specified by this :class:`.SqlSoup`
|
||||
instance's constructor will be used, which defaults to
|
||||
``object``.
|
||||
:param mapper_args: Dictionary of arguments which will
|
||||
be passed directly to :func:`.orm.mapper`.
|
||||
|
||||
"""
|
||||
|
||||
j = join(left, right, onclause=onclause, isouter=isouter)
|
||||
return self.map(j, base=base, **mapper_args)
|
||||
|
||||
def entity(self, attr, schema=None):
|
||||
"""Return the named entity from this :class:`.SqlSoup`, or
|
||||
create if not present.
|
||||
|
||||
For more generalized mapping, see :meth:`.map_to`.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self._cache[attr]
|
||||
except KeyError, ke:
|
||||
return self.map_to(attr, tablename=attr, schema=schema)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self.entity(attr)
|
||||
|
||||
def __repr__(self):
|
||||
return 'SqlSoup(%r)' % self._metadata
|
||||
|
||||
Reference in New Issue
Block a user