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

157 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# orm/base.py 

2# Copyright (C) 2005-2023 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"""Constants and rudimental functions used throughout the ORM. 

9 

10""" 

11 

12import operator 

13 

14from . import exc 

15from .. import exc as sa_exc 

16from .. import inspection 

17from .. import util 

18 

19 

20PASSIVE_NO_RESULT = util.symbol( 

21 "PASSIVE_NO_RESULT", 

22 """Symbol returned by a loader callable or other attribute/history 

23 retrieval operation when a value could not be determined, based 

24 on loader callable flags. 

25 """, 

26) 

27 

28PASSIVE_CLASS_MISMATCH = util.symbol( 

29 "PASSIVE_CLASS_MISMATCH", 

30 """Symbol indicating that an object is locally present for a given 

31 primary key identity but it is not of the requested class. The 

32 return value is therefore None and no SQL should be emitted.""", 

33) 

34 

35ATTR_WAS_SET = util.symbol( 

36 "ATTR_WAS_SET", 

37 """Symbol returned by a loader callable to indicate the 

38 retrieved value, or values, were assigned to their attributes 

39 on the target object. 

40 """, 

41) 

42 

43ATTR_EMPTY = util.symbol( 

44 "ATTR_EMPTY", 

45 """Symbol used internally to indicate an attribute had no callable.""", 

46) 

47 

48NO_VALUE = util.symbol( 

49 "NO_VALUE", 

50 """Symbol which may be placed as the 'previous' value of an attribute, 

51 indicating no value was loaded for an attribute when it was modified, 

52 and flags indicated we were not to load it. 

53 """, 

54) 

55NEVER_SET = NO_VALUE 

56""" 

57Synonymous with NO_VALUE 

58 

59.. versionchanged:: 1.4 NEVER_SET was merged with NO_VALUE 

60""" 

61 

62NO_CHANGE = util.symbol( 

63 "NO_CHANGE", 

64 """No callables or SQL should be emitted on attribute access 

65 and no state should change 

66 """, 

67 canonical=0, 

68) 

69 

70CALLABLES_OK = util.symbol( 

71 "CALLABLES_OK", 

72 """Loader callables can be fired off if a value 

73 is not present. 

74 """, 

75 canonical=1, 

76) 

77 

78SQL_OK = util.symbol( 

79 "SQL_OK", 

80 """Loader callables can emit SQL at least on scalar value attributes.""", 

81 canonical=2, 

82) 

83 

84RELATED_OBJECT_OK = util.symbol( 

85 "RELATED_OBJECT_OK", 

86 """Callables can use SQL to load related objects as well 

87 as scalar value attributes. 

88 """, 

89 canonical=4, 

90) 

91 

92INIT_OK = util.symbol( 

93 "INIT_OK", 

94 """Attributes should be initialized with a blank 

95 value (None or an empty collection) upon get, if no other 

96 value can be obtained. 

97 """, 

98 canonical=8, 

99) 

100 

101NON_PERSISTENT_OK = util.symbol( 

102 "NON_PERSISTENT_OK", 

103 """Callables can be emitted if the parent is not persistent.""", 

104 canonical=16, 

105) 

106 

107LOAD_AGAINST_COMMITTED = util.symbol( 

108 "LOAD_AGAINST_COMMITTED", 

109 """Callables should use committed values as primary/foreign keys during a 

110 load. 

111 """, 

112 canonical=32, 

113) 

114 

115NO_AUTOFLUSH = util.symbol( 

116 "NO_AUTOFLUSH", 

117 """Loader callables should disable autoflush.""", 

118 canonical=64, 

119) 

120 

121NO_RAISE = util.symbol( 

122 "NO_RAISE", 

123 """Loader callables should not raise any assertions""", 

124 canonical=128, 

125) 

126 

127DEFERRED_HISTORY_LOAD = util.symbol( 

128 "DEFERRED_HISTORY_LOAD", 

129 """indicates special load of the previous value of an attribute""", 

130 canonical=256, 

131) 

132 

133# pre-packaged sets of flags used as inputs 

134PASSIVE_OFF = util.symbol( 

135 "PASSIVE_OFF", 

136 "Callables can be emitted in all cases.", 

137 canonical=( 

138 RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK 

139 ), 

140) 

141PASSIVE_RETURN_NO_VALUE = util.symbol( 

142 "PASSIVE_RETURN_NO_VALUE", 

143 """PASSIVE_OFF ^ INIT_OK""", 

144 canonical=PASSIVE_OFF ^ INIT_OK, 

145) 

146PASSIVE_NO_INITIALIZE = util.symbol( 

147 "PASSIVE_NO_INITIALIZE", 

148 "PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK", 

149 canonical=PASSIVE_RETURN_NO_VALUE ^ CALLABLES_OK, 

150) 

151PASSIVE_NO_FETCH = util.symbol( 

152 "PASSIVE_NO_FETCH", "PASSIVE_OFF ^ SQL_OK", canonical=PASSIVE_OFF ^ SQL_OK 

153) 

154PASSIVE_NO_FETCH_RELATED = util.symbol( 

155 "PASSIVE_NO_FETCH_RELATED", 

156 "PASSIVE_OFF ^ RELATED_OBJECT_OK", 

157 canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK, 

158) 

159PASSIVE_ONLY_PERSISTENT = util.symbol( 

160 "PASSIVE_ONLY_PERSISTENT", 

161 "PASSIVE_OFF ^ NON_PERSISTENT_OK", 

162 canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK, 

163) 

164 

165PASSIVE_MERGE = util.symbol( 

166 "PASSIVE_OFF | NO_RAISE", 

167 "Symbol used specifically for session.merge() and similar cases", 

168 canonical=PASSIVE_OFF | NO_RAISE, 

169) 

170 

171DEFAULT_MANAGER_ATTR = "_sa_class_manager" 

172DEFAULT_STATE_ATTR = "_sa_instance_state" 

173 

174EXT_CONTINUE = util.symbol("EXT_CONTINUE") 

175EXT_STOP = util.symbol("EXT_STOP") 

176EXT_SKIP = util.symbol("EXT_SKIP") 

177 

178ONETOMANY = util.symbol( 

179 "ONETOMANY", 

180 """Indicates the one-to-many direction for a :func:`_orm.relationship`. 

181 

182 This symbol is typically used by the internals but may be exposed within 

183 certain API features. 

184 

185 """, 

186) 

187 

188MANYTOONE = util.symbol( 

189 "MANYTOONE", 

190 """Indicates the many-to-one direction for a :func:`_orm.relationship`. 

191 

192 This symbol is typically used by the internals but may be exposed within 

193 certain API features. 

194 

195 """, 

196) 

197 

198MANYTOMANY = util.symbol( 

199 "MANYTOMANY", 

200 """Indicates the many-to-many direction for a :func:`_orm.relationship`. 

201 

202 This symbol is typically used by the internals but may be exposed within 

203 certain API features. 

204 

205 """, 

206) 

207 

208NOT_EXTENSION = util.symbol( 

209 "NOT_EXTENSION", 

210 """Symbol indicating an :class:`InspectionAttr` that's 

211 not part of sqlalchemy.ext. 

212 

213 Is assigned to the :attr:`.InspectionAttr.extension_type` 

214 attribute. 

215 

216 """, 

217) 

218 

219_never_set = frozenset([NEVER_SET]) 

220 

221_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT]) 

222 

223_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED") 

224 

225_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE") 

226 

227_RAISE_FOR_STATE = util.symbol("RAISE_FOR_STATE") 

228 

229 

230def _assertions(*assertions): 

231 @util.decorator 

232 def generate(fn, *args, **kw): 

233 self = args[0] 

234 for assertion in assertions: 

235 assertion(self, fn.__name__) 

236 fn(self, *args[1:], **kw) 

237 

238 return generate 

239 

240 

241# these can be replaced by sqlalchemy.ext.instrumentation 

242# if augmented class instrumentation is enabled. 

243def manager_of_class(cls): 

244 return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None) 

245 

246 

247instance_state = operator.attrgetter(DEFAULT_STATE_ATTR) 

248 

249instance_dict = operator.attrgetter("__dict__") 

250 

251 

252def instance_str(instance): 

253 """Return a string describing an instance.""" 

254 

255 return state_str(instance_state(instance)) 

256 

257 

258def state_str(state): 

259 """Return a string describing an instance via its InstanceState.""" 

260 

261 if state is None: 

262 return "None" 

263 else: 

264 return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj())) 

265 

266 

267def state_class_str(state): 

268 """Return a string describing an instance's class via its 

269 InstanceState. 

270 """ 

271 

272 if state is None: 

273 return "None" 

274 else: 

275 return "<%s>" % (state.class_.__name__,) 

276 

277 

278def attribute_str(instance, attribute): 

279 return instance_str(instance) + "." + attribute 

280 

281 

282def state_attribute_str(state, attribute): 

283 return state_str(state) + "." + attribute 

284 

285 

286def object_mapper(instance): 

287 """Given an object, return the primary Mapper associated with the object 

288 instance. 

289 

290 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError` 

291 if no mapping is configured. 

292 

293 This function is available via the inspection system as:: 

294 

295 inspect(instance).mapper 

296 

297 Using the inspection system will raise 

298 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is 

299 not part of a mapping. 

300 

301 """ 

302 return object_state(instance).mapper 

303 

304 

305def object_state(instance): 

306 """Given an object, return the :class:`.InstanceState` 

307 associated with the object. 

308 

309 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError` 

310 if no mapping is configured. 

311 

312 Equivalent functionality is available via the :func:`_sa.inspect` 

313 function as:: 

314 

315 inspect(instance) 

316 

317 Using the inspection system will raise 

318 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is 

319 not part of a mapping. 

320 

321 """ 

322 state = _inspect_mapped_object(instance) 

323 if state is None: 

324 raise exc.UnmappedInstanceError(instance) 

325 else: 

326 return state 

327 

328 

329@inspection._inspects(object) 

330def _inspect_mapped_object(instance): 

331 try: 

332 return instance_state(instance) 

333 except (exc.UnmappedClassError,) + exc.NO_STATE: 

334 return None 

335 

336 

337def _class_to_mapper(class_or_mapper): 

338 insp = inspection.inspect(class_or_mapper, False) 

339 if insp is not None: 

340 return insp.mapper 

341 else: 

342 raise exc.UnmappedClassError(class_or_mapper) 

343 

344 

345def _mapper_or_none(entity): 

346 """Return the :class:`_orm.Mapper` for the given class or None if the 

347 class is not mapped. 

348 """ 

349 

350 insp = inspection.inspect(entity, False) 

351 if insp is not None: 

352 return insp.mapper 

353 else: 

354 return None 

355 

356 

357def _is_mapped_class(entity): 

358 """Return True if the given object is a mapped class, 

359 :class:`_orm.Mapper`, or :class:`.AliasedClass`. 

360 """ 

361 

362 insp = inspection.inspect(entity, False) 

363 return ( 

364 insp is not None 

365 and not insp.is_clause_element 

366 and (insp.is_mapper or insp.is_aliased_class) 

367 ) 

368 

369 

370def _orm_columns(entity): 

371 insp = inspection.inspect(entity, False) 

372 if hasattr(insp, "selectable") and hasattr(insp.selectable, "c"): 

373 return [c for c in insp.selectable.c] 

374 else: 

375 return [entity] 

376 

377 

378def _is_aliased_class(entity): 

379 insp = inspection.inspect(entity, False) 

380 return insp is not None and getattr(insp, "is_aliased_class", False) 

381 

382 

383def _entity_descriptor(entity, key): 

384 """Return a class attribute given an entity and string name. 

385 

386 May return :class:`.InstrumentedAttribute` or user-defined 

387 attribute. 

388 

389 """ 

390 insp = inspection.inspect(entity) 

391 if insp.is_selectable: 

392 description = entity 

393 entity = insp.c 

394 elif insp.is_aliased_class: 

395 entity = insp.entity 

396 description = entity 

397 elif hasattr(insp, "mapper"): 

398 description = entity = insp.mapper.class_ 

399 else: 

400 description = entity 

401 

402 try: 

403 return getattr(entity, key) 

404 except AttributeError as err: 

405 util.raise_( 

406 sa_exc.InvalidRequestError( 

407 "Entity '%s' has no property '%s'" % (description, key) 

408 ), 

409 replace_context=err, 

410 ) 

411 

412 

413_state_mapper = util.dottedgetter("manager.mapper") 

414 

415 

416@inspection._inspects(type) 

417def _inspect_mapped_class(class_, configure=False): 

418 try: 

419 class_manager = manager_of_class(class_) 

420 if not class_manager.is_mapped: 

421 return None 

422 mapper = class_manager.mapper 

423 except exc.NO_STATE: 

424 return None 

425 else: 

426 if configure: 

427 mapper._check_configure() 

428 return mapper 

429 

430 

431def class_mapper(class_, configure=True): 

432 """Given a class, return the primary :class:`_orm.Mapper` associated 

433 with the key. 

434 

435 Raises :exc:`.UnmappedClassError` if no mapping is configured 

436 on the given class, or :exc:`.ArgumentError` if a non-class 

437 object is passed. 

438 

439 Equivalent functionality is available via the :func:`_sa.inspect` 

440 function as:: 

441 

442 inspect(some_mapped_class) 

443 

444 Using the inspection system will raise 

445 :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped. 

446 

447 """ 

448 mapper = _inspect_mapped_class(class_, configure=configure) 

449 if mapper is None: 

450 if not isinstance(class_, type): 

451 raise sa_exc.ArgumentError( 

452 "Class object expected, got '%r'." % (class_,) 

453 ) 

454 raise exc.UnmappedClassError(class_) 

455 else: 

456 return mapper 

457 

458 

459class InspectionAttr(object): 

460 """A base class applied to all ORM objects that can be returned 

461 by the :func:`_sa.inspect` function. 

462 

463 The attributes defined here allow the usage of simple boolean 

464 checks to test basic facts about the object returned. 

465 

466 While the boolean checks here are basically the same as using 

467 the Python isinstance() function, the flags here can be used without 

468 the need to import all of these classes, and also such that 

469 the SQLAlchemy class system can change while leaving the flags 

470 here intact for forwards-compatibility. 

471 

472 """ 

473 

474 __slots__ = () 

475 

476 is_selectable = False 

477 """Return True if this object is an instance of 

478 :class:`_expression.Selectable`.""" 

479 

480 is_aliased_class = False 

481 """True if this object is an instance of :class:`.AliasedClass`.""" 

482 

483 is_instance = False 

484 """True if this object is an instance of :class:`.InstanceState`.""" 

485 

486 is_mapper = False 

487 """True if this object is an instance of :class:`_orm.Mapper`.""" 

488 

489 is_bundle = False 

490 """True if this object is an instance of :class:`.Bundle`.""" 

491 

492 is_property = False 

493 """True if this object is an instance of :class:`.MapperProperty`.""" 

494 

495 is_attribute = False 

496 """True if this object is a Python :term:`descriptor`. 

497 

498 This can refer to one of many types. Usually a 

499 :class:`.QueryableAttribute` which handles attributes events on behalf 

500 of a :class:`.MapperProperty`. But can also be an extension type 

501 such as :class:`.AssociationProxy` or :class:`.hybrid_property`. 

502 The :attr:`.InspectionAttr.extension_type` will refer to a constant 

503 identifying the specific subtype. 

504 

505 .. seealso:: 

506 

507 :attr:`_orm.Mapper.all_orm_descriptors` 

508 

509 """ 

510 

511 _is_internal_proxy = False 

512 """True if this object is an internal proxy object. 

513 

514 .. versionadded:: 1.2.12 

515 

516 """ 

517 

518 is_clause_element = False 

519 """True if this object is an instance of 

520 :class:`_expression.ClauseElement`.""" 

521 

522 extension_type = NOT_EXTENSION 

523 """The extension type, if any. 

524 Defaults to :data:`.interfaces.NOT_EXTENSION` 

525 

526 .. seealso:: 

527 

528 :data:`.HYBRID_METHOD` 

529 

530 :data:`.HYBRID_PROPERTY` 

531 

532 :data:`.ASSOCIATION_PROXY` 

533 

534 """ 

535 

536 

537class InspectionAttrInfo(InspectionAttr): 

538 """Adds the ``.info`` attribute to :class:`.InspectionAttr`. 

539 

540 The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo` 

541 is that the former is compatible as a mixin for classes that specify 

542 ``__slots__``; this is essentially an implementation artifact. 

543 

544 """ 

545 

546 @util.memoized_property 

547 def info(self): 

548 """Info dictionary associated with the object, allowing user-defined 

549 data to be associated with this :class:`.InspectionAttr`. 

550 

551 The dictionary is generated when first accessed. Alternatively, 

552 it can be specified as a constructor argument to the 

553 :func:`.column_property`, :func:`_orm.relationship`, or 

554 :func:`.composite` 

555 functions. 

556 

557 .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also 

558 available on extension types via the 

559 :attr:`.InspectionAttrInfo.info` attribute, so that it can apply 

560 to a wider variety of ORM and extension constructs. 

561 

562 .. seealso:: 

563 

564 :attr:`.QueryableAttribute.info` 

565 

566 :attr:`.SchemaItem.info` 

567 

568 """ 

569 return {} 

570 

571 

572class _MappedAttribute(object): 

573 """Mixin for attributes which should be replaced by mapper-assigned 

574 attributes. 

575 

576 """ 

577 

578 __slots__ = ()