Skip to content

Commit

Permalink
Fix pickling of httpclient.HTTPError subclasses and web.HTTPError
Browse files Browse the repository at this point in the history
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 "<stdin>", line 1, in <module>
    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 "<stdin>", line 1, in <module>
      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 <[email protected]>
  • Loading branch information
andersk committed Nov 2, 2024
1 parent 2a0e1d1 commit 9cb31f2
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 4 deletions.
1 change: 0 additions & 1 deletion tornado/httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 5 additions & 3 deletions tornado/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,9 @@ def log_exception(
if isinstance(value, HTTPError):
if value.log_message:
format = "%d %s: " + value.log_message
args = [value.status_code, self._request_summary()] + list(value.args)
args = [value.status_code, self._request_summary()] + list(
value.log_args
)
gen_log.warning(format, *args)
else:
app_log.error(
Expand Down Expand Up @@ -2474,7 +2476,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)
if log_message and not args:
self.log_message = log_message.replace("%", "%%")
Expand All @@ -2485,7 +2487,7 @@ def __str__(self) -> str:
self.reason or httputil.responses.get(self.status_code, "Unknown"),
)
if self.log_message:
return message + " (" + (self.log_message % self.args) + ")"
return message + " (" + (self.log_message % self.log_args) + ")"
else:
return message

Expand Down

0 comments on commit 9cb31f2

Please sign in to comment.