bpo-22807: Expose platform UUID generation safety information. (#138)
bpo-22807: Expose platform UUID generation safety information.
This commit is contained in:
parent
ace5c0fdd9
commit
8c130d7f81
|
@ -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:
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
43
Lib/uuid.py
43
Lib/uuid.py
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue