From 88134623d6949b4fec1aaaa00d68593049d91481 Mon Sep 17 00:00:00 2001 From: b84330808 Date: Thu, 29 Oct 2026 10:19:00 +0900 Subject: [PATCH] gh-71468: Make xmlrpc.client.Fault and ProtocolError picklable Both exceptions called Error.__init__() without arguments, so args was empty and unpickling called the constructor with no arguments, which raised TypeError. Pass the constructor arguments to Error.__init__(), as configparser and smtplib exceptions do. Co-Authored-By: Claude Opus 5.5 --- Lib/test/test_xmlrpc.py | 24 +++++++++++++++++++ Lib/xmlrpc/client.py | 4 ++-- ...6-10-29-01-18-55.gh-issue-71468.wZ-CyG.rst | 3 +++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-29-01-18-55.gh-issue-71468.wZ-CyG.rst diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py index ee0e24f6e86ae33..0ad365b42b2f3b1 100644 --- a/Lib/test/test_xmlrpc.py +++ b/Lib/test/test_xmlrpc.py @@ -1,6 +1,7 @@ import base64 import datetime import decimal +import pickle import sys import time import unittest @@ -480,6 +481,29 @@ def test_dump_fault(self): s = xmlrpclib.Marshaller().dumps(f) self.assertRaises(xmlrpclib.Fault, xmlrpclib.loads, s) + def test_pickle(self): + # gh-71468 + f = xmlrpclib.Fault(42, 'Test Fault') + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + newf = pickle.loads(pickle.dumps(f, proto)) + self.assertIs(type(newf), xmlrpclib.Fault) + self.assertEqual(newf.faultCode, 42) + self.assertEqual(newf.faultString, 'Test Fault') + + def test_protocol_error_pickle(self): + # gh-71468 + e = xmlrpclib.ProtocolError('localhost/RPC2', 500, + 'Internal Server Error', {'X-Test': '1'}) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(proto=proto): + newe = pickle.loads(pickle.dumps(e, proto)) + self.assertIs(type(newe), xmlrpclib.ProtocolError) + self.assertEqual(newe.url, 'localhost/RPC2') + self.assertEqual(newe.errcode, 500) + self.assertEqual(newe.errmsg, 'Internal Server Error') + self.assertEqual(newe.headers, {'X-Test': '1'}) + def test_dotted_attribute(self): # this will raise AttributeError because code don't want us to use # private methods diff --git a/Lib/xmlrpc/client.py b/Lib/xmlrpc/client.py index 84e4e4d11a7319e..f5afce27ba7996d 100644 --- a/Lib/xmlrpc/client.py +++ b/Lib/xmlrpc/client.py @@ -201,7 +201,7 @@ class Error(Exception): class ProtocolError(Error): """Indicates an HTTP protocol error.""" def __init__(self, url, errcode, errmsg, headers): - Error.__init__(self) + Error.__init__(self, url, errcode, errmsg, headers) self.url = url self.errcode = errcode self.errmsg = errmsg @@ -233,7 +233,7 @@ class ResponseError(Error): class Fault(Error): """Indicates an XML-RPC fault package.""" def __init__(self, faultCode, faultString, **extra): - Error.__init__(self) + Error.__init__(self, faultCode, faultString) self.faultCode = faultCode self.faultString = faultString def __repr__(self): diff --git a/Misc/NEWS.d/next/Library/2026-10-29-01-18-55.gh-issue-71468.wZ-CyG.rst b/Misc/NEWS.d/next/Library/2026-10-29-01-18-55.gh-issue-71468.wZ-CyG.rst new file mode 100644 index 000000000000000..3b3e512044e0870 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-29-01-18-55.gh-issue-71468.wZ-CyG.rst @@ -0,0 +1,3 @@ +:class:`xmlrpc.client.Fault` and :class:`xmlrpc.client.ProtocolError` +exceptions can now be unpickled. Previously, unpickling them raised +:exc:`TypeError`.