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

304 statements  

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

1# orm/instrumentation.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 

8"""Defines SQLAlchemy's system of class instrumentation. 

9 

10This module is usually not directly visible to user applications, but 

11defines a large part of the ORM's interactivity. 

12 

13instrumentation.py deals with registration of end-user classes 

14for state tracking. It interacts closely with state.py 

15and attributes.py which establish per-instance and per-class-attribute 

16instrumentation, respectively. 

17 

18The class instrumentation system can be customized on a per-class 

19or global basis using the :mod:`sqlalchemy.ext.instrumentation` 

20module, which provides the means to build and specify 

21alternate instrumentation forms. 

22 

23.. versionchanged: 0.8 

24 The instrumentation extension system was moved out of the 

25 ORM and into the external :mod:`sqlalchemy.ext.instrumentation` 

26 package. When that package is imported, it installs 

27 itself within sqlalchemy.orm so that its more comprehensive 

28 resolution mechanics take effect. 

29 

30""" 

31 

32 

33import weakref 

34 

35from . import base 

36from . import collections 

37from . import exc 

38from . import interfaces 

39from . import state 

40from .. import util 

41from ..util import HasMemoized 

42 

43 

44DEL_ATTR = util.symbol("DEL_ATTR") 

45 

46 

47class ClassManager(HasMemoized, dict): 

48 """Tracks state information at the class level.""" 

49 

50 MANAGER_ATTR = base.DEFAULT_MANAGER_ATTR 

51 STATE_ATTR = base.DEFAULT_STATE_ATTR 

52 

53 _state_setter = staticmethod(util.attrsetter(STATE_ATTR)) 

54 

55 expired_attribute_loader = None 

56 "previously known as deferred_scalar_loader" 

57 

58 init_method = None 

59 

60 factory = None 

61 mapper = None 

62 declarative_scan = None 

63 registry = None 

64 

65 @property 

66 @util.deprecated( 

67 "1.4", 

68 message="The ClassManager.deferred_scalar_loader attribute is now " 

69 "named expired_attribute_loader", 

70 ) 

71 def deferred_scalar_loader(self): 

72 return self.expired_attribute_loader 

73 

74 @deferred_scalar_loader.setter 

75 @util.deprecated( 

76 "1.4", 

77 message="The ClassManager.deferred_scalar_loader attribute is now " 

78 "named expired_attribute_loader", 

79 ) 

80 def deferred_scalar_loader(self, obj): 

81 self.expired_attribute_loader = obj 

82 

83 def __init__(self, class_): 

84 self.class_ = class_ 

85 self.info = {} 

86 self.new_init = None 

87 self.local_attrs = {} 

88 self.originals = {} 

89 self._finalized = False 

90 

91 self._bases = [ 

92 mgr 

93 for mgr in [ 

94 manager_of_class(base) 

95 for base in self.class_.__bases__ 

96 if isinstance(base, type) 

97 ] 

98 if mgr is not None 

99 ] 

100 

101 for base_ in self._bases: 

102 self.update(base_) 

103 

104 self.dispatch._events._new_classmanager_instance(class_, self) 

105 

106 for basecls in class_.__mro__: 

107 mgr = manager_of_class(basecls) 

108 if mgr is not None: 

109 self.dispatch._update(mgr.dispatch) 

110 

111 self.manage() 

112 

113 if "__del__" in class_.__dict__: 

114 util.warn( 

115 "__del__() method on class %s will " 

116 "cause unreachable cycles and memory leaks, " 

117 "as SQLAlchemy instrumentation often creates " 

118 "reference cycles. Please remove this method." % class_ 

119 ) 

120 

121 def _update_state( 

122 self, 

123 finalize=False, 

124 mapper=None, 

125 registry=None, 

126 declarative_scan=None, 

127 expired_attribute_loader=None, 

128 init_method=None, 

129 ): 

130 

131 if mapper: 

132 self.mapper = mapper 

133 if registry: 

134 registry._add_manager(self) 

135 if declarative_scan: 

136 self.declarative_scan = weakref.ref(declarative_scan) 

137 if expired_attribute_loader: 

138 self.expired_attribute_loader = expired_attribute_loader 

139 

140 if init_method: 

141 assert not self._finalized, ( 

142 "class is already instrumented, " 

143 "init_method %s can't be applied" % init_method 

144 ) 

145 self.init_method = init_method 

146 

147 if not self._finalized: 

148 self.original_init = ( 

149 self.init_method 

150 if self.init_method is not None 

151 and self.class_.__init__ is object.__init__ 

152 else self.class_.__init__ 

153 ) 

154 

155 if finalize and not self._finalized: 

156 self._finalize() 

157 

158 def _finalize(self): 

159 if self._finalized: 

160 return 

161 self._finalized = True 

162 

163 self._instrument_init() 

164 

165 _instrumentation_factory.dispatch.class_instrument(self.class_) 

166 

167 def __hash__(self): 

168 return id(self) 

169 

170 def __eq__(self, other): 

171 return other is self 

172 

173 @property 

174 def is_mapped(self): 

175 return "mapper" in self.__dict__ 

176 

177 @HasMemoized.memoized_attribute 

178 def _all_key_set(self): 

179 return frozenset(self) 

180 

181 @HasMemoized.memoized_attribute 

182 def _collection_impl_keys(self): 

183 return frozenset( 

184 [attr.key for attr in self.values() if attr.impl.collection] 

185 ) 

186 

187 @HasMemoized.memoized_attribute 

188 def _scalar_loader_impls(self): 

189 return frozenset( 

190 [ 

191 attr.impl 

192 for attr in self.values() 

193 if attr.impl.accepts_scalar_loader 

194 ] 

195 ) 

196 

197 @HasMemoized.memoized_attribute 

198 def _loader_impls(self): 

199 return frozenset([attr.impl for attr in self.values()]) 

200 

201 @util.memoized_property 

202 def mapper(self): 

203 # raises unless self.mapper has been assigned 

204 raise exc.UnmappedClassError(self.class_) 

205 

206 def _all_sqla_attributes(self, exclude=None): 

207 """return an iterator of all classbound attributes that are 

208 implement :class:`.InspectionAttr`. 

209 

210 This includes :class:`.QueryableAttribute` as well as extension 

211 types such as :class:`.hybrid_property` and 

212 :class:`.AssociationProxy`. 

213 

214 """ 

215 

216 found = {} 

217 

218 # constraints: 

219 # 1. yield keys in cls.__dict__ order 

220 # 2. if a subclass has the same key as a superclass, include that 

221 # key as part of the ordering of the superclass, because an 

222 # overridden key is usually installed by the mapper which is going 

223 # on a different ordering 

224 # 3. don't use getattr() as this fires off descriptors 

225 

226 for supercls in self.class_.__mro__[0:-1]: 

227 inherits = supercls.__mro__[1] 

228 for key in supercls.__dict__: 

229 found.setdefault(key, supercls) 

230 if key in inherits.__dict__: 

231 continue 

232 val = found[key].__dict__[key] 

233 if ( 

234 isinstance(val, interfaces.InspectionAttr) 

235 and val.is_attribute 

236 ): 

237 yield key, val 

238 

239 def _get_class_attr_mro(self, key, default=None): 

240 """return an attribute on the class without tripping it.""" 

241 

242 for supercls in self.class_.__mro__: 

243 if key in supercls.__dict__: 

244 return supercls.__dict__[key] 

245 else: 

246 return default 

247 

248 def _attr_has_impl(self, key): 

249 """Return True if the given attribute is fully initialized. 

250 

251 i.e. has an impl. 

252 """ 

253 

254 return key in self and self[key].impl is not None 

255 

256 def _subclass_manager(self, cls): 

257 """Create a new ClassManager for a subclass of this ClassManager's 

258 class. 

259 

260 This is called automatically when attributes are instrumented so that 

261 the attributes can be propagated to subclasses against their own 

262 class-local manager, without the need for mappers etc. to have already 

263 pre-configured managers for the full class hierarchy. Mappers 

264 can post-configure the auto-generated ClassManager when needed. 

265 

266 """ 

267 return register_class(cls, finalize=False) 

268 

269 def _instrument_init(self): 

270 self.new_init = _generate_init(self.class_, self, self.original_init) 

271 self.install_member("__init__", self.new_init) 

272 

273 @util.memoized_property 

274 def _state_constructor(self): 

275 self.dispatch.first_init(self, self.class_) 

276 return state.InstanceState 

277 

278 def manage(self): 

279 """Mark this instance as the manager for its class.""" 

280 

281 setattr(self.class_, self.MANAGER_ATTR, self) 

282 

283 @util.hybridmethod 

284 def manager_getter(self): 

285 return _default_manager_getter 

286 

287 @util.hybridmethod 

288 def state_getter(self): 

289 """Return a (instance) -> InstanceState callable. 

290 

291 "state getter" callables should raise either KeyError or 

292 AttributeError if no InstanceState could be found for the 

293 instance. 

294 """ 

295 

296 return _default_state_getter 

297 

298 @util.hybridmethod 

299 def dict_getter(self): 

300 return _default_dict_getter 

301 

302 def instrument_attribute(self, key, inst, propagated=False): 

303 if propagated: 

304 if key in self.local_attrs: 

305 return # don't override local attr with inherited attr 

306 else: 

307 self.local_attrs[key] = inst 

308 self.install_descriptor(key, inst) 

309 self._reset_memoizations() 

310 self[key] = inst 

311 

312 for cls in self.class_.__subclasses__(): 

313 manager = self._subclass_manager(cls) 

314 manager.instrument_attribute(key, inst, True) 

315 

316 def subclass_managers(self, recursive): 

317 for cls in self.class_.__subclasses__(): 

318 mgr = manager_of_class(cls) 

319 if mgr is not None and mgr is not self: 

320 yield mgr 

321 if recursive: 

322 for m in mgr.subclass_managers(True): 

323 yield m 

324 

325 def post_configure_attribute(self, key): 

326 _instrumentation_factory.dispatch.attribute_instrument( 

327 self.class_, key, self[key] 

328 ) 

329 

330 def uninstrument_attribute(self, key, propagated=False): 

331 if key not in self: 

332 return 

333 if propagated: 

334 if key in self.local_attrs: 

335 return # don't get rid of local attr 

336 else: 

337 del self.local_attrs[key] 

338 self.uninstall_descriptor(key) 

339 self._reset_memoizations() 

340 del self[key] 

341 for cls in self.class_.__subclasses__(): 

342 manager = manager_of_class(cls) 

343 if manager: 

344 manager.uninstrument_attribute(key, True) 

345 

346 def unregister(self): 

347 """remove all instrumentation established by this ClassManager.""" 

348 

349 for key in list(self.originals): 

350 self.uninstall_member(key) 

351 

352 self.mapper = self.dispatch = self.new_init = None 

353 self.info.clear() 

354 

355 for key in list(self): 

356 if key in self.local_attrs: 

357 self.uninstrument_attribute(key) 

358 

359 if self.MANAGER_ATTR in self.class_.__dict__: 

360 delattr(self.class_, self.MANAGER_ATTR) 

361 

362 def install_descriptor(self, key, inst): 

363 if key in (self.STATE_ATTR, self.MANAGER_ATTR): 

364 raise KeyError( 

365 "%r: requested attribute name conflicts with " 

366 "instrumentation attribute of the same name." % key 

367 ) 

368 setattr(self.class_, key, inst) 

369 

370 def uninstall_descriptor(self, key): 

371 delattr(self.class_, key) 

372 

373 def install_member(self, key, implementation): 

374 if key in (self.STATE_ATTR, self.MANAGER_ATTR): 

375 raise KeyError( 

376 "%r: requested attribute name conflicts with " 

377 "instrumentation attribute of the same name." % key 

378 ) 

379 self.originals.setdefault(key, self.class_.__dict__.get(key, DEL_ATTR)) 

380 setattr(self.class_, key, implementation) 

381 

382 def uninstall_member(self, key): 

383 original = self.originals.pop(key, None) 

384 if original is not DEL_ATTR: 

385 setattr(self.class_, key, original) 

386 else: 

387 delattr(self.class_, key) 

388 

389 def instrument_collection_class(self, key, collection_class): 

390 return collections.prepare_instrumentation(collection_class) 

391 

392 def initialize_collection(self, key, state, factory): 

393 user_data = factory() 

394 adapter = collections.CollectionAdapter( 

395 self.get_impl(key), state, user_data 

396 ) 

397 return adapter, user_data 

398 

399 def is_instrumented(self, key, search=False): 

400 if search: 

401 return key in self 

402 else: 

403 return key in self.local_attrs 

404 

405 def get_impl(self, key): 

406 return self[key].impl 

407 

408 @property 

409 def attributes(self): 

410 return iter(self.values()) 

411 

412 # InstanceState management 

413 

414 def new_instance(self, state=None): 

415 instance = self.class_.__new__(self.class_) 

416 if state is None: 

417 state = self._state_constructor(instance, self) 

418 self._state_setter(instance, state) 

419 return instance 

420 

421 def setup_instance(self, instance, state=None): 

422 if state is None: 

423 state = self._state_constructor(instance, self) 

424 self._state_setter(instance, state) 

425 

426 def teardown_instance(self, instance): 

427 delattr(instance, self.STATE_ATTR) 

428 

429 def _serialize(self, state, state_dict): 

430 return _SerializeManager(state, state_dict) 

431 

432 def _new_state_if_none(self, instance): 

433 """Install a default InstanceState if none is present. 

434 

435 A private convenience method used by the __init__ decorator. 

436 

437 """ 

438 if hasattr(instance, self.STATE_ATTR): 

439 return False 

440 elif self.class_ is not instance.__class__ and self.is_mapped: 

441 # this will create a new ClassManager for the 

442 # subclass, without a mapper. This is likely a 

443 # user error situation but allow the object 

444 # to be constructed, so that it is usable 

445 # in a non-ORM context at least. 

446 return self._subclass_manager( 

447 instance.__class__ 

448 )._new_state_if_none(instance) 

449 else: 

450 state = self._state_constructor(instance, self) 

451 self._state_setter(instance, state) 

452 return state 

453 

454 def has_state(self, instance): 

455 return hasattr(instance, self.STATE_ATTR) 

456 

457 def has_parent(self, state, key, optimistic=False): 

458 """TODO""" 

459 return self.get_impl(key).hasparent(state, optimistic=optimistic) 

460 

461 def __bool__(self): 

462 """All ClassManagers are non-zero regardless of attribute state.""" 

463 return True 

464 

465 __nonzero__ = __bool__ 

466 

467 def __repr__(self): 

468 return "<%s of %r at %x>" % ( 

469 self.__class__.__name__, 

470 self.class_, 

471 id(self), 

472 ) 

473 

474 

475class _SerializeManager(object): 

476 """Provide serialization of a :class:`.ClassManager`. 

477 

478 The :class:`.InstanceState` uses ``__init__()`` on serialize 

479 and ``__call__()`` on deserialize. 

480 

481 """ 

482 

483 def __init__(self, state, d): 

484 self.class_ = state.class_ 

485 manager = state.manager 

486 manager.dispatch.pickle(state, d) 

487 

488 def __call__(self, state, inst, state_dict): 

489 state.manager = manager = manager_of_class(self.class_) 

490 if manager is None: 

491 raise exc.UnmappedInstanceError( 

492 inst, 

493 "Cannot deserialize object of type %r - " 

494 "no mapper() has " 

495 "been configured for this class within the current " 

496 "Python process!" % self.class_, 

497 ) 

498 elif manager.is_mapped and not manager.mapper.configured: 

499 manager.mapper._check_configure() 

500 

501 # setup _sa_instance_state ahead of time so that 

502 # unpickle events can access the object normally. 

503 # see [ticket:2362] 

504 if inst is not None: 

505 manager.setup_instance(inst, state) 

506 manager.dispatch.unpickle(state, state_dict) 

507 

508 

509class InstrumentationFactory(object): 

510 """Factory for new ClassManager instances.""" 

511 

512 def create_manager_for_cls(self, class_): 

513 assert class_ is not None 

514 assert manager_of_class(class_) is None 

515 

516 # give a more complicated subclass 

517 # a chance to do what it wants here 

518 manager, factory = self._locate_extended_factory(class_) 

519 

520 if factory is None: 

521 factory = ClassManager 

522 manager = factory(class_) 

523 

524 self._check_conflicts(class_, factory) 

525 

526 manager.factory = factory 

527 

528 return manager 

529 

530 def _locate_extended_factory(self, class_): 

531 """Overridden by a subclass to do an extended lookup.""" 

532 return None, None 

533 

534 def _check_conflicts(self, class_, factory): 

535 """Overridden by a subclass to test for conflicting factories.""" 

536 return 

537 

538 def unregister(self, class_): 

539 manager = manager_of_class(class_) 

540 manager.unregister() 

541 self.dispatch.class_uninstrument(class_) 

542 

543 

544# this attribute is replaced by sqlalchemy.ext.instrumentation 

545# when imported. 

546_instrumentation_factory = InstrumentationFactory() 

547 

548# these attributes are replaced by sqlalchemy.ext.instrumentation 

549# when a non-standard InstrumentationManager class is first 

550# used to instrument a class. 

551instance_state = _default_state_getter = base.instance_state 

552 

553instance_dict = _default_dict_getter = base.instance_dict 

554 

555manager_of_class = _default_manager_getter = base.manager_of_class 

556 

557 

558def register_class( 

559 class_, 

560 finalize=True, 

561 mapper=None, 

562 registry=None, 

563 declarative_scan=None, 

564 expired_attribute_loader=None, 

565 init_method=None, 

566): 

567 """Register class instrumentation. 

568 

569 Returns the existing or newly created class manager. 

570 

571 """ 

572 

573 manager = manager_of_class(class_) 

574 if manager is None: 

575 manager = _instrumentation_factory.create_manager_for_cls(class_) 

576 manager._update_state( 

577 mapper=mapper, 

578 registry=registry, 

579 declarative_scan=declarative_scan, 

580 expired_attribute_loader=expired_attribute_loader, 

581 init_method=init_method, 

582 finalize=finalize, 

583 ) 

584 

585 return manager 

586 

587 

588def unregister_class(class_): 

589 """Unregister class instrumentation.""" 

590 

591 _instrumentation_factory.unregister(class_) 

592 

593 

594def is_instrumented(instance, key): 

595 """Return True if the given attribute on the given instance is 

596 instrumented by the attributes package. 

597 

598 This function may be used regardless of instrumentation 

599 applied directly to the class, i.e. no descriptors are required. 

600 

601 """ 

602 return manager_of_class(instance.__class__).is_instrumented( 

603 key, search=True 

604 ) 

605 

606 

607def _generate_init(class_, class_manager, original_init): 

608 """Build an __init__ decorator that triggers ClassManager events.""" 

609 

610 # TODO: we should use the ClassManager's notion of the 

611 # original '__init__' method, once ClassManager is fixed 

612 # to always reference that. 

613 

614 if original_init is None: 

615 original_init = class_.__init__ 

616 

617 # Go through some effort here and don't change the user's __init__ 

618 # calling signature, including the unlikely case that it has 

619 # a return value. 

620 # FIXME: need to juggle local names to avoid constructor argument 

621 # clashes. 

622 func_body = """\ 

623def __init__(%(apply_pos)s): 

624 new_state = class_manager._new_state_if_none(%(self_arg)s) 

625 if new_state: 

626 return new_state._initialize_instance(%(apply_kw)s) 

627 else: 

628 return original_init(%(apply_kw)s) 

629""" 

630 func_vars = util.format_argspec_init(original_init, grouped=False) 

631 func_text = func_body % func_vars 

632 

633 if util.py2k: 

634 func = getattr(original_init, "im_func", original_init) 

635 func_defaults = getattr(func, "func_defaults", None) 

636 else: 

637 func_defaults = getattr(original_init, "__defaults__", None) 

638 func_kw_defaults = getattr(original_init, "__kwdefaults__", None) 

639 

640 env = locals().copy() 

641 env["__name__"] = __name__ 

642 exec(func_text, env) 

643 __init__ = env["__init__"] 

644 __init__.__doc__ = original_init.__doc__ 

645 __init__._sa_original_init = original_init 

646 

647 if func_defaults: 

648 __init__.__defaults__ = func_defaults 

649 if not util.py2k and func_kw_defaults: 

650 __init__.__kwdefaults__ = func_kw_defaults 

651 

652 return __init__