GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bpo-22807: Expose platform UUID generation safety information. by warsaw · Pull Request #138 · python/cpython

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
warsaw merged 12 commits into python:master from warsaw:bpo-22807
Feb 18, 2017

Uh oh!

There was an error while loading. Please reload this page.

Merged

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

Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
12 commits
Select commit Hold shift + click to select a range
bbdef4c
bpo-22807: Expose platform UUID generation safety information.
warsaw Feb 16, 2017
ccefb3a
pragma-fy some lines that are okay not to get covered.
warsaw Feb 16, 2017
9040ce0
Make is_safe a keyword argument.
warsaw Feb 17, 2017
440f560
Oops, fix indentation.
warsaw Feb 17, 2017
1b3d88e
Make devguide link in README more prominent (#145)
ncoghlan Feb 18, 2017
ace5c0f
bpo-29571: Use correct locale encoding in test_re (#149)
ncoghlan Feb 18, 2017
7787df6
bpo-22807: Expose platform UUID generation safety information.
warsaw Feb 16, 2017
27753e2
pragma-fy some lines that are okay not to get covered.
warsaw Feb 16, 2017
c898201
Make is_safe a keyword argument.
warsaw Feb 17, 2017
fb3088a
Oops, fix indentation.
warsaw Feb 17, 2017
4956e88
Merge branch 'bpo-22807' of github.com:warsaw/cpython into bpo-22807
warsaw Feb 18, 2017
7b29c47
Respond to review
warsaw Feb 18, 2017
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view
Prev Previous commit
Next Next commit
bpo-22807: Expose platform UUID generation safety information.
  • Loading branch information

Uh oh!

There was an error while loading. Please reload this page.

@warsaw
warsaw committed Feb 18, 2017
commit 7787df6752ecba9727b3a3f5d52aef3698654c8d

There are no files selected for viewing

31 changes: 30 additions & 1 deletion 31 Doc/library/uuid.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -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
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

.. attribute:: SafeUUID.safe
Comment thread Outdated

@berkerpeksag berkerpeksag Feb 17, 2017

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is the current style in uuid.rst, but it might be better use the following style in new documentation:

.. class:: SafeUUID

   .. versionadded:: 3.7

   .. attribute:: safe
      The UUID was generated by the platform in a multiprocessing-safe way.

   ...

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@warsaw warsaw Feb 18, 2017

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks nice, thanks. The one problem is that SafeUUID still gets labeled as a class in the HTML. Okay, technically it is a class, but it would be nice to have an enum:: markup. I grep'd around in the source and couldn't find any such examples, so I don't know if we support that yet.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@berkerpeksag berkerpeksag Feb 18, 2017

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. We don't have an enum directive now, but it should be easy to add one. You can add me to nosy list if you create an issue on bugs.p.o :)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

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.

.. versionadded:: 3.7

.. 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
bytes as the *bytes* argument, a string of 16 bytes in little-endian order as
Expand Down Expand Up @@ -120,6 +142,13 @@ random UUID.
The UUID version number (1 through 5, meaningful only when the variant is
: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:


Expand Down
40 changes: 40 additions & 0 deletions 40 Lib/test/test_uuid.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,46 @@ def test_uuid1(self):
equal(((u.clock_seq_hi_variant & 0x3f) << 8) |
u.clock_seq_low, 0x3fff)

@unittest.skipUnless(importable('ctypes'), 'requires ctypes')
Comment thread Outdated

@berkerpeksag berkerpeksag Feb 17, 2017

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I'd flip the order of the decorators.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@warsaw warsaw Feb 18, 2017

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@unittest.skipUnless(uuid._uuid_generate_time.restype is not None,
'requires uuid_generate_time_safe(3)')
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')
Comment thread

@berkerpeksag berkerpeksag Feb 17, 2017

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a suggestion: Using self.subTest() would make the following four tests much shorter and IMO easier to understand.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@warsaw warsaw Feb 18, 2017

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've played a bit with subTest() and I'm not entirely thrilled with it. AFAICT, subtests only print output when they fail, not when they succeed. In verbose mode, I'd want to see all 4 tests printed individually. (That's one of the things I'd like to eventually fix.) For now, I think I'll leave them as separate tests.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
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):
equal = self.assertEqual

Expand Down
36 changes: 31 additions & 5 deletions 36 Lib/uuid.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@

import os

from enum import Enum


__author__ = 'Ka-Ping Yee <[email protected]>'

RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
Expand All @@ -55,7 +58,14 @@
int_ = int # The built-in int 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.
UUID objects are immutable, hashable, and usable as dictionary keys.
Converting a UUID to a string with str() yields something in the form
Expand Down Expand Up @@ -104,7 +114,7 @@ class UUID(object):
"""

def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
int=None, version=None):
int=None, version=None, is_safe=SafeUUID.unknown):
Comment thread Outdated

@Carreau Carreau Feb 17, 2017

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind on whether this should affect older versions of Python.

If so, you can also consider making it kwarg-only. As you are already using is as a kwarg. Not sure if that's useful, or worth it. It's may also be a question of taste, and leaving it as is seem perfectly fine.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@warsaw warsaw Feb 17, 2017

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! I did think about that, and it might indeed make sense, but it's a matter of style in this case, and I wasn't sure I wanted to introduce that to the API. But on thinking about it again now that you bring it up, I does make sense.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
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
in little-endian order as the 'bytes_le' argument, a tuple of six
Expand All @@ -128,6 +138,10 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
be given. The 'version' argument is optional; if given, the resulting
UUID will have its variant and version set according to RFC 4122,
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:
Expand Down Expand Up @@ -182,6 +196,7 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
int &= ~(0xf000 << 64)
int |= version << 76
self.__dict__['int'] = int
self.__dict__['is_safe'] = is_safe
Comment thread

@Carreau Carreau Feb 17, 2017

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to assert the type of is_safe ? There seem to extensive checking of the rest of the parameters.
Not that I imagine people creating UUID object in their own code, but is_safe feel also a lot like a Boolean.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@warsaw warsaw Feb 17, 2017

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it's worth type checking is_safe. The other ones are checked to ensure sane, compliant UUIDs, but is_safe is just a flag with some extra information (which may or may not be helpful to an application). If someone wants to manually instantiate UUIDs with bogus is_safe flags, oh well.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

def __eq__(self, other):
if isinstance(other, UUID):
Expand Down Expand Up @@ -474,8 +489,15 @@ def _netbios_getnode():
lib = ctypes.CDLL(ctypes.util.find_library(libname))
except Exception:
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'):
_uuid_generate_time = lib.uuid_generate_time
# void uuid_generate_time(uuid_t out);
_uuid_generate_time.restype = None
break
del _libnames

Expand Down Expand Up @@ -566,8 +588,12 @@ def uuid1(node=None, clock_seq=None):
# use UuidCreate here because its UUIDs don't conform to RFC 4122).
if _uuid_generate_time and node is clock_seq is None:
_buffer = ctypes.create_string_buffer(16)
_uuid_generate_time(_buffer)
return UUID(bytes=bytes_(_buffer.raw))
safely_generated = _uuid_generate_time(_buffer)
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
import time
Expand Down
4 changes: 4 additions & 0 deletions 4 Misc/NEWS
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ Extension Modules
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
now emit DeprecationWarnings and have better descriptive messages.

Expand Down
Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.