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

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/loading.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"""private module containing functions used to convert database
9rows into object instances and associated state.
11the functions here are called primarily by Query, Mapper,
12as well as some of the attribute loading strategies.
14"""
15from __future__ import absolute_import
17import collections
19from . import attributes
20from . import exc as orm_exc
21from . import path_registry
22from . import strategy_options
23from .base import _DEFER_FOR_STATE
24from .base import _SET_DEFERRED_EXPIRED
25from .util import _none_set
26from .util import state_str
27from .. import exc as sa_exc
28from .. import util
29from ..sql import util as sql_util
32_new_runid = util.counter()
35def instances(query, cursor, context):
36 """Return an ORM result as an iterator."""
38 context.runid = _new_runid()
39 context.post_load_paths = {}
41 filtered = query._has_mapper_entities
43 single_entity = query.is_single_entity
45 if filtered:
46 if single_entity:
47 filter_fn = id
48 else:
50 def filter_fn(row):
51 return tuple(
52 id(item) if ent.use_id_for_hash else item
53 for ent, item in zip(query._entities, row)
54 )
56 try:
57 (process, labels) = list(
58 zip(
59 *[
60 query_entity.row_processor(query, context, cursor)
61 for query_entity in query._entities
62 ]
63 )
64 )
66 if not single_entity:
67 keyed_tuple = util.lightweight_named_tuple("result", labels)
69 while True:
70 context.partials = {}
72 if query._yield_per:
73 fetch = cursor.fetchmany(query._yield_per)
74 if not fetch:
75 break
76 else:
77 fetch = cursor.fetchall()
79 if single_entity:
80 proc = process[0]
81 rows = [proc(row) for row in fetch]
82 else:
83 rows = [
84 keyed_tuple([proc(row) for proc in process])
85 for row in fetch
86 ]
88 for path, post_load in context.post_load_paths.items():
89 post_load.invoke(context, path)
91 if filtered:
92 rows = util.unique_list(rows, filter_fn)
94 for row in rows:
95 yield row
97 if not query._yield_per:
98 break
99 except Exception:
100 with util.safe_reraise():
101 cursor.close()
104@util.dependencies("sqlalchemy.orm.query")
105def merge_result(querylib, query, iterator, load=True):
106 """Merge a result into this :class:`_query.Query` object's Session."""
108 session = query.session
109 if load:
110 # flush current contents if we expect to load data
111 session._autoflush()
113 autoflush = session.autoflush
114 try:
115 session.autoflush = False
116 single_entity = len(query._entities) == 1
117 if single_entity:
118 if isinstance(query._entities[0], querylib._MapperEntity):
119 result = [
120 session._merge(
121 attributes.instance_state(instance),
122 attributes.instance_dict(instance),
123 load=load,
124 _recursive={},
125 _resolve_conflict_map={},
126 )
127 for instance in iterator
128 ]
129 else:
130 result = list(iterator)
131 else:
132 mapped_entities = [
133 i
134 for i, e in enumerate(query._entities)
135 if isinstance(e, querylib._MapperEntity)
136 ]
137 result = []
138 keys = [ent._label_name for ent in query._entities]
139 keyed_tuple = util.lightweight_named_tuple("result", keys)
140 for row in iterator:
141 newrow = list(row)
142 for i in mapped_entities:
143 if newrow[i] is not None:
144 newrow[i] = session._merge(
145 attributes.instance_state(newrow[i]),
146 attributes.instance_dict(newrow[i]),
147 load=load,
148 _recursive={},
149 _resolve_conflict_map={},
150 )
151 result.append(keyed_tuple(newrow))
153 return iter(result)
154 finally:
155 session.autoflush = autoflush
158def get_from_identity(session, mapper, key, passive):
159 """Look up the given key in the given session's identity map,
160 check the object for expired state if found.
162 """
163 instance = session.identity_map.get(key)
164 if instance is not None:
166 state = attributes.instance_state(instance)
168 if mapper.inherits and not state.mapper.isa(mapper):
169 return attributes.PASSIVE_CLASS_MISMATCH
171 # expired - ensure it still exists
172 if state.expired:
173 if not passive & attributes.SQL_OK:
174 # TODO: no coverage here
175 return attributes.PASSIVE_NO_RESULT
176 elif not passive & attributes.RELATED_OBJECT_OK:
177 # this mode is used within a flush and the instance's
178 # expired state will be checked soon enough, if necessary
179 return instance
180 try:
181 state._load_expired(state, passive)
182 except orm_exc.ObjectDeletedError:
183 session._remove_newly_deleted([state])
184 return None
185 return instance
186 else:
187 return None
190def load_on_ident(
191 query, key, refresh_state=None, with_for_update=None, only_load_props=None
192):
193 """Load the given identity key from the database."""
195 if key is not None:
196 ident = key[1]
197 identity_token = key[2]
198 else:
199 ident = identity_token = None
201 return load_on_pk_identity(
202 query,
203 ident,
204 refresh_state=refresh_state,
205 with_for_update=with_for_update,
206 only_load_props=only_load_props,
207 identity_token=identity_token,
208 )
211def load_on_pk_identity(
212 query,
213 primary_key_identity,
214 refresh_state=None,
215 with_for_update=None,
216 only_load_props=None,
217 identity_token=None,
218):
220 """Load the given primary key identity from the database."""
222 if refresh_state is None:
223 q = query._clone()
224 q._get_condition()
225 else:
226 q = query._clone()
228 if primary_key_identity is not None:
229 mapper = query._mapper_zero()
231 (_get_clause, _get_params) = mapper._get_clause
233 # None present in ident - turn those comparisons
234 # into "IS NULL"
235 if None in primary_key_identity:
236 nones = set(
237 [
238 _get_params[col].key
239 for col, value in zip(
240 mapper.primary_key, primary_key_identity
241 )
242 if value is None
243 ]
244 )
245 _get_clause = sql_util.adapt_criterion_to_null(_get_clause, nones)
247 if len(nones) == len(primary_key_identity):
248 util.warn(
249 "fully NULL primary key identity cannot load any "
250 "object. This condition may raise an error in a future "
251 "release."
252 )
253 _get_clause = q._adapt_clause(_get_clause, True, False)
254 q._criterion = _get_clause
256 params = dict(
257 [
258 (_get_params[primary_key].key, id_val)
259 for id_val, primary_key in zip(
260 primary_key_identity, mapper.primary_key
261 )
262 ]
263 )
265 q._params = params
267 # with_for_update needs to be query.LockmodeArg()
268 if with_for_update is not None:
269 version_check = True
270 q._for_update_arg = with_for_update
271 elif query._for_update_arg is not None:
272 version_check = True
273 q._for_update_arg = query._for_update_arg
274 else:
275 version_check = False
277 q._get_options(
278 populate_existing=bool(refresh_state),
279 version_check=version_check,
280 only_load_props=only_load_props,
281 refresh_state=refresh_state,
282 identity_token=identity_token,
283 )
284 q._order_by = None
286 try:
287 return q.one()
288 except orm_exc.NoResultFound:
289 return None
292def _setup_entity_query(
293 context,
294 mapper,
295 query_entity,
296 path,
297 adapter,
298 column_collection,
299 with_polymorphic=None,
300 only_load_props=None,
301 polymorphic_discriminator=None,
302 **kw
303):
305 if with_polymorphic:
306 poly_properties = mapper._iterate_polymorphic_properties(
307 with_polymorphic
308 )
309 else:
310 poly_properties = mapper._polymorphic_properties
312 quick_populators = {}
314 path.set(context.attributes, "memoized_setups", quick_populators)
316 for value in poly_properties:
317 if only_load_props and value.key not in only_load_props:
318 continue
319 value.setup(
320 context,
321 query_entity,
322 path,
323 adapter,
324 only_load_props=only_load_props,
325 column_collection=column_collection,
326 memoized_populators=quick_populators,
327 **kw
328 )
330 if (
331 polymorphic_discriminator is not None
332 and polymorphic_discriminator is not mapper.polymorphic_on
333 ):
335 if adapter:
336 pd = adapter.columns[polymorphic_discriminator]
337 else:
338 pd = polymorphic_discriminator
339 column_collection.append(pd)
342def _warn_for_runid_changed(state):
343 util.warn(
344 "Loading context for %s has changed within a load/refresh "
345 "handler, suggesting a row refresh operation took place. If this "
346 "event handler is expected to be "
347 "emitting row refresh operations within an existing load or refresh "
348 "operation, set restore_load_context=True when establishing the "
349 "listener to ensure the context remains unchanged when the event "
350 "handler completes." % (state_str(state),)
351 )
354def _instance_processor(
355 mapper,
356 context,
357 result,
358 path,
359 adapter,
360 only_load_props=None,
361 refresh_state=None,
362 polymorphic_discriminator=None,
363 _polymorphic_from=None,
364):
365 """Produce a mapper level row processor callable
366 which processes rows into mapped instances."""
368 # note that this method, most of which exists in a closure
369 # called _instance(), resists being broken out, as
370 # attempts to do so tend to add significant function
371 # call overhead. _instance() is the most
372 # performance-critical section in the whole ORM.
374 pk_cols = mapper.primary_key
376 if adapter:
377 pk_cols = [adapter.columns[c] for c in pk_cols]
379 identity_class = mapper._identity_class
381 populators = collections.defaultdict(list)
383 props = mapper._prop_set
384 if only_load_props is not None:
385 props = props.intersection(mapper._props[k] for k in only_load_props)
387 quick_populators = path.get(
388 context.attributes, "memoized_setups", _none_set
389 )
391 for prop in props:
392 if prop in quick_populators:
393 # this is an inlined path just for column-based attributes.
394 col = quick_populators[prop]
395 if col is _DEFER_FOR_STATE:
396 populators["new"].append(
397 (prop.key, prop._deferred_column_loader)
398 )
399 elif col is _SET_DEFERRED_EXPIRED:
400 # note that in this path, we are no longer
401 # searching in the result to see if the column might
402 # be present in some unexpected way.
403 populators["expire"].append((prop.key, False))
404 else:
405 getter = None
406 # the "adapter" can be here via different paths,
407 # e.g. via adapter present at setup_query or adapter
408 # applied to the query afterwards via eager load subquery.
409 # If the column here
410 # were already a product of this adapter, sending it through
411 # the adapter again can return a totally new expression that
412 # won't be recognized in the result, and the ColumnAdapter
413 # currently does not accommodate for this. OTOH, if the
414 # column were never applied through this adapter, we may get
415 # None back, in which case we still won't get our "getter".
416 # so try both against result._getter(). See issue #4048
417 if adapter:
418 adapted_col = adapter.columns[col]
419 if adapted_col is not None:
420 getter = result._getter(adapted_col, False)
421 if not getter:
422 getter = result._getter(col, False)
423 if getter:
424 populators["quick"].append((prop.key, getter))
425 else:
426 # fall back to the ColumnProperty itself, which
427 # will iterate through all of its columns
428 # to see if one fits
429 prop.create_row_processor(
430 context, path, mapper, result, adapter, populators
431 )
432 else:
433 prop.create_row_processor(
434 context, path, mapper, result, adapter, populators
435 )
437 propagate_options = context.propagate_options
438 load_path = (
439 context.query._current_path + path
440 if context.query._current_path.path
441 else path
442 )
444 session_identity_map = context.session.identity_map
446 populate_existing = context.populate_existing or mapper.always_refresh
447 load_evt = bool(mapper.class_manager.dispatch.load)
448 refresh_evt = bool(mapper.class_manager.dispatch.refresh)
449 persistent_evt = bool(context.session.dispatch.loaded_as_persistent)
450 if persistent_evt:
451 loaded_as_persistent = context.session.dispatch.loaded_as_persistent
452 instance_state = attributes.instance_state
453 instance_dict = attributes.instance_dict
454 session_id = context.session.hash_key
455 version_check = context.version_check
456 runid = context.runid
457 identity_token = context.identity_token
459 if not refresh_state and _polymorphic_from is not None:
460 key = ("loader", path.path)
461 if key in context.attributes and context.attributes[key].strategy == (
462 ("selectinload_polymorphic", True),
463 ):
464 selectin_load_via = mapper._should_selectin_load(
465 context.attributes[key].local_opts["entities"],
466 _polymorphic_from,
467 )
468 else:
469 selectin_load_via = mapper._should_selectin_load(
470 None, _polymorphic_from
471 )
473 if selectin_load_via and selectin_load_via is not _polymorphic_from:
474 # only_load_props goes w/ refresh_state only, and in a refresh
475 # we are a single row query for the exact entity; polymorphic
476 # loading does not apply
477 assert only_load_props is None
479 callable_ = _load_subclass_via_in(context, path, selectin_load_via)
481 PostLoad.callable_for_path(
482 context,
483 load_path,
484 selectin_load_via.mapper,
485 selectin_load_via,
486 callable_,
487 selectin_load_via,
488 )
490 post_load = PostLoad.for_context(context, load_path, only_load_props)
492 if refresh_state:
493 refresh_identity_key = refresh_state.key
494 if refresh_identity_key is None:
495 # super-rare condition; a refresh is being called
496 # on a non-instance-key instance; this is meant to only
497 # occur within a flush()
498 refresh_identity_key = mapper._identity_key_from_state(
499 refresh_state
500 )
501 else:
502 refresh_identity_key = None
504 if mapper.allow_partial_pks:
505 is_not_primary_key = _none_set.issuperset
506 else:
507 is_not_primary_key = _none_set.intersection
509 def _instance(row):
511 # determine the state that we'll be populating
512 if refresh_identity_key:
513 # fixed state that we're refreshing
514 state = refresh_state
515 instance = state.obj()
516 dict_ = instance_dict(instance)
517 isnew = state.runid != runid
518 currentload = True
519 loaded_instance = False
520 else:
521 # look at the row, see if that identity is in the
522 # session, or we have to create a new one
523 identitykey = (
524 identity_class,
525 tuple([row[column] for column in pk_cols]),
526 identity_token,
527 )
529 instance = session_identity_map.get(identitykey)
531 if instance is not None:
532 # existing instance
533 state = instance_state(instance)
534 dict_ = instance_dict(instance)
536 isnew = state.runid != runid
537 currentload = not isnew
538 loaded_instance = False
540 if version_check and not currentload:
541 _validate_version_id(mapper, state, dict_, row, adapter)
543 else:
544 # create a new instance
546 # check for non-NULL values in the primary key columns,
547 # else no entity is returned for the row
548 if is_not_primary_key(identitykey[1]):
549 return None
551 isnew = True
552 currentload = True
553 loaded_instance = True
555 instance = mapper.class_manager.new_instance()
557 dict_ = instance_dict(instance)
558 state = instance_state(instance)
559 state.key = identitykey
560 state.identity_token = identity_token
562 # attach instance to session.
563 state.session_id = session_id
564 session_identity_map._add_unpresent(state, identitykey)
566 # populate. this looks at whether this state is new
567 # for this load or was existing, and whether or not this
568 # row is the first row with this identity.
569 if currentload or populate_existing:
570 # full population routines. Objects here are either
571 # just created, or we are doing a populate_existing
573 # be conservative about setting load_path when populate_existing
574 # is in effect; want to maintain options from the original
575 # load. see test_expire->test_refresh_maintains_deferred_options
576 if isnew and (propagate_options or not populate_existing):
577 state.load_options = propagate_options
578 state.load_path = load_path
580 _populate_full(
581 context,
582 row,
583 state,
584 dict_,
585 isnew,
586 load_path,
587 loaded_instance,
588 populate_existing,
589 populators,
590 )
592 if isnew:
593 # state.runid should be equal to context.runid / runid
594 # here, however for event checks we are being more conservative
595 # and checking against existing run id
596 # assert state.runid == runid
598 existing_runid = state.runid
600 if loaded_instance:
601 if load_evt:
602 state.manager.dispatch.load(state, context)
603 if state.runid != existing_runid:
604 _warn_for_runid_changed(state)
605 if persistent_evt:
606 loaded_as_persistent(context.session, state)
607 if state.runid != existing_runid:
608 _warn_for_runid_changed(state)
609 elif refresh_evt:
610 state.manager.dispatch.refresh(
611 state, context, only_load_props
612 )
613 if state.runid != runid:
614 _warn_for_runid_changed(state)
616 if populate_existing or state.modified:
617 if refresh_state and only_load_props:
618 state._commit(dict_, only_load_props)
619 else:
620 state._commit_all(dict_, session_identity_map)
622 if post_load:
623 post_load.add_state(state, True)
625 else:
626 # partial population routines, for objects that were already
627 # in the Session, but a row matches them; apply eager loaders
628 # on existing objects, etc.
629 unloaded = state.unloaded
630 isnew = state not in context.partials
632 if not isnew or unloaded or populators["eager"]:
633 # state is having a partial set of its attributes
634 # refreshed. Populate those attributes,
635 # and add to the "context.partials" collection.
637 to_load = _populate_partial(
638 context,
639 row,
640 state,
641 dict_,
642 isnew,
643 load_path,
644 unloaded,
645 populators,
646 )
648 if isnew:
649 if refresh_evt:
650 existing_runid = state.runid
651 state.manager.dispatch.refresh(state, context, to_load)
652 if state.runid != existing_runid:
653 _warn_for_runid_changed(state)
655 state._commit(dict_, to_load)
657 if post_load and context.invoke_all_eagers:
658 post_load.add_state(state, False)
660 return instance
662 if mapper.polymorphic_map and not _polymorphic_from and not refresh_state:
663 # if we are doing polymorphic, dispatch to a different _instance()
664 # method specific to the subclass mapper
665 _instance = _decorate_polymorphic_switch(
666 _instance,
667 context,
668 mapper,
669 result,
670 path,
671 polymorphic_discriminator,
672 adapter,
673 )
675 return _instance
678def _load_subclass_via_in(context, path, entity):
679 mapper = entity.mapper
681 zero_idx = len(mapper.base_mapper.primary_key) == 1
683 if entity.is_aliased_class:
684 q, enable_opt, disable_opt = mapper._subclass_load_via_in(entity)
685 else:
686 q, enable_opt, disable_opt = mapper._subclass_load_via_in_mapper
688 def do_load(context, path, states, load_only, effective_entity):
689 orig_query = context.query
691 q2 = q._with_lazyload_options(
692 (enable_opt,) + orig_query._with_options + (disable_opt,),
693 path.parent,
694 cache_path=path,
695 )
697 if orig_query._populate_existing:
698 q2.add_criteria(lambda q: q.populate_existing())
700 q2(context.session).params(
701 primary_keys=[
702 state.key[1][0] if zero_idx else state.key[1]
703 for state, load_attrs in states
704 ]
705 ).all()
707 return do_load
710def _populate_full(
711 context,
712 row,
713 state,
714 dict_,
715 isnew,
716 load_path,
717 loaded_instance,
718 populate_existing,
719 populators,
720):
721 if isnew:
722 # first time we are seeing a row with this identity.
723 state.runid = context.runid
725 for key, getter in populators["quick"]:
726 dict_[key] = getter(row)
727 if populate_existing:
728 for key, set_callable in populators["expire"]:
729 dict_.pop(key, None)
730 if set_callable:
731 state.expired_attributes.add(key)
732 else:
733 for key, set_callable in populators["expire"]:
734 if set_callable:
735 state.expired_attributes.add(key)
736 for key, populator in populators["new"]:
737 populator(state, dict_, row)
738 for key, populator in populators["delayed"]:
739 populator(state, dict_, row)
740 elif load_path != state.load_path:
741 # new load path, e.g. object is present in more than one
742 # column position in a series of rows
743 state.load_path = load_path
745 # if we have data, and the data isn't in the dict, OK, let's put
746 # it in.
747 for key, getter in populators["quick"]:
748 if key not in dict_:
749 dict_[key] = getter(row)
751 # otherwise treat like an "already seen" row
752 for key, populator in populators["existing"]:
753 populator(state, dict_, row)
754 # TODO: allow "existing" populator to know this is
755 # a new path for the state:
756 # populator(state, dict_, row, new_path=True)
758 else:
759 # have already seen rows with this identity in this same path.
760 for key, populator in populators["existing"]:
761 populator(state, dict_, row)
763 # TODO: same path
764 # populator(state, dict_, row, new_path=False)
767def _populate_partial(
768 context, row, state, dict_, isnew, load_path, unloaded, populators
769):
771 if not isnew:
772 to_load = context.partials[state]
773 for key, populator in populators["existing"]:
774 if key in to_load:
775 populator(state, dict_, row)
776 else:
777 to_load = unloaded
778 context.partials[state] = to_load
780 for key, getter in populators["quick"]:
781 if key in to_load:
782 dict_[key] = getter(row)
783 for key, set_callable in populators["expire"]:
784 if key in to_load:
785 dict_.pop(key, None)
786 if set_callable:
787 state.expired_attributes.add(key)
788 for key, populator in populators["new"]:
789 if key in to_load:
790 populator(state, dict_, row)
791 for key, populator in populators["delayed"]:
792 if key in to_load:
793 populator(state, dict_, row)
794 for key, populator in populators["eager"]:
795 if key not in unloaded:
796 populator(state, dict_, row)
798 return to_load
801def _validate_version_id(mapper, state, dict_, row, adapter):
803 version_id_col = mapper.version_id_col
805 if version_id_col is None:
806 return
808 if adapter:
809 version_id_col = adapter.columns[version_id_col]
811 if (
812 mapper._get_state_attr_by_column(state, dict_, mapper.version_id_col)
813 != row[version_id_col]
814 ):
815 raise orm_exc.StaleDataError(
816 "Instance '%s' has version id '%s' which "
817 "does not match database-loaded version id '%s'."
818 % (
819 state_str(state),
820 mapper._get_state_attr_by_column(
821 state, dict_, mapper.version_id_col
822 ),
823 row[version_id_col],
824 )
825 )
828def _decorate_polymorphic_switch(
829 instance_fn,
830 context,
831 mapper,
832 result,
833 path,
834 polymorphic_discriminator,
835 adapter,
836):
837 if polymorphic_discriminator is not None:
838 polymorphic_on = polymorphic_discriminator
839 else:
840 polymorphic_on = mapper.polymorphic_on
841 if polymorphic_on is None:
842 return instance_fn
844 if adapter:
845 polymorphic_on = adapter.columns[polymorphic_on]
847 def configure_subclass_mapper(discriminator):
848 try:
849 sub_mapper = mapper.polymorphic_map[discriminator]
850 except KeyError:
851 raise AssertionError(
852 "No such polymorphic_identity %r is defined" % discriminator
853 )
854 else:
855 if sub_mapper is mapper:
856 return None
858 return _instance_processor(
859 sub_mapper,
860 context,
861 result,
862 path,
863 adapter,
864 _polymorphic_from=mapper,
865 )
867 polymorphic_instances = util.PopulateDict(configure_subclass_mapper)
869 def polymorphic_instance(row):
870 discriminator = row[polymorphic_on]
871 if discriminator is not None:
872 _instance = polymorphic_instances[discriminator]
873 if _instance:
874 return _instance(row)
875 return instance_fn(row)
877 return polymorphic_instance
880class PostLoad(object):
881 """Track loaders and states for "post load" operations.
883 """
885 __slots__ = "loaders", "states", "load_keys"
887 def __init__(self):
888 self.loaders = {}
889 self.states = util.OrderedDict()
890 self.load_keys = None
892 def add_state(self, state, overwrite):
893 # the states for a polymorphic load here are all shared
894 # within a single PostLoad object among multiple subtypes.
895 # Filtering of callables on a per-subclass basis needs to be done at
896 # the invocation level
897 self.states[state] = overwrite
899 def invoke(self, context, path):
900 if not self.states:
901 return
902 path = path_registry.PathRegistry.coerce(path)
903 for token, limit_to_mapper, loader, arg, kw in self.loaders.values():
904 states = [
905 (state, overwrite)
906 for state, overwrite in self.states.items()
907 if state.manager.mapper.isa(limit_to_mapper)
908 ]
909 if states:
910 loader(context, path, states, self.load_keys, *arg, **kw)
911 self.states.clear()
913 @classmethod
914 def for_context(cls, context, path, only_load_props):
915 pl = context.post_load_paths.get(path.path)
916 if pl is not None and only_load_props:
917 pl.load_keys = only_load_props
918 return pl
920 @classmethod
921 def path_exists(self, context, path, key):
922 return (
923 path.path in context.post_load_paths
924 and key in context.post_load_paths[path.path].loaders
925 )
927 @classmethod
928 def callable_for_path(
929 cls, context, path, limit_to_mapper, token, loader_callable, *arg, **kw
930 ):
931 if path.path in context.post_load_paths:
932 pl = context.post_load_paths[path.path]
933 else:
934 pl = context.post_load_paths[path.path] = PostLoad()
935 pl.loaders[token] = (token, limit_to_mapper, loader_callable, arg, kw)
938def load_scalar_attributes(mapper, state, attribute_names):
939 """initiate a column-based attribute refresh operation."""
941 # assert mapper is _state_mapper(state)
942 session = state.session
943 if not session:
944 raise orm_exc.DetachedInstanceError(
945 "Instance %s is not bound to a Session; "
946 "attribute refresh operation cannot proceed" % (state_str(state))
947 )
949 has_key = bool(state.key)
951 result = False
953 # in the case of inheritance, particularly concrete and abstract
954 # concrete inheritance, the class manager might have some keys
955 # of attributes on the superclass that we didn't actually map.
956 # These could be mapped as "concrete, dont load" or could be completely
957 # exluded from the mapping and we know nothing about them. Filter them
958 # here to prevent them from coming through.
959 if attribute_names:
960 attribute_names = attribute_names.intersection(mapper.attrs.keys())
962 if mapper.inherits and not mapper.concrete:
963 # because we are using Core to produce a select() that we
964 # pass to the Query, we aren't calling setup() for mapped
965 # attributes; in 1.0 this means deferred attrs won't get loaded
966 # by default
967 statement = mapper._optimized_get_statement(state, attribute_names)
968 if statement is not None:
969 result = load_on_ident(
970 session.query(mapper)
971 .options(strategy_options.Load(mapper).undefer("*"))
972 .from_statement(statement),
973 None,
974 only_load_props=attribute_names,
975 refresh_state=state,
976 )
978 if result is False:
979 if has_key:
980 identity_key = state.key
981 else:
982 # this codepath is rare - only valid when inside a flush, and the
983 # object is becoming persistent but hasn't yet been assigned
984 # an identity_key.
985 # check here to ensure we have the attrs we need.
986 pk_attrs = [
987 mapper._columntoproperty[col].key for col in mapper.primary_key
988 ]
989 if state.expired_attributes.intersection(pk_attrs):
990 raise sa_exc.InvalidRequestError(
991 "Instance %s cannot be refreshed - it's not "
992 " persistent and does not "
993 "contain a full primary key." % state_str(state)
994 )
995 identity_key = mapper._identity_key_from_state(state)
997 if (
998 _none_set.issubset(identity_key) and not mapper.allow_partial_pks
999 ) or _none_set.issuperset(identity_key):
1000 util.warn_limited(
1001 "Instance %s to be refreshed doesn't "
1002 "contain a full primary key - can't be refreshed "
1003 "(and shouldn't be expired, either).",
1004 state_str(state),
1005 )
1006 return
1008 result = load_on_ident(
1009 session.query(mapper),
1010 identity_key,
1011 refresh_state=state,
1012 only_load_props=attribute_names,
1013 )
1015 # if instance is pending, a refresh operation
1016 # may not complete (even if PK attributes are assigned)
1017 if has_key and result is None:
1018 raise orm_exc.ObjectDeletedError(state)