scoping.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. # orm/scoping.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. from .. import exc as sa_exc
  8. from ..util import ScopedRegistry, ThreadLocalRegistry, warn
  9. from . import class_mapper, exc as orm_exc
  10. from .session import Session
  11. __all__ = ['scoped_session']
  12. class scoped_session(object):
  13. """Provides scoped management of :class:`.Session` objects.
  14. See :ref:`unitofwork_contextual` for a tutorial.
  15. """
  16. session_factory = None
  17. """The `session_factory` provided to `__init__` is stored in this
  18. attribute and may be accessed at a later time. This can be useful when
  19. a new non-scoped :class:`.Session` or :class:`.Connection` to the
  20. database is needed."""
  21. def __init__(self, session_factory, scopefunc=None):
  22. """Construct a new :class:`.scoped_session`.
  23. :param session_factory: a factory to create new :class:`.Session`
  24. instances. This is usually, but not necessarily, an instance
  25. of :class:`.sessionmaker`.
  26. :param scopefunc: optional function which defines
  27. the current scope. If not passed, the :class:`.scoped_session`
  28. object assumes "thread-local" scope, and will use
  29. a Python ``threading.local()`` in order to maintain the current
  30. :class:`.Session`. If passed, the function should return
  31. a hashable token; this token will be used as the key in a
  32. dictionary in order to store and retrieve the current
  33. :class:`.Session`.
  34. """
  35. self.session_factory = session_factory
  36. if scopefunc:
  37. self.registry = ScopedRegistry(session_factory, scopefunc)
  38. else:
  39. self.registry = ThreadLocalRegistry(session_factory)
  40. def __call__(self, **kw):
  41. r"""Return the current :class:`.Session`, creating it
  42. using the :attr:`.scoped_session.session_factory` if not present.
  43. :param \**kw: Keyword arguments will be passed to the
  44. :attr:`.scoped_session.session_factory` callable, if an existing
  45. :class:`.Session` is not present. If the :class:`.Session` is present
  46. and keyword arguments have been passed,
  47. :exc:`~sqlalchemy.exc.InvalidRequestError` is raised.
  48. """
  49. if kw:
  50. scope = kw.pop('scope', False)
  51. if scope is not None:
  52. if self.registry.has():
  53. raise sa_exc.InvalidRequestError(
  54. "Scoped session is already present; "
  55. "no new arguments may be specified.")
  56. else:
  57. sess = self.session_factory(**kw)
  58. self.registry.set(sess)
  59. return sess
  60. else:
  61. return self.session_factory(**kw)
  62. else:
  63. return self.registry()
  64. def remove(self):
  65. """Dispose of the current :class:`.Session`, if present.
  66. This will first call :meth:`.Session.close` method
  67. on the current :class:`.Session`, which releases any existing
  68. transactional/connection resources still being held; transactions
  69. specifically are rolled back. The :class:`.Session` is then
  70. discarded. Upon next usage within the same scope,
  71. the :class:`.scoped_session` will produce a new
  72. :class:`.Session` object.
  73. """
  74. if self.registry.has():
  75. self.registry().close()
  76. self.registry.clear()
  77. def configure(self, **kwargs):
  78. """reconfigure the :class:`.sessionmaker` used by this
  79. :class:`.scoped_session`.
  80. See :meth:`.sessionmaker.configure`.
  81. """
  82. if self.registry.has():
  83. warn('At least one scoped session is already present. '
  84. ' configure() can not affect sessions that have '
  85. 'already been created.')
  86. self.session_factory.configure(**kwargs)
  87. def query_property(self, query_cls=None):
  88. """return a class property which produces a :class:`.Query` object
  89. against the class and the current :class:`.Session` when called.
  90. e.g.::
  91. Session = scoped_session(sessionmaker())
  92. class MyClass(object):
  93. query = Session.query_property()
  94. # after mappers are defined
  95. result = MyClass.query.filter(MyClass.name=='foo').all()
  96. Produces instances of the session's configured query class by
  97. default. To override and use a custom implementation, provide
  98. a ``query_cls`` callable. The callable will be invoked with
  99. the class's mapper as a positional argument and a session
  100. keyword argument.
  101. There is no limit to the number of query properties placed on
  102. a class.
  103. """
  104. class query(object):
  105. def __get__(s, instance, owner):
  106. try:
  107. mapper = class_mapper(owner)
  108. if mapper:
  109. if query_cls:
  110. # custom query class
  111. return query_cls(mapper, session=self.registry())
  112. else:
  113. # session's configured query class
  114. return self.registry().query(mapper)
  115. except orm_exc.UnmappedClassError:
  116. return None
  117. return query()
  118. ScopedSession = scoped_session
  119. """Old name for backwards compatibility."""
  120. def instrument(name):
  121. def do(self, *args, **kwargs):
  122. return getattr(self.registry(), name)(*args, **kwargs)
  123. return do
  124. for meth in Session.public_methods:
  125. setattr(scoped_session, meth, instrument(meth))
  126. def makeprop(name):
  127. def set(self, attr):
  128. setattr(self.registry(), name, attr)
  129. def get(self):
  130. return getattr(self.registry(), name)
  131. return property(get, set)
  132. for prop in ('bind', 'dirty', 'deleted', 'new', 'identity_map',
  133. 'is_active', 'autoflush', 'no_autoflush', 'info'):
  134. setattr(scoped_session, prop, makeprop(prop))
  135. def clslevel(name):
  136. def do(cls, *args, **kwargs):
  137. return getattr(Session, name)(*args, **kwargs)
  138. return classmethod(do)
  139. for prop in ('close_all', 'object_session', 'identity_key'):
  140. setattr(scoped_session, prop, clslevel(prop))