Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# orm/deprecated_interfaces.py 

2# Copyright (C) 2005-2020 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 

8from .interfaces import EXT_CONTINUE 

9from .. import event 

10from .. import util 

11 

12 

13@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces") 

14class MapperExtension(object): 

15 """Base implementation for :class:`_orm.Mapper` event hooks. 

16 

17 .. deprecated:: 0.7 

18 

19 :class:`.MapperExtension` is deprecated and will be removed in a future 

20 release. Please refer to :func:`.event.listen` in conjunction with 

21 the :class:`.MapperEvents` listener interface. 

22 

23 New extension classes subclass :class:`.MapperExtension` and are specified 

24 using the ``extension`` mapper() argument, which is a single 

25 :class:`.MapperExtension` or a list of such:: 

26 

27 from sqlalchemy.orm.interfaces import MapperExtension 

28 

29 class MyExtension(MapperExtension): 

30 def before_insert(self, mapper, connection, instance): 

31 print "instance %s before insert !" % instance 

32 

33 m = mapper(User, users_table, extension=MyExtension()) 

34 

35 A single mapper can maintain a chain of ``MapperExtension`` 

36 objects. When a particular mapping event occurs, the 

37 corresponding method on each ``MapperExtension`` is invoked 

38 serially, and each method has the ability to halt the chain 

39 from proceeding further:: 

40 

41 m = mapper(User, users_table, extension=[ext1, ext2, ext3]) 

42 

43 Each ``MapperExtension`` method returns the symbol 

44 EXT_CONTINUE by default. This symbol generally means "move 

45 to the next ``MapperExtension`` for processing". For methods 

46 that return objects like translated rows or new object 

47 instances, EXT_CONTINUE means the result of the method 

48 should be ignored. In some cases it's required for a 

49 default mapper activity to be performed, such as adding a 

50 new instance to a result list. 

51 

52 The symbol EXT_STOP has significance within a chain 

53 of ``MapperExtension`` objects that the chain will be stopped 

54 when this symbol is returned. Like EXT_CONTINUE, it also 

55 has additional significance in some cases that a default 

56 mapper activity will not be performed. 

57 

58 """ 

59 

60 @classmethod 

61 def _adapt_instrument_class(cls, self, listener): 

62 cls._adapt_listener_methods(self, listener, ("instrument_class",)) 

63 

64 @classmethod 

65 def _adapt_listener(cls, self, listener): 

66 cls._adapt_listener_methods( 

67 self, 

68 listener, 

69 ( 

70 "init_instance", 

71 "init_failed", 

72 "reconstruct_instance", 

73 "before_insert", 

74 "after_insert", 

75 "before_update", 

76 "after_update", 

77 "before_delete", 

78 "after_delete", 

79 ), 

80 ) 

81 

82 @classmethod 

83 def _adapt_listener_methods(cls, self, listener, methods): 

84 

85 for meth in methods: 

86 me_meth = getattr(MapperExtension, meth) 

87 ls_meth = getattr(listener, meth) 

88 

89 if not util.methods_equivalent(me_meth, ls_meth): 

90 util.warn_deprecated( 

91 "MapperExtension.%s is deprecated. The " 

92 "MapperExtension class will be removed in a future " 

93 "release. Please transition to the @event interface, " 

94 "using @event.listens_for(mapped_class, '%s')." 

95 % (meth, meth) 

96 ) 

97 

98 if meth == "reconstruct_instance": 

99 

100 def go(ls_meth): 

101 def reconstruct(instance, ctx): 

102 ls_meth(self, instance) 

103 

104 return reconstruct 

105 

106 event.listen( 

107 self.class_manager, 

108 "load", 

109 go(ls_meth), 

110 raw=False, 

111 propagate=True, 

112 ) 

113 elif meth == "init_instance": 

114 

115 def go(ls_meth): 

116 def init_instance(instance, args, kwargs): 

117 ls_meth( 

118 self, 

119 self.class_, 

120 self.class_manager.original_init, 

121 instance, 

122 args, 

123 kwargs, 

124 ) 

125 

126 return init_instance 

127 

128 event.listen( 

129 self.class_manager, 

130 "init", 

131 go(ls_meth), 

132 raw=False, 

133 propagate=True, 

134 ) 

135 elif meth == "init_failed": 

136 

137 def go(ls_meth): 

138 def init_failed(instance, args, kwargs): 

139 util.warn_exception( 

140 ls_meth, 

141 self, 

142 self.class_, 

143 self.class_manager.original_init, 

144 instance, 

145 args, 

146 kwargs, 

147 ) 

148 

149 return init_failed 

150 

151 event.listen( 

152 self.class_manager, 

153 "init_failure", 

154 go(ls_meth), 

155 raw=False, 

156 propagate=True, 

157 ) 

158 else: 

159 event.listen( 

160 self, 

161 "%s" % meth, 

162 ls_meth, 

163 raw=False, 

164 retval=True, 

165 propagate=True, 

166 ) 

167 

168 def instrument_class(self, mapper, class_): 

169 """Receive a class when the mapper is first constructed, and has 

170 applied instrumentation to the mapped class. 

171 

172 The return value is only significant within the ``MapperExtension`` 

173 chain; the parent mapper's behavior isn't modified by this method. 

174 

175 """ 

176 return EXT_CONTINUE 

177 

178 def init_instance(self, mapper, class_, oldinit, instance, args, kwargs): 

179 """Receive an instance when its constructor is called. 

180 

181 This method is only called during a userland construction of 

182 an object. It is not called when an object is loaded from the 

183 database. 

184 

185 The return value is only significant within the ``MapperExtension`` 

186 chain; the parent mapper's behavior isn't modified by this method. 

187 

188 """ 

189 return EXT_CONTINUE 

190 

191 def init_failed(self, mapper, class_, oldinit, instance, args, kwargs): 

192 """Receive an instance when its constructor has been called, 

193 and raised an exception. 

194 

195 This method is only called during a userland construction of 

196 an object. It is not called when an object is loaded from the 

197 database. 

198 

199 The return value is only significant within the ``MapperExtension`` 

200 chain; the parent mapper's behavior isn't modified by this method. 

201 

202 """ 

203 return EXT_CONTINUE 

204 

205 def reconstruct_instance(self, mapper, instance): 

206 """Receive an object instance after it has been created via 

207 ``__new__``, and after initial attribute population has 

208 occurred. 

209 

210 This typically occurs when the instance is created based on 

211 incoming result rows, and is only called once for that 

212 instance's lifetime. 

213 

214 Note that during a result-row load, this method is called upon 

215 the first row received for this instance. Note that some 

216 attributes and collections may or may not be loaded or even 

217 initialized, depending on what's present in the result rows. 

218 

219 The return value is only significant within the ``MapperExtension`` 

220 chain; the parent mapper's behavior isn't modified by this method. 

221 

222 """ 

223 return EXT_CONTINUE 

224 

225 def before_insert(self, mapper, connection, instance): 

226 """Receive an object instance before that instance is inserted 

227 into its table. 

228 

229 This is a good place to set up primary key values and such 

230 that aren't handled otherwise. 

231 

232 Column-based attributes can be modified within this method 

233 which will result in the new value being inserted. However 

234 *no* changes to the overall flush plan can be made, and 

235 manipulation of the ``Session`` will not have the desired effect. 

236 To manipulate the ``Session`` within an extension, use 

237 ``SessionExtension``. 

238 

239 The return value is only significant within the ``MapperExtension`` 

240 chain; the parent mapper's behavior isn't modified by this method. 

241 

242 """ 

243 

244 return EXT_CONTINUE 

245 

246 def after_insert(self, mapper, connection, instance): 

247 """Receive an object instance after that instance is inserted. 

248 

249 The return value is only significant within the ``MapperExtension`` 

250 chain; the parent mapper's behavior isn't modified by this method. 

251 

252 """ 

253 

254 return EXT_CONTINUE 

255 

256 def before_update(self, mapper, connection, instance): 

257 """Receive an object instance before that instance is updated. 

258 

259 Note that this method is called for all instances that are marked as 

260 "dirty", even those which have no net changes to their column-based 

261 attributes. An object is marked as dirty when any of its column-based 

262 attributes have a "set attribute" operation called or when any of its 

263 collections are modified. If, at update time, no column-based 

264 attributes have any net changes, no UPDATE statement will be issued. 

265 This means that an instance being sent to before_update is *not* a 

266 guarantee that an UPDATE statement will be issued (although you can 

267 affect the outcome here). 

268 

269 To detect if the column-based attributes on the object have net 

270 changes, and will therefore generate an UPDATE statement, use 

271 ``object_session(instance).is_modified(instance, 

272 include_collections=False)``. 

273 

274 Column-based attributes can be modified within this method 

275 which will result in the new value being updated. However 

276 *no* changes to the overall flush plan can be made, and 

277 manipulation of the ``Session`` will not have the desired effect. 

278 To manipulate the ``Session`` within an extension, use 

279 ``SessionExtension``. 

280 

281 The return value is only significant within the ``MapperExtension`` 

282 chain; the parent mapper's behavior isn't modified by this method. 

283 

284 """ 

285 

286 return EXT_CONTINUE 

287 

288 def after_update(self, mapper, connection, instance): 

289 """Receive an object instance after that instance is updated. 

290 

291 The return value is only significant within the ``MapperExtension`` 

292 chain; the parent mapper's behavior isn't modified by this method. 

293 

294 """ 

295 

296 return EXT_CONTINUE 

297 

298 def before_delete(self, mapper, connection, instance): 

299 """Receive an object instance before that instance is deleted. 

300 

301 Note that *no* changes to the overall flush plan can be made 

302 here; and manipulation of the ``Session`` will not have the 

303 desired effect. To manipulate the ``Session`` within an 

304 extension, use ``SessionExtension``. 

305 

306 The return value is only significant within the ``MapperExtension`` 

307 chain; the parent mapper's behavior isn't modified by this method. 

308 

309 """ 

310 

311 return EXT_CONTINUE 

312 

313 def after_delete(self, mapper, connection, instance): 

314 """Receive an object instance after that instance is deleted. 

315 

316 The return value is only significant within the ``MapperExtension`` 

317 chain; the parent mapper's behavior isn't modified by this method. 

318 

319 """ 

320 

321 return EXT_CONTINUE 

322 

323 

324@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces") 

325class SessionExtension(object): 

326 

327 """Base implementation for :class:`.Session` event hooks. 

328 

329 .. deprecated:: 0.7 

330 

331 :class:`.SessionExtension` is deprecated and will be removed in a future 

332 release. Please refer to :func:`.event.listen` in conjunction with 

333 the :class:`.SessionEvents` listener interface. 

334 

335 Subclasses may be installed into a :class:`.Session` (or 

336 :class:`.sessionmaker`) using the ``extension`` keyword 

337 argument:: 

338 

339 from sqlalchemy.orm.interfaces import SessionExtension 

340 

341 class MySessionExtension(SessionExtension): 

342 def before_commit(self, session): 

343 print "before commit!" 

344 

345 Session = sessionmaker(extension=MySessionExtension()) 

346 

347 The same :class:`.SessionExtension` instance can be used 

348 with any number of sessions. 

349 

350 """ 

351 

352 @classmethod 

353 def _adapt_listener(cls, self, listener): 

354 for meth in [ 

355 "before_commit", 

356 "after_commit", 

357 "after_rollback", 

358 "before_flush", 

359 "after_flush", 

360 "after_flush_postexec", 

361 "after_begin", 

362 "after_attach", 

363 "after_bulk_update", 

364 "after_bulk_delete", 

365 ]: 

366 me_meth = getattr(SessionExtension, meth) 

367 ls_meth = getattr(listener, meth) 

368 

369 if not util.methods_equivalent(me_meth, ls_meth): 

370 util.warn_deprecated( 

371 "SessionExtension.%s is deprecated. The " 

372 "SessionExtension class will be removed in a future " 

373 "release. Please transition to the @event interface, " 

374 "using @event.listens_for(Session, '%s')." % (meth, meth) 

375 ) 

376 

377 event.listen(self, meth, getattr(listener, meth)) 

378 

379 def before_commit(self, session): 

380 """Execute right before commit is called. 

381 

382 Note that this may not be per-flush if a longer running 

383 transaction is ongoing.""" 

384 

385 def after_commit(self, session): 

386 """Execute after a commit has occurred. 

387 

388 Note that this may not be per-flush if a longer running 

389 transaction is ongoing.""" 

390 

391 def after_rollback(self, session): 

392 """Execute after a rollback has occurred. 

393 

394 Note that this may not be per-flush if a longer running 

395 transaction is ongoing.""" 

396 

397 def before_flush(self, session, flush_context, instances): 

398 """Execute before flush process has started. 

399 

400 `instances` is an optional list of objects which were passed to 

401 the ``flush()`` method. """ 

402 

403 def after_flush(self, session, flush_context): 

404 """Execute after flush has completed, but before commit has been 

405 called. 

406 

407 Note that the session's state is still in pre-flush, i.e. 'new', 

408 'dirty', and 'deleted' lists still show pre-flush state as well 

409 as the history settings on instance attributes.""" 

410 

411 def after_flush_postexec(self, session, flush_context): 

412 """Execute after flush has completed, and after the post-exec 

413 state occurs. 

414 

415 This will be when the 'new', 'dirty', and 'deleted' lists are in 

416 their final state. An actual commit() may or may not have 

417 occurred, depending on whether or not the flush started its own 

418 transaction or participated in a larger transaction. """ 

419 

420 def after_begin(self, session, transaction, connection): 

421 """Execute after a transaction is begun on a connection 

422 

423 `transaction` is the SessionTransaction. This method is called 

424 after an engine level transaction is begun on a connection. """ 

425 

426 def after_attach(self, session, instance): 

427 """Execute after an instance is attached to a session. 

428 

429 This is called after an add, delete or merge. """ 

430 

431 def after_bulk_update(self, session, query, query_context, result): 

432 """Execute after a bulk update operation to the session. 

433 

434 This is called after a session.query(...).update() 

435 

436 `query` is the query object that this update operation was 

437 called on. `query_context` was the query context object. 

438 `result` is the result object returned from the bulk operation. 

439 """ 

440 

441 def after_bulk_delete(self, session, query, query_context, result): 

442 """Execute after a bulk delete operation to the session. 

443 

444 This is called after a session.query(...).delete() 

445 

446 `query` is the query object that this delete operation was 

447 called on. `query_context` was the query context object. 

448 `result` is the result object returned from the bulk operation. 

449 """ 

450 

451 

452@util.langhelpers.dependency_for("sqlalchemy.orm.interfaces") 

453class AttributeExtension(object): 

454 """Base implementation for :class:`.AttributeImpl` event hooks, events 

455 that fire upon attribute mutations in user code. 

456 

457 .. deprecated:: 0.7 

458 

459 :class:`.AttributeExtension` is deprecated and will be removed in a 

460 future release. Please refer to :func:`.event.listen` in conjunction 

461 with the :class:`.AttributeEvents` listener interface. 

462 

463 :class:`.AttributeExtension` is used to listen for set, 

464 remove, and append events on individual mapped attributes. 

465 It is established on an individual mapped attribute using 

466 the `extension` argument, available on 

467 :func:`.column_property`, :func:`_orm.relationship`, and 

468 others:: 

469 

470 from sqlalchemy.orm.interfaces import AttributeExtension 

471 from sqlalchemy.orm import mapper, relationship, column_property 

472 

473 class MyAttrExt(AttributeExtension): 

474 def append(self, state, value, initiator): 

475 print "append event !" 

476 return value 

477 

478 def set(self, state, value, oldvalue, initiator): 

479 print "set event !" 

480 return value 

481 

482 mapper(SomeClass, sometable, properties={ 

483 'foo':column_property(sometable.c.foo, extension=MyAttrExt()), 

484 'bar':relationship(Bar, extension=MyAttrExt()) 

485 }) 

486 

487 Note that the :class:`.AttributeExtension` methods 

488 :meth:`~.AttributeExtension.append` and 

489 :meth:`~.AttributeExtension.set` need to return the 

490 ``value`` parameter. The returned value is used as the 

491 effective value, and allows the extension to change what is 

492 ultimately persisted. 

493 

494 AttributeExtension is assembled within the descriptors associated 

495 with a mapped class. 

496 

497 """ 

498 

499 active_history = True 

500 """indicates that the set() method would like to receive the 'old' value, 

501 even if it means firing lazy callables. 

502 

503 Note that ``active_history`` can also be set directly via 

504 :func:`.column_property` and :func:`_orm.relationship`. 

505 

506 """ 

507 

508 @classmethod 

509 def _adapt_listener(cls, self, listener): 

510 for meth in ["append", "remove", "set"]: 

511 me_meth = getattr(AttributeExtension, meth) 

512 ls_meth = getattr(listener, meth) 

513 

514 if not util.methods_equivalent(me_meth, ls_meth): 

515 util.warn_deprecated( 

516 "AttributeExtension.%s is deprecated. The " 

517 "AttributeExtension class will be removed in a future " 

518 "release. Please transition to the @event interface, " 

519 "using @event.listens_for(Class.attribute, '%s')." 

520 % (meth, meth) 

521 ) 

522 

523 event.listen( 

524 self, 

525 "append", 

526 listener.append, 

527 active_history=listener.active_history, 

528 raw=True, 

529 retval=True, 

530 ) 

531 event.listen( 

532 self, 

533 "remove", 

534 listener.remove, 

535 active_history=listener.active_history, 

536 raw=True, 

537 retval=True, 

538 ) 

539 event.listen( 

540 self, 

541 "set", 

542 listener.set, 

543 active_history=listener.active_history, 

544 raw=True, 

545 retval=True, 

546 ) 

547 

548 def append(self, state, value, initiator): 

549 """Receive a collection append event. 

550 

551 The returned value will be used as the actual value to be 

552 appended. 

553 

554 """ 

555 return value 

556 

557 def remove(self, state, value, initiator): 

558 """Receive a remove event. 

559 

560 No return value is defined. 

561 

562 """ 

563 pass 

564 

565 def set(self, state, value, oldvalue, initiator): 

566 """Receive a set event. 

567 

568 The returned value will be used as the actual value to be 

569 set. 

570 

571 """ 

572 return value