54c3256102c97712c1531ab49e81f97b1bd882ff
[tiramisu.git] / tiramisu / storage / sqlalchemy / option.py
1 # -*- coding: utf-8 -*-
2 ""
3 # Copyright (C) 2014 Team tiramisu (see AUTHORS for all contributors)
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19 # ____________________________________________________________
20 from tiramisu.i18n import _
21
22 from sqlalchemy.ext.declarative import declarative_base, declared_attr
23 from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
24     PickleType, ForeignKey, Table
25 from sqlalchemy.orm import relationship, backref
26 from sqlalchemy.orm import sessionmaker
27
28
29 #FIXME
30 engine = create_engine('sqlite:///:memory:')
31 SqlAlchemyBase = declarative_base()
32 #FIXME a voir:
33 #         # Organization.members will be a Query object - no loading
34 #         # of the entire collection occurs unless requested
35 #         lazy="dynamic",
36 #____________________________________________________________
37 #
38 # require
39
40
41 #_Base : object dans la base de donnée
42 # => _RequireOption => il y a une liste d'espect dans _RequireExpected
43 # => _PropertyOption => liste des propriétés
44 # => _Information => dictionnaire avec clef valeur
45 # => _CallbackParam avec des Options
46 require_table = Table('require', SqlAlchemyBase.metadata,
47                       Column('left_id', Integer, ForeignKey('requireoption.id')),
48                       Column('right_id', Integer, ForeignKey('baseoption.id'))
49                       )
50
51
52 class _RequireExpected(SqlAlchemyBase):
53     __tablename__ = 'expected'
54     id = Column(Integer, primary_key=True)
55     expected = Column(PickleType)
56     require = Column(Integer, ForeignKey('requireoption.id'))
57
58     def __init__(self, expected):
59         self.expected = expected
60
61
62 class _RequireOption(SqlAlchemyBase):
63     __tablename__ = 'requireoption'
64     id = Column(Integer, primary_key=True)
65     option = relationship('_Base', lazy='joined', cascade="all, delete-orphan")
66     #option = relationship('_Base')
67     expected = relationship("_RequireExpected")
68     action = Column(String, nullable=False)
69     inverse = Column(Boolean, default=False)
70     transitive = Column(Boolean, default=True)
71     same_action = Column(Boolean, default=True)
72
73     def __init__(self, option, expected, action, inverse, transitive,
74                  same_action):
75         #self.r_opt = option.id
76         self.option = option
77         for expect in expected:
78             self.expected.append(_RequireExpected(expect))
79         self.action = action
80         self.inverse = inverse
81         self.transitive = transitive
82         self.same_action = same_action
83
84     def get_expected(self):
85         for expected in self.expected:
86             yield(expected.expected)
87
88     #def get_option(self, config):
89     #    return config.cfgimpl_get_description().impl_get_opt_by_id(self.r_opt)
90
91
92 #____________________________________________________________
93 #
94 # properties
95 property_table = Table('property', SqlAlchemyBase.metadata,
96                        Column('left_id', Integer, ForeignKey('propertyoption.name')),
97                        Column('right_id', Integer, ForeignKey('baseoption.id'))
98                        )
99
100
101 class _PropertyOption(SqlAlchemyBase):
102     __tablename__ = 'propertyoption'
103     name = Column(String, primary_key=True)
104
105     def __init__(self, name):
106         self.name = name
107
108
109 #____________________________________________________________
110 #
111 # information
112 class _Information(SqlAlchemyBase):
113     __tablename__ = 'information'
114     id = Column(Integer, primary_key=True)
115     option = Column(Integer, ForeignKey('baseoption.id'))
116     key = Column(String)
117     value = Column(PickleType)
118
119     def __init__(self, key, value):
120         self.key = key
121         self.value = value
122
123
124 #____________________________________________________________
125 #
126 # callback
127 class _CallbackParamOption(SqlAlchemyBase):
128     __tablename__ = 'callback_param_option'
129     id = Column(Integer, primary_key=True)
130     callback_param = Column(Integer, ForeignKey('callback_param.id'))
131     option = Column(Integer)
132     force_permissive = Column(Boolean)
133     value = Column(PickleType)
134
135     def __init__(self, option=None, force_permissive=None,  value=None):
136         if value is not None:
137             self.value = value
138         else:
139             self.option = option.id
140             self.force_permissive = force_permissive
141
142
143 class _CallbackParam(SqlAlchemyBase):
144     __tablename__ = 'callback_param'
145     id = Column(Integer, primary_key=True)
146     callback = Column(Integer, ForeignKey('baseoption.id'))
147     name = Column(String)
148     params = relationship('_CallbackParamOption')
149
150     def __init__(self, name, params):
151         self.name = name
152         for param in params:
153             if isinstance(param, tuple):
154                 self.params.append(_CallbackParamOption(option=param[0],
155                                                         force_permissive=param[1]))
156             else:
157                 self.params.append(_CallbackParamOption(value=param))
158
159
160 #____________________________________________________________
161 #
162 # consistency
163 consistency_table = Table('consistencyopt', SqlAlchemyBase.metadata,
164                           Column('left_id', Integer, ForeignKey('consistency.id')),
165                           Column('right_id', Integer, ForeignKey('baseoption.id'))
166                           )
167
168
169 class _Consistency(SqlAlchemyBase):
170     __tablename__ = 'consistency'
171     id = Column(Integer, primary_key=True)
172     func = Column(PickleType)
173
174     def __init__(self, func, all_cons_opts):
175         self.func = func
176         for option in all_cons_opts:
177             option._consistencies.append(self)
178
179
180 #____________________________________________________________
181 #
182 # Base
183 class _Base(SqlAlchemyBase):
184     __tablename__ = 'baseoption'
185     id = Column(Integer, primary_key=True)
186     _name = Column(String)
187     _informations = relationship('_Information')
188     _default = Column(PickleType)
189     _default_multi = Column(PickleType)
190     _requires = relationship('_RequireOption', secondary=require_table,
191                              backref=backref('self_option', enable_typechecks=False))
192     _multi = Column(Boolean)
193     _multitype = Column(String)
194     _callback = Column(PickleType)
195     _callback_params = relationship('_CallbackParam')
196     _validator = Column(PickleType)
197     _validator_params = relationship('_CallbackParam')
198     _parent = Column(Integer, ForeignKey('baseoption.id'))
199     _children = relationship('BaseOption', enable_typechecks=False)
200     _properties = relationship('_PropertyOption', secondary=property_table,
201                                backref=backref('options', enable_typechecks=False))
202     _warnings_only = Column(Boolean)
203     _readonly = Column(Boolean, default=False)
204     _consistencies = relationship('_Consistency', secondary=consistency_table,
205                                   backref=backref('options', enable_typechecks=False))
206     _choice_values = Column(PickleType)
207     _choice_open_values = Column(Boolean)
208     _type = Column(String(50))
209     _r_option = Column(Integer, ForeignKey('requireoption.id'))
210     __mapper_args__ = {
211         'polymorphic_identity': 'option',
212         'polymorphic_on': _type
213     }
214     #FIXME devrait etre une table
215     _optiondescription_group_type = Column(String)
216
217     def __init__(self):
218         self.commit()
219
220     def commit(self):
221         session.add(self)
222         session.commit()
223
224     def _get_property_object(self, propname):
225         prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == propname).first()
226         if prop_obj is None:
227             prop_obj = _PropertyOption(propname)
228         return prop_obj
229
230     def _add_require(self, require):
231         self._requires.append(_RequireOption(*require))
232
233     def _add_callback(self, key, values):
234         self._callback_params.append(_CallbackParam(key, values))
235
236     def _add_validator(self, key, values):
237         self._validator_params.append(_CallbackParam(key, values))
238
239     def _add_consistency(self, func, all_cons_opts):
240         _Consistency(func, all_cons_opts)
241
242     # ____________________________________________________________
243     # information
244     def impl_set_information(self, key, value):
245         """updates the information's attribute
246         (which is a dictionary)
247
248         :param key: information's key (ex: "help", "doc"
249         :param value: information's value (ex: "the help string")
250         """
251         info = session.query(_Information).filter_by(option=self.id, key=key).first()
252         #FIXME pas append ! remplacer !
253         if info is None:
254             self._informations.append(_Information(key, value))
255         else:
256             info.value = value
257
258     def impl_get_information(self, key, default=None):
259         """retrieves one information's item
260
261         :param key: the item string (ex: "help")
262         """
263         info = session.query(_Information).filter_by(option=self.id, key=key).first()
264         if info is not None:
265             return info.value
266         elif default is not None:
267             return default
268         else:
269             raise ValueError(_("information's item not found: {0}").format(
270                 key))
271
272
273 class StorageOptionDescription(object):
274     def impl_get_opt_by_path(self, path):
275         try:
276             #FIXME
277             idx = self._cache_paths[1].index(path)
278             opt_id = self._cache_paths[0][idx]
279             return session.query(_Base).filter_by(id=opt_id).first()
280         except ValueError:
281             raise AttributeError(_('no option for path {0}').format(path))
282
283     def impl_get_path_by_opt(self, opt):
284         try:
285             print opt, type(opt)
286             return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
287         except ValueError:
288             raise AttributeError(_('no option {0} found').format(opt))
289
290
291 class StorageBase(_Base):
292     @declared_attr
293     def __mapper_args__(self):
294         return {'polymorphic_identity': self.__name__.lower()}
295
296
297 #FIXME
298 SqlAlchemyBase.metadata.create_all(engine)
299 Session = sessionmaker(bind=engine)
300 session = Session()