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