From 8e719ef2419aec9b4b18215834c01affdcc680a1 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Mon, 21 May 2018 00:45:50 -0800 Subject: [PATCH] Updated Guid to create random unique Guid if no arguments Also updated doc string for Guid class. --- exts/imports/guid.py | 156 ++++++++++++++++++++++++++----------------- 1 file changed, 93 insertions(+), 63 deletions(-) diff --git a/exts/imports/guid.py b/exts/imports/guid.py index 6c716b1..c2337c4 100644 --- a/exts/imports/guid.py +++ b/exts/imports/guid.py @@ -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 secrets import randbelow +import uuid +from datetime import datetime +import os # Constants for masking bytes MAXINT64 = 0xFFFFFFFFFFFFFFFF +MAXINT32 = 0xFFFFFFFF MAXINT16 = 0xFFFF 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: - """ - A Global Unique Identifier (Guid) based on the one provided in C#'s System package + """This function does something. - 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 + + >>> 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=()): - """ - 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 len(guid_tuple) == 11: if all(isinstance(t, int) for t in guid_tuple): @@ -69,17 +105,27 @@ class Guid: guid = s.pack(guid_tuple[0], guid_tuple[1] & MAXINT16, guid_tuple[2] & MAXINT16, - *guid_tuple[3]) + *guid_tuple[3:]) self.guid = s.unpack(guid) else: raise RuntimeError('Every item in tuple must be an integer') else: raise RuntimeError('Tuple must be length 11 to create the guid') 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 - def from_tuple(cls, guid_bytes: bytes): + def from_bytes(cls, guid_bytes: bytes): s = Struct('I2H8B') if len(guid_bytes) == 16: guid = s.pack((int(guid_bytes[3]) << 24) @@ -99,14 +145,16 @@ class Guid: @classmethod def from_int(cls, guid_int1: int, guid_int2: int=0): - if cls.bytes_needed(guid_int1) <= 16 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: + if cls._bytes_needed(guid_int1) <= 4 and cls._bytes_needed(guid_int2) <= 4 and guid_int2 != 0: int1, int2 = (guid_int1 << 32), (guid_int2 & 0xFFFFFFFF) guid_int = int1 | int2 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) + # 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: raise RuntimeError('Integer is to large') 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]), *b[8:16] ) - return cls(s.unpack(guid)) + return cls(guid) @classmethod def from_string(cls, string: str): @@ -139,20 +187,24 @@ class Guid: raise TypeError('String is not formatted properly must be in format ' '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): output = '' - 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] >> 24, self.guid[0] >> 16) + output += self._hexs_to_chars(self.guid[0] >> 8, self.guid[0]) 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 += 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 += self.hexs_to_chars(self.guid[3], self.guid[4]) + output += self._hexs_to_chars(self.guid[3], self.guid[4]) output += '-' - 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[9], self.guid[10]) + 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[9], self.guid[10]) return output def to_byte_array(self): @@ -175,18 +227,18 @@ class Guid: b[15] = self.guid[10] & MAXINT8 return b - def hexs_to_chars(self, _a, _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)}' + 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)}' return out @staticmethod - def bytes_needed(a): + def _bytes_needed(a: int) -> int: if a == 0: return 1 return int(log(a, 256)) + 1 @staticmethod - def hex_to_char(a: int): + def _hex_to_char(a: int) -> str: a = a & 0xf out = chr(a - 10 + 0x61 if a > 9 else a + 0x30) return out @@ -196,31 +248,9 @@ class Guid: return t[0] ^ ((int(t[1]) << 16) | int(t[2])) ^ ((int(t[5]) << 24) | t[10]) def __eq__(self, other): - if other is None or not isinstance(other, Guid): - 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 + if self.__class__ == other.__class__: + return self.guid == getattr(other, 'guid', None) + return False def __bool__(self): return not (self.guid == (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))