@@ -85,6 +85,17 @@ class SafeUUID:
85
85
unknown = None
86
86
87
87
88
+ _UINT_128_MAX = (1 << 128 ) - 1
89
+ # 128-bit mask to clear the variant and version bits of a UUID integral value
90
+ _RFC_4122_CLEARFLAGS_MASK = ~ ((0xf000 << 64 ) | (0xc000 << 48 ))
91
+ # RFC 4122 variant bits and version bits to activate on a UUID integral value.
92
+ _RFC_4122_VERSION_1_FLAGS = ((1 << 76 ) | (0x8000 << 48 ))
93
+ _RFC_4122_VERSION_3_FLAGS = ((3 << 76 ) | (0x8000 << 48 ))
94
+ _RFC_4122_VERSION_4_FLAGS = ((4 << 76 ) | (0x8000 << 48 ))
95
+ _RFC_4122_VERSION_5_FLAGS = ((5 << 76 ) | (0x8000 << 48 ))
96
+ _RFC_4122_VERSION_8_FLAGS = ((8 << 76 ) | (0x8000 << 48 ))
97
+
98
+
88
99
class UUID :
89
100
"""Instances of the UUID class represent UUIDs as specified in RFC 4122.
90
101
UUID objects are immutable, hashable, and usable as dictionary keys.
@@ -174,57 +185,69 @@ def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
174
185
if [hex , bytes , bytes_le , fields , int ].count (None ) != 4 :
175
186
raise TypeError ('one of the hex, bytes, bytes_le, fields, '
176
187
'or int arguments must be given' )
177
- if hex is not None :
188
+ if int is not None :
189
+ pass
190
+ elif hex is not None :
178
191
hex = hex .replace ('urn:' , '' ).replace ('uuid:' , '' )
179
192
hex = hex .strip ('{}' ).replace ('-' , '' )
180
193
if len (hex ) != 32 :
181
194
raise ValueError ('badly formed hexadecimal UUID string' )
182
195
int = int_ (hex , 16 )
183
- if bytes_le is not None :
196
+ elif bytes_le is not None :
184
197
if len (bytes_le ) != 16 :
185
198
raise ValueError ('bytes_le is not a 16-char string' )
199
+ assert isinstance (bytes_le , bytes_ ), repr (bytes_le )
186
200
bytes = (bytes_le [4 - 1 ::- 1 ] + bytes_le [6 - 1 :4 - 1 :- 1 ] +
187
201
bytes_le [8 - 1 :6 - 1 :- 1 ] + bytes_le [8 :])
188
- if bytes is not None :
202
+ int = int_ .from_bytes (bytes ) # big endian
203
+ elif bytes is not None :
189
204
if len (bytes ) != 16 :
190
205
raise ValueError ('bytes is not a 16-char string' )
191
206
assert isinstance (bytes , bytes_ ), repr (bytes )
192
207
int = int_ .from_bytes (bytes ) # big endian
193
- if fields is not None :
208
+ elif fields is not None :
194
209
if len (fields ) != 6 :
195
210
raise ValueError ('fields is not a 6-tuple' )
196
211
(time_low , time_mid , time_hi_version ,
197
212
clock_seq_hi_variant , clock_seq_low , node ) = fields
198
- if not 0 <= time_low < 1 << 32 :
213
+ if not 0 <= time_low < ( 1 << 32 ) :
199
214
raise ValueError ('field 1 out of range (need a 32-bit value)' )
200
- if not 0 <= time_mid < 1 << 16 :
215
+ if not 0 <= time_mid < ( 1 << 16 ) :
201
216
raise ValueError ('field 2 out of range (need a 16-bit value)' )
202
- if not 0 <= time_hi_version < 1 << 16 :
217
+ if not 0 <= time_hi_version < ( 1 << 16 ) :
203
218
raise ValueError ('field 3 out of range (need a 16-bit value)' )
204
- if not 0 <= clock_seq_hi_variant < 1 << 8 :
219
+ if not 0 <= clock_seq_hi_variant < ( 1 << 8 ) :
205
220
raise ValueError ('field 4 out of range (need an 8-bit value)' )
206
- if not 0 <= clock_seq_low < 1 << 8 :
221
+ if not 0 <= clock_seq_low < ( 1 << 8 ) :
207
222
raise ValueError ('field 5 out of range (need an 8-bit value)' )
208
- if not 0 <= node < 1 << 48 :
223
+ if not 0 <= node < ( 1 << 48 ) :
209
224
raise ValueError ('field 6 out of range (need a 48-bit value)' )
210
225
clock_seq = (clock_seq_hi_variant << 8 ) | clock_seq_low
211
226
int = ((time_low << 96 ) | (time_mid << 80 ) |
212
227
(time_hi_version << 64 ) | (clock_seq << 48 ) | node )
213
- if int is not None :
214
- if not 0 <= int < 1 << 128 :
215
- raise ValueError ('int is out of range (need a 128-bit value)' )
228
+ if not 0 <= int <= _UINT_128_MAX :
229
+ raise ValueError ('int is out of range (need a 128-bit value)' )
216
230
if version is not None :
217
231
if not 1 <= version <= 8 :
218
232
raise ValueError ('illegal version number' )
233
+ # clear the variant and the version number bits
234
+ int &= _RFC_4122_CLEARFLAGS_MASK
219
235
# Set the variant to RFC 4122/9562.
220
- int &= ~ (0xc000 << 48 )
221
- int |= 0x8000 << 48
236
+ int |= 0x8000_0000_0000_0000 # (0x8000 << 48)
222
237
# Set the version number.
223
- int &= ~ (0xf000 << 64 )
224
238
int |= version << 76
225
239
object .__setattr__ (self , 'int' , int )
226
240
object .__setattr__ (self , 'is_safe' , is_safe )
227
241
242
+ @classmethod
243
+ def _from_int (cls , value ):
244
+ """Create a UUID from an integer *value*. Internal use only."""
245
+ assert 0 <= value <= _UINT_128_MAX , repr (value )
246
+ self = object .__new__ (cls )
247
+ object .__setattr__ (self , 'int' , value )
248
+ object .__setattr__ (self , 'is_safe' , SafeUUID .unknown )
249
+ return self
250
+
228
251
def __getstate__ (self ):
229
252
d = {'int' : self .int }
230
253
if self .is_safe != SafeUUID .unknown :
@@ -700,24 +723,30 @@ def uuid3(namespace, name):
700
723
"""Generate a UUID from the MD5 hash of a namespace UUID and a name."""
701
724
if isinstance (name , str ):
702
725
name = bytes (name , "utf-8" )
703
- from hashlib import md5
704
- digest = md5 (
705
- namespace . bytes + name ,
706
- usedforsecurity = False
707
- ). digest ()
708
- return UUID ( bytes = digest [: 16 ], version = 3 )
726
+ import hashlib
727
+ h = hashlib . md5 (namespace . bytes + name , usedforsecurity = False )
728
+ int_uuid_3 = int . from_bytes ( h . digest ())
729
+ int_uuid_3 &= _RFC_4122_CLEARFLAGS_MASK
730
+ int_uuid_3 |= _RFC_4122_VERSION_3_FLAGS
731
+ return UUID . _from_int ( int_uuid_3 )
709
732
710
733
def uuid4 ():
711
734
"""Generate a random UUID."""
712
- return UUID (bytes = os .urandom (16 ), version = 4 )
735
+ int_uuid_4 = int .from_bytes (os .urandom (16 ))
736
+ int_uuid_4 &= _RFC_4122_CLEARFLAGS_MASK
737
+ int_uuid_4 |= _RFC_4122_VERSION_4_FLAGS
738
+ return UUID ._from_int (int_uuid_4 )
713
739
714
740
def uuid5 (namespace , name ):
715
741
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
716
742
if isinstance (name , str ):
717
743
name = bytes (name , "utf-8" )
718
- from hashlib import sha1
719
- hash = sha1 (namespace .bytes + name ).digest ()
720
- return UUID (bytes = hash [:16 ], version = 5 )
744
+ import hashlib
745
+ h = hashlib .sha1 (namespace .bytes + name , usedforsecurity = False )
746
+ int_uuid_5 = int .from_bytes (h .digest ()[:16 ])
747
+ int_uuid_5 &= _RFC_4122_CLEARFLAGS_MASK
748
+ int_uuid_5 |= _RFC_4122_VERSION_5_FLAGS
749
+ return UUID ._from_int (int_uuid_5 )
721
750
722
751
def uuid8 (a = None , b = None , c = None ):
723
752
"""Generate a UUID from three custom blocks.
@@ -740,7 +769,9 @@ def uuid8(a=None, b=None, c=None):
740
769
int_uuid_8 = (a & 0xffff_ffff_ffff ) << 80
741
770
int_uuid_8 |= (b & 0xfff ) << 64
742
771
int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
743
- return UUID (int = int_uuid_8 , version = 8 )
772
+ # by construction, the variant and version bits are already cleared
773
+ int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
774
+ return UUID ._from_int (int_uuid_8 )
744
775
745
776
def main ():
746
777
"""Run the uuid command line interface."""
0 commit comments