Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/db/transaction.py: 62%

127 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from contextlib import ContextDecorator, contextmanager 

2 

3from django.db import ( 

4 DEFAULT_DB_ALIAS, 

5 DatabaseError, 

6 Error, 

7 ProgrammingError, 

8 connections, 

9) 

10 

11 

12class TransactionManagementError(ProgrammingError): 

13 """Transaction management is used improperly.""" 

14 

15 pass 

16 

17 

18def get_connection(using=None): 

19 """ 

20 Get a database connection by name, or the default database connection 

21 if no name is provided. This is a private API. 

22 """ 

23 if using is None: 

24 using = DEFAULT_DB_ALIAS 

25 return connections[using] 

26 

27 

28def get_autocommit(using=None): 

29 """Get the autocommit status of the connection.""" 

30 return get_connection(using).get_autocommit() 

31 

32 

33def set_autocommit(autocommit, using=None): 

34 """Set the autocommit status of the connection.""" 

35 return get_connection(using).set_autocommit(autocommit) 

36 

37 

38def commit(using=None): 

39 """Commit a transaction.""" 

40 get_connection(using).commit() 

41 

42 

43def rollback(using=None): 

44 """Roll back a transaction.""" 

45 get_connection(using).rollback() 

46 

47 

48def savepoint(using=None): 

49 """ 

50 Create a savepoint (if supported and required by the backend) inside the 

51 current transaction. Return an identifier for the savepoint that will be 

52 used for the subsequent rollback or commit. 

53 """ 

54 return get_connection(using).savepoint() 

55 

56 

57def savepoint_rollback(sid, using=None): 

58 """ 

59 Roll back the most recent savepoint (if one exists). Do nothing if 

60 savepoints are not supported. 

61 """ 

62 get_connection(using).savepoint_rollback(sid) 

63 

64 

65def savepoint_commit(sid, using=None): 

66 """ 

67 Commit the most recent savepoint (if one exists). Do nothing if 

68 savepoints are not supported. 

69 """ 

70 get_connection(using).savepoint_commit(sid) 

71 

72 

73def clean_savepoints(using=None): 

74 """ 

75 Reset the counter used to generate unique savepoint ids in this thread. 

76 """ 

77 get_connection(using).clean_savepoints() 

78 

79 

80def get_rollback(using=None): 

81 """Get the "needs rollback" flag -- for *advanced use* only.""" 

82 return get_connection(using).get_rollback() 

83 

84 

85def set_rollback(rollback, using=None): 

86 """ 

87 Set or unset the "needs rollback" flag -- for *advanced use* only. 

88 

89 When `rollback` is `True`, trigger a rollback when exiting the innermost 

90 enclosing atomic block that has `savepoint=True` (that's the default). Use 

91 this to force a rollback without raising an exception. 

92 

93 When `rollback` is `False`, prevent such a rollback. Use this only after 

94 rolling back to a known-good state! Otherwise, you break the atomic block 

95 and data corruption may occur. 

96 """ 

97 return get_connection(using).set_rollback(rollback) 

98 

99 

100@contextmanager 

101def mark_for_rollback_on_error(using=None): 

102 """ 

103 Internal low-level utility to mark a transaction as "needs rollback" when 

104 an exception is raised while not enforcing the enclosed block to be in a 

105 transaction. This is needed by Model.save() and friends to avoid starting a 

106 transaction when in autocommit mode and a single query is executed. 

107 

108 It's equivalent to: 

109 

110 connection = get_connection(using) 

111 if connection.get_autocommit(): 

112 yield 

113 else: 

114 with transaction.atomic(using=using, savepoint=False): 

115 yield 

116 

117 but it uses low-level utilities to avoid performance overhead. 

118 """ 

119 try: 

120 yield 

121 except Exception: 

122 connection = get_connection(using) 

123 if connection.in_atomic_block: 

124 connection.needs_rollback = True 

125 raise 

126 

127 

128def on_commit(func, using=None): 

129 """ 

130 Register `func` to be called when the current transaction is committed. 

131 If the current transaction is rolled back, `func` will not be called. 

132 """ 

133 get_connection(using).on_commit(func) 

134 

135 

136################################# 

137# Decorators / context managers # 

138################################# 

139 

140 

141class Atomic(ContextDecorator): 

142 """ 

143 Guarantee the atomic execution of a given block. 

144 

145 An instance can be used either as a decorator or as a context manager. 

146 

147 When it's used as a decorator, __call__ wraps the execution of the 

148 decorated function in the instance itself, used as a context manager. 

149 

150 When it's used as a context manager, __enter__ creates a transaction or a 

151 savepoint, depending on whether a transaction is already in progress, and 

152 __exit__ commits the transaction or releases the savepoint on normal exit, 

153 and rolls back the transaction or to the savepoint on exceptions. 

154 

155 It's possible to disable the creation of savepoints if the goal is to 

156 ensure that some code runs within a transaction without creating overhead. 

157 

158 A stack of savepoints identifiers is maintained as an attribute of the 

159 connection. None denotes the absence of a savepoint. 

160 

161 This allows reentrancy even if the same AtomicWrapper is reused. For 

162 example, it's possible to define `oa = atomic('other')` and use `@oa` or 

163 `with oa:` multiple times. 

164 

165 Since database connections are thread-local, this is thread-safe. 

166 

167 An atomic block can be tagged as durable. In this case, raise a 

168 RuntimeError if it's nested within another atomic block. This guarantees 

169 that database changes in a durable block are committed to the database when 

170 the block exists without error. 

171 

172 This is a private API. 

173 """ 

174 

175 # This private flag is provided only to disable the durability checks in 

176 # TestCase. 

177 _ensure_durability = True 

178 

179 def __init__(self, using, savepoint, durable): 

180 self.using = using 

181 self.savepoint = savepoint 

182 self.durable = durable 

183 

184 def __enter__(self): 

185 connection = get_connection(self.using) 

186 

187 if self.durable and self._ensure_durability and connection.in_atomic_block: 187 ↛ 188line 187 didn't jump to line 188, because the condition on line 187 was never true

188 raise RuntimeError( 

189 "A durable atomic block cannot be nested within another " 

190 "atomic block." 

191 ) 

192 if not connection.in_atomic_block: 

193 # Reset state when entering an outermost atomic block. 

194 connection.commit_on_exit = True 

195 connection.needs_rollback = False 

196 if not connection.get_autocommit(): 196 ↛ 200line 196 didn't jump to line 200, because the condition on line 196 was never true

197 # Pretend we're already in an atomic block to bypass the code 

198 # that disables autocommit to enter a transaction, and make a 

199 # note to deal with this case in __exit__. 

200 connection.in_atomic_block = True 

201 connection.commit_on_exit = False 

202 

203 if connection.in_atomic_block: 

204 # We're already in a transaction; create a savepoint, unless we 

205 # were told not to or we're already waiting for a rollback. The 

206 # second condition avoids creating useless savepoints and prevents 

207 # overwriting needs_rollback until the rollback is performed. 

208 if self.savepoint and not connection.needs_rollback: 

209 sid = connection.savepoint() 

210 connection.savepoint_ids.append(sid) 

211 else: 

212 connection.savepoint_ids.append(None) 

213 else: 

214 connection.set_autocommit( 

215 False, force_begin_transaction_with_broken_autocommit=True 

216 ) 

217 connection.in_atomic_block = True 

218 

219 def __exit__(self, exc_type, exc_value, traceback): 

220 connection = get_connection(self.using) 

221 

222 if connection.savepoint_ids: 

223 sid = connection.savepoint_ids.pop() 

224 else: 

225 # Prematurely unset this flag to allow using commit or rollback. 

226 connection.in_atomic_block = False 

227 

228 try: 

229 if connection.closed_in_transaction: 229 ↛ 232line 229 didn't jump to line 232, because the condition on line 229 was never true

230 # The database will perform a rollback by itself. 

231 # Wait until we exit the outermost block. 

232 pass 

233 

234 elif exc_type is None and not connection.needs_rollback: 

235 if connection.in_atomic_block: 

236 # Release savepoint if there is one 

237 if sid is not None: 

238 try: 

239 connection.savepoint_commit(sid) 

240 except DatabaseError: 

241 try: 

242 connection.savepoint_rollback(sid) 

243 # The savepoint won't be reused. Release it to 

244 # minimize overhead for the database server. 

245 connection.savepoint_commit(sid) 

246 except Error: 

247 # If rolling back to a savepoint fails, mark for 

248 # rollback at a higher level and avoid shadowing 

249 # the original exception. 

250 connection.needs_rollback = True 

251 raise 

252 else: 

253 # Commit transaction 

254 try: 

255 connection.commit() 

256 except DatabaseError: 

257 try: 

258 connection.rollback() 

259 except Error: 

260 # An error during rollback means that something 

261 # went wrong with the connection. Drop it. 

262 connection.close() 

263 raise 

264 else: 

265 # This flag will be set to True again if there isn't a savepoint 

266 # allowing to perform the rollback at this level. 

267 connection.needs_rollback = False 

268 if connection.in_atomic_block: 

269 # Roll back to savepoint if there is one, mark for rollback 

270 # otherwise. 

271 if sid is None: 271 ↛ 272line 271 didn't jump to line 272, because the condition on line 271 was never true

272 connection.needs_rollback = True 

273 else: 

274 try: 

275 connection.savepoint_rollback(sid) 

276 # The savepoint won't be reused. Release it to 

277 # minimize overhead for the database server. 

278 connection.savepoint_commit(sid) 

279 except Error: 

280 # If rolling back to a savepoint fails, mark for 

281 # rollback at a higher level and avoid shadowing 

282 # the original exception. 

283 connection.needs_rollback = True 

284 else: 

285 # Roll back transaction 

286 try: 

287 connection.rollback() 

288 except Error: 

289 # An error during rollback means that something 

290 # went wrong with the connection. Drop it. 

291 connection.close() 

292 

293 finally: 

294 # Outermost block exit when autocommit was enabled. 

295 if not connection.in_atomic_block: 

296 if connection.closed_in_transaction: 296 ↛ 297line 296 didn't jump to line 297, because the condition on line 296 was never true

297 connection.connection = None 

298 else: 

299 connection.set_autocommit(True) 

300 # Outermost block exit when autocommit was disabled. 

301 elif not connection.savepoint_ids and not connection.commit_on_exit: 301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true

302 if connection.closed_in_transaction: 

303 connection.connection = None 

304 else: 

305 connection.in_atomic_block = False 

306 

307 

308def atomic(using=None, savepoint=True, durable=False): 

309 # Bare decorator: @atomic -- although the first argument is called 

310 # `using`, it's actually the function being decorated. 

311 if callable(using): 

312 return Atomic(DEFAULT_DB_ALIAS, savepoint, durable)(using) 

313 # Decorator: @atomic(...) or context manager: with atomic(...): ... 

314 else: 

315 return Atomic(using, savepoint, durable) 

316 

317 

318def _non_atomic_requests(view, using): 

319 try: 

320 view._non_atomic_requests.add(using) 

321 except AttributeError: 

322 view._non_atomic_requests = {using} 

323 return view 

324 

325 

326def non_atomic_requests(using=None): 

327 if callable(using): 

328 return _non_atomic_requests(using, DEFAULT_DB_ALIAS) 

329 else: 

330 if using is None: 

331 using = DEFAULT_DB_ALIAS 

332 return lambda view: _non_atomic_requests(view, using)