bpo-22807: Expose platform UUID generation safety information. (#138)

bpo-22807: Expose platform UUID generation safety information.
This commit is contained in:
Barry Warsaw 2017-02-18 15:45:49 -05:00 committed by GitHub
parent ace5c0fdd9
commit 8c130d7f81
4 changed files with 111 additions and 7 deletions

View File

@ -19,8 +19,30 @@ If all you want is a unique ID, you should probably call :func:`uuid1` or
a UUID containing the computer's network address. :func:`uuid4` creates a a UUID containing the computer's network address. :func:`uuid4` creates a
random UUID. random UUID.
Depending on support from the underlying platform, :func:`uuid1` may or may
not return a "safe" UUID. A safe UUID is one which is generated using
synchronization methods that ensure no two processes can obtain the same
UUID. All instances of :class:`UUID` have an :attr:`is_safe` attribute
which relays any information about the UUID's safety, using this enumeration:
.. class:: UUID(hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None) .. class:: SafeUUID
.. versionadded:: 3.7
.. attribute:: SafeUUID.safe
The UUID was generated by the platform in a multiprocessing-safe way.
.. attribute:: SafeUUID.unsafe
The UUID was not generated in a multiprocessing-safe way.
.. attribute:: SafeUUID.unknown
The platform does not provide information on whether the UUID was
generated safely or not.
.. class:: UUID(hex=None, bytes=None, bytes_le=None, fields=None, int=None, version=None, *, is_safe=SafeUUID.unknown)
Create a UUID from either a string of 32 hexadecimal digits, a string of 16 Create a UUID from either a string of 32 hexadecimal digits, a string of 16
bytes as the *bytes* argument, a string of 16 bytes in little-endian order as bytes as the *bytes* argument, a string of 16 bytes in little-endian order as
@ -120,6 +142,13 @@ random UUID.
The UUID version number (1 through 5, meaningful only when the variant is The UUID version number (1 through 5, meaningful only when the variant is
:const:`RFC_4122`). :const:`RFC_4122`).
.. attribute:: UUID.is_safe
An enumeration of :class:`SafeUUID` which indicates whether the platform
generated the UUID in a multiprocessing-safe way.
.. versionadded:: 3.7
The :mod:`uuid` module defines the following functions: The :mod:`uuid` module defines the following functions:

View File

@ -340,6 +340,46 @@ class TestUUID(unittest.TestCase):
equal(((u.clock_seq_hi_variant & 0x3f) << 8) | equal(((u.clock_seq_hi_variant & 0x3f) << 8) |
u.clock_seq_low, 0x3fff) u.clock_seq_low, 0x3fff)
@unittest.skipUnless(uuid._uuid_generate_time.restype is not None,
'requires uuid_generate_time_safe(3)')
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_uuid1_safe(self):
u = uuid.uuid1()
# uuid_generate_time_safe() may return 0 or -1 but what it returns is
# dependent on the underlying platform support. At least it cannot be
# unknown (unless I suppose the platform is buggy).
self.assertNotEqual(u.is_safe, uuid.SafeUUID.unknown)
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_uuid1_unknown(self):
# Even if the platform has uuid_generate_time_safe(), let's mock it to
# be uuid_generate_time() and ensure the safety is unknown.
with unittest.mock.patch.object(uuid._uuid_generate_time,
'restype', None):
u = uuid.uuid1()
self.assertEqual(u.is_safe, uuid.SafeUUID.unknown)
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_uuid1_is_safe(self):
with unittest.mock.patch.object(uuid._uuid_generate_time,
'restype', lambda x: 0):
u = uuid.uuid1()
self.assertEqual(u.is_safe, uuid.SafeUUID.safe)
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_uuid1_is_unsafe(self):
with unittest.mock.patch.object(uuid._uuid_generate_time,
'restype', lambda x: -1):
u = uuid.uuid1()
self.assertEqual(u.is_safe, uuid.SafeUUID.unsafe)
@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
def test_uuid1_bogus_return_value(self):
with unittest.mock.patch.object(uuid._uuid_generate_time,
'restype', lambda x: 3):
u = uuid.uuid1()
self.assertEqual(u.is_safe, uuid.SafeUUID.unknown)
def test_uuid3(self): def test_uuid3(self):
equal = self.assertEqual equal = self.assertEqual

View File

@ -46,6 +46,9 @@ Typical usage:
import os import os
from enum import Enum
__author__ = 'Ka-Ping Yee <ping@zesty.ca>' __author__ = 'Ka-Ping Yee <ping@zesty.ca>'
RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [ RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
@ -55,7 +58,14 @@ RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
int_ = int # The built-in int type int_ = int # The built-in int type
bytes_ = bytes # The built-in bytes type bytes_ = bytes # The built-in bytes type
class UUID(object):
class SafeUUID(Enum):
safe = 0
unsafe = -1
unknown = None
class UUID:
"""Instances of the UUID class represent UUIDs as specified in RFC 4122. """Instances of the UUID class represent UUIDs as specified in RFC 4122.
UUID objects are immutable, hashable, and usable as dictionary keys. UUID objects are immutable, hashable, and usable as dictionary keys.
Converting a UUID to a string with str() yields something in the form Converting a UUID to a string with str() yields something in the form
@ -101,10 +111,15 @@ class UUID(object):
version the UUID version number (1 through 5, meaningful only version the UUID version number (1 through 5, meaningful only
when the variant is RFC_4122) when the variant is RFC_4122)
is_safe An enum indicating whether the UUID has been generated in
a way that is safe for multiprocessing applications, via
uuid_generate_time_safe(3).
""" """
def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None, def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
int=None, version=None): int=None, version=None,
*, is_safe=SafeUUID.unknown):
r"""Create a UUID from either a string of 32 hexadecimal digits, r"""Create a UUID from either a string of 32 hexadecimal digits,
a string of 16 bytes as the 'bytes' argument, a string of 16 bytes a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
in little-endian order as the 'bytes_le' argument, a tuple of six in little-endian order as the 'bytes_le' argument, a tuple of six
@ -128,6 +143,10 @@ class UUID(object):
be given. The 'version' argument is optional; if given, the resulting be given. The 'version' argument is optional; if given, the resulting
UUID will have its variant and version set according to RFC 4122, UUID will have its variant and version set according to RFC 4122,
overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'. overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
is_safe is an enum exposed as an attribute on the instance. It
indicates whether the UUID has been generated in a way that is safe
for multiprocessing applications, via uuid_generate_time_safe(3).
""" """
if [hex, bytes, bytes_le, fields, int].count(None) != 4: if [hex, bytes, bytes_le, fields, int].count(None) != 4:
@ -182,6 +201,7 @@ class UUID(object):
int &= ~(0xf000 << 64) int &= ~(0xf000 << 64)
int |= version << 76 int |= version << 76
self.__dict__['int'] = int self.__dict__['int'] = int
self.__dict__['is_safe'] = is_safe
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, UUID): if isinstance(other, UUID):
@ -472,10 +492,17 @@ try:
for libname in _libnames: for libname in _libnames:
try: try:
lib = ctypes.CDLL(ctypes.util.find_library(libname)) lib = ctypes.CDLL(ctypes.util.find_library(libname))
except Exception: except Exception: # pragma: nocover
continue continue
if hasattr(lib, 'uuid_generate_time'): # Try to find the safe variety first.
if hasattr(lib, 'uuid_generate_time_safe'):
_uuid_generate_time = lib.uuid_generate_time_safe
# int uuid_generate_time_safe(uuid_t out);
break
elif hasattr(lib, 'uuid_generate_time'): # pragma: nocover
_uuid_generate_time = lib.uuid_generate_time _uuid_generate_time = lib.uuid_generate_time
# void uuid_generate_time(uuid_t out);
_uuid_generate_time.restype = None
break break
del _libnames del _libnames
@ -566,8 +593,12 @@ def uuid1(node=None, clock_seq=None):
# use UuidCreate here because its UUIDs don't conform to RFC 4122). # use UuidCreate here because its UUIDs don't conform to RFC 4122).
if _uuid_generate_time and node is clock_seq is None: if _uuid_generate_time and node is clock_seq is None:
_buffer = ctypes.create_string_buffer(16) _buffer = ctypes.create_string_buffer(16)
_uuid_generate_time(_buffer) safely_generated = _uuid_generate_time(_buffer)
return UUID(bytes=bytes_(_buffer.raw)) try:
is_safe = SafeUUID(safely_generated)
except ValueError:
is_safe = SafeUUID.unknown
return UUID(bytes=bytes_(_buffer.raw), is_safe=is_safe)
global _last_timestamp global _last_timestamp
import time import time

View File

@ -229,6 +229,10 @@ Extension Modules
Library Library
------- -------
- bpo-22807: Add uuid.SafeUUID and uuid.UUID.is_safe to relay information from
the platform about whether generated UUIDs are generated with a
multiprocessing safe method.
- bpo-29576: Improve some deprecations in importlib. Some deprecated methods - bpo-29576: Improve some deprecations in importlib. Some deprecated methods
now emit DeprecationWarnings and have better descriptive messages. now emit DeprecationWarnings and have better descriptive messages.