Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/base.py: 63%
157 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1# orm/base.py
2# Copyright (C) 2005-2023 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
8"""Constants and rudimental functions used throughout the ORM.
10"""
12import operator
14from . import exc
15from .. import exc as sa_exc
16from .. import inspection
17from .. import util
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)
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)
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)
43ATTR_EMPTY = util.symbol(
44 "ATTR_EMPTY",
45 """Symbol used internally to indicate an attribute had no callable.""",
46)
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
59.. versionchanged:: 1.4 NEVER_SET was merged with NO_VALUE
60"""
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)
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)
78SQL_OK = util.symbol(
79 "SQL_OK",
80 """Loader callables can emit SQL at least on scalar value attributes.""",
81 canonical=2,
82)
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)
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)
101NON_PERSISTENT_OK = util.symbol(
102 "NON_PERSISTENT_OK",
103 """Callables can be emitted if the parent is not persistent.""",
104 canonical=16,
105)
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)
115NO_AUTOFLUSH = util.symbol(
116 "NO_AUTOFLUSH",
117 """Loader callables should disable autoflush.""",
118 canonical=64,
119)
121NO_RAISE = util.symbol(
122 "NO_RAISE",
123 """Loader callables should not raise any assertions""",
124 canonical=128,
125)
127DEFERRED_HISTORY_LOAD = util.symbol(
128 "DEFERRED_HISTORY_LOAD",
129 """indicates special load of the previous value of an attribute""",
130 canonical=256,
131)
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)
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)
171DEFAULT_MANAGER_ATTR = "_sa_class_manager"
172DEFAULT_STATE_ATTR = "_sa_instance_state"
174EXT_CONTINUE = util.symbol("EXT_CONTINUE")
175EXT_STOP = util.symbol("EXT_STOP")
176EXT_SKIP = util.symbol("EXT_SKIP")
178ONETOMANY = util.symbol(
179 "ONETOMANY",
180 """Indicates the one-to-many direction for a :func:`_orm.relationship`.
182 This symbol is typically used by the internals but may be exposed within
183 certain API features.
185 """,
186)
188MANYTOONE = util.symbol(
189 "MANYTOONE",
190 """Indicates the many-to-one direction for a :func:`_orm.relationship`.
192 This symbol is typically used by the internals but may be exposed within
193 certain API features.
195 """,
196)
198MANYTOMANY = util.symbol(
199 "MANYTOMANY",
200 """Indicates the many-to-many direction for a :func:`_orm.relationship`.
202 This symbol is typically used by the internals but may be exposed within
203 certain API features.
205 """,
206)
208NOT_EXTENSION = util.symbol(
209 "NOT_EXTENSION",
210 """Symbol indicating an :class:`InspectionAttr` that's
211 not part of sqlalchemy.ext.
213 Is assigned to the :attr:`.InspectionAttr.extension_type`
214 attribute.
216 """,
217)
219_never_set = frozenset([NEVER_SET])
221_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
223_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
225_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
227_RAISE_FOR_STATE = util.symbol("RAISE_FOR_STATE")
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)
238 return generate
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)
247instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
249instance_dict = operator.attrgetter("__dict__")
252def instance_str(instance):
253 """Return a string describing an instance."""
255 return state_str(instance_state(instance))
258def state_str(state):
259 """Return a string describing an instance via its InstanceState."""
261 if state is None:
262 return "None"
263 else:
264 return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))
267def state_class_str(state):
268 """Return a string describing an instance's class via its
269 InstanceState.
270 """
272 if state is None:
273 return "None"
274 else:
275 return "<%s>" % (state.class_.__name__,)
278def attribute_str(instance, attribute):
279 return instance_str(instance) + "." + attribute
282def state_attribute_str(state, attribute):
283 return state_str(state) + "." + attribute
286def object_mapper(instance):
287 """Given an object, return the primary Mapper associated with the object
288 instance.
290 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
291 if no mapping is configured.
293 This function is available via the inspection system as::
295 inspect(instance).mapper
297 Using the inspection system will raise
298 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
299 not part of a mapping.
301 """
302 return object_state(instance).mapper
305def object_state(instance):
306 """Given an object, return the :class:`.InstanceState`
307 associated with the object.
309 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
310 if no mapping is configured.
312 Equivalent functionality is available via the :func:`_sa.inspect`
313 function as::
315 inspect(instance)
317 Using the inspection system will raise
318 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
319 not part of a mapping.
321 """
322 state = _inspect_mapped_object(instance)
323 if state is None:
324 raise exc.UnmappedInstanceError(instance)
325 else:
326 return state
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
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)
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 """
350 insp = inspection.inspect(entity, False)
351 if insp is not None:
352 return insp.mapper
353 else:
354 return None
357def _is_mapped_class(entity):
358 """Return True if the given object is a mapped class,
359 :class:`_orm.Mapper`, or :class:`.AliasedClass`.
360 """
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 )
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]
378def _is_aliased_class(entity):
379 insp = inspection.inspect(entity, False)
380 return insp is not None and getattr(insp, "is_aliased_class", False)
383def _entity_descriptor(entity, key):
384 """Return a class attribute given an entity and string name.
386 May return :class:`.InstrumentedAttribute` or user-defined
387 attribute.
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
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 )
413_state_mapper = util.dottedgetter("manager.mapper")
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
431def class_mapper(class_, configure=True):
432 """Given a class, return the primary :class:`_orm.Mapper` associated
433 with the key.
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.
439 Equivalent functionality is available via the :func:`_sa.inspect`
440 function as::
442 inspect(some_mapped_class)
444 Using the inspection system will raise
445 :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
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
459class InspectionAttr(object):
460 """A base class applied to all ORM objects that can be returned
461 by the :func:`_sa.inspect` function.
463 The attributes defined here allow the usage of simple boolean
464 checks to test basic facts about the object returned.
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.
472 """
474 __slots__ = ()
476 is_selectable = False
477 """Return True if this object is an instance of
478 :class:`_expression.Selectable`."""
480 is_aliased_class = False
481 """True if this object is an instance of :class:`.AliasedClass`."""
483 is_instance = False
484 """True if this object is an instance of :class:`.InstanceState`."""
486 is_mapper = False
487 """True if this object is an instance of :class:`_orm.Mapper`."""
489 is_bundle = False
490 """True if this object is an instance of :class:`.Bundle`."""
492 is_property = False
493 """True if this object is an instance of :class:`.MapperProperty`."""
495 is_attribute = False
496 """True if this object is a Python :term:`descriptor`.
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.
505 .. seealso::
507 :attr:`_orm.Mapper.all_orm_descriptors`
509 """
511 _is_internal_proxy = False
512 """True if this object is an internal proxy object.
514 .. versionadded:: 1.2.12
516 """
518 is_clause_element = False
519 """True if this object is an instance of
520 :class:`_expression.ClauseElement`."""
522 extension_type = NOT_EXTENSION
523 """The extension type, if any.
524 Defaults to :data:`.interfaces.NOT_EXTENSION`
526 .. seealso::
528 :data:`.HYBRID_METHOD`
530 :data:`.HYBRID_PROPERTY`
532 :data:`.ASSOCIATION_PROXY`
534 """
537class InspectionAttrInfo(InspectionAttr):
538 """Adds the ``.info`` attribute to :class:`.InspectionAttr`.
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.
544 """
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`.
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.
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.
562 .. seealso::
564 :attr:`.QueryableAttribute.info`
566 :attr:`.SchemaItem.info`
568 """
569 return {}
572class _MappedAttribute(object):
573 """Mixin for attributes which should be replaced by mapper-assigned
574 attributes.
576 """
578 __slots__ = ()