@@ -55,10 +55,16 @@ class _Status:
5555
5656try :
5757 # SQLAlchemy v1/v2 exception sets
58- from sqlalchemy .exc import IntegrityError , DBAPIError , OperationalError
58+ from sqlalchemy .exc import (
59+ IntegrityError ,
60+ DBAPIError ,
61+ OperationalError ,
62+ StatementError ,
63+ )
5964 from sqlalchemy .orm .exc import NoResultFound # type: ignore
6065except Exception : # pragma: no cover
61- IntegrityError = DBAPIError = OperationalError = NoResultFound = None # type: ignore
66+ IntegrityError = DBAPIError = OperationalError = StatementError = None # type: ignore
67+ NoResultFound = None # type: ignore
6268
6369
6470# Detect asyncpg constraint errors without importing asyncpg (optional dep).
@@ -99,6 +105,50 @@ def _format_validation(err: Any) -> Any:
99105 return _limit (str (err ))
100106
101107
108+ def _format_sqlalchemy_error_data (exc : BaseException ) -> Optional [Dict [str , Any ]]:
109+ data : Dict [str , Any ] = {}
110+ hide_parameters = bool (getattr (exc , "hide_parameters" , False ))
111+ statement = getattr (exc , "statement" , None )
112+ if statement and not hide_parameters :
113+ data ["statement" ] = _limit (str (statement ))
114+ if hasattr (exc , "params" ):
115+ params = getattr (exc , "params" )
116+ if hide_parameters :
117+ data .update (_safe_params_metadata (params ))
118+ else :
119+ try :
120+ params_repr = repr (params )
121+ except Exception : # pragma: no cover
122+ params_repr = "<unrepresentable params>"
123+ data ["params" ] = _limit (params_repr )
124+ orig = getattr (exc , "orig" , None )
125+ if orig :
126+ data ["orig" ] = _limit (f"{ type (orig ).__name__ } : { orig } " )
127+ return data or None
128+
129+
130+ def _safe_params_metadata (params : Any ) -> Dict [str , Any ]:
131+ metadata : Dict [str , Any ] = {"params_redacted" : True }
132+ if isinstance (params , Mapping ):
133+ metadata ["param_keys" ] = list (params .keys ())
134+ metadata ["param_types" ] = {
135+ key : type (value ).__name__ for key , value in params .items ()
136+ }
137+ return metadata
138+ if isinstance (params , (list , tuple )):
139+ metadata ["param_keys" ] = list (range (len (params )))
140+ metadata ["param_types" ] = [type (value ).__name__ for value in params ]
141+ return metadata
142+ metadata ["param_keys" ] = [0 ]
143+ metadata ["param_types" ] = [type (params ).__name__ ]
144+ return metadata
145+
146+
147+ def _looks_like_validation_error (message : str ) -> bool :
148+ lowered = message .lower ()
149+ return "not null constraint" in lowered or "check constraint" in lowered
150+
151+
102152def _get_temp (ctx : Any ) -> Mapping [str , Any ]:
103153 tmp = getattr (ctx , "temp" , None )
104154 return tmp if isinstance (tmp , Mapping ) else {}
@@ -139,11 +189,14 @@ def _read_in_errors(ctx: Any) -> List[Dict[str, Any]]:
139189 "IntegrityError" ,
140190 "DBAPIError" ,
141191 "OperationalError" ,
192+ "StatementError" ,
142193 "NoResultFound" ,
143194 "_is_asyncpg_constraint_error" ,
144195 "_limit" ,
145196 "_stringify_exc" ,
146197 "_format_validation" ,
198+ "_format_sqlalchemy_error_data" ,
199+ "_looks_like_validation_error" ,
147200 "_get_temp" ,
148201 "_has_in_errors" ,
149202 "_read_in_errors" ,
0 commit comments