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/base.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 

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 

18from ..sql import expression 

19 

20 

21PASSIVE_NO_RESULT = util.symbol( 

22 "PASSIVE_NO_RESULT", 

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

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

25 on loader callable flags. 

26 """, 

27) 

28 

29PASSIVE_CLASS_MISMATCH = util.symbol( 

30 "PASSIVE_CLASS_MISMATCH", 

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

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

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

34) 

35 

36ATTR_WAS_SET = util.symbol( 

37 "ATTR_WAS_SET", 

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

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

40 on the target object. 

41 """, 

42) 

43 

44ATTR_EMPTY = util.symbol( 

45 "ATTR_EMPTY", 

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

47) 

48 

49NO_VALUE = util.symbol( 

50 "NO_VALUE", 

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

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

53 and flags indicated we were not to load it. 

54 """, 

55) 

56 

57NEVER_SET = util.symbol( 

58 "NEVER_SET", 

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

60 indicating that the attribute had not been assigned to previously. 

61 """, 

62) 

63 

64NO_CHANGE = util.symbol( 

65 "NO_CHANGE", 

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

67 and no state should change 

68 """, 

69 canonical=0, 

70) 

71 

72CALLABLES_OK = util.symbol( 

73 "CALLABLES_OK", 

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

75 is not present. 

76 """, 

77 canonical=1, 

78) 

79 

80SQL_OK = util.symbol( 

81 "SQL_OK", 

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

83 canonical=2, 

84) 

85 

86RELATED_OBJECT_OK = util.symbol( 

87 "RELATED_OBJECT_OK", 

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

89 as scalar value attributes. 

90 """, 

91 canonical=4, 

92) 

93 

94INIT_OK = util.symbol( 

95 "INIT_OK", 

96 """Attributes should be initialized with a blank 

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

98 value can be obtained. 

99 """, 

100 canonical=8, 

101) 

102 

103NON_PERSISTENT_OK = util.symbol( 

104 "NON_PERSISTENT_OK", 

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

106 canonical=16, 

107) 

108 

109LOAD_AGAINST_COMMITTED = util.symbol( 

110 "LOAD_AGAINST_COMMITTED", 

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

112 load. 

113 """, 

114 canonical=32, 

115) 

116 

117NO_AUTOFLUSH = util.symbol( 

118 "NO_AUTOFLUSH", 

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

120 canonical=64, 

121) 

122 

123NO_RAISE = util.symbol( 

124 "NO_RAISE", 

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

126 canonical=128, 

127) 

128 

129# pre-packaged sets of flags used as inputs 

130PASSIVE_OFF = util.symbol( 

131 "PASSIVE_OFF", 

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

133 canonical=( 

134 RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK 

135 ), 

136) 

137PASSIVE_RETURN_NEVER_SET = util.symbol( 

138 "PASSIVE_RETURN_NEVER_SET", 

139 """PASSIVE_OFF ^ INIT_OK""", 

140 canonical=PASSIVE_OFF ^ INIT_OK, 

141) 

142PASSIVE_NO_INITIALIZE = util.symbol( 

143 "PASSIVE_NO_INITIALIZE", 

144 "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK", 

145 canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK, 

146) 

147PASSIVE_NO_FETCH = util.symbol( 

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

149) 

150PASSIVE_NO_FETCH_RELATED = util.symbol( 

151 "PASSIVE_NO_FETCH_RELATED", 

152 "PASSIVE_OFF ^ RELATED_OBJECT_OK", 

153 canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK, 

154) 

155PASSIVE_ONLY_PERSISTENT = util.symbol( 

156 "PASSIVE_ONLY_PERSISTENT", 

157 "PASSIVE_OFF ^ NON_PERSISTENT_OK", 

158 canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK, 

159) 

160 

161DEFAULT_MANAGER_ATTR = "_sa_class_manager" 

162DEFAULT_STATE_ATTR = "_sa_instance_state" 

163_INSTRUMENTOR = ("mapper", "instrumentor") 

164 

165EXT_CONTINUE = util.symbol("EXT_CONTINUE") 

166EXT_STOP = util.symbol("EXT_STOP") 

167EXT_SKIP = util.symbol("EXT_SKIP") 

168 

169ONETOMANY = util.symbol( 

170 "ONETOMANY", 

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

172 

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

174 certain API features. 

175 

176 """, 

177) 

178 

179MANYTOONE = util.symbol( 

180 "MANYTOONE", 

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

182 

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

184 certain API features. 

185 

186 """, 

187) 

188 

189MANYTOMANY = util.symbol( 

190 "MANYTOMANY", 

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

192 

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

194 certain API features. 

195 

196 """, 

197) 

198 

199NOT_EXTENSION = util.symbol( 

200 "NOT_EXTENSION", 

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

202 not part of sqlalchemy.ext. 

203 

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

205 attribute. 

206 

207 """, 

208) 

209 

210_never_set = frozenset([NEVER_SET]) 

211 

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

213 

214_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED") 

215 

216_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE") 

217 

218 

219def _generative(*assertions): 

220 """Mark a method as generative, e.g. method-chained.""" 

221 

222 @util.decorator 

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

224 self = args[0]._clone() 

225 for assertion in assertions: 

226 assertion(self, fn.__name__) 

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

228 return self 

229 

230 return generate 

231 

232 

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

234# if augmented class instrumentation is enabled. 

235def manager_of_class(cls): 

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

237 

238 

239instance_state = operator.attrgetter(DEFAULT_STATE_ATTR) 

240 

241instance_dict = operator.attrgetter("__dict__") 

242 

243 

244def instance_str(instance): 

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

246 

247 return state_str(instance_state(instance)) 

248 

249 

250def state_str(state): 

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

252 

253 if state is None: 

254 return "None" 

255 else: 

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

257 

258 

259def state_class_str(state): 

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

261 InstanceState. 

262 """ 

263 

264 if state is None: 

265 return "None" 

266 else: 

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

268 

269 

270def attribute_str(instance, attribute): 

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

272 

273 

274def state_attribute_str(state, attribute): 

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

276 

277 

278def object_mapper(instance): 

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

280 instance. 

281 

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

283 if no mapping is configured. 

284 

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

286 

287 inspect(instance).mapper 

288 

289 Using the inspection system will raise 

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

291 not part of a mapping. 

292 

293 """ 

294 return object_state(instance).mapper 

295 

296 

297def object_state(instance): 

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

299 associated with the object. 

300 

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

302 if no mapping is configured. 

303 

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

305 function as:: 

306 

307 inspect(instance) 

308 

309 Using the inspection system will raise 

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

311 not part of a mapping. 

312 

313 """ 

314 state = _inspect_mapped_object(instance) 

315 if state is None: 

316 raise exc.UnmappedInstanceError(instance) 

317 else: 

318 return state 

319 

320 

321@inspection._inspects(object) 

322def _inspect_mapped_object(instance): 

323 try: 

324 return instance_state(instance) 

325 # TODO: whats the py-2/3 syntax to catch two 

326 # different kinds of exceptions at once ? 

327 except exc.UnmappedClassError: 

328 return None 

329 except exc.NO_STATE: 

330 return None 

331 

332 

333def _class_to_mapper(class_or_mapper): 

334 insp = inspection.inspect(class_or_mapper, False) 

335 if insp is not None: 

336 return insp.mapper 

337 else: 

338 raise exc.UnmappedClassError(class_or_mapper) 

339 

340 

341def _mapper_or_none(entity): 

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

343 class is not mapped. 

344 """ 

345 

346 insp = inspection.inspect(entity, False) 

347 if insp is not None: 

348 return insp.mapper 

349 else: 

350 return None 

351 

352 

353def _is_mapped_class(entity): 

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

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

356 """ 

357 

358 insp = inspection.inspect(entity, False) 

359 return ( 

360 insp is not None 

361 and not insp.is_clause_element 

362 and (insp.is_mapper or insp.is_aliased_class) 

363 ) 

364 

365 

366def _attr_as_key(attr): 

367 if hasattr(attr, "key"): 

368 return attr.key 

369 else: 

370 return expression._column_as_key(attr) 

371 

372 

373def _orm_columns(entity): 

374 insp = inspection.inspect(entity, False) 

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

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

377 else: 

378 return [entity] 

379 

380 

381def _is_aliased_class(entity): 

382 insp = inspection.inspect(entity, False) 

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

384 

385 

386def _entity_descriptor(entity, key): 

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

388 

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

390 attribute. 

391 

392 """ 

393 insp = inspection.inspect(entity) 

394 if insp.is_selectable: 

395 description = entity 

396 entity = insp.c 

397 elif insp.is_aliased_class: 

398 entity = insp.entity 

399 description = entity 

400 elif hasattr(insp, "mapper"): 

401 description = entity = insp.mapper.class_ 

402 else: 

403 description = entity 

404 

405 try: 

406 return getattr(entity, key) 

407 except AttributeError as err: 

408 util.raise_( 

409 sa_exc.InvalidRequestError( 

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

411 ), 

412 replace_context=err, 

413 ) 

414 

415 

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

417 

418 

419@inspection._inspects(type) 

420def _inspect_mapped_class(class_, configure=False): 

421 try: 

422 class_manager = manager_of_class(class_) 

423 if not class_manager.is_mapped: 

424 return None 

425 mapper = class_manager.mapper 

426 except exc.NO_STATE: 

427 return None 

428 else: 

429 if configure and mapper._new_mappers: 

430 mapper._configure_all() 

431 return mapper 

432 

433 

434def class_mapper(class_, configure=True): 

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

436 with the key. 

437 

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

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

440 object is passed. 

441 

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

443 function as:: 

444 

445 inspect(some_mapped_class) 

446 

447 Using the inspection system will raise 

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

449 

450 """ 

451 mapper = _inspect_mapped_class(class_, configure=configure) 

452 if mapper is None: 

453 if not isinstance(class_, type): 

454 raise sa_exc.ArgumentError( 

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

456 ) 

457 raise exc.UnmappedClassError(class_) 

458 else: 

459 return mapper 

460 

461 

462class InspectionAttr(object): 

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

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

465 

466 The attributes defined here allow the usage of simple boolean 

467 checks to test basic facts about the object returned. 

468 

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

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

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

472 the SQLAlchemy class system can change while leaving the flags 

473 here intact for forwards-compatibility. 

474 

475 """ 

476 

477 __slots__ = () 

478 

479 is_selectable = False 

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

481 """:class:`expression.Selectable`.""" 

482 

483 is_aliased_class = False 

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

485 

486 is_instance = False 

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

488 

489 is_mapper = False 

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

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__ = ()