-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
gh-89083: add support for UUID version 7 (RFC 9562) #121119
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.
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
Conversation
You already have three counter overflow protections:
The timestamp will not be full for about 6900 years. If the system clock stops and the timestamp is used as a counter, it will also last a long time. You can be absolutely calm |
Yes, but I wanted to know whether the RFC actually considered the case when you use your own offset. Let's say we want to generate a future UUID for some obscure reason, I was wondering "is there anything on that topics?" But I think I'll just leave it to future generations. What I meant is "what do you do if the operation of incrementing the timestamp itself overflows"? |
Don't set offsets to 6900 years or minus 2k years, and everything will be OK. Foolproofing is an implementation detail. |
When the timestamp goes beyond the upper or lower limit of the acceptable range, a zero offset can be applied. This is how I would do it. The RFC does not cover this issue. I think timestamp offset could be a competitive advantage of this implementation without significant cost. UUIDv1 can be forgotten and no longer upgraded. This is an outdated version |
Great job on this PR. One thing... In the get_counter_and_tail method: Might I suggest to explicitly specify the required byteorder using the byteorder argument? Running this code in an older python env gives an error: It seems like some default is now provided, but in my opinion, this could lead to some ambiguity. See below: There is another usage of int.from_bytes in the same uuid module, perhaps if the above is being addressed, this could be put within same scope. |
This feature would only be put in 3.14 or later, so we can ignore this.
It doesn't matter whether it's little or big endian here since we are only interested in randomness and not actual data. In addition, not specifying it might be a bit faster since the C implementation currently does: if (byteorder == NULL)
little_endian = 0;
else if (_PyUnicode_Equal(byteorder, &_Py_ID(little)))
little_endian = 1;
else if (_PyUnicode_Equal(byteorder, &_Py_ID(big)))
little_endian = 0; So, not specifying the byteorder, is equivalent to have byteorder being NULL out there, which saves a string comparison. |
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 few minor comments as I looked through this code (I'm interested in uuid7 suport in our project). Thanks for this! It's looking good.
@merwok Do you find the formulation still clear in |
@@ -919,8 +919,9 @@ urllib | |||
uuid | |||
---- | |||
|
|||
* Add support for UUID versions 6 and 8 via :func:`uuid.uuid6` and | |||
:func:`uuid.uuid8` respectively, as specified in :rfc:`9562`. | |||
* Add support for UUID versions 6, 7, and 8 via :func:`uuid.uuid6`, |
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.
Not to be pedantic, but is via correct? It seems to suggest that the functions are the only thing that support these versions, but the support is added in the UUID class and the functions are there too as a convenience.
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.
Yes, but in general, we don't really want people to directly use the UUID class. Strictly speaking, we're only adding the support for the version value but we don't check how it's been generated.
I prefer users to actually use the factories. Otherwise, I can say that the UUID class now accepts version
to be 6, 7, or 8.
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 slightly disagree but won’t argue 🙂
@@ -808,6 +809,80 @@ def uuid6(node=None, clock_seq=None): | |||
int_uuid_6 |= _RFC_4122_VERSION_6_FLAGS | |||
return UUID._from_int(int_uuid_6) | |||
|
|||
_last_timestamp_v7 = None |
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.
_last_timestamp_v7 = None | |
_last_timestamp_v7 = None |
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 wanted to apply a PEP-8 change in a separate PR because the module has inconsistencies. It seems a bit weird to only PEP-8ify this part of the code while the rest is not really PEP-8ified. See #121119 (comment).
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.
python-dev doesn’t have a practice of doing reformatting-only PRs.
Remember that consistency for its own sake is not a goal (see PEP 20)
Instead, follow good conventions in code that is added or already changed.
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.
Well... if a core dev endorses the change, I think it's fine. I don't mind endorsing it. I didn't do it for uuid6() nor for uuid8() when I wrote the function as there were more 1-blank lines separations rather than 2 blank lines separations. But if you insist on adding 2 blank lines, I'll also add them around the other functions because I prefer being consistent in this case (honestly, having 2 blank lines around only UUIDv7 makes it harder to read IMO).
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 would say PEP-8 tells me that we can also ignore the PEP if the surrounding code already breaks it. But I will make a commit to just add blank lines around the functions I've added (uuid6 to uuid8).
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 don't think that it's worth it to reformat the whole uuid.py file to PEP 8, but respecting PEP 8 for new code (or code near changed code) is a good practice.
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.
Also, adding a few blank lines is innocuous (it does not change git blame, or risk changing the meaning of code), so it’s fine to do in existing code in this PR.
Generally people saying they want to «apply PEP 8» think of more bigger changes.
[note: marking this convo as unresolved just to help Victor or Hugo see it, not because there’s something left to do for the PR 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 would say PEP-8 tells me that we can also ignore the PEP if the surrounding code already breaks it.
This is about for example methods using camelCase in unittest or logging, not spaces!
Opportunity to make new code more PEP-8.
@hugovk I eventually settled on doing PEP-8 for the new UUID functions and left the rest of the module untouched. Ideally, I really wanted a cosmetic-only PR because the module is slow to evolve, but I don't want to spend too much time trying to convince other core devs. So I'll just update new code. |
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
We finally landed that PR! Thank you everyone for the feedback and the patience :) |
) Add support for generating UUIDv7 objects according to RFC 9562, §5.7 [1]. The functionality is provided by the `uuid.uuid7()` function. The implementation is based on a 42-bit counter as described by Method 1, §6.2 [2] and guarantees monotonicity within the same millisecond. [1]: https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7 [2]: https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2 --------- Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Victor Stinner <vstinner@python.org> Co-authored-by: Éric <merwok@netwok.org>
Based on the discussion in #89083 and https://discuss.python.org/t/rfc-4122-9562-uuid-version-7-and-8-implementation/56725/2, this is the implementation that I suggest for the standard library.
The documentation is still missing because I don't have a good formulation for now.
In this PR, I did not include the following:
The reason is that I want to keep the first implementation simple for the sake of review. In addition, we did not give the add mutex for UUIDv1 so I don't want to do it only for v7.
@sergeyprokhorenko I don't know if you have the answer, but is there any safe guards if the timestamp overflows actually? or do we just don't care at all for now? (like, leave the problem for the future generations?)
📚 Documentation preview 📚: https://cpython-previews--121119.org.readthedocs.build/