root/galaxy-central/eggs/SQLAlchemy-0.5.6_dev_r6498-py2.6.egg/sqlalchemy/orm/session.py @ 3

リビジョン 3, 59.1 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# session.py
2# Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com
3#
4# This module is part of SQLAlchemy and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7"""Provides the Session class and related utilities."""
8
9import weakref
10from itertools import chain
11import sqlalchemy.exceptions as sa_exc
12from sqlalchemy import util, sql, engine, log
13from sqlalchemy.sql import util as sql_util, expression
14from sqlalchemy.orm import (
15    SessionExtension, attributes, exc, query, unitofwork, util as mapperutil, state
16    )
17from sqlalchemy.orm.util import object_mapper as _object_mapper
18from sqlalchemy.orm.util import class_mapper as _class_mapper
19from sqlalchemy.orm.util import (
20    _class_to_mapper, _state_has_identity, _state_mapper,
21    )
22from sqlalchemy.orm.mapper import Mapper
23from sqlalchemy.orm.unitofwork import UOWTransaction
24from sqlalchemy.orm import identity
25
26__all__ = ['Session', 'SessionTransaction', 'SessionExtension']
27
28
29def sessionmaker(bind=None, class_=None, autoflush=True, autocommit=False,
30                 expire_on_commit=True, **kwargs):
31    """Generate a custom-configured :class:`~sqlalchemy.orm.session.Session` class.
32
33    The returned object is a subclass of ``Session``, which, when instantiated
34    with no arguments, uses the keyword arguments configured here as its
35    constructor arguments.
36
37    It is intended that the `sessionmaker()` function be called within the
38    global scope of an application, and the returned class be made available
39    to the rest of the application as the single class used to instantiate
40    sessions.
41
42    e.g.::
43
44        # global scope
45        Session = sessionmaker(autoflush=False)
46
47        # later, in a local scope, create and use a session:
48        sess = Session()
49
50    Any keyword arguments sent to the constructor itself will override the
51    "configured" keywords::
52
53        Session = sessionmaker()
54
55        # bind an individual session to a connection
56        sess = Session(bind=connection)
57
58    The class also includes a special classmethod ``configure()``, which
59    allows additional configurational options to take place after the custom
60    ``Session`` class has been generated.  This is useful particularly for
61    defining the specific ``Engine`` (or engines) to which new instances of
62    ``Session`` should be bound::
63
64        Session = sessionmaker()
65        Session.configure(bind=create_engine('sqlite:///foo.db'))
66
67        sess = Session()
68
69    Options:
70
71    autocommit
72      Defaults to ``False``. When ``True``, the ``Session`` does not keep a
73      persistent transaction running, and will acquire connections from the
74      engine on an as-needed basis, returning them immediately after their
75      use. Flushes will begin and commit (or possibly rollback) their own
76      transaction if no transaction is present. When using this mode, the
77      `session.begin()` method may be used to begin a transaction explicitly.
78
79      Leaving it on its default value of ``False`` means that the ``Session``
80      will acquire a connection and begin a transaction the first time it is
81      used, which it will maintain persistently until ``rollback()``,
82      ``commit()``, or ``close()`` is called. When the transaction is released
83      by any of these methods, the ``Session`` is ready for the next usage,
84      which will again acquire and maintain a new connection/transaction.
85
86    autoflush
87      When ``True``, all query operations will issue a ``flush()`` call to
88      this ``Session`` before proceeding. This is a convenience feature so
89      that ``flush()`` need not be called repeatedly in order for database
90      queries to retrieve results. It's typical that ``autoflush`` is used in
91      conjunction with ``autocommit=False``.  In this scenario, explicit calls
92      to ``flush()`` are rarely needed; you usually only need to call
93      ``commit()`` (which flushes) to finalize changes.
94
95    bind
96      An optional ``Engine`` or ``Connection`` to which this ``Session``
97      should be bound. When specified, all SQL operations performed by this
98      session will execute via this connectable.
99
100    binds
101      An optional dictionary, which contains more granular "bind" information
102      than the ``bind`` parameter provides. This dictionary can map individual
103      ``Table`` instances as well as ``Mapper`` instances to individual
104      ``Engine`` or ``Connection`` objects. Operations which proceed relative
105      to a particular ``Mapper`` will consult this dictionary for the direct
106      ``Mapper`` instance as well as the mapper's ``mapped_table`` attribute
107      in order to locate an connectable to use. The full resolution is
108      described in the ``get_bind()`` method of ``Session``. Usage looks
109      like::
110
111        sess = Session(binds={
112            SomeMappedClass: create_engine('postgres://engine1'),
113            somemapper: create_engine('postgres://engine2'),
114            some_table: create_engine('postgres://engine3'),
115            })
116
117      Also see the ``bind_mapper()`` and ``bind_table()`` methods.
118
119    \class_
120      Specify an alternate class other than ``sqlalchemy.orm.session.Session``
121      which should be used by the returned class.  This is the only argument
122      that is local to the ``sessionmaker()`` function, and is not sent
123      directly to the constructor for ``Session``.
124
125    echo_uow
126      Deprecated.  Use
127      ``logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG)``.
128
129    _enable_transaction_accounting
130      Defaults to ``True``.  A legacy-only flag which when ``False``
131      disables *all* 0.5-style object accounting on transaction boundaries,
132      including auto-expiry of instances on rollback and commit, maintenance of
133      the "new" and "deleted" lists upon rollback, and autoflush
134      of pending changes upon begin(), all of which are interdependent.
135
136    expire_on_commit
137      Defaults to ``True``. When ``True``, all instances will be fully expired after
138      each ``commit()``, so that all attribute/object access subsequent to a completed
139      transaction will load from the most recent database state.
140
141    extension
142      An optional :class:`~sqlalchemy.orm.session.SessionExtension` instance, or
143      a list of such instances, which
144      will receive pre- and post- commit and flush events, as well as a
145      post-rollback event.  User- defined code may be placed within these
146      hooks using a user-defined subclass of ``SessionExtension``.
147
148    query_cls
149      Class which should be used to create new Query objects, as returned
150      by the ``query()`` method.  Defaults to :class:`~sqlalchemy.orm.query.Query`.
151
152    twophase
153      When ``True``, all transactions will be started using
154      :mod:~sqlalchemy.engine_TwoPhaseTransaction. During a ``commit()``, after
155      ``flush()`` has been issued for all attached databases, the
156      ``prepare()`` method on each database's ``TwoPhaseTransaction`` will be
157      called. This allows each database to roll back the entire transaction,
158      before each transaction is committed.
159
160    weak_identity_map
161      When set to the default value of ``True``, a weak-referencing map is
162      used; instances which are not externally referenced will be garbage
163      collected immediately. For dereferenced instances which have pending
164      changes present, the attribute management system will create a temporary
165      strong-reference to the object which lasts until the changes are flushed
166      to the database, at which point it's again dereferenced. Alternatively,
167      when using the value ``False``, the identity map uses a regular Python
168      dictionary to store instances. The session will maintain all instances
169      present until they are removed using expunge(), clear(), or purge().
170
171    """
172    if 'transactional' in kwargs:
173        util.warn_deprecated(
174            "The 'transactional' argument to sessionmaker() is deprecated; "
175            "use autocommit=True|False instead.")
176        autocommit = not kwargs.pop('transactional')
177
178    kwargs['bind'] = bind
179    kwargs['autoflush'] = autoflush
180    kwargs['autocommit'] = autocommit
181    kwargs['expire_on_commit'] = expire_on_commit
182
183    if class_ is None:
184        class_ = Session
185
186    class Sess(object):
187        def __init__(self, **local_kwargs):
188            for k in kwargs:
189                local_kwargs.setdefault(k, kwargs[k])
190            super(Sess, self).__init__(**local_kwargs)
191
192        def configure(self, **new_kwargs):
193            """(Re)configure the arguments for this sessionmaker.
194
195            e.g.::
196
197                Session = sessionmaker()
198
199                Session.configure(bind=create_engine('sqlite://'))
200            """
201            kwargs.update(new_kwargs)
202        configure = classmethod(configure)
203    s = type.__new__(type, "Session", (Sess, class_), {})
204    return s
205
206
207class SessionTransaction(object):
208    """A Session-level transaction.
209
210    This corresponds to one or more :class:`~sqlalchemy.engine.Transaction`
211    instances behind the scenes, with one ``Transaction`` per ``Engine`` in
212    use.
213
214    Direct usage of ``SessionTransaction`` is not necessary as of SQLAlchemy
215    0.4; use the ``begin()`` and ``commit()`` methods on ``Session`` itself.
216
217    The ``SessionTransaction`` object is **not** thread-safe.
218
219    .. index::
220      single: thread safety; SessionTransaction
221
222    """
223
224    def __init__(self, session, parent=None, nested=False):
225        self.session = session
226        self._connections = {}
227        self._parent = parent
228        self.nested = nested
229        self._active = True
230        self._prepared = False
231        if not parent and nested:
232            raise sa_exc.InvalidRequestError(
233                "Can't start a SAVEPOINT transaction when no existing "
234                "transaction is in progress")
235
236        if self.session._enable_transaction_accounting:
237            self._take_snapshot()
238
239    @property
240    def is_active(self):
241        return self.session is not None and self._active
242
243    def _assert_is_active(self):
244        self._assert_is_open()
245        if not self._active:
246            raise sa_exc.InvalidRequestError(
247                "The transaction is inactive due to a rollback in a "
248                "subtransaction.  Issue rollback() to cancel the transaction.")
249
250    def _assert_is_open(self, error_msg="The transaction is closed"):
251        if self.session is None:
252            raise sa_exc.InvalidRequestError(error_msg)
253
254    @property
255    def _is_transaction_boundary(self):
256        return self.nested or not self._parent
257
258    def connection(self, bindkey, **kwargs):
259        self._assert_is_active()
260        engine = self.session.get_bind(bindkey, **kwargs)
261        return self._connection_for_bind(engine)
262
263    def _begin(self, nested=False):
264        self._assert_is_active()
265        return SessionTransaction(
266            self.session, self, nested=nested)
267
268    def _iterate_parents(self, upto=None):
269        if self._parent is upto:
270            return (self,)
271        else:
272            if self._parent is None:
273                raise sa_exc.InvalidRequestError(
274                    "Transaction %s is not on the active transaction list" % (
275                    upto))
276            return (self,) + self._parent._iterate_parents(upto)
277
278    def _take_snapshot(self):
279        if not self._is_transaction_boundary:
280            self._new = self._parent._new
281            self._deleted = self._parent._deleted
282            return
283
284        if not self.session._flushing:
285            self.session.flush()
286
287        self._new = weakref.WeakKeyDictionary()
288        self._deleted = weakref.WeakKeyDictionary()
289
290    def _restore_snapshot(self):
291        assert self._is_transaction_boundary
292
293        for s in set(self._deleted).union(self.session._deleted):
294            self.session._update_impl(s)
295
296        assert not self.session._deleted
297
298        for s in set(self._new).union(self.session._new):
299            self.session._expunge_state(s)
300
301        for s in self.session.identity_map.all_states():
302            _expire_state(s, None, instance_dict=self.session.identity_map)
303
304    def _remove_snapshot(self):
305        assert self._is_transaction_boundary
306
307        if not self.nested and self.session.expire_on_commit:
308            for s in self.session.identity_map.all_states():
309                _expire_state(s, None, instance_dict=self.session.identity_map)
310
311    def _connection_for_bind(self, bind):
312        self._assert_is_active()
313
314        if bind in self._connections:
315            return self._connections[bind][0]
316
317        if self._parent:
318            conn = self._parent._connection_for_bind(bind)
319            if not self.nested:
320                return conn
321        else:
322            if isinstance(bind, engine.Connection):
323                conn = bind
324                if conn.engine in self._connections:
325                    raise sa_exc.InvalidRequestError(
326                        "Session already has a Connection associated for the "
327                        "given Connection's Engine")
328            else:
329                conn = bind.contextual_connect()
330
331        if self.session.twophase and self._parent is None:
332            transaction = conn.begin_twophase()
333        elif self.nested:
334            transaction = conn.begin_nested()
335        else:
336            transaction = conn.begin()
337
338        self._connections[conn] = self._connections[conn.engine] = \
339          (conn, transaction, conn is not bind)
340        for ext in self.session.extensions:
341            ext.after_begin(self.session, self, conn)
342        return conn
343
344    def prepare(self):
345        if self._parent is not None or not self.session.twophase:
346            raise sa_exc.InvalidRequestError(
347                "Only root two phase transactions of can be prepared")
348        self._prepare_impl()
349
350    def _prepare_impl(self):
351        self._assert_is_active()
352        if self._parent is None or self.nested:
353            for ext in self.session.extensions:
354                ext.before_commit(self.session)
355
356        stx = self.session.transaction
357        if stx is not self:
358            for subtransaction in stx._iterate_parents(upto=self):
359                subtransaction.commit()
360
361        if not self.session._flushing:
362            self.session.flush()
363
364        if self._parent is None and self.session.twophase:
365            try:
366                for t in set(self._connections.values()):
367                    t[1].prepare()
368            except:
369                self.rollback()
370                raise
371
372        self._deactivate()
373        self._prepared = True
374
375    def commit(self):
376        self._assert_is_open()
377        if not self._prepared:
378            self._prepare_impl()
379
380        if self._parent is None or self.nested:
381            for t in set(self._connections.values()):
382                t[1].commit()
383
384            for ext in self.session.extensions:
385                ext.after_commit(self.session)
386
387            if self.session._enable_transaction_accounting:
388                self._remove_snapshot()
389
390        self.close()
391        return self._parent
392
393    def rollback(self):
394        self._assert_is_open()
395
396        stx = self.session.transaction
397        if stx is not self:
398            for subtransaction in stx._iterate_parents(upto=self):
399                subtransaction.close()
400
401        if self.is_active or self._prepared:
402            for transaction in self._iterate_parents():
403                if transaction._parent is None or transaction.nested:
404                    transaction._rollback_impl()
405                    transaction._deactivate()
406                    break
407                else:
408                    transaction._deactivate()
409
410        self.close()
411        return self._parent
412
413    def _rollback_impl(self):
414        for t in set(self._connections.values()):
415            t[1].rollback()
416
417        if self.session._enable_transaction_accounting:
418            self._restore_snapshot()
419
420        for ext in self.session.extensions:
421            ext.after_rollback(self.session)
422
423    def _deactivate(self):
424        self._active = False
425
426    def close(self):
427        self.session.transaction = self._parent
428        if self._parent is None:
429            for connection, transaction, autoclose in set(self._connections.values()):
430                if autoclose:
431                    connection.close()
432                else:
433                    transaction.close()
434            if not self.session.autocommit:
435                self.session.begin()
436        self._deactivate()
437        self.session = None
438        self._connections = None
439
440    def __enter__(self):
441        return self
442
443    def __exit__(self, type, value, traceback):
444        self._assert_is_open("Cannot end transaction context. The transaction was closed from within the context")
445        if self.session.transaction is None:
446            return
447        if type is None:
448            try:
449                self.commit()
450            except:
451                self.rollback()
452                raise
453        else:
454            self.rollback()
455
456class Session(object):
457    """Manages persistence operations for ORM-mapped objects.
458
459    The Session is the front end to SQLAlchemy's **Unit of Work**
460    implementation.  The concept behind Unit of Work is to track modifications
461    to a field of objects, and then be able to flush those changes to the
462    database in a single operation.
463
464    SQLAlchemy's unit of work includes these functions:
465
466    * The ability to track in-memory changes on scalar- and collection-based
467      object attributes, such that database persistence operations can be
468      assembled based on those changes.
469
470    * The ability to organize individual SQL queries and population of newly
471      generated primary and foreign key-holding attributes during a persist
472      operation such that referential integrity is maintained at all times.
473
474    * The ability to maintain insert ordering against the order in which new
475      instances were added to the session.
476
477    * An Identity Map, which is a dictionary keying instances to their unique
478      primary key identity. This ensures that only one copy of a particular
479      entity is ever present within the session, even if repeated load
480      operations for the same entity occur. This allows many parts of an
481      application to get a handle to a particular object without any chance of
482      modifications going to two different places.
483
484    When dealing with instances of mapped classes, an instance may be
485    *attached* to a particular Session, else it is *unattached* . An instance
486    also may or may not correspond to an actual row in the database. These
487    conditions break up into four distinct states:
488
489    * *Transient* - an instance that's not in a session, and is not saved to
490      the database; i.e. it has no database identity. The only relationship
491      such an object has to the ORM is that its class has a ``mapper()``
492      associated with it.
493
494    * *Pending* - when you ``add()`` a transient instance, it becomes
495      pending. It still wasn't actually flushed to the database yet, but it
496      will be when the next flush occurs.
497
498    * *Persistent* - An instance which is present in the session and has a
499      record in the database. You get persistent instances by either flushing
500      so that the pending instances become persistent, or by querying the
501      database for existing instances (or moving persistent instances from
502      other sessions into your local session).
503
504    * *Detached* - an instance which has a record in the database, but is not
505      in any session. Theres nothing wrong with this, and you can use objects
506      normally when they're detached, **except** they will not be able to
507      issue any SQL in order to load collections or attributes which are not
508      yet loaded, or were marked as "expired".
509
510    The session methods which control instance state include ``add()``,
511    ``delete()``, ``merge()``, and ``expunge()``.
512
513    The Session object is generally **not** threadsafe.  A session which is
514    set to ``autocommit`` and is only read from may be used by concurrent
515    threads if it's acceptable that some object instances may be loaded twice.
516
517    The typical pattern to managing Sessions in a multi-threaded environment
518    is either to use mutexes to limit concurrent access to one thread at a
519    time, or more commonly to establish a unique session for every thread,
520    using a threadlocal variable.  SQLAlchemy provides a thread-managed
521    Session adapter, provided by the :func:`~sqlalchemy.orm.scoped_session`
522    function.
523
524    """
525
526    public_methods = (
527        '__contains__', '__iter__', 'add', 'add_all', 'begin', 'begin_nested',
528        'clear', 'close', 'commit', 'connection', 'delete', 'execute', 'expire',
529        'expire_all', 'expunge', 'expunge_all', 'flush', 'get_bind', 'is_modified',
530        'merge', 'query', 'refresh', 'rollback', 'save',
531        'save_or_update', 'scalar', 'update')
532
533    def __init__(self, bind=None, autoflush=True, expire_on_commit=True,
534                _enable_transaction_accounting=True,
535                 autocommit=False, twophase=False, echo_uow=None,
536                 weak_identity_map=True, binds=None, extension=None, query_cls=query.Query):
537        """Construct a new Session.
538
539        Arguments to ``Session`` are described using the
540        :func:`~sqlalchemy.orm.sessionmaker` function.
541
542        """
543       
544        if echo_uow is not None:
545            util.warn_deprecated(
546                "echo_uow is deprecated. "
547                "Use logging.getLogger('sqlalchemy.orm.unitofwork').setLevel(logging.DEBUG).")
548            log.class_logger(UOWTransaction, echo_uow)
549
550        if weak_identity_map:
551            self._identity_cls = identity.WeakInstanceDict
552        else:
553            self._identity_cls = identity.StrongInstanceDict
554        self.identity_map = self._identity_cls()
555
556        self._new = {}   # InstanceState->object, strong refs object
557        self._deleted = {}  # same
558        self.bind = bind
559        self.__binds = {}
560        self._flushing = False
561        self.transaction = None
562        self.hash_key = id(self)
563        self.autoflush = autoflush
564        self.autocommit = autocommit
565        self.expire_on_commit = expire_on_commit
566        self._enable_transaction_accounting = _enable_transaction_accounting
567        self.twophase = twophase
568        self.extensions = util.to_list(extension) or []
569        self._query_cls = query_cls
570        self._mapper_flush_opts = {}
571
572        if binds is not None:
573            for mapperortable, value in binds.iteritems():
574                if isinstance(mapperortable, type):
575                    mapperortable = _class_mapper(mapperortable).base_mapper
576                self.__binds[mapperortable] = value
577                if isinstance(mapperortable, Mapper):
578                    for t in mapperortable._all_tables:
579                        self.__binds[t] = value
580
581        if not self.autocommit:
582            self.begin()
583        _sessions[self.hash_key] = self
584
585    def begin(self, subtransactions=False, nested=False):
586        """Begin a transaction on this Session.
587
588        If this Session is already within a transaction, either a plain
589        transaction or nested transaction, an error is raised, unless
590        ``subtransactions=True`` or ``nested=True`` is specified.
591
592        The ``subtransactions=True`` flag indicates that this ``begin()`` can
593        create a subtransaction if a transaction is already in progress.  A
594        subtransaction is a non-transactional, delimiting construct that
595        allows matching begin()/commit() pairs to be nested together, with
596        only the outermost begin/commit pair actually affecting transactional
597        state.  When a rollback is issued, the subtransaction will directly
598        roll back the innermost real transaction, however each subtransaction
599        still must be explicitly rolled back to maintain proper stacking of
600        subtransactions.
601
602        If no transaction is in progress, then a real transaction is begun.
603
604        The ``nested`` flag begins a SAVEPOINT transaction and is equivalent
605        to calling ``begin_nested()``.
606
607        """
608        if self.transaction is not None:
609            if subtransactions or nested:
610                self.transaction = self.transaction._begin(
611                    nested=nested)
612            else:
613                raise sa_exc.InvalidRequestError(
614                    "A transaction is already begun.  Use subtransactions=True "
615                    "to allow subtransactions.")
616        else:
617            self.transaction = SessionTransaction(
618                self, nested=nested)
619        return self.transaction  # needed for __enter__/__exit__ hook
620
621    def begin_nested(self):
622        """Begin a `nested` transaction on this Session.
623
624        The target database(s) must support SQL SAVEPOINTs or a
625        SQLAlchemy-supported vendor implementation of the idea.
626
627        The nested transaction is a real transation, unlike a "subtransaction"
628        which corresponds to multiple ``begin()`` calls.  The next
629        ``rollback()`` or ``commit()`` call will operate upon this nested
630        transaction.
631
632        """
633        return self.begin(nested=True)
634
635    def rollback(self):
636        """Rollback the current transaction in progress.
637
638        If no transaction is in progress, this method is a pass-through.
639
640        This method rolls back the current transaction or nested transaction
641        regardless of subtransactions being in effect.  All subtransactions up
642        to the first real transaction are closed.  Subtransactions occur when
643        begin() is called multiple times.
644
645        """
646        if self.transaction is None:
647            pass
648        else:
649            self.transaction.rollback()
650
651    def commit(self):
652        """Flush pending changes and commit the current transaction.
653
654        If no transaction is in progress, this method raises an
655        InvalidRequestError.
656
657        If a subtransaction is in effect (which occurs when begin() is called
658        multiple times), the subtransaction will be closed, and the next call
659        to ``commit()`` will operate on the enclosing transaction.
660
661        For a session configured with autocommit=False, a new transaction will
662        be begun immediately after the commit, but note that the newly begun
663        transaction does *not* use any connection resources until the first
664        SQL is actually emitted.
665
666        """
667        if self.transaction is None:
668            if not self.autocommit:
669                self.begin()
670            else:
671                raise sa_exc.InvalidRequestError("No transaction is begun.")
672
673        self.transaction.commit()
674
675    def prepare(self):
676        """Prepare the current transaction in progress for two phase commit.
677
678        If no transaction is in progress, this method raises an
679        InvalidRequestError.
680
681        Only root transactions of two phase sessions can be prepared. If the
682        current transaction is not such, an InvalidRequestError is raised.
683
684        """
685        if self.transaction is None:
686            if not self.autocommit:
687                self.begin()
688            else:
689                raise sa_exc.InvalidRequestError("No transaction is begun.")
690
691        self.transaction.prepare()
692
693    def connection(self, mapper=None, clause=None):
694        """Return the active Connection.
695
696        Retrieves the ``Connection`` managing the current transaction.  Any
697        operations executed on the Connection will take place in the same
698        transactional context as ``Session`` operations.
699
700        For ``autocommit`` Sessions with no active manual transaction,
701        ``connection()`` is a passthrough to ``contextual_connect()`` on the
702        underlying engine.
703
704        Ambiguity in multi-bind or unbound Sessions can be resolved through
705        any of the optional keyword arguments.  See ``get_bind()`` for more
706        information.
707
708        mapper
709          Optional, a ``mapper`` or mapped class
710
711        clause
712          Optional, any ``ClauseElement``
713
714        """
715        return self.__connection(self.get_bind(mapper, clause))
716
717    def __connection(self, engine, **kwargs):
718        if self.transaction is not None:
719            return self.transaction._connection_for_bind(engine)
720        else:
721            return engine.contextual_connect(**kwargs)
722
723    def execute(self, clause, params=None, mapper=None, **kw):
724        """Execute a clause within the current transaction.
725
726        Returns a ``ResultProxy`` of execution results.  `autocommit` Sessions
727        will create a transaction on the fly.
728
729        Connection ambiguity in multi-bind or unbound Sessions will be
730        resolved by inspecting the clause for binds.  The 'mapper' and
731        'instance' keyword arguments may be used if this is insufficient, See
732        ``get_bind()`` for more information.
733
734        clause
735            A ClauseElement (i.e. select(), text(), etc.) or
736            string SQL statement to be executed
737
738        params
739            Optional, a dictionary of bind parameters.
740
741        mapper
742          Optional, a ``mapper`` or mapped class
743
744        \**kw
745          Additional keyword arguments are sent to :meth:`get_bind()`
746          which locates a connectable to use for the execution.
747          Subclasses of :class:`Session` may override this.
748         
749        """
750        clause = expression._literal_as_text(clause)
751
752        engine = self.get_bind(mapper, clause=clause, **kw)
753
754        return self.__connection(engine, close_with_result=True).execute(
755            clause, params or {})
756
757    def scalar(self, clause, params=None, mapper=None, **kw):
758        """Like execute() but return a scalar result."""
759       
760        return self.execute(clause, params=params, mapper=mapper, **kw).scalar()
761
762    def close(self):
763        """Close this Session.
764
765        This clears all items and ends any transaction in progress.
766
767        If this session were created with ``autocommit=False``, a new
768        transaction is immediately begun.  Note that this new transaction does
769        not use any connection resources until they are first needed.
770
771        """
772        self.expunge_all()
773        if self.transaction is not None:
774            for transaction in self.transaction._iterate_parents():
775                transaction.close()
776
777    @classmethod
778    def close_all(cls):
779        """Close *all* sessions in memory."""
780
781        for sess in _sessions.values():
782            sess.close()
783
784    def expunge_all(self):
785        """Remove all object instances from this ``Session``.
786
787        This is equivalent to calling ``expunge(obj)`` on all objects in this
788        ``Session``.
789
790        """
791        for state in self.identity_map.all_states() + list(self._new):
792            state.detach()
793
794        self.identity_map = self._identity_cls()
795        self._new = {}
796        self._deleted = {}
797
798    clear = util.deprecated("Use session.expunge_all()")(expunge_all)
799
800    # TODO: need much more test coverage for bind_mapper() and similar !
801    # TODO: + crystalize + document resolution order vis. bind_mapper/bind_table
802
803    def bind_mapper(self, mapper, bind):
804        """Bind operations for a mapper to a Connectable.
805
806        mapper
807          A mapper instance or mapped class
808
809        bind
810          Any Connectable: a ``Engine`` or ``Connection``.
811
812        All subsequent operations involving this mapper will use the given
813        `bind`.
814
815        """
816        if isinstance(mapper, type):
817            mapper = _class_mapper(mapper)
818
819        self.__binds[mapper.base_mapper] = bind
820        for t in mapper._all_tables:
821            self.__binds[t] = bind
822
823    def bind_table(self, table, bind):
824        """Bind operations on a Table to a Connectable.
825
826        table
827          A ``Table`` instance
828
829        bind
830          Any Connectable: a ``Engine`` or ``Connection``.
831
832        All subsequent operations involving this ``Table`` will use the
833        given `bind`.
834
835        """
836        self.__binds[table] = bind
837
838    def get_bind(self, mapper, clause=None):
839        """Return an engine corresponding to the given arguments.
840
841        All arguments are optional.
842
843        mapper
844          Optional, a ``Mapper`` or mapped class
845
846        clause
847          Optional, A ClauseElement (i.e. select(), text(), etc.)
848
849        """
850        if mapper is clause is None:
851            if self.bind:
852                return self.bind
853            else:
854                raise sa_exc.UnboundExecutionError(
855                    "This session is not bound to a single Engine or "
856                    "Connection, and no context was provided to locate "
857                    "a binding.")
858
859        c_mapper = mapper is not None and _class_to_mapper(mapper) or None
860
861        # manually bound?
862        if self.__binds:
863            if c_mapper:
864                if c_mapper.base_mapper in self.__binds:
865                    return self.__binds[c_mapper.base_mapper]
866                elif c_mapper.mapped_table in self.__binds:
867                    return self.__binds[c_mapper.mapped_table]
868            if clause:
869                for t in sql_util.find_tables(clause):
870                    if t in self.__binds:
871                        return self.__binds[t]
872
873        if self.bind:
874            return self.bind
875
876        if isinstance(clause, sql.expression.ClauseElement) and clause.bind:
877            return clause.bind
878
879        if c_mapper and c_mapper.mapped_table.bind:
880            return c_mapper.mapped_table.bind
881
882        context = []
883        if mapper is not None:
884            context.append('mapper %s' % c_mapper)
885        if clause is not None:
886            context.append('SQL expression')
887
888        raise sa_exc.UnboundExecutionError(
889            "Could not locate a bind configured on %s or this Session" % (
890            ', '.join(context)))
891
892    def query(self, *entities, **kwargs):
893        """Return a new ``Query`` object corresponding to this ``Session``."""
894
895        return self._query_cls(entities, self, **kwargs)
896
897    def _autoflush(self):
898        if self.autoflush and not self._flushing:
899            self.flush()
900
901    def _finalize_loaded(self, states):
902        for state, dict_ in states.items():
903            state.commit_all(dict_, self.identity_map)
904
905    def refresh(self, instance, attribute_names=None):
906        """Refresh the attributes on the given instance.
907
908        A query will be issued to the database and all attributes will be
909        refreshed with their current database value.
910
911        Lazy-loaded relational attributes will remain lazily loaded, so that
912        the instance-wide refresh operation will be followed immediately by
913        the lazy load of that attribute.
914
915        Eagerly-loaded relational attributes will eagerly load within the
916        single refresh operation.
917
918        The ``attribute_names`` argument is an iterable collection of
919        attribute names indicating a subset of attributes to be refreshed.
920
921        """
922        try:
923            state = attributes.instance_state(instance)
924        except exc.NO_STATE:
925            raise exc.UnmappedInstanceError(instance)
926        self._validate_persistent(state)
927        if self.query(_object_mapper(instance))._get(
928                state.key, refresh_state=state,
929                only_load_props=attribute_names) is None:
930            raise sa_exc.InvalidRequestError(
931                "Could not refresh instance '%s'" %
932                mapperutil.instance_str(instance))
933
934    def expire_all(self):
935        """Expires all persistent instances within this Session."""
936
937        for state in self.identity_map.all_states():
938            _expire_state(state, None, instance_dict=self.identity_map)
939
940    def expire(self, instance, attribute_names=None):
941        """Expire the attributes on an instance.
942
943        Marks the attributes of an instance as out of date.  When an expired
944        attribute is next accessed, query will be issued to the database and
945        the attributes will be refreshed with their current database value.
946        ``expire()`` is a lazy variant of ``refresh()``.
947
948        The ``attribute_names`` argument is an iterable collection
949        of attribute names indicating a subset of attributes to be
950        expired.
951
952        """
953        try:
954            state = attributes.instance_state(instance)
955        except exc.NO_STATE:
956            raise exc.UnmappedInstanceError(instance)
957        self._validate_persistent(state)
958        if attribute_names:
959            _expire_state(state, attribute_names=attribute_names, instance_dict=self.identity_map)
960        else:
961            # pre-fetch the full cascade since the expire is going to
962            # remove associations
963            cascaded = list(_cascade_state_iterator('refresh-expire', state))
964            _expire_state(state, None, instance_dict=self.identity_map)
965            for (state, m, o) in cascaded:
966                _expire_state(state, None, instance_dict=self.identity_map)
967
968    def prune(self):
969        """Remove unreferenced instances cached in the identity map.
970
971        Note that this method is only meaningful if "weak_identity_map" is set
972        to False.  The default weak identity map is self-pruning.
973
974        Removes any object in this Session's identity map that is not
975        referenced in user code, modified, new or scheduled for deletion.
976        Returns the number of objects pruned.
977
978        """
979        return self.identity_map.prune()
980
981    def expunge(self, instance):
982        """Remove the `instance` from this ``Session``.
983
984        This will free all internal references to the instance.  Cascading
985        will be applied according to the *expunge* cascade rule.
986
987        """
988        try:
989            state = attributes.instance_state(instance)
990        except exc.NO_STATE:
991            raise exc.UnmappedInstanceError(instance)
992        if state.session_id is not self.hash_key:
993            raise sa_exc.InvalidRequestError(
994                "Instance %s is not present in this Session" %
995                mapperutil.state_str(state))
996        for s, m, o in [(state, None, None)] + list(_cascade_state_iterator('expunge', state)):
997            self._expunge_state(s)
998
999    def _expunge_state(self, state):
1000        if state in self._new:
1001            self._new.pop(state)
1002            state.detach()
1003        elif self.identity_map.contains_state(state):
1004            self.identity_map.discard(state)
1005            self._deleted.pop(state, None)
1006            state.detach()
1007
1008    def _register_newly_persistent(self, state):
1009        mapper = _state_mapper(state)
1010
1011        # prevent against last minute dereferences of the object
1012        obj = state.obj()
1013        if obj is not None:
1014
1015            instance_key = mapper._identity_key_from_state(state)
1016
1017            if state.key is None:
1018                state.key = instance_key
1019            elif state.key != instance_key:
1020                # primary key switch.
1021                # use discard() in case another state has already replaced this
1022                # one in the identity map (see test/orm/test_naturalpks.py ReversePKsTest)
1023                self.identity_map.discard(state)
1024                state.key = instance_key
1025           
1026            self.identity_map.replace(state)
1027            state.commit_all(state.dict, self.identity_map)
1028           
1029        # remove from new last, might be the last strong ref
1030        if state in self._new:
1031            if self._enable_transaction_accounting and self.transaction:
1032                self.transaction._new[state] = True
1033            self._new.pop(state)
1034
1035    def _remove_newly_deleted(self, state):
1036        if self._enable_transaction_accounting and self.transaction:
1037            self.transaction._deleted[state] = True
1038
1039        self.identity_map.discard(state)
1040        self._deleted.pop(state, None)
1041
1042    @util.deprecated("Use session.add()")
1043    def save(self, instance):
1044        """Add a transient (unsaved) instance to this ``Session``.
1045
1046        This operation cascades the `save_or_update` method to associated
1047        instances if the relation is mapped with ``cascade="save-update"``.
1048
1049
1050        """
1051        state = _state_for_unsaved_instance(instance)
1052        self._save_impl(state)
1053        self._cascade_save_or_update(state)
1054
1055    def _save_without_cascade(self, instance):
1056        """Used by scoping.py to save on init without cascade."""
1057
1058        state = _state_for_unsaved_instance(instance, create=True)
1059        self._save_impl(state)
1060
1061    @util.deprecated("Use session.add()")
1062    def update(self, instance):
1063        """Bring a detached (saved) instance into this ``Session``.
1064
1065        If there is a persistent instance with the same instance key, but
1066        different identity already associated with this ``Session``, an
1067        InvalidRequestError exception is thrown.
1068
1069        This operation cascades the `save_or_update` method to associated
1070        instances if the relation is mapped with ``cascade="save-update"``.
1071
1072        """
1073        try:
1074            state = attributes.instance_state(instance)
1075        except exc.NO_STATE:
1076            raise exc.UnmappedInstanceError(instance)
1077        self._update_impl(state)
1078        self._cascade_save_or_update(state)
1079
1080    def add(self, instance):
1081        """Place an object in the ``Session``.
1082
1083        Its state will be persisted to the database on the next flush
1084        operation.
1085
1086        Repeated calls to ``add()`` will be ignored. The opposite of ``add()``
1087        is ``expunge()``.
1088
1089        """
1090        state = _state_for_unknown_persistence_instance(instance)
1091        self._save_or_update_state(state)
1092
1093    def add_all(self, instances):
1094        """Add the given collection of instances to this ``Session``."""
1095
1096        for instance in instances:
1097            self.add(instance)
1098
1099    def _save_or_update_state(self, state):
1100        self._save_or_update_impl(state)
1101        self._cascade_save_or_update(state)
1102
1103    save_or_update = (
1104        util.deprecated("Use session.add()")(add))
1105
1106    def _cascade_save_or_update(self, state):
1107        for state, mapper in _cascade_unknown_state_iterator('save-update', state, halt_on=lambda c:c in self):
1108            self._save_or_update_impl(state)
1109
1110    def delete(self, instance):
1111        """Mark an instance as deleted.
1112
1113        The database delete operation occurs upon ``flush()``.
1114
1115        """
1116        try:
1117            state = attributes.instance_state(instance)
1118        except exc.NO_STATE:
1119            raise exc.UnmappedInstanceError(instance)
1120
1121        if state.key is None:
1122            raise sa_exc.InvalidRequestError(
1123                "Instance '%s' is not persisted" %
1124                mapperutil.state_str(state))
1125
1126        if state in self._deleted:
1127            return
1128           
1129        # ensure object is attached to allow the
1130        # cascade operation to load deferred attributes
1131        # and collections
1132        self._attach(state)
1133
1134        # grab the cascades before adding the item to the deleted list
1135        # so that autoflush does not delete the item
1136        cascade_states = list(_cascade_state_iterator('delete', state))
1137
1138        self._deleted[state] = state.obj()
1139        self.identity_map.add(state)
1140
1141        for state, m, o in cascade_states:
1142            self._delete_impl(state)
1143
1144    def merge(self, instance, dont_load=False):
1145        """Copy the state an instance onto the persistent instance with the same identifier.
1146
1147        If there is no persistent instance currently associated with the
1148        session, it will be loaded.  Return the persistent instance. If the
1149        given instance is unsaved, save a copy of and return it as a newly
1150        persistent instance. The given instance does not become associated
1151        with the session.
1152
1153        This operation cascades to associated instances if the association is
1154        mapped with ``cascade="merge"``.
1155
1156        """
1157        # TODO: this should be an IdentityDict for instances, but will
1158        # need a separate dict for PropertyLoader tuples
1159        _recursive = {}
1160        self._autoflush()
1161        autoflush = self.autoflush
1162        try:
1163            self.autoflush = False
1164            return self._merge(instance, dont_load=dont_load, _recursive=_recursive)
1165        finally:
1166            self.autoflush = autoflush
1167       
1168    def _merge(self, instance, dont_load=False, _recursive=None):
1169        mapper = _object_mapper(instance)
1170        if instance in _recursive:
1171            return _recursive[instance]
1172
1173        new_instance = False
1174        state = attributes.instance_state(instance)
1175        key = state.key
1176
1177        if key is None:
1178            if dont_load:
1179                raise sa_exc.InvalidRequestError(
1180                    "merge() with dont_load=True option does not support "
1181                    "objects transient (i.e. unpersisted) objects.  flush() "
1182                    "all changes on mapped instances before merging with "
1183                    "dont_load=True.")
1184            key = mapper._identity_key_from_state(state)
1185
1186        merged = None
1187        if key:
1188            if key in self.identity_map:
1189                merged = self.identity_map[key]
1190            elif dont_load:
1191                if state.modified:
1192                    raise sa_exc.InvalidRequestError(
1193                        "merge() with dont_load=True option does not support "
1194                        "objects marked as 'dirty'.  flush() all changes on "
1195                        "mapped instances before merging with dont_load=True.")
1196                merged = mapper.class_manager.new_instance()
1197                merged_state = attributes.instance_state(merged)
1198                merged_state.key = key
1199                self._update_impl(merged_state)
1200                new_instance = True
1201            else:
1202                merged = self.query(mapper.class_).get(key[1])
1203
1204        if merged is None:
1205            merged = mapper.class_manager.new_instance()
1206            merged_state = attributes.instance_state(merged)
1207            new_instance = True
1208            self.add(merged)
1209
1210        _recursive[instance] = merged
1211
1212        for prop in mapper.iterate_properties:
1213            prop.merge(self, instance, merged, dont_load, _recursive)
1214
1215        if dont_load:
1216            attributes.instance_state(merged).commit_all(attributes.instance_dict(merged), self.identity_map)  # remove any history
1217
1218        if new_instance:
1219            merged_state._run_on_load(merged)
1220        return merged
1221
1222    @classmethod
1223    def identity_key(cls, *args, **kwargs):
1224        return mapperutil.identity_key(*args, **kwargs)
1225
1226    @classmethod
1227    def object_session(cls, instance):
1228        """Return the ``Session`` to which an object belongs."""
1229
1230        return object_session(instance)
1231
1232    def _validate_persistent(self, state):
1233        if not self.identity_map.contains_state(state):
1234            raise sa_exc.InvalidRequestError(
1235                "Instance '%s' is not persistent within this Session" %
1236                mapperutil.state_str(state))
1237
1238    def _save_impl(self, state):
1239        if state.key is not None:
1240            raise sa_exc.InvalidRequestError(
1241                "Object '%s' already has an identity - it can't be registered "
1242                "as pending" % mapperutil.state_str(state))
1243               
1244        self._attach(state)
1245        if state not in self._new:
1246            self._new[state] = state.obj()
1247            state.insert_order = len(self._new)
1248
1249    def _update_impl(self, state):
1250        if (self.identity_map.contains_state(state) and
1251            state not in self._deleted):
1252            return
1253           
1254        if state.key is None:
1255            raise sa_exc.InvalidRequestError(
1256                "Instance '%s' is not persisted" %
1257                mapperutil.state_str(state))
1258
1259        self._attach(state)
1260        self._deleted.pop(state, None)
1261        self.identity_map.add(state)
1262
1263    def _save_or_update_impl(self, state):
1264        if state.key is None:
1265            self._save_impl(state)
1266        else:
1267            self._update_impl(state)
1268
1269    def _delete_impl(self, state):
1270        if state in self._deleted:
1271            return
1272
1273        if state.key is None:
1274            return
1275                   
1276        self._attach(state)
1277        self._deleted[state] = state.obj()
1278        self.identity_map.add(state)
1279   
1280    def _attach(self, state):
1281        if state.key and \
1282            state.key in self.identity_map and \
1283            not self.identity_map.contains_state(state):
1284            raise sa_exc.InvalidRequestError(
1285                "Can't attach instance %s; another instance with key %s is already present in this session." %
1286                    (mapperutil.state_str(state), state.key)
1287                )
1288               
1289        if state.session_id and state.session_id is not self.hash_key:
1290            raise sa_exc.InvalidRequestError(
1291                "Object '%s' is already attached to session '%s' "
1292                "(this is '%s')" % (mapperutil.state_str(state),
1293                                    state.session_id, self.hash_key))
1294                                   
1295        if state.session_id != self.hash_key:
1296            state.session_id = self.hash_key
1297            for ext in self.extensions:
1298                ext.after_attach(self, state.obj())
1299
1300    def __contains__(self, instance):
1301        """Return True if the instance is associated with this session.
1302
1303        The instance may be pending or persistent within the Session for a
1304        result of True.
1305
1306        """
1307        try:
1308            state = attributes.instance_state(instance)
1309        except exc.NO_STATE:
1310            raise exc.UnmappedInstanceError(instance)
1311        return self._contains_state(state)
1312
1313    def __iter__(self):
1314        """Iterate over all pending or persistent instances within this Session."""
1315
1316        return iter(list(self._new.values()) + self.identity_map.values())
1317
1318    def _contains_state(self, state):
1319        return state in self._new or self.identity_map.contains_state(state)
1320
1321    def flush(self, objects=None):
1322        """Flush all the object changes to the database.
1323
1324        Writes out all pending object creations, deletions and modifications
1325        to the database as INSERTs, DELETEs, UPDATEs, etc.  Operations are
1326        automatically ordered by the Session's unit of work dependency
1327        solver..
1328
1329        Database operations will be issued in the current transactional
1330        context and do not affect the state of the transaction.  You may
1331        flush() as often as you like within a transaction to move changes from
1332        Python to the database's transaction buffer.
1333
1334        For ``autocommit`` Sessions with no active manual transaction, flush()
1335        will create a transaction on the fly that surrounds the entire set of
1336        operations int the flush.
1337
1338        objects
1339          Optional; a list or tuple collection.  Restricts the flush operation
1340          to only these objects, rather than all pending changes.
1341          Deprecated - this flag prevents the session from properly maintaining
1342          accounting among inter-object relations and can cause invalid results.
1343
1344        """
1345
1346        if objects:
1347            util.warn_deprecated(
1348                "The 'objects' argument to session.flush() is deprecated; "
1349                "Please do not add objects to the session which should not yet be persisted.")
1350       
1351        if self._flushing:
1352            raise sa_exc.InvalidRequestError("Session is already flushing")
1353           
1354        try:
1355            self._flushing = True
1356            self._flush(objects)
1357        finally:
1358            self._flushing = False
1359           
1360    def _flush(self, objects=None):
1361        if (not self.identity_map.check_modified() and
1362            not self._deleted and not self._new):
1363            return
1364
1365        dirty = self._dirty_states
1366        if not dirty and not self._deleted and not self._new:
1367            self.identity_map._modified.clear()
1368            return
1369
1370        flush_context = UOWTransaction(self)
1371
1372        if self.extensions:
1373            for ext in self.extensions:
1374                ext.before_flush(self, flush_context, objects)
1375            dirty = self._dirty_states
1376           
1377        deleted = set(self._deleted)
1378        new = set(self._new)
1379
1380        dirty = set(dirty).difference(deleted)
1381
1382        # create the set of all objects we want to operate upon
1383        if objects:
1384            # specific list passed in
1385            objset = set()
1386            for o in objects:
1387                try:
1388                    state = attributes.instance_state(o)
1389                except exc.NO_STATE:
1390                    raise exc.UnmappedInstanceError(o)
1391                objset.add(state)
1392        else:
1393            objset = None
1394
1395        # store objects whose fate has been decided
1396        processed = set()
1397
1398        # put all saves/updates into the flush context.  detect top-level
1399        # orphans and throw them into deleted.
1400        if objset:
1401            proc = new.union(dirty).intersection(objset).difference(deleted)
1402        else:
1403            proc = new.union(dirty).difference(deleted)
1404           
1405        for state in proc:
1406            is_orphan = _state_mapper(state)._is_orphan(state)
1407            if is_orphan and not _state_has_identity(state):
1408                path = ", nor ".join(
1409                    ["any parent '%s' instance "
1410                     "via that classes' '%s' attribute" %
1411                     (cls.__name__, key)
1412                     for (key, cls) in chain(*(m.delete_orphans for m in _state_mapper(state).iterate_to_root()))])
1413                raise exc.FlushError(
1414                    "Instance %s is an unsaved, pending instance and is an "
1415                    "orphan (is not attached to %s)" % (
1416                    mapperutil.state_str(state), path))
1417            flush_context.register_object(state, isdelete=is_orphan)
1418            processed.add(state)
1419
1420        # put all remaining deletes into the flush context.
1421        if objset:
1422            proc = deleted.intersection(objset).difference(processed)
1423        else:
1424            proc = deleted.difference(processed)
1425        for state in proc:
1426            flush_context.register_object(state, isdelete=True)
1427
1428        if len(flush_context.tasks) == 0:
1429            return
1430
1431        flush_context.transaction = transaction = self.begin(
1432            subtransactions=True)
1433        try:
1434            flush_context.execute()
1435
1436            for ext in self.extensions:
1437                ext.after_flush(self, flush_context)
1438            transaction.commit()
1439        except:
1440            transaction.rollback()
1441            raise
1442       
1443        flush_context.finalize_flush_changes()
1444
1445        # useful assertions:
1446        #if not objects:
1447        #    assert not self.identity_map._modified
1448        #else:
1449        #    assert self.identity_map._modified == self.identity_map._modified.difference(objects)
1450        #self.identity_map._modified.clear()
1451       
1452        for ext in self.extensions:
1453            ext.after_flush_postexec(self, flush_context)
1454
1455    def is_modified(self, instance, include_collections=True, passive=False):
1456        """Return True if instance has modified attributes.
1457
1458        This method retrieves a history instance for each instrumented
1459        attribute on the instance and performs a comparison of the current
1460        value to its previously committed value.  Note that instances present
1461        in the 'dirty' collection may result in a value of ``False`` when
1462        tested with this method.
1463
1464        `include_collections` indicates if multivalued collections should be
1465        included in the operation.  Setting this to False is a way to detect
1466        only local-column based properties (i.e. scalar columns or many-to-one
1467        foreign keys) that would result in an UPDATE for this instance upon
1468        flush.
1469
1470        The `passive` flag indicates if unloaded attributes and collections
1471        should not be loaded in the course of performing this test.
1472
1473        """
1474        try:
1475            state = attributes.instance_state(instance)
1476        except exc.NO_STATE:
1477            raise exc.UnmappedInstanceError(instance)
1478        dict_ = state.dict
1479        for attr in state.manager.attributes:
1480            if \
1481                (
1482                    not include_collections and
1483                    hasattr(attr.impl, 'get_collection')
1484                ) or not hasattr(attr.impl, 'get_history'):
1485                continue
1486               
1487            (added, unchanged, deleted) = \
1488                    attr.impl.get_history(state, dict_, passive=passive)
1489                                           
1490            if added or deleted:
1491                return True
1492        return False
1493
1494    @property
1495    def is_active(self):
1496        """True if this Session has an active transaction."""
1497
1498        return self.transaction and self.transaction.is_active
1499
1500    @property
1501    def _dirty_states(self):
1502        """The set of all persistent states considered dirty.
1503
1504        This method returns all states that were modified including
1505        those that were possibly deleted.
1506
1507        """
1508        return self.identity_map._dirty_states()
1509
1510    @property
1511    def dirty(self):
1512        """The set of all persistent instances considered dirty.
1513
1514        Instances are considered dirty when they were modified but not
1515        deleted.
1516
1517        Note that this 'dirty' calculation is 'optimistic'; most
1518        attribute-setting or collection modification operations will
1519        mark an instance as 'dirty' and place it in this set, even if
1520        there is no net change to the attribute's value.  At flush
1521        time, the value of each attribute is compared to its
1522        previously saved value, and if there's no net change, no SQL
1523        operation will occur (this is a more expensive operation so
1524        it's only done at flush time).
1525
1526        To check if an instance has actionable net changes to its
1527        attributes, use the is_modified() method.
1528
1529        """
1530        return util.IdentitySet(
1531            [state.obj()
1532             for state in self._dirty_states
1533             if state not in self._deleted])
1534
1535    @property
1536    def deleted(self):
1537        "The set of all instances marked as 'deleted' within this ``Session``"
1538
1539        return util.IdentitySet(self._deleted.values())
1540
1541    @property
1542    def new(self):
1543        "The set of all instances marked as 'new' within this ``Session``."
1544
1545        return util.IdentitySet(self._new.values())
1546
1547_expire_state = state.InstanceState.expire_attributes
1548   
1549UOWEventHandler = unitofwork.UOWEventHandler
1550
1551_sessions = weakref.WeakValueDictionary()
1552
1553def _cascade_state_iterator(cascade, state, **kwargs):
1554    mapper = _state_mapper(state)
1555    # yield the state, object, mapper.  yielding the object
1556    # allows the iterator's results to be held in a list without
1557    # states being garbage collected
1558    for (o, m) in mapper.cascade_iterator(cascade, state, **kwargs):
1559        yield attributes.instance_state(o), o, m
1560
1561def _cascade_unknown_state_iterator(cascade, state, **kwargs):
1562    mapper = _state_mapper(state)
1563    for (o, m) in mapper.cascade_iterator(cascade, state, **kwargs):
1564        yield _state_for_unknown_persistence_instance(o), m
1565
1566def _state_for_unsaved_instance(instance, create=False):
1567    try:
1568        state = attributes.instance_state(instance)
1569    except AttributeError:
1570        raise exc.UnmappedInstanceError(instance)
1571    if state:
1572        if state.key is not None:
1573            raise sa_exc.InvalidRequestError(
1574                "Instance '%s' is already persistent" %
1575                mapperutil.state_str(state))
1576    elif create:
1577        manager = attributes.manager_of_class(instance.__class__)
1578        if manager is None:
1579            raise exc.UnmappedInstanceError(instance)
1580        state = manager.setup_instance(instance)
1581    else:
1582        raise exc.UnmappedInstanceError(instance)
1583
1584    return state
1585
1586def _state_for_unknown_persistence_instance(instance):
1587    try:
1588        state = attributes.instance_state(instance)
1589    except exc.NO_STATE:
1590        raise exc.UnmappedInstanceError(instance)
1591
1592    return state
1593
1594def object_session(instance):
1595    """Return the ``Session`` to which instance belongs, or None."""
1596
1597    return _state_session(attributes.instance_state(instance))
1598
1599def _state_session(state):
1600    if state.session_id:
1601        try:
1602            return _sessions[state.session_id]
1603        except KeyError:
1604            pass
1605    return None
1606
1607# Lazy initialization to avoid circular imports
1608unitofwork.object_session = object_session
1609unitofwork._state_session = _state_session
1610from sqlalchemy.orm import mapper
1611mapper._expire_state = _expire_state
1612mapper._state_session = _state_session
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。