Skip to content

Commit d53f1ca

Browse files
authored
[3.7] bpo-34621: fix uuid.UUID (un)pickling compatbility with older Python versions (<3.7) (GH-9133)
1 parent e9119a5 commit d53f1ca

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

Lib/test/test_uuid.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
from test import support
33
import builtins
44
import contextlib
5+
import copy
56
import io
67
import os
8+
import pickle
79
import shutil
810
import subprocess
11+
import sys
912

1013
py_uuid = support.import_fresh_module('uuid', blocked=['_uuid'])
1114
c_uuid = support.import_fresh_module('uuid', fresh=['_uuid'])
@@ -311,6 +314,140 @@ def test_getnode(self):
311314
node2 = self.uuid.getnode()
312315
self.assertEqual(node1, node2, '%012x != %012x' % (node1, node2))
313316

317+
def test_pickle_roundtrip(self):
318+
def check(actual, expected):
319+
self.assertEqual(actual, expected)
320+
self.assertEqual(actual.is_safe, expected.is_safe)
321+
322+
with support.swap_item(sys.modules, 'uuid', self.uuid):
323+
for is_safe in self.uuid.SafeUUID:
324+
u = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5',
325+
is_safe=is_safe)
326+
check(copy.copy(u), u)
327+
check(copy.deepcopy(u), u)
328+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
329+
with self.subTest(protocol=proto):
330+
check(pickle.loads(pickle.dumps(u, proto)), u)
331+
332+
def test_unpickle_previous_python_versions(self):
333+
def check(actual, expected):
334+
self.assertEqual(actual, expected)
335+
self.assertEqual(actual.is_safe, expected.is_safe)
336+
337+
pickled_uuids = [
338+
# Python 2.7, protocol 0
339+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
340+
b'tR(dS\'int\'\nL287307832597519156748809049798316161701L\nsb.',
341+
# Python 2.7, protocol 1
342+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
343+
b'tR}U\x03intL287307832597519156748809049798316161701L\nsb.',
344+
# Python 2.7, protocol 2
345+
b'\x80\x02cuuid\nUUID\n)\x81}U\x03int\x8a\x11\xa5z\xecz\nI\xdf}'
346+
b'\xde\xa0Bf\xcey%\xd8\x00sb.',
347+
# Python 3.6, protocol 0
348+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
349+
b'tR(dVint\nL287307832597519156748809049798316161701L\nsb.',
350+
# Python 3.6, protocol 1
351+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
352+
b'tR}X\x03\x00\x00\x00intL287307832597519156748809049798316161701L'
353+
b'\nsb.',
354+
# Python 3.6, protocol 2
355+
b'\x80\x02cuuid\nUUID\n)\x81}X\x03\x00\x00\x00int\x8a\x11\xa5z\xec'
356+
b'z\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00sb.',
357+
# Python 3.6, protocol 3
358+
b'\x80\x03cuuid\nUUID\n)\x81}X\x03\x00\x00\x00int\x8a\x11\xa5z\xec'
359+
b'z\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00sb.',
360+
# Python 3.6, protocol 4
361+
b'\x80\x04\x95+\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x8c\x04UUI'
362+
b'D\x93)\x81}\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0Bf\xcey%'
363+
b'\xd8\x00sb.',
364+
# Python 3.7, protocol 0
365+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
366+
b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n'
367+
b'cuuid\nSafeUUID\n(NtRsb.',
368+
# Python 3.7, protocol 1
369+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
370+
b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701'
371+
b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(NtRub.',
372+
# Python 3.7, protocol 2
373+
b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
374+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
375+
b'id\nSafeUUID\nN\x85Rub.',
376+
# Python 3.7, protocol 3
377+
b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
378+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
379+
b'id\nSafeUUID\nN\x85Rub.',
380+
# Python 3.7, protocol 4
381+
b'\x80\x04\x95F\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c'
382+
b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0'
383+
b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93N\x85Rub'
384+
b'.',
385+
]
386+
pickled_uuids_safe = [
387+
# Python 3.7, protocol 0
388+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
389+
b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n'
390+
b'cuuid\nSafeUUID\n(I0\ntRsb.',
391+
# Python 3.7, protocol 1
392+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
393+
b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701'
394+
b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(K\x00tRub.',
395+
# Python 3.7, protocol 2
396+
b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
397+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
398+
b'id\nSafeUUID\nK\x00\x85Rub.',
399+
# Python 3.7, protocol 3
400+
b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
401+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
402+
b'id\nSafeUUID\nK\x00\x85Rub.',
403+
# Python 3.7, protocol 4
404+
b'\x80\x04\x95G\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c'
405+
b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0'
406+
b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93K\x00'
407+
b'\x85Rub.',
408+
]
409+
pickled_uuids_unsafe = [
410+
# Python 3.7, protocol 0
411+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
412+
b'tR(dVint\nL287307832597519156748809049798316161701L\nsVis_safe\n'
413+
b'cuuid\nSafeUUID\n(I-1\ntRsb.',
414+
# Python 3.7, protocol 1
415+
b'ccopy_reg\n_reconstructor\n(cuuid\nUUID\nc__builtin__\nobject\nN'
416+
b'tR}(X\x03\x00\x00\x00intL287307832597519156748809049798316161701'
417+
b'L\nX\x07\x00\x00\x00is_safecuuid\nSafeUUID\n(J\xff\xff\xff\xfftR'
418+
b'ub.',
419+
# Python 3.7, protocol 2
420+
b'\x80\x02cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
421+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
422+
b'id\nSafeUUID\nJ\xff\xff\xff\xff\x85Rub.',
423+
# Python 3.7, protocol 3
424+
b'\x80\x03cuuid\nUUID\n)\x81}(X\x03\x00\x00\x00int\x8a\x11\xa5z'
425+
b'\xecz\nI\xdf}\xde\xa0Bf\xcey%\xd8\x00X\x07\x00\x00\x00is_safecuu'
426+
b'id\nSafeUUID\nJ\xff\xff\xff\xff\x85Rub.',
427+
# Python 3.7, protocol 4
428+
b'\x80\x04\x95J\x00\x00\x00\x00\x00\x00\x00\x8c\x04uuid\x94\x8c'
429+
b'\x04UUID\x93)\x81}(\x8c\x03int\x8a\x11\xa5z\xecz\nI\xdf}\xde\xa0'
430+
b'Bf\xcey%\xd8\x00\x8c\x07is_safeh\x00\x8c\x08SafeUUID\x93J\xff'
431+
b'\xff\xff\xff\x85Rub.',
432+
]
433+
434+
u = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5')
435+
u_safe = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5',
436+
is_safe=self.uuid.SafeUUID.safe)
437+
u_unsafe = self.uuid.UUID('d82579ce6642a0de7ddf490a7aec7aa5',
438+
is_safe=self.uuid.SafeUUID.unsafe)
439+
440+
with support.swap_item(sys.modules, 'uuid', self.uuid):
441+
for pickled in pickled_uuids:
442+
# is_safe was added in 3.7. When unpickling values from older
443+
# versions, is_safe will be missing, so it should be set to
444+
# SafeUUID.unknown.
445+
check(pickle.loads(pickled), u)
446+
for pickled in pickled_uuids_safe:
447+
check(pickle.loads(pickled), u_safe)
448+
for pickled in pickled_uuids_unsafe:
449+
check(pickle.loads(pickled), u_unsafe)
450+
314451
# bpo-32502: UUID1 requires a 48-bit identifier, but hardware identifiers
315452
# need not necessarily be 48 bits (e.g., EUI-64).
316453
def test_uuid1_eui64(self):

Lib/uuid.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,23 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
204204
self.__dict__['int'] = int
205205
self.__dict__['is_safe'] = is_safe
206206

207+
def __getstate__(self):
208+
state = self.__dict__
209+
if self.is_safe != SafeUUID.unknown:
210+
# is_safe is a SafeUUID instance. Return just its value, so that
211+
# it can be un-pickled in older Python versions without SafeUUID.
212+
state = state.copy()
213+
state['is_safe'] = self.is_safe.value
214+
return state
215+
216+
def __setstate__(self, state):
217+
self.__dict__.update(state)
218+
# is_safe was added in 3.7; it is also omitted when it is "unknown"
219+
self.__dict__['is_safe'] = (
220+
SafeUUID(state['is_safe'])
221+
if 'is_safe' in state else SafeUUID.unknown
222+
)
223+
207224
def __eq__(self, other):
208225
if isinstance(other, UUID):
209226
return self.int == other.int
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix un/pickling compatbility of uuid.UUID objects with older versions of
2+
Python (<3.7).

0 commit comments

Comments
 (0)