This repository has been archived by the owner on Jun 24, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsqlalchemy.py
86 lines (73 loc) · 3.25 KB
/
sqlalchemy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from typing import Optional
from contextlib import contextmanager
import sqlalchemy as sa
import sqlalchemy.exc
import sqlalchemy.orm.exc
import psycopg2
from apiens.tools.sqlalchemy.postgres.pg_integrity_error import extract_postgres_unique_violation_column_names
from apiens.util.exception import exception_from
from apiens.error import exc
from .exception import convert_unexpected_error
@contextmanager # and a decorator
def converting_sa_errors(*, Model: type, exc=exc, _=exc._):
""" Convert common SqlAlchemy errors into human-friendly Application Errors
SqlAlchemy raises errors that are cryptic to the end-user.
Convert them into something readable.
Raises:
exc.E_NOT_FOUND: when not found
exc.E_NOT_FOUND: when multiple found
exc.E_CONFLICT_DUPLICATE: for unique index violations
"""
try:
yield
except sa.exc.SQLAlchemyError as e:
new_error = convert_sa_error(e, Model=Model, exc=exc, _=_)
raise new_error or e
def convert_sa_error(error: Exception, *, Model: type, exc=exc, _=exc._) -> Optional[exc.BaseApplicationError]:
""" Given a SqlAlchemy exception, convert it to `exc` API exceptions """
# NoResultFound
if isinstance(error, sa.orm.exc.NoResultFound):
e = exc.E_NOT_FOUND.format(
_('Cannot find {object}'),
_('Make sure you have entered a correct URL with a valid id'),
object=Model.__name__,
)
return exception_from(e, error)
# MultipleResultsFound
if isinstance(error, sa.orm.exc.MultipleResultsFound):
e = exc.E_NOT_FOUND.format(
_('Several {object}s found'),
_('Make sure you have entered a correct URL with a valid id'),
object=Model.__name__,
)
return exception_from(e, error)
# IntegrityError
if isinstance(error, sa.exc.IntegrityError):
# Check: unique violation errors
if isinstance(error.orig, psycopg2.errors.UniqueViolation):
# NOTE: this function will only find the column name if your unique indexes have explicit names!!
failed_column_names = extract_postgres_unique_violation_column_names(error, Model.metadata) # type: ignore[attr-defined]
if failed_column_names:
e = exc.E_CONFLICT_DUPLICATE(
# We use a custom format() because we want comma-separated column names
_('Conflicting {object} object found for {failed_columns}').format(
failed_columns=', '.join(failed_column_names),
object=Model.__name__,
),
_('Please choose something unique for {failed_columns}'),
failed_columns=failed_column_names,
object=Model.__name__,
)
else:
e = exc.E_CONFLICT_DUPLICATE.format(
_('Conflicting {object} object found'),
_('Please choose something unique'),
object=Model.__name__,
failed_columns=None,
)
return exception_from(e, error)
# Reraise as a server error
else:
raise convert_unexpected_error(error, exc=exc)
# Otherwise
return None