1 import os, time
2 from yaml import safe_load, safe_dump
3
5 """
6 The resolver class implements a persistent two-way mapping,
7 typically between strings.
8
9 Persistence is ensured by using a YAML file to store the mapping
10
11 Resolver maps both keys to values and values to keys.
12
13 If asked to map an unknown key or value, the code treats this
14 as a value and auto-generates a key using a hash function.
15
16 EXAMPLE OF USE:
17 >>> r = Resolver("parrot.yml")
18 >>> r.get('parrot','is dead')
19 'is dead'
20 >>> r['met'] = 'its maker'
21 >>> r['met']
22 'its maker'
23 >>> r.save()
24 >>> r2 = Resolver("parrot.yml")
25 >>> r2['its maker']
26 'met'
27 """
29 """
30 Resolve using mapping stored in file named fn
31
32 NOTE: the file's directory must be read-write
33 Resolver will save temporary files to this directory
34 """
35 assert type(fn)==str
36 self.fn = fn
37 self.tbl = {}
38 self.itbl = {}
39
41 """
42 Load contents from file storage.
43
44 Silently returns if file is not found / inaccessible
45 """
46
47 try:
48 os.stat(self.fn)
49 except OSError:
50
51 return
52
53 f = open(self.fn,'r')
54 doc = safe_load(f)
55 f.close()
56 if type(doc) is not dict:
57 raise TypeError("File '%s' does not contain a YAML mapping" % self.fn)
58 self.tbl = doc
59
60 self.itbl = {}
61 for k,v in self.tbl.iteritems():
62 self.itbl[v] = k
63
64 - def put(self,key,val):
65 """The obvious"""
66 self.tbl[key]=val
67 self.itbl[val]=key
68 return self
69
71 """Automatically generate a key for a value"""
72 return 'obj%04d' % (abs(hash(val)) % 10000)
73 return key
74
76 """
77 Save mapping to a YAML file.
78
79 EXAMPLE:
80 >>> r = Resolver('spam')
81 >>> r['ham']='spam'
82 >>> r['banana']='weapon'
83 >>> r.save()
84
85 ALGORITHM:
86 File is first saved to a temporary filename, then the old file
87 is removed and the new file renamed (a.k.a. 'safe save')
88 """
89
90 nfn = self.fn + '~'
91
92 f = open(nfn,'w')
93 f.write("# Saved at %s\n" % time.asctime() )
94 safe_dump(self.tbl,f,default_style="!")
95 f.close()
96
97 if os.name != 'posix':
98 print "WARNING: you are using an inferior OS that does not support posix rename semantics"
99 print " attempting a workaround..."
100 try:
101 os.remove(self.fn)
102 except OSError:
103
104 pass
105
106 os.rename(nfn,self.fn)
107
109 """
110 Set up the two-way mapping key<-->value
111 NOTE: this does not automatically save !
112 """
113 self.put(key,value)
114
115 - def get( self, key, default=None ):
116 """
117 Find key/value associated with key. If not found, return default
118 """
119 r = self.tbl.get(key,default)
120 if r is default:
121 r = self.itbl.get(key,default)
122 return r
123
126
128 """
129 Find key/value associated with key.
130 If not found:
131 - Parameter is a value
132 - A key is generated by using autokey, or self._autokey(key) if None
133 - The mapping is saved to disk
134 """
135 if not self.tbl:
136 self.load()
137 tag = []
138 r = self.get(key,tag)
139 if r is tag:
140 if autokey is None:
141 r = self._autokey(key)
142 else:
143 r = autokey
144 self.put(r,key)
145 self.save()
146 return r
147