Update SQLAlchemy
This commit is contained in:
+73
-60
@@ -1,5 +1,5 @@
|
||||
# orm/util.py
|
||||
# Copyright (C) 2005-2012 the SQLAlchemy authors and contributors <see AUTHORS file>
|
||||
# Copyright (C) 2005-2013 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
|
||||
@@ -30,15 +30,15 @@ class CascadeOptions(frozenset):
|
||||
|
||||
def __new__(cls, arg):
|
||||
values = set([
|
||||
c for c
|
||||
c for c
|
||||
in re.split('\s*,\s*', arg or "")
|
||||
if c
|
||||
])
|
||||
|
||||
if values.difference(cls._allowed_cascades):
|
||||
raise sa_exc.ArgumentError(
|
||||
"Invalid cascade option(s): %s" %
|
||||
", ".join([repr(x) for x in
|
||||
"Invalid cascade option(s): %s" %
|
||||
", ".join([repr(x) for x in
|
||||
sorted(
|
||||
values.difference(cls._allowed_cascades)
|
||||
)])
|
||||
@@ -68,30 +68,42 @@ class CascadeOptions(frozenset):
|
||||
",".join([x for x in sorted(self)])
|
||||
)
|
||||
|
||||
def _validator_events(desc, key, validator):
|
||||
def _validator_events(desc, key, validator, include_removes):
|
||||
"""Runs a validation method on an attribute value to be set or appended."""
|
||||
|
||||
def append(state, value, initiator):
|
||||
return validator(state.obj(), key, value)
|
||||
if include_removes:
|
||||
def append(state, value, initiator):
|
||||
return validator(state.obj(), key, value, False)
|
||||
|
||||
def set_(state, value, oldvalue, initiator):
|
||||
return validator(state.obj(), key, value)
|
||||
def set_(state, value, oldvalue, initiator):
|
||||
return validator(state.obj(), key, value, False)
|
||||
|
||||
def remove(state, value, initiator):
|
||||
validator(state.obj(), key, value, True)
|
||||
else:
|
||||
def append(state, value, initiator):
|
||||
return validator(state.obj(), key, value)
|
||||
|
||||
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)
|
||||
if include_removes:
|
||||
event.listen(desc, "remove", remove, raw=True, retval=True)
|
||||
|
||||
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
|
||||
|
||||
:param table_map: mapping of polymorphic identities to
|
||||
:class:`.Table` objects.
|
||||
:param typecolname: string name of a "discriminator" column, which will be
|
||||
: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()`
|
||||
: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
|
||||
@@ -105,7 +117,7 @@ def polymorphic_union(table_map, typecolname, aliasname='p_union', cast_nulls=Tr
|
||||
for key in table_map.keys():
|
||||
table = table_map[key]
|
||||
|
||||
# mysql doesnt like selecting from a select;
|
||||
# mysql doesnt like selecting from a select;
|
||||
# make it an alias of the select
|
||||
if isinstance(table, sql.Select):
|
||||
table = table.alias()
|
||||
@@ -187,7 +199,7 @@ def identity_key(*args, **kwargs):
|
||||
% ", ".join(kwargs.keys()))
|
||||
mapper = class_mapper(class_)
|
||||
if "ident" in locals():
|
||||
return mapper.identity_key_from_primary_key(ident)
|
||||
return mapper.identity_key_from_primary_key(util.to_list(ident))
|
||||
return mapper.identity_key_from_row(row)
|
||||
instance = kwargs.pop("instance")
|
||||
if kwargs:
|
||||
@@ -203,14 +215,14 @@ class ORMAdapter(sql_util.ColumnAdapter):
|
||||
and the AliasedClass if any is referenced.
|
||||
|
||||
"""
|
||||
def __init__(self, entity, equivalents=None,
|
||||
def __init__(self, entity, equivalents=None,
|
||||
chain_to=None, adapt_required=False):
|
||||
self.mapper, selectable, is_aliased_class = _entity_info(entity)
|
||||
if is_aliased_class:
|
||||
self.aliased_class = entity
|
||||
else:
|
||||
self.aliased_class = None
|
||||
sql_util.ColumnAdapter.__init__(self, selectable,
|
||||
sql_util.ColumnAdapter.__init__(self, selectable,
|
||||
equivalents, chain_to,
|
||||
adapt_required=adapt_required)
|
||||
|
||||
@@ -236,51 +248,51 @@ 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
|
||||
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. 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
|
||||
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.
|
||||
|
||||
|
||||
.. versionadded:: 0.7.3
|
||||
|
||||
"""
|
||||
def __init__(self, cls, alias=None, name=None, adapt_on_names=False):
|
||||
self.__mapper = _class_to_mapper(cls)
|
||||
@@ -299,8 +311,8 @@ class AliasedClass(object):
|
||||
|
||||
def __getstate__(self):
|
||||
return {
|
||||
'mapper':self.__mapper,
|
||||
'alias':self.__alias,
|
||||
'mapper':self.__mapper,
|
||||
'alias':self.__alias,
|
||||
'name':self._sa_label_name,
|
||||
'adapt_on_names':self.__adapt_on_names,
|
||||
}
|
||||
@@ -321,7 +333,7 @@ class AliasedClass(object):
|
||||
def __adapt_element(self, elem):
|
||||
return self.__adapter.traverse(elem).\
|
||||
_annotate({
|
||||
'parententity': self,
|
||||
'parententity': self,
|
||||
'parentmapper':self.__mapper}
|
||||
)
|
||||
|
||||
@@ -388,7 +400,7 @@ class _ORMJoin(expression.Join):
|
||||
|
||||
__visit_name__ = expression.Join.__visit_name__
|
||||
|
||||
def __init__(self, left, right, onclause=None,
|
||||
def __init__(self, left, right, onclause=None,
|
||||
isouter=False, join_to_left=True):
|
||||
adapt_from = None
|
||||
|
||||
@@ -447,7 +459,7 @@ 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
|
||||
@@ -460,30 +472,30 @@ def join(left, right, onclause=None, isouter=False, join_to_left=True):
|
||||
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
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
@@ -504,23 +516,24 @@ def with_parent(instance, prop):
|
||||
|
||||
The SQL rendered is the same as that rendered when a lazy loader
|
||||
would fire off from the given parent on that attribute, meaning
|
||||
that the appropriate state is taken from the parent object in
|
||||
that the appropriate state is taken from the parent object in
|
||||
Python without the need to render joins to the parent table
|
||||
in the rendered statement.
|
||||
|
||||
As of 0.6.4, this method accepts parent instances in all
|
||||
persistence states, including transient, persistent, and detached.
|
||||
Only the requisite primary key/foreign key attributes need to
|
||||
be populated. Previous versions didn't work with transient
|
||||
instances.
|
||||
.. versionchanged:: 0.6.4
|
||||
This method accepts parent instances in all
|
||||
persistence states, including transient, persistent, and detached.
|
||||
Only the requisite primary key/foreign key attributes need to
|
||||
be populated. Previous versions didn't work with transient
|
||||
instances.
|
||||
|
||||
:param instance:
|
||||
An instance which has some :func:`.relationship`.
|
||||
|
||||
:param property:
|
||||
String property name, or class-bound attribute, which indicates
|
||||
what relationship from the instance should be used to reconcile the
|
||||
parent/child relationship.
|
||||
what relationship from the instance should be used to reconcile the
|
||||
parent/child relationship.
|
||||
|
||||
"""
|
||||
if isinstance(prop, basestring):
|
||||
@@ -529,8 +542,8 @@ def with_parent(instance, prop):
|
||||
elif isinstance(prop, attributes.QueryableAttribute):
|
||||
prop = prop.property
|
||||
|
||||
return prop.compare(operators.eq,
|
||||
instance,
|
||||
return prop.compare(operators.eq,
|
||||
instance,
|
||||
value_is_parent=True)
|
||||
|
||||
|
||||
@@ -584,7 +597,7 @@ def _entity_descriptor(entity, key):
|
||||
return getattr(entity, key)
|
||||
except AttributeError:
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Entity '%s' has no property '%s'" %
|
||||
"Entity '%s' has no property '%s'" %
|
||||
(description, key)
|
||||
)
|
||||
|
||||
@@ -626,7 +639,7 @@ def object_mapper(instance):
|
||||
raise exc.UnmappedInstanceError(instance)
|
||||
|
||||
def class_mapper(class_, compile=True):
|
||||
"""Given a class, return the primary :class:`.Mapper` associated
|
||||
"""Given a class, return the primary :class:`.Mapper` associated
|
||||
with the key.
|
||||
|
||||
Raises :class:`.UnmappedClassError` if no mapping is configured
|
||||
@@ -640,8 +653,8 @@ 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_)
|
||||
if not isinstance(class_, type):
|
||||
raise sa_exc.ArgumentError("Class object expected, got '%r'." % class_)
|
||||
raise exc.UnmappedClassError(class_)
|
||||
|
||||
if compile and mapperlib.module._new_mappers:
|
||||
@@ -672,7 +685,7 @@ def has_identity(object):
|
||||
return state.has_identity
|
||||
|
||||
def _is_mapped_class(cls):
|
||||
"""Return True if the given object is a mapped class,
|
||||
"""Return True if the given object is a mapped class,
|
||||
:class:`.Mapper`, or :class:`.AliasedClass`."""
|
||||
|
||||
if isinstance(cls, (AliasedClass, mapperlib.Mapper)):
|
||||
@@ -685,7 +698,7 @@ def _is_mapped_class(cls):
|
||||
return False
|
||||
|
||||
def _mapper_or_none(cls):
|
||||
"""Return the :class:`.Mapper` for the given class or None if the
|
||||
"""Return the :class:`.Mapper` for the given class or None if the
|
||||
class is not mapped."""
|
||||
|
||||
manager = attributes.manager_of_class(cls)
|
||||
|
||||
Reference in New Issue
Block a user