Learn more  » Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Bower components Debian packages RPM packages NuGet packages

aroundthecode / SQLAlchemy   python

Repository URL to install this package:

Version: 1.2.10 

/ orm / attributes.py

# orm/attributes.py
# Copyright (C) 2005-2018 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 instrumentation for class attributes and their interaction
with instances.

This module is usually not directly visible to user applications, but
defines a large part of the ORM's interactivity.


"""

import operator
from .. import util, event, inspection
from . import interfaces, collections, exc as orm_exc

from .base import instance_state, instance_dict, manager_of_class

from .base import PASSIVE_NO_RESULT, ATTR_WAS_SET, ATTR_EMPTY, NO_VALUE,\
    NEVER_SET, NO_CHANGE, CALLABLES_OK, SQL_OK, RELATED_OBJECT_OK,\
    INIT_OK, NON_PERSISTENT_OK, LOAD_AGAINST_COMMITTED, PASSIVE_OFF,\
    PASSIVE_RETURN_NEVER_SET, PASSIVE_NO_INITIALIZE, PASSIVE_NO_FETCH,\
    PASSIVE_NO_FETCH_RELATED, PASSIVE_ONLY_PERSISTENT, NO_AUTOFLUSH
from .base import state_str, instance_str


@inspection._self_inspects
class QueryableAttribute(interfaces._MappedAttribute,
                         interfaces.InspectionAttr,
                         interfaces.PropComparator):
    """Base class for :term:`descriptor` objects that intercept
    attribute events on behalf of a :class:`.MapperProperty`
    object.  The actual :class:`.MapperProperty` is accessible
    via the :attr:`.QueryableAttribute.property`
    attribute.


    .. seealso::

        :class:`.InstrumentedAttribute`

        :class:`.MapperProperty`

        :attr:`.Mapper.all_orm_descriptors`

        :attr:`.Mapper.attrs`
    """

    is_attribute = True

    def __init__(self, class_, key, impl=None,
                 comparator=None, parententity=None,
                 of_type=None):
        self.class_ = class_
        self.key = key
        self.impl = impl
        self.comparator = comparator
        self._parententity = parententity
        self._of_type = of_type

        manager = manager_of_class(class_)
        # manager is None in the case of AliasedClass
        if manager:
            # propagate existing event listeners from
            # immediate superclass
            for base in manager._bases:
                if key in base:
                    self.dispatch._update(base[key].dispatch)

    @util.memoized_property
    def _supports_population(self):
        return self.impl.supports_population

    def get_history(self, instance, passive=PASSIVE_OFF):
        return self.impl.get_history(instance_state(instance),
                                     instance_dict(instance), passive)

    def __selectable__(self):
        # TODO: conditionally attach this method based on clause_element ?
        return self

    @util.memoized_property
    def info(self):
        """Return the 'info' dictionary for the underlying SQL element.

        The behavior here is as follows:

        * If the attribute is a column-mapped property, i.e.
          :class:`.ColumnProperty`, which is mapped directly
          to a schema-level :class:`.Column` object, this attribute
          will return the :attr:`.SchemaItem.info` dictionary associated
          with the core-level :class:`.Column` object.

        * If the attribute is a :class:`.ColumnProperty` but is mapped to
          any other kind of SQL expression other than a :class:`.Column`,
          the attribute will refer to the :attr:`.MapperProperty.info`
          dictionary associated directly with the :class:`.ColumnProperty`,
          assuming the SQL expression itself does not have its own ``.info``
          attribute (which should be the case, unless a user-defined SQL
          construct has defined one).

        * If the attribute refers to any other kind of
          :class:`.MapperProperty`, including :class:`.RelationshipProperty`,
          the attribute will refer to the :attr:`.MapperProperty.info`
          dictionary associated with that :class:`.MapperProperty`.

        * To access the :attr:`.MapperProperty.info` dictionary of the
          :class:`.MapperProperty` unconditionally, including for a
          :class:`.ColumnProperty` that's associated directly with a
          :class:`.schema.Column`, the attribute can be referred to using
          :attr:`.QueryableAttribute.property` attribute, as
          ``MyClass.someattribute.property.info``.

        .. versionadded:: 0.8.0

        .. seealso::

            :attr:`.SchemaItem.info`

            :attr:`.MapperProperty.info`

        """
        return self.comparator.info

    @util.memoized_property
    def parent(self):
        """Return an inspection instance representing the parent.

        This will be either an instance of :class:`.Mapper`
        or :class:`.AliasedInsp`, depending upon the nature
        of the parent entity which this attribute is associated
        with.

        """
        return inspection.inspect(self._parententity)

    @property
    def expression(self):
        return self.comparator.__clause_element__()

    def __clause_element__(self):
        return self.comparator.__clause_element__()

    def _query_clause_element(self):
        """like __clause_element__(), but called specifically
        by :class:`.Query` to allow special behavior."""

        return self.comparator._query_clause_element()

    def _bulk_update_tuples(self, value):
        """Return setter tuples for a bulk UPDATE."""

        return self.comparator._bulk_update_tuples(value)

    def adapt_to_entity(self, adapt_to_entity):
        assert not self._of_type
        return self.__class__(adapt_to_entity.entity,
                              self.key, impl=self.impl,
                              comparator=self.comparator.adapt_to_entity(
                                  adapt_to_entity),
                              parententity=adapt_to_entity)

    def of_type(self, cls):
        return QueryableAttribute(
            self.class_,
            self.key,
            self.impl,
            self.comparator.of_type(cls),
            self._parententity,
            of_type=cls)

    def label(self, name):
        return self._query_clause_element().label(name)

    def operate(self, op, *other, **kwargs):
        return op(self.comparator, *other, **kwargs)

    def reverse_operate(self, op, other, **kwargs):
        return op(other, self.comparator, **kwargs)

    def hasparent(self, state, optimistic=False):
        return self.impl.hasparent(state, optimistic=optimistic) is not False

    def __getattr__(self, key):
        try:
            return getattr(self.comparator, key)
        except AttributeError:
            raise AttributeError(
                'Neither %r object nor %r object associated with %s '
                'has an attribute %r' % (
                    type(self).__name__,
                    type(self.comparator).__name__,
                    self,
                    key)
            )

    def __str__(self):
        return "%s.%s" % (self.class_.__name__, self.key)

    @util.memoized_property
    def property(self):
        """Return the :class:`.MapperProperty` associated with this
        :class:`.QueryableAttribute`.


        Return values here will commonly be instances of
        :class:`.ColumnProperty` or :class:`.RelationshipProperty`.


        """
        return self.comparator.property


class InstrumentedAttribute(QueryableAttribute):
    """Class bound instrumented attribute which adds basic
    :term:`descriptor` methods.

    See :class:`.QueryableAttribute` for a description of most features.


    """

    def __set__(self, instance, value):
        self.impl.set(instance_state(instance),
                      instance_dict(instance), value, None)

    def __delete__(self, instance):
        self.impl.delete(instance_state(instance), instance_dict(instance))

    def __get__(self, instance, owner):
        if instance is None:
            return self

        dict_ = instance_dict(instance)
        if self._supports_population and self.key in dict_:
            return dict_[self.key]
        else:
            return self.impl.get(instance_state(instance), dict_)


def create_proxied_attribute(descriptor):
    """Create an QueryableAttribute / user descriptor hybrid.

    Returns a new QueryableAttribute type that delegates descriptor
    behavior and getattr() to the given descriptor.
    """

    # TODO: can move this to descriptor_props if the need for this
    # function is removed from ext/hybrid.py

    class Proxy(QueryableAttribute):
        """Presents the :class:`.QueryableAttribute` interface as a
        proxy on top of a Python descriptor / :class:`.PropComparator`
        combination.

        """

        def __init__(self, class_, key, descriptor,
                     comparator,
                     adapt_to_entity=None, doc=None,
                     original_property=None):
            self.class_ = class_
            self.key = key
            self.descriptor = descriptor
            self.original_property = original_property
            self._comparator = comparator
            self._adapt_to_entity = adapt_to_entity
            self.__doc__ = doc

        @property
        def property(self):
            return self.comparator.property

        @util.memoized_property
        def comparator(self):
            if util.callable(self._comparator):
                self._comparator = self._comparator()
            if self._adapt_to_entity:
                self._comparator = self._comparator.adapt_to_entity(
                    self._adapt_to_entity)
            return self._comparator

        def adapt_to_entity(self, adapt_to_entity):
            return self.__class__(adapt_to_entity.entity,
                                  self.key,
                                  self.descriptor,
                                  self._comparator,
                                  adapt_to_entity)

        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return self.descriptor.__get__(instance, owner)

        def __str__(self):
            return "%s.%s" % (self.class_.__name__, self.key)

        def __getattr__(self, attribute):
            """Delegate __getattr__ to the original descriptor and/or
            comparator."""

            try:
                return getattr(descriptor, attribute)
            except AttributeError:
                try:
                    return getattr(self.comparator, attribute)
                except AttributeError:
                    raise AttributeError(
                        'Neither %r object nor %r object associated with %s '
                        'has an attribute %r' % (
                            type(descriptor).__name__,
                            type(self.comparator).__name__,
                            self,
                            attribute)
                    )

    Proxy.__name__ = type(descriptor).__name__ + 'Proxy'

    util.monkeypatch_proxied_specials(Proxy, type(descriptor),
                                      name='descriptor',
                                      from_instance=descriptor)
    return Proxy

OP_REMOVE = util.symbol("REMOVE")
OP_APPEND = util.symbol("APPEND")
OP_REPLACE = util.symbol("REPLACE")
OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
OP_MODIFIED = util.symbol("MODIFIED")


class Event(object):
    """A token propagated throughout the course of a chain of attribute
    events.

    Serves as an indicator of the source of the event and also provides
    a means of controlling propagation across a chain of attribute
    operations.

    The :class:`.Event` object is sent as the ``initiator`` argument
    when dealing with events such as :meth:`.AttributeEvents.append`,
Loading ...