Updated SqlAlchemy + the new files
This commit is contained in:
@@ -0,0 +1,540 @@
|
||||
# orm/base.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
"""Constants and rudimental functions used throughout the ORM.
|
||||
|
||||
"""
|
||||
|
||||
from .. import util, inspection, exc as sa_exc
|
||||
from ..sql import expression
|
||||
from . import exc
|
||||
import operator
|
||||
|
||||
PASSIVE_NO_RESULT = util.symbol(
|
||||
'PASSIVE_NO_RESULT',
|
||||
"""Symbol returned by a loader callable or other attribute/history
|
||||
retrieval operation when a value could not be determined, based
|
||||
on loader callable flags.
|
||||
"""
|
||||
)
|
||||
|
||||
ATTR_WAS_SET = util.symbol(
|
||||
'ATTR_WAS_SET',
|
||||
"""Symbol returned by a loader callable to indicate the
|
||||
retrieved value, or values, were assigned to their attributes
|
||||
on the target object.
|
||||
"""
|
||||
)
|
||||
|
||||
ATTR_EMPTY = util.symbol(
|
||||
'ATTR_EMPTY',
|
||||
"""Symbol used internally to indicate an attribute had no callable."""
|
||||
)
|
||||
|
||||
NO_VALUE = util.symbol(
|
||||
'NO_VALUE',
|
||||
"""Symbol which may be placed as the 'previous' value of an attribute,
|
||||
indicating no value was loaded for an attribute when it was modified,
|
||||
and flags indicated we were not to load it.
|
||||
"""
|
||||
)
|
||||
|
||||
NEVER_SET = util.symbol(
|
||||
'NEVER_SET',
|
||||
"""Symbol which may be placed as the 'previous' value of an attribute
|
||||
indicating that the attribute had not been assigned to previously.
|
||||
"""
|
||||
)
|
||||
|
||||
NO_CHANGE = util.symbol(
|
||||
"NO_CHANGE",
|
||||
"""No callables or SQL should be emitted on attribute access
|
||||
and no state should change
|
||||
""", canonical=0
|
||||
)
|
||||
|
||||
CALLABLES_OK = util.symbol(
|
||||
"CALLABLES_OK",
|
||||
"""Loader callables can be fired off if a value
|
||||
is not present.
|
||||
""", canonical=1
|
||||
)
|
||||
|
||||
SQL_OK = util.symbol(
|
||||
"SQL_OK",
|
||||
"""Loader callables can emit SQL at least on scalar value attributes.""",
|
||||
canonical=2
|
||||
)
|
||||
|
||||
RELATED_OBJECT_OK = util.symbol(
|
||||
"RELATED_OBJECT_OK",
|
||||
"""Callables can use SQL to load related objects as well
|
||||
as scalar value attributes.
|
||||
""", canonical=4
|
||||
)
|
||||
|
||||
INIT_OK = util.symbol(
|
||||
"INIT_OK",
|
||||
"""Attributes should be initialized with a blank
|
||||
value (None or an empty collection) upon get, if no other
|
||||
value can be obtained.
|
||||
""", canonical=8
|
||||
)
|
||||
|
||||
NON_PERSISTENT_OK = util.symbol(
|
||||
"NON_PERSISTENT_OK",
|
||||
"""Callables can be emitted if the parent is not persistent.""",
|
||||
canonical=16
|
||||
)
|
||||
|
||||
LOAD_AGAINST_COMMITTED = util.symbol(
|
||||
"LOAD_AGAINST_COMMITTED",
|
||||
"""Callables should use committed values as primary/foreign keys during a
|
||||
load.
|
||||
""", canonical=32
|
||||
)
|
||||
|
||||
NO_AUTOFLUSH = util.symbol(
|
||||
"NO_AUTOFLUSH",
|
||||
"""Loader callables should disable autoflush.""",
|
||||
canonical=64
|
||||
)
|
||||
|
||||
# pre-packaged sets of flags used as inputs
|
||||
PASSIVE_OFF = util.symbol(
|
||||
"PASSIVE_OFF",
|
||||
"Callables can be emitted in all cases.",
|
||||
canonical=(RELATED_OBJECT_OK | NON_PERSISTENT_OK |
|
||||
INIT_OK | CALLABLES_OK | SQL_OK)
|
||||
)
|
||||
PASSIVE_RETURN_NEVER_SET = util.symbol(
|
||||
"PASSIVE_RETURN_NEVER_SET",
|
||||
"""PASSIVE_OFF ^ INIT_OK""",
|
||||
canonical=PASSIVE_OFF ^ INIT_OK
|
||||
)
|
||||
PASSIVE_NO_INITIALIZE = util.symbol(
|
||||
"PASSIVE_NO_INITIALIZE",
|
||||
"PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK",
|
||||
canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK
|
||||
)
|
||||
PASSIVE_NO_FETCH = util.symbol(
|
||||
"PASSIVE_NO_FETCH",
|
||||
"PASSIVE_OFF ^ SQL_OK",
|
||||
canonical=PASSIVE_OFF ^ SQL_OK
|
||||
)
|
||||
PASSIVE_NO_FETCH_RELATED = util.symbol(
|
||||
"PASSIVE_NO_FETCH_RELATED",
|
||||
"PASSIVE_OFF ^ RELATED_OBJECT_OK",
|
||||
canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK
|
||||
)
|
||||
PASSIVE_ONLY_PERSISTENT = util.symbol(
|
||||
"PASSIVE_ONLY_PERSISTENT",
|
||||
"PASSIVE_OFF ^ NON_PERSISTENT_OK",
|
||||
canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK
|
||||
)
|
||||
|
||||
DEFAULT_MANAGER_ATTR = '_sa_class_manager'
|
||||
DEFAULT_STATE_ATTR = '_sa_instance_state'
|
||||
_INSTRUMENTOR = ('mapper', 'instrumentor')
|
||||
|
||||
EXT_CONTINUE = util.symbol('EXT_CONTINUE')
|
||||
EXT_STOP = util.symbol('EXT_STOP')
|
||||
|
||||
ONETOMANY = util.symbol(
|
||||
'ONETOMANY',
|
||||
"""Indicates the one-to-many direction for a :func:`.relationship`.
|
||||
|
||||
This symbol is typically used by the internals but may be exposed within
|
||||
certain API features.
|
||||
|
||||
""")
|
||||
|
||||
MANYTOONE = util.symbol(
|
||||
'MANYTOONE',
|
||||
"""Indicates the many-to-one direction for a :func:`.relationship`.
|
||||
|
||||
This symbol is typically used by the internals but may be exposed within
|
||||
certain API features.
|
||||
|
||||
""")
|
||||
|
||||
MANYTOMANY = util.symbol(
|
||||
'MANYTOMANY',
|
||||
"""Indicates the many-to-many direction for a :func:`.relationship`.
|
||||
|
||||
This symbol is typically used by the internals but may be exposed within
|
||||
certain API features.
|
||||
|
||||
""")
|
||||
|
||||
NOT_EXTENSION = util.symbol(
|
||||
'NOT_EXTENSION',
|
||||
"""Symbol indicating an :class:`InspectionAttr` that's
|
||||
not part of sqlalchemy.ext.
|
||||
|
||||
Is assigned to the :attr:`.InspectionAttr.extension_type`
|
||||
attibute.
|
||||
|
||||
""")
|
||||
|
||||
_never_set = frozenset([NEVER_SET])
|
||||
|
||||
_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
|
||||
|
||||
_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
|
||||
|
||||
_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
|
||||
|
||||
|
||||
def _generative(*assertions):
|
||||
"""Mark a method as generative, e.g. method-chained."""
|
||||
|
||||
@util.decorator
|
||||
def generate(fn, *args, **kw):
|
||||
self = args[0]._clone()
|
||||
for assertion in assertions:
|
||||
assertion(self, fn.__name__)
|
||||
fn(self, *args[1:], **kw)
|
||||
return self
|
||||
return generate
|
||||
|
||||
|
||||
# these can be replaced by sqlalchemy.ext.instrumentation
|
||||
# if augmented class instrumentation is enabled.
|
||||
def manager_of_class(cls):
|
||||
return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None)
|
||||
|
||||
instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
|
||||
|
||||
instance_dict = operator.attrgetter('__dict__')
|
||||
|
||||
|
||||
def instance_str(instance):
|
||||
"""Return a string describing an instance."""
|
||||
|
||||
return state_str(instance_state(instance))
|
||||
|
||||
|
||||
def state_str(state):
|
||||
"""Return a string describing an instance via its InstanceState."""
|
||||
|
||||
if state is None:
|
||||
return "None"
|
||||
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
|
||||
|
||||
|
||||
def state_attribute_str(state, attribute):
|
||||
return state_str(state) + "." + attribute
|
||||
|
||||
|
||||
def object_mapper(instance):
|
||||
"""Given an object, return the primary Mapper associated with the object
|
||||
instance.
|
||||
|
||||
Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
|
||||
if no mapping is configured.
|
||||
|
||||
This function is available via the inspection system as::
|
||||
|
||||
inspect(instance).mapper
|
||||
|
||||
Using the inspection system will raise
|
||||
:class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
|
||||
not part of a mapping.
|
||||
|
||||
"""
|
||||
return object_state(instance).mapper
|
||||
|
||||
|
||||
def object_state(instance):
|
||||
"""Given an object, return the :class:`.InstanceState`
|
||||
associated with the object.
|
||||
|
||||
Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
|
||||
if no mapping is configured.
|
||||
|
||||
Equivalent functionality is available via the :func:`.inspect`
|
||||
function as::
|
||||
|
||||
inspect(instance)
|
||||
|
||||
Using the inspection system will raise
|
||||
:class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
|
||||
not part of a mapping.
|
||||
|
||||
"""
|
||||
state = _inspect_mapped_object(instance)
|
||||
if state is None:
|
||||
raise exc.UnmappedInstanceError(instance)
|
||||
else:
|
||||
return state
|
||||
|
||||
|
||||
@inspection._inspects(object)
|
||||
def _inspect_mapped_object(instance):
|
||||
try:
|
||||
return instance_state(instance)
|
||||
# TODO: whats the py-2/3 syntax to catch two
|
||||
# different kinds of exceptions at once ?
|
||||
except exc.UnmappedClassError:
|
||||
return None
|
||||
except exc.NO_STATE:
|
||||
return None
|
||||
|
||||
|
||||
def _class_to_mapper(class_or_mapper):
|
||||
insp = inspection.inspect(class_or_mapper, False)
|
||||
if insp is not None:
|
||||
return insp.mapper
|
||||
else:
|
||||
raise exc.UnmappedClassError(class_or_mapper)
|
||||
|
||||
|
||||
def _mapper_or_none(entity):
|
||||
"""Return the :class:`.Mapper` for the given class or None if the
|
||||
class is not mapped.
|
||||
"""
|
||||
|
||||
insp = inspection.inspect(entity, False)
|
||||
if insp is not None:
|
||||
return insp.mapper
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _is_mapped_class(entity):
|
||||
"""Return True if the given object is a mapped class,
|
||||
:class:`.Mapper`, or :class:`.AliasedClass`.
|
||||
"""
|
||||
|
||||
insp = inspection.inspect(entity, False)
|
||||
return insp is not None and \
|
||||
not insp.is_clause_element and \
|
||||
(
|
||||
insp.is_mapper or insp.is_aliased_class
|
||||
)
|
||||
|
||||
|
||||
def _attr_as_key(attr):
|
||||
if hasattr(attr, 'key'):
|
||||
return attr.key
|
||||
else:
|
||||
return expression._column_as_key(attr)
|
||||
|
||||
|
||||
def _orm_columns(entity):
|
||||
insp = inspection.inspect(entity, False)
|
||||
if hasattr(insp, 'selectable') and hasattr(insp.selectable, 'c'):
|
||||
return [c for c in insp.selectable.c]
|
||||
else:
|
||||
return [entity]
|
||||
|
||||
|
||||
def _is_aliased_class(entity):
|
||||
insp = inspection.inspect(entity, False)
|
||||
return insp is not None and \
|
||||
getattr(insp, "is_aliased_class", False)
|
||||
|
||||
|
||||
def _entity_descriptor(entity, key):
|
||||
"""Return a class attribute given an entity and string name.
|
||||
|
||||
May return :class:`.InstrumentedAttribute` or user-defined
|
||||
attribute.
|
||||
|
||||
"""
|
||||
insp = inspection.inspect(entity)
|
||||
if insp.is_selectable:
|
||||
description = entity
|
||||
entity = insp.c
|
||||
elif insp.is_aliased_class:
|
||||
entity = insp.entity
|
||||
description = entity
|
||||
elif hasattr(insp, "mapper"):
|
||||
description = entity = insp.mapper.class_
|
||||
else:
|
||||
description = entity
|
||||
|
||||
try:
|
||||
return getattr(entity, key)
|
||||
except AttributeError:
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Entity '%s' has no property '%s'" %
|
||||
(description, key)
|
||||
)
|
||||
|
||||
_state_mapper = util.dottedgetter('manager.mapper')
|
||||
|
||||
|
||||
@inspection._inspects(type)
|
||||
def _inspect_mapped_class(class_, configure=False):
|
||||
try:
|
||||
class_manager = manager_of_class(class_)
|
||||
if not class_manager.is_mapped:
|
||||
return None
|
||||
mapper = class_manager.mapper
|
||||
except exc.NO_STATE:
|
||||
return None
|
||||
else:
|
||||
if configure and mapper._new_mappers:
|
||||
mapper._configure_all()
|
||||
return mapper
|
||||
|
||||
|
||||
def class_mapper(class_, configure=True):
|
||||
"""Given a class, return the primary :class:`.Mapper` associated
|
||||
with the key.
|
||||
|
||||
Raises :exc:`.UnmappedClassError` if no mapping is configured
|
||||
on the given class, or :exc:`.ArgumentError` if a non-class
|
||||
object is passed.
|
||||
|
||||
Equivalent functionality is available via the :func:`.inspect`
|
||||
function as::
|
||||
|
||||
inspect(some_mapped_class)
|
||||
|
||||
Using the inspection system will raise
|
||||
:class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
|
||||
|
||||
"""
|
||||
mapper = _inspect_mapped_class(class_, configure=configure)
|
||||
if mapper is None:
|
||||
if not isinstance(class_, type):
|
||||
raise sa_exc.ArgumentError(
|
||||
"Class object expected, got '%r'." % (class_, ))
|
||||
raise exc.UnmappedClassError(class_)
|
||||
else:
|
||||
return mapper
|
||||
|
||||
|
||||
class InspectionAttr(object):
|
||||
"""A base class applied to all ORM objects that can be returned
|
||||
by the :func:`.inspect` function.
|
||||
|
||||
The attributes defined here allow the usage of simple boolean
|
||||
checks to test basic facts about the object returned.
|
||||
|
||||
While the boolean checks here are basically the same as using
|
||||
the Python isinstance() function, the flags here can be used without
|
||||
the need to import all of these classes, and also such that
|
||||
the SQLAlchemy class system can change while leaving the flags
|
||||
here intact for forwards-compatibility.
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
is_selectable = False
|
||||
"""Return True if this object is an instance of :class:`.Selectable`."""
|
||||
|
||||
is_aliased_class = False
|
||||
"""True if this object is an instance of :class:`.AliasedClass`."""
|
||||
|
||||
is_instance = False
|
||||
"""True if this object is an instance of :class:`.InstanceState`."""
|
||||
|
||||
is_mapper = False
|
||||
"""True if this object is an instance of :class:`.Mapper`."""
|
||||
|
||||
is_property = False
|
||||
"""True if this object is an instance of :class:`.MapperProperty`."""
|
||||
|
||||
is_attribute = False
|
||||
"""True if this object is a Python :term:`descriptor`.
|
||||
|
||||
This can refer to one of many types. Usually a
|
||||
:class:`.QueryableAttribute` which handles attributes events on behalf
|
||||
of a :class:`.MapperProperty`. But can also be an extension type
|
||||
such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
|
||||
The :attr:`.InspectionAttr.extension_type` will refer to a constant
|
||||
identifying the specific subtype.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.Mapper.all_orm_descriptors`
|
||||
|
||||
"""
|
||||
|
||||
is_clause_element = False
|
||||
"""True if this object is an instance of :class:`.ClauseElement`."""
|
||||
|
||||
extension_type = NOT_EXTENSION
|
||||
"""The extension type, if any.
|
||||
Defaults to :data:`.interfaces.NOT_EXTENSION`
|
||||
|
||||
.. versionadded:: 0.8.0
|
||||
|
||||
.. seealso::
|
||||
|
||||
:data:`.HYBRID_METHOD`
|
||||
|
||||
:data:`.HYBRID_PROPERTY`
|
||||
|
||||
:data:`.ASSOCIATION_PROXY`
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class InspectionAttrInfo(InspectionAttr):
|
||||
"""Adds the ``.info`` attribute to :class:`.InspectionAttr`.
|
||||
|
||||
The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo`
|
||||
is that the former is compatible as a mixin for classes that specify
|
||||
``__slots__``; this is essentially an implementation artifact.
|
||||
|
||||
"""
|
||||
|
||||
@util.memoized_property
|
||||
def info(self):
|
||||
"""Info dictionary associated with the object, allowing user-defined
|
||||
data to be associated with this :class:`.InspectionAttr`.
|
||||
|
||||
The dictionary is generated when first accessed. Alternatively,
|
||||
it can be specified as a constructor argument to the
|
||||
:func:`.column_property`, :func:`.relationship`, or :func:`.composite`
|
||||
functions.
|
||||
|
||||
.. versionadded:: 0.8 Added support for .info to all
|
||||
:class:`.MapperProperty` subclasses.
|
||||
|
||||
.. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also
|
||||
available on extension types via the
|
||||
:attr:`.InspectionAttrInfo.info` attribute, so that it can apply
|
||||
to a wider variety of ORM and extension constructs.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:attr:`.QueryableAttribute.info`
|
||||
|
||||
:attr:`.SchemaItem.info`
|
||||
|
||||
"""
|
||||
return {}
|
||||
|
||||
|
||||
class _MappedAttribute(object):
|
||||
"""Mixin for attributes which should be replaced by mapper-assigned
|
||||
attributes.
|
||||
|
||||
"""
|
||||
__slots__ = ()
|
||||
@@ -0,0 +1,487 @@
|
||||
# orm/deprecated_interfaces.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
from .. import event, util
|
||||
from .interfaces import EXT_CONTINUE
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
|
||||
class MapperExtension(object):
|
||||
"""Base implementation for :class:`.Mapper` event hooks.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.MapperExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.MapperEvents`.
|
||||
|
||||
New extension classes subclass :class:`.MapperExtension` and are specified
|
||||
using the ``extension`` mapper() argument, which is a single
|
||||
:class:`.MapperExtension` or a list of such::
|
||||
|
||||
from sqlalchemy.orm.interfaces import MapperExtension
|
||||
|
||||
class MyExtension(MapperExtension):
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
print "instance %s before insert !" % instance
|
||||
|
||||
m = mapper(User, users_table, extension=MyExtension())
|
||||
|
||||
A single mapper can maintain a chain of ``MapperExtension``
|
||||
objects. When a particular mapping event occurs, the
|
||||
corresponding method on each ``MapperExtension`` is invoked
|
||||
serially, and each method has the ability to halt the chain
|
||||
from proceeding further::
|
||||
|
||||
m = mapper(User, users_table, extension=[ext1, ext2, ext3])
|
||||
|
||||
Each ``MapperExtension`` method returns the symbol
|
||||
EXT_CONTINUE by default. This symbol generally means "move
|
||||
to the next ``MapperExtension`` for processing". For methods
|
||||
that return objects like translated rows or new object
|
||||
instances, EXT_CONTINUE means the result of the method
|
||||
should be ignored. In some cases it's required for a
|
||||
default mapper activity to be performed, such as adding a
|
||||
new instance to a result list.
|
||||
|
||||
The symbol EXT_STOP has significance within a chain
|
||||
of ``MapperExtension`` objects that the chain will be stopped
|
||||
when this symbol is returned. Like EXT_CONTINUE, it also
|
||||
has additional significance in some cases that a default
|
||||
mapper activity will not be performed.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_instrument_class(cls, self, listener):
|
||||
cls._adapt_listener_methods(self, listener, ('instrument_class',))
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
cls._adapt_listener_methods(
|
||||
self, listener,
|
||||
(
|
||||
'init_instance',
|
||||
'init_failed',
|
||||
'reconstruct_instance',
|
||||
'before_insert',
|
||||
'after_insert',
|
||||
'before_update',
|
||||
'after_update',
|
||||
'before_delete',
|
||||
'after_delete'
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener_methods(cls, self, listener, methods):
|
||||
|
||||
for meth in methods:
|
||||
me_meth = getattr(MapperExtension, meth)
|
||||
ls_meth = getattr(listener, meth)
|
||||
|
||||
if not util.methods_equivalent(me_meth, ls_meth):
|
||||
if meth == 'reconstruct_instance':
|
||||
def go(ls_meth):
|
||||
def reconstruct(instance, ctx):
|
||||
ls_meth(self, instance)
|
||||
return reconstruct
|
||||
event.listen(self.class_manager, 'load',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
elif meth == 'init_instance':
|
||||
def go(ls_meth):
|
||||
def init_instance(instance, args, kwargs):
|
||||
ls_meth(self, self.class_,
|
||||
self.class_manager.original_init,
|
||||
instance, args, kwargs)
|
||||
return init_instance
|
||||
event.listen(self.class_manager, 'init',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
elif meth == 'init_failed':
|
||||
def go(ls_meth):
|
||||
def init_failed(instance, args, kwargs):
|
||||
util.warn_exception(
|
||||
ls_meth, self, self.class_,
|
||||
self.class_manager.original_init,
|
||||
instance, args, kwargs)
|
||||
|
||||
return init_failed
|
||||
event.listen(self.class_manager, 'init_failure',
|
||||
go(ls_meth), raw=False, propagate=True)
|
||||
else:
|
||||
event.listen(self, "%s" % meth, ls_meth,
|
||||
raw=False, retval=True, propagate=True)
|
||||
|
||||
def instrument_class(self, mapper, class_):
|
||||
"""Receive a class when the mapper is first constructed, and has
|
||||
applied instrumentation to the mapped class.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def init_instance(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
"""Receive an instance when its constructor is called.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def init_failed(self, mapper, class_, oldinit, instance, args, kwargs):
|
||||
"""Receive an instance when its constructor has been called,
|
||||
and raised an exception.
|
||||
|
||||
This method is only called during a userland construction of
|
||||
an object. It is not called when an object is loaded from the
|
||||
database.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def reconstruct_instance(self, mapper, instance):
|
||||
"""Receive an object instance after it has been created via
|
||||
``__new__``, and after initial attribute population has
|
||||
occurred.
|
||||
|
||||
This typically occurs when the instance is created based on
|
||||
incoming result rows, and is only called once for that
|
||||
instance's lifetime.
|
||||
|
||||
Note that during a result-row load, this method is called upon
|
||||
the first row received for this instance. Note that some
|
||||
attributes and collections may or may not be loaded or even
|
||||
initialized, depending on what's present in the result rows.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_insert(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is inserted
|
||||
into its table.
|
||||
|
||||
This is a good place to set up primary key values and such
|
||||
that aren't handled otherwise.
|
||||
|
||||
Column-based attributes can be modified within this method
|
||||
which will result in the new value being inserted. However
|
||||
*no* changes to the overall flush plan can be made, and
|
||||
manipulation of the ``Session`` will not have the desired effect.
|
||||
To manipulate the ``Session`` within an extension, use
|
||||
``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_insert(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is inserted.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_update(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is updated.
|
||||
|
||||
Note that this method is called for all instances that are marked as
|
||||
"dirty", even those which have no net changes to their column-based
|
||||
attributes. An object is marked as dirty when any of its column-based
|
||||
attributes have a "set attribute" operation called or when any of its
|
||||
collections are modified. If, at update time, no column-based
|
||||
attributes have any net changes, no UPDATE statement will be issued.
|
||||
This means that an instance being sent to before_update is *not* a
|
||||
guarantee that an UPDATE statement will be issued (although you can
|
||||
affect the outcome here).
|
||||
|
||||
To detect if the column-based attributes on the object have net
|
||||
changes, and will therefore generate an UPDATE statement, use
|
||||
``object_session(instance).is_modified(instance,
|
||||
include_collections=False)``.
|
||||
|
||||
Column-based attributes can be modified within this method
|
||||
which will result in the new value being updated. However
|
||||
*no* changes to the overall flush plan can be made, and
|
||||
manipulation of the ``Session`` will not have the desired effect.
|
||||
To manipulate the ``Session`` within an extension, use
|
||||
``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_update(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is updated.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def before_delete(self, mapper, connection, instance):
|
||||
"""Receive an object instance before that instance is deleted.
|
||||
|
||||
Note that *no* changes to the overall flush plan can be made
|
||||
here; and manipulation of the ``Session`` will not have the
|
||||
desired effect. To manipulate the ``Session`` within an
|
||||
extension, use ``SessionExtension``.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
def after_delete(self, mapper, connection, instance):
|
||||
"""Receive an object instance after that instance is deleted.
|
||||
|
||||
The return value is only significant within the ``MapperExtension``
|
||||
chain; the parent mapper's behavior isn't modified by this method.
|
||||
|
||||
"""
|
||||
|
||||
return EXT_CONTINUE
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
|
||||
class SessionExtension(object):
|
||||
|
||||
"""Base implementation for :class:`.Session` event hooks.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.SessionExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.SessionEvents`.
|
||||
|
||||
Subclasses may be installed into a :class:`.Session` (or
|
||||
:class:`.sessionmaker`) using the ``extension`` keyword
|
||||
argument::
|
||||
|
||||
from sqlalchemy.orm.interfaces import SessionExtension
|
||||
|
||||
class MySessionExtension(SessionExtension):
|
||||
def before_commit(self, session):
|
||||
print "before commit!"
|
||||
|
||||
Session = sessionmaker(extension=MySessionExtension())
|
||||
|
||||
The same :class:`.SessionExtension` instance can be used
|
||||
with any number of sessions.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
for meth in [
|
||||
'before_commit',
|
||||
'after_commit',
|
||||
'after_rollback',
|
||||
'before_flush',
|
||||
'after_flush',
|
||||
'after_flush_postexec',
|
||||
'after_begin',
|
||||
'after_attach',
|
||||
'after_bulk_update',
|
||||
'after_bulk_delete',
|
||||
]:
|
||||
me_meth = getattr(SessionExtension, meth)
|
||||
ls_meth = getattr(listener, meth)
|
||||
|
||||
if not util.methods_equivalent(me_meth, ls_meth):
|
||||
event.listen(self, meth, getattr(listener, meth))
|
||||
|
||||
def before_commit(self, session):
|
||||
"""Execute right before commit is called.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def after_commit(self, session):
|
||||
"""Execute after a commit has occurred.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def after_rollback(self, session):
|
||||
"""Execute after a rollback has occurred.
|
||||
|
||||
Note that this may not be per-flush if a longer running
|
||||
transaction is ongoing."""
|
||||
|
||||
def before_flush(self, session, flush_context, instances):
|
||||
"""Execute before flush process has started.
|
||||
|
||||
`instances` is an optional list of objects which were passed to
|
||||
the ``flush()`` method. """
|
||||
|
||||
def after_flush(self, session, flush_context):
|
||||
"""Execute after flush has completed, but before commit has been
|
||||
called.
|
||||
|
||||
Note that the session's state is still in pre-flush, i.e. 'new',
|
||||
'dirty', and 'deleted' lists still show pre-flush state as well
|
||||
as the history settings on instance attributes."""
|
||||
|
||||
def after_flush_postexec(self, session, flush_context):
|
||||
"""Execute after flush has completed, and after the post-exec
|
||||
state occurs.
|
||||
|
||||
This will be when the 'new', 'dirty', and 'deleted' lists are in
|
||||
their final state. An actual commit() may or may not have
|
||||
occurred, depending on whether or not the flush started its own
|
||||
transaction or participated in a larger transaction. """
|
||||
|
||||
def after_begin(self, session, transaction, connection):
|
||||
"""Execute after a transaction is begun on a connection
|
||||
|
||||
`transaction` is the SessionTransaction. This method is called
|
||||
after an engine level transaction is begun on a connection. """
|
||||
|
||||
def after_attach(self, session, instance):
|
||||
"""Execute after an instance is attached to a session.
|
||||
|
||||
This is called after an add, delete or merge. """
|
||||
|
||||
def after_bulk_update(self, session, query, query_context, result):
|
||||
"""Execute after a bulk update operation to the session.
|
||||
|
||||
This is called after a session.query(...).update()
|
||||
|
||||
`query` is the query object that this update operation was
|
||||
called on. `query_context` was the query context object.
|
||||
`result` is the result object returned from the bulk operation.
|
||||
"""
|
||||
|
||||
def after_bulk_delete(self, session, query, query_context, result):
|
||||
"""Execute after a bulk delete operation to the session.
|
||||
|
||||
This is called after a session.query(...).delete()
|
||||
|
||||
`query` is the query object that this delete operation was
|
||||
called on. `query_context` was the query context object.
|
||||
`result` is the result object returned from the bulk operation.
|
||||
"""
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces")
|
||||
class AttributeExtension(object):
|
||||
"""Base implementation for :class:`.AttributeImpl` event hooks, events
|
||||
that fire upon attribute mutations in user code.
|
||||
|
||||
.. note::
|
||||
|
||||
:class:`.AttributeExtension` is deprecated. Please
|
||||
refer to :func:`.event.listen` as well as
|
||||
:class:`.AttributeEvents`.
|
||||
|
||||
:class:`.AttributeExtension` is used to listen for set,
|
||||
remove, and append events on individual mapped attributes.
|
||||
It is established on an individual mapped attribute using
|
||||
the `extension` argument, available on
|
||||
:func:`.column_property`, :func:`.relationship`, and
|
||||
others::
|
||||
|
||||
from sqlalchemy.orm.interfaces import AttributeExtension
|
||||
from sqlalchemy.orm import mapper, relationship, column_property
|
||||
|
||||
class MyAttrExt(AttributeExtension):
|
||||
def append(self, state, value, initiator):
|
||||
print "append event !"
|
||||
return value
|
||||
|
||||
def set(self, state, value, oldvalue, initiator):
|
||||
print "set event !"
|
||||
return value
|
||||
|
||||
mapper(SomeClass, sometable, properties={
|
||||
'foo':column_property(sometable.c.foo, extension=MyAttrExt()),
|
||||
'bar':relationship(Bar, extension=MyAttrExt())
|
||||
})
|
||||
|
||||
Note that the :class:`.AttributeExtension` methods
|
||||
:meth:`~.AttributeExtension.append` and
|
||||
:meth:`~.AttributeExtension.set` need to return the
|
||||
``value`` parameter. The returned value is used as the
|
||||
effective value, and allows the extension to change what is
|
||||
ultimately persisted.
|
||||
|
||||
AttributeExtension is assembled within the descriptors associated
|
||||
with a mapped class.
|
||||
|
||||
"""
|
||||
|
||||
active_history = True
|
||||
"""indicates that the set() method would like to receive the 'old' value,
|
||||
even if it means firing lazy callables.
|
||||
|
||||
Note that ``active_history`` can also be set directly via
|
||||
:func:`.column_property` and :func:`.relationship`.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _adapt_listener(cls, self, listener):
|
||||
event.listen(self, 'append', listener.append,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
event.listen(self, 'remove', listener.remove,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
event.listen(self, 'set', listener.set,
|
||||
active_history=listener.active_history,
|
||||
raw=True, retval=True)
|
||||
|
||||
def append(self, state, value, initiator):
|
||||
"""Receive a collection append event.
|
||||
|
||||
The returned value will be used as the actual value to be
|
||||
appended.
|
||||
|
||||
"""
|
||||
return value
|
||||
|
||||
def remove(self, state, value, initiator):
|
||||
"""Receive a remove event.
|
||||
|
||||
No return value is defined.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def set(self, state, value, oldvalue, initiator):
|
||||
"""Receive a set event.
|
||||
|
||||
The returned value will be used as the actual value to be
|
||||
set.
|
||||
|
||||
"""
|
||||
return value
|
||||
@@ -0,0 +1,699 @@
|
||||
# orm/descriptor_props.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
"""Descriptor properties are more "auxiliary" properties
|
||||
that exist as configurational elements, but don't participate
|
||||
as actively in the load/persist ORM loop.
|
||||
|
||||
"""
|
||||
|
||||
from .interfaces import MapperProperty, PropComparator
|
||||
from .util import _none_set
|
||||
from . import attributes
|
||||
from .. import util, sql, exc as sa_exc, event, schema
|
||||
from ..sql import expression
|
||||
from . import properties
|
||||
from . import query
|
||||
|
||||
|
||||
class DescriptorProperty(MapperProperty):
|
||||
""":class:`.MapperProperty` which proxies access to a
|
||||
user-defined descriptor."""
|
||||
|
||||
doc = None
|
||||
|
||||
def instrument_class(self, mapper):
|
||||
prop = self
|
||||
|
||||
class _ProxyImpl(object):
|
||||
accepts_scalar_loader = False
|
||||
expire_missing = True
|
||||
collection = False
|
||||
|
||||
def __init__(self, key):
|
||||
self.key = key
|
||||
|
||||
if hasattr(prop, 'get_history'):
|
||||
def get_history(self, state, dict_,
|
||||
passive=attributes.PASSIVE_OFF):
|
||||
return prop.get_history(state, dict_, passive)
|
||||
|
||||
if self.descriptor is None:
|
||||
desc = getattr(mapper.class_, self.key, None)
|
||||
if mapper._is_userland_descriptor(desc):
|
||||
self.descriptor = desc
|
||||
|
||||
if self.descriptor is None:
|
||||
def fset(obj, value):
|
||||
setattr(obj, self.name, value)
|
||||
|
||||
def fdel(obj):
|
||||
delattr(obj, self.name)
|
||||
|
||||
def fget(obj):
|
||||
return getattr(obj, self.name)
|
||||
|
||||
self.descriptor = property(
|
||||
fget=fget,
|
||||
fset=fset,
|
||||
fdel=fdel,
|
||||
)
|
||||
|
||||
proxy_attr = attributes.create_proxied_attribute(
|
||||
self.descriptor)(
|
||||
self.parent.class_,
|
||||
self.key,
|
||||
self.descriptor,
|
||||
lambda: self._comparator_factory(mapper),
|
||||
doc=self.doc,
|
||||
original_property=self
|
||||
)
|
||||
proxy_attr.impl = _ProxyImpl(self.key)
|
||||
mapper.class_manager.instrument_attribute(self.key, proxy_attr)
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
|
||||
class CompositeProperty(DescriptorProperty):
|
||||
"""Defines a "composite" mapped attribute, representing a collection
|
||||
of columns as one attribute.
|
||||
|
||||
:class:`.CompositeProperty` is constructed using the :func:`.composite`
|
||||
function.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`mapper_composite`
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, class_, *attrs, **kwargs):
|
||||
r"""Return a composite column-based property for use with a Mapper.
|
||||
|
||||
See the mapping documentation section :ref:`mapper_composite` for a
|
||||
full usage example.
|
||||
|
||||
The :class:`.MapperProperty` returned by :func:`.composite`
|
||||
is the :class:`.CompositeProperty`.
|
||||
|
||||
:param class\_:
|
||||
The "composite type" class.
|
||||
|
||||
:param \*cols:
|
||||
List of Column objects to be mapped.
|
||||
|
||||
:param active_history=False:
|
||||
When ``True``, indicates that the "previous" value for a
|
||||
scalar attribute should be loaded when replaced, if not
|
||||
already loaded. See the same flag on :func:`.column_property`.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
This flag specifically becomes meaningful
|
||||
- previously it was a placeholder.
|
||||
|
||||
:param group:
|
||||
A group name for this property when marked as deferred.
|
||||
|
||||
:param deferred:
|
||||
When True, the column property is "deferred", meaning that it does
|
||||
not load immediately, and is instead loaded when the attribute is
|
||||
first accessed on an instance. See also
|
||||
:func:`~sqlalchemy.orm.deferred`.
|
||||
|
||||
:param comparator_factory: a class which extends
|
||||
:class:`.CompositeProperty.Comparator` which provides custom SQL
|
||||
clause generation for comparison operations.
|
||||
|
||||
:param doc:
|
||||
optional string that will be applied as the doc on the
|
||||
class-bound descriptor.
|
||||
|
||||
:param info: Optional data dictionary which will be populated into the
|
||||
:attr:`.MapperProperty.info` attribute of this object.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
:param extension:
|
||||
an :class:`.AttributeExtension` instance,
|
||||
or list of extensions, which will be prepended to the list of
|
||||
attribute listeners for the resulting descriptor placed on the
|
||||
class. **Deprecated.** Please see :class:`.AttributeEvents`.
|
||||
|
||||
"""
|
||||
super(CompositeProperty, self).__init__()
|
||||
|
||||
self.attrs = attrs
|
||||
self.composite_class = class_
|
||||
self.active_history = kwargs.get('active_history', False)
|
||||
self.deferred = kwargs.get('deferred', False)
|
||||
self.group = kwargs.get('group', None)
|
||||
self.comparator_factory = kwargs.pop('comparator_factory',
|
||||
self.__class__.Comparator)
|
||||
if 'info' in kwargs:
|
||||
self.info = kwargs.pop('info')
|
||||
|
||||
util.set_creation_order(self)
|
||||
self._create_descriptor()
|
||||
|
||||
def instrument_class(self, mapper):
|
||||
super(CompositeProperty, self).instrument_class(mapper)
|
||||
self._setup_event_handlers()
|
||||
|
||||
def do_init(self):
|
||||
"""Initialization which occurs after the :class:`.CompositeProperty`
|
||||
has been associated with its parent mapper.
|
||||
|
||||
"""
|
||||
self._setup_arguments_on_columns()
|
||||
|
||||
def _create_descriptor(self):
|
||||
"""Create the Python descriptor that will serve as
|
||||
the access point on instances of the mapped class.
|
||||
|
||||
"""
|
||||
|
||||
def fget(instance):
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
state = attributes.instance_state(instance)
|
||||
|
||||
if self.key not in dict_:
|
||||
# key not present. Iterate through related
|
||||
# attributes, retrieve their values. This
|
||||
# ensures they all load.
|
||||
values = [
|
||||
getattr(instance, key)
|
||||
for key in self._attribute_keys
|
||||
]
|
||||
|
||||
# current expected behavior here is that the composite is
|
||||
# created on access if the object is persistent or if
|
||||
# col attributes have non-None. This would be better
|
||||
# if the composite were created unconditionally,
|
||||
# but that would be a behavioral change.
|
||||
if self.key not in dict_ and (
|
||||
state.key is not None or
|
||||
not _none_set.issuperset(values)
|
||||
):
|
||||
dict_[self.key] = self.composite_class(*values)
|
||||
state.manager.dispatch.refresh(state, None, [self.key])
|
||||
|
||||
return dict_.get(self.key, None)
|
||||
|
||||
def fset(instance, value):
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
state = attributes.instance_state(instance)
|
||||
attr = state.manager[self.key]
|
||||
previous = dict_.get(self.key, attributes.NO_VALUE)
|
||||
for fn in attr.dispatch.set:
|
||||
value = fn(state, value, previous, attr.impl)
|
||||
dict_[self.key] = value
|
||||
if value is None:
|
||||
for key in self._attribute_keys:
|
||||
setattr(instance, key, None)
|
||||
else:
|
||||
for key, value in zip(
|
||||
self._attribute_keys,
|
||||
value.__composite_values__()):
|
||||
setattr(instance, key, value)
|
||||
|
||||
def fdel(instance):
|
||||
state = attributes.instance_state(instance)
|
||||
dict_ = attributes.instance_dict(instance)
|
||||
previous = dict_.pop(self.key, attributes.NO_VALUE)
|
||||
attr = state.manager[self.key]
|
||||
attr.dispatch.remove(state, previous, attr.impl)
|
||||
for key in self._attribute_keys:
|
||||
setattr(instance, key, None)
|
||||
|
||||
self.descriptor = property(fget, fset, fdel)
|
||||
|
||||
@util.memoized_property
|
||||
def _comparable_elements(self):
|
||||
return [
|
||||
getattr(self.parent.class_, prop.key)
|
||||
for prop in self.props
|
||||
]
|
||||
|
||||
@util.memoized_property
|
||||
def props(self):
|
||||
props = []
|
||||
for attr in self.attrs:
|
||||
if isinstance(attr, str):
|
||||
prop = self.parent.get_property(
|
||||
attr, _configure_mappers=False)
|
||||
elif isinstance(attr, schema.Column):
|
||||
prop = self.parent._columntoproperty[attr]
|
||||
elif isinstance(attr, attributes.InstrumentedAttribute):
|
||||
prop = attr.property
|
||||
else:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Composite expects Column objects or mapped "
|
||||
"attributes/attribute names as arguments, got: %r"
|
||||
% (attr,))
|
||||
props.append(prop)
|
||||
return props
|
||||
|
||||
@property
|
||||
def columns(self):
|
||||
return [a for a in self.attrs if isinstance(a, schema.Column)]
|
||||
|
||||
def _setup_arguments_on_columns(self):
|
||||
"""Propagate configuration arguments made on this composite
|
||||
to the target columns, for those that apply.
|
||||
|
||||
"""
|
||||
for prop in self.props:
|
||||
prop.active_history = self.active_history
|
||||
if self.deferred:
|
||||
prop.deferred = self.deferred
|
||||
prop.strategy_key = (
|
||||
("deferred", True),
|
||||
("instrument", True))
|
||||
prop.group = self.group
|
||||
|
||||
def _setup_event_handlers(self):
|
||||
"""Establish events that populate/expire the composite attribute."""
|
||||
|
||||
def load_handler(state, *args):
|
||||
dict_ = state.dict
|
||||
|
||||
if self.key in dict_:
|
||||
return
|
||||
|
||||
# if column elements aren't loaded, skip.
|
||||
# __get__() will initiate a load for those
|
||||
# columns
|
||||
for k in self._attribute_keys:
|
||||
if k not in dict_:
|
||||
return
|
||||
|
||||
# assert self.key not in dict_
|
||||
dict_[self.key] = self.composite_class(
|
||||
*[state.dict[key] for key in
|
||||
self._attribute_keys]
|
||||
)
|
||||
|
||||
def expire_handler(state, keys):
|
||||
if keys is None or set(self._attribute_keys).intersection(keys):
|
||||
state.dict.pop(self.key, None)
|
||||
|
||||
def insert_update_handler(mapper, connection, state):
|
||||
"""After an insert or update, some columns may be expired due
|
||||
to server side defaults, or re-populated due to client side
|
||||
defaults. Pop out the composite value here so that it
|
||||
recreates.
|
||||
|
||||
"""
|
||||
|
||||
state.dict.pop(self.key, None)
|
||||
|
||||
event.listen(self.parent, 'after_insert',
|
||||
insert_update_handler, raw=True)
|
||||
event.listen(self.parent, 'after_update',
|
||||
insert_update_handler, raw=True)
|
||||
event.listen(self.parent, 'load',
|
||||
load_handler, raw=True, propagate=True)
|
||||
event.listen(self.parent, 'refresh',
|
||||
load_handler, raw=True, propagate=True)
|
||||
event.listen(self.parent, 'expire',
|
||||
expire_handler, raw=True, propagate=True)
|
||||
|
||||
# TODO: need a deserialize hook here
|
||||
|
||||
@util.memoized_property
|
||||
def _attribute_keys(self):
|
||||
return [
|
||||
prop.key for prop in self.props
|
||||
]
|
||||
|
||||
def get_history(self, state, dict_, passive=attributes.PASSIVE_OFF):
|
||||
"""Provided for userland code that uses attributes.get_history()."""
|
||||
|
||||
added = []
|
||||
deleted = []
|
||||
|
||||
has_history = False
|
||||
for prop in self.props:
|
||||
key = prop.key
|
||||
hist = state.manager[key].impl.get_history(state, dict_)
|
||||
if hist.has_changes():
|
||||
has_history = True
|
||||
|
||||
non_deleted = hist.non_deleted()
|
||||
if non_deleted:
|
||||
added.extend(non_deleted)
|
||||
else:
|
||||
added.append(None)
|
||||
if hist.deleted:
|
||||
deleted.extend(hist.deleted)
|
||||
else:
|
||||
deleted.append(None)
|
||||
|
||||
if has_history:
|
||||
return attributes.History(
|
||||
[self.composite_class(*added)],
|
||||
(),
|
||||
[self.composite_class(*deleted)]
|
||||
)
|
||||
else:
|
||||
return attributes.History(
|
||||
(), [self.composite_class(*added)], ()
|
||||
)
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
return self.comparator_factory(self, mapper)
|
||||
|
||||
class CompositeBundle(query.Bundle):
|
||||
def __init__(self, property, expr):
|
||||
self.property = property
|
||||
super(CompositeProperty.CompositeBundle, self).__init__(
|
||||
property.key, *expr)
|
||||
|
||||
def create_row_processor(self, query, procs, labels):
|
||||
def proc(row):
|
||||
return self.property.composite_class(
|
||||
*[proc(row) for proc in procs])
|
||||
return proc
|
||||
|
||||
class Comparator(PropComparator):
|
||||
"""Produce boolean, comparison, and other operators for
|
||||
:class:`.CompositeProperty` attributes.
|
||||
|
||||
See the example in :ref:`composite_operations` for an overview
|
||||
of usage , as well as the documentation for :class:`.PropComparator`.
|
||||
|
||||
See also:
|
||||
|
||||
:class:`.PropComparator`
|
||||
|
||||
:class:`.ColumnOperators`
|
||||
|
||||
:ref:`types_operators`
|
||||
|
||||
:attr:`.TypeEngine.comparator_factory`
|
||||
|
||||
"""
|
||||
|
||||
__hash__ = None
|
||||
|
||||
@property
|
||||
def clauses(self):
|
||||
return self.__clause_element__()
|
||||
|
||||
def __clause_element__(self):
|
||||
return expression.ClauseList(
|
||||
group=False, *self._comparable_elements)
|
||||
|
||||
def _query_clause_element(self):
|
||||
return CompositeProperty.CompositeBundle(
|
||||
self.prop, self.__clause_element__())
|
||||
|
||||
@util.memoized_property
|
||||
def _comparable_elements(self):
|
||||
if self._adapt_to_entity:
|
||||
return [
|
||||
getattr(
|
||||
self._adapt_to_entity.entity,
|
||||
prop.key
|
||||
) for prop in self.prop._comparable_elements
|
||||
]
|
||||
else:
|
||||
return self.prop._comparable_elements
|
||||
|
||||
def __eq__(self, other):
|
||||
if other is None:
|
||||
values = [None] * len(self.prop._comparable_elements)
|
||||
else:
|
||||
values = other.__composite_values__()
|
||||
comparisons = [
|
||||
a == b
|
||||
for a, b in zip(self.prop._comparable_elements, values)
|
||||
]
|
||||
if self._adapt_to_entity:
|
||||
comparisons = [self.adapter(x) for x in comparisons]
|
||||
return sql.and_(*comparisons)
|
||||
|
||||
def __ne__(self, other):
|
||||
return sql.not_(self.__eq__(other))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.parent.class_.__name__) + "." + self.key
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
|
||||
class ConcreteInheritedProperty(DescriptorProperty):
|
||||
"""A 'do nothing' :class:`.MapperProperty` that disables
|
||||
an attribute on a concrete subclass that is only present
|
||||
on the inherited mapper, not the concrete classes' mapper.
|
||||
|
||||
Cases where this occurs include:
|
||||
|
||||
* When the superclass mapper is mapped against a
|
||||
"polymorphic union", which includes all attributes from
|
||||
all subclasses.
|
||||
* When a relationship() is configured on an inherited mapper,
|
||||
but not on the subclass mapper. Concrete mappers require
|
||||
that relationship() is configured explicitly on each
|
||||
subclass.
|
||||
|
||||
"""
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
comparator_callable = None
|
||||
|
||||
for m in self.parent.iterate_to_root():
|
||||
p = m._props[self.key]
|
||||
if not isinstance(p, ConcreteInheritedProperty):
|
||||
comparator_callable = p.comparator_factory
|
||||
break
|
||||
return comparator_callable
|
||||
|
||||
def __init__(self):
|
||||
super(ConcreteInheritedProperty, self).__init__()
|
||||
def warn():
|
||||
raise AttributeError("Concrete %s does not implement "
|
||||
"attribute %r at the instance level. Add "
|
||||
"this property explicitly to %s." %
|
||||
(self.parent, self.key, self.parent))
|
||||
|
||||
class NoninheritedConcreteProp(object):
|
||||
def __set__(s, obj, value):
|
||||
warn()
|
||||
|
||||
def __delete__(s, obj):
|
||||
warn()
|
||||
|
||||
def __get__(s, obj, owner):
|
||||
if obj is None:
|
||||
return self.descriptor
|
||||
warn()
|
||||
self.descriptor = NoninheritedConcreteProp()
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
|
||||
class SynonymProperty(DescriptorProperty):
|
||||
|
||||
def __init__(self, name, map_column=None,
|
||||
descriptor=None, comparator_factory=None,
|
||||
doc=None, info=None):
|
||||
"""Denote an attribute name as a synonym to a mapped property,
|
||||
in that the attribute will mirror the value and expression behavior
|
||||
of another attribute.
|
||||
|
||||
:param name: the name of the existing mapped property. This
|
||||
can refer to the string name of any :class:`.MapperProperty`
|
||||
configured on the class, including column-bound attributes
|
||||
and relationships.
|
||||
|
||||
:param descriptor: a Python :term:`descriptor` that will be used
|
||||
as a getter (and potentially a setter) when this attribute is
|
||||
accessed at the instance level.
|
||||
|
||||
:param map_column: if ``True``, the :func:`.synonym` construct will
|
||||
locate the existing named :class:`.MapperProperty` based on the
|
||||
attribute name of this :func:`.synonym`, and assign it to a new
|
||||
attribute linked to the name of this :func:`.synonym`.
|
||||
That is, given a mapping like::
|
||||
|
||||
class MyClass(Base):
|
||||
__tablename__ = 'my_table'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
job_status = Column(String(50))
|
||||
|
||||
job_status = synonym("_job_status", map_column=True)
|
||||
|
||||
The above class ``MyClass`` will now have the ``job_status``
|
||||
:class:`.Column` object mapped to the attribute named
|
||||
``_job_status``, and the attribute named ``job_status`` will refer
|
||||
to the synonym itself. This feature is typically used in
|
||||
conjunction with the ``descriptor`` argument in order to link a
|
||||
user-defined descriptor as a "wrapper" for an existing column.
|
||||
|
||||
:param info: Optional data dictionary which will be populated into the
|
||||
:attr:`.InspectionAttr.info` attribute of this object.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
:param comparator_factory: A subclass of :class:`.PropComparator`
|
||||
that will provide custom comparison behavior at the SQL expression
|
||||
level.
|
||||
|
||||
.. note::
|
||||
|
||||
For the use case of providing an attribute which redefines both
|
||||
Python-level and SQL-expression level behavior of an attribute,
|
||||
please refer to the Hybrid attribute introduced at
|
||||
:ref:`mapper_hybrids` for a more effective technique.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`synonyms` - examples of functionality.
|
||||
|
||||
:ref:`mapper_hybrids` - Hybrids provide a better approach for
|
||||
more complicated attribute-wrapping schemes than synonyms.
|
||||
|
||||
"""
|
||||
super(SynonymProperty, self).__init__()
|
||||
|
||||
self.name = name
|
||||
self.map_column = map_column
|
||||
self.descriptor = descriptor
|
||||
self.comparator_factory = comparator_factory
|
||||
self.doc = doc or (descriptor and descriptor.__doc__) or None
|
||||
if info:
|
||||
self.info = info
|
||||
|
||||
util.set_creation_order(self)
|
||||
|
||||
# TODO: when initialized, check _proxied_property,
|
||||
# emit a warning if its not a column-based property
|
||||
|
||||
@util.memoized_property
|
||||
def _proxied_property(self):
|
||||
return getattr(self.parent.class_, self.name).property
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
prop = self._proxied_property
|
||||
|
||||
if self.comparator_factory:
|
||||
comp = self.comparator_factory(prop, mapper)
|
||||
else:
|
||||
comp = prop.comparator_factory(prop, mapper)
|
||||
return comp
|
||||
|
||||
def set_parent(self, parent, init):
|
||||
if self.map_column:
|
||||
# implement the 'map_column' option.
|
||||
if self.key not in parent.mapped_table.c:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't compile synonym '%s': no column on table "
|
||||
"'%s' named '%s'"
|
||||
% (self.name, parent.mapped_table.description, self.key))
|
||||
elif parent.mapped_table.c[self.key] in \
|
||||
parent._columntoproperty and \
|
||||
parent._columntoproperty[
|
||||
parent.mapped_table.c[self.key]
|
||||
].key == self.name:
|
||||
raise sa_exc.ArgumentError(
|
||||
"Can't call map_column=True for synonym %r=%r, "
|
||||
"a ColumnProperty already exists keyed to the name "
|
||||
"%r for column %r" %
|
||||
(self.key, self.name, self.name, self.key)
|
||||
)
|
||||
p = properties.ColumnProperty(parent.mapped_table.c[self.key])
|
||||
parent._configure_property(
|
||||
self.name, p,
|
||||
init=init,
|
||||
setparent=True)
|
||||
p._mapped_by_synonym = self.key
|
||||
|
||||
self.parent = parent
|
||||
|
||||
|
||||
@util.langhelpers.dependency_for("sqlalchemy.orm.properties")
|
||||
class ComparableProperty(DescriptorProperty):
|
||||
"""Instruments a Python property for use in query expressions."""
|
||||
|
||||
def __init__(
|
||||
self, comparator_factory, descriptor=None, doc=None, info=None):
|
||||
"""Provides a method of applying a :class:`.PropComparator`
|
||||
to any Python descriptor attribute.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
:func:`.comparable_property` is superseded by
|
||||
the :mod:`~sqlalchemy.ext.hybrid` extension. See the example
|
||||
at :ref:`hybrid_custom_comparators`.
|
||||
|
||||
Allows any Python descriptor to behave like a SQL-enabled
|
||||
attribute when used at the class level in queries, allowing
|
||||
redefinition of expression operator behavior.
|
||||
|
||||
In the example below we redefine :meth:`.PropComparator.operate`
|
||||
to wrap both sides of an expression in ``func.lower()`` to produce
|
||||
case-insensitive comparison::
|
||||
|
||||
from sqlalchemy.orm import comparable_property
|
||||
from sqlalchemy.orm.interfaces import PropComparator
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy import Integer, String, Column
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
class CaseInsensitiveComparator(PropComparator):
|
||||
def __clause_element__(self):
|
||||
return self.prop
|
||||
|
||||
def operate(self, op, other):
|
||||
return op(
|
||||
func.lower(self.__clause_element__()),
|
||||
func.lower(other)
|
||||
)
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class SearchWord(Base):
|
||||
__tablename__ = 'search_word'
|
||||
id = Column(Integer, primary_key=True)
|
||||
word = Column(String)
|
||||
word_insensitive = comparable_property(lambda prop, mapper:
|
||||
CaseInsensitiveComparator(
|
||||
mapper.c.word, mapper)
|
||||
)
|
||||
|
||||
|
||||
A mapping like the above allows the ``word_insensitive`` attribute
|
||||
to render an expression like::
|
||||
|
||||
>>> print SearchWord.word_insensitive == "Trucks"
|
||||
lower(search_word.word) = lower(:lower_1)
|
||||
|
||||
:param comparator_factory:
|
||||
A PropComparator subclass or factory that defines operator behavior
|
||||
for this property.
|
||||
|
||||
:param descriptor:
|
||||
Optional when used in a ``properties={}`` declaration. The Python
|
||||
descriptor or property to layer comparison behavior on top of.
|
||||
|
||||
The like-named descriptor will be automatically retrieved from the
|
||||
mapped class if left blank in a ``properties`` declaration.
|
||||
|
||||
:param info: Optional data dictionary which will be populated into the
|
||||
:attr:`.InspectionAttr.info` attribute of this object.
|
||||
|
||||
.. versionadded:: 1.0.0
|
||||
|
||||
"""
|
||||
super(ComparableProperty, self).__init__()
|
||||
self.descriptor = descriptor
|
||||
self.comparator_factory = comparator_factory
|
||||
self.doc = doc or (descriptor and descriptor.__doc__) or None
|
||||
if info:
|
||||
self.info = info
|
||||
util.set_creation_order(self)
|
||||
|
||||
def _comparator_factory(self, mapper):
|
||||
return self.comparator_factory(self, mapper)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,528 @@
|
||||
# orm/instrumentation.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
"""Defines SQLAlchemy's system of class instrumentation.
|
||||
|
||||
This module is usually not directly visible to user applications, but
|
||||
defines a large part of the ORM's interactivity.
|
||||
|
||||
instrumentation.py deals with registration of end-user classes
|
||||
for state tracking. It interacts closely with state.py
|
||||
and attributes.py which establish per-instance and per-class-attribute
|
||||
instrumentation, respectively.
|
||||
|
||||
The class instrumentation system can be customized on a per-class
|
||||
or global basis using the :mod:`sqlalchemy.ext.instrumentation`
|
||||
module, which provides the means to build and specify
|
||||
alternate instrumentation forms.
|
||||
|
||||
.. versionchanged: 0.8
|
||||
The instrumentation extension system was moved out of the
|
||||
ORM and into the external :mod:`sqlalchemy.ext.instrumentation`
|
||||
package. When that package is imported, it installs
|
||||
itself within sqlalchemy.orm so that its more comprehensive
|
||||
resolution mechanics take effect.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from . import exc, collections, interfaces, state
|
||||
from .. import util
|
||||
from . import base
|
||||
|
||||
|
||||
_memoized_key_collection = util.group_expirable_memoized_property()
|
||||
|
||||
|
||||
class ClassManager(dict):
|
||||
"""tracks state information at the class level."""
|
||||
|
||||
MANAGER_ATTR = base.DEFAULT_MANAGER_ATTR
|
||||
STATE_ATTR = base.DEFAULT_STATE_ATTR
|
||||
|
||||
_state_setter = staticmethod(util.attrsetter(STATE_ATTR))
|
||||
|
||||
deferred_scalar_loader = None
|
||||
|
||||
original_init = object.__init__
|
||||
|
||||
factory = None
|
||||
|
||||
def __init__(self, class_):
|
||||
self.class_ = class_
|
||||
self.info = {}
|
||||
self.new_init = None
|
||||
self.local_attrs = {}
|
||||
self.originals = {}
|
||||
|
||||
self._bases = [mgr for mgr in [
|
||||
manager_of_class(base)
|
||||
for base in self.class_.__bases__
|
||||
if isinstance(base, type)
|
||||
] if mgr is not None]
|
||||
|
||||
for base in self._bases:
|
||||
self.update(base)
|
||||
|
||||
self.dispatch._events._new_classmanager_instance(class_, self)
|
||||
# events._InstanceEventsHold.populate(class_, self)
|
||||
|
||||
for basecls in class_.__mro__:
|
||||
mgr = manager_of_class(basecls)
|
||||
if mgr is not None:
|
||||
self.dispatch._update(mgr.dispatch)
|
||||
self.manage()
|
||||
self._instrument_init()
|
||||
|
||||
if '__del__' in class_.__dict__:
|
||||
util.warn("__del__() method on class %s will "
|
||||
"cause unreachable cycles and memory leaks, "
|
||||
"as SQLAlchemy instrumentation often creates "
|
||||
"reference cycles. Please remove this method." %
|
||||
class_)
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return other is self
|
||||
|
||||
@property
|
||||
def is_mapped(self):
|
||||
return 'mapper' in self.__dict__
|
||||
|
||||
@_memoized_key_collection
|
||||
def _all_key_set(self):
|
||||
return frozenset(self)
|
||||
|
||||
@_memoized_key_collection
|
||||
def _collection_impl_keys(self):
|
||||
return frozenset([
|
||||
attr.key for attr in self.values() if attr.impl.collection])
|
||||
|
||||
@_memoized_key_collection
|
||||
def _scalar_loader_impls(self):
|
||||
return frozenset([
|
||||
attr.impl for attr in
|
||||
self.values() if attr.impl.accepts_scalar_loader])
|
||||
|
||||
@util.memoized_property
|
||||
def mapper(self):
|
||||
# raises unless self.mapper has been assigned
|
||||
raise exc.UnmappedClassError(self.class_)
|
||||
|
||||
def _all_sqla_attributes(self, exclude=None):
|
||||
"""return an iterator of all classbound attributes that are
|
||||
implement :class:`.InspectionAttr`.
|
||||
|
||||
This includes :class:`.QueryableAttribute` as well as extension
|
||||
types such as :class:`.hybrid_property` and
|
||||
:class:`.AssociationProxy`.
|
||||
|
||||
"""
|
||||
if exclude is None:
|
||||
exclude = set()
|
||||
for supercls in self.class_.__mro__:
|
||||
for key in set(supercls.__dict__).difference(exclude):
|
||||
exclude.add(key)
|
||||
val = supercls.__dict__[key]
|
||||
if isinstance(val, interfaces.InspectionAttr):
|
||||
yield key, val
|
||||
|
||||
def _attr_has_impl(self, key):
|
||||
"""Return True if the given attribute is fully initialized.
|
||||
|
||||
i.e. has an impl.
|
||||
"""
|
||||
|
||||
return key in self and self[key].impl is not None
|
||||
|
||||
def _subclass_manager(self, cls):
|
||||
"""Create a new ClassManager for a subclass of this ClassManager's
|
||||
class.
|
||||
|
||||
This is called automatically when attributes are instrumented so that
|
||||
the attributes can be propagated to subclasses against their own
|
||||
class-local manager, without the need for mappers etc. to have already
|
||||
pre-configured managers for the full class hierarchy. Mappers
|
||||
can post-configure the auto-generated ClassManager when needed.
|
||||
|
||||
"""
|
||||
manager = manager_of_class(cls)
|
||||
if manager is None:
|
||||
manager = _instrumentation_factory.create_manager_for_cls(cls)
|
||||
return manager
|
||||
|
||||
def _instrument_init(self):
|
||||
# TODO: self.class_.__init__ is often the already-instrumented
|
||||
# __init__ from an instrumented superclass. We still need to make
|
||||
# our own wrapper, but it would
|
||||
# be nice to wrap the original __init__ and not our existing wrapper
|
||||
# of such, since this adds method overhead.
|
||||
self.original_init = self.class_.__init__
|
||||
self.new_init = _generate_init(self.class_, self)
|
||||
self.install_member('__init__', self.new_init)
|
||||
|
||||
def _uninstrument_init(self):
|
||||
if self.new_init:
|
||||
self.uninstall_member('__init__')
|
||||
self.new_init = None
|
||||
|
||||
@util.memoized_property
|
||||
def _state_constructor(self):
|
||||
self.dispatch.first_init(self, self.class_)
|
||||
return state.InstanceState
|
||||
|
||||
def manage(self):
|
||||
"""Mark this instance as the manager for its class."""
|
||||
|
||||
setattr(self.class_, self.MANAGER_ATTR, self)
|
||||
|
||||
def dispose(self):
|
||||
"""Dissasociate this manager from its class."""
|
||||
|
||||
delattr(self.class_, self.MANAGER_ATTR)
|
||||
|
||||
@util.hybridmethod
|
||||
def manager_getter(self):
|
||||
return _default_manager_getter
|
||||
|
||||
@util.hybridmethod
|
||||
def state_getter(self):
|
||||
"""Return a (instance) -> InstanceState callable.
|
||||
|
||||
"state getter" callables should raise either KeyError or
|
||||
AttributeError if no InstanceState could be found for the
|
||||
instance.
|
||||
"""
|
||||
|
||||
return _default_state_getter
|
||||
|
||||
@util.hybridmethod
|
||||
def dict_getter(self):
|
||||
return _default_dict_getter
|
||||
|
||||
def instrument_attribute(self, key, inst, propagated=False):
|
||||
if propagated:
|
||||
if key in self.local_attrs:
|
||||
return # don't override local attr with inherited attr
|
||||
else:
|
||||
self.local_attrs[key] = inst
|
||||
self.install_descriptor(key, inst)
|
||||
_memoized_key_collection.expire_instance(self)
|
||||
self[key] = inst
|
||||
|
||||
for cls in self.class_.__subclasses__():
|
||||
manager = self._subclass_manager(cls)
|
||||
manager.instrument_attribute(key, inst, True)
|
||||
|
||||
def subclass_managers(self, recursive):
|
||||
for cls in self.class_.__subclasses__():
|
||||
mgr = manager_of_class(cls)
|
||||
if mgr is not None and mgr is not self:
|
||||
yield mgr
|
||||
if recursive:
|
||||
for m in mgr.subclass_managers(True):
|
||||
yield m
|
||||
|
||||
def post_configure_attribute(self, key):
|
||||
_instrumentation_factory.dispatch.\
|
||||
attribute_instrument(self.class_, key, self[key])
|
||||
|
||||
def uninstrument_attribute(self, key, propagated=False):
|
||||
if key not in self:
|
||||
return
|
||||
if propagated:
|
||||
if key in self.local_attrs:
|
||||
return # don't get rid of local attr
|
||||
else:
|
||||
del self.local_attrs[key]
|
||||
self.uninstall_descriptor(key)
|
||||
_memoized_key_collection.expire_instance(self)
|
||||
del self[key]
|
||||
for cls in self.class_.__subclasses__():
|
||||
manager = manager_of_class(cls)
|
||||
if manager:
|
||||
manager.uninstrument_attribute(key, True)
|
||||
|
||||
def unregister(self):
|
||||
"""remove all instrumentation established by this ClassManager."""
|
||||
|
||||
self._uninstrument_init()
|
||||
|
||||
self.mapper = self.dispatch = None
|
||||
self.info.clear()
|
||||
|
||||
for key in list(self):
|
||||
if key in self.local_attrs:
|
||||
self.uninstrument_attribute(key)
|
||||
|
||||
def install_descriptor(self, key, inst):
|
||||
if key in (self.STATE_ATTR, self.MANAGER_ATTR):
|
||||
raise KeyError("%r: requested attribute name conflicts with "
|
||||
"instrumentation attribute of the same name." %
|
||||
key)
|
||||
setattr(self.class_, key, inst)
|
||||
|
||||
def uninstall_descriptor(self, key):
|
||||
delattr(self.class_, key)
|
||||
|
||||
def install_member(self, key, implementation):
|
||||
if key in (self.STATE_ATTR, self.MANAGER_ATTR):
|
||||
raise KeyError("%r: requested attribute name conflicts with "
|
||||
"instrumentation attribute of the same name." %
|
||||
key)
|
||||
self.originals.setdefault(key, getattr(self.class_, key, None))
|
||||
setattr(self.class_, key, implementation)
|
||||
|
||||
def uninstall_member(self, key):
|
||||
original = self.originals.pop(key, None)
|
||||
if original is not None:
|
||||
setattr(self.class_, key, original)
|
||||
|
||||
def instrument_collection_class(self, key, collection_class):
|
||||
return collections.prepare_instrumentation(collection_class)
|
||||
|
||||
def initialize_collection(self, key, state, factory):
|
||||
user_data = factory()
|
||||
adapter = collections.CollectionAdapter(
|
||||
self.get_impl(key), state, user_data)
|
||||
return adapter, user_data
|
||||
|
||||
def is_instrumented(self, key, search=False):
|
||||
if search:
|
||||
return key in self
|
||||
else:
|
||||
return key in self.local_attrs
|
||||
|
||||
def get_impl(self, key):
|
||||
return self[key].impl
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return iter(self.values())
|
||||
|
||||
# InstanceState management
|
||||
|
||||
def new_instance(self, state=None):
|
||||
instance = self.class_.__new__(self.class_)
|
||||
if state is None:
|
||||
state = self._state_constructor(instance, self)
|
||||
self._state_setter(instance, state)
|
||||
return instance
|
||||
|
||||
def setup_instance(self, instance, state=None):
|
||||
if state is None:
|
||||
state = self._state_constructor(instance, self)
|
||||
self._state_setter(instance, state)
|
||||
|
||||
def teardown_instance(self, instance):
|
||||
delattr(instance, self.STATE_ATTR)
|
||||
|
||||
def _serialize(self, state, state_dict):
|
||||
return _SerializeManager(state, state_dict)
|
||||
|
||||
def _new_state_if_none(self, instance):
|
||||
"""Install a default InstanceState if none is present.
|
||||
|
||||
A private convenience method used by the __init__ decorator.
|
||||
|
||||
"""
|
||||
if hasattr(instance, self.STATE_ATTR):
|
||||
return False
|
||||
elif self.class_ is not instance.__class__ and \
|
||||
self.is_mapped:
|
||||
# this will create a new ClassManager for the
|
||||
# subclass, without a mapper. This is likely a
|
||||
# user error situation but allow the object
|
||||
# to be constructed, so that it is usable
|
||||
# in a non-ORM context at least.
|
||||
return self._subclass_manager(instance.__class__).\
|
||||
_new_state_if_none(instance)
|
||||
else:
|
||||
state = self._state_constructor(instance, self)
|
||||
self._state_setter(instance, state)
|
||||
return state
|
||||
|
||||
def has_state(self, instance):
|
||||
return hasattr(instance, self.STATE_ATTR)
|
||||
|
||||
def has_parent(self, state, key, optimistic=False):
|
||||
"""TODO"""
|
||||
return self.get_impl(key).hasparent(state, optimistic=optimistic)
|
||||
|
||||
def __bool__(self):
|
||||
"""All ClassManagers are non-zero regardless of attribute state."""
|
||||
return True
|
||||
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s of %r at %x>' % (
|
||||
self.__class__.__name__, self.class_, id(self))
|
||||
|
||||
|
||||
class _SerializeManager(object):
|
||||
"""Provide serialization of a :class:`.ClassManager`.
|
||||
|
||||
The :class:`.InstanceState` uses ``__init__()`` on serialize
|
||||
and ``__call__()`` on deserialize.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, state, d):
|
||||
self.class_ = state.class_
|
||||
manager = state.manager
|
||||
manager.dispatch.pickle(state, d)
|
||||
|
||||
def __call__(self, state, inst, state_dict):
|
||||
state.manager = manager = manager_of_class(self.class_)
|
||||
if manager is None:
|
||||
raise exc.UnmappedInstanceError(
|
||||
inst,
|
||||
"Cannot deserialize object of type %r - "
|
||||
"no mapper() has "
|
||||
"been configured for this class within the current "
|
||||
"Python process!" %
|
||||
self.class_)
|
||||
elif manager.is_mapped and not manager.mapper.configured:
|
||||
manager.mapper._configure_all()
|
||||
|
||||
# setup _sa_instance_state ahead of time so that
|
||||
# unpickle events can access the object normally.
|
||||
# see [ticket:2362]
|
||||
if inst is not None:
|
||||
manager.setup_instance(inst, state)
|
||||
manager.dispatch.unpickle(state, state_dict)
|
||||
|
||||
|
||||
class InstrumentationFactory(object):
|
||||
"""Factory for new ClassManager instances."""
|
||||
|
||||
def create_manager_for_cls(self, class_):
|
||||
assert class_ is not None
|
||||
assert manager_of_class(class_) is None
|
||||
|
||||
# give a more complicated subclass
|
||||
# a chance to do what it wants here
|
||||
manager, factory = self._locate_extended_factory(class_)
|
||||
|
||||
if factory is None:
|
||||
factory = ClassManager
|
||||
manager = factory(class_)
|
||||
|
||||
self._check_conflicts(class_, factory)
|
||||
|
||||
manager.factory = factory
|
||||
|
||||
self.dispatch.class_instrument(class_)
|
||||
return manager
|
||||
|
||||
def _locate_extended_factory(self, class_):
|
||||
"""Overridden by a subclass to do an extended lookup."""
|
||||
return None, None
|
||||
|
||||
def _check_conflicts(self, class_, factory):
|
||||
"""Overridden by a subclass to test for conflicting factories."""
|
||||
return
|
||||
|
||||
def unregister(self, class_):
|
||||
manager = manager_of_class(class_)
|
||||
manager.unregister()
|
||||
manager.dispose()
|
||||
self.dispatch.class_uninstrument(class_)
|
||||
if ClassManager.MANAGER_ATTR in class_.__dict__:
|
||||
delattr(class_, ClassManager.MANAGER_ATTR)
|
||||
|
||||
# this attribute is replaced by sqlalchemy.ext.instrumentation
|
||||
# when importred.
|
||||
_instrumentation_factory = InstrumentationFactory()
|
||||
|
||||
# these attributes are replaced by sqlalchemy.ext.instrumentation
|
||||
# when a non-standard InstrumentationManager class is first
|
||||
# used to instrument a class.
|
||||
instance_state = _default_state_getter = base.instance_state
|
||||
|
||||
instance_dict = _default_dict_getter = base.instance_dict
|
||||
|
||||
manager_of_class = _default_manager_getter = base.manager_of_class
|
||||
|
||||
|
||||
def register_class(class_):
|
||||
"""Register class instrumentation.
|
||||
|
||||
Returns the existing or newly created class manager.
|
||||
|
||||
"""
|
||||
|
||||
manager = manager_of_class(class_)
|
||||
if manager is None:
|
||||
manager = _instrumentation_factory.create_manager_for_cls(class_)
|
||||
return manager
|
||||
|
||||
|
||||
def unregister_class(class_):
|
||||
"""Unregister class instrumentation."""
|
||||
|
||||
_instrumentation_factory.unregister(class_)
|
||||
|
||||
|
||||
def is_instrumented(instance, key):
|
||||
"""Return True if the given attribute on the given instance is
|
||||
instrumented by the attributes package.
|
||||
|
||||
This function may be used regardless of instrumentation
|
||||
applied directly to the class, i.e. no descriptors are required.
|
||||
|
||||
"""
|
||||
return manager_of_class(instance.__class__).\
|
||||
is_instrumented(key, search=True)
|
||||
|
||||
|
||||
def _generate_init(class_, class_manager):
|
||||
"""Build an __init__ decorator that triggers ClassManager events."""
|
||||
|
||||
# TODO: we should use the ClassManager's notion of the
|
||||
# original '__init__' method, once ClassManager is fixed
|
||||
# to always reference that.
|
||||
original__init__ = class_.__init__
|
||||
assert original__init__
|
||||
|
||||
# Go through some effort here and don't change the user's __init__
|
||||
# calling signature, including the unlikely case that it has
|
||||
# a return value.
|
||||
# FIXME: need to juggle local names to avoid constructor argument
|
||||
# clashes.
|
||||
func_body = """\
|
||||
def __init__(%(apply_pos)s):
|
||||
new_state = class_manager._new_state_if_none(%(self_arg)s)
|
||||
if new_state:
|
||||
return new_state._initialize_instance(%(apply_kw)s)
|
||||
else:
|
||||
return original__init__(%(apply_kw)s)
|
||||
"""
|
||||
func_vars = util.format_argspec_init(original__init__, grouped=False)
|
||||
func_text = func_body % func_vars
|
||||
|
||||
if util.py2k:
|
||||
func = getattr(original__init__, 'im_func', original__init__)
|
||||
func_defaults = getattr(func, 'func_defaults', None)
|
||||
else:
|
||||
func_defaults = getattr(original__init__, '__defaults__', None)
|
||||
func_kw_defaults = getattr(original__init__, '__kwdefaults__', None)
|
||||
|
||||
env = locals().copy()
|
||||
exec(func_text, env)
|
||||
__init__ = env['__init__']
|
||||
__init__.__doc__ = original__init__.__doc__
|
||||
|
||||
if func_defaults:
|
||||
__init__.__defaults__ = func_defaults
|
||||
if not util.py2k and func_kw_defaults:
|
||||
__init__.__kwdefaults__ = func_kw_defaults
|
||||
|
||||
return __init__
|
||||
@@ -0,0 +1,703 @@
|
||||
# orm/loading.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
|
||||
"""private module containing functions used to convert database
|
||||
rows into object instances and associated state.
|
||||
|
||||
the functions here are called primarily by Query, Mapper,
|
||||
as well as some of the attribute loading strategies.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .. import util
|
||||
from . import attributes, exc as orm_exc
|
||||
from ..sql import util as sql_util
|
||||
from . import strategy_options
|
||||
|
||||
from .util import _none_set, state_str
|
||||
from .base import _SET_DEFERRED_EXPIRED, _DEFER_FOR_STATE
|
||||
from .. import exc as sa_exc
|
||||
import collections
|
||||
|
||||
_new_runid = util.counter()
|
||||
|
||||
|
||||
def instances(query, cursor, context):
|
||||
"""Return an ORM result as an iterator."""
|
||||
|
||||
context.runid = _new_runid()
|
||||
|
||||
filtered = query._has_mapper_entities
|
||||
|
||||
single_entity = len(query._entities) == 1 and \
|
||||
query._entities[0].supports_single_entity
|
||||
|
||||
if filtered:
|
||||
if single_entity:
|
||||
filter_fn = id
|
||||
else:
|
||||
def filter_fn(row):
|
||||
return tuple(
|
||||
id(item)
|
||||
if ent.use_id_for_hash
|
||||
else item
|
||||
for ent, item in zip(query._entities, row)
|
||||
)
|
||||
|
||||
try:
|
||||
(process, labels) = \
|
||||
list(zip(*[
|
||||
query_entity.row_processor(query,
|
||||
context, cursor)
|
||||
for query_entity in query._entities
|
||||
]))
|
||||
|
||||
if not single_entity:
|
||||
keyed_tuple = util.lightweight_named_tuple('result', labels)
|
||||
|
||||
while True:
|
||||
context.partials = {}
|
||||
|
||||
if query._yield_per:
|
||||
fetch = cursor.fetchmany(query._yield_per)
|
||||
if not fetch:
|
||||
break
|
||||
else:
|
||||
fetch = cursor.fetchall()
|
||||
|
||||
if single_entity:
|
||||
proc = process[0]
|
||||
rows = [proc(row) for row in fetch]
|
||||
else:
|
||||
rows = [keyed_tuple([proc(row) for proc in process])
|
||||
for row in fetch]
|
||||
|
||||
if filtered:
|
||||
rows = util.unique_list(rows, filter_fn)
|
||||
|
||||
for row in rows:
|
||||
yield row
|
||||
|
||||
if not query._yield_per:
|
||||
break
|
||||
except Exception as err:
|
||||
cursor.close()
|
||||
util.raise_from_cause(err)
|
||||
|
||||
|
||||
@util.dependencies("sqlalchemy.orm.query")
|
||||
def merge_result(querylib, query, iterator, load=True):
|
||||
"""Merge a result into this :class:`.Query` object's Session."""
|
||||
|
||||
session = query.session
|
||||
if load:
|
||||
# flush current contents if we expect to load data
|
||||
session._autoflush()
|
||||
|
||||
autoflush = session.autoflush
|
||||
try:
|
||||
session.autoflush = False
|
||||
single_entity = len(query._entities) == 1
|
||||
if single_entity:
|
||||
if isinstance(query._entities[0], querylib._MapperEntity):
|
||||
result = [session._merge(
|
||||
attributes.instance_state(instance),
|
||||
attributes.instance_dict(instance),
|
||||
load=load, _recursive={}, _resolve_conflict_map={})
|
||||
for instance in iterator]
|
||||
else:
|
||||
result = list(iterator)
|
||||
else:
|
||||
mapped_entities = [i for i, e in enumerate(query._entities)
|
||||
if isinstance(e, querylib._MapperEntity)]
|
||||
result = []
|
||||
keys = [ent._label_name for ent in query._entities]
|
||||
keyed_tuple = util.lightweight_named_tuple('result', keys)
|
||||
for row in iterator:
|
||||
newrow = list(row)
|
||||
for i in mapped_entities:
|
||||
if newrow[i] is not None:
|
||||
newrow[i] = session._merge(
|
||||
attributes.instance_state(newrow[i]),
|
||||
attributes.instance_dict(newrow[i]),
|
||||
load=load, _recursive={}, _resolve_conflict_map={})
|
||||
result.append(keyed_tuple(newrow))
|
||||
|
||||
return iter(result)
|
||||
finally:
|
||||
session.autoflush = autoflush
|
||||
|
||||
|
||||
def get_from_identity(session, key, passive):
|
||||
"""Look up the given key in the given session's identity map,
|
||||
check the object for expired state if found.
|
||||
|
||||
"""
|
||||
instance = session.identity_map.get(key)
|
||||
if instance is not None:
|
||||
|
||||
state = attributes.instance_state(instance)
|
||||
|
||||
# expired - ensure it still exists
|
||||
if state.expired:
|
||||
if not passive & attributes.SQL_OK:
|
||||
# TODO: no coverage here
|
||||
return attributes.PASSIVE_NO_RESULT
|
||||
elif not passive & attributes.RELATED_OBJECT_OK:
|
||||
# this mode is used within a flush and the instance's
|
||||
# expired state will be checked soon enough, if necessary
|
||||
return instance
|
||||
try:
|
||||
state._load_expired(state, passive)
|
||||
except orm_exc.ObjectDeletedError:
|
||||
session._remove_newly_deleted([state])
|
||||
return None
|
||||
return instance
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def load_on_ident(query, key,
|
||||
refresh_state=None, lockmode=None,
|
||||
only_load_props=None):
|
||||
"""Load the given identity key from the database."""
|
||||
|
||||
if key is not None:
|
||||
ident = key[1]
|
||||
else:
|
||||
ident = None
|
||||
|
||||
if refresh_state is None:
|
||||
q = query._clone()
|
||||
q._get_condition()
|
||||
else:
|
||||
q = query._clone()
|
||||
|
||||
if ident is not None:
|
||||
mapper = query._mapper_zero()
|
||||
|
||||
(_get_clause, _get_params) = mapper._get_clause
|
||||
|
||||
# None present in ident - turn those comparisons
|
||||
# into "IS NULL"
|
||||
if None in ident:
|
||||
nones = set([
|
||||
_get_params[col].key for col, value in
|
||||
zip(mapper.primary_key, ident) if value is None
|
||||
])
|
||||
_get_clause = sql_util.adapt_criterion_to_null(
|
||||
_get_clause, nones)
|
||||
|
||||
_get_clause = q._adapt_clause(_get_clause, True, False)
|
||||
q._criterion = _get_clause
|
||||
|
||||
params = dict([
|
||||
(_get_params[primary_key].key, id_val)
|
||||
for id_val, primary_key in zip(ident, mapper.primary_key)
|
||||
])
|
||||
|
||||
q._params = params
|
||||
|
||||
if lockmode is not None:
|
||||
version_check = True
|
||||
q = q.with_lockmode(lockmode)
|
||||
elif query._for_update_arg is not None:
|
||||
version_check = True
|
||||
q._for_update_arg = query._for_update_arg
|
||||
else:
|
||||
version_check = False
|
||||
|
||||
q._get_options(
|
||||
populate_existing=bool(refresh_state),
|
||||
version_check=version_check,
|
||||
only_load_props=only_load_props,
|
||||
refresh_state=refresh_state)
|
||||
q._order_by = None
|
||||
|
||||
try:
|
||||
return q.one()
|
||||
except orm_exc.NoResultFound:
|
||||
return None
|
||||
|
||||
|
||||
def _setup_entity_query(
|
||||
context, mapper, query_entity,
|
||||
path, adapter, column_collection,
|
||||
with_polymorphic=None, only_load_props=None,
|
||||
polymorphic_discriminator=None, **kw):
|
||||
|
||||
if with_polymorphic:
|
||||
poly_properties = mapper._iterate_polymorphic_properties(
|
||||
with_polymorphic)
|
||||
else:
|
||||
poly_properties = mapper._polymorphic_properties
|
||||
|
||||
quick_populators = {}
|
||||
|
||||
path.set(
|
||||
context.attributes,
|
||||
"memoized_setups",
|
||||
quick_populators)
|
||||
|
||||
for value in poly_properties:
|
||||
if only_load_props and \
|
||||
value.key not in only_load_props:
|
||||
continue
|
||||
value.setup(
|
||||
context,
|
||||
query_entity,
|
||||
path,
|
||||
adapter,
|
||||
only_load_props=only_load_props,
|
||||
column_collection=column_collection,
|
||||
memoized_populators=quick_populators,
|
||||
**kw
|
||||
)
|
||||
|
||||
if polymorphic_discriminator is not None and \
|
||||
polymorphic_discriminator \
|
||||
is not mapper.polymorphic_on:
|
||||
|
||||
if adapter:
|
||||
pd = adapter.columns[polymorphic_discriminator]
|
||||
else:
|
||||
pd = polymorphic_discriminator
|
||||
column_collection.append(pd)
|
||||
|
||||
|
||||
def _instance_processor(
|
||||
mapper, context, result, path, adapter,
|
||||
only_load_props=None, refresh_state=None,
|
||||
polymorphic_discriminator=None,
|
||||
_polymorphic_from=None):
|
||||
"""Produce a mapper level row processor callable
|
||||
which processes rows into mapped instances."""
|
||||
|
||||
# note that this method, most of which exists in a closure
|
||||
# called _instance(), resists being broken out, as
|
||||
# attempts to do so tend to add significant function
|
||||
# call overhead. _instance() is the most
|
||||
# performance-critical section in the whole ORM.
|
||||
|
||||
pk_cols = mapper.primary_key
|
||||
|
||||
if adapter:
|
||||
pk_cols = [adapter.columns[c] for c in pk_cols]
|
||||
|
||||
identity_class = mapper._identity_class
|
||||
|
||||
populators = collections.defaultdict(list)
|
||||
|
||||
props = mapper._prop_set
|
||||
if only_load_props is not None:
|
||||
props = props.intersection(
|
||||
mapper._props[k] for k in only_load_props)
|
||||
|
||||
quick_populators = path.get(
|
||||
context.attributes, "memoized_setups", _none_set)
|
||||
|
||||
for prop in props:
|
||||
if prop in quick_populators:
|
||||
# this is an inlined path just for column-based attributes.
|
||||
col = quick_populators[prop]
|
||||
if col is _DEFER_FOR_STATE:
|
||||
populators["new"].append(
|
||||
(prop.key, prop._deferred_column_loader))
|
||||
elif col is _SET_DEFERRED_EXPIRED:
|
||||
# note that in this path, we are no longer
|
||||
# searching in the result to see if the column might
|
||||
# be present in some unexpected way.
|
||||
populators["expire"].append((prop.key, False))
|
||||
else:
|
||||
if adapter:
|
||||
col = adapter.columns[col]
|
||||
getter = result._getter(col, False)
|
||||
if getter:
|
||||
populators["quick"].append((prop.key, getter))
|
||||
else:
|
||||
# fall back to the ColumnProperty itself, which
|
||||
# will iterate through all of its columns
|
||||
# to see if one fits
|
||||
prop.create_row_processor(
|
||||
context, path, mapper, result, adapter, populators)
|
||||
else:
|
||||
prop.create_row_processor(
|
||||
context, path, mapper, result, adapter, populators)
|
||||
|
||||
propagate_options = context.propagate_options
|
||||
load_path = context.query._current_path + path \
|
||||
if context.query._current_path.path else path
|
||||
|
||||
session_identity_map = context.session.identity_map
|
||||
|
||||
populate_existing = context.populate_existing or mapper.always_refresh
|
||||
load_evt = bool(mapper.class_manager.dispatch.load)
|
||||
refresh_evt = bool(mapper.class_manager.dispatch.refresh)
|
||||
persistent_evt = bool(context.session.dispatch.loaded_as_persistent)
|
||||
if persistent_evt:
|
||||
loaded_as_persistent = context.session.dispatch.loaded_as_persistent
|
||||
instance_state = attributes.instance_state
|
||||
instance_dict = attributes.instance_dict
|
||||
session_id = context.session.hash_key
|
||||
version_check = context.version_check
|
||||
runid = context.runid
|
||||
|
||||
if refresh_state:
|
||||
refresh_identity_key = refresh_state.key
|
||||
if refresh_identity_key is None:
|
||||
# super-rare condition; a refresh is being called
|
||||
# on a non-instance-key instance; this is meant to only
|
||||
# occur within a flush()
|
||||
refresh_identity_key = \
|
||||
mapper._identity_key_from_state(refresh_state)
|
||||
else:
|
||||
refresh_identity_key = None
|
||||
|
||||
if mapper.allow_partial_pks:
|
||||
is_not_primary_key = _none_set.issuperset
|
||||
else:
|
||||
is_not_primary_key = _none_set.intersection
|
||||
|
||||
def _instance(row):
|
||||
|
||||
# determine the state that we'll be populating
|
||||
if refresh_identity_key:
|
||||
# fixed state that we're refreshing
|
||||
state = refresh_state
|
||||
instance = state.obj()
|
||||
dict_ = instance_dict(instance)
|
||||
isnew = state.runid != runid
|
||||
currentload = True
|
||||
loaded_instance = False
|
||||
else:
|
||||
# look at the row, see if that identity is in the
|
||||
# session, or we have to create a new one
|
||||
identitykey = (
|
||||
identity_class,
|
||||
tuple([row[column] for column in pk_cols])
|
||||
)
|
||||
|
||||
instance = session_identity_map.get(identitykey)
|
||||
|
||||
if instance is not None:
|
||||
# existing instance
|
||||
state = instance_state(instance)
|
||||
dict_ = instance_dict(instance)
|
||||
|
||||
isnew = state.runid != runid
|
||||
currentload = not isnew
|
||||
loaded_instance = False
|
||||
|
||||
if version_check and not currentload:
|
||||
_validate_version_id(mapper, state, dict_, row, adapter)
|
||||
|
||||
else:
|
||||
# create a new instance
|
||||
|
||||
# check for non-NULL values in the primary key columns,
|
||||
# else no entity is returned for the row
|
||||
if is_not_primary_key(identitykey[1]):
|
||||
return None
|
||||
|
||||
isnew = True
|
||||
currentload = True
|
||||
loaded_instance = True
|
||||
|
||||
instance = mapper.class_manager.new_instance()
|
||||
|
||||
dict_ = instance_dict(instance)
|
||||
state = instance_state(instance)
|
||||
state.key = identitykey
|
||||
|
||||
# attach instance to session.
|
||||
state.session_id = session_id
|
||||
session_identity_map._add_unpresent(state, identitykey)
|
||||
|
||||
# populate. this looks at whether this state is new
|
||||
# for this load or was existing, and whether or not this
|
||||
# row is the first row with this identity.
|
||||
if currentload or populate_existing:
|
||||
# full population routines. Objects here are either
|
||||
# just created, or we are doing a populate_existing
|
||||
|
||||
# be conservative about setting load_path when populate_existing
|
||||
# is in effect; want to maintain options from the original
|
||||
# load. see test_expire->test_refresh_maintains_deferred_options
|
||||
if isnew and (propagate_options or not populate_existing):
|
||||
state.load_options = propagate_options
|
||||
state.load_path = load_path
|
||||
|
||||
_populate_full(
|
||||
context, row, state, dict_, isnew, load_path,
|
||||
loaded_instance, populate_existing, populators)
|
||||
|
||||
if isnew:
|
||||
if loaded_instance:
|
||||
if load_evt:
|
||||
state.manager.dispatch.load(state, context)
|
||||
if persistent_evt:
|
||||
loaded_as_persistent(context.session, state.obj())
|
||||
elif refresh_evt:
|
||||
state.manager.dispatch.refresh(
|
||||
state, context, only_load_props)
|
||||
|
||||
if populate_existing or state.modified:
|
||||
if refresh_state and only_load_props:
|
||||
state._commit(dict_, only_load_props)
|
||||
else:
|
||||
state._commit_all(dict_, session_identity_map)
|
||||
|
||||
else:
|
||||
# partial population routines, for objects that were already
|
||||
# in the Session, but a row matches them; apply eager loaders
|
||||
# on existing objects, etc.
|
||||
unloaded = state.unloaded
|
||||
isnew = state not in context.partials
|
||||
|
||||
if not isnew or unloaded or populators["eager"]:
|
||||
# state is having a partial set of its attributes
|
||||
# refreshed. Populate those attributes,
|
||||
# and add to the "context.partials" collection.
|
||||
|
||||
to_load = _populate_partial(
|
||||
context, row, state, dict_, isnew, load_path,
|
||||
unloaded, populators)
|
||||
|
||||
if isnew:
|
||||
if refresh_evt:
|
||||
state.manager.dispatch.refresh(
|
||||
state, context, to_load)
|
||||
|
||||
state._commit(dict_, to_load)
|
||||
|
||||
return instance
|
||||
|
||||
if mapper.polymorphic_map and not _polymorphic_from and not refresh_state:
|
||||
# if we are doing polymorphic, dispatch to a different _instance()
|
||||
# method specific to the subclass mapper
|
||||
_instance = _decorate_polymorphic_switch(
|
||||
_instance, context, mapper, result, path,
|
||||
polymorphic_discriminator, adapter)
|
||||
|
||||
return _instance
|
||||
|
||||
|
||||
def _populate_full(
|
||||
context, row, state, dict_, isnew, load_path,
|
||||
loaded_instance, populate_existing, populators):
|
||||
if isnew:
|
||||
# first time we are seeing a row with this identity.
|
||||
state.runid = context.runid
|
||||
|
||||
for key, getter in populators["quick"]:
|
||||
dict_[key] = getter(row)
|
||||
if populate_existing:
|
||||
for key, set_callable in populators["expire"]:
|
||||
dict_.pop(key, None)
|
||||
if set_callable:
|
||||
state.expired_attributes.add(key)
|
||||
else:
|
||||
for key, set_callable in populators["expire"]:
|
||||
if set_callable:
|
||||
state.expired_attributes.add(key)
|
||||
for key, populator in populators["new"]:
|
||||
populator(state, dict_, row)
|
||||
for key, populator in populators["delayed"]:
|
||||
populator(state, dict_, row)
|
||||
elif load_path != state.load_path:
|
||||
# new load path, e.g. object is present in more than one
|
||||
# column position in a series of rows
|
||||
state.load_path = load_path
|
||||
|
||||
# if we have data, and the data isn't in the dict, OK, let's put
|
||||
# it in.
|
||||
for key, getter in populators["quick"]:
|
||||
if key not in dict_:
|
||||
dict_[key] = getter(row)
|
||||
|
||||
# otherwise treat like an "already seen" row
|
||||
for key, populator in populators["existing"]:
|
||||
populator(state, dict_, row)
|
||||
# TODO: allow "existing" populator to know this is
|
||||
# a new path for the state:
|
||||
# populator(state, dict_, row, new_path=True)
|
||||
|
||||
else:
|
||||
# have already seen rows with this identity in this same path.
|
||||
for key, populator in populators["existing"]:
|
||||
populator(state, dict_, row)
|
||||
|
||||
# TODO: same path
|
||||
# populator(state, dict_, row, new_path=False)
|
||||
|
||||
|
||||
def _populate_partial(
|
||||
context, row, state, dict_, isnew, load_path,
|
||||
unloaded, populators):
|
||||
|
||||
if not isnew:
|
||||
to_load = context.partials[state]
|
||||
for key, populator in populators["existing"]:
|
||||
if key in to_load:
|
||||
populator(state, dict_, row)
|
||||
else:
|
||||
to_load = unloaded
|
||||
context.partials[state] = to_load
|
||||
|
||||
for key, getter in populators["quick"]:
|
||||
if key in to_load:
|
||||
dict_[key] = getter(row)
|
||||
for key, set_callable in populators["expire"]:
|
||||
if key in to_load:
|
||||
dict_.pop(key, None)
|
||||
if set_callable:
|
||||
state.expired_attributes.add(key)
|
||||
for key, populator in populators["new"]:
|
||||
if key in to_load:
|
||||
populator(state, dict_, row)
|
||||
for key, populator in populators["delayed"]:
|
||||
if key in to_load:
|
||||
populator(state, dict_, row)
|
||||
for key, populator in populators["eager"]:
|
||||
if key not in unloaded:
|
||||
populator(state, dict_, row)
|
||||
|
||||
return to_load
|
||||
|
||||
|
||||
def _validate_version_id(mapper, state, dict_, row, adapter):
|
||||
|
||||
version_id_col = mapper.version_id_col
|
||||
|
||||
if version_id_col is None:
|
||||
return
|
||||
|
||||
if adapter:
|
||||
version_id_col = adapter.columns[version_id_col]
|
||||
|
||||
if mapper._get_state_attr_by_column(
|
||||
state, dict_, mapper.version_id_col) != row[version_id_col]:
|
||||
raise orm_exc.StaleDataError(
|
||||
"Instance '%s' has version id '%s' which "
|
||||
"does not match database-loaded version id '%s'."
|
||||
% (state_str(state), mapper._get_state_attr_by_column(
|
||||
state, dict_, mapper.version_id_col),
|
||||
row[version_id_col]))
|
||||
|
||||
|
||||
def _decorate_polymorphic_switch(
|
||||
instance_fn, context, mapper, result, path,
|
||||
polymorphic_discriminator, adapter):
|
||||
if polymorphic_discriminator is not None:
|
||||
polymorphic_on = polymorphic_discriminator
|
||||
else:
|
||||
polymorphic_on = mapper.polymorphic_on
|
||||
if polymorphic_on is None:
|
||||
return instance_fn
|
||||
|
||||
if adapter:
|
||||
polymorphic_on = adapter.columns[polymorphic_on]
|
||||
|
||||
def configure_subclass_mapper(discriminator):
|
||||
try:
|
||||
sub_mapper = mapper.polymorphic_map[discriminator]
|
||||
except KeyError:
|
||||
raise AssertionError(
|
||||
"No such polymorphic_identity %r is defined" %
|
||||
discriminator)
|
||||
else:
|
||||
if sub_mapper is mapper:
|
||||
return None
|
||||
|
||||
return _instance_processor(
|
||||
sub_mapper, context, result,
|
||||
path, adapter, _polymorphic_from=mapper)
|
||||
|
||||
polymorphic_instances = util.PopulateDict(
|
||||
configure_subclass_mapper
|
||||
)
|
||||
|
||||
def polymorphic_instance(row):
|
||||
discriminator = row[polymorphic_on]
|
||||
if discriminator is not None:
|
||||
_instance = polymorphic_instances[discriminator]
|
||||
if _instance:
|
||||
return _instance(row)
|
||||
return instance_fn(row)
|
||||
return polymorphic_instance
|
||||
|
||||
|
||||
def load_scalar_attributes(mapper, state, attribute_names):
|
||||
"""initiate a column-based attribute refresh operation."""
|
||||
|
||||
# assert mapper is _state_mapper(state)
|
||||
session = state.session
|
||||
if not session:
|
||||
raise orm_exc.DetachedInstanceError(
|
||||
"Instance %s is not bound to a Session; "
|
||||
"attribute refresh operation cannot proceed" %
|
||||
(state_str(state)))
|
||||
|
||||
has_key = bool(state.key)
|
||||
|
||||
result = False
|
||||
|
||||
if mapper.inherits and not mapper.concrete:
|
||||
# because we are using Core to produce a select() that we
|
||||
# pass to the Query, we aren't calling setup() for mapped
|
||||
# attributes; in 1.0 this means deferred attrs won't get loaded
|
||||
# by default
|
||||
statement = mapper._optimized_get_statement(state, attribute_names)
|
||||
if statement is not None:
|
||||
result = load_on_ident(
|
||||
session.query(mapper).
|
||||
options(
|
||||
strategy_options.Load(mapper).undefer("*")
|
||||
).from_statement(statement),
|
||||
None,
|
||||
only_load_props=attribute_names,
|
||||
refresh_state=state
|
||||
)
|
||||
|
||||
if result is False:
|
||||
if has_key:
|
||||
identity_key = state.key
|
||||
else:
|
||||
# this codepath is rare - only valid when inside a flush, and the
|
||||
# object is becoming persistent but hasn't yet been assigned
|
||||
# an identity_key.
|
||||
# check here to ensure we have the attrs we need.
|
||||
pk_attrs = [mapper._columntoproperty[col].key
|
||||
for col in mapper.primary_key]
|
||||
if state.expired_attributes.intersection(pk_attrs):
|
||||
raise sa_exc.InvalidRequestError(
|
||||
"Instance %s cannot be refreshed - it's not "
|
||||
" persistent and does not "
|
||||
"contain a full primary key." % state_str(state))
|
||||
identity_key = mapper._identity_key_from_state(state)
|
||||
|
||||
if (_none_set.issubset(identity_key) and
|
||||
not mapper.allow_partial_pks) or \
|
||||
_none_set.issuperset(identity_key):
|
||||
util.warn_limited(
|
||||
"Instance %s to be refreshed doesn't "
|
||||
"contain a full primary key - can't be refreshed "
|
||||
"(and shouldn't be expired, either).",
|
||||
state_str(state))
|
||||
return
|
||||
|
||||
result = load_on_ident(
|
||||
session.query(mapper),
|
||||
identity_key,
|
||||
refresh_state=state,
|
||||
only_load_props=attribute_names)
|
||||
|
||||
# if instance is pending, a refresh operation
|
||||
# may not complete (even if PK attributes are assigned)
|
||||
if has_key and result is None:
|
||||
raise orm_exc.ObjectDeletedError(state)
|
||||
@@ -0,0 +1,271 @@
|
||||
# orm/path_registry.py
|
||||
# Copyright (C) 2005-2017 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
|
||||
"""Path tracking utilities, representing mapper graph traversals.
|
||||
|
||||
"""
|
||||
|
||||
from .. import inspection
|
||||
from .. import util
|
||||
from .. import exc
|
||||
from itertools import chain
|
||||
from .base import class_mapper
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _unreduce_path(path):
|
||||
return PathRegistry.deserialize(path)
|
||||
|
||||
|
||||
_WILDCARD_TOKEN = "*"
|
||||
_DEFAULT_TOKEN = "_sa_default"
|
||||
|
||||
|
||||
class PathRegistry(object):
|
||||
"""Represent query load paths and registry functions.
|
||||
|
||||
Basically represents structures like:
|
||||
|
||||
(<User mapper>, "orders", <Order mapper>, "items", <Item mapper>)
|
||||
|
||||
These structures are generated by things like
|
||||
query options (joinedload(), subqueryload(), etc.) and are
|
||||
used to compose keys stored in the query._attributes dictionary
|
||||
for various options.
|
||||
|
||||
They are then re-composed at query compile/result row time as
|
||||
the query is formed and as rows are fetched, where they again
|
||||
serve to compose keys to look up options in the context.attributes
|
||||
dictionary, which is copied from query._attributes.
|
||||
|
||||
The path structure has a limited amount of caching, where each
|
||||
"root" ultimately pulls from a fixed registry associated with
|
||||
the first mapper, that also contains elements for each of its
|
||||
property keys. However paths longer than two elements, which
|
||||
are the exception rather than the rule, are generated on an
|
||||
as-needed basis.
|
||||
|
||||
"""
|
||||
|
||||
is_token = False
|
||||
is_root = False
|
||||
|
||||
def __eq__(self, other):
|
||||
return other is not None and \
|
||||
self.path == other.path
|
||||
|
||||
def set(self, attributes, key, value):
|
||||
log.debug("set '%s' on path '%s' to '%s'", key, self, value)
|
||||
attributes[(key, self.path)] = value
|
||||
|
||||
def setdefault(self, attributes, key, value):
|
||||
log.debug("setdefault '%s' on path '%s' to '%s'", key, self, value)
|
||||
attributes.setdefault((key, self.path), value)
|
||||
|
||||
def get(self, attributes, key, value=None):
|
||||
key = (key, self.path)
|
||||
if key in attributes:
|
||||
return attributes[key]
|
||||
else:
|
||||
return value
|
||||
|
||||
def __len__(self):
|
||||
return len(self.path)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return len(self.path)
|
||||
|
||||
def pairs(self):
|
||||
path = self.path
|
||||
for i in range(0, len(path), 2):
|
||||
yield path[i], path[i + 1]
|
||||
|
||||
def contains_mapper(self, mapper):
|
||||
for path_mapper in [
|
||||
self.path[i] for i in range(0, len(self.path), 2)
|
||||
]:
|
||||
if path_mapper.is_mapper and \
|
||||
path_mapper.isa(mapper):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def contains(self, attributes, key):
|
||||
return (key, self.path) in attributes
|
||||
|
||||
def __reduce__(self):
|
||||
return _unreduce_path, (self.serialize(), )
|
||||
|
||||
def serialize(self):
|
||||
path = self.path
|
||||
return list(zip(
|
||||
[m.class_ for m in [path[i] for i in range(0, len(path), 2)]],
|
||||
[path[i].key for i in range(1, len(path), 2)] + [None]
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, path):
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
p = tuple(chain(*[(class_mapper(mcls),
|
||||
class_mapper(mcls).attrs[key]
|
||||
if key is not None else None)
|
||||
for mcls, key in path]))
|
||||
if p and p[-1] is None:
|
||||
p = p[0:-1]
|
||||
return cls.coerce(p)
|
||||
|
||||
@classmethod
|
||||
def per_mapper(cls, mapper):
|
||||
return EntityRegistry(
|
||||
cls.root, mapper
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, raw):
|
||||
return util.reduce(lambda prev, next: prev[next], raw, cls.root)
|
||||
|
||||
def token(self, token):
|
||||
if token.endswith(':' + _WILDCARD_TOKEN):
|
||||
return TokenRegistry(self, token)
|
||||
elif token.endswith(":" + _DEFAULT_TOKEN):
|
||||
return TokenRegistry(self.root, token)
|
||||
else:
|
||||
raise exc.ArgumentError("invalid token: %s" % token)
|
||||
|
||||
def __add__(self, other):
|
||||
return util.reduce(
|
||||
lambda prev, next: prev[next],
|
||||
other.path, self)
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r)" % (self.__class__.__name__, self.path, )
|
||||
|
||||
|
||||
class RootRegistry(PathRegistry):
|
||||
"""Root registry, defers to mappers so that
|
||||
paths are maintained per-root-mapper.
|
||||
|
||||
"""
|
||||
path = ()
|
||||
has_entity = False
|
||||
is_aliased_class = False
|
||||
is_root = True
|
||||
|
||||
def __getitem__(self, entity):
|
||||
return entity._path_registry
|
||||
|
||||
PathRegistry.root = RootRegistry()
|
||||
|
||||
|
||||
class TokenRegistry(PathRegistry):
|
||||
def __init__(self, parent, token):
|
||||
self.token = token
|
||||
self.parent = parent
|
||||
self.path = parent.path + (token,)
|
||||
|
||||
has_entity = False
|
||||
|
||||
is_token = True
|
||||
|
||||
def generate_for_superclasses(self):
|
||||
if not self.parent.is_aliased_class and not self.parent.is_root:
|
||||
for ent in self.parent.mapper.iterate_to_root():
|
||||
yield TokenRegistry(self.parent.parent[ent], self.token)
|
||||
else:
|
||||
yield self
|
||||
|
||||
def __getitem__(self, entity):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class PropRegistry(PathRegistry):
|
||||
def __init__(self, parent, prop):
|
||||
# restate this path in terms of the
|
||||
# given MapperProperty's parent.
|
||||
insp = inspection.inspect(parent[-1])
|
||||
if not insp.is_aliased_class or insp._use_mapper_path:
|
||||
parent = parent.parent[prop.parent]
|
||||
elif insp.is_aliased_class and insp.with_polymorphic_mappers:
|
||||
if prop.parent is not insp.mapper and \
|
||||
prop.parent in insp.with_polymorphic_mappers:
|
||||
subclass_entity = parent[-1]._entity_for_mapper(prop.parent)
|
||||
parent = parent.parent[subclass_entity]
|
||||
|
||||
self.prop = prop
|
||||
self.parent = parent
|
||||
self.path = parent.path + (prop,)
|
||||
|
||||
self._wildcard_path_loader_key = (
|
||||
"loader",
|
||||
self.parent.path + self.prop._wildcard_token
|
||||
)
|
||||
self._default_path_loader_key = self.prop._default_path_loader_key
|
||||
self._loader_key = ("loader", self.path)
|
||||
|
||||
def __str__(self):
|
||||
return " -> ".join(
|
||||
str(elem) for elem in self.path
|
||||
)
|
||||
|
||||
@util.memoized_property
|
||||
def has_entity(self):
|
||||
return hasattr(self.prop, "mapper")
|
||||
|
||||
@util.memoized_property
|
||||
def entity(self):
|
||||
return self.prop.mapper
|
||||
|
||||
@property
|
||||
def mapper(self):
|
||||
return self.entity
|
||||
|
||||
@property
|
||||
def entity_path(self):
|
||||
return self[self.entity]
|
||||
|
||||
def __getitem__(self, entity):
|
||||
if isinstance(entity, (int, slice)):
|
||||
return self.path[entity]
|
||||
else:
|
||||
return EntityRegistry(
|
||||
self, entity
|
||||
)
|
||||
|
||||
|
||||
class EntityRegistry(PathRegistry, dict):
|
||||
is_aliased_class = False
|
||||
has_entity = True
|
||||
|
||||
def __init__(self, parent, entity):
|
||||
self.key = entity
|
||||
self.parent = parent
|
||||
self.is_aliased_class = entity.is_aliased_class
|
||||
self.entity = entity
|
||||
self.path = parent.path + (entity,)
|
||||
self.entity_path = self
|
||||
|
||||
@property
|
||||
def mapper(self):
|
||||
return inspection.inspect(self.entity).mapper
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
__nonzero__ = __bool__
|
||||
|
||||
def __getitem__(self, entity):
|
||||
if isinstance(entity, (int, slice)):
|
||||
return self.path[entity]
|
||||
else:
|
||||
return dict.__getitem__(self, entity)
|
||||
|
||||
def __missing__(self, key):
|
||||
self[key] = item = PropRegistry(self, key)
|
||||
return item
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user