1# orm/base.py
2# Copyright (C) 2005-2024 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: https://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
18
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
28PASSIVE_CLASS_MISMATCH = util.symbol(
29 "PASSIVE_CLASS_MISMATCH",
30 """Symbol indicating that an object is locally present for a given
31 primary key identity but it is not of the requested class. The
32 return value is therefore None and no SQL should be emitted.""",
33)
34
35ATTR_WAS_SET = util.symbol(
36 "ATTR_WAS_SET",
37 """Symbol returned by a loader callable to indicate the
38 retrieved value, or values, were assigned to their attributes
39 on the target object.
40 """,
41)
42
43ATTR_EMPTY = util.symbol(
44 "ATTR_EMPTY",
45 """Symbol used internally to indicate an attribute had no callable.""",
46)
47
48NO_VALUE = util.symbol(
49 "NO_VALUE",
50 """Symbol which may be placed as the 'previous' value of an attribute,
51 indicating no value was loaded for an attribute when it was modified,
52 and flags indicated we were not to load it.
53 """,
54)
55NEVER_SET = NO_VALUE
56"""
57Synonymous with NO_VALUE
58
59.. versionchanged:: 1.4 NEVER_SET was merged with NO_VALUE
60"""
61
62NO_CHANGE = util.symbol(
63 "NO_CHANGE",
64 """No callables or SQL should be emitted on attribute access
65 and no state should change
66 """,
67 canonical=0,
68)
69
70CALLABLES_OK = util.symbol(
71 "CALLABLES_OK",
72 """Loader callables can be fired off if a value
73 is not present.
74 """,
75 canonical=1,
76)
77
78SQL_OK = util.symbol(
79 "SQL_OK",
80 """Loader callables can emit SQL at least on scalar value attributes.""",
81 canonical=2,
82)
83
84RELATED_OBJECT_OK = util.symbol(
85 "RELATED_OBJECT_OK",
86 """Callables can use SQL to load related objects as well
87 as scalar value attributes.
88 """,
89 canonical=4,
90)
91
92INIT_OK = util.symbol(
93 "INIT_OK",
94 """Attributes should be initialized with a blank
95 value (None or an empty collection) upon get, if no other
96 value can be obtained.
97 """,
98 canonical=8,
99)
100
101NON_PERSISTENT_OK = util.symbol(
102 "NON_PERSISTENT_OK",
103 """Callables can be emitted if the parent is not persistent.""",
104 canonical=16,
105)
106
107LOAD_AGAINST_COMMITTED = util.symbol(
108 "LOAD_AGAINST_COMMITTED",
109 """Callables should use committed values as primary/foreign keys during a
110 load.
111 """,
112 canonical=32,
113)
114
115NO_AUTOFLUSH = util.symbol(
116 "NO_AUTOFLUSH",
117 """Loader callables should disable autoflush.""",
118 canonical=64,
119)
120
121NO_RAISE = util.symbol(
122 "NO_RAISE",
123 """Loader callables should not raise any assertions""",
124 canonical=128,
125)
126
127DEFERRED_HISTORY_LOAD = util.symbol(
128 "DEFERRED_HISTORY_LOAD",
129 """indicates special load of the previous value of an attribute""",
130 canonical=256,
131)
132
133# pre-packaged sets of flags used as inputs
134PASSIVE_OFF = util.symbol(
135 "PASSIVE_OFF",
136 "Callables can be emitted in all cases.",
137 canonical=(
138 RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK
139 ),
140)
141PASSIVE_RETURN_NO_VALUE = util.symbol(
142 "PASSIVE_RETURN_NO_VALUE",
143 """PASSIVE_OFF ^ INIT_OK""",
144 canonical=PASSIVE_OFF ^ INIT_OK,
145)
146PASSIVE_NO_INITIALIZE = util.symbol(
147 "PASSIVE_NO_INITIALIZE",
148 "PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK",
149 canonical=PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK,
150)
151PASSIVE_NO_FETCH = util.symbol(
152 "PASSIVE_NO_FETCH", "PASSIVE_OFF ^ SQL_OK", canonical=PASSIVE_OFF ^ SQL_OK
153)
154PASSIVE_NO_FETCH_RELATED = util.symbol(
155 "PASSIVE_NO_FETCH_RELATED",
156 "PASSIVE_OFF ^ RELATED_OBJECT_OK",
157 canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK,
158)
159PASSIVE_ONLY_PERSISTENT = util.symbol(
160 "PASSIVE_ONLY_PERSISTENT",
161 "PASSIVE_OFF ^ NON_PERSISTENT_OK",
162 canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK,
163)
164
165PASSIVE_MERGE = util.symbol(
166 "PASSIVE_OFF | NO_RAISE",
167 "Symbol used specifically for session.merge() and similar cases",
168 canonical=PASSIVE_OFF | NO_RAISE,
169)
170
171DEFAULT_MANAGER_ATTR = "_sa_class_manager"
172DEFAULT_STATE_ATTR = "_sa_instance_state"
173
174EXT_CONTINUE = util.symbol("EXT_CONTINUE")
175EXT_STOP = util.symbol("EXT_STOP")
176EXT_SKIP = util.symbol("EXT_SKIP")
177
178ONETOMANY = util.symbol(
179 "ONETOMANY",
180 """Indicates the one-to-many direction for a :func:`_orm.relationship`.
181
182 This symbol is typically used by the internals but may be exposed within
183 certain API features.
184
185 """,
186)
187
188MANYTOONE = util.symbol(
189 "MANYTOONE",
190 """Indicates the many-to-one direction for a :func:`_orm.relationship`.
191
192 This symbol is typically used by the internals but may be exposed within
193 certain API features.
194
195 """,
196)
197
198MANYTOMANY = util.symbol(
199 "MANYTOMANY",
200 """Indicates the many-to-many direction for a :func:`_orm.relationship`.
201
202 This symbol is typically used by the internals but may be exposed within
203 certain API features.
204
205 """,
206)
207
208NOT_EXTENSION = util.symbol(
209 "NOT_EXTENSION",
210 """Symbol indicating an :class:`InspectionAttr` that's
211 not part of sqlalchemy.ext.
212
213 Is assigned to the :attr:`.InspectionAttr.extension_type`
214 attribute.
215
216 """,
217)
218
219_never_set = frozenset([NEVER_SET])
220
221_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
222
223_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
224
225_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
226
227_RAISE_FOR_STATE = util.symbol("RAISE_FOR_STATE")
228
229
230def _assertions(*assertions):
231 @util.decorator
232 def generate(fn, *args, **kw):
233 self = args[0]
234 for assertion in assertions:
235 assertion(self, fn.__name__)
236 fn(self, *args[1:], **kw)
237
238 return generate
239
240
241# these can be replaced by sqlalchemy.ext.instrumentation
242# if augmented class instrumentation is enabled.
243def manager_of_class(cls):
244 return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None)
245
246
247instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
248
249instance_dict = operator.attrgetter("__dict__")
250
251
252def instance_str(instance):
253 """Return a string describing an instance."""
254
255 return state_str(instance_state(instance))
256
257
258def state_str(state):
259 """Return a string describing an instance via its InstanceState."""
260
261 if state is None:
262 return "None"
263 else:
264 return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))
265
266
267def state_class_str(state):
268 """Return a string describing an instance's class via its
269 InstanceState.
270 """
271
272 if state is None:
273 return "None"
274 else:
275 return "<%s>" % (state.class_.__name__,)
276
277
278def attribute_str(instance, attribute):
279 return instance_str(instance) + "." + attribute
280
281
282def state_attribute_str(state, attribute):
283 return state_str(state) + "." + attribute
284
285
286def object_mapper(instance):
287 """Given an object, return the primary Mapper associated with the object
288 instance.
289
290 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
291 if no mapping is configured.
292
293 This function is available via the inspection system as::
294
295 inspect(instance).mapper
296
297 Using the inspection system will raise
298 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
299 not part of a mapping.
300
301 """
302 return object_state(instance).mapper
303
304
305def object_state(instance):
306 """Given an object, return the :class:`.InstanceState`
307 associated with the object.
308
309 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
310 if no mapping is configured.
311
312 Equivalent functionality is available via the :func:`_sa.inspect`
313 function as::
314
315 inspect(instance)
316
317 Using the inspection system will raise
318 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
319 not part of a mapping.
320
321 """
322 state = _inspect_mapped_object(instance)
323 if state is None:
324 raise exc.UnmappedInstanceError(instance)
325 else:
326 return state
327
328
329@inspection._inspects(object)
330def _inspect_mapped_object(instance):
331 try:
332 return instance_state(instance)
333 except (exc.UnmappedClassError,) + exc.NO_STATE:
334 return None
335
336
337def _class_to_mapper(class_or_mapper):
338 insp = inspection.inspect(class_or_mapper, False)
339 if insp is not None:
340 return insp.mapper
341 else:
342 raise exc.UnmappedClassError(class_or_mapper)
343
344
345def _mapper_or_none(entity):
346 """Return the :class:`_orm.Mapper` for the given class or None if the
347 class is not mapped.
348 """
349
350 insp = inspection.inspect(entity, False)
351 if insp is not None:
352 return insp.mapper
353 else:
354 return None
355
356
357def _is_mapped_class(entity):
358 """Return True if the given object is a mapped class,
359 :class:`_orm.Mapper`, or :class:`.AliasedClass`.
360 """
361
362 insp = inspection.inspect(entity, False)
363 return (
364 insp is not None
365 and not insp.is_clause_element
366 and (insp.is_mapper or insp.is_aliased_class)
367 )
368
369
370def _orm_columns(entity):
371 insp = inspection.inspect(entity, False)
372 if hasattr(insp, "selectable") and hasattr(insp.selectable, "c"):
373 return [c for c in insp.selectable.c]
374 else:
375 return [entity]
376
377
378def _is_aliased_class(entity):
379 insp = inspection.inspect(entity, False)
380 return insp is not None and getattr(insp, "is_aliased_class", False)
381
382
383def _entity_descriptor(entity, key):
384 """Return a class attribute given an entity and string name.
385
386 May return :class:`.InstrumentedAttribute` or user-defined
387 attribute.
388
389 """
390 insp = inspection.inspect(entity)
391 if insp.is_selectable:
392 description = entity
393 entity = insp.c
394 elif insp.is_aliased_class:
395 entity = insp.entity
396 description = entity
397 elif hasattr(insp, "mapper"):
398 description = entity = insp.mapper.class_
399 else:
400 description = entity
401
402 try:
403 return getattr(entity, key)
404 except AttributeError as err:
405 util.raise_(
406 sa_exc.InvalidRequestError(
407 "Entity '%s' has no property '%s'" % (description, key)
408 ),
409 replace_context=err,
410 )
411
412
413_state_mapper = util.dottedgetter("manager.mapper")
414
415
416@inspection._inspects(type)
417def _inspect_mapped_class(class_, configure=False):
418 try:
419 class_manager = manager_of_class(class_)
420 if not class_manager.is_mapped:
421 return None
422 mapper = class_manager.mapper
423 except exc.NO_STATE:
424 return None
425 else:
426 if configure:
427 mapper._check_configure()
428 return mapper
429
430
431def class_mapper(class_, configure=True):
432 """Given a class, return the primary :class:`_orm.Mapper` associated
433 with the key.
434
435 Raises :exc:`.UnmappedClassError` if no mapping is configured
436 on the given class, or :exc:`.ArgumentError` if a non-class
437 object is passed.
438
439 Equivalent functionality is available via the :func:`_sa.inspect`
440 function as::
441
442 inspect(some_mapped_class)
443
444 Using the inspection system will raise
445 :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
446
447 """
448 mapper = _inspect_mapped_class(class_, configure=configure)
449 if mapper is None:
450 if not isinstance(class_, type):
451 raise sa_exc.ArgumentError(
452 "Class object expected, got '%r'." % (class_,)
453 )
454 raise exc.UnmappedClassError(class_)
455 else:
456 return mapper
457
458
459class InspectionAttr(object):
460 """A base class applied to all ORM objects that can be returned
461 by the :func:`_sa.inspect` function.
462
463 The attributes defined here allow the usage of simple boolean
464 checks to test basic facts about the object returned.
465
466 While the boolean checks here are basically the same as using
467 the Python isinstance() function, the flags here can be used without
468 the need to import all of these classes, and also such that
469 the SQLAlchemy class system can change while leaving the flags
470 here intact for forwards-compatibility.
471
472 """
473
474 __slots__ = ()
475
476 is_selectable = False
477 """Return True if this object is an instance of
478 :class:`_expression.Selectable`."""
479
480 is_aliased_class = False
481 """True if this object is an instance of :class:`.AliasedClass`."""
482
483 is_instance = False
484 """True if this object is an instance of :class:`.InstanceState`."""
485
486 is_mapper = False
487 """True if this object is an instance of :class:`_orm.Mapper`."""
488
489 is_bundle = False
490 """True if this object is an instance of :class:`.Bundle`."""
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__ = ()