Repository: jsonpickle/jsonpickle (python)

Issue:
Cannot decode subclass of exception with keyword parameter
I am using jsonpickle v4.1.1.

I am likely doing something dumb, but I am getting an error when trying to decode into a subclass of Exception with one keyword only argument. I can minimally reproduce this as follows:

```py
import jsonpickle

class Foo(Exception):
    def __init__(self, a, *, b):
        self.a = a
        self.b = b

>>> jsonpickle.encode(Foo("a", b="b"))
'{"py/reduce": [{"py/type": "__main__.Foo"}, {"py/tuple": ["a"]}, {"a": "a", "b": "b"}]}'

>>> jsonpickle.decode(jsonpickle.encode(Foo("a", b="b")))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/dfarr/Developer/resonatehq-examples/deep-research/.venv/lib/python3.12/site-packages/jsonpickle/unpickler.py", line 103, in decode
    return context.restore(data, reset=reset, classes=classes)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/dfarr/Developer/resonatehq-examples/deep-research/.venv/lib/python3.12/site-packages/jsonpickle/unpickler.py", line 420, in restore
    value = self._restore(obj)
            ^^^^^^^^^^^^^^^^^^
  File "/Users/dfarr/Developer/resonatehq-examples/deep-research/.venv/lib/python3.12/site-packages/jsonpickle/unpickler.py", line 402, in _restore
    return restore(obj)
           ^^^^^^^^^^^^
  File "/Users/dfarr/Developer/resonatehq-examples/deep-research/.venv/lib/python3.12/site-packages/jsonpickle/unpickler.py", line 555, in _restore_reduce
    stage1 = f(*args)
             ^^^^^^^^
TypeError: Foo.__init__() missing 1 required keyword-only argument: 'b'
```

I also get a similar error when Foo is defined with two positional arguments and I instantiate as `Foo("a", b="b")`. However, I do not get this error if I instantiate with two positional args in this case `Foo("a", "b")`

Pull request:
Fix handling of Exception subclasses with keywork-only arguments
Fixes #564. 

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 5f2e36d..fa5cc20 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,13 +4,14 @@ v5.0.0
       You can re-enable yaml support using ``jsonpickle.ext.yaml.register()``.
       (#550) (+551)
     * **Breaking Change**: Support for CPython 3.7 and 3.8 was dropped, and support for
-      CPython 3.13 was added. (#561)
+      CPython 3.13 was added. (#561) (+563)
     * **Breaking Change**: Changed ``is_*`` functions in jsonpickle.util to the private API
-      rather than the public one by adding a single underscore to their names.
+      rather than the public one by adding a single underscore to their names. (+563)
     * **Breaking Change**: Removed the deprecated functions deprecated in 4.1.0 with commit
       59dda25. Additionally, ``util.is_sequence`` was removed due to having only one caller,
-      ``util.is_sequence_subclass``.
-    * Mypy-compatible typing has been added to the entire jsonpickle public API! (#561)
+      ``util.is_sequence_subclass``. (+563)
+    * Mypy-compatible typing has been added to the entire jsonpickle public API! (#561) (+563)
+    * Fixed bug with pickling subclasses of Exception with keyword-only args. (#564) (+565)
 
 v4.1.1
 ======
diff --git a/jsonpickle/unpickler.py b/jsonpickle/unpickler.py
index 0821579..b36533e 100644
--- a/jsonpickle/unpickler.py
+++ b/jsonpickle/unpickler.py
@@ -580,7 +580,12 @@ class Unpickler:
                 proxy.reset(result)
                 self._swapref(proxy, result)
                 return result
-            stage1 = f(*args)
+            try:
+                stage1 = f(*args)
+            except TypeError:
+                # this happens when there are missing kwargs and args don't match so we bypass
+                # __init__ since the state dict will set all attributes immediately afterwards
+                stage1 = f.__new__(f, *args)
 
         if state:
             try:


Test patch:
diff --git a/tests/jsonpickle_test.py b/tests/jsonpickle_test.py
index 1a616a2..995faf8 100644
--- a/tests/jsonpickle_test.py
+++ b/tests/jsonpickle_test.py
@@ -173,6 +173,13 @@ class SafeString(str, SafeData):
     __slots__ = ()
 
 
+class KwargsSubclassTest(Exception):
+    def __init__(self, a, *, b):
+        self.a = a
+        self.b = b
+        self.c = a + b
+
+
 def on_missing_callback(class_name):
     # not actually a runtime problem but it doesn't matter
     warnings.warn('The unpickler could not find %s' % class_name, RuntimeWarning)
@@ -1199,6 +1206,14 @@ def test_readonly_str_attrs():
     assert safe_str == actual
 
 
+def test_exception_kwargs():
+    actual = KwargsSubclassTest("a", b="b")
+    encoded = jsonpickle.encode(actual)
+    decoded = jsonpickle.decode(encoded)
+    assert type(actual) == type(decoded)
+    assert actual.c == decoded.c
+
+
 class PicklableNamedTuple:
     """A namedtuple wrapper that uses ``__getnewargs__``