--- /dev/null
+.. 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.
+
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:
parameters,
e,
self.dialect.dbapi.Error,
+ hide_parameters=self.engine.hide_parameters,
dialect=self.dialect,
),
exc_info,
parameters,
e,
self.dialect.dbapi.Error,
+ hide_parameters=self.engine.hide_parameters,
connection_invalidated=self._is_disconnect,
dialect=self.dialect,
)
None,
e,
dialect.dbapi.Error,
+ hide_parameters=engine.hide_parameters,
connection_invalidated=is_disconnect,
)
else:
echo=None,
proxy=None,
execution_options=None,
+ hide_parameters=False,
):
self.pool = pool
self.url = url
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)
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
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
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):
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):
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)
params,
orig,
dbapi_base_err,
+ hide_parameters=False,
connection_invalidated=False,
dialect=None,
):
statement,
params,
orig,
+ hide_parameters=hide_parameters,
code=orig.code,
)
elif not isinstance(orig, dbapi_base_err) and statement:
statement,
params,
orig,
+ hide_parameters=hide_parameters,
)
glob = globals()
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):
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)
statement,
params,
orig,
+ hide_parameters,
code=code,
)
self.connection_invalidated = connection_invalidated
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"),
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
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)
"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 (?)",
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()