# orm/attributes.py
# Copyright (C) 2005-2013 the SQLAlchemy authors and contributors <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

"""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 operator import itemgetter

from .. import util, event, inspection
from . import interfaces, collections, events, exc as orm_exc
from .instrumentation import instance_state, instance_dict, manager_of_class

orm_util = util.importlater("sqlalchemy.orm", "util")

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
)

# 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
)


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):