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