Updated Guid to create random unique Guid if no arguments

Also updated doc string for Guid class.
This commit is contained in:
Dustin Pianalto 2018-05-21 00:45:50 -08:00
parent 43d868e20f
commit 8e719ef241

View File

@ -31,37 +31,73 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
from struct import Struct, pack from struct import Struct, pack, unpack
from math import log from math import log
from secrets import randbelow
import uuid
from datetime import datetime
import os
# Constants for masking bytes # Constants for masking bytes
MAXINT64 = 0xFFFFFFFFFFFFFFFF MAXINT64 = 0xFFFFFFFFFFFFFFFF
MAXINT32 = 0xFFFFFFFF
MAXINT16 = 0xFFFF MAXINT16 = 0xFFFF
MAXINT8 = 0xFF MAXINT8 = 0xFF
# Host Machine's MAC Address
MAC_ADDR = uuid.getnode()
# Geeksbot Epoch Jan 01 2018 in seconds since Jan 01 1970
GEPOCH = datetime(2018, 1, 1).timestamp()
class Guid: class Guid:
""" """This function does something.
A Global Unique Identifier (Guid) based on the one provided in C#'s System package
The Guid is stored as a tuple of length 11 for easy processing and comparisons Args:
:param guid_tuple: (Optional) A tuple of length 11 containing
the different parts of the Guid
Returns:
Guid object
Raises:
RuntimeError
Example Usage:
>>> g = guid.Guid.from_int(1234567899876534, 23456754325675)
>>> g
<Guid hash=3721039026>
>>> print(g)
'3d1f8cb6-62d5-0004-ab58-827355150000'
If initiated without any arguments a unique, semi-random Guid will be
produced using a random number, host computer's mac address, python
process id, and the current time in the following format:
######## - #### - #### - #### - ############
^ ^ ^ ^ ^
Random MAC address PID Current Time
masked to the last In microseconds
4 bytes (last 2 of since Jan 01 2018
manufacture ID and 1514797200 seconds
all of serial num) After Unix Epoch.
>>> g = guid.Guid()
>>> print(g)
93da0643-375a-5496-f92e-060b44965042
You can create a Guid object several different ways and there are
constructor methods for the most common use cases.
Guid.from_bytes(bytes_obj) Takes a bytes object of length 16
Guid.from_int(int, int) Takes 1 or 2 integers (see help for size restrictions)
Guid.from_string(str) Takes the string representation in the
format returned by Guid.to_string()
Guid.empty() Takes no arguments and returns an empty (all 0s) Guid
Attributes:
guid (tuple): A tuple containing the guid
""" """
def __init__(self, guid_tuple: tuple=()): def __init__(self, guid_tuple: tuple=()):
"""
Initialize the Guid
Arguments:
Union[int, str, bytes, short(int will be masked to short)]:
The arguments can be in any of the following formats:
(bytes(len 16))
(int(len <= 4), short, short, bytes(len 8))
(int(len <= 8), int(len <= 8))
(int(len <= 16))
(str) - str must be formatted 00000000-0000-0000-0000-000000000000
"""
if bool(guid_tuple): if bool(guid_tuple):
if len(guid_tuple) == 11: if len(guid_tuple) == 11:
if all(isinstance(t, int) for t in guid_tuple): if all(isinstance(t, int) for t in guid_tuple):
@ -69,17 +105,27 @@ class Guid:
guid = s.pack(guid_tuple[0], guid = s.pack(guid_tuple[0],
guid_tuple[1] & MAXINT16, guid_tuple[1] & MAXINT16,
guid_tuple[2] & MAXINT16, guid_tuple[2] & MAXINT16,
*guid_tuple[3]) *guid_tuple[3:])
self.guid = s.unpack(guid) self.guid = s.unpack(guid)
else: else:
raise RuntimeError('Every item in tuple must be an integer') raise RuntimeError('Every item in tuple must be an integer')
else: else:
raise RuntimeError('Tuple must be length 11 to create the guid') raise RuntimeError('Tuple must be length 11 to create the guid')
else: else:
self.guid = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) # Generate semi random unique Guid
time_now = int((datetime.utcnow().timestamp() - GEPOCH) * 1000000)
rand_int = randbelow(0xFFFFFFFF)
packed_guid = pack('2I4H',
rand_int,
MAC_ADDR & MAXINT32,
os.getpid() & MAXINT16,
(time_now >> 32) & MAXINT16,
(time_now >> 16) & MAXINT16,
time_now & MAXINT16)
self.guid = unpack('I2H8B', packed_guid)
@classmethod @classmethod
def from_tuple(cls, guid_bytes: bytes): def from_bytes(cls, guid_bytes: bytes):
s = Struct('I2H8B') s = Struct('I2H8B')
if len(guid_bytes) == 16: if len(guid_bytes) == 16:
guid = s.pack((int(guid_bytes[3]) << 24) guid = s.pack((int(guid_bytes[3]) << 24)
@ -99,14 +145,16 @@ class Guid:
@classmethod @classmethod
def from_int(cls, guid_int1: int, guid_int2: int=0): def from_int(cls, guid_int1: int, guid_int2: int=0):
if cls.bytes_needed(guid_int1) <= 16 and guid_int2 == 0: if cls._bytes_needed(guid_int1) <= 4 and cls._bytes_needed(guid_int2) <= 4 and guid_int2 != 0:
b = pack('QQ', guid_int1 >> 64, guid_int1 & MAXINT64)
elif cls.bytes_needed(guid_int1) <= 4 and cls.bytes_needed(guid_int2) <= 4:
int1, int2 = (guid_int1 << 32), (guid_int2 & 0xFFFFFFFF) int1, int2 = (guid_int1 << 32), (guid_int2 & 0xFFFFFFFF)
guid_int = int1 | int2 guid_int = int1 | int2
b = pack('Q8x', guid_int) b = pack('Q8x', guid_int)
elif cls.bytes_needed(guid_int1) <= 8 and cls.bytes_needed(guid_int2) <= 8: elif cls._bytes_needed(guid_int1) <= 8 and cls._bytes_needed(guid_int2) <= 8 and guid_int2 != 0:
b = pack('QQ', guid_int1, guid_int2) b = pack('QQ', guid_int1, guid_int2)
# elif cls._bytes_needed(guid_int1) <= 8 and guid_int2 == 0:
# b = pack('QQ', guid_int1 >> 32, guid_int1 & MAXINT32)
elif cls._bytes_needed(guid_int1) <= 16 and guid_int2 == 0:
b = pack('QQ', guid_int1 >> 64, guid_int1 & MAXINT64)
else: else:
raise RuntimeError('Integer is to large') raise RuntimeError('Integer is to large')
guid = ((int(b[3]) << 24) | (int(b[2]) << 16) | (int(b[1]) << 8) | b[0], guid = ((int(b[3]) << 24) | (int(b[2]) << 16) | (int(b[1]) << 8) | b[0],
@ -114,7 +162,7 @@ class Guid:
((int(b[7]) << 8) | b[6]), ((int(b[7]) << 8) | b[6]),
*b[8:16] *b[8:16]
) )
return cls(s.unpack(guid)) return cls(guid)
@classmethod @classmethod
def from_string(cls, string: str): def from_string(cls, string: str):
@ -139,20 +187,24 @@ class Guid:
raise TypeError('String is not formatted properly must be in format ' raise TypeError('String is not formatted properly must be in format '
'00000000-0000-0000-0000-000000000000') '00000000-0000-0000-0000-000000000000')
@classmethod
def empty(cls):
return cls((0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
def to_string(self): def to_string(self):
output = '' output = ''
output += self.hexs_to_chars(self.guid[0] >> 24, self.guid[0] >> 16) output += self._hexs_to_chars(self.guid[0] >> 24, self.guid[0] >> 16)
output += self.hexs_to_chars(self.guid[0] >> 8, self.guid[0]) output += self._hexs_to_chars(self.guid[0] >> 8, self.guid[0])
output += '-' output += '-'
output += self.hexs_to_chars(self.guid[1] >> 8, self.guid[1]) output += self._hexs_to_chars(self.guid[1] >> 8, self.guid[1])
output += '-' output += '-'
output += self.hexs_to_chars(self.guid[2] >> 8, self.guid[2]) output += self._hexs_to_chars(self.guid[2] >> 8, self.guid[2])
output += '-' output += '-'
output += self.hexs_to_chars(self.guid[3], self.guid[4]) output += self._hexs_to_chars(self.guid[3], self.guid[4])
output += '-' output += '-'
output += self.hexs_to_chars(self.guid[5], self.guid[6]) output += self._hexs_to_chars(self.guid[5], self.guid[6])
output += self.hexs_to_chars(self.guid[7], self.guid[8]) output += self._hexs_to_chars(self.guid[7], self.guid[8])
output += self.hexs_to_chars(self.guid[9], self.guid[10]) output += self._hexs_to_chars(self.guid[9], self.guid[10])
return output return output
def to_byte_array(self): def to_byte_array(self):
@ -175,18 +227,18 @@ class Guid:
b[15] = self.guid[10] & MAXINT8 b[15] = self.guid[10] & MAXINT8
return b return b
def hexs_to_chars(self, _a, _b): def _hexs_to_chars(self, _a: int, _b: int) -> str:
out = f'{self.hex_to_char(_a>>4)}{self.hex_to_char(_a)}{self.hex_to_char(_b>>4)}{self.hex_to_char(_b)}' out = f'{self._hex_to_char(_a>>4)}{self._hex_to_char(_a)}{self._hex_to_char(_b>>4)}{self._hex_to_char(_b)}'
return out return out
@staticmethod @staticmethod
def bytes_needed(a): def _bytes_needed(a: int) -> int:
if a == 0: if a == 0:
return 1 return 1
return int(log(a, 256)) + 1 return int(log(a, 256)) + 1
@staticmethod @staticmethod
def hex_to_char(a: int): def _hex_to_char(a: int) -> str:
a = a & 0xf a = a & 0xf
out = chr(a - 10 + 0x61 if a > 9 else a + 0x30) out = chr(a - 10 + 0x61 if a > 9 else a + 0x30)
return out return out
@ -196,31 +248,9 @@ class Guid:
return t[0] ^ ((int(t[1]) << 16) | int(t[2])) ^ ((int(t[5]) << 24) | t[10]) return t[0] ^ ((int(t[1]) << 16) | int(t[2])) ^ ((int(t[5]) << 24) | t[10])
def __eq__(self, other): def __eq__(self, other):
if other is None or not isinstance(other, Guid): if self.__class__ == other.__class__:
return self.guid == getattr(other, 'guid', None)
return False return False
if other.guid[0] != self.guid[0]:
return False
if other.guid[1] != self.guid[1]:
return False
if other.guid[2] != self.guid[2]:
return False
if other.guid[3] != self.guid[3]:
return False
if other.guid[4] != self.guid[4]:
return False
if other.guid[5] != self.guid[5]:
return False
if other.guid[6] != self.guid[6]:
return False
if other.guid[7] != self.guid[7]:
return False
if other.guid[8] != self.guid[8]:
return False
if other.guid[9] != self.guid[9]:
return False
if other.guid[10] != self.guid[10]:
return False
return True
def __bool__(self): def __bool__(self):
return not (self.guid == (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) return not (self.guid == (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))