Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/sqlalchemy/orm/base.py : 71%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# orm/base.py
2# Copyright (C) 2005-2020 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
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
18from ..sql import expression
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)
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)
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)
44ATTR_EMPTY = util.symbol(
45 "ATTR_EMPTY",
46 """Symbol used internally to indicate an attribute had no callable.""",
47)
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)
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)
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)
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)
80SQL_OK = util.symbol(
81 "SQL_OK",
82 """Loader callables can emit SQL at least on scalar value attributes.""",
83 canonical=2,
84)
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)
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)
103NON_PERSISTENT_OK = util.symbol(
104 "NON_PERSISTENT_OK",
105 """Callables can be emitted if the parent is not persistent.""",
106 canonical=16,
107)
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)
117NO_AUTOFLUSH = util.symbol(
118 "NO_AUTOFLUSH",
119 """Loader callables should disable autoflush.""",
120 canonical=64,
121)
123NO_RAISE = util.symbol(
124 "NO_RAISE",
125 """Loader callables should not raise any assertions""",
126 canonical=128,
127)
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)
161DEFAULT_MANAGER_ATTR = "_sa_class_manager"
162DEFAULT_STATE_ATTR = "_sa_instance_state"
163_INSTRUMENTOR = ("mapper", "instrumentor")
165EXT_CONTINUE = util.symbol("EXT_CONTINUE")
166EXT_STOP = util.symbol("EXT_STOP")
167EXT_SKIP = util.symbol("EXT_SKIP")
169ONETOMANY = util.symbol(
170 "ONETOMANY",
171 """Indicates the one-to-many direction for a :func:`_orm.relationship`.
173 This symbol is typically used by the internals but may be exposed within
174 certain API features.
176 """,
177)
179MANYTOONE = util.symbol(
180 "MANYTOONE",
181 """Indicates the many-to-one direction for a :func:`_orm.relationship`.
183 This symbol is typically used by the internals but may be exposed within
184 certain API features.
186 """,
187)
189MANYTOMANY = util.symbol(
190 "MANYTOMANY",
191 """Indicates the many-to-many direction for a :func:`_orm.relationship`.
193 This symbol is typically used by the internals but may be exposed within
194 certain API features.
196 """,
197)
199NOT_EXTENSION = util.symbol(
200 "NOT_EXTENSION",
201 """Symbol indicating an :class:`InspectionAttr` that's
202 not part of sqlalchemy.ext.
204 Is assigned to the :attr:`.InspectionAttr.extension_type`
205 attribute.
207 """,
208)
210_never_set = frozenset([NEVER_SET])
212_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT])
214_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED")
216_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE")
219def _generative(*assertions):
220 """Mark a method as generative, e.g. method-chained."""
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
230 return generate
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)
239instance_state = operator.attrgetter(DEFAULT_STATE_ATTR)
241instance_dict = operator.attrgetter("__dict__")
244def instance_str(instance):
245 """Return a string describing an instance."""
247 return state_str(instance_state(instance))
250def state_str(state):
251 """Return a string describing an instance via its InstanceState."""
253 if state is None:
254 return "None"
255 else:
256 return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj()))
259def state_class_str(state):
260 """Return a string describing an instance's class via its
261 InstanceState.
262 """
264 if state is None:
265 return "None"
266 else:
267 return "<%s>" % (state.class_.__name__,)
270def attribute_str(instance, attribute):
271 return instance_str(instance) + "." + attribute
274def state_attribute_str(state, attribute):
275 return state_str(state) + "." + attribute
278def object_mapper(instance):
279 """Given an object, return the primary Mapper associated with the object
280 instance.
282 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
283 if no mapping is configured.
285 This function is available via the inspection system as::
287 inspect(instance).mapper
289 Using the inspection system will raise
290 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
291 not part of a mapping.
293 """
294 return object_state(instance).mapper
297def object_state(instance):
298 """Given an object, return the :class:`.InstanceState`
299 associated with the object.
301 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError`
302 if no mapping is configured.
304 Equivalent functionality is available via the :func:`_sa.inspect`
305 function as::
307 inspect(instance)
309 Using the inspection system will raise
310 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is
311 not part of a mapping.
313 """
314 state = _inspect_mapped_object(instance)
315 if state is None:
316 raise exc.UnmappedInstanceError(instance)
317 else:
318 return state
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
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)
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 """
346 insp = inspection.inspect(entity, False)
347 if insp is not None:
348 return insp.mapper
349 else:
350 return None
353def _is_mapped_class(entity):
354 """Return True if the given object is a mapped class,
355 :class:`_orm.Mapper`, or :class:`.AliasedClass`.
356 """
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 )
366def _attr_as_key(attr):
367 if hasattr(attr, "key"):
368 return attr.key
369 else:
370 return expression._column_as_key(attr)
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]
381def _is_aliased_class(entity):
382 insp = inspection.inspect(entity, False)
383 return insp is not None and getattr(insp, "is_aliased_class", False)
386def _entity_descriptor(entity, key):
387 """Return a class attribute given an entity and string name.
389 May return :class:`.InstrumentedAttribute` or user-defined
390 attribute.
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
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 )
416_state_mapper = util.dottedgetter("manager.mapper")
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
434def class_mapper(class_, configure=True):
435 """Given a class, return the primary :class:`_orm.Mapper` associated
436 with the key.
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.
442 Equivalent functionality is available via the :func:`_sa.inspect`
443 function as::
445 inspect(some_mapped_class)
447 Using the inspection system will raise
448 :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped.
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
462class InspectionAttr(object):
463 """A base class applied to all ORM objects that can be returned
464 by the :func:`_sa.inspect` function.
466 The attributes defined here allow the usage of simple boolean
467 checks to test basic facts about the object returned.
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.
475 """
477 __slots__ = ()
479 is_selectable = False
480 """Return True if this object is an instance of """
481 """:class:`expression.Selectable`."""
483 is_aliased_class = False
484 """True if this object is an instance of :class:`.AliasedClass`."""
486 is_instance = False
487 """True if this object is an instance of :class:`.InstanceState`."""
489 is_mapper = False
490 """True if this object is an instance of :class:`_orm.Mapper`."""
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__ = ()