You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
6.4KB

  1. # orm/exc.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. """SQLAlchemy ORM exceptions."""
  8. from .. import exc as sa_exc
  9. from .. import util
  10. from ..exc import MultipleResultsFound # noqa
  11. from ..exc import NoResultFound # noqa
  12. NO_STATE = (AttributeError, KeyError)
  13. """Exception types that may be raised by instrumentation implementations."""
  14. class StaleDataError(sa_exc.SQLAlchemyError):
  15. """An operation encountered database state that is unaccounted for.
  16. Conditions which cause this to happen include:
  17. * A flush may have attempted to update or delete rows
  18. and an unexpected number of rows were matched during
  19. the UPDATE or DELETE statement. Note that when
  20. version_id_col is used, rows in UPDATE or DELETE statements
  21. are also matched against the current known version
  22. identifier.
  23. * A mapped object with version_id_col was refreshed,
  24. and the version number coming back from the database does
  25. not match that of the object itself.
  26. * A object is detached from its parent object, however
  27. the object was previously attached to a different parent
  28. identity which was garbage collected, and a decision
  29. cannot be made if the new parent was really the most
  30. recent "parent".
  31. """
  32. ConcurrentModificationError = StaleDataError
  33. class FlushError(sa_exc.SQLAlchemyError):
  34. """A invalid condition was detected during flush()."""
  35. class UnmappedError(sa_exc.InvalidRequestError):
  36. """Base for exceptions that involve expected mappings not present."""
  37. class ObjectDereferencedError(sa_exc.SQLAlchemyError):
  38. """An operation cannot complete due to an object being garbage
  39. collected.
  40. """
  41. class DetachedInstanceError(sa_exc.SQLAlchemyError):
  42. """An attempt to access unloaded attributes on a
  43. mapped instance that is detached."""
  44. code = "bhk3"
  45. class UnmappedInstanceError(UnmappedError):
  46. """An mapping operation was requested for an unknown instance."""
  47. @util.preload_module("sqlalchemy.orm.base")
  48. def __init__(self, obj, msg=None):
  49. base = util.preloaded.orm_base
  50. if not msg:
  51. try:
  52. base.class_mapper(type(obj))
  53. name = _safe_cls_name(type(obj))
  54. msg = (
  55. "Class %r is mapped, but this instance lacks "
  56. "instrumentation. This occurs when the instance "
  57. "is created before sqlalchemy.orm.mapper(%s) "
  58. "was called." % (name, name)
  59. )
  60. except UnmappedClassError:
  61. msg = _default_unmapped(type(obj))
  62. if isinstance(obj, type):
  63. msg += (
  64. "; was a class (%s) supplied where an instance was "
  65. "required?" % _safe_cls_name(obj)
  66. )
  67. UnmappedError.__init__(self, msg)
  68. def __reduce__(self):
  69. return self.__class__, (None, self.args[0])
  70. class UnmappedClassError(UnmappedError):
  71. """An mapping operation was requested for an unknown class."""
  72. def __init__(self, cls, msg=None):
  73. if not msg:
  74. msg = _default_unmapped(cls)
  75. UnmappedError.__init__(self, msg)
  76. def __reduce__(self):
  77. return self.__class__, (None, self.args[0])
  78. class ObjectDeletedError(sa_exc.InvalidRequestError):
  79. """A refresh operation failed to retrieve the database
  80. row corresponding to an object's known primary key identity.
  81. A refresh operation proceeds when an expired attribute is
  82. accessed on an object, or when :meth:`_query.Query.get` is
  83. used to retrieve an object which is, upon retrieval, detected
  84. as expired. A SELECT is emitted for the target row
  85. based on primary key; if no row is returned, this
  86. exception is raised.
  87. The true meaning of this exception is simply that
  88. no row exists for the primary key identifier associated
  89. with a persistent object. The row may have been
  90. deleted, or in some cases the primary key updated
  91. to a new value, outside of the ORM's management of the target
  92. object.
  93. """
  94. @util.preload_module("sqlalchemy.orm.base")
  95. def __init__(self, state, msg=None):
  96. base = util.preloaded.orm_base
  97. if not msg:
  98. msg = (
  99. "Instance '%s' has been deleted, or its "
  100. "row is otherwise not present." % base.state_str(state)
  101. )
  102. sa_exc.InvalidRequestError.__init__(self, msg)
  103. def __reduce__(self):
  104. return self.__class__, (None, self.args[0])
  105. class UnmappedColumnError(sa_exc.InvalidRequestError):
  106. """Mapping operation was requested on an unknown column."""
  107. class LoaderStrategyException(sa_exc.InvalidRequestError):
  108. """A loader strategy for an attribute does not exist."""
  109. def __init__(
  110. self,
  111. applied_to_property_type,
  112. requesting_property,
  113. applies_to,
  114. actual_strategy_type,
  115. strategy_key,
  116. ):
  117. if actual_strategy_type is None:
  118. sa_exc.InvalidRequestError.__init__(
  119. self,
  120. "Can't find strategy %s for %s"
  121. % (strategy_key, requesting_property),
  122. )
  123. else:
  124. sa_exc.InvalidRequestError.__init__(
  125. self,
  126. 'Can\'t apply "%s" strategy to property "%s", '
  127. 'which is a "%s"; this loader strategy is intended '
  128. 'to be used with a "%s".'
  129. % (
  130. util.clsname_as_plain_name(actual_strategy_type),
  131. requesting_property,
  132. util.clsname_as_plain_name(applied_to_property_type),
  133. util.clsname_as_plain_name(applies_to),
  134. ),
  135. )
  136. def _safe_cls_name(cls):
  137. try:
  138. cls_name = ".".join((cls.__module__, cls.__name__))
  139. except AttributeError:
  140. cls_name = getattr(cls, "__name__", None)
  141. if cls_name is None:
  142. cls_name = repr(cls)
  143. return cls_name
  144. @util.preload_module("sqlalchemy.orm.base")
  145. def _default_unmapped(cls):
  146. base = util.preloaded.orm_base
  147. try:
  148. mappers = base.manager_of_class(cls).mappers
  149. except (TypeError,) + NO_STATE:
  150. mappers = {}
  151. name = _safe_cls_name(cls)
  152. if not mappers:
  153. return "Class '%s' is not mapped" % name