From 58c22aa70f4d88e5045529866f49fe494525e59e Mon Sep 17 00:00:00 2001 From: Emmanuel Garette Date: Sun, 6 Jul 2014 15:35:13 +0200 Subject: [PATCH] better support for sqlalchemy storage --- tiramisu/storage/sqlalchemy/setting.py | 110 +++++++++++++++++++ tiramisu/storage/sqlalchemy/storage.py | 59 +++++++++++ tiramisu/storage/sqlalchemy/util.py | 39 +++++++ tiramisu/storage/sqlalchemy/value.py | 141 +++++++++++++++++++++++++ 4 files changed, 349 insertions(+) create mode 100644 tiramisu/storage/sqlalchemy/setting.py create mode 100644 tiramisu/storage/sqlalchemy/storage.py create mode 100644 tiramisu/storage/sqlalchemy/util.py create mode 100644 tiramisu/storage/sqlalchemy/value.py diff --git a/tiramisu/storage/sqlalchemy/setting.py b/tiramisu/storage/sqlalchemy/setting.py new file mode 100644 index 0000000..c9ad3da --- /dev/null +++ b/tiramisu/storage/sqlalchemy/setting.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +"default plugin for setting: set it in a simple dictionary" +# Copyright (C) 2014 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# ____________________________________________________________ +from ..util import Cache +from .util import SqlAlchemyBase +import util +from sqlalchemy import Column, Integer, String, PickleType, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.orm.collections import attribute_mapped_collection + + +#____________________________________________________________ +# +# properties|permissives +class _Property(SqlAlchemyBase): + __tablename__ = 'property' + id = Column(Integer, primary_key=True) + setting = Column(Integer, ForeignKey('settings.id'), nullable=False) + path = Column(String) + properties = Column(PickleType) + + def __init__(self, path, properties): + self.path = path + self.properties = properties + + +class _Permissive (SqlAlchemyBase): + __tablename__ = 'permissive' + id = Column(Integer, primary_key=True) + setting = Column(Integer, ForeignKey('settings.id'), nullable=False) + path = Column(String) + permissives = Column(PickleType) + + def __init__(self, path, permissives): + self.path = path + self.permissives = permissives + + +#____________________________________________________________ +#FIXME marche pas le cache ... de toute facon je vais faire un storage separe ! +class Settings(Cache, SqlAlchemyBase): + __tablename__ = 'settings' + id = Column(Integer, primary_key=True) + _props = relationship("_Property", + collection_class=attribute_mapped_collection('path'), + cascade="all, delete-orphan") + _properties = association_proxy("_props", "properties") + _perms = relationship("_Permissive", + collection_class=attribute_mapped_collection('path'), + cascade="all, delete-orphan") + _permissives = association_proxy("_perms", "permissives") + + def __init__(self, storage): + super(Settings, self).__init__(storage) + + # properties + def setproperties(self, path, properties): + self._properties[path] = properties + + def getproperties(self, path, default_properties): + return self._properties.get(path, set(default_properties)) + + def hasproperties(self, path): + return path in self._properties + + def reset_all_properties(self): + self._properties.clear() + + def delproperties(self, path): + try: + del(self._properties[path]) + except KeyError: + pass + + # permissive + def setpermissive(self, path, permissive): + self._permissives[path] = frozenset(permissive) + util.session.commit() + + def getpermissive(self, path=None): + ret = self._permissives.get(path, frozenset()) + #replace None by a frozenset() + return {None: frozenset()}.get(ret, ret) + + def get_modified_properties(self): + """return all modified settings in a dictionary + example: {'path1': set(['prop1', 'prop2'])} + """ + return self._properties + + def get_modified_permissives(self): + """return all modified permissives in a dictionary + example: {'path1': set(['perm1', 'perm2'])} + """ + return self._permissives diff --git a/tiramisu/storage/sqlalchemy/storage.py b/tiramisu/storage/sqlalchemy/storage.py new file mode 100644 index 0000000..252d29e --- /dev/null +++ b/tiramisu/storage/sqlalchemy/storage.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2013 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# ____________________________________________________________ +from tiramisu.i18n import _ +from tiramisu.error import ConfigError +from ..util import SerializeObject + + +class Setting(SerializeObject): + """Dictionary storage has no particular setting. + """ + pass + + +setting = Setting() +_list_sessions = [] + + +def list_sessions(): # pragma: optional cover + return _list_sessions + + +def delete_session(session_id): # pragma: optional cover + raise ConfigError(_('dictionary storage cannot delete session')) + + +class Storage(object): + __slots__ = ('session_id', 'persistent') + storage = 'dictionary' + #if object could be serializable + serializable = True + + def __init__(self, session_id, persistent, test=False): + if not test and session_id in _list_sessions: # pragma: optional cover + raise ValueError(_('session already used')) + if persistent: # pragma: optional cover + raise ValueError(_('a dictionary cannot be persistent')) + self.session_id = session_id + self.persistent = persistent + _list_sessions.append(self.session_id) + + def __del__(self): + try: + _list_sessions.remove(self.session_id) + except AttributeError: # pragma: optional cover + pass diff --git a/tiramisu/storage/sqlalchemy/util.py b/tiramisu/storage/sqlalchemy/util.py new file mode 100644 index 0000000..7af84a1 --- /dev/null +++ b/tiramisu/storage/sqlalchemy/util.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +"" +# Copyright (C) 2014 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 General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# ____________________________________________________________ +from sqlalchemy.orm import sessionmaker +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import create_engine + + +engine = create_engine('sqlite:///:memory:') +SqlAlchemyBase = declarative_base() + +global session +session = None + + +def load(): + global session + if session is None: + #engine.echo = True + #print SqlAlchemyBase.metadata.tables.keys() + SqlAlchemyBase.metadata.create_all(engine) + Session = sessionmaker(bind=engine) + session = Session() diff --git a/tiramisu/storage/sqlalchemy/value.py b/tiramisu/storage/sqlalchemy/value.py new file mode 100644 index 0000000..30447eb --- /dev/null +++ b/tiramisu/storage/sqlalchemy/value.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +"plugin for value: set it in sqlalchemy" +# Copyright (C) 2013-2014 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 +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see . +# ____________________________________________________________ + +#FIXME : il me faut une classe pour le owner ! +#FIXME : pas si simple que ca ... parce que on lit un owner pour une config ... +#FIXME : mais ca serait peut etre logique +#FIXME : c'est en fait dans le Setting qu'il faut faire ca ... a voir après + + +from ..util import Cache +from .util import SqlAlchemyBase +from sqlalchemy import Column, Integer, String, PickleType, ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.orm.collections import attribute_mapped_collection +from sqlalchemy.ext.associationproxy import association_proxy + + +#____________________________________________________________ +# +# information +class _Vinformation(SqlAlchemyBase): + __tablename__ = 'vinformation' + id = Column(Integer, primary_key=True) + values = Column(Integer, ForeignKey('values.id')) + key = Column(String) + value = Column(PickleType) + + def __init__(self, key, value): + self.key = key + self.value = value + + +class _Value(SqlAlchemyBase): + __tablename__ = 'value' + id = Column(Integer, primary_key=True) + values = Column(Integer, ForeignKey('values.id'), nullable=False) + path = Column(String, nullable=True, unique=True, index=True) + #FIXME a revoir avec le owner dans le setting + owner = Column(String, nullable=False) + value = Column(PickleType, nullable=False) + + def __init__(self, key, value): + self.path = key + self.value = value[0] + self.owner = value[1] + + +class Values(Cache, SqlAlchemyBase): + __tablename__ = 'values' + id = Column(Integer, primary_key=True) + _vals = relationship("_Value", + collection_class=attribute_mapped_collection('key'), + cascade="all, delete-orphan") + _informations = association_proxy("_vals", "value") + _infos = relationship("_Vinformation", + collection_class=attribute_mapped_collection('key'), + cascade="all, delete-orphan") + _informations = association_proxy("_infos", "value") + + def __init__(self, storage): + """init plugin means create values storage + """ + self._values = {} + self._informations = {} + super(Values, self).__init__(storage) + + # value + def setvalue(self, path, value, owner): + """set value for a path + a specified value must be associated to an owner + """ + self._values[path] = (owner, value) + + def getvalue(self, path): + """get value for a path + return: only value, not the owner + """ + return self._values[path][1] + + def hasvalue(self, path): + """if path has a value + return: boolean + """ + return path in self._values + + def resetvalue(self, path): + """remove value means delete value in storage + """ + del(self._values[path]) + + def get_modified_values(self): + """return all values in a dictionary + example: {'path1': (owner, 'value1'), 'path2': (owner, 'value2')} + """ + return self._values + + # owner + def setowner(self, path, owner): + """change owner for a path + """ + self._values[path] = (owner, self._values[path][1]) + + def getowner(self, path, default): + """get owner for a path + return: owner object + """ + return self._values.get(path, (default, None))[0] + + def set_information(self, key, value): + """updates the information's attribute + (which is a dictionary) + + :param key: information's key (ex: "help", "doc" + :param value: information's value (ex: "the help string") + """ + self._informations[key] = value + + def get_information(self, key): + """retrieves one information's item + + :param key: the item string (ex: "help") + """ + if key in self._informations: + return self._informations[key] + else: # pragma: optional cover + raise ValueError("not found")