1# orm/base.py
2# Copyright (C) 2005-2019 the SQLAlchemy authors and contributors
3# <see AUTHORS file>
4#
5# This module is part of SQLAlchemy and is released under
6# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
8"""Constants and rudimental functions used throughout the ORM.
9
10"""
11
12import operator
13
14from . import exc
15from .. import exc as sa_exc
16from .. import inspection
17from .. import util
18from ..sql import expression
19
20PASSIVE_NO_RESULT = util.symbol(
21    "PASSIVE_NO_RESULT",
22    """Symbol returned by a loader callable or other attribute/history
23    retrieval operation when a value could not be determined, based
24    on loader callable flags.
25    """,
26)
27
28ATTR_WAS_SET = util.symbol(
29    "ATTR_WAS_SET",
30    """Symbol returned by a loader callable to indicate the
31    retrieved value, or values, were assigned to their attributes
32    on the target object.
33    """,
34)
35
36ATTR_EMPTY = util.symbol(
37    "ATTR_EMPTY",
38    """Symbol used internally to indicate an attribute had no callable.""",
39)
40
41NO_VALUE = util.symbol(
42    "NO_VALUE",
43    """Symbol which may be placed as the 'previous' value of an attribute,
44    indicating no value was loaded for an attribute when it was modified,
45    and flags indicated we were not to load it.
46    """,
47)
48
49NEVER_SET = util.symbol(
50    "NEVER_SET",
51    """Symbol which may be placed as the 'previous' value of an attribute
52    indicating that the attribute had not been assigned to previously.
53    """,
54)
55
56NO_CHANGE = util.symbol(
57    "NO_CHANGE",
58    """No callables or SQL should be emitted on attribute access
59    and no state should change
60    """,
61    canonical=0,
62)
63
64CALLABLES_OK = util.symbol(
65    "CALLABLES_OK",
66    """Loader callables can be fired off if a value
67    is not present.
68    """,
69    canonical=1,
70)
71
72SQL_OK = util.symbol(
73    "SQL_OK",
74    """Loader callables can emit SQL at least on scalar value attributes.""",
75    canonical=2,
76)
77
78RELATED_OBJECT_OK = util.symbol(
79    "RELATED_OBJECT_OK",
80    """Callables can use SQL to load related objects as well
81    as scalar value attributes.
82    """,
83    canonical=4,
84)
85
86INIT_OK = util.symbol(
87    "INIT_OK",
88    """Attributes should be initialized with a blank
89    value (None or an empty collection) upon get, if no other
90    value can be obtained.
91    """,
92    canonical=8,
93)
94
95NON_PERSISTENT_OK = util.symbol(
96    "NON_PERSISTENT_OK",
97    """Callables can be emitted if the parent is not persistent.""",
98    canonical=16,
99)
100
101LOAD_AGAINST_COMMITTED = util.symbol(
102    "LOAD_AGAINST_COMMITTED",
103    """Callables should use committed values as primary/foreign keys during a
104    load.
105    """,
106    canonical=32,
107)
108
109NO_AUTOFLUSH = util.symbol(
110    "NO_AUTOFLUSH",
111    """Loader callables should disable autoflush.""",
112    canonical=64,
113)
114
115# pre-packaged sets of flags used as inputs
116PASSIVE_OFF = util.symbol(
117    "PASSIVE_OFF",
118    "Callables can be emitted in all cases.",
119    canonical=(
120        RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK
121    ),
122)
123PASSIVE_RETURN_NEVER_SET = util.symbol(
124    "PASSIVE_RETURN_NEVER_SET",
125    """PASSIVE_OFF ^ INIT_OK""",
126    canonical=PASSIVE_OFF ^ INIT_OK,
127)
128PASSIVE_NO_INITIALIZE = util.symbol(
129    "PASSIVE_NO_INITIALIZE",
130    "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK",
131    canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK,
132)
133PASSIVE_NO_FETCH = util.symbol(
134    "PASSIVE_NO_FETCH", "PASSIVE_OFF ^ SQL_OK", canonical=PASSIVE_OFF ^ SQL_OK
135)
136PASSIVE_NO_FETCH_RELATED = util.symbol(
137    "PASSIVE_NO_FETCH_RELATED",
138    "PASSIVE_OFF ^ RELATED_OBJECT_OK",
139    canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK,
140)
141PASSIVE_ONLY_PERSISTENT = util.symbol(
142    "PASSIVE_ONLY_PERSISTENT",
143    "PASSIVE_OFF ^ NON_PERSISTENT_OK",
144    canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK,
145)
146
147DEFAULT_MANAGER_ATTR = "_sa_class_manager"
148DEFAULT_STATE_ATTR = "_sa_instance_state"
149_INSTRUMENTOR = ("mapper", "instrumentor")
150
151EXT_CONTINUE = util.symbol("EXT_CONTINUE")
152EXT_STOP = util.symbol("EXT_STOP")
153
154ONETOMANY = util.symbol(
155    "ONETOMANY",
156    """Indicates the one-to-many direction for a :func:`.relationship`.
157
158    This symbol is typically used by the internals but may be exposed within
159    certain API features.
160
161    """,
162)
163
164MANYTOONE = util.symbol(
165    "MANYTOONE",
166    """Indicates the many-to-one direction for a :func:`.relationship`.
167
168    This symbol is typically used by the internals but may be exposed within
169    certain API features.
170
171    """,
172)
173
174MANYTOMANY = util.symbol(
175    "MANYTOMANY",
176    """Indicates the many-to-many direction for a :func:`.relationship`.
177
178    This symbol is typically used by the internals but may be exposed within
179    certain API features.
180
181    """,
182)
183
184NOT_EXTENSION = util.symbol(
185    "NOT_EXTENSION",
186    """Symbol indicating an :class:`InspectionAttr` that's
187    not part of sqlalchemy.ext.
188
189    Is assigned to the :attr:`.InspectionAttr.extension_type`
190    attribute.
191
192    """,
193)
194
195_never_set = frozenset([NEVER_SET])
196
197_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
198
199_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
200
201_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
202
203
204def _generative(*assertions):
205    """Mark a method as generative, e.g. method-chained."""
206
207    @util.decorator
208    def generate(fn, *args, **kw):
209        self = args[0]._clone()
210        for assertion in assertions:
211            assertion(self, fn.__name__)
212        fn(self, *args[1:], **kw)
213        return self
214
215    return generate
216
217
218# these can be replaced by sqlalchemy.ext.instrumentation
219# if augmented class instrumentation is enabled.
220def manager_of_class(cls):
221    return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None)
222
223
224instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
225
226instance_dict = operator.attrgetter("__dict__")
227
228
229def instance_str(instance):
230    """Return a string describing an instance."""
231
232    return state_str(instance_state(instance))
233
234
235def state_str(state):
236    """Return a string describing an instance via its InstanceState."""
237
238    if state is None:
239        return "None"
240    else:
241        return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))
242
243
244def state_class_str(state):
245    """Return a string describing an instance's class via its
246    InstanceState.
247    """
248
249    if state is None:
250        return "None"
251    else:
252        return "<%s>" % (state.class_.__name__,)
253
254
255def attribute_str(instance, attribute):
256    return instance_str(instance) + "." + attribute
257
258
259def state_attribute_str(state, attribute):
260    return state_str(state) + "." + attribute
261
262
263def object_mapper(instance):
264    """Given an object, return the primary Mapper associated with the object
265    instance.
266
267    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
268    if no mapping is configured.
269
270    This function is available via the inspection system as::
271
272        inspect(instance).mapper
273
274    Using the inspection system will raise
275    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
276    not part of a mapping.
277
278    """
279    return object_state(instance).mapper
280
281
282def object_state(instance):
283    """Given an object, return the :class:`.InstanceState`
284    associated with the object.
285
286    Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
287    if no mapping is configured.
288
289    Equivalent functionality is available via the :func:`.inspect`
290    function as::
291
292        inspect(instance)
293
294    Using the inspection system will raise
295    :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
296    not part of a mapping.
297
298    """
299    state = _inspect_mapped_object(instance)
300    if state is None:
301        raise exc.UnmappedInstanceError(instance)
302    else:
303        return state
304
305
306@inspection._inspects(object)
307def _inspect_mapped_object(instance):
308    try:
309        return instance_state(instance)
310        # TODO: whats the py-2/3 syntax to catch two
311        # different kinds of exceptions at once ?
312    except exc.UnmappedClassError:
313        return None
314    except exc.NO_STATE:
315        return None
316
317
318def _class_to_mapper(class_or_mapper):
319    insp = inspection.inspect(class_or_mapper, False)
320    if insp is not None:
321        return insp.mapper
322    else:
323        raise exc.UnmappedClassError(class_or_mapper)
324
325
326def _mapper_or_none(entity):
327    """Return the :class:`.Mapper` for the given class or None if the
328    class is not mapped.
329    """
330
331    insp = inspection.inspect(entity, False)
332    if insp is not None:
333        return insp.mapper
334    else:
335        return None
336
337
338def _is_mapped_class(entity):
339    """Return True if the given object is a mapped class,
340    :class:`.Mapper`, or :class:`.AliasedClass`.
341    """
342
343    insp = inspection.inspect(entity, False)
344    return (
345        insp is not None
346        and not insp.is_clause_element
347        and (insp.is_mapper or insp.is_aliased_class)
348    )
349
350
351def _attr_as_key(attr):
352    if hasattr(attr, "key"):
353        return attr.key
354    else:
355        return expression._column_as_key(attr)
356
357
358def _orm_columns(entity):
359    insp = inspection.inspect(entity, False)
360    if hasattr(insp, "selectable") and hasattr(insp.selectable, "c"):
361        return [c for c in insp.selectable.c]
362    else:
363        return [entity]
364
365
366def _is_aliased_class(entity):
367    insp = inspection.inspect(entity, False)
368    return insp is not None and getattr(insp, "is_aliased_class", False)
369
370
371def _entity_descriptor(entity, key):
372    """Return a class attribute given an entity and string name.
373
374    May return :class:`.InstrumentedAttribute` or user-defined
375    attribute.
376
377    """
378    insp = inspection.inspect(entity)
379    if insp.is_selectable:
380        description = entity
381        entity = insp.c
382    elif insp.is_aliased_class:
383        entity = insp.entity
384        description = entity
385    elif hasattr(insp, "mapper"):
386        description = entity = insp.mapper.class_
387    else:
388        description = entity
389
390    try:
391        return getattr(entity, key)
392    except AttributeError:
393        raise sa_exc.InvalidRequestError(
394            "Entity '%s' has no property '%s'" % (description, key)
395        )
396
397
398_state_mapper = util.dottedgetter("manager.mapper")
399
400
401@inspection._inspects(type)
402def _inspect_mapped_class(class_, configure=False):
403    try:
404        class_manager = manager_of_class(class_)
405        if not class_manager.is_mapped:
406            return None
407        mapper = class_manager.mapper
408    except exc.NO_STATE:
409        return None
410    else:
411        if configure and mapper._new_mappers:
412            mapper._configure_all()
413        return mapper
414
415
416def class_mapper(class_, configure=True):
417    """Given a class, return the primary :class:`.Mapper` associated
418    with the key.
419
420    Raises :exc:`.UnmappedClassError` if no mapping is configured
421    on the given class, or :exc:`.ArgumentError` if a non-class
422    object is passed.
423
424    Equivalent functionality is available via the :func:`.inspect`
425    function as::
426
427        inspect(some_mapped_class)
428
429    Using the inspection system will raise
430    :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
431
432    """
433    mapper = _inspect_mapped_class(class_, configure=configure)
434    if mapper is None:
435        if not isinstance(class_, type):
436            raise sa_exc.ArgumentError(
437                "Class object expected, got '%r'." % (class_,)
438            )
439        raise exc.UnmappedClassError(class_)
440    else:
441        return mapper
442
443
444class InspectionAttr(object):
445    """A base class applied to all ORM objects that can be returned
446    by the :func:`.inspect` function.
447
448    The attributes defined here allow the usage of simple boolean
449    checks to test basic facts about the object returned.
450
451    While the boolean checks here are basically the same as using
452    the Python isinstance() function, the flags here can be used without
453    the need to import all of these classes, and also such that
454    the SQLAlchemy class system can change while leaving the flags
455    here intact for forwards-compatibility.
456
457    """
458
459    __slots__ = ()
460
461    is_selectable = False
462    """Return True if this object is an instance of :class:`.Selectable`."""
463
464    is_aliased_class = False
465    """True if this object is an instance of :class:`.AliasedClass`."""
466
467    is_instance = False
468    """True if this object is an instance of :class:`.InstanceState`."""
469
470    is_mapper = False
471    """True if this object is an instance of :class:`.Mapper`."""
472
473    is_property = False
474    """True if this object is an instance of :class:`.MapperProperty`."""
475
476    is_attribute = False
477    """True if this object is a Python :term:`descriptor`.
478
479    This can refer to one of many types.   Usually a
480    :class:`.QueryableAttribute` which handles attributes events on behalf
481    of a :class:`.MapperProperty`.   But can also be an extension type
482    such as :class:`.AssociationProxy` or :class:`.hybrid_property`.
483    The :attr:`.InspectionAttr.extension_type` will refer to a constant
484    identifying the specific subtype.
485
486    .. seealso::
487
488        :attr:`.Mapper.all_orm_descriptors`
489
490    """
491
492    _is_internal_proxy = False
493    """True if this object is an internal proxy object.
494
495    .. versionadded:: 1.2.12
496
497    """
498
499    is_clause_element = False
500    """True if this object is an instance of :class:`.ClauseElement`."""
501
502    extension_type = NOT_EXTENSION
503    """The extension type, if any.
504    Defaults to :data:`.interfaces.NOT_EXTENSION`
505
506    .. seealso::
507
508        :data:`.HYBRID_METHOD`
509
510        :data:`.HYBRID_PROPERTY`
511
512        :data:`.ASSOCIATION_PROXY`
513
514    """
515
516
517class InspectionAttrInfo(InspectionAttr):
518    """Adds the ``.info`` attribute to :class:`.InspectionAttr`.
519
520    The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo`
521    is that the former is compatible as a mixin for classes that specify
522    ``__slots__``; this is essentially an implementation artifact.
523
524    """
525
526    @util.memoized_property
527    def info(self):
528        """Info dictionary associated with the object, allowing user-defined
529        data to be associated with this :class:`.InspectionAttr`.
530
531        The dictionary is generated when first accessed.  Alternatively,
532        it can be specified as a constructor argument to the
533        :func:`.column_property`, :func:`.relationship`, or :func:`.composite`
534        functions.
535
536        .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also
537           available on extension types via the
538           :attr:`.InspectionAttrInfo.info` attribute, so that it can apply
539           to a wider variety of ORM and extension constructs.
540
541        .. seealso::
542
543            :attr:`.QueryableAttribute.info`
544
545            :attr:`.SchemaItem.info`
546
547        """
548        return {}
549
550
551class _MappedAttribute(object):
552    """Mixin for attributes which should be replaced by mapper-assigned
553    attributes.
554
555    """
556
557    __slots__ = ()
558