1# sqlalchemy/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: https://www.opensource.org/licenses/mit-license.php 7 8"""Exceptions used with SQLAlchemy. 9 10The base exception class is :exc:`.SQLAlchemyError`. Exceptions which are 11raised as a result of DBAPI exceptions are all subclasses of 12:exc:`.DBAPIError`. 13 14""" 15 16from .util import _preloaded 17from .util import compat 18 19_version_token = None 20 21 22class HasDescriptionCode(object): 23 """helper which adds 'code' as an attribute and '_code_str' as a method""" 24 25 code = None 26 27 def __init__(self, *arg, **kw): 28 code = kw.pop("code", None) 29 if code is not None: 30 self.code = code 31 super(HasDescriptionCode, self).__init__(*arg, **kw) 32 33 def _code_str(self): 34 if not self.code: 35 return "" 36 else: 37 return ( 38 "(Background on this error at: " 39 "https://sqlalche.me/e/%s/%s)" 40 % ( 41 _version_token, 42 self.code, 43 ) 44 ) 45 46 def __str__(self): 47 message = super(HasDescriptionCode, self).__str__() 48 if self.code: 49 message = "%s %s" % (message, self._code_str()) 50 return message 51 52 53class SQLAlchemyError(HasDescriptionCode, Exception): 54 """Generic error class.""" 55 56 def _message(self, as_unicode=compat.py3k): 57 # rules: 58 # 59 # 1. under py2k, for __str__ return single string arg as it was 60 # given without converting to unicode. for __unicode__ 61 # do a conversion but check that it's not unicode already just in 62 # case 63 # 64 # 2. under py3k, single arg string will usually be a unicode 65 # object, but since __str__() must return unicode, check for 66 # bytestring just in case 67 # 68 # 3. for multiple self.args, this is not a case in current 69 # SQLAlchemy though this is happening in at least one known external 70 # library, call str() which does a repr(). 71 # 72 if len(self.args) == 1: 73 text = self.args[0] 74 75 if as_unicode and isinstance(text, compat.binary_types): 76 text = compat.decode_backslashreplace(text, "utf-8") 77 # This is for when the argument is not a string of any sort. 78 # Otherwise, converting this exception to string would fail for 79 # non-string arguments. 80 elif compat.py3k or not as_unicode: 81 text = str(text) 82 else: 83 text = compat.text_type(text) 84 85 return text 86 else: 87 # this is not a normal case within SQLAlchemy but is here for 88 # compatibility with Exception.args - the str() comes out as 89 # a repr() of the tuple 90 return str(self.args) 91 92 def _sql_message(self, as_unicode): 93 message = self._message(as_unicode) 94 95 if self.code: 96 message = "%s %s" % (message, self._code_str()) 97 98 return message 99 100 def __str__(self): 101 return self._sql_message(compat.py3k) 102 103 def __unicode__(self): 104 return self._sql_message(as_unicode=True) 105 106 107class ArgumentError(SQLAlchemyError): 108 """Raised when an invalid or conflicting function argument is supplied. 109 110 This error generally corresponds to construction time state errors. 111 112 """ 113 114 115class ObjectNotExecutableError(ArgumentError): 116 """Raised when an object is passed to .execute() that can't be 117 executed as SQL. 118 119 .. versionadded:: 1.1 120 121 """ 122 123 def __init__(self, target): 124 super(ObjectNotExecutableError, self).__init__( 125 "Not an executable object: %r" % target 126 ) 127 self.target = target 128 129 def __reduce__(self): 130 return self.__class__, (self.target,) 131 132 133class NoSuchModuleError(ArgumentError): 134 """Raised when a dynamically-loaded module (usually a database dialect) 135 of a particular name cannot be located.""" 136 137 138class NoForeignKeysError(ArgumentError): 139 """Raised when no foreign keys can be located between two selectables 140 during a join.""" 141 142 143class AmbiguousForeignKeysError(ArgumentError): 144 """Raised when more than one foreign key matching can be located 145 between two selectables during a join.""" 146 147 148class CircularDependencyError(SQLAlchemyError): 149 """Raised by topological sorts when a circular dependency is detected. 150 151 There are two scenarios where this error occurs: 152 153 * In a Session flush operation, if two objects are mutually dependent 154 on each other, they can not be inserted or deleted via INSERT or 155 DELETE statements alone; an UPDATE will be needed to post-associate 156 or pre-deassociate one of the foreign key constrained values. 157 The ``post_update`` flag described at :ref:`post_update` can resolve 158 this cycle. 159 * In a :attr:`_schema.MetaData.sorted_tables` operation, two 160 :class:`_schema.ForeignKey` 161 or :class:`_schema.ForeignKeyConstraint` objects mutually refer to each 162 other. Apply the ``use_alter=True`` flag to one or both, 163 see :ref:`use_alter`. 164 165 """ 166 167 def __init__(self, message, cycles, edges, msg=None, code=None): 168 if msg is None: 169 message += " (%s)" % ", ".join(repr(s) for s in cycles) 170 else: 171 message = msg 172 SQLAlchemyError.__init__(self, message, code=code) 173 self.cycles = cycles 174 self.edges = edges 175 176 def __reduce__(self): 177 return ( 178 self.__class__, 179 (None, self.cycles, self.edges, self.args[0]), 180 {"code": self.code} if self.code is not None else {}, 181 ) 182 183 184class CompileError(SQLAlchemyError): 185 """Raised when an error occurs during SQL compilation""" 186 187 188class UnsupportedCompilationError(CompileError): 189 """Raised when an operation is not supported by the given compiler. 190 191 .. seealso:: 192 193 :ref:`faq_sql_expression_string` 194 195 :ref:`error_l7de` 196 """ 197 198 code = "l7de" 199 200 def __init__(self, compiler, element_type, message=None): 201 super(UnsupportedCompilationError, self).__init__( 202 "Compiler %r can't render element of type %s%s" 203 % (compiler, element_type, ": %s" % message if message else "") 204 ) 205 self.compiler = compiler 206 self.element_type = element_type 207 self.message = message 208 209 def __reduce__(self): 210 return self.__class__, (self.compiler, self.element_type, self.message) 211 212 213class IdentifierError(SQLAlchemyError): 214 """Raised when a schema name is beyond the max character limit""" 215 216 217class DisconnectionError(SQLAlchemyError): 218 """A disconnect is detected on a raw DB-API connection. 219 220 This error is raised and consumed internally by a connection pool. It can 221 be raised by the :meth:`_events.PoolEvents.checkout` 222 event so that the host pool 223 forces a retry; the exception will be caught three times in a row before 224 the pool gives up and raises :class:`~sqlalchemy.exc.InvalidRequestError` 225 regarding the connection attempt. 226 227 """ 228 229 invalidate_pool = False 230 231 232class InvalidatePoolError(DisconnectionError): 233 """Raised when the connection pool should invalidate all stale connections. 234 235 A subclass of :class:`_exc.DisconnectionError` that indicates that the 236 disconnect situation encountered on the connection probably means the 237 entire pool should be invalidated, as the database has been restarted. 238 239 This exception will be handled otherwise the same way as 240 :class:`_exc.DisconnectionError`, allowing three attempts to reconnect 241 before giving up. 242 243 .. versionadded:: 1.2 244 245 """ 246 247 invalidate_pool = True 248 249 250class TimeoutError(SQLAlchemyError): # noqa 251 """Raised when a connection pool times out on getting a connection.""" 252 253 254class InvalidRequestError(SQLAlchemyError): 255 """SQLAlchemy was asked to do something it can't do. 256 257 This error generally corresponds to runtime state errors. 258 259 """ 260 261 262class NoInspectionAvailable(InvalidRequestError): 263 """A subject passed to :func:`sqlalchemy.inspection.inspect` produced 264 no context for inspection.""" 265 266 267class PendingRollbackError(InvalidRequestError): 268 """A transaction has failed and needs to be rolled back before 269 continuing. 270 271 .. versionadded:: 1.4 272 273 """ 274 275 276class ResourceClosedError(InvalidRequestError): 277 """An operation was requested from a connection, cursor, or other 278 object that's in a closed state.""" 279 280 281class NoSuchColumnError(InvalidRequestError, KeyError): 282 """A nonexistent column is requested from a ``Row``.""" 283 284 285class NoResultFound(InvalidRequestError): 286 """A database result was required but none was found. 287 288 289 .. versionchanged:: 1.4 This exception is now part of the 290 ``sqlalchemy.exc`` module in Core, moved from the ORM. The symbol 291 remains importable from ``sqlalchemy.orm.exc``. 292 293 294 """ 295 296 297class MultipleResultsFound(InvalidRequestError): 298 """A single database result was required but more than one were found. 299 300 .. versionchanged:: 1.4 This exception is now part of the 301 ``sqlalchemy.exc`` module in Core, moved from the ORM. The symbol 302 remains importable from ``sqlalchemy.orm.exc``. 303 304 305 """ 306 307 308class NoReferenceError(InvalidRequestError): 309 """Raised by ``ForeignKey`` to indicate a reference cannot be resolved.""" 310 311 312class AwaitRequired(InvalidRequestError): 313 """Error raised by the async greenlet spawn if no async operation 314 was awaited when it required one. 315 316 """ 317 318 code = "xd1r" 319 320 321class MissingGreenlet(InvalidRequestError): 322 r"""Error raised by the async greenlet await\_ if called while not inside 323 the greenlet spawn context. 324 325 """ 326 327 code = "xd2s" 328 329 330class NoReferencedTableError(NoReferenceError): 331 """Raised by ``ForeignKey`` when the referred ``Table`` cannot be 332 located. 333 334 """ 335 336 def __init__(self, message, tname): 337 NoReferenceError.__init__(self, message) 338 self.table_name = tname 339 340 def __reduce__(self): 341 return self.__class__, (self.args[0], self.table_name) 342 343 344class NoReferencedColumnError(NoReferenceError): 345 """Raised by ``ForeignKey`` when the referred ``Column`` cannot be 346 located. 347 348 """ 349 350 def __init__(self, message, tname, cname): 351 NoReferenceError.__init__(self, message) 352 self.table_name = tname 353 self.column_name = cname 354 355 def __reduce__(self): 356 return ( 357 self.__class__, 358 (self.args[0], self.table_name, self.column_name), 359 ) 360 361 362class NoSuchTableError(InvalidRequestError): 363 """Table does not exist or is not visible to a connection.""" 364 365 366class UnreflectableTableError(InvalidRequestError): 367 """Table exists but can't be reflected for some reason. 368 369 .. versionadded:: 1.2 370 371 """ 372 373 374class UnboundExecutionError(InvalidRequestError): 375 """SQL was attempted without a database connection to execute it on.""" 376 377 378class DontWrapMixin(object): 379 """A mixin class which, when applied to a user-defined Exception class, 380 will not be wrapped inside of :exc:`.StatementError` if the error is 381 emitted within the process of executing a statement. 382 383 E.g.:: 384 385 from sqlalchemy.exc import DontWrapMixin 386 387 class MyCustomException(Exception, DontWrapMixin): 388 pass 389 390 class MySpecialType(TypeDecorator): 391 impl = String 392 393 def process_bind_param(self, value, dialect): 394 if value == 'invalid': 395 raise MyCustomException("invalid!") 396 397 """ 398 399 400class StatementError(SQLAlchemyError): 401 """An error occurred during execution of a SQL statement. 402 403 :class:`StatementError` wraps the exception raised 404 during execution, and features :attr:`.statement` 405 and :attr:`.params` attributes which supply context regarding 406 the specifics of the statement which had an issue. 407 408 The wrapped exception object is available in 409 the :attr:`.orig` attribute. 410 411 """ 412 413 statement = None 414 """The string SQL statement being invoked when this exception occurred.""" 415 416 params = None 417 """The parameter list being used when this exception occurred.""" 418 419 orig = None 420 """The DBAPI exception object.""" 421 422 ismulti = None 423 424 def __init__( 425 self, 426 message, 427 statement, 428 params, 429 orig, 430 hide_parameters=False, 431 code=None, 432 ismulti=None, 433 ): 434 SQLAlchemyError.__init__(self, message, code=code) 435 self.statement = statement 436 self.params = params 437 self.orig = orig 438 self.ismulti = ismulti 439 self.hide_parameters = hide_parameters 440 self.detail = [] 441 442 def add_detail(self, msg): 443 self.detail.append(msg) 444 445 def __reduce__(self): 446 return ( 447 self.__class__, 448 ( 449 self.args[0], 450 self.statement, 451 self.params, 452 self.orig, 453 self.hide_parameters, 454 self.__dict__.get("code"), 455 self.ismulti, 456 ), 457 {"detail": self.detail}, 458 ) 459 460 @_preloaded.preload_module("sqlalchemy.sql.util") 461 def _sql_message(self, as_unicode): 462 util = _preloaded.preloaded.sql_util 463 464 details = [self._message(as_unicode=as_unicode)] 465 if self.statement: 466 if not as_unicode and not compat.py3k: 467 stmt_detail = "[SQL: %s]" % compat.safe_bytestring( 468 self.statement 469 ) 470 else: 471 stmt_detail = "[SQL: %s]" % self.statement 472 details.append(stmt_detail) 473 if self.params: 474 if self.hide_parameters: 475 details.append( 476 "[SQL parameters hidden due to hide_parameters=True]" 477 ) 478 else: 479 params_repr = util._repr_params( 480 self.params, 10, ismulti=self.ismulti 481 ) 482 details.append("[parameters: %r]" % params_repr) 483 code_str = self._code_str() 484 if code_str: 485 details.append(code_str) 486 return "\n".join(["(%s)" % det for det in self.detail] + details) 487 488 489class DBAPIError(StatementError): 490 """Raised when the execution of a database operation fails. 491 492 Wraps exceptions raised by the DB-API underlying the 493 database operation. Driver-specific implementations of the standard 494 DB-API exception types are wrapped by matching sub-types of SQLAlchemy's 495 :class:`DBAPIError` when possible. DB-API's ``Error`` type maps to 496 :class:`DBAPIError` in SQLAlchemy, otherwise the names are identical. Note 497 that there is no guarantee that different DB-API implementations will 498 raise the same exception type for any given error condition. 499 500 :class:`DBAPIError` features :attr:`~.StatementError.statement` 501 and :attr:`~.StatementError.params` attributes which supply context 502 regarding the specifics of the statement which had an issue, for the 503 typical case when the error was raised within the context of 504 emitting a SQL statement. 505 506 The wrapped exception object is available in the 507 :attr:`~.StatementError.orig` attribute. Its type and properties are 508 DB-API implementation specific. 509 510 """ 511 512 code = "dbapi" 513 514 @classmethod 515 def instance( 516 cls, 517 statement, 518 params, 519 orig, 520 dbapi_base_err, 521 hide_parameters=False, 522 connection_invalidated=False, 523 dialect=None, 524 ismulti=None, 525 ): 526 # Don't ever wrap these, just return them directly as if 527 # DBAPIError didn't exist. 528 if ( 529 isinstance(orig, BaseException) and not isinstance(orig, Exception) 530 ) or isinstance(orig, DontWrapMixin): 531 return orig 532 533 if orig is not None: 534 # not a DBAPI error, statement is present. 535 # raise a StatementError 536 if isinstance(orig, SQLAlchemyError) and statement: 537 return StatementError( 538 "(%s.%s) %s" 539 % ( 540 orig.__class__.__module__, 541 orig.__class__.__name__, 542 orig.args[0], 543 ), 544 statement, 545 params, 546 orig, 547 hide_parameters=hide_parameters, 548 code=orig.code, 549 ismulti=ismulti, 550 ) 551 elif not isinstance(orig, dbapi_base_err) and statement: 552 return StatementError( 553 "(%s.%s) %s" 554 % ( 555 orig.__class__.__module__, 556 orig.__class__.__name__, 557 orig, 558 ), 559 statement, 560 params, 561 orig, 562 hide_parameters=hide_parameters, 563 ismulti=ismulti, 564 ) 565 566 glob = globals() 567 for super_ in orig.__class__.__mro__: 568 name = super_.__name__ 569 if dialect: 570 name = dialect.dbapi_exception_translation_map.get( 571 name, name 572 ) 573 if name in glob and issubclass(glob[name], DBAPIError): 574 cls = glob[name] 575 break 576 577 return cls( 578 statement, 579 params, 580 orig, 581 connection_invalidated=connection_invalidated, 582 hide_parameters=hide_parameters, 583 code=cls.code, 584 ismulti=ismulti, 585 ) 586 587 def __reduce__(self): 588 return ( 589 self.__class__, 590 ( 591 self.statement, 592 self.params, 593 self.orig, 594 self.hide_parameters, 595 self.connection_invalidated, 596 self.__dict__.get("code"), 597 self.ismulti, 598 ), 599 {"detail": self.detail}, 600 ) 601 602 def __init__( 603 self, 604 statement, 605 params, 606 orig, 607 hide_parameters=False, 608 connection_invalidated=False, 609 code=None, 610 ismulti=None, 611 ): 612 try: 613 text = str(orig) 614 except Exception as e: 615 text = "Error in str() of DB-API-generated exception: " + str(e) 616 StatementError.__init__( 617 self, 618 "(%s.%s) %s" 619 % (orig.__class__.__module__, orig.__class__.__name__, text), 620 statement, 621 params, 622 orig, 623 hide_parameters, 624 code=code, 625 ismulti=ismulti, 626 ) 627 self.connection_invalidated = connection_invalidated 628 629 630class InterfaceError(DBAPIError): 631 """Wraps a DB-API InterfaceError.""" 632 633 code = "rvf5" 634 635 636class DatabaseError(DBAPIError): 637 """Wraps a DB-API DatabaseError.""" 638 639 code = "4xp6" 640 641 642class DataError(DatabaseError): 643 """Wraps a DB-API DataError.""" 644 645 code = "9h9h" 646 647 648class OperationalError(DatabaseError): 649 """Wraps a DB-API OperationalError.""" 650 651 code = "e3q8" 652 653 654class IntegrityError(DatabaseError): 655 """Wraps a DB-API IntegrityError.""" 656 657 code = "gkpj" 658 659 660class InternalError(DatabaseError): 661 """Wraps a DB-API InternalError.""" 662 663 code = "2j85" 664 665 666class ProgrammingError(DatabaseError): 667 """Wraps a DB-API ProgrammingError.""" 668 669 code = "f405" 670 671 672class NotSupportedError(DatabaseError): 673 """Wraps a DB-API NotSupportedError.""" 674 675 code = "tw8g" 676 677 678# Warnings 679 680 681class SADeprecationWarning(HasDescriptionCode, DeprecationWarning): 682 """Issued for usage of deprecated APIs.""" 683 684 deprecated_since = None 685 "Indicates the version that started raising this deprecation warning" 686 687 688class Base20DeprecationWarning(SADeprecationWarning): 689 """Issued for usage of APIs specifically deprecated or legacy in 690 SQLAlchemy 2.0. 691 692 .. seealso:: 693 694 :ref:`error_b8d9`. 695 696 :ref:`deprecation_20_mode` 697 698 """ 699 700 deprecated_since = "1.4" 701 "Indicates the version that started raising this deprecation warning" 702 703 def __str__(self): 704 return ( 705 super(Base20DeprecationWarning, self).__str__() 706 + " (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)" 707 ) 708 709 710class LegacyAPIWarning(Base20DeprecationWarning): 711 """indicates an API that is in 'legacy' status, a long term deprecation.""" 712 713 714class RemovedIn20Warning(Base20DeprecationWarning): 715 """indicates an API that will be fully removed in SQLAlchemy 2.0.""" 716 717 718class MovedIn20Warning(RemovedIn20Warning): 719 """Subtype of RemovedIn20Warning to indicate an API that moved only.""" 720 721 722class SAPendingDeprecationWarning(PendingDeprecationWarning): 723 """A similar warning as :class:`_exc.SADeprecationWarning`, this warning 724 is not used in modern versions of SQLAlchemy. 725 726 """ 727 728 deprecated_since = None 729 "Indicates the version that started raising this deprecation warning" 730 731 732class SAWarning(HasDescriptionCode, RuntimeWarning): 733 """Issued at runtime.""" 734