attributes.py 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617
  1. # orm/attributes.py
  2. # Copyright (C) 2005-2017 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. """Defines instrumentation for class attributes and their interaction
  8. with instances.
  9. This module is usually not directly visible to user applications, but
  10. defines a large part of the ORM's interactivity.
  11. """
  12. import operator
  13. from .. import util, event, inspection
  14. from . import interfaces, collections, exc as orm_exc
  15. from .base import instance_state, instance_dict, manager_of_class
  16. from .base import PASSIVE_NO_RESULT, ATTR_WAS_SET, ATTR_EMPTY, NO_VALUE,\
  17. NEVER_SET, NO_CHANGE, CALLABLES_OK, SQL_OK, RELATED_OBJECT_OK,\
  18. INIT_OK, NON_PERSISTENT_OK, LOAD_AGAINST_COMMITTED, PASSIVE_OFF,\
  19. PASSIVE_RETURN_NEVER_SET, PASSIVE_NO_INITIALIZE, PASSIVE_NO_FETCH,\
  20. PASSIVE_NO_FETCH_RELATED, PASSIVE_ONLY_PERSISTENT, NO_AUTOFLUSH
  21. from .base import state_str, instance_str
  22. @inspection._self_inspects
  23. class QueryableAttribute(interfaces._MappedAttribute,
  24. interfaces.InspectionAttr,
  25. interfaces.PropComparator):
  26. """Base class for :term:`descriptor` objects that intercept
  27. attribute events on behalf of a :class:`.MapperProperty`
  28. object. The actual :class:`.MapperProperty` is accessible
  29. via the :attr:`.QueryableAttribute.property`
  30. attribute.
  31. .. seealso::
  32. :class:`.InstrumentedAttribute`
  33. :class:`.MapperProperty`
  34. :attr:`.Mapper.all_orm_descriptors`
  35. :attr:`.Mapper.attrs`
  36. """
  37. is_attribute = True
  38. def __init__(self, class_, key, impl=None,
  39. comparator=None, parententity=None,
  40. of_type=None):
  41. self.class_ = class_
  42. self.key = key
  43. self.impl = impl
  44. self.comparator = comparator
  45. self._parententity = parententity
  46. self._of_type = of_type
  47. manager = manager_of_class(class_)
  48. # manager is None in the case of AliasedClass
  49. if manager:
  50. # propagate existing event listeners from
  51. # immediate superclass
  52. for base in manager._bases:
  53. if key in base:
  54. self.dispatch._update(base[key].dispatch)
  55. @util.memoized_property
  56. def _supports_population(self):
  57. return self.impl.supports_population
  58. def get_history(self, instance, passive=PASSIVE_OFF):
  59. return self.impl.get_history(instance_state(instance),
  60. instance_dict(instance), passive)
  61. def __selectable__(self):
  62. # TODO: conditionally attach this method based on clause_element ?
  63. return self
  64. @util.memoized_property
  65. def info(self):
  66. """Return the 'info' dictionary for the underlying SQL element.
  67. The behavior here is as follows:
  68. * If the attribute is a column-mapped property, i.e.
  69. :class:`.ColumnProperty`, which is mapped directly
  70. to a schema-level :class:`.Column` object, this attribute
  71. will return the :attr:`.SchemaItem.info` dictionary associated
  72. with the core-level :class:`.Column` object.
  73. * If the attribute is a :class:`.ColumnProperty` but is mapped to
  74. any other kind of SQL expression other than a :class:`.Column`,
  75. the attribute will refer to the :attr:`.MapperProperty.info`
  76. dictionary associated directly with the :class:`.ColumnProperty`,
  77. assuming the SQL expression itself does not have its own ``.info``
  78. attribute (which should be the case, unless a user-defined SQL
  79. construct has defined one).
  80. * If the attribute refers to any other kind of
  81. :class:`.MapperProperty`, including :class:`.RelationshipProperty`,
  82. the attribute will refer to the :attr:`.MapperProperty.info`
  83. dictionary associated with that :class:`.MapperProperty`.
  84. * To access the :attr:`.MapperProperty.info` dictionary of the
  85. :class:`.MapperProperty` unconditionally, including for a
  86. :class:`.ColumnProperty` that's associated directly with a
  87. :class:`.schema.Column`, the attribute can be referred to using
  88. :attr:`.QueryableAttribute.property` attribute, as
  89. ``MyClass.someattribute.property.info``.
  90. .. versionadded:: 0.8.0
  91. .. seealso::
  92. :attr:`.SchemaItem.info`
  93. :attr:`.MapperProperty.info`
  94. """
  95. return self.comparator.info
  96. @util.memoized_property
  97. def parent(self):
  98. """Return an inspection instance representing the parent.
  99. This will be either an instance of :class:`.Mapper`
  100. or :class:`.AliasedInsp`, depending upon the nature
  101. of the parent entity which this attribute is associated
  102. with.
  103. """
  104. return inspection.inspect(self._parententity)
  105. @property
  106. def expression(self):
  107. return self.comparator.__clause_element__()
  108. def __clause_element__(self):
  109. return self.comparator.__clause_element__()
  110. def _query_clause_element(self):
  111. """like __clause_element__(), but called specifically
  112. by :class:`.Query` to allow special behavior."""
  113. return self.comparator._query_clause_element()
  114. def adapt_to_entity(self, adapt_to_entity):
  115. assert not self._of_type
  116. return self.__class__(adapt_to_entity.entity,
  117. self.key, impl=self.impl,
  118. comparator=self.comparator.adapt_to_entity(
  119. adapt_to_entity),
  120. parententity=adapt_to_entity)
  121. def of_type(self, cls):
  122. return QueryableAttribute(
  123. self.class_,
  124. self.key,
  125. self.impl,
  126. self.comparator.of_type(cls),
  127. self._parententity,
  128. of_type=cls)
  129. def label(self, name):
  130. return self._query_clause_element().label(name)
  131. def operate(self, op, *other, **kwargs):
  132. return op(self.comparator, *other, **kwargs)
  133. def reverse_operate(self, op, other, **kwargs):
  134. return op(other, self.comparator, **kwargs)
  135. def hasparent(self, state, optimistic=False):
  136. return self.impl.hasparent(state, optimistic=optimistic) is not False
  137. def __getattr__(self, key):
  138. try:
  139. return getattr(self.comparator, key)
  140. except AttributeError:
  141. raise AttributeError(
  142. 'Neither %r object nor %r object associated with %s '
  143. 'has an attribute %r' % (
  144. type(self).__name__,
  145. type(self.comparator).__name__,
  146. self,
  147. key)
  148. )
  149. def __str__(self):
  150. return "%s.%s" % (self.class_.__name__, self.key)
  151. @util.memoized_property
  152. def property(self):
  153. """Return the :class:`.MapperProperty` associated with this
  154. :class:`.QueryableAttribute`.
  155. Return values here will commonly be instances of
  156. :class:`.ColumnProperty` or :class:`.RelationshipProperty`.
  157. """
  158. return self.comparator.property
  159. class InstrumentedAttribute(QueryableAttribute):
  160. """Class bound instrumented attribute which adds basic
  161. :term:`descriptor` methods.
  162. See :class:`.QueryableAttribute` for a description of most features.
  163. """
  164. def __set__(self, instance, value):
  165. self.impl.set(instance_state(instance),
  166. instance_dict(instance), value, None)
  167. def __delete__(self, instance):
  168. self.impl.delete(instance_state(instance), instance_dict(instance))
  169. def __get__(self, instance, owner):
  170. if instance is None:
  171. return self
  172. dict_ = instance_dict(instance)
  173. if self._supports_population and self.key in dict_:
  174. return dict_[self.key]
  175. else:
  176. return self.impl.get(instance_state(instance), dict_)
  177. def create_proxied_attribute(descriptor):
  178. """Create an QueryableAttribute / user descriptor hybrid.
  179. Returns a new QueryableAttribute type that delegates descriptor
  180. behavior and getattr() to the given descriptor.
  181. """
  182. # TODO: can move this to descriptor_props if the need for this
  183. # function is removed from ext/hybrid.py
  184. class Proxy(QueryableAttribute):
  185. """Presents the :class:`.QueryableAttribute` interface as a
  186. proxy on top of a Python descriptor / :class:`.PropComparator`
  187. combination.
  188. """
  189. def __init__(self, class_, key, descriptor,
  190. comparator,
  191. adapt_to_entity=None, doc=None,
  192. original_property=None):
  193. self.class_ = class_
  194. self.key = key
  195. self.descriptor = descriptor
  196. self.original_property = original_property
  197. self._comparator = comparator
  198. self._adapt_to_entity = adapt_to_entity
  199. self.__doc__ = doc
  200. @property
  201. def property(self):
  202. return self.comparator.property
  203. @util.memoized_property
  204. def comparator(self):
  205. if util.callable(self._comparator):
  206. self._comparator = self._comparator()
  207. if self._adapt_to_entity:
  208. self._comparator = self._comparator.adapt_to_entity(
  209. self._adapt_to_entity)
  210. return self._comparator
  211. def adapt_to_entity(self, adapt_to_entity):
  212. return self.__class__(adapt_to_entity.entity,
  213. self.key,
  214. self.descriptor,
  215. self._comparator,
  216. adapt_to_entity)
  217. def __get__(self, instance, owner):
  218. if instance is None:
  219. return self
  220. else:
  221. return self.descriptor.__get__(instance, owner)
  222. def __str__(self):
  223. return "%s.%s" % (self.class_.__name__, self.key)
  224. def __getattr__(self, attribute):
  225. """Delegate __getattr__ to the original descriptor and/or
  226. comparator."""
  227. try:
  228. return getattr(descriptor, attribute)
  229. except AttributeError:
  230. try:
  231. return getattr(self.comparator, attribute)
  232. except AttributeError:
  233. raise AttributeError(
  234. 'Neither %r object nor %r object associated with %s '
  235. 'has an attribute %r' % (
  236. type(descriptor).__name__,
  237. type(self.comparator).__name__,
  238. self,
  239. attribute)
  240. )
  241. Proxy.__name__ = type(descriptor).__name__ + 'Proxy'
  242. util.monkeypatch_proxied_specials(Proxy, type(descriptor),
  243. name='descriptor',
  244. from_instance=descriptor)
  245. return Proxy
  246. OP_REMOVE = util.symbol("REMOVE")
  247. OP_APPEND = util.symbol("APPEND")
  248. OP_REPLACE = util.symbol("REPLACE")
  249. class Event(object):
  250. """A token propagated throughout the course of a chain of attribute
  251. events.
  252. Serves as an indicator of the source of the event and also provides
  253. a means of controlling propagation across a chain of attribute
  254. operations.
  255. The :class:`.Event` object is sent as the ``initiator`` argument
  256. when dealing with the :meth:`.AttributeEvents.append`,
  257. :meth:`.AttributeEvents.set`,
  258. and :meth:`.AttributeEvents.remove` events.
  259. The :class:`.Event` object is currently interpreted by the backref
  260. event handlers, and is used to control the propagation of operations
  261. across two mutually-dependent attributes.
  262. .. versionadded:: 0.9.0
  263. :var impl: The :class:`.AttributeImpl` which is the current event
  264. initiator.
  265. :var op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE` or
  266. :attr:`.OP_REPLACE`, indicating the source operation.
  267. """
  268. __slots__ = 'impl', 'op', 'parent_token'
  269. def __init__(self, attribute_impl, op):
  270. self.impl = attribute_impl
  271. self.op = op
  272. self.parent_token = self.impl.parent_token
  273. def __eq__(self, other):
  274. return isinstance(other, Event) and \
  275. other.impl is self.impl and \
  276. other.op == self.op
  277. @property
  278. def key(self):
  279. return self.impl.key
  280. def hasparent(self, state):
  281. return self.impl.hasparent(state)
  282. class AttributeImpl(object):
  283. """internal implementation for instrumented attributes."""
  284. def __init__(self, class_, key,
  285. callable_, dispatch, trackparent=False, extension=None,
  286. compare_function=None, active_history=False,
  287. parent_token=None, expire_missing=True,
  288. send_modified_events=True,
  289. **kwargs):
  290. r"""Construct an AttributeImpl.
  291. \class_
  292. associated class
  293. key
  294. string name of the attribute
  295. \callable_
  296. optional function which generates a callable based on a parent
  297. instance, which produces the "default" values for a scalar or
  298. collection attribute when it's first accessed, if not present
  299. already.
  300. trackparent
  301. if True, attempt to track if an instance has a parent attached
  302. to it via this attribute.
  303. extension
  304. a single or list of AttributeExtension object(s) which will
  305. receive set/delete/append/remove/etc. events. Deprecated.
  306. The event package is now used.
  307. compare_function
  308. a function that compares two values which are normally
  309. assignable to this attribute.
  310. active_history
  311. indicates that get_history() should always return the "old" value,
  312. even if it means executing a lazy callable upon attribute change.
  313. parent_token
  314. Usually references the MapperProperty, used as a key for
  315. the hasparent() function to identify an "owning" attribute.
  316. Allows multiple AttributeImpls to all match a single
  317. owner attribute.
  318. expire_missing
  319. if False, don't add an "expiry" callable to this attribute
  320. during state.expire_attributes(None), if no value is present
  321. for this key.
  322. send_modified_events
  323. if False, the InstanceState._modified_event method will have no
  324. effect; this means the attribute will never show up as changed in a
  325. history entry.
  326. """
  327. self.class_ = class_
  328. self.key = key
  329. self.callable_ = callable_
  330. self.dispatch = dispatch
  331. self.trackparent = trackparent
  332. self.parent_token = parent_token or self
  333. self.send_modified_events = send_modified_events
  334. if compare_function is None:
  335. self.is_equal = operator.eq
  336. else:
  337. self.is_equal = compare_function
  338. # TODO: pass in the manager here
  339. # instead of doing a lookup
  340. attr = manager_of_class(class_)[key]
  341. for ext in util.to_list(extension or []):
  342. ext._adapt_listener(attr, ext)
  343. if active_history:
  344. self.dispatch._active_history = True
  345. self.expire_missing = expire_missing
  346. __slots__ = (
  347. 'class_', 'key', 'callable_', 'dispatch', 'trackparent',
  348. 'parent_token', 'send_modified_events', 'is_equal', 'expire_missing'
  349. )
  350. def __str__(self):
  351. return "%s.%s" % (self.class_.__name__, self.key)
  352. def _get_active_history(self):
  353. """Backwards compat for impl.active_history"""
  354. return self.dispatch._active_history
  355. def _set_active_history(self, value):
  356. self.dispatch._active_history = value
  357. active_history = property(_get_active_history, _set_active_history)
  358. def hasparent(self, state, optimistic=False):
  359. """Return the boolean value of a `hasparent` flag attached to
  360. the given state.
  361. The `optimistic` flag determines what the default return value
  362. should be if no `hasparent` flag can be located.
  363. As this function is used to determine if an instance is an
  364. *orphan*, instances that were loaded from storage should be
  365. assumed to not be orphans, until a True/False value for this
  366. flag is set.
  367. An instance attribute that is loaded by a callable function
  368. will also not have a `hasparent` flag.
  369. """
  370. msg = "This AttributeImpl is not configured to track parents."
  371. assert self.trackparent, msg
  372. return state.parents.get(id(self.parent_token), optimistic) \
  373. is not False
  374. def sethasparent(self, state, parent_state, value):
  375. """Set a boolean flag on the given item corresponding to
  376. whether or not it is attached to a parent object via the
  377. attribute represented by this ``InstrumentedAttribute``.
  378. """
  379. msg = "This AttributeImpl is not configured to track parents."
  380. assert self.trackparent, msg
  381. id_ = id(self.parent_token)
  382. if value:
  383. state.parents[id_] = parent_state
  384. else:
  385. if id_ in state.parents:
  386. last_parent = state.parents[id_]
  387. if last_parent is not False and \
  388. last_parent.key != parent_state.key:
  389. if last_parent.obj() is None:
  390. raise orm_exc.StaleDataError(
  391. "Removing state %s from parent "
  392. "state %s along attribute '%s', "
  393. "but the parent record "
  394. "has gone stale, can't be sure this "
  395. "is the most recent parent." %
  396. (state_str(state),
  397. state_str(parent_state),
  398. self.key))
  399. return
  400. state.parents[id_] = False
  401. def get_history(self, state, dict_, passive=PASSIVE_OFF):
  402. raise NotImplementedError()
  403. def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
  404. """Return a list of tuples of (state, obj)
  405. for all objects in this attribute's current state
  406. + history.
  407. Only applies to object-based attributes.
  408. This is an inlining of existing functionality
  409. which roughly corresponds to:
  410. get_state_history(
  411. state,
  412. key,
  413. passive=PASSIVE_NO_INITIALIZE).sum()
  414. """
  415. raise NotImplementedError()
  416. def initialize(self, state, dict_):
  417. """Initialize the given state's attribute with an empty value."""
  418. value = None
  419. for fn in self.dispatch.init_scalar:
  420. ret = fn(state, value, dict_)
  421. if ret is not ATTR_EMPTY:
  422. value = ret
  423. return value
  424. def get(self, state, dict_, passive=PASSIVE_OFF):
  425. """Retrieve a value from the given object.
  426. If a callable is assembled on this object's attribute, and
  427. passive is False, the callable will be executed and the
  428. resulting value will be set as the new value for this attribute.
  429. """
  430. if self.key in dict_:
  431. return dict_[self.key]
  432. else:
  433. # if history present, don't load
  434. key = self.key
  435. if key not in state.committed_state or \
  436. state.committed_state[key] is NEVER_SET:
  437. if not passive & CALLABLES_OK:
  438. return PASSIVE_NO_RESULT
  439. if key in state.expired_attributes:
  440. value = state._load_expired(state, passive)
  441. elif key in state.callables:
  442. callable_ = state.callables[key]
  443. value = callable_(state, passive)
  444. elif self.callable_:
  445. value = self.callable_(state, passive)
  446. else:
  447. value = ATTR_EMPTY
  448. if value is PASSIVE_NO_RESULT or value is NEVER_SET:
  449. return value
  450. elif value is ATTR_WAS_SET:
  451. try:
  452. return dict_[key]
  453. except KeyError:
  454. # TODO: no test coverage here.
  455. raise KeyError(
  456. "Deferred loader for attribute "
  457. "%r failed to populate "
  458. "correctly" % key)
  459. elif value is not ATTR_EMPTY:
  460. return self.set_committed_value(state, dict_, value)
  461. if not passive & INIT_OK:
  462. return NEVER_SET
  463. else:
  464. # Return a new, empty value
  465. return self.initialize(state, dict_)
  466. def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  467. self.set(state, dict_, value, initiator, passive=passive)
  468. def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  469. self.set(state, dict_, None, initiator,
  470. passive=passive, check_old=value)
  471. def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  472. self.set(state, dict_, None, initiator,
  473. passive=passive, check_old=value, pop=True)
  474. def set(self, state, dict_, value, initiator,
  475. passive=PASSIVE_OFF, check_old=None, pop=False):
  476. raise NotImplementedError()
  477. def get_committed_value(self, state, dict_, passive=PASSIVE_OFF):
  478. """return the unchanged value of this attribute"""
  479. if self.key in state.committed_state:
  480. value = state.committed_state[self.key]
  481. if value in (NO_VALUE, NEVER_SET):
  482. return None
  483. else:
  484. return value
  485. else:
  486. return self.get(state, dict_, passive=passive)
  487. def set_committed_value(self, state, dict_, value):
  488. """set an attribute value on the given instance and 'commit' it."""
  489. dict_[self.key] = value
  490. state._commit(dict_, [self.key])
  491. return value
  492. class ScalarAttributeImpl(AttributeImpl):
  493. """represents a scalar value-holding InstrumentedAttribute."""
  494. accepts_scalar_loader = True
  495. uses_objects = False
  496. supports_population = True
  497. collection = False
  498. __slots__ = '_replace_token', '_append_token', '_remove_token'
  499. def __init__(self, *arg, **kw):
  500. super(ScalarAttributeImpl, self).__init__(*arg, **kw)
  501. self._replace_token = self._append_token = None
  502. self._remove_token = None
  503. def _init_append_token(self):
  504. self._replace_token = self._append_token = Event(self, OP_REPLACE)
  505. return self._replace_token
  506. _init_append_or_replace_token = _init_append_token
  507. def _init_remove_token(self):
  508. self._remove_token = Event(self, OP_REMOVE)
  509. return self._remove_token
  510. def delete(self, state, dict_):
  511. # TODO: catch key errors, convert to attributeerror?
  512. if self.dispatch._active_history:
  513. old = self.get(state, dict_, PASSIVE_RETURN_NEVER_SET)
  514. else:
  515. old = dict_.get(self.key, NO_VALUE)
  516. if self.dispatch.remove:
  517. self.fire_remove_event(state, dict_, old, self._remove_token)
  518. state._modified_event(dict_, self, old)
  519. del dict_[self.key]
  520. def get_history(self, state, dict_, passive=PASSIVE_OFF):
  521. if self.key in dict_:
  522. return History.from_scalar_attribute(self, state, dict_[self.key])
  523. else:
  524. if passive & INIT_OK:
  525. passive ^= INIT_OK
  526. current = self.get(state, dict_, passive=passive)
  527. if current is PASSIVE_NO_RESULT:
  528. return HISTORY_BLANK
  529. else:
  530. return History.from_scalar_attribute(self, state, current)
  531. def set(self, state, dict_, value, initiator,
  532. passive=PASSIVE_OFF, check_old=None, pop=False):
  533. if self.dispatch._active_history:
  534. old = self.get(state, dict_, PASSIVE_RETURN_NEVER_SET)
  535. else:
  536. old = dict_.get(self.key, NO_VALUE)
  537. if self.dispatch.set:
  538. value = self.fire_replace_event(state, dict_,
  539. value, old, initiator)
  540. state._modified_event(dict_, self, old)
  541. dict_[self.key] = value
  542. def fire_replace_event(self, state, dict_, value, previous, initiator):
  543. for fn in self.dispatch.set:
  544. value = fn(
  545. state, value, previous,
  546. initiator or self._replace_token or
  547. self._init_append_or_replace_token())
  548. return value
  549. def fire_remove_event(self, state, dict_, value, initiator):
  550. for fn in self.dispatch.remove:
  551. fn(state, value,
  552. initiator or self._remove_token or self._init_remove_token())
  553. @property
  554. def type(self):
  555. self.property.columns[0].type
  556. class ScalarObjectAttributeImpl(ScalarAttributeImpl):
  557. """represents a scalar-holding InstrumentedAttribute,
  558. where the target object is also instrumented.
  559. Adds events to delete/set operations.
  560. """
  561. accepts_scalar_loader = False
  562. uses_objects = True
  563. supports_population = True
  564. collection = False
  565. __slots__ = ()
  566. def delete(self, state, dict_):
  567. old = self.get(state, dict_)
  568. self.fire_remove_event(
  569. state, dict_, old,
  570. self._remove_token or self._init_remove_token())
  571. del dict_[self.key]
  572. def get_history(self, state, dict_, passive=PASSIVE_OFF):
  573. if self.key in dict_:
  574. return History.from_object_attribute(self, state, dict_[self.key])
  575. else:
  576. if passive & INIT_OK:
  577. passive ^= INIT_OK
  578. current = self.get(state, dict_, passive=passive)
  579. if current is PASSIVE_NO_RESULT:
  580. return HISTORY_BLANK
  581. else:
  582. return History.from_object_attribute(self, state, current)
  583. def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
  584. if self.key in dict_:
  585. current = dict_[self.key]
  586. elif passive & CALLABLES_OK:
  587. current = self.get(state, dict_, passive=passive)
  588. else:
  589. return []
  590. # can't use __hash__(), can't use __eq__() here
  591. if current is not None and \
  592. current is not PASSIVE_NO_RESULT and \
  593. current is not NEVER_SET:
  594. ret = [(instance_state(current), current)]
  595. else:
  596. ret = [(None, None)]
  597. if self.key in state.committed_state:
  598. original = state.committed_state[self.key]
  599. if original is not None and \
  600. original is not PASSIVE_NO_RESULT and \
  601. original is not NEVER_SET and \
  602. original is not current:
  603. ret.append((instance_state(original), original))
  604. return ret
  605. def set(self, state, dict_, value, initiator,
  606. passive=PASSIVE_OFF, check_old=None, pop=False):
  607. """Set a value on the given InstanceState.
  608. """
  609. if self.dispatch._active_history:
  610. old = self.get(
  611. state, dict_,
  612. passive=PASSIVE_ONLY_PERSISTENT |
  613. NO_AUTOFLUSH | LOAD_AGAINST_COMMITTED)
  614. else:
  615. old = self.get(
  616. state, dict_, passive=PASSIVE_NO_FETCH ^ INIT_OK |
  617. LOAD_AGAINST_COMMITTED)
  618. if check_old is not None and \
  619. old is not PASSIVE_NO_RESULT and \
  620. check_old is not old:
  621. if pop:
  622. return
  623. else:
  624. raise ValueError(
  625. "Object %s not associated with %s on attribute '%s'" % (
  626. instance_str(check_old),
  627. state_str(state),
  628. self.key
  629. ))
  630. value = self.fire_replace_event(state, dict_, value, old, initiator)
  631. dict_[self.key] = value
  632. def fire_remove_event(self, state, dict_, value, initiator):
  633. if self.trackparent and value is not None:
  634. self.sethasparent(instance_state(value), state, False)
  635. for fn in self.dispatch.remove:
  636. fn(state, value, initiator or
  637. self._remove_token or self._init_remove_token())
  638. state._modified_event(dict_, self, value)
  639. def fire_replace_event(self, state, dict_, value, previous, initiator):
  640. if self.trackparent:
  641. if (previous is not value and
  642. previous not in (None, PASSIVE_NO_RESULT, NEVER_SET)):
  643. self.sethasparent(instance_state(previous), state, False)
  644. for fn in self.dispatch.set:
  645. value = fn(
  646. state, value, previous, initiator or
  647. self._replace_token or self._init_append_or_replace_token())
  648. state._modified_event(dict_, self, previous)
  649. if self.trackparent:
  650. if value is not None:
  651. self.sethasparent(instance_state(value), state, True)
  652. return value
  653. class CollectionAttributeImpl(AttributeImpl):
  654. """A collection-holding attribute that instruments changes in membership.
  655. Only handles collections of instrumented objects.
  656. InstrumentedCollectionAttribute holds an arbitrary, user-specified
  657. container object (defaulting to a list) and brokers access to the
  658. CollectionAdapter, a "view" onto that object that presents consistent bag
  659. semantics to the orm layer independent of the user data implementation.
  660. """
  661. accepts_scalar_loader = False
  662. uses_objects = True
  663. supports_population = True
  664. collection = True
  665. __slots__ = (
  666. 'copy', 'collection_factory', '_append_token', '_remove_token',
  667. '_duck_typed_as'
  668. )
  669. def __init__(self, class_, key, callable_, dispatch,
  670. typecallable=None, trackparent=False, extension=None,
  671. copy_function=None, compare_function=None, **kwargs):
  672. super(CollectionAttributeImpl, self).__init__(
  673. class_,
  674. key,
  675. callable_, dispatch,
  676. trackparent=trackparent,
  677. extension=extension,
  678. compare_function=compare_function,
  679. **kwargs)
  680. if copy_function is None:
  681. copy_function = self.__copy
  682. self.copy = copy_function
  683. self.collection_factory = typecallable
  684. self._append_token = None
  685. self._remove_token = None
  686. self._duck_typed_as = util.duck_type_collection(
  687. self.collection_factory())
  688. if getattr(self.collection_factory, "_sa_linker", None):
  689. @event.listens_for(self, "init_collection")
  690. def link(target, collection, collection_adapter):
  691. collection._sa_linker(collection_adapter)
  692. @event.listens_for(self, "dispose_collection")
  693. def unlink(target, collection, collection_adapter):
  694. collection._sa_linker(None)
  695. def _init_append_token(self):
  696. self._append_token = Event(self, OP_APPEND)
  697. return self._append_token
  698. def _init_remove_token(self):
  699. self._remove_token = Event(self, OP_REMOVE)
  700. return self._remove_token
  701. def __copy(self, item):
  702. return [y for y in collections.collection_adapter(item)]
  703. def get_history(self, state, dict_, passive=PASSIVE_OFF):
  704. current = self.get(state, dict_, passive=passive)
  705. if current is PASSIVE_NO_RESULT:
  706. return HISTORY_BLANK
  707. else:
  708. return History.from_collection(self, state, current)
  709. def get_all_pending(self, state, dict_, passive=PASSIVE_NO_INITIALIZE):
  710. # NOTE: passive is ignored here at the moment
  711. if self.key not in dict_:
  712. return []
  713. current = dict_[self.key]
  714. current = getattr(current, '_sa_adapter')
  715. if self.key in state.committed_state:
  716. original = state.committed_state[self.key]
  717. if original not in (NO_VALUE, NEVER_SET):
  718. current_states = [((c is not None) and
  719. instance_state(c) or None, c)
  720. for c in current]
  721. original_states = [((c is not None) and
  722. instance_state(c) or None, c)
  723. for c in original]
  724. current_set = dict(current_states)
  725. original_set = dict(original_states)
  726. return \
  727. [(s, o) for s, o in current_states
  728. if s not in original_set] + \
  729. [(s, o) for s, o in current_states
  730. if s in original_set] + \
  731. [(s, o) for s, o in original_states
  732. if s not in current_set]
  733. return [(instance_state(o), o) for o in current]
  734. def fire_append_event(self, state, dict_, value, initiator):
  735. for fn in self.dispatch.append:
  736. value = fn(
  737. state, value,
  738. initiator or self._append_token or self._init_append_token())
  739. state._modified_event(dict_, self, NEVER_SET, True)
  740. if self.trackparent and value is not None:
  741. self.sethasparent(instance_state(value), state, True)
  742. return value
  743. def fire_pre_remove_event(self, state, dict_, initiator):
  744. state._modified_event(dict_, self, NEVER_SET, True)
  745. def fire_remove_event(self, state, dict_, value, initiator):
  746. if self.trackparent and value is not None:
  747. self.sethasparent(instance_state(value), state, False)
  748. for fn in self.dispatch.remove:
  749. fn(state, value,
  750. initiator or self._remove_token or self._init_remove_token())
  751. state._modified_event(dict_, self, NEVER_SET, True)
  752. def delete(self, state, dict_):
  753. if self.key not in dict_:
  754. return
  755. state._modified_event(dict_, self, NEVER_SET, True)
  756. collection = self.get_collection(state, state.dict)
  757. collection.clear_with_event()
  758. # TODO: catch key errors, convert to attributeerror?
  759. del dict_[self.key]
  760. def initialize(self, state, dict_):
  761. """Initialize this attribute with an empty collection."""
  762. _, user_data = self._initialize_collection(state)
  763. dict_[self.key] = user_data
  764. return user_data
  765. def _initialize_collection(self, state):
  766. adapter, collection = state.manager.initialize_collection(
  767. self.key, state, self.collection_factory)
  768. self.dispatch.init_collection(state, collection, adapter)
  769. return adapter, collection
  770. def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  771. collection = self.get_collection(state, dict_, passive=passive)
  772. if collection is PASSIVE_NO_RESULT:
  773. value = self.fire_append_event(state, dict_, value, initiator)
  774. assert self.key not in dict_, \
  775. "Collection was loaded during event handling."
  776. state._get_pending_mutation(self.key).append(value)
  777. else:
  778. collection.append_with_event(value, initiator)
  779. def remove(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  780. collection = self.get_collection(state, state.dict, passive=passive)
  781. if collection is PASSIVE_NO_RESULT:
  782. self.fire_remove_event(state, dict_, value, initiator)
  783. assert self.key not in dict_, \
  784. "Collection was loaded during event handling."
  785. state._get_pending_mutation(self.key).remove(value)
  786. else:
  787. collection.remove_with_event(value, initiator)
  788. def pop(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
  789. try:
  790. # TODO: better solution here would be to add
  791. # a "popper" role to collections.py to complement
  792. # "remover".
  793. self.remove(state, dict_, value, initiator, passive=passive)
  794. except (ValueError, KeyError, IndexError):
  795. pass
  796. def set(self, state, dict_, value, initiator=None,
  797. passive=PASSIVE_OFF, pop=False, _adapt=True):
  798. iterable = orig_iterable = value
  799. # pulling a new collection first so that an adaptation exception does
  800. # not trigger a lazy load of the old collection.
  801. new_collection, user_data = self._initialize_collection(state)
  802. if _adapt:
  803. if new_collection._converter is not None:
  804. iterable = new_collection._converter(iterable)
  805. else:
  806. setting_type = util.duck_type_collection(iterable)
  807. receiving_type = self._duck_typed_as
  808. if setting_type is not receiving_type:
  809. given = iterable is None and 'None' or \
  810. iterable.__class__.__name__
  811. wanted = self._duck_typed_as.__name__
  812. raise TypeError(
  813. "Incompatible collection type: %s is not %s-like" % (
  814. given, wanted))
  815. # If the object is an adapted collection, return the (iterable)
  816. # adapter.
  817. if hasattr(iterable, '_sa_iterator'):
  818. iterable = iterable._sa_iterator()
  819. elif setting_type is dict:
  820. if util.py3k:
  821. iterable = iterable.values()
  822. else:
  823. iterable = getattr(
  824. iterable, 'itervalues', iterable.values)()
  825. else:
  826. iterable = iter(iterable)
  827. new_values = list(iterable)
  828. old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
  829. if old is PASSIVE_NO_RESULT:
  830. old = self.initialize(state, dict_)
  831. elif old is orig_iterable:
  832. # ignore re-assignment of the current collection, as happens
  833. # implicitly with in-place operators (foo.collection |= other)
  834. return
  835. # place a copy of "old" in state.committed_state
  836. state._modified_event(dict_, self, old, True)
  837. old_collection = old._sa_adapter
  838. dict_[self.key] = user_data
  839. collections.bulk_replace(
  840. new_values, old_collection, new_collection)
  841. del old._sa_adapter
  842. self.dispatch.dispose_collection(state, old, old_collection)
  843. def _invalidate_collection(self, collection):
  844. adapter = getattr(collection, '_sa_adapter')
  845. adapter.invalidated = True
  846. def set_committed_value(self, state, dict_, value):
  847. """Set an attribute value on the given instance and 'commit' it."""
  848. collection, user_data = self._initialize_collection(state)
  849. if value:
  850. collection.append_multiple_without_event(value)
  851. state.dict[self.key] = user_data
  852. state._commit(dict_, [self.key])
  853. if self.key in state._pending_mutations:
  854. # pending items exist. issue a modified event,
  855. # add/remove new items.
  856. state._modified_event(dict_, self, user_data, True)
  857. pending = state._pending_mutations.pop(self.key)
  858. added = pending.added_items
  859. removed = pending.deleted_items
  860. for item in added:
  861. collection.append_without_event(item)
  862. for item in removed:
  863. collection.remove_without_event(item)
  864. return user_data
  865. def get_collection(self, state, dict_,
  866. user_data=None, passive=PASSIVE_OFF):
  867. """Retrieve the CollectionAdapter associated with the given state.
  868. Creates a new CollectionAdapter if one does not exist.
  869. """
  870. if user_data is None:
  871. user_data = self.get(state, dict_, passive=passive)
  872. if user_data is PASSIVE_NO_RESULT:
  873. return user_data
  874. return getattr(user_data, '_sa_adapter')
  875. def backref_listeners(attribute, key, uselist):
  876. """Apply listeners to synchronize a two-way relationship."""
  877. # use easily recognizable names for stack traces
  878. parent_token = attribute.impl.parent_token
  879. parent_impl = attribute.impl
  880. def _acceptable_key_err(child_state, initiator, child_impl):
  881. raise ValueError(
  882. "Bidirectional attribute conflict detected: "
  883. 'Passing object %s to attribute "%s" '
  884. 'triggers a modify event on attribute "%s" '
  885. 'via the backref "%s".' % (
  886. state_str(child_state),
  887. initiator.parent_token,
  888. child_impl.parent_token,
  889. attribute.impl.parent_token
  890. )
  891. )
  892. def emit_backref_from_scalar_set_event(state, child, oldchild, initiator):
  893. if oldchild is child:
  894. return child
  895. if oldchild is not None and \
  896. oldchild is not PASSIVE_NO_RESULT and \
  897. oldchild is not NEVER_SET:
  898. # With lazy=None, there's no guarantee that the full collection is
  899. # present when updating via a backref.
  900. old_state, old_dict = instance_state(oldchild),\
  901. instance_dict(oldchild)
  902. impl = old_state.manager[key].impl
  903. if initiator.impl is not impl or \
  904. initiator.op not in (OP_REPLACE, OP_REMOVE):
  905. impl.pop(old_state,
  906. old_dict,
  907. state.obj(),
  908. parent_impl._append_token or
  909. parent_impl._init_append_token(),
  910. passive=PASSIVE_NO_FETCH)
  911. if child is not None:
  912. child_state, child_dict = instance_state(child),\
  913. instance_dict(child)
  914. child_impl = child_state.manager[key].impl
  915. if initiator.parent_token is not parent_token and \
  916. initiator.parent_token is not child_impl.parent_token:
  917. _acceptable_key_err(state, initiator, child_impl)
  918. elif initiator.impl is not child_impl or \
  919. initiator.op not in (OP_APPEND, OP_REPLACE):
  920. child_impl.append(
  921. child_state,
  922. child_dict,
  923. state.obj(),
  924. initiator,
  925. passive=PASSIVE_NO_FETCH)
  926. return child
  927. def emit_backref_from_collection_append_event(state, child, initiator):
  928. if child is None:
  929. return
  930. child_state, child_dict = instance_state(child), \
  931. instance_dict(child)
  932. child_impl = child_state.manager[key].impl
  933. if initiator.parent_token is not parent_token and \
  934. initiator.parent_token is not child_impl.parent_token:
  935. _acceptable_key_err(state, initiator, child_impl)
  936. elif initiator.impl is not child_impl or \
  937. initiator.op not in (OP_APPEND, OP_REPLACE):
  938. child_impl.append(
  939. child_state,
  940. child_dict,
  941. state.obj(),
  942. initiator,
  943. passive=PASSIVE_NO_FETCH)
  944. return child
  945. def emit_backref_from_collection_remove_event(state, child, initiator):
  946. if child is not None:
  947. child_state, child_dict = instance_state(child),\
  948. instance_dict(child)
  949. child_impl = child_state.manager[key].impl
  950. if initiator.impl is not child_impl or \
  951. initiator.op not in (OP_REMOVE, OP_REPLACE):
  952. child_impl.pop(
  953. child_state,
  954. child_dict,
  955. state.obj(),
  956. initiator,
  957. passive=PASSIVE_NO_FETCH)
  958. if uselist:
  959. event.listen(attribute, "append",
  960. emit_backref_from_collection_append_event,
  961. retval=True, raw=True)
  962. else:
  963. event.listen(attribute, "set",
  964. emit_backref_from_scalar_set_event,
  965. retval=True, raw=True)
  966. # TODO: need coverage in test/orm/ of remove event
  967. event.listen(attribute, "remove",
  968. emit_backref_from_collection_remove_event,
  969. retval=True, raw=True)
  970. _NO_HISTORY = util.symbol('NO_HISTORY')
  971. _NO_STATE_SYMBOLS = frozenset([
  972. id(PASSIVE_NO_RESULT),
  973. id(NO_VALUE),
  974. id(NEVER_SET)])
  975. History = util.namedtuple("History", [
  976. "added", "unchanged", "deleted"
  977. ])
  978. class History(History):
  979. """A 3-tuple of added, unchanged and deleted values,
  980. representing the changes which have occurred on an instrumented
  981. attribute.
  982. The easiest way to get a :class:`.History` object for a particular
  983. attribute on an object is to use the :func:`.inspect` function::
  984. from sqlalchemy import inspect
  985. hist = inspect(myobject).attrs.myattribute.history
  986. Each tuple member is an iterable sequence:
  987. * ``added`` - the collection of items added to the attribute (the first
  988. tuple element).
  989. * ``unchanged`` - the collection of items that have not changed on the
  990. attribute (the second tuple element).
  991. * ``deleted`` - the collection of items that have been removed from the
  992. attribute (the third tuple element).
  993. """
  994. def __bool__(self):
  995. return self != HISTORY_BLANK
  996. __nonzero__ = __bool__
  997. def empty(self):
  998. """Return True if this :class:`.History` has no changes
  999. and no existing, unchanged state.
  1000. """
  1001. return not bool(
  1002. (self.added or self.deleted)
  1003. or self.unchanged
  1004. )
  1005. def sum(self):
  1006. """Return a collection of added + unchanged + deleted."""
  1007. return (self.added or []) +\
  1008. (self.unchanged or []) +\
  1009. (self.deleted or [])
  1010. def non_deleted(self):
  1011. """Return a collection of added + unchanged."""
  1012. return (self.added or []) +\
  1013. (self.unchanged or [])
  1014. def non_added(self):
  1015. """Return a collection of unchanged + deleted."""
  1016. return (self.unchanged or []) +\
  1017. (self.deleted or [])
  1018. def has_changes(self):
  1019. """Return True if this :class:`.History` has changes."""
  1020. return bool(self.added or self.deleted)
  1021. def as_state(self):
  1022. return History(
  1023. [(c is not None)
  1024. and instance_state(c) or None
  1025. for c in self.added],
  1026. [(c is not None)
  1027. and instance_state(c) or None
  1028. for c in self.unchanged],
  1029. [(c is not None)
  1030. and instance_state(c) or None
  1031. for c in self.deleted],
  1032. )
  1033. @classmethod
  1034. def from_scalar_attribute(cls, attribute, state, current):
  1035. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  1036. if original is _NO_HISTORY:
  1037. if current is NEVER_SET:
  1038. return cls((), (), ())
  1039. else:
  1040. return cls((), [current], ())
  1041. # don't let ClauseElement expressions here trip things up
  1042. elif attribute.is_equal(current, original) is True:
  1043. return cls((), [current], ())
  1044. else:
  1045. # current convention on native scalars is to not
  1046. # include information
  1047. # about missing previous value in "deleted", but
  1048. # we do include None, which helps in some primary
  1049. # key situations
  1050. if id(original) in _NO_STATE_SYMBOLS:
  1051. deleted = ()
  1052. else:
  1053. deleted = [original]
  1054. if current is NEVER_SET:
  1055. return cls((), (), deleted)
  1056. else:
  1057. return cls([current], (), deleted)
  1058. @classmethod
  1059. def from_object_attribute(cls, attribute, state, current):
  1060. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  1061. if original is _NO_HISTORY:
  1062. if current is NO_VALUE or current is NEVER_SET:
  1063. return cls((), (), ())
  1064. else:
  1065. return cls((), [current], ())
  1066. elif current is original:
  1067. return cls((), [current], ())
  1068. else:
  1069. # current convention on related objects is to not
  1070. # include information
  1071. # about missing previous value in "deleted", and
  1072. # to also not include None - the dependency.py rules
  1073. # ignore the None in any case.
  1074. if id(original) in _NO_STATE_SYMBOLS or original is None:
  1075. deleted = ()
  1076. else:
  1077. deleted = [original]
  1078. if current is NO_VALUE or current is NEVER_SET:
  1079. return cls((), (), deleted)
  1080. else:
  1081. return cls([current], (), deleted)
  1082. @classmethod
  1083. def from_collection(cls, attribute, state, current):
  1084. original = state.committed_state.get(attribute.key, _NO_HISTORY)
  1085. if current is NO_VALUE or current is NEVER_SET:
  1086. return cls((), (), ())
  1087. current = getattr(current, '_sa_adapter')
  1088. if original in (NO_VALUE, NEVER_SET):
  1089. return cls(list(current), (), ())
  1090. elif original is _NO_HISTORY:
  1091. return cls((), list(current), ())
  1092. else:
  1093. current_states = [((c is not None) and instance_state(c)
  1094. or None, c)
  1095. for c in current
  1096. ]
  1097. original_states = [((c is not None) and instance_state(c)
  1098. or None, c)
  1099. for c in original
  1100. ]
  1101. current_set = dict(current_states)
  1102. original_set = dict(original_states)
  1103. return cls(
  1104. [o for s, o in current_states if s not in original_set],
  1105. [o for s, o in current_states if s in original_set],
  1106. [o for s, o in original_states if s not in current_set]
  1107. )
  1108. HISTORY_BLANK = History(None, None, None)
  1109. def get_history(obj, key, passive=PASSIVE_OFF):
  1110. """Return a :class:`.History` record for the given object
  1111. and attribute key.
  1112. :param obj: an object whose class is instrumented by the
  1113. attributes package.
  1114. :param key: string attribute name.
  1115. :param passive: indicates loading behavior for the attribute
  1116. if the value is not already present. This is a
  1117. bitflag attribute, which defaults to the symbol
  1118. :attr:`.PASSIVE_OFF` indicating all necessary SQL
  1119. should be emitted.
  1120. """
  1121. if passive is True:
  1122. util.warn_deprecated("Passing True for 'passive' is deprecated. "
  1123. "Use attributes.PASSIVE_NO_INITIALIZE")
  1124. passive = PASSIVE_NO_INITIALIZE
  1125. elif passive is False:
  1126. util.warn_deprecated("Passing False for 'passive' is "
  1127. "deprecated. Use attributes.PASSIVE_OFF")
  1128. passive = PASSIVE_OFF
  1129. return get_state_history(instance_state(obj), key, passive)
  1130. def get_state_history(state, key, passive=PASSIVE_OFF):
  1131. return state.get_history(key, passive)
  1132. def has_parent(cls, obj, key, optimistic=False):
  1133. """TODO"""
  1134. manager = manager_of_class(cls)
  1135. state = instance_state(obj)
  1136. return manager.has_parent(state, key, optimistic)
  1137. def register_attribute(class_, key, **kw):
  1138. comparator = kw.pop('comparator', None)
  1139. parententity = kw.pop('parententity', None)
  1140. doc = kw.pop('doc', None)
  1141. desc = register_descriptor(class_, key,
  1142. comparator, parententity, doc=doc)
  1143. register_attribute_impl(class_, key, **kw)
  1144. return desc
  1145. def register_attribute_impl(class_, key,
  1146. uselist=False, callable_=None,
  1147. useobject=False,
  1148. impl_class=None, backref=None, **kw):
  1149. manager = manager_of_class(class_)
  1150. if uselist:
  1151. factory = kw.pop('typecallable', None)
  1152. typecallable = manager.instrument_collection_class(
  1153. key, factory or list)
  1154. else:
  1155. typecallable = kw.pop('typecallable', None)
  1156. dispatch = manager[key].dispatch
  1157. if impl_class:
  1158. impl = impl_class(class_, key, typecallable, dispatch, **kw)
  1159. elif uselist:
  1160. impl = CollectionAttributeImpl(class_, key, callable_, dispatch,
  1161. typecallable=typecallable, **kw)
  1162. elif useobject:
  1163. impl = ScalarObjectAttributeImpl(class_, key, callable_,
  1164. dispatch, **kw)
  1165. else:
  1166. impl = ScalarAttributeImpl(class_, key, callable_, dispatch, **kw)
  1167. manager[key].impl = impl
  1168. if backref:
  1169. backref_listeners(manager[key], backref, uselist)
  1170. manager.post_configure_attribute(key)
  1171. return manager[key]
  1172. def register_descriptor(class_, key, comparator=None,
  1173. parententity=None, doc=None):
  1174. manager = manager_of_class(class_)
  1175. descriptor = InstrumentedAttribute(class_, key, comparator=comparator,
  1176. parententity=parententity)
  1177. descriptor.__doc__ = doc
  1178. manager.instrument_attribute(key, descriptor)
  1179. return descriptor
  1180. def unregister_attribute(class_, key):
  1181. manager_of_class(class_).uninstrument_attribute(key)
  1182. def init_collection(obj, key):
  1183. """Initialize a collection attribute and return the collection adapter.
  1184. This function is used to provide direct access to collection internals
  1185. for a previously unloaded attribute. e.g.::
  1186. collection_adapter = init_collection(someobject, 'elements')
  1187. for elem in values:
  1188. collection_adapter.append_without_event(elem)
  1189. For an easier way to do the above, see
  1190. :func:`~sqlalchemy.orm.attributes.set_committed_value`.
  1191. obj is an instrumented object instance. An InstanceState
  1192. is accepted directly for backwards compatibility but
  1193. this usage is deprecated.
  1194. """
  1195. state = instance_state(obj)
  1196. dict_ = state.dict
  1197. return init_state_collection(state, dict_, key)
  1198. def init_state_collection(state, dict_, key):
  1199. """Initialize a collection attribute and return the collection adapter."""
  1200. attr = state.manager[key].impl
  1201. user_data = attr.initialize(state, dict_)
  1202. return attr.get_collection(state, dict_, user_data)
  1203. def set_committed_value(instance, key, value):
  1204. """Set the value of an attribute with no history events.
  1205. Cancels any previous history present. The value should be
  1206. a scalar value for scalar-holding attributes, or
  1207. an iterable for any collection-holding attribute.
  1208. This is the same underlying method used when a lazy loader
  1209. fires off and loads additional data from the database.
  1210. In particular, this method can be used by application code
  1211. which has loaded additional attributes or collections through
  1212. separate queries, which can then be attached to an instance
  1213. as though it were part of its original loaded state.
  1214. """
  1215. state, dict_ = instance_state(instance), instance_dict(instance)
  1216. state.manager[key].impl.set_committed_value(state, dict_, value)
  1217. def set_attribute(instance, key, value):
  1218. """Set the value of an attribute, firing history events.
  1219. This function may be used regardless of instrumentation
  1220. applied directly to the class, i.e. no descriptors are required.
  1221. Custom attribute management schemes will need to make usage
  1222. of this method to establish attribute state as understood
  1223. by SQLAlchemy.
  1224. """
  1225. state, dict_ = instance_state(instance), instance_dict(instance)
  1226. state.manager[key].impl.set(state, dict_, value, None)
  1227. def get_attribute(instance, key):
  1228. """Get the value of an attribute, firing any callables required.
  1229. This function may be used regardless of instrumentation
  1230. applied directly to the class, i.e. no descriptors are required.
  1231. Custom attribute management schemes will need to make usage
  1232. of this method to make usage of attribute state as understood
  1233. by SQLAlchemy.
  1234. """
  1235. state, dict_ = instance_state(instance), instance_dict(instance)
  1236. return state.manager[key].impl.get(state, dict_)
  1237. def del_attribute(instance, key):
  1238. """Delete the value of an attribute, firing history events.
  1239. This function may be used regardless of instrumentation
  1240. applied directly to the class, i.e. no descriptors are required.
  1241. Custom attribute management schemes will need to make usage
  1242. of this method to establish attribute state as understood
  1243. by SQLAlchemy.
  1244. """
  1245. state, dict_ = instance_state(instance), instance_dict(instance)
  1246. state.manager[key].impl.delete(state, dict_)
  1247. def flag_modified(instance, key):
  1248. """Mark an attribute on an instance as 'modified'.
  1249. This sets the 'modified' flag on the instance and
  1250. establishes an unconditional change event for the given attribute.
  1251. """
  1252. state, dict_ = instance_state(instance), instance_dict(instance)
  1253. impl = state.manager[key].impl
  1254. state._modified_event(dict_, impl, NO_VALUE, force=True)