From 6bad3c6e644763acb7e667d6de35cc4ec5ec2ca6 Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Tue, 4 Jul 2017 19:59:42 +0200 Subject: [PATCH] update sqlite storage --- test/test_cache.py | 3 +- test/test_option_calculation.py | 2 +- test/test_parsing_group.py | 3 + test/test_storage.py | 71 +++++++++- tiramisu/config.py | 29 +--- tiramisu/option/baseoption.py | 14 +- tiramisu/option/masterslave.py | 6 +- tiramisu/storage/__init__.py | 20 ++- tiramisu/storage/dictionary/setting.py | 8 +- tiramisu/storage/dictionary/storage.py | 2 +- tiramisu/storage/dictionary/value.py | 4 +- tiramisu/storage/sqlite3/__init__.py | 4 +- tiramisu/storage/sqlite3/setting.py | 80 ++++++++--- tiramisu/storage/sqlite3/sqlite3db.py | 8 +- tiramisu/storage/sqlite3/storage.py | 51 ++++--- tiramisu/storage/sqlite3/value.py | 189 +++++++++++++++++++------ tiramisu/value.py | 5 +- 17 files changed, 365 insertions(+), 134 deletions(-) diff --git a/test/test_cache.py b/test/test_cache.py index 13466d6..b3963b2 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -2,8 +2,9 @@ from autopath import do_autopath do_autopath() -from tiramisu import setting +from tiramisu import setting, value setting.expires_time = 1 +value.expires_time = 1 from tiramisu.option import IntOption, StrOption, OptionDescription from tiramisu.config import Config from tiramisu.error import ConfigError diff --git a/test/test_option_calculation.py b/test/test_option_calculation.py index 2db667d..b1fc7dd 100644 --- a/test/test_option_calculation.py +++ b/test/test_option_calculation.py @@ -644,7 +644,7 @@ def test_consistency_master_and_slaves_master_mandatory_non_transitive(): maconfig = OptionDescription('rootconfig', '', [interface1, interface2]) cfg = Config(maconfig) cfg.read_write() - assert list(cfg.cfgimpl_get_values().mandatory_warnings()) == ["val1.val1"] + assert list(cfg.cfgimpl_get_values().mandatory_warnings()) == ["val1.val1", "val1.val2"] def test_callback_master_and_slaves_master_list(): diff --git a/test/test_parsing_group.py b/test/test_parsing_group.py index 9065608..0ac2457 100644 --- a/test/test_parsing_group.py +++ b/test/test_parsing_group.py @@ -692,3 +692,6 @@ def test_groups_with_master_get_modified_value(): assert cfg.cfgimpl_get_values().get_modified_values() == {'ip_admin_eth0.ip_admin_eth0': ('user', ('192.168.1.1',))} cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.255'] assert cfg.cfgimpl_get_values().get_modified_values() == {'ip_admin_eth0.ip_admin_eth0': ('user', ('192.168.1.1',)), 'ip_admin_eth0.netmask_admin_eth0': ({'0': 'user'}, {'0': '255.255.255.255'})} + cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.1.1') + cfg.ip_admin_eth0.netmask_admin_eth0[-1] = '255.255.255.255' + assert cfg.cfgimpl_get_values().get_modified_values() == {'ip_admin_eth0.ip_admin_eth0': ('user', ('192.168.1.1', '192.168.1.1')), 'ip_admin_eth0.netmask_admin_eth0': ({'0': 'user', '1': 'user'}, {'0': '255.255.255.255', '1': '255.255.255.255'})} diff --git a/test/test_storage.py b/test/test_storage.py index 6d01e3a..ad12a51 100644 --- a/test/test_storage.py +++ b/test/test_storage.py @@ -6,7 +6,7 @@ do_autopath() from tiramisu.config import Config from tiramisu.option import BoolOption, OptionDescription -from tiramisu.setting import owners +from tiramisu.setting import groups, owners from tiramisu.storage import list_sessions, delete_session @@ -21,6 +21,7 @@ def test_list(): o = OptionDescription('od', '', [b]) c = Config(o, session_id='test_non_persistent') c.cfgimpl_get_settings().remove('cache') + c.b = True assert 'test_non_persistent' in list_sessions('config') del(c) assert 'test_non_persistent' not in list_sessions('config') @@ -40,7 +41,8 @@ def test_list_sessions_persistent(): b = BoolOption('b', '') o = OptionDescription('od', '', [b]) try: - Config(o, session_id='test_persistent', persistent=True) + c = Config(o, session_id='test_persistent', persistent=True) + c.b = True except ValueError: # storage is not persistent pass @@ -139,6 +141,46 @@ def test_create_persistent_retrieve_owner(): del(c) +def test_create_persistent_retrieve_owner_masterslaves(): + a = BoolOption('a', '', multi=True) + b = BoolOption('b', '', multi=True) + o = OptionDescription('a', '', [a, b]) + o.impl_set_group_type(groups.master) + o1 = OptionDescription('a', '', [o]) + try: + c = Config(o1, session_id='test_persistent', persistent=True) + except ValueError: + # storage is not persistent + pass + else: + assert c.getowner(a) == owners.default + assert c.getowner(b) == owners.default + c.a.a = [True] + c.a.a.append(False) + c.a.b[1] = True + assert c.getowner(a) == owners.user + assert c.getowner(b, 0) == owners.default + assert c.getowner(b, 1) == owners.user + owners.addowner('persistentowner2') + c.cfgimpl_get_values().setowner(b, owners.persistentowner2, 1) + c.a.b[0] = True + assert c.getowner(b, 0) == owners.user + assert c.getowner(b, 1) == owners.persistentowner2 + del(c) + # + c = Config(o1, session_id='test_persistent', persistent=True) + assert c.getowner(b, 0) == owners.user + assert c.getowner(b, 1) == owners.persistentowner2 + delete_session('config', c.impl_getsessionid()) + del(c) + # + c = Config(o1, session_id='test_persistent', persistent=True) + assert c.a.b == [] + assert c.getowner(b) == owners.default + delete_session('config', c.impl_getsessionid()) + del(c) + + def test_two_persistent_owner(): b = BoolOption('b', '') o = OptionDescription('od', '', [b]) @@ -204,3 +246,28 @@ def test_two_persistent_information(): c2.cfgimpl_get_settings().remove('cache') assert c2.impl_get_information('info') == 'string' delete_session('config', 'test_persistent') + + +def test_two_different_persistents(): + b = BoolOption('b', '') + o = OptionDescription('od', '', [b]) + try: + c = Config(o, session_id='test_persistent', persistent=True) + c.cfgimpl_get_settings().remove('cache') + d = Config(o, session_id='test_persistent2', persistent=True) + d.cfgimpl_get_settings().remove('cache') + except ValueError: + # storage is not persistent + pass + else: + c.cfgimpl_get_settings()[b].append('test') + assert str(c.cfgimpl_get_settings()[b]) in ["['test']", "[u'test']"] + assert str(d.cfgimpl_get_settings()[b]) == "[]" + assert c.b is None + assert d.b is None + c.b = True + assert c.b == True + assert d.b is None + + delete_session('config', 'test_persistent') + delete_session('config', 'test_persistent2') diff --git a/tiramisu/config.py b/tiramisu/config.py index f22e622..7fd8269 100644 --- a/tiramisu/config.py +++ b/tiramisu/config.py @@ -87,25 +87,6 @@ class SubConfig(object): return self, None return self, path[-1] - #def __hash__(self): - #FIXME - # return hash(self.cfgimpl_get_description().impl_getkey(self)) - - #def __eq__(self, other): - #FIXME - # "Config's comparison" - # if not isinstance(other, Config): - # return False - # return self.cfgimpl_get_description().impl_getkey(self) == \ - # other.cfgimpl_get_description().impl_getkey(other) - - #def __ne__(self, other): - #FIXME - # "Config's comparison" - # if not isinstance(other, Config): - # return True - # return not self == other - # ______________________________________________________________________ def __iter__(self, force_permissive=False): """Pythonesque way of parsing group's ordered options. @@ -287,6 +268,8 @@ class SubConfig(object): option = self.cfgimpl_get_description().__getattr__(name, context=context) subpath = self._get_subpath(name) + if _setting_properties is undefined: + _setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=True) if isinstance(option, DynSymLinkOption): cfg = self.cfgimpl_get_values()._get_cached_value( option, path=subpath, @@ -681,10 +664,10 @@ class _CommonConfig(SubConfig): session = self.cfgimpl_get_values()._p_.getsession() config.cfgimpl_get_values()._p_.importation(self.cfgimpl_get_values()._p_.exportation( session)) - config.cfgimpl_get_settings()._p_._properties = self.cfgimpl_get_settings( - )._p_.get_modified_properties() - config.cfgimpl_get_settings()._p_._permissives = self.cfgimpl_get_settings( - )._p_.get_modified_permissives() + config.cfgimpl_get_settings()._p_.set_modified_properties(self.cfgimpl_get_settings( + )._p_.get_modified_properties()) + config.cfgimpl_get_settings()._p_.set_modified_permissives(self.cfgimpl_get_settings( + )._p_.get_modified_permissives()) return config diff --git a/tiramisu/option/baseoption.py b/tiramisu/option/baseoption.py index 47d361b..a2435ef 100644 --- a/tiramisu/option/baseoption.py +++ b/tiramisu/option/baseoption.py @@ -525,7 +525,8 @@ class Option(OnlyOption): def impl_validate(self, value, context=undefined, validate=True, force_index=None, force_submulti_index=None, current_opt=undefined, is_multi=None, - display_error=True, display_warnings=True, multi=None): + display_error=True, display_warnings=True, multi=None, + setting_properties=undefined): """ :param value: the option's value :param context: Config's context @@ -544,8 +545,9 @@ class Option(OnlyOption): if current_opt is undefined: current_opt = self - display_warnings = display_warnings and (context is undefined or - 'warnings' in context.cfgimpl_get_settings()) + if display_warnings and setting_properties is undefined and context is not undefined: + setting_properties = context.cfgimpl_get_settings()._getproperties(read_write=False) + display_warnings = display_warnings and (setting_properties is undefined or 'warnings' in setting_properties) def _is_not_unique(value): if display_error and self.impl_is_unique() and len(set(value)) != len(value): for idx, val in enumerate(value): @@ -1160,7 +1162,8 @@ class DynSymLinkOption(object): def impl_validate(self, value, context=undefined, validate=True, force_index=None, force_submulti_index=None, is_multi=None, - display_error=True, display_warnings=True, multi=None): + display_error=True, display_warnings=True, multi=None, + setting_properties=undefined): return self._impl_getopt().impl_validate(value, context, validate, force_index, force_submulti_index, @@ -1168,7 +1171,8 @@ class DynSymLinkOption(object): is_multi=is_multi, display_error=display_error, display_warnings=display_warnings, - multi=multi) + multi=multi, + setting_properties=setting_properties) def impl_is_dynsymlinkoption(self): return True diff --git a/tiramisu/option/masterslave.py b/tiramisu/option/masterslave.py index b40b8c8..4f7e61c 100644 --- a/tiramisu/option/masterslave.py +++ b/tiramisu/option/masterslave.py @@ -182,7 +182,7 @@ class MasterSlaves(object): masterp = master.impl_getpath(context) masterlen = self.get_length(values, opt, session, validate, undefined, undefined, force_permissive, - master=master) + master=master, setting_properties=setting_properties) if isinstance(masterlen, Exception): if isinstance(masterlen, PropertiesOptionError): masterlen.set_orig_opt(opt) @@ -252,7 +252,7 @@ class MasterSlaves(object): def get_length(self, values, opt, session, validate=True, slave_path=undefined, slave_value=undefined, force_permissive=False, master=None, - masterp=None): + masterp=None, setting_properties=undefined): """get master len with slave option""" if master is None: master = self.getmaster(opt) @@ -262,7 +262,7 @@ class MasterSlaves(object): slave_path = undefined value = self.getitem(values, master, masterp, validate, force_permissive, None, True, session, slave_path=slave_path, - slave_value=slave_value) + slave_value=slave_value, setting_properties=setting_properties) if isinstance(value, Exception): return value return len(value) diff --git a/tiramisu/storage/__init__.py b/tiramisu/storage/__init__.py index a95a629..ff74d79 100644 --- a/tiramisu/storage/__init__.py +++ b/tiramisu/storage/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -36,12 +36,15 @@ from ..i18n import _ MODULE_PATH = os.path.split(os.path.split(os.path.split(__file__)[0])[0])[1] +DEFAULT_STORAGE = 'dictionary' + + class StorageType(object): """Object to store storage's type. If a Config is already set, default storage is store as selected storage. You cannot change it after. """ - default_storage = os.environ.get('TIRAMISU_STORAGE', 'dictionary') + default_storage = os.environ.get('TIRAMISU_STORAGE', DEFAULT_STORAGE) storage_type = None mod = None @@ -55,6 +58,9 @@ class StorageType(object): def get(self): if self.storage_type is None: self.storage_type = self.default_storage + set_to_default = True + else: + set_to_default = False if self.mod is None: modulepath = '{0}.storage.{1}'.format(MODULE_PATH, self.storage_type) try: @@ -70,8 +76,9 @@ class StorageType(object): storage_type = StorageType() storage_option_type = StorageType() +storage_option_type.set(DEFAULT_STORAGE) storage_validation = StorageType() -storage_validation.set('dictionary') +storage_validation.set(DEFAULT_STORAGE) def set_storage(type_, name, **kwargs): # pragma: optional cover @@ -126,7 +133,7 @@ def get_storages(context, session_id, persistent): session_id = gen_id(context) imp = storage_type.get() storage = imp.Storage(session_id, persistent) - settings = imp.Settings(session_id, storage) + settings = imp.Settings(storage) values = imp.Values(storage) return settings, values @@ -168,8 +175,9 @@ def delete_session(type_, session_id): # pragma: optional cover session = storage_module.storage.getsession() storage_module.value.delete_session(session_id, session) storage_module.storage.delete_session(session_id, session) - session.commit() + if session: + session.commit() del(session) -__all__ = (set_storage, list_sessions, delete_session) +__all__ = ('set_storage', 'list_sessions', 'delete_session') diff --git a/tiramisu/storage/dictionary/setting.py b/tiramisu/storage/dictionary/setting.py index 5bb985b..2d8ab06 100644 --- a/tiramisu/storage/dictionary/setting.py +++ b/tiramisu/storage/dictionary/setting.py @@ -22,7 +22,7 @@ from ..util import Cache class Settings(Cache): __slots__ = ('_properties', '_permissives') - def __init__(self, session_id, storage): + def __init__(self, storage): # properties attribute: the name of a property enables this property # key is None for global properties self._properties = {} @@ -59,8 +59,14 @@ class Settings(Cache): """ return copy(self._properties) + def set_modified_properties(self, properties): + self._properties = properties + def get_modified_permissives(self): """return all modified permissives in a dictionary example: {'path1': set(['perm1', 'perm2'])} """ return copy(self._permissives) + + def set_modified_permissives(self, permissives): + self._permissives = permissives diff --git a/tiramisu/storage/dictionary/storage.py b/tiramisu/storage/dictionary/storage.py index 0f17b1b..c868928 100644 --- a/tiramisu/storage/dictionary/storage.py +++ b/tiramisu/storage/dictionary/storage.py @@ -33,7 +33,7 @@ def list_sessions(): # pragma: optional cover return _list_sessions -def delete_session(session_id): # pragma: optional cover +def delete_session(session_id, session): # pragma: optional cover raise ConfigError(_('dictionary storage cannot delete session')) diff --git a/tiramisu/storage/dictionary/value.py b/tiramisu/storage/dictionary/value.py index 0dc82bc..0a03e99 100644 --- a/tiramisu/storage/dictionary/value.py +++ b/tiramisu/storage/dictionary/value.py @@ -182,11 +182,11 @@ class Values(Cache): indexes = self._values[1][path_idx] if indexes is None: if index is not None: # pragma: no cover - raise ValueError('index is forbidden') + raise ValueError('index is forbidden for {}'.format(path)) value = self._values[nb][path_idx] else: if index is None: # pragma: no cover - raise ValueError('index is mandatory') + raise ValueError('index is mandatory for {}'.format(path)) if index in indexes: subidx = indexes.index(index) value = self._values[nb][path_idx][subidx] diff --git a/tiramisu/storage/sqlite3/__init__.py b/tiramisu/storage/sqlite3/__init__.py index 4f9754b..a9f67d7 100644 --- a/tiramisu/storage/sqlite3/__init__.py +++ b/tiramisu/storage/sqlite3/__init__.py @@ -22,6 +22,6 @@ You should not configure differents Configs with same session_id. """ from .value import Values from .setting import Settings -from .storage import setting, Storage, list_sessions, delete_session +from .storage import Storage, list_sessions, delete_session -__all__ = (setting, Values, Settings, Storage, list_sessions, delete_session) +__all__ = ('Values', 'Settings', 'Storage', 'list_sessions', 'delete_session') diff --git a/tiramisu/storage/sqlite3/setting.py b/tiramisu/storage/sqlite3/setting.py index a4478d2..8f4484a 100644 --- a/tiramisu/storage/sqlite3/setting.py +++ b/tiramisu/storage/sqlite3/setting.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for setting: set it in a simple dictionary" -# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -22,10 +22,10 @@ class Settings(Sqlite3DB): __slots__ = tuple() def __init__(self, storage): - settings_table = 'CREATE TABLE IF NOT EXISTS property(path text ' - settings_table += 'primary key, properties text)' - permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path text ' - permissives_table += 'primary key, permissives text)' + settings_table = 'CREATE TABLE IF NOT EXISTS property(path TEXT,' + settings_table += 'properties text, session_id TEXT, PRIMARY KEY(path, session_id))' + permissives_table = 'CREATE TABLE IF NOT EXISTS permissive(path TEXT,' + permissives_table += 'permissives TEXT, session_id TEXT, PRIMARY KEY(path, session_id))' # should init cache too super(Settings, self).__init__(storage) self._storage.execute(settings_table, commit=False) @@ -34,16 +34,23 @@ class Settings(Sqlite3DB): # properties def setproperties(self, path, properties): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM property WHERE path = ?", (path,), + self._storage.execute("DELETE FROM property WHERE path = ? AND session_id = ?", + (path, self._session_id), False) - self._storage.execute("INSERT INTO property(path, properties) VALUES " - "(?, ?)", (path, - self._sqlite_encode(properties))) + self._storage.execute("INSERT INTO property(path, properties, session_id) VALUES " + "(?, ?, ?)", (path, + self._sqlite_encode(properties), + self._session_id)) + + def delproperties(self, path): + path = self._sqlite_encode_path(path) + self._storage.execute("DELETE FROM property WHERE path = ? AND session_id = ?", + (path, self._session_id)) def getproperties(self, path, default_properties): path = self._sqlite_encode_path(path) value = self._storage.select("SELECT properties FROM property WHERE " - "path = ?", (path,)) + "path = ? AND session_id = ?", (path, self._session_id)) if value is None: return set(default_properties) else: @@ -52,29 +59,32 @@ class Settings(Sqlite3DB): def hasproperties(self, path): path = self._sqlite_encode_path(path) return self._storage.select("SELECT properties FROM property WHERE " - "path = ?", (path,)) is not None + "path = ? AND session_id = ?", (path, self._session_id) + ) is not None def reset_all_properties(self): - self._storage.execute("DELETE FROM property") + self._storage.execute("DELETE FROM property WHERE session_id = ?", (self._session_id,)) def reset_properties(self, path): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM property WHERE path = ?", (path,)) + self._storage.execute("DELETE FROM property WHERE path = ? AND session_id = ?", + (path, self._session_id)) # permissive def setpermissive(self, path, permissive): path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM permissive WHERE path = ?", (path,), + self._storage.execute("DELETE FROM permissive WHERE path = ? AND session_id = ?", + (path, self._session_id), False) - self._storage.execute("INSERT INTO permissive(path, permissives) " - "VALUES (?, ?)", (path, - self._sqlite_encode(permissive) - )) + self._storage.execute("INSERT INTO permissive(path, permissives, session_id) " + "VALUES (?, ?, ?)", (path, + self._sqlite_encode(permissive), + self._session_id)) def getpermissive(self, path='_none'): permissives = self._storage.select("SELECT permissives FROM " - "permissive WHERE path = ?", - (path,)) + "permissive WHERE path = ? AND session_id = ?", + (path, self._session_id)) if permissives is None: return frozenset() else: @@ -85,19 +95,43 @@ class Settings(Sqlite3DB): example: {'path1': set(['prop1', 'prop2'])} """ ret = {} - for path, properties in self._storage.select("SELECT * FROM property", - only_one=False): + for path, properties, _ in self._storage.select("SELECT * FROM property " + "WHERE session_id = ?", + (self._session_id,), + only_one=False): path = self._sqlite_decode_path(path) ret[path] = self._sqlite_decode(properties) return ret + def set_modified_properties(self, properties): + self._storage.execute("DELETE FROM property", commit=False) + for path, property_ in properties.items(): + self._storage.execute("INSERT INTO property(path, property, session_id) " + "VALUES (?, ?, ?)", (path, + self._sqlite_encode(property_), + self._session_id, + ), False) + self._storage._conn.commit() + def get_modified_permissives(self): """return all modified permissives in a dictionary example: {'path1': set(['perm1', 'perm2'])} """ ret = {} - for path, permissives in self._storage.select("SELECT * FROM permissive", + for path, permissives in self._storage.select("SELECT * FROM permissive " + "WHERE session_id = ?", + (self._session_id,), only_one=False): path = self._sqlite_decode_path(path) ret[path] = self._sqlite_decode(permissives) return ret + + def set_modified_permissives(self, permissives): + self._storage.execute("DELETE FROM permissive", commit=False) + for path, permissive in permissives.items(): + self._storage.execute("INSERT INTO permissive(path, permissive, session_id) " + "VALUES (?, ?, ?)", (path, + self._sqlite_encode(permissive), + self._session_id, + ), False) + self._storage._conn.commit() diff --git a/tiramisu/storage/sqlite3/sqlite3db.py b/tiramisu/storage/sqlite3/sqlite3db.py index 2b33117..0728454 100644 --- a/tiramisu/storage/sqlite3/sqlite3db.py +++ b/tiramisu/storage/sqlite3/sqlite3db.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "sqlite3 cache" -# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -23,7 +23,11 @@ from ..util import Cache class Sqlite3DB(Cache): - __slots__ = tuple() + __slots__ = ('_session_id',) + def __init__(self, storage): + self._session_id = storage.session_id + super(Sqlite3DB, self).__init__(storage) + def _sqlite_decode_path(self, path): if path == '_none': return None diff --git a/tiramisu/storage/sqlite3/storage.py b/tiramisu/storage/sqlite3/storage.py index b441466..7802dfb 100644 --- a/tiramisu/storage/sqlite3/storage.py +++ b/tiramisu/storage/sqlite3/storage.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for cache: set it in a simple dictionary" -# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -17,7 +17,7 @@ # ____________________________________________________________ from os import unlink -from os.path import basename, splitext, join +from os.path import basename, splitext, join, isfile import sqlite3 from glob import glob from ..util import SerializeObject @@ -29,25 +29,39 @@ class Setting(SerializeObject): """ extension = 'db' dir_database = '/tmp' + name = 'tiramisu' -setting = Setting() +SETTING = Setting() -def _gen_filename(name): - return join(setting.dir_database, '{0}.{1}'.format(name, - setting.extension)) +def _gen_filename(): + return join(SETTING.dir_database, '{0}.{1}'.format(SETTING.name, SETTING.extension)) def list_sessions(): - names = [] - for filename in glob(_gen_filename('*')): - names.append(basename(splitext(filename)[0])) + conn = sqlite3.connect(_gen_filename()) + conn.text_factory = str + cursor = conn.cursor() + names = [row[0] for row in cursor.execute("SELECT DISTINCT session_id FROM value").fetchall()] + conn.close() return names -def delete_session(session_id): - unlink(_gen_filename(session_id)) +def delete_session(session_id, session): + conn = sqlite3.connect(_gen_filename()) + conn.text_factory = str + cursor = conn.cursor() + cursor.execute("DELETE FROM property WHERE session_id = ?", + (session_id,)) + cursor.execute("DELETE FROM permissive WHERE session_id = ?", + (session_id,)) + cursor.execute("DELETE FROM value WHERE session_id = ?", + (session_id,)) + cursor.execute("DELETE FROM information WHERE session_id = ?", + (session_id,)) + conn.commit() + conn.close() class Storage(object): @@ -56,12 +70,9 @@ class Storage(object): def __init__(self, session_id, persistent, test=False): self.persistent = persistent - if self.persistent: - self.serializable = True - else: - self.serializable = False + self.serializable = self.persistent self.session_id = session_id - self._conn = sqlite3.connect(_gen_filename(self.session_id)) + self._conn = sqlite3.connect(_gen_filename()) self._conn.text_factory = str self._cursor = self._conn.cursor() @@ -83,4 +94,10 @@ class Storage(object): self._cursor.close() self._conn.close() if not self.persistent: - delete_session(self.session_id) + session = None + if delete_session is not None: + delete_session(self.session_id, session) + + +def getsession(): + pass diff --git a/tiramisu/storage/sqlite3/value.py b/tiramisu/storage/sqlite3/value.py index eaa434f..b06bf9e 100644 --- a/tiramisu/storage/sqlite3/value.py +++ b/tiramisu/storage/sqlite3/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "default plugin for value: set it in a simple dictionary" -# Copyright (C) 2013 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -17,7 +17,9 @@ # ____________________________________________________________ from .sqlite3db import Sqlite3DB -from tiramisu.setting import owners +from .storage import delete_session +from ...setting import undefined, owners +from ...i18n import _ class Values(Sqlite3DB): @@ -28,87 +30,128 @@ class Values(Sqlite3DB): """ # should init cache too super(Values, self).__init__(storage) - values_table = 'CREATE TABLE IF NOT EXISTS value(path text primary ' - values_table += 'key, value text, owner text)' + values_table = 'CREATE TABLE IF NOT EXISTS value(path TEXT, ' + values_table += 'value TEXT, owner TEXT, idx INTEGER, session_id TEXT NOT NULL, '\ + 'PRIMARY KEY (path, idx, session_id))' self._storage.execute(values_table, commit=False) - informations_table = 'CREATE TABLE IF NOT EXISTS information(key text primary ' - informations_table += 'key, value text)' + informations_table = 'CREATE TABLE IF NOT EXISTS information(key TEXT PRIMARY ' + informations_table += 'KEY, value TEXT, session_id TEXT NOT NULL)' self._storage.execute(informations_table) - for owner in self._storage.select("SELECT DISTINCT owner FROM value", tuple(), False): - try: - getattr(owners, owner[0]) - except AttributeError: - owners.addowner(owner[0]) + + def getsession(self): + pass # sqlite - def _sqlite_select(self, path): - return self._storage.select("SELECT value FROM value WHERE path = ?", - (path,)) + def _sqlite_select(self, path, index): + request = "SELECT value FROM value WHERE path = ? AND session_id = ?" + params = (path, self._session_id) + if index is not None: + request += " and idx = ?" + params = (path, self._session_id, index) + return self._storage.select(request, params) # value - def setvalue(self, path, value, owner): + def setvalue(self, path, value, owner, index, session): """set value for an option a specified value must be associated to an owner """ - self.resetvalue(path) path = self._sqlite_encode_path(path) - self._storage.execute("INSERT INTO value(path, value, owner) VALUES " - "(?, ?, ?)", (path, self._sqlite_encode(value), - str(owner))) + if index is not None: + self._storage.execute("DELETE FROM value WHERE path = ? AND idx = ? AND " + "session_id = ?", (path, index, self._session_id), + commit=False) + self._storage.execute("INSERT INTO value(path, value, owner, idx, session_id) VALUES " + "(?, ?, ?, ?, ?)", (path, self._sqlite_encode(value), + self._sqlite_encode(str(owner)), + index, + self._session_id), + commit=True) + else: + self._storage.execute("DELETE FROM value WHERE path = ? AND session_id = ?", + (path, self._session_id), + commit=False) + self._storage.execute("INSERT INTO value(path, value, owner, session_id) VALUES " + "(?, ?, ?, ?)", (path, self._sqlite_encode(value), + self._sqlite_encode(str(owner)), + self._session_id), + commit=True) - def getvalue(self, path): + def getvalue(self, path, session, index=None): """get value for an option return: only value, not the owner """ path = self._sqlite_encode_path(path) - return self._sqlite_decode(self._sqlite_select(path)[0]) + values = self._sqlite_select(path, index) + if values is None: + return values + return self._sqlite_decode(values[0]) - def hasvalue(self, path): + def hasvalue(self, path, index=None): """if opt has a value return: boolean """ path = self._sqlite_encode_path(path) - return self._sqlite_select(path) is not None + return self._sqlite_select(path, index) is not None - def resetvalue(self, path): + def resetvalue(self, path, session): """remove value means delete value in storage """ path = self._sqlite_encode_path(path) - self._storage.execute("DELETE FROM value WHERE path = ?", (path,)) + self._storage.execute("DELETE FROM value WHERE path = ? AND session_id = ?", (path, self._session_id)) def get_modified_values(self): """return all values in a dictionary example: {option1: (owner, 'value1'), option2: (owner, 'value2')} """ ret = {} - for path, value, owner in self._storage.select("SELECT * FROM value", - only_one=False): + for path, value, owner, index, _ in self._storage.select("SELECT * FROM value WHERE " + "session_id = ?", + (self._session_id,), + only_one=False): path = self._sqlite_decode_path(path) - owner = getattr(owners, owner) + owner = getattr(owners, self._sqlite_decode(owner)) value = self._sqlite_decode(value) - ret[path] = (owner, value) + if isinstance(value, list): + value = tuple(value) + if index is None: + ret[path] = (owner, value) + else: + if path in ret: + ret[path][0][str(index)] = owner + ret[path][1][str(index)] = value + else: + ret[path] = ({str(index): owner}, {str(index): value}) return ret # owner - def setowner(self, path, owner): + def setowner(self, path, owner, session, index=None): """change owner for an option """ path = self._sqlite_encode_path(path) - self._storage.execute("UPDATE value SET owner = ? WHERE path = ?", - (str(owner), path)) + if index is None: + self._storage.execute("UPDATE value SET owner = ? WHERE path = ? AND session_id = ?", + (self._sqlite_encode(str(owner)), path, self._session_id)) + else: + self._storage.execute("UPDATE value SET owner = ? WHERE path = ? and idx = ? AND session_id = ?", + (self._sqlite_encode(str(owner)), path, index, self._session_id)) - def getowner(self, path, default): + def getowner(self, path, default, session, index=None, only_default=False): """get owner for an option return: owner object """ path = self._sqlite_encode_path(path) - owner = self._storage.select("SELECT owner FROM value WHERE path = ?", - (path,)) + request = "SELECT owner FROM value WHERE path = ? AND session_id = ?" + if index is not None: + request += " AND idx = ?" + params = (path, self._session_id, index) + else: + params = (path, self._session_id) + owner = self._storage.select(request, params) if owner is None: return default else: - owner = owner[0] + owner = self._sqlite_decode(owner[0]) # autocreate owners try: return getattr(owners, owner) @@ -123,19 +166,79 @@ class Values(Sqlite3DB): :param key: information's key (ex: "help", "doc" :param value: information's value (ex: "the help string") """ - self._storage.execute("DELETE FROM information WHERE key = ?", (key,), + self._storage.execute("DELETE FROM information WHERE key = ? AND session_id = ?", + (key, self._session_id), False) - self._storage.execute("INSERT INTO information(key, value) VALUES " - "(?, ?)", (key, self._sqlite_encode(value))) + self._storage.execute("INSERT INTO information(key, value, session_id) VALUES " + "(?, ?, ?)", (key, self._sqlite_encode(value), self._session_id)) - def get_information(self, key): + def get_information(self, key, default): """retrieves one information's item :param key: the item string (ex: "help") """ - value = self._storage.select("SELECT value FROM information WHERE key = ?", - (key,)) + value = self._storage.select("SELECT value FROM information WHERE key = ? AND " + "session_id = ?", + (key, self._session_id)) if value is None: - raise ValueError("not found") + if default is undefined: + raise ValueError(_("information's item" + " not found: {0}").format(key)) + return default else: return self._sqlite_decode(value[0]) + + def del_information(self, key, raises): + if raises and self._storage.select("SELECT value FROM information WHERE key = ? " + "AND session_id = ?", + (key, self._session_id)) is None: + raise ValueError(_("information's item not found {0}").format(key)) + self._storage.execute("DELETE FROM information WHERE key = ? AND session_id = ?", + (key, self._session_id)) + + def exportation(self, session, fake=False): + rows = self._storage.select("SELECT path, value, owner, idx FROM value WHERE " + "session_id = ?;", (self._session_id,), only_one=False) + ret = [[], [], [], []] + for row in rows: + path = row[0] + value = self._sqlite_decode(row[1]) + owner = self._sqlite_decode(row[2]) + index = row[3] + if index is None: + ret[0].append(path) + ret[1].append(index) + ret[2].append(value) + ret[3].append(owner) + else: + if path in ret[0]: + path_idx = ret[0].index(path) + ret[1][path_idx].append(index) + ret[2][path_idx].append(value) + else: + ret[0].append(path) + ret[1].append([index]) + ret[2].append([value]) + ret[3].append(owner) + + return ret + + def importation(self, export): + self._storage.execute("DELETE FROM value WHERE session_id = ?", (self._session_id,), + commit=False) + for idx, path in enumerate(export[0]): + index = export[1][idx] + value = export[2][idx] + owner = export[3][idx] + self._storage.execute("INSERT INTO value(path, value, owner, idx, session_id) VALUES " + "(?, ?, ?, ?, ?)", (path, self._sqlite_encode(value), + self._sqlite_encode(str(owner)), index, + self._session_id)) + self._storage._conn.commit() + + def get_max_length(self, path, session): + val_max = self._storage.select("SELECT max(idx) FROM value WHERE path = ? AND session_id = ?", + (path, self._session_id), False) + if val_max[0][0] is None: + return 0 + return val_max[0][0] + 1 diff --git a/tiramisu/value.py b/tiramisu/value.py index a600990..afe8ea5 100644 --- a/tiramisu/value.py +++ b/tiramisu/value.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- "takes care of the option's values and multi values" -# Copyright (C) 2013-2014 Team tiramisu (see AUTHORS for all contributors) +# Copyright (C) 2013-2017 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the @@ -390,7 +390,8 @@ class Values(object): force_index=index, force_submulti_index=force_submulti_index, display_error=False, - display_warnings=display_warnings) + display_warnings=display_warnings, + setting_properties=setting_properties,) if config_error is not None: return config_error return value