Picklable HTTPError (#39285)

This commit is contained in:
Harmen Stoppels 2023-08-07 10:48:35 +02:00 committed by GitHub
parent 8cd9497522
commit 27f04b3544
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 2 deletions

View File

@ -3,7 +3,10 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections import collections
import email.message
import os import os
import pickle
import urllib.request
import pytest import pytest
@ -339,3 +342,25 @@ def get_s3_session(url, method="fetch"):
def test_s3_url_parsing(): 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("example.com") == "https://example.com"
assert spack.util.s3._parse_s3_endpoint_url("http://example.com") == "http://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)

View File

@ -17,6 +17,7 @@
import urllib.parse import urllib.parse
from html.parser import HTMLParser from html.parser import HTMLParser
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
from typing import IO, Optional
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
from urllib.request import HTTPSHandler, Request, build_opener from urllib.request import HTTPSHandler, Request, build_opener
@ -40,7 +41,9 @@
class DetailedHTTPError(HTTPError): 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 self.req = req
super().__init__(req.get_full_url(), code, msg, hdrs, fp) super().__init__(req.get_full_url(), code, msg, hdrs, fp)
@ -48,7 +51,13 @@ def __str__(self):
# Note: HTTPError, is actually a kind of non-seekable response object, so # 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 # best not to read the response body here (even if it may include a human-readable
# error message). # 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): class SpackHTTPDefaultErrorHandler(urllib.request.HTTPDefaultErrorHandler):