]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add hide_parameters flag to create_engine
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 21 Aug 2019 21:29:55 +0000 (17:29 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 22 Aug 2019 18:01:48 +0000 (14:01 -0400)
Added new parameter :paramref:`.create_engine.hide_parameters` which when
set to True will cause SQL parameters to no longer be logged, nor rendered
in the string representation of a :class:`.StatementError` object.

Fixes: #4815
Change-Id: Ib87f868b6936cf6b42b192644e9d732ec24266c2
(cherry picked from commit 4b321e8a5e6b728a818a801c3ad90bb759c584bc)

doc/build/changelog/unreleased_13/4815.rst [new file with mode: 0644]
lib/sqlalchemy/engine/__init__.py
lib/sqlalchemy/engine/base.py
lib/sqlalchemy/exc.py
test/engine/test_execute.py
test/engine/test_logging.py

diff --git a/doc/build/changelog/unreleased_13/4815.rst b/doc/build/changelog/unreleased_13/4815.rst
new file mode 100644 (file)
index 0000000..5157f99
--- /dev/null
@@ -0,0 +1,8 @@
+.. change::
+    :tags: feature, engine
+    :tickets: 4815
+
+    Added new parameter :paramref:`.create_engine.hide_parameters` which when
+    set to True will cause SQL parameters to no longer be logged, nor rendered
+    in the string representation of a :class:`.StatementError` object.
+
index 765ab9c86918e68c49be349fb77353b0932d5123..d008d52125af89f09c0bc53933365be25fcc3f82 100644 (file)
@@ -254,6 +254,12 @@ def create_engine(*args, **kwargs):
         be applied to all connections.  See
         :meth:`~sqlalchemy.engine.Connection.execution_options`
 
+    :param hide_parameters: Boolean, when set to True, SQL statement parameters
+        will not be displayed in INFO logging nor will they be formatted into
+        the string representation of :class:`.StatementError` objects.
+
+        .. versionadded:: 1.3.8
+
     :param implicit_returning=True: When ``True``, a RETURNING-
         compatible construct, if available, will be used to
         fetch newly generated primary key values when a single row
index 9b74f4fedafcf92040fb86cfacdf7915f5f6ecdf..fd9b11edbc09d0a7411c0c782b48f1a8458d2be1 100644 (file)
@@ -1207,9 +1207,14 @@ class Connection(Connectable):
 
         if self._echo:
             self.engine.logger.info(statement)
-            self.engine.logger.info(
-                "%r", sql_util._repr_params(parameters, batches=10)
-            )
+            if not self.engine.hide_parameters:
+                self.engine.logger.info(
+                    "%r", sql_util._repr_params(parameters, batches=10)
+                )
+            else:
+                self.engine.logger.info(
+                    "[SQL parameters hidden due to hide_parameters=True]"
+                )
 
         evt_handled = False
         try:
@@ -1373,6 +1378,7 @@ class Connection(Connectable):
                     parameters,
                     e,
                     self.dialect.dbapi.Error,
+                    hide_parameters=self.engine.hide_parameters,
                     dialect=self.dialect,
                 ),
                 exc_info,
@@ -1393,6 +1399,7 @@ class Connection(Connectable):
                     parameters,
                     e,
                     self.dialect.dbapi.Error,
+                    hide_parameters=self.engine.hide_parameters,
                     connection_invalidated=self._is_disconnect,
                     dialect=self.dialect,
                 )
@@ -1493,6 +1500,7 @@ class Connection(Connectable):
                 None,
                 e,
                 dialect.dbapi.Error,
+                hide_parameters=engine.hide_parameters,
                 connection_invalidated=is_disconnect,
             )
         else:
@@ -1855,6 +1863,7 @@ class Engine(Connectable, log.Identified):
         echo=None,
         proxy=None,
         execution_options=None,
+        hide_parameters=False,
     ):
         self.pool = pool
         self.url = url
@@ -1862,6 +1871,7 @@ class Engine(Connectable, log.Identified):
         if logging_name:
             self.logging_name = logging_name
         self.echo = echo
+        self.hide_parameters = hide_parameters
         log.instance_logger(self, echoflag=echo)
         if proxy:
             interfaces.ConnectionProxy._adapt_listener(self, proxy)
@@ -2306,6 +2316,7 @@ class OptionEngine(Engine):
         self.dialect = proxied.dialect
         self.logging_name = proxied.logging_name
         self.echo = proxied.echo
+        self.hide_parameters = proxied.hide_parameters
         log.instance_logger(self, echoflag=self.echo)
 
         # note: this will propagate events that are assigned to the parent
index 1e575626a5061b9e41ec285beef581242dd05c0c..efee58d991643d9cdf2356c7b31debfe0d441c81 100644 (file)
@@ -332,11 +332,20 @@ class StatementError(SQLAlchemyError):
     orig = None
     """The DBAPI exception object."""
 
-    def __init__(self, message, statement, params, orig, code=None):
+    def __init__(
+        self,
+        message,
+        statement,
+        params,
+        orig,
+        hide_parameters=False,
+        code=None,
+    ):
         SQLAlchemyError.__init__(self, message, code=code)
         self.statement = statement
         self.params = params
         self.orig = orig
+        self.hide_parameters = hide_parameters
         self.detail = []
 
     def add_detail(self, msg):
@@ -345,7 +354,13 @@ class StatementError(SQLAlchemyError):
     def __reduce__(self):
         return (
             self.__class__,
-            (self.args[0], self.statement, self.params, self.orig),
+            (
+                self.args[0],
+                self.statement,
+                self.params,
+                self.orig,
+                self.hide_parameters,
+            ),
         )
 
     def _sql_message(self, as_unicode):
@@ -361,8 +376,13 @@ class StatementError(SQLAlchemyError):
                 stmt_detail = "[SQL: %s]" % self.statement
             details.append(stmt_detail)
             if self.params:
-                params_repr = util._repr_params(self.params, 10)
-                details.append("[parameters: %r]" % params_repr)
+                if self.hide_parameters:
+                    details.append(
+                        "[SQL parameters hidden due to hide_parameters=True]"
+                    )
+                else:
+                    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)
@@ -401,6 +421,7 @@ class DBAPIError(StatementError):
         params,
         orig,
         dbapi_base_err,
+        hide_parameters=False,
         connection_invalidated=False,
         dialect=None,
     ):
@@ -425,6 +446,7 @@ class DBAPIError(StatementError):
                     statement,
                     params,
                     orig,
+                    hide_parameters=hide_parameters,
                     code=orig.code,
                 )
             elif not isinstance(orig, dbapi_base_err) and statement:
@@ -438,6 +460,7 @@ class DBAPIError(StatementError):
                     statement,
                     params,
                     orig,
+                    hide_parameters=hide_parameters,
                 )
 
             glob = globals()
@@ -452,7 +475,12 @@ class DBAPIError(StatementError):
                     break
 
         return cls(
-            statement, params, orig, connection_invalidated, code=cls.code
+            statement,
+            params,
+            orig,
+            connection_invalidated=connection_invalidated,
+            hide_parameters=hide_parameters,
+            code=cls.code,
         )
 
     def __reduce__(self):
@@ -462,12 +490,19 @@ class DBAPIError(StatementError):
                 self.statement,
                 self.params,
                 self.orig,
+                self.hide_parameters,
                 self.connection_invalidated,
             ),
         )
 
     def __init__(
-        self, statement, params, orig, connection_invalidated=False, code=None
+        self,
+        statement,
+        params,
+        orig,
+        hide_parameters=False,
+        connection_invalidated=False,
+        code=None,
     ):
         try:
             text = str(orig)
@@ -480,6 +515,7 @@ class DBAPIError(StatementError):
             statement,
             params,
             orig,
+            hide_parameters,
             code=code,
         )
         self.connection_invalidated = connection_invalidated
index 335c3a487b6b02db3e1b6c76c65199d55b5ea595..d78a669a5c0a7fce94eac4c7bf20ff5d2e7db282 100644 (file)
@@ -518,10 +518,14 @@ class ExecuteTest(fixtures.TestBase):
     def _test_stmt_exception_pickleable(self, orig):
         for sa_exc in (
             tsa.exc.StatementError(
-                "some error", "select * from table", {"foo": "bar"}, orig
+                "some error",
+                "select * from table",
+                {"foo": "bar"},
+                orig,
+                False,
             ),
             tsa.exc.InterfaceError(
-                "select * from table", {"foo": "bar"}, orig
+                "select * from table", {"foo": "bar"}, orig, True
             ),
             tsa.exc.NoReferencedTableError("message", "tname"),
             tsa.exc.NoReferencedColumnError("message", "tname", "cname"),
index bd425c940daf1707ee953f21bb96ca75a1098ed8..d55f4249a0f34d061bf3222af4fb93692df6d308 100644 (file)
@@ -1,7 +1,13 @@
 import logging.handlers
 
 import sqlalchemy as tsa
+from sqlalchemy import bindparam
+from sqlalchemy import Column
+from sqlalchemy import MetaData
+from sqlalchemy import or_
 from sqlalchemy import select
+from sqlalchemy import String
+from sqlalchemy import Table
 from sqlalchemy import util
 from sqlalchemy.testing import assert_raises_message
 from sqlalchemy.testing import engines
@@ -18,7 +24,11 @@ class LogParamsTest(fixtures.TestBase):
 
     def setup(self):
         self.eng = engines.testing_engine(options={"echo": True})
+        self.no_param_engine = engines.testing_engine(
+            options={"echo": True, "hide_parameters": True}
+        )
         self.eng.execute("create table foo (data string)")
+        self.no_param_engine.execute("create table foo (data string)")
         self.buf = logging.handlers.BufferingHandler(100)
         for log in [logging.getLogger("sqlalchemy.engine")]:
             log.addHandler(self.buf)
@@ -41,6 +51,16 @@ class LogParamsTest(fixtures.TestBase):
             "parameter sets ...  {'data': '98'}, {'data': '99'}]",
         )
 
+    def test_log_no_parameters(self):
+        self.no_param_engine.execute(
+            "INSERT INTO foo (data) values (:data)",
+            [{"data": str(i)} for i in range(100)],
+        )
+        eq_(
+            self.buf.buffer[1].message,
+            "[SQL parameters hidden due to hide_parameters=True]",
+        )
+
     def test_log_large_list(self):
         self.eng.execute(
             "INSERT INTO foo (data) values (?)",
@@ -106,6 +126,48 @@ class LogParamsTest(fixtures.TestBase):
             r"\(.*.NoneType\) None\n\[SQL: foo\]\n\[parameters: {'x': 'y'}\]",
         )
 
+    def test_exception_format_hide_parameters(self):
+        exception = tsa.exc.IntegrityError(
+            "foo", {"x": "y"}, None, hide_parameters=True
+        )
+        eq_regex(
+            str(exception),
+            r"\(.*.NoneType\) None\n\[SQL: foo\]\n"
+            r"\[SQL parameters hidden due to hide_parameters=True\]",
+        )
+
+    def test_exception_format_hide_parameters_dbapi_round_trip(self):
+        assert_raises_message(
+            tsa.exc.DBAPIError,
+            r".*INSERT INTO nonexistent \(data\) values \(:data\)\]\n"
+            r"\[SQL parameters hidden due to hide_parameters=True\]",
+            lambda: self.no_param_engine.execute(
+                "INSERT INTO nonexistent (data) values (:data)",
+                [{"data": str(i)} for i in range(10)],
+            ),
+        )
+
+    def test_exception_format_hide_parameters_nondbapi_round_trip(self):
+        foo = Table("foo", MetaData(), Column("data", String))
+
+        with self.no_param_engine.connect() as conn:
+            assert_raises_message(
+                tsa.exc.StatementError,
+                r"\(sqlalchemy.exc.InvalidRequestError\) A value is required "
+                r"for bind parameter 'the_data_2'\n"
+                r"\[SQL: SELECT foo.data \nFROM foo \nWHERE "
+                r"foo.data = \? OR foo.data = \?\]\n"
+                r"\[SQL parameters hidden due to hide_parameters=True\]",
+                conn.execute,
+                select([foo]).where(
+                    or_(
+                        foo.c.data == bindparam("the_data_1"),
+                        foo.c.data == bindparam("the_data_2"),
+                    )
+                ),
+                {"the_data_1": "some data"},
+            )
+
     def test_exception_format_unexpected_parameter(self):
         # test that if the parameters aren't any known type, we just
         # run through repr()