2
# Copyright (c) 2006 Canonical
3
# Copyright (c) 2004 Conectiva, Inc.
5
# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
7
# This Python module is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU General Public License as published
9
# by the Free Software Foundation; either version 2 of the License, or (at
10
# your option) any later version.
12
# This Python module is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this Python module; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26
__all__ = ["Persist", "PickleBackend", "BPickleBackend", "ConfigObjBackend",
27
"path_string_to_tuple", "path_tuple_to_string", "RootedPersist",
28
"PersistError", "PersistReadOnlyError"]
34
class PersistError(Exception):
38
class PersistReadOnlyError(PersistError):
42
class Persist(object):
43
"""Persistence handler.
45
There are three different kinds of opition maps, regarding the
46
persistence and priority that maps are queried.
48
hard - Options are persistent.
49
soft - Options are not persistent, and have a higher priority
50
than persistent options.
51
weak - Options are not persistent, and have a lower priority
52
than persistent options.
54
@ivar filename: The name of the file where persist data is saved
55
or None if not filename is available.
59
def __init__(self, backend=None, filename=None):
61
@param backend: The backend to use. If none is specified,
62
L{BPickleBackend} will be used.
63
@param filename: The default filename to save to and load from. If
64
specified, and the file exists, it will be immediately
65
loaded. Specifying this will also allow L{save} to be called
66
without any arguments to save the persist.
69
backend = BPickleBackend()
70
self._backend = backend
71
self._hardmap = backend.new()
74
self._readonly = False
75
self._modified = False
77
self.filename = filename
78
if filename is not None and os.path.exists(filename):
81
def _get_readonly(self):
84
def _set_readonly(self, flag):
85
self._readonly = bool(flag)
87
def _get_modified(self):
90
readonly = property(_get_readonly, _set_readonly)
91
modified = property(_get_modified)
93
def reset_modified(self):
94
self._modified = False
96
def assert_writable(self):
98
raise PersistReadOnlyError("Configuration is in readonly mode.")
100
def load(self, filepath):
101
filepath = os.path.expanduser(filepath)
102
if not os.path.isfile(filepath):
103
raise PersistError("File not found: %s" % filepath)
104
if os.path.getsize(filepath) == 0:
107
self._hardmap = self._backend.load(filepath)
109
filepathold = filepath+".old"
110
if (os.path.isfile(filepathold) and
111
os.path.getsize(filepathold) > 0):
112
# warning("Broken configuration file at %s" % filepath)
113
# warning("Trying backup at %s" % filepathold)
115
self._hardmap = self._backend.load(filepathold)
117
raise PersistError("Broken configuration file at %s" %
120
raise PersistError("Broken configuration file at %s" %
123
def save(self, filepath=None):
124
"""Save the persist to the given C{filepath}.
126
If None is specified, then the filename passed during construction will
130
if self.filename is None:
131
raise PersistError("Need a filename!")
132
filepath = self.filename
133
filepath = os.path.expanduser(filepath)
134
if os.path.isfile(filepath):
135
os.rename(filepath, filepath+".old")
136
dirname = os.path.dirname(filepath)
137
if dirname and not os.path.isdir(dirname):
139
self._backend.save(filepath, self._hardmap)
141
def _traverse(self, obj, path, default=NOTHING, setvalue=NOTHING):
142
if setvalue is not NOTHING:
143
setvalue = self._backend.copy(setvalue)
150
newobj = self._backend.get(obj, elem)
151
if newobj is NotImplemented:
153
path = path[:-len(queue)]
154
raise PersistError("Can't traverse %r (%r): %r" %
155
(type(obj), path_tuple_to_string(path),
159
if newobj is not marker:
160
if setvalue is not marker:
161
newobj = self._backend.set(obj, elem, setvalue)
163
if setvalue is marker:
168
if type(queue[0]) is int:
174
newobj = self._backend.set(obj, elem, newvalue)
175
if newobj is NotImplemented:
176
raise PersistError("Can't traverse %r with %r" %
177
(type(obj), type(elem)))
184
def _getvalue(self, path, soft=False, hard=False, weak=False):
185
if type(path) is str:
186
path = path_string_to_tuple(path)
189
value = self._traverse(self._softmap, path, marker)
191
value = self._traverse(self._hardmap, path, marker)
193
value = self._traverse(self._weakmap, path, marker)
195
value = self._traverse(self._softmap, path, marker)
197
value = self._traverse(self._hardmap, path, marker)
199
value = self._traverse(self._weakmap, path, marker)
202
def has(self, path, value=NOTHING, soft=False, hard=False, weak=False):
203
obj = self._getvalue(path, soft, hard, weak)
207
elif value is marker:
209
result = self._backend.has(obj, value)
210
if result is NotImplemented:
211
raise PersistError("Can't check %r for containment" % type(obj))
214
def keys(self, path, soft=False, hard=False, weak=False):
215
obj = self._getvalue(path, soft, hard, weak)
218
result = self._backend.keys(obj)
219
if result is NotImplemented:
220
raise PersistError("Can't return keys for %s" % type(obj))
223
def get(self, path, default=None, soft=False, hard=False, weak=False):
224
value = self._getvalue(path, soft, hard, weak)
227
return self._backend.copy(value)
229
def set(self, path, value, soft=False, weak=False):
231
if type(path) is str:
232
path = path_string_to_tuple(path)
238
self.assert_writable()
239
self._modified = True
241
self._traverse(map, path, setvalue=value)
243
def add(self, path, value, unique=False, soft=False, weak=False):
245
if type(path) is str:
246
path = path_string_to_tuple(path)
252
self.assert_writable()
253
self._modified = True
256
current = self._traverse(map, path)
257
if type(current) is list and value in current:
259
path = path+(sys.maxint,)
260
self._traverse(map, path, setvalue=value)
262
def remove(self, path, value=NOTHING, soft=False, weak=False):
264
if type(path) is str:
265
path = path_string_to_tuple(path)
271
self.assert_writable()
272
self._modified = True
277
obj = self._traverse(map, path[:-1])
281
obj = self._traverse(map, path)
285
if obj is not marker:
286
result = self._backend.remove(obj, elem, isvalue)
287
if result is NotImplemented:
288
raise PersistError("Can't remove %r from %r" %
290
if self._backend.empty(obj):
291
if value is not marker:
299
def move(self, oldpath, newpath, soft=False, weak=False):
300
if not (soft or weak):
301
self.assert_writable()
302
if type(oldpath) is str:
303
oldpath = path_string_to_tuple(oldpath)
304
if type(newpath) is str:
305
newpath = path_string_to_tuple(newpath)
308
value = self._getvalue(oldpath, soft, not (soft or weak), weak)
309
if value is not marker:
310
self.remove(oldpath, soft=soft, weak=weak)
311
self.set(newpath, value, weak, soft)
315
def root_at(self, path):
316
return RootedPersist(self, path)
319
class RootedPersist(object):
321
def __init__(self, parent, root):
323
if type(root) is str:
324
self.root = path_string_to_tuple(root)
328
readonly = property(lambda self: self.parent.readonly)
329
modified = property(lambda self: self.parent.modified)
331
def assert_writable(self):
332
self.parent.assert_writable()
334
def has(self, path, value=NOTHING, soft=False, hard=False, weak=False):
335
if type(path) is str:
336
path = path_string_to_tuple(path)
337
return self.parent.has(self.root+path, value, soft, hard, weak)
339
def keys(self, path, soft=False, hard=False, weak=False):
340
if type(path) is str:
341
path = path_string_to_tuple(path)
342
return self.parent.keys(self.root+path, soft, hard, weak)
344
def get(self, path, default=None, soft=False, hard=False, weak=False):
345
if type(path) is str:
346
path = path_string_to_tuple(path)
347
return self.parent.get(self.root+path, default, soft, hard, weak)
349
def set(self, path, value, soft=False, weak=False):
350
if type(path) is str:
351
path = path_string_to_tuple(path)
352
return self.parent.set(self.root+path, value, soft, weak)
354
def add(self, path, value, unique=False, soft=False, weak=False):
355
if type(path) is str:
356
path = path_string_to_tuple(path)
357
return self.parent.add(self.root+path, value, unique, soft, weak)
359
def remove(self, path, value=NOTHING, soft=False, weak=False):
360
if type(path) is str:
361
path = path_string_to_tuple(path)
362
return self.parent.remove(self.root+path, value, soft, weak)
364
def move(self, oldpath, newpath, soft=False, weak=False):
365
if type(oldpath) is str:
366
oldpath = path_string_to_tuple(oldpath)
367
if type(newpath) is str:
368
newpath = path_string_to_tuple(newpath)
369
return self.parent.move(self.root+oldpath, self.root+newpath,
372
def root_at(self, path):
373
if type(path) is str:
374
path = path_string_to_tuple(path)
375
return self.parent.root_at(self.root+path)
378
_splitpath = re.compile(r"(\[-?\d+\])|(?<!\\)\.").split
380
def path_string_to_tuple(path):
381
if "." not in path and "[" not in path:
384
tokens = _splitpath(path)
387
if token[0] == "[" and token[-1] == "]":
389
result.append(int(token[1:-1]))
391
raise PersistError("Invalid path index: %r" % token)
393
result.append(token.replace(r"\.", "."))
397
def path_tuple_to_string(path):
400
if type(elem) is int:
401
result[-1] += "[%d]" % elem
403
result.append(str(elem).replace(".", "\."))
404
return ".".join(result)
407
class Backend(object):
410
raise NotImplementedError
412
def load(self, filepath):
413
raise NotImplementedError
415
def save(self, filepath, map):
416
raise NotImplementedError
418
def get(self, obj, elem, _marker=NOTHING):
419
if type(obj) is dict:
420
newobj = obj.get(elem, _marker)
421
elif type(obj) in (tuple, list):
422
if type(elem) is int:
432
newobj = NotImplemented
435
def set(self, obj, elem, value):
436
if type(obj) is dict:
437
newobj = obj[elem] = value
438
elif type(obj) is list and type(elem) is int:
443
elif elem < 0 and abs(elem) > lenobj:
446
newobj = obj[elem] = value
448
newobj = NotImplemented
451
def remove(self, obj, elem, isvalue):
453
if type(obj) is dict:
457
elif type(obj) is list:
458
if not isvalue and type(elem) is int:
465
obj[:] = [x for x in obj if x != elem]
468
result = NotImplemented
471
def copy(self, value):
472
if type(value) in (dict, list):
473
return copy.deepcopy(value)
476
def empty(self, obj):
479
def has(self, obj, elem):
480
contains = getattr(obj, "__contains__", None)
482
return contains(elem)
483
return NotImplemented
486
keys = getattr(obj, "keys", None)
489
elif type(obj) is list:
490
return range(len(obj))
491
return NotImplemented
494
class PickleBackend(Backend):
498
self._pickle = cPickle
503
def load(self, filepath):
504
file = open(filepath)
506
return self._pickle.load(file)
510
def save(self, filepath, map):
511
file = open(filepath, "w")
513
self._pickle.dump(map, file, 2)
518
class BPickleBackend(Backend):
521
from landscape.lib import bpickle
522
self._bpickle = bpickle
527
def load(self, filepath):
528
file = open(filepath)
530
return self._bpickle.loads(file.read())
534
def save(self, filepath, map):
535
file = open(filepath, "w")
537
file.write(self._bpickle.dumps(map))
542
class ConfigObjBackend(Backend):
545
from landscape.lib import configobj
546
self.ConfigObj = configobj.ConfigObj
547
self.Section = configobj.Section
550
return self.ConfigObj(unrepr=True)
552
def load(self, filepath):
553
return self.ConfigObj(filepath, unrepr=True)
555
def save(self, filepath, map):
556
file = open(filepath, "w")
562
def get(self, obj, elem, _marker=NOTHING):
563
if isinstance(obj, self.Section):
564
return obj.get(elem, _marker)
565
return Backend.get(self, obj, elem)
567
def set(self, obj, elem, value):
568
if isinstance(obj, self.Section):
571
return Backend.set(self, obj, elem, value)
573
def remove(self, obj, elem, isvalue):
574
if isinstance(obj, self.Section):
579
return Backend.remove(self, obj, elem, isvalue)
581
def copy(self, value):
582
if isinstance(value, self.Section):
584
return Backend.copy(self, value)