Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/scoping.py: 53%

55 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# orm/scoping.py 

2# Copyright (C) 2005-2022 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: https://www.opensource.org/licenses/mit-license.php 

7 

8from . import class_mapper 

9from . import exc as orm_exc 

10from .session import Session 

11from .. import exc as sa_exc 

12from ..util import create_proxy_methods 

13from ..util import ScopedRegistry 

14from ..util import ThreadLocalRegistry 

15from ..util import warn 

16from ..util import warn_deprecated 

17 

18__all__ = ["scoped_session", "ScopedSessionMixin"] 

19 

20 

21class ScopedSessionMixin(object): 

22 @property 

23 def _proxied(self): 

24 return self.registry() 

25 

26 def __call__(self, **kw): 

27 r"""Return the current :class:`.Session`, creating it 

28 using the :attr:`.scoped_session.session_factory` if not present. 

29 

30 :param \**kw: Keyword arguments will be passed to the 

31 :attr:`.scoped_session.session_factory` callable, if an existing 

32 :class:`.Session` is not present. If the :class:`.Session` is present 

33 and keyword arguments have been passed, 

34 :exc:`~sqlalchemy.exc.InvalidRequestError` is raised. 

35 

36 """ 

37 if kw: 

38 if self.registry.has(): 

39 raise sa_exc.InvalidRequestError( 

40 "Scoped session is already present; " 

41 "no new arguments may be specified." 

42 ) 

43 else: 

44 sess = self.session_factory(**kw) 

45 self.registry.set(sess) 

46 else: 

47 sess = self.registry() 

48 if not self._support_async and sess._is_asyncio: 

49 warn_deprecated( 

50 "Using `scoped_session` with asyncio is deprecated and " 

51 "will raise an error in a future version. " 

52 "Please use `async_scoped_session` instead.", 

53 "1.4.23", 

54 ) 

55 return sess 

56 

57 def configure(self, **kwargs): 

58 """reconfigure the :class:`.sessionmaker` used by this 

59 :class:`.scoped_session`. 

60 

61 See :meth:`.sessionmaker.configure`. 

62 

63 """ 

64 

65 if self.registry.has(): 

66 warn( 

67 "At least one scoped session is already present. " 

68 " configure() can not affect sessions that have " 

69 "already been created." 

70 ) 

71 

72 self.session_factory.configure(**kwargs) 

73 

74 

75@create_proxy_methods( 

76 Session, 

77 ":class:`_orm.Session`", 

78 ":class:`_orm.scoping.scoped_session`", 

79 classmethods=["close_all", "object_session", "identity_key"], 

80 methods=[ 

81 "__contains__", 

82 "__iter__", 

83 "add", 

84 "add_all", 

85 "begin", 

86 "begin_nested", 

87 "close", 

88 "commit", 

89 "connection", 

90 "delete", 

91 "execute", 

92 "expire", 

93 "expire_all", 

94 "expunge", 

95 "expunge_all", 

96 "flush", 

97 "get", 

98 "get_bind", 

99 "is_modified", 

100 "bulk_save_objects", 

101 "bulk_insert_mappings", 

102 "bulk_update_mappings", 

103 "merge", 

104 "query", 

105 "refresh", 

106 "rollback", 

107 "scalar", 

108 "scalars", 

109 ], 

110 attributes=[ 

111 "bind", 

112 "dirty", 

113 "deleted", 

114 "new", 

115 "identity_map", 

116 "is_active", 

117 "autoflush", 

118 "no_autoflush", 

119 "info", 

120 "autocommit", 

121 ], 

122) 

123class scoped_session(ScopedSessionMixin): 

124 """Provides scoped management of :class:`.Session` objects. 

125 

126 See :ref:`unitofwork_contextual` for a tutorial. 

127 

128 .. note:: 

129 

130 When using :ref:`asyncio_toplevel`, the async-compatible 

131 :class:`_asyncio.async_scoped_session` class should be 

132 used in place of :class:`.scoped_session`. 

133 

134 """ 

135 

136 _support_async = False 

137 

138 session_factory = None 

139 """The `session_factory` provided to `__init__` is stored in this 

140 attribute and may be accessed at a later time. This can be useful when 

141 a new non-scoped :class:`.Session` or :class:`_engine.Connection` to the 

142 database is needed.""" 

143 

144 def __init__(self, session_factory, scopefunc=None): 

145 """Construct a new :class:`.scoped_session`. 

146 

147 :param session_factory: a factory to create new :class:`.Session` 

148 instances. This is usually, but not necessarily, an instance 

149 of :class:`.sessionmaker`. 

150 :param scopefunc: optional function which defines 

151 the current scope. If not passed, the :class:`.scoped_session` 

152 object assumes "thread-local" scope, and will use 

153 a Python ``threading.local()`` in order to maintain the current 

154 :class:`.Session`. If passed, the function should return 

155 a hashable token; this token will be used as the key in a 

156 dictionary in order to store and retrieve the current 

157 :class:`.Session`. 

158 

159 """ 

160 self.session_factory = session_factory 

161 

162 if scopefunc: 

163 self.registry = ScopedRegistry(session_factory, scopefunc) 

164 else: 

165 self.registry = ThreadLocalRegistry(session_factory) 

166 

167 def remove(self): 

168 """Dispose of the current :class:`.Session`, if present. 

169 

170 This will first call :meth:`.Session.close` method 

171 on the current :class:`.Session`, which releases any existing 

172 transactional/connection resources still being held; transactions 

173 specifically are rolled back. The :class:`.Session` is then 

174 discarded. Upon next usage within the same scope, 

175 the :class:`.scoped_session` will produce a new 

176 :class:`.Session` object. 

177 

178 """ 

179 

180 if self.registry.has(): 

181 self.registry().close() 

182 self.registry.clear() 

183 

184 def query_property(self, query_cls=None): 

185 """return a class property which produces a :class:`_query.Query` 

186 object 

187 against the class and the current :class:`.Session` when called. 

188 

189 e.g.:: 

190 

191 Session = scoped_session(sessionmaker()) 

192 

193 class MyClass(object): 

194 query = Session.query_property() 

195 

196 # after mappers are defined 

197 result = MyClass.query.filter(MyClass.name=='foo').all() 

198 

199 Produces instances of the session's configured query class by 

200 default. To override and use a custom implementation, provide 

201 a ``query_cls`` callable. The callable will be invoked with 

202 the class's mapper as a positional argument and a session 

203 keyword argument. 

204 

205 There is no limit to the number of query properties placed on 

206 a class. 

207 

208 """ 

209 

210 class query(object): 

211 def __get__(s, instance, owner): 

212 try: 

213 mapper = class_mapper(owner) 

214 if mapper: 

215 if query_cls: 

216 # custom query class 

217 return query_cls(mapper, session=self.registry()) 

218 else: 

219 # session's configured query class 

220 return self.registry().query(mapper) 

221 except orm_exc.UnmappedClassError: 

222 return None 

223 

224 return query() 

225 

226 

227ScopedSession = scoped_session 

228"""Old name for backwards compatibility."""