400
402
os.close(os.open(path, os.O_CREAT | os.O_APPEND, mode))
405
class ConfigurationImmutable(Exception):
406
"""The configuration is read-only; it cannot be mutated."""
403
409
class ConfigurationDatabase:
404
410
"""Store configuration in an sqlite3 database."""
406
def __init__(self, database):
412
def __init__(self, database, mutable=False):
407
413
self.database = database
414
self.mutable = mutable
408
415
with self.cursor() as cursor:
410
417
"CREATE TABLE IF NOT EXISTS configuration "
432
439
return json.loads(data[0])
434
441
def __setitem__(self, name, data):
435
with self.cursor() as cursor:
437
"INSERT OR REPLACE INTO configuration (name, data) "
438
"VALUES (?, ?)", (name, json.dumps(data)))
443
with self.cursor() as cursor:
445
"INSERT OR REPLACE INTO configuration (name, data) "
446
"VALUES (?, ?)", (name, json.dumps(data)))
448
raise ConfigurationImmutable(
449
"%s: Cannot set `%s'." % (self, name))
440
451
def __delitem__(self, name):
453
with self.cursor() as cursor:
455
"DELETE FROM configuration"
456
" WHERE name = ?", (name,))
458
raise ConfigurationImmutable(
459
"%s: Cannot set `%s'." % (self, name))
461
def __unicode__(self):
441
462
with self.cursor() as cursor:
443
"DELETE FROM configuration"
444
" WHERE name = ?", (name,))
463
# https://www.sqlite.org/pragma.html#pragma_database_list
464
databases = "; ".join(
465
"%s=%s" % (name, ":memory:" if path == "" else path)
466
for (_, name, path) in cursor.execute("PRAGMA database_list"))
467
return "%s(%s)" % (self.__class__.__name__, databases)
448
471
def open(cls, dbpath):
449
472
"""Open a configuration database.
474
**Note** that this returns a context manager which will open the
477
# Ensure `dbpath` exists...
479
# before opening it with sqlite.
480
database = sqlite3.connect(dbpath)
482
yield cls(database, mutable=False)
492
def open_for_update(cls, dbpath):
493
"""Open a configuration database.
451
495
**Note** that this returns a context manager which will close the
452
database on exit, saving if the exit is clean.
496
database on exit, COMMITTING changes if the exit is clean.
454
498
# Ensure `dbpath` exists...
456
500
# before opening it with sqlite.
457
501
database = sqlite3.connect(dbpath)
503
yield cls(database, mutable=True)
493
538
return self.config[name]
495
540
def __setitem__(self, name, data):
496
self.config[name] = data
499
def __delitem__(self, name):
500
if name in self.config:
501
del self.config[name]
542
self.config[name] = data
502
543
self.dirty = True
545
raise ConfigurationImmutable(
546
"%s: Cannot set `%s'." % (self, name))
548
def __delitem__(self, name):
550
if name in self.config:
551
del self.config[name]
554
raise ConfigurationImmutable(
555
"%s: Cannot set `%s'." % (self, name))
505
558
"""Load the configuration."""
530
583
self.path, mode=mode)
531
584
self.dirty = False
586
def __unicode__(self):
587
return "%s(%r)" % (self.__class__.__name__, self.path)
535
591
def open(cls, path):
592
"""Open a configuration file read-only.
594
This avoids all the locking that happens in `open_for_update`. However,
595
it will create the configuration file if it does not yet exist.
597
**Note** that this returns a context manager which will DISCARD
598
changes to the configuration on exit.
600
# Ensure `path` exists...
602
# before loading it in.
603
configfile = cls(path, mutable=False)
609
def open_for_update(cls, path):
536
610
"""Open a configuration file.
538
612
Locks are taken so that there can only be *one* reader or writer for a
674
748
with cls.backend.open(filepath) as store:
753
def open_for_update(cls, filepath=None):
755
filepath = cls.DEFAULT_FILENAME
756
ensure_dir(os.path.dirname(filepath))
757
with cls.backend.open_for_update(filepath) as store:
678
761
class ConfigurationOption:
679
762
"""Define a configuration option.