1# Copyright (C) 2008 John Paulett (john -at- paulett.org) 2# Copyright (C) 2009-2021 David Aguilar (davvid -at- gmail.com) 3# All rights reserved. 4# 5# This software is licensed as described in the file COPYING, which 6# you should have received as part of this distribution. 7from __future__ import absolute_import, division, unicode_literals 8import os 9import unittest 10import collections 11 12import pytest 13 14import jsonpickle 15import jsonpickle.backend 16import jsonpickle.handlers 17from jsonpickle import compat, tags, util 18from jsonpickle.compat import PY2, PY3 19from helper import SkippableTest 20 21 22class Thing(object): 23 def __init__(self, name): 24 self.name = name 25 self.child = None 26 27 def __repr__(self): 28 return 'Thing("%s")' % self.name 29 30 31class Capture(object): 32 def __init__(self, *args, **kwargs): 33 self.args = args 34 self.kwargs = kwargs 35 36 37class ThingWithProps(object): 38 def __init__(self, name='', dogs='reliable', monkies='tricksy'): 39 self.name = name 40 self._critters = (('dogs', dogs), ('monkies', monkies)) 41 42 def _get_identity(self): 43 keys = [self.dogs, self.monkies, self.name] 44 return hash('-'.join([str(key) for key in keys])) 45 46 identity = property(_get_identity) 47 48 def _get_dogs(self): 49 return self._critters[0][1] 50 51 dogs = property(_get_dogs) 52 53 def _get_monkies(self): 54 return self._critters[1][1] 55 56 monkies = property(_get_monkies) 57 58 def __getstate__(self): 59 out = dict( 60 __identity__=self.identity, 61 nom=self.name, 62 dogs=self.dogs, 63 monkies=self.monkies, 64 ) 65 return out 66 67 def __setstate__(self, state_dict): 68 self._critters = ( 69 ('dogs', state_dict.get('dogs')), 70 ('monkies', state_dict.get('monkies')), 71 ) 72 self.name = state_dict.get('nom', '') 73 ident = state_dict.get('__identity__') 74 if ident != self.identity: 75 raise ValueError('expanded object does not match originial state!') 76 77 def __eq__(self, other): 78 return self.identity == other.identity 79 80 81class UserDict(dict): 82 """A user class that inherits from :class:`dict`""" 83 84 def __init__(self, **kwargs): 85 dict.__init__(self, **kwargs) 86 self.valid = False 87 88 89class Outer(object): 90 class Middle(object): 91 class Inner(object): 92 pass 93 94 95class PicklingTestCase(unittest.TestCase): 96 def setUp(self): 97 self.pickler = jsonpickle.pickler.Pickler() 98 self.unpickler = jsonpickle.unpickler.Unpickler() 99 self.b85_pickler = jsonpickle.pickler.Pickler(use_base85=True) 100 101 def tearDown(self): 102 self.pickler.reset() 103 self.unpickler.reset() 104 105 @unittest.skipIf(not PY2, 'Python 2-specific base85 test') 106 def test_base85_always_false_on_py2(self): 107 self.assertFalse(self.b85_pickler.use_base85) 108 109 @unittest.skipIf(PY2, 'Base85 not supported on Python 2') 110 def test_base85_override_py3(self): 111 """Ensure the Python 2 check still lets us set use_base85 on Python 3""" 112 self.assertTrue(self.b85_pickler.use_base85) 113 114 @unittest.skipIf(PY2, 'Base85 not supported on Python 2') 115 def test_bytes_default_base85(self): 116 data = os.urandom(16) 117 encoded = util.b85encode(data) 118 self.assertEqual({tags.B85: encoded}, self.b85_pickler.flatten(data)) 119 120 @unittest.skipIf(PY2, 'Base85 not supported on Python 2') 121 def test_py3_bytes_base64_default(self): 122 data = os.urandom(16) 123 encoded = util.b64encode(data) 124 self.assertEqual({tags.B64: encoded}, self.pickler.flatten(data)) 125 126 @unittest.skipIf(not PY2, 'Python 2-specific base64 test') 127 def test_py2_default_base64(self): 128 data = os.urandom(16) 129 encoded = util.b64encode(data) 130 self.assertEqual({tags.B64: encoded}, self.pickler.flatten(data)) 131 132 @unittest.skipIf(PY2, 'Base85 not supported on Python 2') 133 def test_decode_base85(self): 134 pickled = {tags.B85: 'P{Y4;Xv4O{u^=-c'} 135 expected = u'P\u00ffth\u00f6\u00f1 3!'.encode('utf-8') 136 self.assertEqual(expected, self.unpickler.restore(pickled)) 137 138 @unittest.skipIf(PY2, 'Base85 not supported on Python 2') 139 def test_base85_still_handles_base64(self): 140 pickled = {tags.B64: 'UMO/dGjDtsOxIDMh'} 141 expected = u'P\u00ffth\u00f6\u00f1 3!'.encode('utf-8') 142 self.assertEqual(expected, self.unpickler.restore(pickled)) 143 144 @unittest.skipIf(not PY2, 'Python 2-specific base85 test') 145 def test_base85_crashes_py2(self): 146 with self.assertRaises(NotImplementedError): 147 self.unpickler.restore({tags.B85: 'P{Y4;Xv4O{u^=-c'}) 148 149 def test_string(self): 150 self.assertEqual('a string', self.pickler.flatten('a string')) 151 self.assertEqual('a string', self.unpickler.restore('a string')) 152 153 def test_unicode(self): 154 self.assertEqual('a string', self.pickler.flatten('a string')) 155 self.assertEqual('a string', self.unpickler.restore('a string')) 156 157 def test_int(self): 158 self.assertEqual(3, self.pickler.flatten(3)) 159 self.assertEqual(3, self.unpickler.restore(3)) 160 161 def test_float(self): 162 self.assertEqual(3.5, self.pickler.flatten(3.5)) 163 self.assertEqual(3.5, self.unpickler.restore(3.5)) 164 165 def test_boolean(self): 166 self.assertTrue(self.pickler.flatten(True)) 167 self.assertFalse(self.pickler.flatten(False)) 168 self.assertTrue(self.unpickler.restore(True)) 169 self.assertFalse(self.unpickler.restore(False)) 170 171 def test_none(self): 172 self.assertTrue(self.pickler.flatten(None) is None) 173 self.assertTrue(self.unpickler.restore(None) is None) 174 175 def test_list(self): 176 # multiple types of values 177 listA = [1, 35.0, 'value'] 178 self.assertEqual(listA, self.pickler.flatten(listA)) 179 self.assertEqual(listA, self.unpickler.restore(listA)) 180 # nested list 181 listB = [40, 40, listA, 6] 182 self.assertEqual(listB, self.pickler.flatten(listB)) 183 self.assertEqual(listB, self.unpickler.restore(listB)) 184 # 2D list 185 listC = [[1, 2], [3, 4]] 186 self.assertEqual(listC, self.pickler.flatten(listC)) 187 self.assertEqual(listC, self.unpickler.restore(listC)) 188 # empty list 189 listD = [] 190 self.assertEqual(listD, self.pickler.flatten(listD)) 191 self.assertEqual(listD, self.unpickler.restore(listD)) 192 193 def test_set(self): 194 setlist = ['orange', 'apple', 'grape'] 195 setA = set(setlist) 196 197 flattened = self.pickler.flatten(setA) 198 for s in setlist: 199 self.assertTrue(s in flattened[tags.SET]) 200 201 setA_pickle = {tags.SET: setlist} 202 self.assertEqual(setA, self.unpickler.restore(setA_pickle)) 203 204 def test_dict(self): 205 dictA = {'key1': 1.0, 'key2': 20, 'key3': 'thirty', tags.JSON_KEY + '6': 6} 206 self.assertEqual(dictA, self.pickler.flatten(dictA)) 207 self.assertEqual(dictA, self.unpickler.restore(dictA)) 208 dictB = {} 209 self.assertEqual(dictB, self.pickler.flatten(dictB)) 210 self.assertEqual(dictB, self.unpickler.restore(dictB)) 211 212 def test_tuple(self): 213 # currently all collections are converted to lists 214 tupleA = (4, 16, 32) 215 tupleA_pickle = {tags.TUPLE: [4, 16, 32]} 216 self.assertEqual(tupleA_pickle, self.pickler.flatten(tupleA)) 217 self.assertEqual(tupleA, self.unpickler.restore(tupleA_pickle)) 218 tupleB = (4,) 219 tupleB_pickle = {tags.TUPLE: [4]} 220 self.assertEqual(tupleB_pickle, self.pickler.flatten(tupleB)) 221 self.assertEqual(tupleB, self.unpickler.restore(tupleB_pickle)) 222 223 def test_tuple_roundtrip(self): 224 data = (1, 2, 3) 225 newdata = jsonpickle.decode(jsonpickle.encode(data)) 226 self.assertEqual(data, newdata) 227 228 def test_set_roundtrip(self): 229 data = {1, 2, 3} 230 newdata = jsonpickle.decode(jsonpickle.encode(data)) 231 self.assertEqual(data, newdata) 232 233 def test_list_roundtrip(self): 234 data = [1, 2, 3] 235 newdata = jsonpickle.decode(jsonpickle.encode(data)) 236 self.assertEqual(data, newdata) 237 238 def test_class(self): 239 inst = Thing('test name') 240 inst.child = Thing('child name') 241 242 flattened = self.pickler.flatten(inst) 243 self.assertEqual('test name', flattened['name']) 244 child = flattened['child'] 245 self.assertEqual('child name', child['name']) 246 247 inflated = self.unpickler.restore(flattened) 248 self.assertEqual('test name', inflated.name) 249 self.assertTrue(type(inflated) is Thing) 250 self.assertEqual('child name', inflated.child.name) 251 self.assertTrue(type(inflated.child) is Thing) 252 253 def test_classlist(self): 254 array = [Thing('one'), Thing('two'), 'a string'] 255 256 flattened = self.pickler.flatten(array) 257 self.assertEqual('one', flattened[0]['name']) 258 self.assertEqual('two', flattened[1]['name']) 259 self.assertEqual('a string', flattened[2]) 260 261 inflated = self.unpickler.restore(flattened) 262 self.assertEqual('one', inflated[0].name) 263 self.assertTrue(type(inflated[0]) is Thing) 264 self.assertEqual('two', inflated[1].name) 265 self.assertTrue(type(inflated[1]) is Thing) 266 self.assertEqual('a string', inflated[2]) 267 268 def test_classdict(self): 269 dict = {'k1': Thing('one'), 'k2': Thing('two'), 'k3': 3} 270 271 flattened = self.pickler.flatten(dict) 272 self.assertEqual('one', flattened['k1']['name']) 273 self.assertEqual('two', flattened['k2']['name']) 274 self.assertEqual(3, flattened['k3']) 275 276 inflated = self.unpickler.restore(flattened) 277 self.assertEqual('one', inflated['k1'].name) 278 self.assertTrue(type(inflated['k1']) is Thing) 279 self.assertEqual('two', inflated['k2'].name) 280 self.assertTrue(type(inflated['k2']) is Thing) 281 self.assertEqual(3, inflated['k3']) 282 283 def test_recursive(self): 284 """create a recursive structure and test that we can handle it""" 285 parent = Thing('parent') 286 child = Thing('child') 287 child.sibling = Thing('sibling') 288 289 parent.self = parent 290 parent.child = child 291 parent.child.twin = child 292 parent.child.parent = parent 293 parent.child.sibling.parent = parent 294 295 cloned = jsonpickle.decode(jsonpickle.encode(parent)) 296 297 self.assertEqual(parent.name, cloned.name) 298 self.assertEqual(parent.child.name, cloned.child.name) 299 self.assertEqual(parent.child.sibling.name, cloned.child.sibling.name) 300 self.assertEqual(cloned, cloned.child.parent) 301 self.assertEqual(cloned, cloned.child.sibling.parent) 302 self.assertEqual(cloned, cloned.child.twin.parent) 303 self.assertEqual(cloned.child, cloned.child.twin) 304 305 def test_tuple_notunpicklable(self): 306 self.pickler.unpicklable = False 307 308 flattened = self.pickler.flatten(('one', 2, 3)) 309 self.assertEqual(flattened, ['one', 2, 3]) 310 311 def test_set_not_unpicklable(self): 312 self.pickler.unpicklable = False 313 314 flattened = self.pickler.flatten({'one', 2, 3}) 315 self.assertTrue('one' in flattened) 316 self.assertTrue(2 in flattened) 317 self.assertTrue(3 in flattened) 318 self.assertTrue(isinstance(flattened, list)) 319 320 def test_thing_with_module(self): 321 obj = Thing('with-module') 322 obj.themodule = os 323 324 flattened = self.pickler.flatten(obj) 325 inflated = self.unpickler.restore(flattened) 326 self.assertEqual(inflated.themodule, os) 327 328 def test_thing_with_module_safe(self): 329 obj = Thing('with-module') 330 obj.themodule = os 331 flattened = self.pickler.flatten(obj) 332 self.unpickler.safe = True 333 inflated = self.unpickler.restore(flattened) 334 self.assertEqual(inflated.themodule, None) 335 336 def test_thing_with_submodule(self): 337 from distutils import sysconfig 338 339 obj = Thing('with-submodule') 340 obj.submodule = sysconfig 341 342 flattened = self.pickler.flatten(obj) 343 inflated = self.unpickler.restore(flattened) 344 self.assertEqual(inflated.submodule, sysconfig) 345 346 def test_type_reference(self): 347 """This test ensures that users can store references to types.""" 348 obj = Thing('object-with-type-reference') 349 350 # reference the built-in 'object' type 351 obj.typeref = object 352 353 flattened = self.pickler.flatten(obj) 354 self.assertEqual(flattened['typeref'], {tags.TYPE: 'builtins.object'}) 355 356 inflated = self.unpickler.restore(flattened) 357 self.assertEqual(inflated.typeref, object) 358 359 def test_class_reference(self): 360 """This test ensures that users can store references to classes.""" 361 obj = Thing('object-with-class-reference') 362 363 # reference the 'Thing' class (not an instance of the class) 364 obj.classref = Thing 365 366 flattened = self.pickler.flatten(obj) 367 self.assertEqual(flattened['classref'], {tags.TYPE: 'jsonpickle_test.Thing'}) 368 369 inflated = self.unpickler.restore(flattened) 370 self.assertEqual(inflated.classref, Thing) 371 372 def test_supports_getstate_setstate(self): 373 obj = ThingWithProps('object-which-defines-getstate-setstate') 374 flattened = self.pickler.flatten(obj) 375 self.assertTrue(flattened[tags.STATE].get('__identity__')) 376 self.assertTrue(flattened[tags.STATE].get('nom')) 377 inflated = self.unpickler.restore(flattened) 378 self.assertEqual(obj, inflated) 379 380 def test_references(self): 381 obj_a = Thing('foo') 382 obj_b = Thing('bar') 383 coll = [obj_a, obj_b, obj_b] 384 flattened = self.pickler.flatten(coll) 385 inflated = self.unpickler.restore(flattened) 386 self.assertEqual(len(inflated), len(coll)) 387 for x in range(len(coll)): 388 self.assertEqual(repr(coll[x]), repr(inflated[x])) 389 390 def test_references_in_number_keyed_dict(self): 391 """ 392 Make sure a dictionary with numbers as keys and objects as values 393 can make the round trip. 394 395 Because JSON must coerce integers to strings in dict keys, the sort 396 order may have a tendency to change between pickling and unpickling, 397 and this could affect the object references. 398 """ 399 one = Thing('one') 400 two = Thing('two') 401 twelve = Thing('twelve') 402 two.child = twelve 403 obj = { 404 1: one, 405 2: two, 406 12: twelve, 407 } 408 self.assertNotEqual( 409 list(sorted(obj.keys())), list(map(int, sorted(map(str, obj.keys())))) 410 ) 411 flattened = self.pickler.flatten(obj) 412 inflated = self.unpickler.restore(flattened) 413 self.assertEqual(len(inflated), 3) 414 self.assertEqual(inflated['12'].name, 'twelve') 415 416 def test_builtin_error(self): 417 expect = AssertionError 418 json = jsonpickle.encode(expect) 419 actual = jsonpickle.decode(json) 420 self.assertEqual(expect, actual) 421 self.assertTrue(expect is actual) 422 423 def test_builtin_function(self): 424 expect = dir 425 json = jsonpickle.encode(expect) 426 actual = jsonpickle.decode(json) 427 self.assertEqual(expect, actual) 428 self.assertTrue(expect is actual) 429 430 def test_restore_legacy_builtins(self): 431 """ 432 jsonpickle 0.9.6 and earlier used the Python 2 `__builtin__` 433 naming for builtins. Ensure those can be loaded until they're 434 no longer supported. 435 """ 436 ae = jsonpickle.decode('{"py/type": "__builtin__.AssertionError"}') 437 assert ae is AssertionError 438 ae = jsonpickle.decode('{"py/type": "exceptions.AssertionError"}') 439 assert ae is AssertionError 440 cls = jsonpickle.decode('{"py/type": "__builtin__.int"}') 441 assert cls is int 442 443 444class JSONPickleTestCase(SkippableTest): 445 def setUp(self): 446 self.obj = Thing('A name') 447 self.expected_json = ( 448 '{"%s": "jsonpickle_test.Thing", "name": "A name", "child": null}' 449 % tags.OBJECT 450 ) 451 452 def test_API_names(self): 453 """ 454 Enforce expected names in main module 455 """ 456 names = list(vars(jsonpickle)) 457 self.assertIn('pickler', names) 458 self.assertIn('unpickler', names) 459 self.assertIn('JSONBackend', names) 460 self.assertIn('__version__', names) 461 self.assertIn('register', names) 462 self.assertIn('unregister', names) 463 self.assertIn('Pickler', names) 464 self.assertIn('Unpickler', names) 465 self.assertIn('encode', names) 466 self.assertIn('decode', names) 467 468 def test_encode(self): 469 expect = self.obj 470 pickle = jsonpickle.encode(self.obj) 471 actual = jsonpickle.decode(pickle) 472 self.assertEqual(expect.name, actual.name) 473 self.assertEqual(expect.child, actual.child) 474 475 def test_encode_notunpicklable(self): 476 expect = {'name': 'A name', 'child': None} 477 pickle = jsonpickle.encode(self.obj, unpicklable=False) 478 actual = jsonpickle.decode(pickle) 479 self.assertEqual(expect['name'], actual['name']) 480 481 def test_decode(self): 482 actual = jsonpickle.decode(self.expected_json) 483 self.assertEqual(self.obj.name, actual.name) 484 self.assertEqual(type(self.obj), type(actual)) 485 486 def test_json(self): 487 expect = self.obj 488 pickle = jsonpickle.encode(self.obj) 489 actual = jsonpickle.decode(pickle) 490 self.assertEqual(actual.name, expect.name) 491 self.assertEqual(actual.child, expect.child) 492 493 actual = jsonpickle.decode(self.expected_json) 494 self.assertEqual(self.obj.name, actual.name) 495 self.assertEqual(type(self.obj), type(actual)) 496 497 def test_unicode_dict_keys(self): 498 if PY2: 499 uni = unichr(0x1234) # noqa 500 else: 501 uni = chr(0x1234) 502 pickle = jsonpickle.encode({uni: uni}) 503 actual = jsonpickle.decode(pickle) 504 self.assertTrue(uni in actual) 505 self.assertEqual(actual[uni], uni) 506 507 def test_tuple_dict_keys_default(self): 508 """Test that we handle dictionaries with tuples as keys.""" 509 tuple_dict = {(1, 2): 3, (4, 5): {(7, 8): 9}} 510 pickle = jsonpickle.encode(tuple_dict) 511 expect = {'(1, 2)': 3, '(4, 5)': {'(7, 8)': 9}} 512 actual = jsonpickle.decode(pickle) 513 self.assertEqual(expect, actual) 514 515 tuple_dict = {(1, 2): [1, 2]} 516 pickle = jsonpickle.encode(tuple_dict) 517 actual = jsonpickle.decode(pickle) 518 self.assertEqual(actual['(1, 2)'], [1, 2]) 519 520 def test_tuple_dict_keys_with_keys_enabled(self): 521 """Test that we handle dictionaries with tuples as keys.""" 522 tuple_dict = {(1, 2): 3, (4, 5): {(7, 8): 9}} 523 pickle = jsonpickle.encode(tuple_dict, keys=True) 524 expect = tuple_dict 525 actual = jsonpickle.decode(pickle, keys=True) 526 self.assertEqual(expect, actual) 527 528 tuple_dict = {(1, 2): [1, 2]} 529 pickle = jsonpickle.encode(tuple_dict, keys=True) 530 actual = jsonpickle.decode(pickle, keys=True) 531 self.assertEqual(actual[(1, 2)], [1, 2]) 532 533 def test_None_dict_key_default(self): 534 # We do string coercion for non-string keys so None becomes 'None' 535 expect = {'null': None} 536 obj = {None: None} 537 pickle = jsonpickle.encode(obj) 538 actual = jsonpickle.decode(pickle) 539 self.assertEqual(expect, actual) 540 541 def test_None_dict_key_with_keys_enabled(self): 542 expect = {None: None} 543 obj = {None: None} 544 pickle = jsonpickle.encode(obj, keys=True) 545 actual = jsonpickle.decode(pickle, keys=True) 546 self.assertEqual(expect, actual) 547 548 def test_object_dict_keys(self): 549 """Test that we handle random objects as keys.""" 550 thing = Thing('random') 551 pickle = jsonpickle.encode({thing: True}) 552 actual = jsonpickle.decode(pickle) 553 self.assertEqual(actual, {'Thing("random")': True}) 554 555 def test_int_dict_keys_defaults(self): 556 int_dict = {1000: [1, 2]} 557 pickle = jsonpickle.encode(int_dict) 558 actual = jsonpickle.decode(pickle) 559 self.assertEqual(actual['1000'], [1, 2]) 560 561 def test_int_dict_keys_with_keys_enabled(self): 562 int_dict = {1000: [1, 2]} 563 pickle = jsonpickle.encode(int_dict, keys=True) 564 actual = jsonpickle.decode(pickle, keys=True) 565 self.assertEqual(actual[1000], [1, 2]) 566 567 def test_string_key_requiring_escape_dict_keys_with_keys_enabled(self): 568 json_key_dict = {tags.JSON_KEY + '6': [1, 2]} 569 pickled = jsonpickle.encode(json_key_dict, keys=True) 570 unpickled = jsonpickle.decode(pickled, keys=True) 571 self.assertEqual(unpickled[tags.JSON_KEY + '6'], [1, 2]) 572 573 def test_string_key_not_requiring_escape_dict_keys_with_keys_enabled(self): 574 """test that string keys that do not require escaping are not escaped""" 575 str_dict = {'name': [1, 2]} 576 pickled = jsonpickle.encode(str_dict, keys=True) 577 unpickled = jsonpickle.decode(pickled) 578 self.assertTrue('name' in unpickled) 579 580 def test_dict_subclass(self): 581 obj = UserDict() 582 obj.valid = True 583 obj.s = 'string' 584 obj.d = 'd_string' 585 obj['d'] = {} 586 obj['s'] = 'test' 587 pickle = jsonpickle.encode(obj) 588 actual = jsonpickle.decode(pickle) 589 590 self.assertEqual(type(actual), UserDict) 591 self.assertTrue('d' in actual) 592 self.assertTrue('s' in actual) 593 self.assertTrue(hasattr(actual, 'd')) 594 self.assertTrue(hasattr(actual, 's')) 595 self.assertTrue(hasattr(actual, 'valid')) 596 self.assertEqual(obj['d'], actual['d']) 597 self.assertEqual(obj['s'], actual['s']) 598 self.assertEqual(obj.d, actual.d) 599 self.assertEqual(obj.s, actual.s) 600 self.assertEqual(obj.valid, actual.valid) 601 602 def test_list_of_objects(self): 603 """Test that objects in lists are referenced correctly""" 604 a = Thing('a') 605 b = Thing('b') 606 pickle = jsonpickle.encode([a, b, b]) 607 actual = jsonpickle.decode(pickle) 608 self.assertEqual(actual[1], actual[2]) 609 self.assertEqual(type(actual[0]), Thing) 610 self.assertEqual(actual[0].name, 'a') 611 self.assertEqual(actual[1].name, 'b') 612 self.assertEqual(actual[2].name, 'b') 613 614 def test_refs_keys_values(self): 615 """Test that objects in dict keys are referenced correctly""" 616 j = Thing('random') 617 object_dict = {j: j} 618 pickle = jsonpickle.encode(object_dict, keys=True) 619 actual = jsonpickle.decode(pickle, keys=True) 620 self.assertEqual(list(actual.keys()), list(actual.values())) 621 622 def test_object_keys_to_list(self): 623 """Test that objects in dict values are referenced correctly""" 624 j = Thing('random') 625 object_dict = {j: [j, j]} 626 pickle = jsonpickle.encode(object_dict, keys=True) 627 actual = jsonpickle.decode(pickle, keys=True) 628 obj = list(actual.keys())[0] 629 self.assertEqual(j.name, obj.name) 630 self.assertTrue(obj is actual[obj][0]) 631 self.assertTrue(obj is actual[obj][1]) 632 633 def test_refs_in_objects(self): 634 """Test that objects in lists are referenced correctly""" 635 a = Thing('a') 636 b = Thing('b') 637 pickle = jsonpickle.encode([a, b, b]) 638 actual = jsonpickle.decode(pickle) 639 self.assertNotEqual(actual[0], actual[1]) 640 self.assertEqual(actual[1], actual[2]) 641 self.assertTrue(actual[1] is actual[2]) 642 643 def test_refs_recursive(self): 644 """Test that complicated recursive refs work""" 645 646 a = Thing('a') 647 a.self_list = [Thing('0'), Thing('1'), Thing('2')] 648 a.first = a.self_list[0] 649 a.stuff = {a.first: a.first} 650 a.morestuff = {a.self_list[1]: a.stuff} 651 652 pickle = jsonpickle.encode(a, keys=True) 653 b = jsonpickle.decode(pickle, keys=True) 654 655 item = b.self_list[0] 656 self.assertEqual(b.first, item) 657 self.assertEqual(b.stuff[b.first], item) 658 self.assertEqual(b.morestuff[b.self_list[1]][b.first], item) 659 660 def test_load_backend(self): 661 """Test that we can call jsonpickle.load_backend()""" 662 jsonpickle.load_backend('simplejson', 'dumps', 'loads', ValueError) 663 self.assertTrue(True) 664 665 def test_set_preferred_backend_allows_magic(self): 666 """Tests that we can use the pluggable backends magically""" 667 backend = 'os.path' 668 jsonpickle.load_backend(backend, 'split', 'join', AttributeError) 669 jsonpickle.set_preferred_backend(backend) 670 671 slash_hello, world = jsonpickle.encode('/hello/world') 672 jsonpickle.remove_backend(backend) 673 674 self.assertEqual(slash_hello, '/hello') 675 self.assertEqual(world, 'world') 676 677 def test_load_backend_submodule(self): 678 """Test that we can load a submodule as a backend""" 679 jsonpickle.load_backend('os.path', 'split', 'join', AttributeError) 680 self.assertTrue( 681 'os.path' in jsonpickle.json._backend_names 682 and 'os.path' in jsonpickle.json._encoders 683 and 'os.path' in jsonpickle.json._decoders 684 and 'os.path' in jsonpickle.json._encoder_options 685 and 'os.path' in jsonpickle.json._decoder_exceptions 686 ) 687 jsonpickle.remove_backend('os.path') 688 689 def _backend_is_partially_loaded(self, backend): 690 """Return True if the specified backend is incomplete""" 691 return ( 692 backend in jsonpickle.json._backend_names 693 or backend in jsonpickle.json._encoders 694 or backend in jsonpickle.json._decoders 695 or backend in jsonpickle.json._encoder_options 696 or backend in jsonpickle.json._decoder_exceptions 697 ) 698 699 def test_load_backend_handles_bad_encode(self): 700 """Test that we ignore bad encoders""" 701 702 load_backend = jsonpickle.load_backend 703 self.assertFalse(load_backend('os.path', 'bad!', 'split', AttributeError)) 704 self.assertFalse(self._backend_is_partially_loaded('os.path')) 705 706 def test_load_backend_raises_on_bad_decode(self): 707 """Test that we ignore bad decoders""" 708 709 load_backend = jsonpickle.load_backend 710 self.assertFalse(load_backend('os.path', 'join', 'bad!', AttributeError)) 711 self.assertFalse(self._backend_is_partially_loaded('os.path')) 712 713 def test_load_backend_handles_bad_loads_exc(self): 714 """Test that we ignore bad decoder exceptions""" 715 716 load_backend = jsonpickle.load_backend 717 self.assertFalse(load_backend('os.path', 'join', 'split', 'bad!')) 718 self.assertFalse(self._backend_is_partially_loaded('os.path')) 719 720 def test_list_item_reference(self): 721 thing = Thing('parent') 722 thing.child = Thing('child') 723 thing.child.refs = [thing] 724 725 encoded = jsonpickle.encode(thing) 726 decoded = jsonpickle.decode(encoded) 727 728 self.assertEqual(id(decoded.child.refs[0]), id(decoded)) 729 730 def test_reference_to_list(self): 731 thing = Thing('parent') 732 thing.a = [1] 733 thing.b = thing.a 734 thing.b.append(thing.a) 735 thing.b.append([thing.a]) 736 737 encoded = jsonpickle.encode(thing) 738 decoded = jsonpickle.decode(encoded) 739 740 self.assertEqual(decoded.a[0], 1) 741 self.assertEqual(decoded.b[0], 1) 742 self.assertEqual(id(decoded.a), id(decoded.b)) 743 self.assertEqual(id(decoded.a), id(decoded.a[1])) 744 self.assertEqual(id(decoded.a), id(decoded.a[2][0])) 745 746 def test_make_refs_disabled_list(self): 747 obj_a = Thing('foo') 748 obj_b = Thing('bar') 749 coll = [obj_a, obj_b, obj_b] 750 encoded = jsonpickle.encode(coll, make_refs=False) 751 decoded = jsonpickle.decode(encoded) 752 753 self.assertEqual(len(decoded), 3) 754 self.assertTrue(decoded[0] is not decoded[1]) 755 self.assertTrue(decoded[1] is not decoded[2]) 756 757 def test_make_refs_disabled_reference_to_list(self): 758 thing = Thing('parent') 759 thing.a = [1] 760 thing.b = thing.a 761 thing.b.append(thing.a) 762 thing.b.append([thing.a]) 763 764 encoded = jsonpickle.encode(thing, make_refs=False) 765 decoded = jsonpickle.decode(encoded) 766 767 assert decoded.a[0] == 1 768 assert decoded.b[0] == 1 769 assert decoded.a[1][0:3] == '[1,' 770 assert decoded.a[2][0][0:3] == '[1,' 771 772 def test_can_serialize_inner_classes(self): 773 class InnerScope(object): 774 """Private class visible to this method only""" 775 776 def __init__(self, name): 777 self.name = name 778 779 obj = InnerScope('test') 780 encoded = jsonpickle.encode(obj) 781 # Single class 782 decoded = jsonpickle.decode(encoded, classes=InnerScope) 783 self._test_inner_class(InnerScope, obj, decoded) 784 # List of classes 785 decoded = jsonpickle.decode(encoded, classes=[InnerScope]) 786 self._test_inner_class(InnerScope, obj, decoded) 787 # Tuple of classes 788 decoded = jsonpickle.decode(encoded, classes=(InnerScope,)) 789 self._test_inner_class(InnerScope, obj, decoded) 790 # Set of classes 791 decoded = jsonpickle.decode(encoded, classes={InnerScope}) 792 self._test_inner_class(InnerScope, obj, decoded) 793 794 def _test_inner_class(self, InnerScope, obj, decoded): 795 self.assertTrue(isinstance(obj, InnerScope)) 796 self.assertEqual(decoded.name, obj.name) 797 798 def test_can_serialize_nested_classes(self): 799 if PY2: 800 return self.skip('Serialization of nested classes requires ' 'Python >= 3') 801 802 middle = Outer.Middle 803 inner = Outer.Middle.Inner 804 encoded_middle = jsonpickle.encode(middle) 805 encoded_inner = jsonpickle.encode(inner) 806 807 decoded_middle = jsonpickle.decode(encoded_middle) 808 decoded_inner = jsonpickle.decode(encoded_inner) 809 810 self.assertTrue(isinstance(decoded_middle, type)) 811 self.assertTrue(isinstance(decoded_inner, type)) 812 self.assertEqual(decoded_middle, middle) 813 self.assertEqual(decoded_inner, inner) 814 815 def test_can_serialize_nested_class_objects(self): 816 if PY2: 817 return self.skip('Serialization of nested classes requires ' 'Python >= 3') 818 819 middle_obj = Outer.Middle() 820 middle_obj.attribute = 5 821 inner_obj = Outer.Middle.Inner() 822 inner_obj.attribute = 6 823 encoded_middle_obj = jsonpickle.encode(middle_obj) 824 encoded_inner_obj = jsonpickle.encode(inner_obj) 825 826 decoded_middle_obj = jsonpickle.decode(encoded_middle_obj) 827 decoded_inner_obj = jsonpickle.decode(encoded_inner_obj) 828 829 self.assertTrue(isinstance(decoded_middle_obj, Outer.Middle)) 830 self.assertTrue(isinstance(decoded_inner_obj, Outer.Middle.Inner)) 831 self.assertEqual(decoded_middle_obj.attribute, middle_obj.attribute) 832 self.assertEqual(decoded_inner_obj.attribute, inner_obj.attribute) 833 834 835class PicklableNamedTuple(object): 836 """ 837 A picklable namedtuple wrapper, to demonstrate the need 838 for protocol 2 compatibility. Yes, this is contrived in 839 its use of new, but it demonstrates the issue. 840 """ 841 842 def __new__(cls, propnames, vals): 843 # it's necessary to use the correct class name for class resolution 844 # classes that fake their own names may never be unpicklable 845 ntuple = collections.namedtuple(cls.__name__, propnames) 846 ntuple.__getnewargs__ = lambda self: (propnames, vals) 847 instance = ntuple.__new__(ntuple, *vals) 848 return instance 849 850 851class PicklableNamedTupleEx(object): 852 """ 853 A picklable namedtuple wrapper, to demonstrate the need 854 for protocol 4 compatibility. Yes, this is contrived in 855 its use of new, but it demonstrates the issue. 856 """ 857 858 def __getnewargs__(self): 859 raise NotImplementedError("This class needs __getnewargs_ex__") 860 861 def __new__(cls, newargs=__getnewargs__, **kwargs): 862 # it's necessary to use the correct class name for class resolution 863 # classes that fake their own names may never be unpicklable 864 ntuple = collections.namedtuple(cls.__name__, sorted(kwargs.keys())) 865 ntuple.__getnewargs_ex__ = lambda self: ((), kwargs) 866 ntuple.__getnewargs__ = newargs 867 instance = ntuple.__new__(ntuple, *[b for a, b in sorted(kwargs.items())]) 868 return instance 869 870 871class PickleProtocol2Thing(object): 872 def __init__(self, *args): 873 self.args = args 874 875 def __getnewargs__(self): 876 return self.args 877 878 def __eq__(self, other): 879 """ 880 Make PickleProtocol2Thing('slotmagic') == 881 PickleProtocol2Thing('slotmagic') 882 """ 883 if self.__dict__ == other.__dict__ and dir(self) == dir(other): 884 for prop in dir(self): 885 selfprop = getattr(self, prop) 886 if not callable(selfprop) and prop[0] != '_': 887 if selfprop != getattr(other, prop): 888 return False 889 return True 890 else: 891 return False 892 893 894# these two instances are used below and in tests 895slotmagic = PickleProtocol2Thing('slotmagic') 896dictmagic = PickleProtocol2Thing('dictmagic') 897 898 899class PickleProtocol2GetState(PickleProtocol2Thing): 900 def __new__(cls, *args): 901 instance = super(PickleProtocol2GetState, cls).__new__(cls) 902 instance.newargs = args 903 return instance 904 905 def __getstate__(self): 906 return 'I am magic' 907 908 909class PickleProtocol2GetStateDict(PickleProtocol2Thing): 910 def __getstate__(self): 911 return {'magic': True} 912 913 914class PickleProtocol2GetStateSlots(PickleProtocol2Thing): 915 def __getstate__(self): 916 return (None, {'slotmagic': slotmagic}) 917 918 919class PickleProtocol2GetStateSlotsDict(PickleProtocol2Thing): 920 def __getstate__(self): 921 return ({'dictmagic': dictmagic}, {'slotmagic': slotmagic}) 922 923 924class PickleProtocol2GetSetState(PickleProtocol2GetState): 925 def __setstate__(self, state): 926 """ 927 Contrived example, easy to test 928 """ 929 if state == "I am magic": 930 self.magic = True 931 else: 932 self.magic = False 933 934 935class PickleProtocol2ChildThing(object): 936 def __init__(self, child): 937 self.child = child 938 939 def __getnewargs__(self): 940 return ([self.child],) 941 942 943class PickleProtocol2ReduceString(object): 944 def __reduce__(self): 945 return __name__ + '.slotmagic' 946 947 948class PickleProtocol2ReduceExString(object): 949 def __reduce__(self): 950 assert False, "Should not be here" 951 952 def __reduce_ex__(self, n): 953 return __name__ + '.slotmagic' 954 955 956class PickleProtocol2ReduceTuple(object): 957 def __init__(self, argval, optional=None): 958 self.argval = argval 959 self.optional = optional 960 961 def __reduce__(self): 962 return ( 963 PickleProtocol2ReduceTuple, # callable 964 ('yam', 1), # args 965 None, # state 966 iter([]), # listitems 967 iter([]), # dictitems 968 ) 969 970 971@compat.iterator 972class ReducibleIterator(object): 973 def __iter__(self): 974 return self 975 976 def __next__(self): 977 raise StopIteration() 978 979 def __reduce__(self): 980 return ReducibleIterator, () 981 982 983def protocol_2_reduce_tuple_func(*args): 984 return PickleProtocol2ReduceTupleFunc(*args) 985 986 987class PickleProtocol2ReduceTupleFunc(object): 988 def __init__(self, argval, optional=None): 989 self.argval = argval 990 self.optional = optional 991 992 def __reduce__(self): 993 return ( 994 protocol_2_reduce_tuple_func, # callable 995 ('yam', 1), # args 996 None, # state 997 iter([]), # listitems 998 iter([]), # dictitems 999 ) 1000 1001 1002def __newobj__(lol, fail): 1003 """ 1004 newobj is special-cased, such that it is not actually called 1005 """ 1006 1007 1008class PickleProtocol2ReduceNewobj(PickleProtocol2ReduceTupleFunc): 1009 def __new__(cls, *args): 1010 inst = super(cls, cls).__new__(cls) 1011 inst.newargs = args 1012 return inst 1013 1014 def __reduce__(self): 1015 return ( 1016 __newobj__, # callable 1017 (PickleProtocol2ReduceNewobj, 'yam', 1), # args 1018 None, # state 1019 iter([]), # listitems 1020 iter([]), # dictitems 1021 ) 1022 1023 1024class PickleProtocol2ReduceTupleState(PickleProtocol2ReduceTuple): 1025 def __reduce__(self): 1026 return ( 1027 PickleProtocol2ReduceTuple, # callable 1028 ('yam', 1), # args 1029 {'foo': 1}, # state 1030 iter([]), # listitems 1031 iter([]), # dictitems 1032 ) 1033 1034 1035class PickleProtocol2ReduceTupleSetState(PickleProtocol2ReduceTuple): 1036 def __setstate__(self, state): 1037 self.bar = state['foo'] 1038 1039 def __reduce__(self): 1040 return ( 1041 type(self), # callable 1042 ('yam', 1), # args 1043 {'foo': 1}, # state 1044 iter([]), # listitems 1045 iter([]), # dictitems 1046 ) 1047 1048 1049class PickleProtocol2ReduceTupleStateSlots(object): 1050 __slots__ = ('argval', 'optional', 'foo') 1051 1052 def __init__(self, argval, optional=None): 1053 self.argval = argval 1054 self.optional = optional 1055 1056 def __reduce__(self): 1057 return ( 1058 PickleProtocol2ReduceTuple, # callable 1059 ('yam', 1), # args 1060 {'foo': 1}, # state 1061 iter([]), # listitems 1062 iter([]), # dictitems 1063 ) 1064 1065 1066class PickleProtocol2ReduceListitemsAppend(object): 1067 def __init__(self): 1068 self.inner = [] 1069 1070 def __reduce__(self): 1071 return ( 1072 PickleProtocol2ReduceListitemsAppend, # callable 1073 (), # args 1074 {}, # state 1075 iter(['foo', 'bar']), # listitems 1076 iter([]), # dictitems 1077 ) 1078 1079 def append(self, item): 1080 self.inner.append(item) 1081 1082 1083class PickleProtocol2ReduceListitemsExtend(object): 1084 def __init__(self): 1085 self.inner = [] 1086 1087 def __reduce__(self): 1088 return ( 1089 PickleProtocol2ReduceListitemsAppend, # callable 1090 (), # args 1091 {}, # state 1092 iter(['foo', 'bar']), # listitems 1093 iter([]), # dictitems 1094 ) 1095 1096 def extend(self, items): 1097 self.inner.exend(items) 1098 1099 1100class PickleProtocol2ReduceDictitems(object): 1101 def __init__(self): 1102 self.inner = {} 1103 1104 def __reduce__(self): 1105 return ( 1106 PickleProtocol2ReduceDictitems, # callable 1107 (), # args 1108 {}, # state 1109 [], # listitems 1110 iter(zip(['foo', 'bar'], ['foo', 'bar'])), # dictitems 1111 ) 1112 1113 def __setitem__(self, k, v): 1114 return self.inner.__setitem__(k, v) 1115 1116 1117class PickleProtocol2Classic: 1118 def __init__(self, foo): 1119 self.foo = foo 1120 1121 1122class PickleProtocol2ClassicInitargs: 1123 def __init__(self, foo, bar=None): 1124 self.foo = foo 1125 if bar: 1126 self.bar = bar 1127 1128 def __getinitargs__(self): 1129 return ('choo', 'choo') 1130 1131 1132class PicklingProtocol4TestCase(unittest.TestCase): 1133 def test_pickle_newargs_ex(self): 1134 """ 1135 Ensure we can pickle and unpickle an object whose class needs arguments 1136 to __new__ and get back the same typle 1137 """ 1138 instance = PicklableNamedTupleEx(**{'a': 'b', 'n': 2}) 1139 encoded = jsonpickle.encode(instance) 1140 decoded = jsonpickle.decode(encoded) 1141 self.assertEqual(instance, decoded) 1142 1143 def test_validate_reconstruct_by_newargs_ex(self): 1144 """ 1145 Ensure that the exemplar tuple's __getnewargs_ex__ works 1146 This is necessary to know whether the breakage exists 1147 in jsonpickle or not 1148 """ 1149 instance = PicklableNamedTupleEx(**{'a': 'b', 'n': 2}) 1150 args, kwargs = instance.__getnewargs_ex__() 1151 newinstance = PicklableNamedTupleEx.__new__( 1152 PicklableNamedTupleEx, *args, **kwargs 1153 ) 1154 self.assertEqual(instance, newinstance) 1155 1156 def test_references(self): 1157 shared = Thing('shared') 1158 instance = PicklableNamedTupleEx(**{'a': shared, 'n': shared}) 1159 child = Thing('child') 1160 shared.child = child 1161 child.child = instance 1162 1163 encoded = jsonpickle.encode(instance) 1164 decoded = jsonpickle.decode(encoded) 1165 1166 self.assertEqual(decoded[0], decoded[1]) 1167 self.assertTrue(decoded[0] is decoded[1]) 1168 self.assertTrue(decoded.a is decoded.n) 1169 self.assertEqual(decoded.a.name, 'shared') 1170 self.assertEqual(decoded.a.child.name, 'child') 1171 self.assertTrue(decoded.a.child.child is decoded) 1172 self.assertTrue(decoded.n.child.child is decoded) 1173 self.assertTrue(decoded.a.child is decoded.n.child) 1174 1175 self.assertEqual(decoded.__class__.__name__, PicklableNamedTupleEx.__name__) 1176 # TODO the class itself looks just like the real class, but it's 1177 # actually a reconstruction; PicklableNamedTupleEx is not type(decoded). 1178 self.assertFalse(decoded.__class__ is PicklableNamedTupleEx) 1179 1180 1181class PicklingProtocol2TestCase(SkippableTest): 1182 def test_classic_init_has_args(self): 1183 """ 1184 Test unpickling a classic instance whose init takes args, 1185 has no __getinitargs__ 1186 Because classic only exists under 2, skipped if PY3 1187 """ 1188 if PY3: 1189 return self.skip('No classic classes in PY3') 1190 instance = PickleProtocol2Classic(3) 1191 encoded = jsonpickle.encode(instance) 1192 decoded = jsonpickle.decode(encoded) 1193 self.assertEqual(decoded.foo, 3) 1194 1195 def test_getinitargs(self): 1196 """ 1197 Test __getinitargs__ with classic instance 1198 1199 Because classic only exists under 2, skipped if PY3 1200 """ 1201 if PY3: 1202 return self.skip('No classic classes in PY3') 1203 instance = PickleProtocol2ClassicInitargs(3) 1204 encoded = jsonpickle.encode(instance) 1205 decoded = jsonpickle.decode(encoded) 1206 self.assertEqual(decoded.bar, 'choo') 1207 1208 def test_reduce_complex_num(self): 1209 instance = 5j 1210 encoded = jsonpickle.encode(instance) 1211 decoded = jsonpickle.decode(encoded) 1212 self.assertEqual(decoded, instance) 1213 1214 def test_reduce_complex_zero(self): 1215 instance = 0j 1216 encoded = jsonpickle.encode(instance) 1217 decoded = jsonpickle.decode(encoded) 1218 self.assertEqual(decoded, instance) 1219 1220 def test_reduce_dictitems(self): 1221 'Test reduce with dictitems set (as a generator)' 1222 instance = PickleProtocol2ReduceDictitems() 1223 encoded = jsonpickle.encode(instance) 1224 decoded = jsonpickle.decode(encoded) 1225 self.assertEqual(decoded.inner, {'foo': 'foo', 'bar': 'bar'}) 1226 1227 def test_reduce_listitems_extend(self): 1228 'Test reduce with listitems set (as a generator), yielding single items' 1229 instance = PickleProtocol2ReduceListitemsExtend() 1230 encoded = jsonpickle.encode(instance) 1231 decoded = jsonpickle.decode(encoded) 1232 self.assertEqual(decoded.inner, ['foo', 'bar']) 1233 1234 def test_reduce_listitems_append(self): 1235 'Test reduce with listitems set (as a generator), yielding single items' 1236 instance = PickleProtocol2ReduceListitemsAppend() 1237 encoded = jsonpickle.encode(instance) 1238 decoded = jsonpickle.decode(encoded) 1239 self.assertEqual(decoded.inner, ['foo', 'bar']) 1240 1241 def test_reduce_state_setstate(self): 1242 """Test reduce with the optional state argument set, 1243 on an object with a __setstate__""" 1244 1245 instance = PickleProtocol2ReduceTupleSetState(5) 1246 encoded = jsonpickle.encode(instance) 1247 decoded = jsonpickle.decode(encoded) 1248 self.assertEqual(decoded.argval, 'yam') 1249 self.assertEqual(decoded.optional, 1) 1250 self.assertEqual(decoded.bar, 1) 1251 self.assertFalse(hasattr(decoded, 'foo')) 1252 1253 def test_reduce_state_no_dict(self): 1254 """Test reduce with the optional state argument set, 1255 on an object with no __dict__, and no __setstate__""" 1256 1257 instance = PickleProtocol2ReduceTupleStateSlots(5) 1258 encoded = jsonpickle.encode(instance) 1259 decoded = jsonpickle.decode(encoded) 1260 self.assertEqual(decoded.argval, 'yam') 1261 self.assertEqual(decoded.optional, 1) 1262 self.assertEqual(decoded.foo, 1) 1263 1264 def test_reduce_state_dict(self): 1265 """Test reduce with the optional state argument set, 1266 on an object with a __dict__, and no __setstate__""" 1267 1268 instance = PickleProtocol2ReduceTupleState(5) 1269 encoded = jsonpickle.encode(instance) 1270 decoded = jsonpickle.decode(encoded) 1271 self.assertEqual(decoded.argval, 'yam') 1272 self.assertEqual(decoded.optional, 1) 1273 self.assertEqual(decoded.foo, 1) 1274 1275 def test_reduce_basic(self): 1276 """Test reduce with only callable and args""" 1277 1278 instance = PickleProtocol2ReduceTuple(5) 1279 encoded = jsonpickle.encode(instance) 1280 decoded = jsonpickle.decode(encoded) 1281 self.assertEqual(decoded.argval, 'yam') 1282 self.assertEqual(decoded.optional, 1) 1283 1284 def test_reduce_basic_func(self): 1285 """Test reduce with only callable and args 1286 1287 callable is a module-level function 1288 1289 """ 1290 instance = PickleProtocol2ReduceTupleFunc(5) 1291 encoded = jsonpickle.encode(instance) 1292 decoded = jsonpickle.decode(encoded) 1293 self.assertEqual(decoded.argval, 'yam') 1294 self.assertEqual(decoded.optional, 1) 1295 1296 def test_reduce_newobj(self): 1297 """Test reduce with callable called __newobj__ 1298 1299 ensures special-case behaviour 1300 1301 """ 1302 instance = PickleProtocol2ReduceNewobj(5) 1303 encoded = jsonpickle.encode(instance) 1304 decoded = jsonpickle.decode(encoded) 1305 self.assertEqual(decoded.newargs, ('yam', 1)) 1306 1307 def test_reduce_iter(self): 1308 instance = iter('123') 1309 self.assertTrue(util.is_iterator(instance)) 1310 encoded = jsonpickle.encode(instance) 1311 decoded = jsonpickle.decode(encoded) 1312 self.assertEqual(next(decoded), '1') 1313 self.assertEqual(next(decoded), '2') 1314 self.assertEqual(next(decoded), '3') 1315 1316 def test_reduce_iterable(self): 1317 """ 1318 An reducible object should be pickleable even if 1319 it is also iterable. 1320 """ 1321 instance = ReducibleIterator() 1322 self.assertTrue(util.is_iterator(instance)) 1323 encoded = jsonpickle.encode(instance) 1324 decoded = jsonpickle.decode(encoded) 1325 self.assertIsInstance(decoded, ReducibleIterator) 1326 1327 def test_reduce_string(self): 1328 """ 1329 Ensure json pickle will accept the redirection to another object when 1330 __reduce__ returns a string 1331 """ 1332 instance = PickleProtocol2ReduceString() 1333 encoded = jsonpickle.encode(instance) 1334 decoded = jsonpickle.decode(encoded) 1335 self.assertEqual(decoded, slotmagic) 1336 1337 def test_reduce_ex_string(self): 1338 """ 1339 Ensure json pickle will accept the redirection to another object when 1340 __reduce_ex__ returns a string 1341 1342 ALSO tests that __reduce_ex__ is called in preference to __reduce__ 1343 """ 1344 instance = PickleProtocol2ReduceExString() 1345 encoded = jsonpickle.encode(instance) 1346 decoded = jsonpickle.decode(encoded) 1347 self.assertEqual(decoded, slotmagic) 1348 1349 def test_pickle_newargs(self): 1350 """ 1351 Ensure we can pickle and unpickle an object whose class needs arguments 1352 to __new__ and get back the same typle 1353 """ 1354 instance = PicklableNamedTuple(('a', 'b'), (1, 2)) 1355 encoded = jsonpickle.encode(instance) 1356 decoded = jsonpickle.decode(encoded) 1357 self.assertEqual(instance, decoded) 1358 1359 def test_validate_reconstruct_by_newargs(self): 1360 """ 1361 Ensure that the exemplar tuple's __getnewargs__ works 1362 This is necessary to know whether the breakage exists 1363 in jsonpickle or not 1364 """ 1365 instance = PicklableNamedTuple(('a', 'b'), (1, 2)) 1366 newinstance = PicklableNamedTuple.__new__( 1367 PicklableNamedTuple, *(instance.__getnewargs__()) 1368 ) 1369 self.assertEqual(instance, newinstance) 1370 1371 def test_getnewargs_priority(self): 1372 """ 1373 Ensure newargs are used before py/state when decoding 1374 (As per PEP 307, classes are not supposed to implement 1375 all three magic methods) 1376 """ 1377 instance = PickleProtocol2GetState('whatevs') 1378 encoded = jsonpickle.encode(instance) 1379 decoded = jsonpickle.decode(encoded) 1380 self.assertEqual(decoded.newargs, ('whatevs',)) 1381 1382 def test_restore_dict_state(self): 1383 """ 1384 Ensure that if getstate returns a dict, and there is no custom 1385 __setstate__, the dict is used as a source of variables to restore 1386 """ 1387 instance = PickleProtocol2GetStateDict('whatevs') 1388 encoded = jsonpickle.encode(instance) 1389 decoded = jsonpickle.decode(encoded) 1390 self.assertTrue(decoded.magic) 1391 1392 def test_restore_slots_state(self): 1393 """ 1394 Ensure that if getstate returns a 2-tuple with a dict in the second 1395 position, and there is no custom __setstate__, the dict is used as a 1396 source of variables to restore 1397 """ 1398 instance = PickleProtocol2GetStateSlots('whatevs') 1399 encoded = jsonpickle.encode(instance) 1400 decoded = jsonpickle.decode(encoded) 1401 self.assertEqual(decoded.slotmagic.__dict__, slotmagic.__dict__) 1402 self.assertEqual(decoded.slotmagic, slotmagic) 1403 1404 def test_restore_slots_dict_state(self): 1405 """ 1406 Ensure that if getstate returns a 2-tuple with a dict in both positions, 1407 and there is no custom __setstate__, the dicts are used as a source of 1408 variables to restore 1409 """ 1410 instance = PickleProtocol2GetStateSlotsDict('whatevs') 1411 encoded = jsonpickle.encode(instance) 1412 decoded = jsonpickle.decode(encoded) 1413 1414 self.assertEqual( 1415 PickleProtocol2Thing('slotmagic'), PickleProtocol2Thing('slotmagic') 1416 ) 1417 self.assertEqual(decoded.slotmagic.__dict__, slotmagic.__dict__) 1418 self.assertEqual(decoded.slotmagic, slotmagic) 1419 self.assertEqual(decoded.dictmagic, dictmagic) 1420 1421 def test_setstate(self): 1422 """ 1423 Ensure output of getstate is passed to setstate 1424 """ 1425 instance = PickleProtocol2GetSetState('whatevs') 1426 encoded = jsonpickle.encode(instance) 1427 decoded = jsonpickle.decode(encoded) 1428 self.assertTrue(decoded.magic) 1429 1430 def test_handles_nested_objects(self): 1431 child = PickleProtocol2Thing(None) 1432 instance = PickleProtocol2Thing(child, child) 1433 1434 encoded = jsonpickle.encode(instance) 1435 decoded = jsonpickle.decode(encoded) 1436 1437 self.assertEqual(PickleProtocol2Thing, decoded.__class__) 1438 self.assertEqual(PickleProtocol2Thing, decoded.args[0].__class__) 1439 self.assertEqual(PickleProtocol2Thing, decoded.args[1].__class__) 1440 self.assertTrue(decoded.args[0] is decoded.args[1]) 1441 1442 def test_cyclical_objects(self, use_tuple=True): 1443 child = Capture(None) 1444 instance = Capture(child, child) 1445 # create a cycle 1446 if use_tuple: 1447 child.args = (instance,) 1448 else: 1449 child.args = [instance] 1450 1451 encoded = jsonpickle.encode(instance) 1452 decoded = jsonpickle.decode(encoded) 1453 1454 # Ensure the right objects were constructed 1455 self.assertEqual(Capture, decoded.__class__) 1456 self.assertEqual(Capture, decoded.args[0].__class__) 1457 self.assertEqual(Capture, decoded.args[1].__class__) 1458 self.assertEqual(Capture, decoded.args[0].args[0].__class__) 1459 self.assertEqual(Capture, decoded.args[1].args[0].__class__) 1460 1461 # It's turtles all the way down 1462 self.assertEqual( 1463 Capture, 1464 decoded.args[0] 1465 .args[0] 1466 .args[0] 1467 .args[0] 1468 .args[0] 1469 .args[0] 1470 .args[0] 1471 .args[0] 1472 .args[0] 1473 .args[0] 1474 .args[0] 1475 .args[0] 1476 .args[0] 1477 .args[0] 1478 .args[0] 1479 .__class__, 1480 ) 1481 # Ensure that references are properly constructed 1482 self.assertTrue(decoded.args[0] is decoded.args[1]) 1483 self.assertTrue(decoded is decoded.args[0].args[0]) 1484 self.assertTrue(decoded is decoded.args[1].args[0]) 1485 self.assertTrue(decoded.args[0] is decoded.args[0].args[0].args[0]) 1486 self.assertTrue(decoded.args[0] is decoded.args[1].args[0].args[0]) 1487 1488 def test_cyclical_objects_list(self): 1489 self.test_cyclical_objects(use_tuple=False) 1490 1491 def test_handles_cyclical_objects_in_lists(self): 1492 child = PickleProtocol2ChildThing(None) 1493 instance = PickleProtocol2ChildThing([child, child]) 1494 child.child = instance # create a cycle 1495 1496 encoded = jsonpickle.encode(instance) 1497 decoded = jsonpickle.decode(encoded) 1498 1499 self.assertTrue(decoded is decoded.child[0].child) 1500 self.assertTrue(decoded is decoded.child[1].child) 1501 1502 def test_cyclical_objects_unpickleable_false(self, use_tuple=True): 1503 child = Capture(None) 1504 instance = Capture(child, child) 1505 # create a cycle 1506 if use_tuple: 1507 child.args = (instance,) 1508 else: 1509 child.args = [instance] 1510 encoded = jsonpickle.encode(instance, unpicklable=False) 1511 decoded = jsonpickle.decode(encoded) 1512 1513 self.assertTrue(isinstance(decoded, dict)) 1514 self.assertTrue('args' in decoded) 1515 self.assertTrue('kwargs' in decoded) 1516 1517 # Tuple is lost via json 1518 args = decoded['args'] 1519 self.assertTrue(isinstance(args, list)) 1520 1521 # Get the children 1522 self.assertEqual(len(args), 2) 1523 decoded_child0 = args[0] 1524 decoded_child1 = args[1] 1525 # Circular references become None 1526 assert decoded_child0 == {'args': [None], 'kwargs': {}} 1527 assert decoded_child1 == {'args': [None], 'kwargs': {}} 1528 1529 def test_cyclical_objects_unpickleable_false_list(self): 1530 self.test_cyclical_objects_unpickleable_false(use_tuple=False) 1531 1532 1533def test_dict_references_are_preserved(): 1534 data = {} 1535 actual = jsonpickle.decode(jsonpickle.encode([data, data])) 1536 assert isinstance(actual, list) 1537 assert isinstance(actual[0], dict) 1538 assert isinstance(actual[1], dict) 1539 assert actual[0] is actual[1] 1540 1541 1542def test_repeat_objects_are_expanded(): 1543 """Ensure that all objects are present in the json output""" 1544 # When references are disabled we should create expanded copies 1545 # of any object that appears more than once in the object stream. 1546 alice = Thing('alice') 1547 bob = Thing('bob') 1548 alice.child = bob 1549 1550 car = Thing('car') 1551 car.driver = alice 1552 car.owner = alice 1553 car.passengers = [alice, bob] 1554 1555 pickler = jsonpickle.Pickler(make_refs=False) 1556 flattened = pickler.flatten(car) 1557 1558 assert flattened['name'] == 'car' 1559 assert flattened['driver']['name'] == 'alice' 1560 assert flattened['owner']['name'] == 'alice' 1561 assert flattened['passengers'][0]['name'] == 'alice' 1562 assert flattened['passengers'][1]['name'] == 'bob' 1563 assert flattened['driver']['child']['name'] == 'bob' 1564 assert flattened['passengers'][0]['child']['name'] == 'bob' 1565 1566 1567if __name__ == '__main__': 1568 pytest.main([__file__]) 1569