~ahasenack/landscape-client/landscape-client-1.5.5-0ubuntu0.9.04.0

« back to all changes in this revision

Viewing changes to landscape/lib/persist.py

  • Committer: Bazaar Package Importer
  • Author(s): Rick Clark
  • Date: 2008-09-08 16:35:57 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080908163557-l3ixzj5dxz37wnw2
Tags: 1.0.18-0ubuntu1
New upstream release 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (c) 2006 Canonical
 
3
# Copyright (c) 2004 Conectiva, Inc.
 
4
#
 
5
# Written by Gustavo Niemeyer <gustavo@niemeyer.net>
 
6
#
 
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.
 
11
#
 
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.
 
16
#
 
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
 
20
#
 
21
import sys, os
 
22
import copy
 
23
import re
 
24
 
 
25
 
 
26
__all__ = ["Persist", "PickleBackend", "BPickleBackend", "ConfigObjBackend",
 
27
           "path_string_to_tuple", "path_tuple_to_string", "RootedPersist",
 
28
           "PersistError", "PersistReadOnlyError"]
 
29
 
 
30
 
 
31
NOTHING = object()
 
32
 
 
33
 
 
34
class PersistError(Exception):
 
35
    pass
 
36
 
 
37
 
 
38
class PersistReadOnlyError(PersistError):
 
39
    pass
 
40
 
 
41
 
 
42
class Persist(object):
 
43
    """Persistence handler.
 
44
 
 
45
    There are three different kinds of opition maps, regarding the
 
46
    persistence and priority that maps are queried.
 
47
 
 
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.
 
53
 
 
54
    @ivar filename: The name of the file where persist data is saved
 
55
        or None if not filename is available.
 
56
    """
 
57
 
 
58
 
 
59
    def __init__(self, backend=None, filename=None):
 
60
        """
 
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.
 
67
        """
 
68
        if backend is None:
 
69
            backend = BPickleBackend()
 
70
        self._backend = backend
 
71
        self._hardmap = backend.new()
 
72
        self._softmap = {}
 
73
        self._weakmap = {}
 
74
        self._readonly = False
 
75
        self._modified = False
 
76
        self._config = self
 
77
        self.filename = filename
 
78
        if filename is not None and os.path.exists(filename):
 
79
            self.load(filename)
 
80
 
 
81
    def _get_readonly(self):
 
82
        return self._readonly
 
83
 
 
84
    def _set_readonly(self, flag):
 
85
        self._readonly = bool(flag)
 
86
 
 
87
    def _get_modified(self):
 
88
        return self._modified
 
89
 
 
90
    readonly = property(_get_readonly, _set_readonly)
 
91
    modified = property(_get_modified)
 
92
 
 
93
    def reset_modified(self):
 
94
        self._modified = False
 
95
 
 
96
    def assert_writable(self):
 
97
        if self._readonly:
 
98
            raise PersistReadOnlyError("Configuration is in readonly mode.")
 
99
 
 
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:
 
105
            return
 
106
        try:
 
107
            self._hardmap = self._backend.load(filepath)
 
108
        except:
 
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)
 
114
                try:
 
115
                    self._hardmap = self._backend.load(filepathold)
 
116
                except:
 
117
                    raise PersistError("Broken configuration file at %s" %
 
118
                                       filepathold)
 
119
            else:
 
120
                raise PersistError("Broken configuration file at %s" %
 
121
                                   filepath)
 
122
 
 
123
    def save(self, filepath=None):
 
124
        """Save the persist to the given C{filepath}.
 
125
 
 
126
        If None is specified, then the filename passed during construction will
 
127
        be used.
 
128
        """
 
129
        if filepath is None:
 
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):
 
138
            os.makedirs(dirname)
 
139
        self._backend.save(filepath, self._hardmap)
 
140
 
 
141
    def _traverse(self, obj, path, default=NOTHING, setvalue=NOTHING):
 
142
        if setvalue is not NOTHING:
 
143
            setvalue = self._backend.copy(setvalue)
 
144
        queue = list(path)
 
145
        marker = NOTHING
 
146
        newobj = obj
 
147
        while queue:
 
148
            obj = newobj
 
149
            elem = queue.pop(0)
 
150
            newobj = self._backend.get(obj, elem)
 
151
            if newobj is NotImplemented:
 
152
                if queue:
 
153
                    path = path[:-len(queue)]
 
154
                raise PersistError("Can't traverse %r (%r): %r" %
 
155
                                   (type(obj), path_tuple_to_string(path),
 
156
                                    str(obj)))
 
157
            if newobj is marker:
 
158
                break
 
159
        if newobj is not marker:
 
160
            if setvalue is not marker:
 
161
                newobj = self._backend.set(obj, elem, setvalue)
 
162
        else:
 
163
            if setvalue is marker:
 
164
                newobj = default
 
165
            else:
 
166
                while True:
 
167
                    if len(queue) > 0:
 
168
                        if type(queue[0]) is int:
 
169
                            newvalue = []
 
170
                        else:
 
171
                            newvalue = {}
 
172
                    else:
 
173
                        newvalue = setvalue
 
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)))
 
178
                    if not queue:
 
179
                        break
 
180
                    obj = newobj
 
181
                    elem = queue.pop(0)
 
182
        return newobj
 
183
 
 
184
    def _getvalue(self, path, soft=False, hard=False, weak=False):
 
185
        if type(path) is str:
 
186
            path = path_string_to_tuple(path)
 
187
        marker = NOTHING
 
188
        if soft:
 
189
            value = self._traverse(self._softmap, path, marker)
 
190
        elif hard:
 
191
            value = self._traverse(self._hardmap, path, marker)
 
192
        elif weak:
 
193
            value = self._traverse(self._weakmap, path, marker)
 
194
        else:
 
195
            value = self._traverse(self._softmap, path, marker)
 
196
            if value is marker:
 
197
                value = self._traverse(self._hardmap, path, marker)
 
198
                if value is marker:
 
199
                    value = self._traverse(self._weakmap, path, marker)
 
200
        return value
 
201
 
 
202
    def has(self, path, value=NOTHING, soft=False, hard=False, weak=False):
 
203
        obj = self._getvalue(path, soft, hard, weak)
 
204
        marker = NOTHING
 
205
        if obj is marker:
 
206
            return False
 
207
        elif value is marker:
 
208
            return True
 
209
        result = self._backend.has(obj, value)
 
210
        if result is NotImplemented:
 
211
            raise PersistError("Can't check %r for containment" % type(obj))
 
212
        return result
 
213
 
 
214
    def keys(self, path, soft=False, hard=False, weak=False):
 
215
        obj = self._getvalue(path, soft, hard, weak)
 
216
        if obj is NOTHING:
 
217
            return []
 
218
        result = self._backend.keys(obj)
 
219
        if result is NotImplemented:
 
220
            raise PersistError("Can't return keys for %s" % type(obj))
 
221
        return result
 
222
 
 
223
    def get(self, path, default=None, soft=False, hard=False, weak=False):
 
224
        value = self._getvalue(path, soft, hard, weak)
 
225
        if value is NOTHING:
 
226
            return default
 
227
        return self._backend.copy(value)
 
228
 
 
229
    def set(self, path, value, soft=False, weak=False):
 
230
        assert path
 
231
        if type(path) is str:
 
232
            path = path_string_to_tuple(path)
 
233
        if soft:
 
234
            map = self._softmap
 
235
        elif weak:
 
236
            map = self._weakmap
 
237
        else:
 
238
            self.assert_writable()
 
239
            self._modified = True
 
240
            map = self._hardmap
 
241
        self._traverse(map, path, setvalue=value)
 
242
 
 
243
    def add(self, path, value, unique=False, soft=False, weak=False):
 
244
        assert path
 
245
        if type(path) is str:
 
246
            path = path_string_to_tuple(path)
 
247
        if soft:
 
248
            map = self._softmap
 
249
        elif weak:
 
250
            map = self._weakmap
 
251
        else:
 
252
            self.assert_writable()
 
253
            self._modified = True
 
254
            map = self._hardmap
 
255
        if unique:
 
256
            current = self._traverse(map, path)
 
257
            if type(current) is list and value in current:
 
258
                return
 
259
        path = path+(sys.maxint,)
 
260
        self._traverse(map, path, setvalue=value)
 
261
 
 
262
    def remove(self, path, value=NOTHING, soft=False, weak=False):
 
263
        assert path
 
264
        if type(path) is str:
 
265
            path = path_string_to_tuple(path)
 
266
        if soft:
 
267
            map = self._softmap
 
268
        elif weak:
 
269
            map = self._weakmap
 
270
        else:
 
271
            self.assert_writable()
 
272
            self._modified = True
 
273
            map = self._hardmap
 
274
        marker = NOTHING
 
275
        while path:
 
276
            if value is marker:
 
277
                obj = self._traverse(map, path[:-1])
 
278
                elem = path[-1]
 
279
                isvalue = False
 
280
            else:
 
281
                obj = self._traverse(map, path)
 
282
                elem = value
 
283
                isvalue = True
 
284
            result = False
 
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" %
 
289
                                       (elem, type(obj)))
 
290
            if self._backend.empty(obj):
 
291
                if value is not marker:
 
292
                    value = marker
 
293
                else:
 
294
                    path = path[:-1]
 
295
            else:
 
296
                break
 
297
        return result
 
298
 
 
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)
 
306
        result = False
 
307
        marker = NOTHING
 
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)
 
312
            result = True
 
313
        return result
 
314
 
 
315
    def root_at(self, path):
 
316
        return RootedPersist(self, path)
 
317
 
 
318
 
 
319
class RootedPersist(object):
 
320
 
 
321
    def __init__(self, parent, root):
 
322
        self.parent = parent
 
323
        if type(root) is str:
 
324
            self.root = path_string_to_tuple(root)
 
325
        else:
 
326
            self.root = root
 
327
 
 
328
    readonly = property(lambda self: self.parent.readonly)
 
329
    modified = property(lambda self: self.parent.modified)
 
330
 
 
331
    def assert_writable(self):
 
332
        self.parent.assert_writable()
 
333
 
 
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)
 
338
 
 
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)
 
343
 
 
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)
 
348
 
 
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)
 
353
 
 
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)
 
358
 
 
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)
 
363
 
 
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,
 
370
                                soft, weak)
 
371
 
 
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)
 
376
 
 
377
 
 
378
_splitpath = re.compile(r"(\[-?\d+\])|(?<!\\)\.").split
 
379
 
 
380
def path_string_to_tuple(path):
 
381
    if "." not in path and "[" not in path:
 
382
        return (path,)
 
383
    result = []
 
384
    tokens = _splitpath(path)
 
385
    for token in tokens:
 
386
        if token:
 
387
            if token[0] == "[" and token[-1] == "]":
 
388
                try:
 
389
                    result.append(int(token[1:-1]))
 
390
                except ValueError:
 
391
                    raise PersistError("Invalid path index: %r" % token)
 
392
            else:
 
393
                result.append(token.replace(r"\.", "."))
 
394
    return tuple(result)
 
395
 
 
396
 
 
397
def path_tuple_to_string(path):
 
398
    result = []
 
399
    for elem in path:
 
400
        if type(elem) is int:
 
401
            result[-1] += "[%d]" % elem
 
402
        else:
 
403
            result.append(str(elem).replace(".", "\."))
 
404
    return ".".join(result)
 
405
 
 
406
 
 
407
class Backend(object):
 
408
 
 
409
    def new(self):
 
410
        raise NotImplementedError
 
411
 
 
412
    def load(self, filepath):
 
413
        raise NotImplementedError
 
414
 
 
415
    def save(self, filepath, map):
 
416
        raise NotImplementedError
 
417
 
 
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:
 
423
                try:
 
424
                    newobj = obj[elem]
 
425
                except IndexError:
 
426
                    newobj = _marker
 
427
            elif elem in obj:
 
428
                newobj = elem
 
429
            else:
 
430
                newobj = _marker
 
431
        else:
 
432
            newobj = NotImplemented
 
433
        return newobj
 
434
 
 
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:
 
439
            lenobj = len(obj)
 
440
            if lenobj <= elem:
 
441
                obj.append(None)
 
442
                elem = lenobj
 
443
            elif elem < 0 and abs(elem) > lenobj:
 
444
                obj.insert(0, None)
 
445
                elem = 0
 
446
            newobj = obj[elem] = value
 
447
        else:
 
448
            newobj = NotImplemented
 
449
        return newobj
 
450
 
 
451
    def remove(self, obj, elem, isvalue):
 
452
        result = False
 
453
        if type(obj) is dict:
 
454
            if elem in obj:
 
455
                del obj[elem]
 
456
                result = True
 
457
        elif type(obj) is list:
 
458
            if not isvalue and type(elem) is int:
 
459
                try:
 
460
                    del obj[elem]
 
461
                    result = True
 
462
                except IndexError:
 
463
                    pass
 
464
            elif elem in obj:
 
465
                obj[:] = [x for x in obj if x != elem]
 
466
                result = True
 
467
        else:
 
468
            result = NotImplemented
 
469
        return result
 
470
 
 
471
    def copy(self, value):
 
472
        if type(value) in (dict, list):
 
473
            return copy.deepcopy(value)
 
474
        return value
 
475
 
 
476
    def empty(self, obj):
 
477
        return (not obj)
 
478
 
 
479
    def has(self, obj, elem):
 
480
        contains = getattr(obj, "__contains__", None)
 
481
        if contains:
 
482
            return contains(elem)
 
483
        return NotImplemented
 
484
 
 
485
    def keys(self, obj):
 
486
        keys = getattr(obj, "keys", None)
 
487
        if keys:
 
488
            return keys()
 
489
        elif type(obj) is list:
 
490
            return range(len(obj))
 
491
        return NotImplemented
 
492
 
 
493
 
 
494
class PickleBackend(Backend):
 
495
 
 
496
    def __init__(self):
 
497
        import cPickle
 
498
        self._pickle = cPickle
 
499
 
 
500
    def new(self):
 
501
        return {}
 
502
 
 
503
    def load(self, filepath):
 
504
        file = open(filepath)
 
505
        try:
 
506
            return self._pickle.load(file)
 
507
        finally:
 
508
            file.close()
 
509
 
 
510
    def save(self, filepath, map):
 
511
        file = open(filepath, "w")
 
512
        try:
 
513
            self._pickle.dump(map, file, 2)
 
514
        finally:
 
515
            file.close()
 
516
 
 
517
 
 
518
class BPickleBackend(Backend):
 
519
 
 
520
    def __init__(self):
 
521
        from landscape.lib import bpickle
 
522
        self._bpickle = bpickle
 
523
 
 
524
    def new(self):
 
525
        return {}
 
526
 
 
527
    def load(self, filepath):
 
528
        file = open(filepath)
 
529
        try:
 
530
            return self._bpickle.loads(file.read())
 
531
        finally:
 
532
            file.close()
 
533
 
 
534
    def save(self, filepath, map):
 
535
        file = open(filepath, "w")
 
536
        try:
 
537
            file.write(self._bpickle.dumps(map))
 
538
        finally:
 
539
            file.close()
 
540
 
 
541
 
 
542
class ConfigObjBackend(Backend):
 
543
 
 
544
    def __init__(self):
 
545
        from landscape.lib import configobj
 
546
        self.ConfigObj = configobj.ConfigObj
 
547
        self.Section = configobj.Section
 
548
 
 
549
    def new(self):
 
550
        return self.ConfigObj(unrepr=True)
 
551
 
 
552
    def load(self, filepath):
 
553
        return self.ConfigObj(filepath, unrepr=True)
 
554
 
 
555
    def save(self, filepath, map):
 
556
        file = open(filepath, "w")
 
557
        try:
 
558
            map.write(file)
 
559
        finally:
 
560
            file.close()
 
561
 
 
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)
 
566
 
 
567
    def set(self, obj, elem, value):
 
568
        if isinstance(obj, self.Section):
 
569
            obj[elem] = value
 
570
            return obj[elem]
 
571
        return Backend.set(self, obj, elem, value)
 
572
 
 
573
    def remove(self, obj, elem, isvalue):
 
574
        if isinstance(obj, self.Section):
 
575
            if elem in obj:
 
576
                del obj[elem]
 
577
                return True
 
578
            return False
 
579
        return Backend.remove(self, obj, elem, isvalue)
 
580
 
 
581
    def copy(self, value):
 
582
        if isinstance(value, self.Section):
 
583
            return value.dict()
 
584
        return Backend.copy(self, value)
 
585
 
 
586
# vim:ts=4:sw=4:et
 
587