]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Change StatementError formatting (newlines and %s)
authorNate Clark <natec425@gmail.com>
Sun, 17 Feb 2019 01:17:56 +0000 (19:17 -0600)
committerNate Clark <natec425@gmail.com>
Sun, 17 Feb 2019 01:17:56 +0000 (19:17 -0600)
This changes the error formatting for StatementError in two ways:

1) Break each error detail up over multiple newlines instead of
   spaced out on a single line. Hopefully, this helps readers scan
   the error message more easily.

2) Change the SQL representation in the message to use __str__ (%s)
   instead of the current behavior that uses __repr__ (%r). This
   should help readers recognize the structure of their SQL,
   particularly if it is a multiline SQL statement. In the multiline
   case, the SQL would be printed over multiple lines instead of
   printing an escaped "\n".

lib/sqlalchemy/exc.py
test/base/test_except.py
test/engine/test_deprecations.py
test/engine/test_execute.py
test/engine/test_logging.py
test/orm/test_session.py
test/sql/test_insert_exec.py

index b9e2f933965afce395dbf6784710c9585c1b0a5e..1475bf35f04acf6e4f018ff0979481ec98d87259 100644 (file)
@@ -346,14 +346,14 @@ class StatementError(SQLAlchemyError):
 
         details = [self._message(as_unicode=as_unicode)]
         if self.statement:
-            details.append("[SQL: %r]" % self.statement)
+            details.append("[SQL: %s]" % self.statement)
             if self.params:
                 params_repr = util._repr_params(self.params, 10)
                 details.append("[parameters: %r]" % params_repr)
         code_str = self._code_str()
         if code_str:
             details.append(code_str)
-        return " ".join(["(%s)" % det for det in self.detail] + details)
+        return "\n".join(["(%s)" % det for det in self.detail] + details)
 
 
 class DBAPIError(StatementError):
index 58943902ce3aa3a14b690f99cfc7756f92adcaa0..203ba33ede402e274e59e6e378c21faa1f21960f 100644 (file)
@@ -70,9 +70,9 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.DBAPIError as exc:
             eq_(
                 str(exc),
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] (Background on this error at: "
-                "http://sqlalche.me/e/e3q8)",
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n"
+                "(Background on this error at: http://sqlalche.me/e/e3q8)",
             )
 
     def test_statement_error_no_code(self):
@@ -86,8 +86,8 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.StatementError as err:
             eq_(
                 str(err),
-                "(sqlalchemy.exc.InvalidRequestError) hello "
-                "[SQL: 'select * from table'] [parameters: [{'x': 1}]]",
+                "(sqlalchemy.exc.InvalidRequestError) hello\n"
+                "[SQL: select * from table]\n[parameters: [{'x': 1}]]",
             )
             eq_(err.args, ("(sqlalchemy.exc.InvalidRequestError) hello",))
 
@@ -102,8 +102,9 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.StatementError as err:
             eq_(
                 str(err),
-                "(sqlalchemy.exc.InvalidRequestError) hello "
-                "[SQL: 'select * from table'] [parameters: [{'x': 1}]] "
+                "(sqlalchemy.exc.InvalidRequestError) hello\n"
+                "[SQL: select * from table]\n"
+                "[parameters: [{'x': 1}]]\n"
                 "(Background on this error at: http://sqlalche.me/e/abcd)",
             )
             eq_(err.args, ("(sqlalchemy.exc.InvalidRequestError) hello",))
@@ -114,7 +115,7 @@ class WrapTest(fixtures.TestBase):
         orig.args = [2006, "Test raise operational error"]
         eq_(
             str(orig),
-            "(2006, 'Test raise operational error') "
+            "(2006, 'Test raise operational error')\n"
             "(Background on this error at: http://sqlalche.me/e/dbapi)",
         )
 
@@ -125,7 +126,7 @@ class WrapTest(fixtures.TestBase):
         eq_(
             compat.text_type(orig),
             compat.u(
-                "méil (Background on this error at: "
+                "méil\n(Background on this error at: "
                 "http://sqlalche.me/e/dbapi)"
             ),
         )
@@ -153,8 +154,9 @@ class WrapTest(fixtures.TestBase):
             )
         except sa_exceptions.DBAPIError as exc:
             assert str(exc).startswith(
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] [parameters: {"
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n"
+                "[parameters: {"
             )
 
     def test_tostring_large_list(self):
@@ -165,10 +167,10 @@ class WrapTest(fixtures.TestBase):
                 OperationalError(),
                 DatabaseError,
             )
-        except sa_exceptions.DBAPIError as exc:
-            assert str(exc).startswith(
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] [parameters: "
+        except sa_exceptions.DBAPIError as ex:
+            assert str(ex).startswith(
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n[parameters: "
                 "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]]"
             )
 
@@ -194,11 +196,11 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.DBAPIError as exc:
             eq_(
                 str(exc),
-                "(test.base.test_except.OperationalError) sql error "
-                "[SQL: 'this is a message'] [parameters: [{1: 1}, "
-                "{1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: "
-                "1}, {1: 1}, {1: 1}]] (Background on this error at: "
-                "http://sqlalche.me/e/e3q8)",
+                "(test.base.test_except.OperationalError) sql error\n"
+                "[SQL: this is a message]\n"
+                "[parameters: [{1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: 1},"
+                " {1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: 1}]]\n"
+                "(Background on this error at: http://sqlalche.me/e/e3q8)",
             )
             eq_(
                 exc.args,
@@ -226,11 +228,12 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.DBAPIError as exc:
             eq_(
                 str(exc),
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] [parameters: [{1: 1}, "
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n"
+                "[parameters: [{1: 1}, "
                 "{1: 1}, {1: 1}, {1: 1}, {1: 1}, {1: 1}, "
                 "{1: 1}, {1: 1}  ... displaying 10 of 11 total "
-                "bound parameter sets ...  {1: 1}, {1: 1}]] "
+                "bound parameter sets ...  {1: 1}, {1: 1}]]\n"
                 "(Background on this error at: http://sqlalche.me/e/e3q8)",
             )
         try:
@@ -244,9 +247,10 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.DBAPIError as exc:
             eq_(
                 str(exc),
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] [parameters: [(1,), "
-                "(1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,)]] "
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n"
+                "[parameters: [(1,), "
+                "(1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,), (1,)]]\n"
                 "(Background on this error at: http://sqlalche.me/e/e3q8)",
             )
         try:
@@ -271,11 +275,12 @@ class WrapTest(fixtures.TestBase):
         except sa_exceptions.DBAPIError as exc:
             eq_(
                 str(exc),
-                "(test.base.test_except.OperationalError)  "
-                "[SQL: 'this is a message'] [parameters: [(1,), "
+                "(test.base.test_except.OperationalError) \n"
+                "[SQL: this is a message]\n"
+                "[parameters: [(1,), "
                 "(1,), (1,), (1,), (1,), (1,), (1,), (1,)  "
                 "... displaying 10 of 11 total bound "
-                "parameter sets ...  (1,), (1,)]] "
+                "parameter sets ...  (1,), (1,)]]\n"
                 "(Background on this error at: http://sqlalche.me/e/e3q8)",
             )
 
index 35226a09720f04f29e4bb7e8d62d263c2e23d78e..29bda0ac2004e2a09274fe4ce0371be3d5be4bce 100644 (file)
@@ -1114,7 +1114,7 @@ class HandleErrorTest(fixtures.TestBase):
         with engine.connect() as conn:
             assert_raises_message(
                 tsa.exc.StatementError,
-                r"\(.*SomeException\) " r"nope \[SQL\: u?'SELECT 1 ",
+                r"\(.*SomeException\) " r"nope\n\[SQL\: u?SELECT 1 ",
                 conn.execute,
                 select([1]).where(column("foo") == literal("bar", MyType())),
             )
index ad9144a382ed1561ab1fef9ea97ab2b6d493e234..fc152417a206b08e7dde54ae930092694d0b5ed5 100644 (file)
@@ -371,7 +371,7 @@ class ExecuteTest(fixtures.TestBase):
         def _go(conn):
             assert_raises_message(
                 tsa.exc.StatementError,
-                r"\(.*.SomeException\) " r"nope \[SQL\: u?'SELECT 1 ",
+                r"\(.*.SomeException\) " r"nope\n\[SQL\: u?SELECT 1 ",
                 conn.execute,
                 select([1]).where(column("foo") == literal("bar", MyType())),
             )
@@ -415,7 +415,7 @@ class ExecuteTest(fixtures.TestBase):
                 )
                 if util.py2k
                 else util.u(
-                    "A value is required for bind parameter 'uname'"
+                    "A value is required for bind parameter 'uname'\n"
                     ".*SELECT users.user_name AS .méil."
                 ),
                 conn.execute,
@@ -2184,7 +2184,7 @@ class HandleErrorTest(fixtures.TestBase):
         with engine.connect() as conn:
             assert_raises_message(
                 tsa.exc.StatementError,
-                r"\(.*.SomeException\) " r"nope \[SQL\: u?'SELECT 1 ",
+                r"\(.*.SomeException\) " r"nope\n\[SQL\: u?SELECT 1 ",
                 conn.execute,
                 select([1]).where(column("foo") == literal("bar", MyType())),
             )
index 8190a3fcde44903e3717da04372600721fb23492..bd425c940daf1707ee953f21bb96ca75a1098ed8 100644 (file)
@@ -103,7 +103,7 @@ class LogParamsTest(fixtures.TestBase):
         exception = tsa.exc.IntegrityError("foo", {"x": "y"}, None)
         eq_regex(
             str(exception),
-            r"\(.*.NoneType\) None \[SQL: 'foo'\] \[parameters: {'x': 'y'}\]",
+            r"\(.*.NoneType\) None\n\[SQL: foo\]\n\[parameters: {'x': 'y'}\]",
         )
 
     def test_exception_format_unexpected_parameter(self):
@@ -112,7 +112,7 @@ class LogParamsTest(fixtures.TestBase):
         exception = tsa.exc.IntegrityError("foo", "bar", "bat")
         eq_regex(
             str(exception),
-            r"\(.*.str\) bat \[SQL: 'foo'\] \[parameters: 'bar'\]",
+            r"\(.*.str\) bat\n\[SQL: foo\]\n\[parameters: 'bar'\]",
         )
 
     def test_exception_format_unexpected_member_parameter(self):
@@ -121,7 +121,7 @@ class LogParamsTest(fixtures.TestBase):
         exception = tsa.exc.IntegrityError("foo", ["bar", "bat"], "hoho")
         eq_regex(
             str(exception),
-            r"\(.*.str\) hoho \[SQL: 'foo'\] \[parameters: \['bar', 'bat'\]\]",
+            r"\(.*.str\) hoho\n\[SQL: foo\]\n\[parameters: \['bar', 'bat'\]\]",
         )
 
     def test_result_large_param(self):
@@ -169,7 +169,7 @@ class LogParamsTest(fixtures.TestBase):
     def test_error_large_dict(self):
         assert_raises_message(
             tsa.exc.DBAPIError,
-            r".*'INSERT INTO nonexistent \(data\) values \(:data\)'\] "
+            r".*INSERT INTO nonexistent \(data\) values \(:data\)\]\n"
             r"\[parameters: "
             r"\[{'data': '0'}, {'data': '1'}, {'data': '2'}, "
             r"{'data': '3'}, {'data': '4'}, {'data': '5'}, "
@@ -186,7 +186,7 @@ class LogParamsTest(fixtures.TestBase):
         assert_raises_message(
             tsa.exc.DBAPIError,
             r".*INSERT INTO nonexistent \(data\) values "
-            r"\(\?\)'\] \[parameters: \[\('0',\), \('1',\), \('2',\), "
+            r"\(\?\)\]\n\[parameters: \[\('0',\), \('1',\), \('2',\), "
             r"\('3',\), \('4',\), \('5',\), \('6',\), \('7',\)  "
             r"... displaying "
             r"10 of 100 total bound parameter sets ...  "
index 2bc11398dc6600fcbab0ae3802343f9020ae7ceb..ad7ff2d35ae38144cbee6b0e77e308c941a92cc7 100644 (file)
@@ -1037,7 +1037,7 @@ class DeferredRelationshipExpressionTest(_fixtures.FixtureTest):
         assert_raises_message(
             sa.exc.StatementError,
             "Can't resolve value for column users.id on object "
-            ".User.*.; the object is detached and the value was expired ",
+            ".User.*.; the object is detached and the value was expired",
             q.one,
         )
 
index fafe7cc9ba97d7304c15423db76d0bfaf074235d..7905dc4bc915252ba607c44bd188b2cc732b9fb9 100644 (file)
@@ -60,8 +60,8 @@ class InsertExecTest(fixtures.TablesTest):
             exc.StatementError,
             r"\(sqlalchemy.exc.InvalidRequestError\) A value is required for "
             "bind parameter 'user_name', in "
-            "parameter group 2 "
-            r"\[SQL: u?'INSERT INTO users",
+            "parameter group 2\n"
+            r"\[SQL: u?INSERT INTO users",
             users.insert().execute,
             {"user_id": 7, "user_name": "jack"},
             {"user_id": 8, "user_name": "ed"},