Packages update

This commit is contained in:
Ruud
2012-02-11 16:28:06 +01:00
parent 3bbf1126c3
commit 02e01fb2d6
217 changed files with 26395 additions and 21194 deletions
+187 -161
View File
@@ -1,16 +1,16 @@
# orm/util.py
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
# Copyright (C) 2005-2012 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
import sqlalchemy.exceptions as sa_exc
from sqlalchemy import sql, util
from sqlalchemy import sql, util, event, exc as sa_exc
from sqlalchemy.sql import expression, util as sql_util, operators
from sqlalchemy.orm.interfaces import MapperExtension, EXT_CONTINUE,\
PropComparator, MapperProperty,\
AttributeExtension
PropComparator, MapperProperty
from sqlalchemy.orm import attributes, exc
import operator
mapperlib = util.importlater("sqlalchemy.orm", "mapperlib")
@@ -20,7 +20,7 @@ all_cascades = frozenset(("delete", "delete-orphan", "all", "merge",
_INSTRUMENTOR = ('mapper', 'instrumentor')
class CascadeOptions(object):
class CascadeOptions(dict):
"""Keeps track of the options sent to relationship().cascade"""
def __init__(self, arg=""):
@@ -28,69 +28,63 @@ class CascadeOptions(object):
values = set()
else:
values = set(c.strip() for c in arg.split(','))
for name in ['save-update', 'delete', 'refresh-expire',
'merge', 'expunge']:
boolean = name in values or 'all' in values
setattr(self, name.replace('-', '_'), boolean)
if boolean:
self[name] = True
self.delete_orphan = "delete-orphan" in values
self.delete = "delete" in values or "all" in values
self.save_update = "save-update" in values or "all" in values
self.merge = "merge" in values or "all" in values
self.expunge = "expunge" in values or "all" in values
self.refresh_expire = "refresh-expire" in values or "all" in values
if self.delete_orphan:
self['delete-orphan'] = True
if self.delete_orphan and not self.delete:
util.warn("The 'delete-orphan' cascade option requires "
"'delete'. This will raise an error in 0.6.")
"'delete'.")
for x in values:
if x not in all_cascades:
raise sa_exc.ArgumentError("Invalid cascade option '%s'" % x)
def __contains__(self, item):
return getattr(self, item.replace("-", "_"), False)
def __repr__(self):
return "CascadeOptions(%s)" % repr(",".join(
[x for x in ['delete', 'save_update', 'merge', 'expunge',
'delete_orphan', 'refresh-expire']
if getattr(self, x, False) is True]))
def _validator_events(desc, key, validator):
"""Runs a validation method on an attribute value to be set or appended."""
class Validator(AttributeExtension):
"""Runs a validation method on an attribute value to be set or appended.
def append(state, value, initiator):
return validator(state.obj(), key, value)
The Validator class is used by the :func:`~sqlalchemy.orm.validates`
decorator, and direct access is usually not needed.
def set_(state, value, oldvalue, initiator):
return validator(state.obj(), key, value)
"""
event.listen(desc, 'append', append, raw=True, retval=True)
event.listen(desc, 'set', set_, raw=True, retval=True)
def __init__(self, key, validator):
"""Construct a new Validator.
key - name of the attribute to be validated;
will be passed as the second argument to
the validation method (the first is the object instance itself).
validator - an function or instance method which accepts
three arguments; an instance (usually just 'self' for a method),
the key name of the attribute, and the value. The function should
return the same value given, unless it wishes to modify it.
"""
self.key = key
self.validator = validator
def append(self, state, value, initiator):
return self.validator(state.obj(), self.key, value)
def set(self, state, value, oldvalue, initiator):
return self.validator(state.obj(), self.key, value)
def polymorphic_union(table_map, typecolname, aliasname='p_union'):
def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=True):
"""Create a ``UNION`` statement used by a polymorphic mapper.
See :ref:`concrete_inheritance` for an example of how
this is used.
:param table_map: mapping of polymorphic identities to
:class:`.Table` objects.
:param typecolname: string name of a "discriminator" column, which will be
derived from the query, producing the polymorphic identity for each row. If
``None``, no polymorphic discriminator is generated.
:param aliasname: name of the :func:`~sqlalchemy.sql.expression.alias()`
construct generated.
:param cast_nulls: if True, non-existent columns, which are represented as labeled
NULLs, will be passed into CAST. This is a legacy behavior that is problematic
on some backends such as Oracle - in which case it can be set to False.
"""
colnames = set()
colnames = util.OrderedSet()
colnamemaps = {}
types = {}
for key in table_map.keys():
@@ -113,7 +107,10 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union'):
try:
return colnamemaps[table][name]
except KeyError:
return sql.cast(sql.null(), types[name]).label(name)
if cast_nulls:
return sql.cast(sql.null(), types[name]).label(name)
else:
return sql.type_coerce(sql.null(), types[name]).label(name)
result = []
for type, table in table_map.iteritems():
@@ -184,78 +181,6 @@ def identity_key(*args, **kwargs):
mapper = object_mapper(instance)
return mapper.identity_key_from_instance(instance)
class ExtensionCarrier(dict):
"""Fronts an ordered collection of MapperExtension objects.
Bundles multiple MapperExtensions into a unified callable unit,
encapsulating ordering, looping and EXT_CONTINUE logic. The
ExtensionCarrier implements the MapperExtension interface, e.g.::
carrier.after_insert(...args...)
The dictionary interface provides containment for implemented
method names mapped to a callable which executes that method
for participating extensions.
"""
interface = set(method for method in dir(MapperExtension)
if not method.startswith('_'))
def __init__(self, extensions=None):
self._extensions = []
for ext in extensions or ():
self.append(ext)
def copy(self):
return ExtensionCarrier(self._extensions)
def push(self, extension):
"""Insert a MapperExtension at the beginning of the collection."""
self._register(extension)
self._extensions.insert(0, extension)
def append(self, extension):
"""Append a MapperExtension at the end of the collection."""
self._register(extension)
self._extensions.append(extension)
def __iter__(self):
"""Iterate over MapperExtensions in the collection."""
return iter(self._extensions)
def _register(self, extension):
"""Register callable fronts for overridden interface methods."""
for method in self.interface.difference(self):
impl = getattr(extension, method, None)
if impl and impl is not getattr(MapperExtension, method):
self[method] = self._create_do(method)
def _create_do(self, method):
"""Return a closure that loops over impls of the named method."""
def _do(*args, **kwargs):
for ext in self._extensions:
ret = getattr(ext, method)(*args, **kwargs)
if ret is not EXT_CONTINUE:
return ret
else:
return EXT_CONTINUE
_do.__name__ = method
return _do
@staticmethod
def _pass(*args, **kwargs):
return EXT_CONTINUE
def __getattr__(self, key):
"""Delegate MapperExtension methods to bundled fronts."""
if key not in self.interface:
raise AttributeError(key)
return self.get(key, self._pass)
class ORMAdapter(sql_util.ColumnAdapter):
"""Extends ColumnAdapter to accept ORM entities.
@@ -296,15 +221,61 @@ class AliasedClass(object):
session.query(User, user_alias).\\
join((user_alias, User.id > user_alias.id)).\\
filter(User.name==user_alias.name)
The resulting object is an instance of :class:`.AliasedClass`, however
it implements a ``__getattribute__()`` scheme which will proxy attribute
access to that of the ORM class being aliased. All classmethods
on the mapped entity should also be available here, including
hybrids created with the :ref:`hybrids_toplevel` extension,
which will receive the :class:`.AliasedClass` as the "class" argument
when classmethods are called.
:param cls: ORM mapped entity which will be "wrapped" around an alias.
:param alias: a selectable, such as an :func:`.alias` or :func:`.select`
construct, which will be rendered in place of the mapped table of the
ORM entity. If left as ``None``, an ordinary :class:`.Alias` of the
ORM entity's mapped table will be generated.
:param name: A name which will be applied both to the :class:`.Alias`
if one is generated, as well as the name present in the "named tuple"
returned by the :class:`.Query` object when results are returned.
:param adapt_on_names: if True, more liberal "matching" will be used when
mapping the mapped columns of the ORM entity to those of the given selectable -
a name-based match will be performed if the given selectable doesn't
otherwise have a column that corresponds to one on the entity. The
use case for this is when associating an entity with some derived
selectable such as one that uses aggregate functions::
class UnitPrice(Base):
__tablename__ = 'unit_price'
...
unit_id = Column(Integer)
price = Column(Numeric)
aggregated_unit_price = Session.query(
func.sum(UnitPrice.price).label('price')
).group_by(UnitPrice.unit_id).subquery()
aggregated_unit_price = aliased(UnitPrice, alias=aggregated_unit_price, adapt_on_names=True)
Above, functions on ``aggregated_unit_price`` which
refer to ``.price`` will return the
``fund.sum(UnitPrice.price).label('price')`` column,
as it is matched on the name "price". Ordinarily, the "price" function wouldn't
have any "column correspondence" to the actual ``UnitPrice.price`` column
as it is not a proxy of the original.
``adapt_on_names`` is new in 0.7.3.
"""
def __init__(self, cls, alias=None, name=None):
def __init__(self, cls, alias=None, name=None, adapt_on_names=False):
self.__mapper = _class_to_mapper(cls)
self.__target = self.__mapper.class_
self.__adapt_on_names = adapt_on_names
if alias is None:
alias = self.__mapper._with_polymorphic_selectable.alias()
alias = self.__mapper._with_polymorphic_selectable.alias(name=name)
self.__adapter = sql_util.ClauseAdapter(alias,
equivalents=self.__mapper._equivalent_columns)
equivalents=self.__mapper._equivalent_columns,
adapt_on_names=self.__adapt_on_names)
self.__alias = alias
# used to assign a name to the RowTuple object
# returned by Query.
@@ -315,15 +286,18 @@ class AliasedClass(object):
return {
'mapper':self.__mapper,
'alias':self.__alias,
'name':self._sa_label_name
'name':self._sa_label_name,
'adapt_on_names':self.__adapt_on_names,
}
def __setstate__(self, state):
self.__mapper = state['mapper']
self.__target = self.__mapper.class_
self.__adapt_on_names = state['adapt_on_names']
alias = state['alias']
self.__adapter = sql_util.ClauseAdapter(alias,
equivalents=self.__mapper._equivalent_columns)
equivalents=self.__mapper._equivalent_columns,
adapt_on_names=self.__adapt_on_names)
self.__alias = alias
name = state['name']
self._sa_label_name = name
@@ -336,23 +310,15 @@ class AliasedClass(object):
'parentmapper':self.__mapper}
)
def __adapt_prop(self, prop):
existing = getattr(self.__target, prop.key)
def __adapt_prop(self, existing, key):
comparator = existing.comparator.adapted(self.__adapt_element)
queryattr = attributes.QueryableAttribute(prop.key,
queryattr = attributes.QueryableAttribute(self, key,
impl=existing.impl, parententity=self, comparator=comparator)
setattr(self, prop.key, queryattr)
setattr(self, key, queryattr)
return queryattr
def __getattr__(self, key):
if self.__mapper.has_property(key):
return self.__adapt_prop(
self.__mapper.get_property(
key, _compile_mappers=False
)
)
for base in self.__target.__mro__:
try:
attr = object.__getattribute__(base, key)
@@ -363,14 +329,19 @@ class AliasedClass(object):
else:
raise AttributeError(key)
if hasattr(attr, 'func_code'):
if isinstance(attr, attributes.QueryableAttribute):
return self.__adapt_prop(attr, key)
elif hasattr(attr, 'func_code'):
is_method = getattr(self.__target, key, None)
if is_method and is_method.im_self is not None:
return util.types.MethodType(attr.im_func, self, self)
else:
return None
elif hasattr(attr, '__get__'):
return attr.__get__(None, self)
ret = attr.__get__(None, self)
if isinstance(ret, PropComparator):
return ret.adapted(self.__adapt_element)
return ret
else:
return attr
@@ -378,6 +349,14 @@ class AliasedClass(object):
return '<AliasedClass at 0x%x; %s>' % (
id(self), self.__target.__name__)
def aliased(element, alias=None, name=None, adapt_on_names=False):
if isinstance(element, expression.FromClause):
if adapt_on_names:
raise sa_exc.ArgumentError("adapt_on_names only applies to ORM elements")
return element.alias(name)
else:
return AliasedClass(element, alias=alias, name=name, adapt_on_names=adapt_on_names)
def _orm_annotate(element, exclude=None):
"""Deep copy the given ClauseElement, annotating each element with the
"_orm_adapt" flag.
@@ -453,29 +432,52 @@ class _ORMJoin(expression.Join):
def join(left, right, onclause=None, isouter=False, join_to_left=True):
"""Produce an inner join between left and right clauses.
:func:`.orm.join` is an extension to the core join interface
provided by :func:`.sql.expression.join()`, where the
left and right selectables may be not only core selectable
objects such as :class:`.Table`, but also mapped classes or
:class:`.AliasedClass` instances. The "on" clause can
be a SQL expression, or an attribute or string name
referencing a configured :func:`.relationship`.
In addition to the interface provided by
:func:`~sqlalchemy.sql.expression.join()`, left and right may be mapped
classes or AliasedClass instances. The onclause may be a
string name of a relationship(), or a class-bound descriptor
representing a relationship.
join_to_left indicates to attempt aliasing the ON clause,
``join_to_left`` indicates to attempt aliasing the ON clause,
in whatever form it is passed, to the selectable
passed as the left side. If False, the onclause
is used as is.
:func:`.orm.join` is not commonly needed in modern usage,
as its functionality is encapsulated within that of the
:meth:`.Query.join` method, which features a
significant amount of automation beyond :func:`.orm.join`
by itself. Explicit usage of :func:`.orm.join`
with :class:`.Query` involves usage of the
:meth:`.Query.select_from` method, as in::
from sqlalchemy.orm import join
session.query(User).\\
select_from(join(User, Address, User.addresses)).\\
filter(Address.email_address=='foo@bar.com')
In modern SQLAlchemy the above join can be written more
succinctly as::
session.query(User).\\
join(User.addresses).\\
filter(Address.email_address=='foo@bar.com')
See :meth:`.Query.join` for information on modern usage
of ORM level joins.
"""
return _ORMJoin(left, right, onclause, isouter, join_to_left)
def outerjoin(left, right, onclause=None, join_to_left=True):
"""Produce a left outer join between left and right clauses.
In addition to the interface provided by
:func:`~sqlalchemy.sql.expression.outerjoin()`, left and right may be
mapped classes or AliasedClass instances. The onclause may be a string
name of a relationship(), or a class-bound descriptor representing a
relationship.
This is the "outer join" version of the :func:`.orm.join` function,
featuring the same behavior except that an OUTER JOIN is generated.
See that function's documentation for other usage details.
"""
return _ORMJoin(left, right, onclause, True, join_to_left)
@@ -508,7 +510,7 @@ def with_parent(instance, prop):
"""
if isinstance(prop, basestring):
mapper = object_mapper(instance)
prop = mapper.get_property(prop, resolve_synonyms=True)
prop = getattr(mapper.class_, prop).property
elif isinstance(prop, attributes.QueryableAttribute):
prop = prop.property
@@ -544,8 +546,8 @@ def _entity_info(entity, compile=True):
else:
return None, entity, False
if compile:
mapper = mapper.compile()
if compile and mapperlib.module._new_mappers:
mapperlib.configure_mappers()
return mapper, mapper._with_polymorphic_selectable, False
def _entity_descriptor(entity, key):
@@ -586,8 +588,7 @@ def _attr_as_key(attr):
def _is_aliased_class(entity):
return isinstance(entity, AliasedClass)
def _state_mapper(state):
return state.manager.mapper
_state_mapper = util.dottedgetter('manager.mapper')
def object_mapper(instance):
"""Given an object, return the primary Mapper associated with the object
@@ -605,9 +606,12 @@ def object_mapper(instance):
raise exc.UnmappedInstanceError(instance)
def class_mapper(class_, compile=True):
"""Given a class, return the primary Mapper associated with the key.
"""Given a class, return the primary :class:`.Mapper` associated
with the key.
Raises UnmappedClassError if no mapping is configured.
Raises :class:`.UnmappedClassError` if no mapping is configured
on the given class, or :class:`.ArgumentError` if a non-class
object is passed.
"""
@@ -616,10 +620,12 @@ def class_mapper(class_, compile=True):
mapper = class_manager.mapper
except exc.NO_STATE:
if not isinstance(class_, type):
raise sa_exc.ArgumentError("Class object expected, got '%r'." % class_)
raise exc.UnmappedClassError(class_)
if compile:
mapper = mapper.compile()
if compile and mapperlib.module._new_mappers:
mapperlib.configure_mappers()
return mapper
def _class_to_mapper(class_or_mapper, compile=True):
@@ -637,16 +643,18 @@ def _class_to_mapper(class_or_mapper, compile=True):
else:
raise exc.UnmappedClassError(class_or_mapper)
if compile:
return mapper.compile()
else:
return mapper
if compile and mapperlib.module._new_mappers:
mapperlib.configure_mappers()
return mapper
def has_identity(object):
state = attributes.instance_state(object)
return state.has_identity
def _is_mapped_class(cls):
"""Return True if the given object is a mapped class,
:class:`.Mapper`, or :class:`.AliasedClass`."""
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
return True
if isinstance(cls, expression.ClauseElement):
@@ -656,6 +664,16 @@ def _is_mapped_class(cls):
return manager and _INSTRUMENTOR in manager.info
return False
def _mapper_or_none(cls):
"""Return the :class:`.Mapper` for the given class or None if the
class is not mapped."""
manager = attributes.manager_of_class(cls)
if manager is not None and _INSTRUMENTOR in manager.info:
return manager.info[_INSTRUMENTOR]
else:
return None
def instance_str(instance):
"""Return a string describing an instance."""
@@ -669,6 +687,14 @@ def state_str(state):
else:
return '<%s at 0x%x>' % (state.class_.__name__, id(state.obj()))
def state_class_str(state):
"""Return a string describing an instance's class via its InstanceState."""
if state is None:
return "None"
else:
return '<%s>' % (state.class_.__name__, )
def attribute_str(instance, attribute):
return instance_str(instance) + "." + attribute