Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

FileUpload: guid.py

Line 
1 #!/usr/bin/env python
2
3 # GUID.py
4 # Version 2.1.
5 #
6 # Copyright (C) 2003  Dr. Conan C. Albrecht <conan_albrechtATbyu.edu>
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
22
23
24 ##################################################################################################
25 ###   A globally-unique identifier made up of time and ip and 8 random digits: 40 characters wide
26 ### 
27 ###   A globally unique identifier that combines ip, time, and random bits.  Since the
28 ###   time is listed first, you can sort records by guid.  You can also extract the time
29 ###   and ip if needed. 
30 ###     
31 ###   GUIDs make wonderful database keys.  They require no access to the
32 ###   database (to get the max index number), they are extremely unique, and they sort
33 ###   automatically by time.   GUIDs prevent key clashes when merging
34 ###   two databases together, combining data, or generating keys in distributed
35 ###   systems.
36 ###
37 ###   There is an Internet Draft for UUIDs, but this module does not implement it.
38 ###   If the draft catches on, perhaps I'll conform the module to it.
39 ###
40
41
42 # Changelog
43 # Sometime, 1997     Created the Java version of GUID
44 #                    Went through many versions in Java
45 # Sometime, 2002     Created the Python version of GUID, mirroring the Java version
46 # November 24, 2003  Changed Python version to be more pythonic, took out object and made just a module
47 # December 2, 2003   Fixed duplicating GUIDs.  Sometimes they duplicate if multiples are created
48 #                    in the same millisecond (it checks the last 100 GUIDs now and has a larger random part)
49 # December 9, 2003   Fixed MAX_RANDOM, which was going over sys.maxint
50 #
51
52 import math
53 import random
54 import socket
55 import sys
56 import time
57 import threading
58
59 # The size of the circular queue.  Larger sizes give more assurance for uniqueness.
60 # Smaller sizes take less memory and are a tiny bit faster
61 QUEUE_SIZE = 100
62
63
64 #############################
65 ###   global module variables
66
67 MAX_RANDOM = sys.maxint # converted to hex goes to 8 chars (at least, in Python 2.3)
68 rand = random.Random()
69 ip = ''
70 lock = threading.RLock()
71 lastguid = ''
72 try:
73   ip = socket.gethostbyname(socket.gethostname())
74 except (socket.gaierror): # if we don't have an ip, default to someting in the 10.x.x.x private range
75   ip = '10'
76   for i in range(3):
77     ip += '.' + str(rand.randrange(1, 254))
78 hexip = ''.join(["%04x" % long(i) for i in ip.split('.')]) # leave space for ip v6 (65K in each sub)
79
80
81 #######################################
82 ###   A simple circular set
83 ###   to ensure we don't duplicate
84 ###   GUIDs in the same millisecond
85  
86 class CircularSet:
87   '''A circular set.  A set that maxes at a given size, replacing the oldest element after maximum size.
88      This implementation is NOT thread safe.  (generate() below is thread safe, though)
89   '''
90   def __init__(self):
91     self.queue = []
92     self.queue_map = {} # for efficiency, we keep a map of everything
93     self.queueindex = 0
94
95   def add(self, val):
96     '''Adds a value to the queue'''
97     # check to see if we have this value.  If so, throw an exception
98     assert not self.queue_map.has_key(val), 'This value is already in the set!'
99    
100     # add the new one to the list
101     if len(self.queue) > self.queueindex:
102       # first remove the previous key at this location
103       del self.queue_map[self.queue[self.queueindex]]
104       self.queue[self.queueindex] = val
105     else:
106       self.queue.append(val)
107      
108     # now add to the map for efficiency
109     self.queue_map[val] = val
110    
111     # increment the queue index
112     self.queueindex += 1
113     if self.queueindex >= QUEUE_SIZE:
114       self.queueindex = 0
115      
116 queue = CircularSet()
117  
118 #################################
119 ###   Public module functions
120
121 def generate():
122   '''Generates a new guid'''
123   global lock, queue  # since we modify the module variable
124   try:
125     lock.acquire() # can't generate two guids at the same time
126     while 1:
127       parts = []
128       # time part
129       parts.append("%016x" % (long(time.time() * 1000)))
130       # ip part
131       parts.append(hexip)
132       # random part
133       parts.append(("%08x" % (rand.random() * MAX_RANDOM))[:8]) # limit to 8 chars, just in case maxint goes up in future Pythons
134       # put them all together
135       guid = ''.join(parts)
136       try:
137         queue.add(guid)  # throws the AssertionError if this GUID is a duplicate of the queue
138         return guid
139       except AssertionError: # signals we already have this GUID in the queue
140         pass
141   finally:
142     lock.release()
143    
144
145 def extract_time(guid):
146   '''Extracts the time portion out of the guid and returns the
147      number of seconds since the epoch as a float'''
148   return float(long(guid[0:16], 16)) / 1000
149
150
151 def extract_ip(guid):
152   '''Extracts the ip portion out of the guid and returns it
153      as a string like 10.10.10.10'''
154   # there's probably a more elegant way to do this
155   ip = ''
156   index = 16
157   while index < 32:
158     if ip != '':
159       ip += "."
160     ip += str(int(guid[index: index + 4], 16))
161     index += 4
162   return ip
163
164
165 def extract_random(guid):
166   '''Extracts the random bits from the guid (returns the bits in decimal)'''
167   return int(guid[32:], 16)
168
169
170 ### TESTING OF GUID CLASS ###
171 if __name__ == "__main__":
172   guid = generate()
173   print "GUID:", guid
174   print "Time:", time.strftime('%a, %d %b %Y %H:%M:%S', time.localtime(extract_time(guid)))
175   print "IP:  ", extract_ip(guid)
176   print "Rand:", extract_random(guid)
177
178  

Hosted by WebFaction

Log in as guest/cpguest to create tickets