From 0b86507ecd29e1e46c129e2b8279457a805e3dca Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 1 Nov 2024 18:36:59 -0700 Subject: [PATCH] Fix pickling of httpclient.HTTPError subclasses and web.HTTPError The `args` member variable is set by `BaseException.__new__` and used by `BaseException.__reduce__` for pickling. To avoid interfering with it, we need to avoid calling `BaseException.__init__` from classes that have subclasses with incompatible constructors, and rename our own `tornado.web.HTTPError.args` member. >>> pickle.loads(pickle.dumps(tornado.simple_httpclient.HTTPTimeoutError("message"))) Traceback (most recent call last): File "", line 1, in TypeError: HTTPTimeoutError.__init__() takes 2 positional arguments but 4 were given >>> str(pickle.loads(pickle.dumps(tornado.web.HTTPError(500, "%s", "foo")))) Traceback (most recent call last): File "", line 1, in File "/home/anders/python/tornado/tornado/web.py", line 2488, in __str__ return message + " (" + (self.log_message % self.args) + ")" ~~~~~~~~~~~~~~~~~^~~~~~~~~~~ TypeError: not enough arguments for format string Signed-off-by: Anders Kaseorg --- tornado/httpclient.py | 1 - tornado/web.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 3a45ffd041..0a2da5def2 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -717,7 +717,6 @@ def __init__( self.code = code self.message = message or httputil.responses.get(code, "Unknown") self.response = response - super().__init__(code, message, response) def __str__(self) -> str: return "HTTP %d: %s" % (self.code, self.message) diff --git a/tornado/web.py b/tornado/web.py index 289c67384f..8c5ca35ff2 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -2520,7 +2520,7 @@ def __init__( ) -> None: self.status_code = status_code self._log_message = log_message - self.args = args + self.log_args = args self.reason = kwargs.get("reason", None) @property @@ -2528,13 +2528,13 @@ def log_message(self) -> Optional[str]: """ A backwards compatible way of accessing log_message. """ - if self._log_message and not self.args: + if self._log_message and not self.log_args: return self._log_message.replace("%", "%%") return self._log_message def get_message(self) -> Optional[str]: - if self._log_message and self.args: - return self._log_message % self.args + if self._log_message and self.log_args: + return self._log_message % self.log_args return self._log_message def __str__(self) -> str: