3 # Define some simple data logging classes for consistency
4 # see the test functions for some usage examples
8 import cPickle as pickle
13 class error (Exception) :
14 "Basic module error class"
17 class errorDirExists (error) :
18 "The specified directory already exists"
23 Creates consistent, timestamped log files.
25 Initialized with log_dir and log_name.
26 log_dir specifies the base data directory.
27 If it doesn't exist, log_dir is created.
29 A subdir of log_dir is created (if necessary) named YYYYMMDD,
30 where YYYYMMDD is the current day in localtime.
31 If noclobber_logsubdir == True, this dir must not exist yet.
33 log_name specifies the base name for the created log files (in the log subdir).
34 The created log filenames are prefixed with a YYYYMMDDHHMMSS timestamp.
35 If the target filename already exists, the filename is postfixed with
36 '_N', where N is the lowest integer that doesn't clobber an existing file.
38 General data is saved to the log files with the write(obj) method.
39 By default, write() cPickles the object passed.
40 You can save in other formats by overriding write()
42 Binary data is can be saved directly to the log files with the
43 write_binary(binary_string) method.
45 All file names are stripped of possibly troublesome characters.
47 def __init__(self, log_dir=".", noclobber_logsubdir=False,
50 # generate lists of not-allowed characters
51 unaltered_chars = "-._" + string.digits + string.letters
52 mapped_pairs = {' ':'_'}
53 allowed_chars = unaltered_chars + "".join(mapped_pairs.keys())
54 all_chars = string.maketrans('','')
55 self.delete_chars = all_chars.translate(all_chars, allowed_chars)
56 trans_from = "".join(mapped_pairs.keys())
57 trans_to = "".join(mapped_pairs.values()) # same order as keys, since no modifications to mapped_pairs were made in between the two calls
58 self.transtable = string.maketrans(trans_from, trans_to)
60 self._log_name = self._clean_filename(log_name) # never checked after this...
61 self._log_dir = self._create_logdir(log_dir) # will not clobber.
62 subdir, timestamp = self._create_logsubdir(self._log_dir,
66 self.timestamp = timestamp
67 def _clean_filename(self, filename) :
69 Currently only works on filenames, since it deletes '/'.
70 If you need it to work on full paths, use os.path.split(your_path)[1]
71 to strip of the filename portion...
73 cleanname = filename.translate(self.transtable, self.delete_chars)
75 def _create_logdir(self, log_dir) :
76 log_dir = os.path.expanduser(log_dir)
77 if not os.path.exists(log_dir) :
78 os.mkdir(log_dir, 0755)
80 def _create_logsubdir(self, log_dir, noclobber_logsubdir,
82 if timestamp == None :
83 timestamp = time.strftime("%Y%m%d") # %H%M%S
84 subdir = os.path.join(log_dir, timestamp)
85 if os.path.exists(subdir) :
86 if noclobber_logsubdir:
87 raise errorDirExists, "%s exists" % subdir
89 os.mkdir(subdir, 0755)
90 return (subdir, timestamp)
91 def get_filename(self, timestamp=None) :
93 Get a filename (using localtime if timestamp==None),
94 appending integers as necessary to avoid clobbering.
95 For use in write() routines.
96 Returns (filepath, timestamp)
98 if timestamp == None :
99 timestamp = time.strftime("%Y%m%d%H%M%S")
100 filename = "%s_%s" % (timestamp, self._log_name)
101 fullname = os.path.join(self.subdir, filename)
104 while os.path.exists(filepath) :
105 filepath = "%s_%d" % (fullname, i)
107 return (filepath, timestamp)
108 def write(self, obj, timestamp=None) :
110 Save object to a timestamped file with pickle.
111 If timestamp == None, use the current localtime.
112 Returns (filepath, timestamp)
114 filepath, timestamp = self.get_filename(timestamp)
115 fd = open(filepath, 'wb')
116 os.chmod(filepath, 0644)
119 return (filepath, timestamp)
120 def write_binary(self, binary_string, timestamp=None) :
122 Save binary_string to a timestamped file.
123 If timestamp == None, use the current localtime.
124 Returns (filepath, timestamp)
126 filepath, timestamp = self.get_filename(timestamp)
127 # open a new file in readonly mode, don't clobber.
128 fd = os.open(filepath, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0644)
130 bytes_remaining = len(binary_string)
131 while bytes_remaining > 0 :
132 bw = os.write(fd, binary_string[bytes_written:])
134 bytes_remaining -= bw
136 return (filepath, timestamp)
137 def _write_dict_of_arrays(self, d, base_filepath) :
138 # open a new file in readonly mode, don't clobber.
139 bfd = open(base_filepath, 'w', 0644)
140 bfd.write("Contents (key : file-extension : format):\n")
141 for key in d.keys() :
142 clean_key = self._clean_filename(key)
143 bfd.write("%s : %s : %s\n" % (key, clean_key, str(d[key].dtype)))
144 # write the keyed array to it's own file
145 filepath = "%s_%s" % (base_filepath, clean_key)
146 d[key].tofile(filepath)
148 def write_dict_of_arrays(self, d, timestamp=None) :
150 Save dict of (string, numpy_array) pairs to timestamped files.
151 If timestamp == None, use the current localtime.
152 Returns (base_filepath, timestamp)
154 base_filepath, timestamp = self.get_filename(timestamp)
155 self._write_dict_of_arrays(d, base_filepath)
156 return (base_filepath, timestamp)
160 Loads data logged by data_log.
162 def read(self, file) :
164 Load an object saved with data_log.write()
166 return pickle.load(open(file, 'rb'))
167 def read_binary(self, file) :
169 Load an object saved with data_log.write_binary()
170 The file-name must not have been altered.
172 raise Exception, "not implemented"
173 def read_dict_of_arrays(self, basefile) :
175 Load an object saved with data_log.write_binary()
176 The file-names must not have been altered.
180 realbasefile = os.path.realpath(basefile)
181 for line in file(realbasefile) :
182 if i > 0 : # ignore first line
183 ldata = line.split(' : ')
185 fpath = "%s_%s" % (realbasefile, ldata[1])
186 exec 'typ = numpy.%s' % ldata[2]
187 obj[name] = numpy.fromfile(fpath, dtype=typ)
193 def _check_data_logsubdir_clobber() :
194 log1 = data_log(_test_dir, noclobber_logsubdir=True)
196 log2 = data_log(_test_dir, noclobber_logsubdir=True)
197 raise error, "Didn't detect old log"
198 except errorDirExists :
199 pass # everything as it should be
200 os.rmdir(log1.subdir)
202 def _check_data_log_filenames() :
203 data = {"Test":True, "Data":[1,2,3,4]}
204 log = data_log(_test_dir, noclobber_logsubdir=True)
207 files[i], ts = log.write(data)
208 print "Contents of log directory (should be 10 identical logs)"
209 os.system('ls -l %s' % log.subdir)
214 def _check_data_log_pickle_integrity() :
215 data = {"Test":True, "Data":[1,2,3,4]}
217 log = data_log(_test_dir, noclobber_logsubdir=True)
218 filepath, ts = log.write(data)
220 fd = open(filepath, 'rb')
221 data_in = pickle.load(fd)
225 print "Saved : ", data
226 print "Read back: ", data_in
227 raise error, "Poorly pickled"
231 def _check_data_log_binary_integrity() :
232 from numpy import zeros, uint16, fromfile
234 data = zeros((npts,), dtype=uint16)
235 for i in range(npts) :
238 log = data_log(_test_dir, noclobber_logsubdir=True)
239 filepath, ts = log.write_binary(data.tostring())
241 data_in = fromfile(filepath, dtype=uint16, count=-1)
243 if npts != len(data_in) :
244 raise error, "Saved %d uint16s, read %d" % (npts, len(data_in))
245 for i in range(npts) :
246 if data_in[i] != data[i] :
247 print "Disagreement in element %d" % i
248 print "Saved %d, read back %d" % (data[i], data_in[i])
249 raise error, "Poorly saved"
253 def _check_data_loc_dict_of_arrays() :
254 from numpy import zeros, uint16, fromfile
256 data1 = zeros((npts,), dtype=uint16)
257 for i in range(npts) :
259 data2 = zeros((npts,), dtype=uint16)
260 for i in range(npts) :
262 data={"data1":data1, "d\/at:$a 2":data2}
264 log = data_log(_test_dir, noclobber_logsubdir=True)
265 filepath, ts = log.write_dict_of_arrays(data)
267 print "Contents of log directory (should be 3 logs)"
268 os.system('ls -l %s' % log.subdir)
269 print "The table of contents file:"
270 os.system('cat %s' % (filepath))
271 data1_in = fromfile(filepath+"_data1", dtype=uint16)
272 data2_in = fromfile(filepath+"_data_2", dtype=uint16)
273 for i in range(npts) :
274 if data1_in[i] != data1[i] :
275 print "Disagreement in element %d of data1" % i
276 print "Saved %d, read back %d" % (data1[i], data1_in[i])
277 raise error, "Poorly saved"
278 if data2_in[i] != data2[i] :
279 print "Disagreement in element %d of data2" % i
280 print "Saved %d, read back %d" % (data2[i], data2_in[i])
281 raise error, "Poorly saved"
283 os.remove(filepath+"_data1")
284 os.remove(filepath+"_data_2")
288 _check_data_logsubdir_clobber()
289 _check_data_log_filenames()
290 _check_data_log_pickle_integrity()
291 _check_data_log_binary_integrity()
292 _check_data_loc_dict_of_arrays()
294 if __name__ == "__main__" :