summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarmen Stoppels <harmenstoppels@gmail.com>2023-08-07 10:48:35 +0200
committerGitHub <noreply@github.com>2023-08-07 10:48:35 +0200
commit27f04b35442b5a6b9015f17bab46b7df3371f3fc (patch)
treecccf0205a1622044c436cbed459ff413ec9711d9
parent8cd9497522939222dc304ee3708fd3154154f67b (diff)
downloadspack-27f04b35442b5a6b9015f17bab46b7df3371f3fc.tar.gz
spack-27f04b35442b5a6b9015f17bab46b7df3371f3fc.tar.bz2
spack-27f04b35442b5a6b9015f17bab46b7df3371f3fc.tar.xz
spack-27f04b35442b5a6b9015f17bab46b7df3371f3fc.zip
Picklable HTTPError (#39285)
-rw-r--r--lib/spack/spack/test/web.py25
-rw-r--r--lib/spack/spack/util/web.py13
2 files changed, 36 insertions, 2 deletions
diff --git a/lib/spack/spack/test/web.py b/lib/spack/spack/test/web.py
index 89023875df..2d6f577799 100644
--- a/lib/spack/spack/test/web.py
+++ b/lib/spack/spack/test/web.py
@@ -3,7 +3,10 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections
+import email.message
import os
+import pickle
+import urllib.request
import pytest
@@ -339,3 +342,25 @@ def test_s3_url_exists(monkeypatch, capfd):
def test_s3_url_parsing():
assert spack.util.s3._parse_s3_endpoint_url("example.com") == "https://example.com"
assert spack.util.s3._parse_s3_endpoint_url("http://example.com") == "http://example.com"
+
+
+def test_detailed_http_error_pickle(tmpdir):
+ tmpdir.join("response").write("response")
+
+ headers = email.message.Message()
+ headers.add_header("Content-Type", "text/plain")
+
+ # Use a temporary file object as a response body
+ with open(str(tmpdir.join("response")), "rb") as f:
+ error = spack.util.web.DetailedHTTPError(
+ urllib.request.Request("http://example.com"), 404, "Not Found", headers, f
+ )
+
+ deserialized = pickle.loads(pickle.dumps(error))
+
+ assert isinstance(deserialized, spack.util.web.DetailedHTTPError)
+ assert deserialized.code == 404
+ assert deserialized.filename == "http://example.com"
+ assert deserialized.reason == "Not Found"
+ assert str(deserialized.info()) == str(headers)
+ assert str(deserialized) == str(error)
diff --git a/lib/spack/spack/util/web.py b/lib/spack/spack/util/web.py
index ffaa54d012..22309ba87f 100644
--- a/lib/spack/spack/util/web.py
+++ b/lib/spack/spack/util/web.py
@@ -17,6 +17,7 @@ import traceback
import urllib.parse
from html.parser import HTMLParser
from pathlib import Path, PurePosixPath
+from typing import IO, Optional
from urllib.error import HTTPError, URLError
from urllib.request import HTTPSHandler, Request, build_opener
@@ -40,7 +41,9 @@ from spack.util.path import convert_to_posix_path
class DetailedHTTPError(HTTPError):
- def __init__(self, req: Request, code: int, msg: str, hdrs: email.message.Message, fp) -> None:
+ def __init__(
+ self, req: Request, code: int, msg: str, hdrs: email.message.Message, fp: Optional[IO]
+ ) -> None:
self.req = req
super().__init__(req.get_full_url(), code, msg, hdrs, fp)
@@ -48,7 +51,13 @@ class DetailedHTTPError(HTTPError):
# Note: HTTPError, is actually a kind of non-seekable response object, so
# best not to read the response body here (even if it may include a human-readable
# error message).
- return f"{self.req.get_method()} {self.url} returned {self.code}: {self.msg}"
+ # Note: use self.filename, not self.url, because the latter requires fp to be an
+ # IO object, which is not the case after unpickling.
+ return f"{self.req.get_method()} {self.filename} returned {self.code}: {self.msg}"
+
+ def __reduce__(self):
+ # fp is an IO object and not picklable, the rest should be.
+ return DetailedHTTPError, (self.req, self.code, self.msg, self.hdrs, None)
class SpackHTTPDefaultErrorHandler(urllib.request.HTTPDefaultErrorHandler):