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