1# orm/base.py 2# Copyright (C) 2005-2021 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__ = () 579