Skip to content

Commit

Permalink
Merge pull request jsonpickle#471 from aviramstr/patch-1
Browse files Browse the repository at this point in the history
Fix jsonpickle handling of missing modules or attributes
  • Loading branch information
Theelx authored Dec 3, 2023
2 parents 0caa5a7 + f0bbefc commit c9d6cd5
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 36 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ v3.0.3
======
* Fixed a bug where pickling some built-in classes (e.g. zoneinfo)
could return a ``None`` module. (#447)
* Fixed a bug where unpickling a missing class would return a different object
instead of ``None``. (+471)
* Fixed the handling of missing classes when setting ``on_missing`` to ``warn`` or ``error``. (+471)

v3.0.2
======
Expand Down
6 changes: 2 additions & 4 deletions jsonpickle/unpickler.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,7 @@ def loadclass(module_and_name, classes=None):
__import__(module)
obj = sys.modules[module]
for class_name in names[up_to:]:
try:
obj = getattr(obj, class_name)
except AttributeError:
continue
obj = getattr(obj, class_name)
return obj
except (AttributeError, ImportError, ValueError):
continue
Expand Down Expand Up @@ -772,6 +769,7 @@ def _restore_object(self, obj):
return instance

if cls is None:
self._process_missing(class_name)
return self._mkref(obj)

return self._restore_object_instance(obj, cls, class_name)
Expand Down
66 changes: 34 additions & 32 deletions tests/jsonpickle_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,42 +498,44 @@ def test_restore_legacy_builtins(self):
self.assertTrue(cls is int)

def test_unpickler_on_missing(self):
class SimpleClass(object):
def __init__(self, i):
self.i = i
encoded = jsonpickle.encode(Outer.Middle.Inner())
self.assertTrue(isinstance(jsonpickle.decode(encoded), Outer.Middle.Inner))

# Alter the encoded string to create cases where the class is missing, in multiple levels
self.assertTrue(encoded == '{"py/object": "jsonpickle_test.Outer.Middle.Inner"}')
missing_cases = [
'{"py/object": "MISSING.Outer.Middle.Inner"}',
'{"py/object": "jsonpickle_test.MISSING.Middle.Inner"}',
'{"py/object": "jsonpickle_test.Outer.MISSING.Inner"}',
'{"py/object": "jsonpickle_test.Outer.Middle.MISSING"}',
]

for case in missing_cases:
# https://docs.python.org/3/library/warnings.html#testing-warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
jsonpickle.decode(case, on_missing='warn')
self.assertTrue(issubclass(w[-1].category, UserWarning))
self.assertTrue(
"Unpickler._restore_object could not find" in str(w[-1].message)
)

jsonpickle.decode(case, on_missing=on_missing_callback)
self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
self.assertTrue("The unpickler couldn't find" in str(w[-1].message))

frozen = jsonpickle.encode(SimpleClass(4))
del SimpleClass

# https://docs.python.org/3/library/warnings.html#testing-warnings

with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
jsonpickle.decode(frozen, on_missing='warn')
self.assertTrue(issubclass(w[-1].category, UserWarning))
self.assertTrue(
"Unpickler._restore_object could not find" in str(w[-1].message)
jsonpickle.decode(case, on_missing='ignore')
== jsonpickle.backend.json.loads(case)
)

jsonpickle.decode(frozen, on_missing=on_missing_callback)
self.assertTrue(issubclass(w[-1].category, RuntimeWarning))
self.assertTrue("The unpickler couldn't find" in str(w[-1].message))

self.assertTrue(
jsonpickle.decode(frozen, on_missing='ignore')
== {
'py/object': 'jsonpickle_test.PicklingTestCase.test_unpickler_on_missing.<locals>.SimpleClass',
'i': 4,
}
)

try:
jsonpickle.decode(frozen, on_missing='error')
except jsonpickle.errors.ClassNotFoundError:
# it's supposed to error
self.assertTrue(True)
else:
self.assertTrue(False)
try:
jsonpickle.decode(case, on_missing='error')
except jsonpickle.errors.ClassNotFoundError:
# it's supposed to error
self.assertTrue(True)
else:
self.assertTrue(False)

def test_private_slot_members(self):
obj = jsonpickle.loads(jsonpickle.dumps(MySlots()))
Expand Down

0 comments on commit c9d6cd5

Please sign in to comment.