1# orm/base.py 2# Copyright (C) 2005-2019 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 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 28ATTR_WAS_SET = util.symbol( 29 "ATTR_WAS_SET", 30 """Symbol returned by a loader callable to indicate the 31 retrieved value, or values, were assigned to their attributes 32 on the target object. 33 """, 34) 35 36ATTR_EMPTY = util.symbol( 37 "ATTR_EMPTY", 38 """Symbol used internally to indicate an attribute had no callable.""", 39) 40 41NO_VALUE = util.symbol( 42 "NO_VALUE", 43 """Symbol which may be placed as the 'previous' value of an attribute, 44 indicating no value was loaded for an attribute when it was modified, 45 and flags indicated we were not to load it. 46 """, 47) 48 49NEVER_SET = util.symbol( 50 "NEVER_SET", 51 """Symbol which may be placed as the 'previous' value of an attribute 52 indicating that the attribute had not been assigned to previously. 53 """, 54) 55 56NO_CHANGE = util.symbol( 57 "NO_CHANGE", 58 """No callables or SQL should be emitted on attribute access 59 and no state should change 60 """, 61 canonical=0, 62) 63 64CALLABLES_OK = util.symbol( 65 "CALLABLES_OK", 66 """Loader callables can be fired off if a value 67 is not present. 68 """, 69 canonical=1, 70) 71 72SQL_OK = util.symbol( 73 "SQL_OK", 74 """Loader callables can emit SQL at least on scalar value attributes.""", 75 canonical=2, 76) 77 78RELATED_OBJECT_OK = util.symbol( 79 "RELATED_OBJECT_OK", 80 """Callables can use SQL to load related objects as well 81 as scalar value attributes. 82 """, 83 canonical=4, 84) 85 86INIT_OK = util.symbol( 87 "INIT_OK", 88 """Attributes should be initialized with a blank 89 value (None or an empty collection) upon get, if no other 90 value can be obtained. 91 """, 92 canonical=8, 93) 94 95NON_PERSISTENT_OK = util.symbol( 96 "NON_PERSISTENT_OK", 97 """Callables can be emitted if the parent is not persistent.""", 98 canonical=16, 99) 100 101LOAD_AGAINST_COMMITTED = util.symbol( 102 "LOAD_AGAINST_COMMITTED", 103 """Callables should use committed values as primary/foreign keys during a 104 load. 105 """, 106 canonical=32, 107) 108 109NO_AUTOFLUSH = util.symbol( 110 "NO_AUTOFLUSH", 111 """Loader callables should disable autoflush.""", 112 canonical=64, 113) 114 115# pre-packaged sets of flags used as inputs 116PASSIVE_OFF = util.symbol( 117 "PASSIVE_OFF", 118 "Callables can be emitted in all cases.", 119 canonical=( 120 RELATED_OBJECT_OK | NON_PERSISTENT_OK | INIT_OK | CALLABLES_OK | SQL_OK 121 ), 122) 123PASSIVE_RETURN_NEVER_SET = util.symbol( 124 "PASSIVE_RETURN_NEVER_SET", 125 """PASSIVE_OFF ^ INIT_OK""", 126 canonical=PASSIVE_OFF ^ INIT_OK, 127) 128PASSIVE_NO_INITIALIZE = util.symbol( 129 "PASSIVE_NO_INITIALIZE", 130 "PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK", 131 canonical=PASSIVE_RETURN_NEVER_SET ^ CALLABLES_OK, 132) 133PASSIVE_NO_FETCH = util.symbol( 134 "PASSIVE_NO_FETCH", "PASSIVE_OFF ^ SQL_OK", canonical=PASSIVE_OFF ^ SQL_OK 135) 136PASSIVE_NO_FETCH_RELATED = util.symbol( 137 "PASSIVE_NO_FETCH_RELATED", 138 "PASSIVE_OFF ^ RELATED_OBJECT_OK", 139 canonical=PASSIVE_OFF ^ RELATED_OBJECT_OK, 140) 141PASSIVE_ONLY_PERSISTENT = util.symbol( 142 "PASSIVE_ONLY_PERSISTENT", 143 "PASSIVE_OFF ^ NON_PERSISTENT_OK", 144 canonical=PASSIVE_OFF ^ NON_PERSISTENT_OK, 145) 146 147DEFAULT_MANAGER_ATTR = "_sa_class_manager" 148DEFAULT_STATE_ATTR = "_sa_instance_state" 149_INSTRUMENTOR = ("mapper", "instrumentor") 150 151EXT_CONTINUE = util.symbol("EXT_CONTINUE") 152EXT_STOP = util.symbol("EXT_STOP") 153 154ONETOMANY = util.symbol( 155 "ONETOMANY", 156 """Indicates the one-to-many direction for a :func:`.relationship`. 157 158 This symbol is typically used by the internals but may be exposed within 159 certain API features. 160 161 """, 162) 163 164MANYTOONE = util.symbol( 165 "MANYTOONE", 166 """Indicates the many-to-one direction for a :func:`.relationship`. 167 168 This symbol is typically used by the internals but may be exposed within 169 certain API features. 170 171 """, 172) 173 174MANYTOMANY = util.symbol( 175 "MANYTOMANY", 176 """Indicates the many-to-many direction for a :func:`.relationship`. 177 178 This symbol is typically used by the internals but may be exposed within 179 certain API features. 180 181 """, 182) 183 184NOT_EXTENSION = util.symbol( 185 "NOT_EXTENSION", 186 """Symbol indicating an :class:`InspectionAttr` that's 187 not part of sqlalchemy.ext. 188 189 Is assigned to the :attr:`.InspectionAttr.extension_type` 190 attribute. 191 192 """, 193) 194 195_never_set = frozenset([NEVER_SET]) 196 197_none_set = frozenset([None, NEVER_SET, PASSIVE_NO_RESULT]) 198 199_SET_DEFERRED_EXPIRED = util.symbol("SET_DEFERRED_EXPIRED") 200 201_DEFER_FOR_STATE = util.symbol("DEFER_FOR_STATE") 202 203 204def _generative(*assertions): 205 """Mark a method as generative, e.g. method-chained.""" 206 207 @util.decorator 208 def generate(fn, *args, **kw): 209 self = args[0]._clone() 210 for assertion in assertions: 211 assertion(self, fn.__name__) 212 fn(self, *args[1:], **kw) 213 return self 214 215 return generate 216 217 218# these can be replaced by sqlalchemy.ext.instrumentation 219# if augmented class instrumentation is enabled. 220def manager_of_class(cls): 221 return cls.__dict__.get(DEFAULT_MANAGER_ATTR, None) 222 223 224instance_state = operator.attrgetter(DEFAULT_STATE_ATTR) 225 226instance_dict = operator.attrgetter("__dict__") 227 228 229def instance_str(instance): 230 """Return a string describing an instance.""" 231 232 return state_str(instance_state(instance)) 233 234 235def state_str(state): 236 """Return a string describing an instance via its InstanceState.""" 237 238 if state is None: 239 return "None" 240 else: 241 return "<%s at 0x%x>" % (state.class_.__name__, id(state.obj())) 242 243 244def state_class_str(state): 245 """Return a string describing an instance's class via its 246 InstanceState. 247 """ 248 249 if state is None: 250 return "None" 251 else: 252 return "<%s>" % (state.class_.__name__,) 253 254 255def attribute_str(instance, attribute): 256 return instance_str(instance) + "." + attribute 257 258 259def state_attribute_str(state, attribute): 260 return state_str(state) + "." + attribute 261 262 263def object_mapper(instance): 264 """Given an object, return the primary Mapper associated with the object 265 instance. 266 267 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError` 268 if no mapping is configured. 269 270 This function is available via the inspection system as:: 271 272 inspect(instance).mapper 273 274 Using the inspection system will raise 275 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is 276 not part of a mapping. 277 278 """ 279 return object_state(instance).mapper 280 281 282def object_state(instance): 283 """Given an object, return the :class:`.InstanceState` 284 associated with the object. 285 286 Raises :class:`sqlalchemy.orm.exc.UnmappedInstanceError` 287 if no mapping is configured. 288 289 Equivalent functionality is available via the :func:`.inspect` 290 function as:: 291 292 inspect(instance) 293 294 Using the inspection system will raise 295 :class:`sqlalchemy.exc.NoInspectionAvailable` if the instance is 296 not part of a mapping. 297 298 """ 299 state = _inspect_mapped_object(instance) 300 if state is None: 301 raise exc.UnmappedInstanceError(instance) 302 else: 303 return state 304 305 306@inspection._inspects(object) 307def _inspect_mapped_object(instance): 308 try: 309 return instance_state(instance) 310 # TODO: whats the py-2/3 syntax to catch two 311 # different kinds of exceptions at once ? 312 except exc.UnmappedClassError: 313 return None 314 except exc.NO_STATE: 315 return None 316 317 318def _class_to_mapper(class_or_mapper): 319 insp = inspection.inspect(class_or_mapper, False) 320 if insp is not None: 321 return insp.mapper 322 else: 323 raise exc.UnmappedClassError(class_or_mapper) 324 325 326def _mapper_or_none(entity): 327 """Return the :class:`.Mapper` for the given class or None if the 328 class is not mapped. 329 """ 330 331 insp = inspection.inspect(entity, False) 332 if insp is not None: 333 return insp.mapper 334 else: 335 return None 336 337 338def _is_mapped_class(entity): 339 """Return True if the given object is a mapped class, 340 :class:`.Mapper`, or :class:`.AliasedClass`. 341 """ 342 343 insp = inspection.inspect(entity, False) 344 return ( 345 insp is not None 346 and not insp.is_clause_element 347 and (insp.is_mapper or insp.is_aliased_class) 348 ) 349 350 351def _attr_as_key(attr): 352 if hasattr(attr, "key"): 353 return attr.key 354 else: 355 return expression._column_as_key(attr) 356 357 358def _orm_columns(entity): 359 insp = inspection.inspect(entity, False) 360 if hasattr(insp, "selectable") and hasattr(insp.selectable, "c"): 361 return [c for c in insp.selectable.c] 362 else: 363 return [entity] 364 365 366def _is_aliased_class(entity): 367 insp = inspection.inspect(entity, False) 368 return insp is not None and getattr(insp, "is_aliased_class", False) 369 370 371def _entity_descriptor(entity, key): 372 """Return a class attribute given an entity and string name. 373 374 May return :class:`.InstrumentedAttribute` or user-defined 375 attribute. 376 377 """ 378 insp = inspection.inspect(entity) 379 if insp.is_selectable: 380 description = entity 381 entity = insp.c 382 elif insp.is_aliased_class: 383 entity = insp.entity 384 description = entity 385 elif hasattr(insp, "mapper"): 386 description = entity = insp.mapper.class_ 387 else: 388 description = entity 389 390 try: 391 return getattr(entity, key) 392 except AttributeError: 393 raise sa_exc.InvalidRequestError( 394 "Entity '%s' has no property '%s'" % (description, key) 395 ) 396 397 398_state_mapper = util.dottedgetter("manager.mapper") 399 400 401@inspection._inspects(type) 402def _inspect_mapped_class(class_, configure=False): 403 try: 404 class_manager = manager_of_class(class_) 405 if not class_manager.is_mapped: 406 return None 407 mapper = class_manager.mapper 408 except exc.NO_STATE: 409 return None 410 else: 411 if configure and mapper._new_mappers: 412 mapper._configure_all() 413 return mapper 414 415 416def class_mapper(class_, configure=True): 417 """Given a class, return the primary :class:`.Mapper` associated 418 with the key. 419 420 Raises :exc:`.UnmappedClassError` if no mapping is configured 421 on the given class, or :exc:`.ArgumentError` if a non-class 422 object is passed. 423 424 Equivalent functionality is available via the :func:`.inspect` 425 function as:: 426 427 inspect(some_mapped_class) 428 429 Using the inspection system will raise 430 :class:`sqlalchemy.exc.NoInspectionAvailable` if the class is not mapped. 431 432 """ 433 mapper = _inspect_mapped_class(class_, configure=configure) 434 if mapper is None: 435 if not isinstance(class_, type): 436 raise sa_exc.ArgumentError( 437 "Class object expected, got '%r'." % (class_,) 438 ) 439 raise exc.UnmappedClassError(class_) 440 else: 441 return mapper 442 443 444class InspectionAttr(object): 445 """A base class applied to all ORM objects that can be returned 446 by the :func:`.inspect` function. 447 448 The attributes defined here allow the usage of simple boolean 449 checks to test basic facts about the object returned. 450 451 While the boolean checks here are basically the same as using 452 the Python isinstance() function, the flags here can be used without 453 the need to import all of these classes, and also such that 454 the SQLAlchemy class system can change while leaving the flags 455 here intact for forwards-compatibility. 456 457 """ 458 459 __slots__ = () 460 461 is_selectable = False 462 """Return True if this object is an instance of :class:`.Selectable`.""" 463 464 is_aliased_class = False 465 """True if this object is an instance of :class:`.AliasedClass`.""" 466 467 is_instance = False 468 """True if this object is an instance of :class:`.InstanceState`.""" 469 470 is_mapper = False 471 """True if this object is an instance of :class:`.Mapper`.""" 472 473 is_property = False 474 """True if this object is an instance of :class:`.MapperProperty`.""" 475 476 is_attribute = False 477 """True if this object is a Python :term:`descriptor`. 478 479 This can refer to one of many types. Usually a 480 :class:`.QueryableAttribute` which handles attributes events on behalf 481 of a :class:`.MapperProperty`. But can also be an extension type 482 such as :class:`.AssociationProxy` or :class:`.hybrid_property`. 483 The :attr:`.InspectionAttr.extension_type` will refer to a constant 484 identifying the specific subtype. 485 486 .. seealso:: 487 488 :attr:`.Mapper.all_orm_descriptors` 489 490 """ 491 492 _is_internal_proxy = False 493 """True if this object is an internal proxy object. 494 495 .. versionadded:: 1.2.12 496 497 """ 498 499 is_clause_element = False 500 """True if this object is an instance of :class:`.ClauseElement`.""" 501 502 extension_type = NOT_EXTENSION 503 """The extension type, if any. 504 Defaults to :data:`.interfaces.NOT_EXTENSION` 505 506 .. seealso:: 507 508 :data:`.HYBRID_METHOD` 509 510 :data:`.HYBRID_PROPERTY` 511 512 :data:`.ASSOCIATION_PROXY` 513 514 """ 515 516 517class InspectionAttrInfo(InspectionAttr): 518 """Adds the ``.info`` attribute to :class:`.InspectionAttr`. 519 520 The rationale for :class:`.InspectionAttr` vs. :class:`.InspectionAttrInfo` 521 is that the former is compatible as a mixin for classes that specify 522 ``__slots__``; this is essentially an implementation artifact. 523 524 """ 525 526 @util.memoized_property 527 def info(self): 528 """Info dictionary associated with the object, allowing user-defined 529 data to be associated with this :class:`.InspectionAttr`. 530 531 The dictionary is generated when first accessed. Alternatively, 532 it can be specified as a constructor argument to the 533 :func:`.column_property`, :func:`.relationship`, or :func:`.composite` 534 functions. 535 536 .. versionchanged:: 1.0.0 :attr:`.MapperProperty.info` is also 537 available on extension types via the 538 :attr:`.InspectionAttrInfo.info` attribute, so that it can apply 539 to a wider variety of ORM and extension constructs. 540 541 .. seealso:: 542 543 :attr:`.QueryableAttribute.info` 544 545 :attr:`.SchemaItem.info` 546 547 """ 548 return {} 549 550 551class _MappedAttribute(object): 552 """Mixin for attributes which should be replaced by mapper-assigned 553 attributes. 554 555 """ 556 557 __slots__ = () 558