Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/uuid6/__init__.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

87 statements  

1r"""UUID objects (universally unique identifiers) according to RFC 9562. 

2 

3This module provides the functions uuid6(), uuid7(), and uuid8() for 

4generating version 6, 7, and 8 UUIDs as specified in RFC 9562. 

5""" 

6 

7import importlib.metadata 

8import secrets 

9import time 

10import uuid 

11from typing import Optional, Tuple 

12 

13__version__ = importlib.metadata.version(__package__) 

14 

15 

16class UUID(uuid.UUID): 

17 r"""Instances of the UUID class represent UUIDs as specified in RFC 9562.""" 

18 

19 __slots__ = () 

20 

21 def __init__( 

22 self, 

23 hex: Optional[str] = None, 

24 bytes: Optional[bytes] = None, 

25 bytes_le: Optional[bytes] = None, 

26 fields: Optional[Tuple[int, int, int, int, int, int]] = None, 

27 int: Optional[int] = None, 

28 version: Optional[int] = None, 

29 *, 

30 is_safe: uuid.SafeUUID = uuid.SafeUUID.unknown, 

31 ) -> None: 

32 r"""Create a UUID.""" 

33 

34 if int is None or [hex, bytes, bytes_le, fields].count(None) != 4: 

35 return super().__init__( 

36 hex=hex, 

37 bytes=bytes, 

38 bytes_le=bytes_le, 

39 fields=fields, 

40 int=int, 

41 version=version, 

42 is_safe=is_safe, 

43 ) 

44 if not 0 <= int < 1 << 128: 

45 raise ValueError("int is out of range (need a 128-bit value)") 

46 if version is not None: 

47 if not 6 <= version <= 8: 

48 raise ValueError("illegal version number") 

49 # Set the variant to RFC 4122. 

50 int &= ~(0xC000 << 48) 

51 int |= 0x8000 << 48 

52 # Set the version number. 

53 int &= ~(0xF000 << 64) 

54 int |= version << 76 

55 super().__init__(int=int, is_safe=is_safe) 

56 

57 @property 

58 def subsec(self) -> int: 

59 return ((self.int >> 64) & 0x0FFF) << 8 | ((self.int >> 54) & 0xFF) 

60 

61 @property 

62 def time(self) -> int: 

63 if self.version == 6: 

64 return ( 

65 (self.time_low << 28) 

66 | (self.time_mid << 12) 

67 | (self.time_hi_version & 0x0FFF) 

68 ) 

69 if self.version == 7: 

70 return self.int >> 80 

71 if self.version == 8: 

72 return (self.int >> 80) * 10**6 + _subsec_decode(self.subsec) 

73 return super().time 

74 

75 

76def _subsec_decode(value: int) -> int: 

77 return -(-value * 10**6 // 2**20) 

78 

79 

80def _subsec_encode(value: int) -> int: 

81 return value * 2**20 // 10**6 

82 

83 

84def uuid1_to_uuid6(uuid1: uuid.UUID) -> UUID: 

85 r"""Generate a UUID version 6 object from a UUID version 1 object.""" 

86 if uuid1.version != 1: 

87 raise ValueError("given UUID's version number must be 1") 

88 h = uuid1.hex 

89 h = h[13:16] + h[8:12] + h[0:5] + "6" + h[5:8] + h[16:] 

90 return UUID(hex=h, is_safe=uuid1.is_safe) 

91 

92 

93_last_v6_timestamp = None 

94_last_v7_timestamp = None 

95_last_v8_timestamp = None 

96 

97 

98def uuid6(node: Optional[int] = None, clock_seq: Optional[int] = None) -> UUID: 

99 r"""UUID version 6 is a field-compatible version of UUIDv1, reordered for 

100 improved DB locality. It is expected that UUIDv6 will primarily be 

101 used in contexts where there are existing v1 UUIDs. Systems that do 

102 not involve legacy UUIDv1 SHOULD consider using UUIDv7 instead. 

103 

104 If 'node' is not given, a random 48-bit number is chosen. 

105 

106 If 'clock_seq' is given, it is used as the sequence number; 

107 otherwise a random 14-bit sequence number is chosen.""" 

108 

109 global _last_v6_timestamp 

110 

111 nanoseconds = time.time_ns() 

112 # 0x01b21dd213814000 is the number of 100-ns intervals between the 

113 # UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00. 

114 timestamp = nanoseconds // 100 + 0x01B21DD213814000 

115 if _last_v6_timestamp is not None and timestamp <= _last_v6_timestamp: 

116 timestamp = _last_v6_timestamp + 1 

117 _last_v6_timestamp = timestamp 

118 if clock_seq is None: 

119 clock_seq = secrets.randbits(14) # instead of stable storage 

120 if node is None: 

121 node = secrets.randbits(48) 

122 time_high_and_time_mid = (timestamp >> 12) & 0xFFFFFFFFFFFF 

123 time_low_and_version = timestamp & 0x0FFF 

124 uuid_int = time_high_and_time_mid << 80 

125 uuid_int |= time_low_and_version << 64 

126 uuid_int |= (clock_seq & 0x3FFF) << 48 

127 uuid_int |= node & 0xFFFFFFFFFFFF 

128 return UUID(int=uuid_int, version=6) 

129 

130 

131def uuid7() -> UUID: 

132 r"""UUID version 7 features a time-ordered value field derived from the 

133 widely implemented and well known Unix Epoch timestamp source, the 

134 number of milliseconds since midnight 1 Jan 1970 UTC, leap seconds 

135 excluded. As well as improved entropy characteristics over versions 

136 1 or 6. 

137 

138 Implementations SHOULD utilize UUID version 7 over UUID version 1 and 

139 6 if possible.""" 

140 

141 global _last_v7_timestamp 

142 

143 nanoseconds = time.time_ns() 

144 timestamp_ms = nanoseconds // 10**6 

145 if _last_v7_timestamp is not None and timestamp_ms <= _last_v7_timestamp: 

146 timestamp_ms = _last_v7_timestamp + 1 

147 _last_v7_timestamp = timestamp_ms 

148 uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 

149 uuid_int |= secrets.randbits(76) 

150 return UUID(int=uuid_int, version=7) 

151 

152 

153def uuid8() -> UUID: 

154 r"""UUID version 8 features a time-ordered value field derived from the 

155 widely implemented and well known Unix Epoch timestamp source, the 

156 number of nanoseconds since midnight 1 Jan 1970 UTC, leap seconds 

157 excluded.""" 

158 

159 global _last_v8_timestamp 

160 

161 nanoseconds = time.time_ns() 

162 if _last_v8_timestamp is not None and nanoseconds <= _last_v8_timestamp: 

163 nanoseconds = _last_v8_timestamp + 1 

164 _last_v8_timestamp = nanoseconds 

165 timestamp_ms, timestamp_ns = divmod(nanoseconds, 10**6) 

166 subsec = _subsec_encode(timestamp_ns) 

167 subsec_a = subsec >> 8 

168 subsec_b = subsec & 0xFF 

169 uuid_int = (timestamp_ms & 0xFFFFFFFFFFFF) << 80 

170 uuid_int |= subsec_a << 64 

171 uuid_int |= subsec_b << 54 

172 uuid_int |= secrets.randbits(54) 

173 return UUID(int=uuid_int, version=8)