require works well in sqlalchemy storage
[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.ext.associationproxy import association_proxy
24 from sqlalchemy import create_engine, Column, Integer, String, Boolean, \
25     PickleType, ForeignKey, Table
26 from sqlalchemy.orm import relationship, backref
27 from sqlalchemy.orm import sessionmaker
28 from sqlalchemy.orm.collections import attribute_mapped_collection
29
30
31 #FIXME
32 engine = create_engine('sqlite:///:memory:')
33 SqlAlchemyBase = declarative_base()
34 #FIXME a voir:
35 #         # Organization.members will be a Query object - no loading
36 #         # of the entire collection occurs unless requested
37 #         lazy="dynamic",
38 #____________________________________________________________
39 #
40 # require
41
42
43 #_Base : object dans la base de donnée
44 # => _PropertyOption => liste des propriétés
45 # => _CallbackParam avec des Options
46 def load_requires(collection_type, proxy):
47     def getter(obj):
48         if obj is None:
49             return None
50         ret = []
51         requires = getattr(obj, proxy.value_attr)
52         for require in requires:
53             option = session.query(_Base).filter_by(id=require.option).first()
54             ret.append(tuple([option, require.expected, require.action, require.inverse, require.transitive, require.same_action]))
55         return tuple(ret)
56
57     def setter(obj, value):
58         setattr(obj, proxy.value_attr, value)
59     return getter, setter
60
61
62 class _Require(SqlAlchemyBase):
63     __tablename__ = "require"
64     id = Column(Integer, primary_key=True)
65     requires_id = Column(Integer, ForeignKey("baseoption.id"), nullable=False)
66     requires = relationship('_RequireOption')
67
68     def __init__(self, requires):
69         for require in requires:
70             self.requires.append(_RequireOption(require))
71
72
73 class _RequireOption(SqlAlchemyBase):
74     __tablename__ = 'requireoption'
75     id = Column(Integer, primary_key=True)
76     require_id = Column(Integer, ForeignKey("require.id"), nullable=False)
77     option = Column(Integer, nullable=False)
78     _expected = relationship("_RequireExpected", collection_class=list,
79                              cascade="all, delete-orphan")
80     expected = association_proxy("_expected", "expected")
81     #expected = Column(String)
82     action = Column(String, nullable=False)
83     inverse = Column(Boolean, default=False)
84     transitive = Column(Boolean, default=True)
85     same_action = Column(Boolean, default=True)
86
87     def __init__(self, values):
88         option, expected, action, inverse, transitive, same_action = values
89         self.option = option.id
90         self.expected = expected
91         self.action = action
92         self.inverse = inverse
93         self.transitive = transitive
94         self.same_action = same_action
95
96
97 class _RequireExpected(SqlAlchemyBase):
98     __tablename__ = 'expected'
99     id = Column(Integer, primary_key=True)
100     require = Column(Integer, ForeignKey('requireoption.id'), nullable=False)
101     expected = Column(PickleType)
102
103     def __init__(self, expected):
104         #FIXME ne pas creer plusieurs fois la meme _expected_
105         #FIXME pareil avec calc_properties
106         self.expected = expected
107
108
109 class _CalcProperties(SqlAlchemyBase):
110     __tablename__ = 'calcproperty'
111     id = Column(Integer, primary_key=True)
112     require = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
113     name = Column(PickleType)
114
115     def __init__(self, name):
116         #FIXME ne pas creer plusieurs fois la meme _expected_
117         #FIXME pareil avec calc_properties
118         self.name = name
119
120
121 #____________________________________________________________
122 #
123 # properties
124 class _PropertyOption(SqlAlchemyBase):
125     __tablename__ = 'propertyoption'
126     id = Column(Integer, primary_key=True)
127     option = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
128     name = Column(String)
129
130     def __init__(self, name):
131         self.name = name
132
133
134 #____________________________________________________________
135 #
136 # information
137 class _Information(SqlAlchemyBase):
138     __tablename__ = 'information'
139     id = Column(Integer, primary_key=True)
140     option = Column(Integer, ForeignKey('baseoption.id'), nullable=False)
141     key = Column(String)
142     value = Column(PickleType)
143
144     def __init__(self, key, value):
145         self.key = key
146         self.value = value
147
148
149 #____________________________________________________________
150 #
151 # callback
152 class _CallbackParamOption(SqlAlchemyBase):
153     __tablename__ = 'callback_param_option'
154     id = Column(Integer, primary_key=True)
155     callback_param = Column(Integer, ForeignKey('callback_param.id'))
156     option = Column(Integer)
157     force_permissive = Column(Boolean)
158     value = Column(PickleType)
159
160     def __init__(self, option=None, force_permissive=None,  value=None):
161         if value is not None:
162             self.value = value
163         else:
164             self.option = option.id
165             self.force_permissive = force_permissive
166
167
168 class _CallbackParam(SqlAlchemyBase):
169     __tablename__ = 'callback_param'
170     id = Column(Integer, primary_key=True)
171     callback = Column(Integer, ForeignKey('baseoption.id'))
172     name = Column(String)
173     params = relationship('_CallbackParamOption')
174
175     def __init__(self, name, params):
176         self.name = name
177         for param in params:
178             if isinstance(param, tuple):
179                 self.params.append(_CallbackParamOption(option=param[0],
180                                                         force_permissive=param[1]))
181             else:
182                 self.params.append(_CallbackParamOption(value=param))
183
184
185 #____________________________________________________________
186 #
187 # consistency
188 consistency_table = Table('consistencyopt', SqlAlchemyBase.metadata,
189                           Column('left_id', Integer, ForeignKey('consistency.id')),
190                           Column('right_id', Integer, ForeignKey('baseoption.id'))
191                           )
192
193
194 class _Consistency(SqlAlchemyBase):
195     __tablename__ = 'consistency'
196     id = Column(Integer, primary_key=True)
197     func = Column(PickleType)
198
199     def __init__(self, func, all_cons_opts):
200         self.func = func
201         for option in all_cons_opts:
202             option._consistencies.append(self)
203
204
205 #____________________________________________________________
206 #
207 # Base
208 class _Base(SqlAlchemyBase):
209     __tablename__ = 'baseoption'
210     id = Column(Integer, primary_key=True)
211     _name = Column(String)
212     #FIXME not autoload
213     _infos = relationship("_Information",
214                           collection_class=attribute_mapped_collection('key'),
215                           cascade="all, delete-orphan")
216     _informations = association_proxy("_infos", "value")
217     _default = Column(PickleType)
218     _default_multi = Column(PickleType)
219     _reqs = relationship("_Require", collection_class=list)
220     _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
221     _multi = Column(Boolean)
222     _multitype = Column(String)
223     _callback = Column(PickleType)
224     _callback_params = relationship('_CallbackParam')
225     _validator = Column(PickleType)
226     _validator_params = relationship('_CallbackParam')
227     _parent = Column(Integer, ForeignKey('baseoption.id'))
228     _children = relationship('BaseOption', enable_typechecks=False)
229     #FIXME pas 2 fois la meme properties dans la base ...
230     #FIXME not autoload
231     #FIXME normalement tuple ... transforme en set !
232     _props = relationship("_PropertyOption", collection_class=set)
233     _properties = association_proxy("_props", "name")
234     #FIXME fusion avec expected
235     _calc_props = relationship("_CalcProperties", collection_class=set)
236     _calc_properties = association_proxy("_calc_props", "name")
237     _warnings_only = Column(Boolean)
238     _readonly = Column(Boolean, default=False)
239     _consistencies = relationship('_Consistency', secondary=consistency_table,
240                                   backref=backref('options', enable_typechecks=False))
241     _choice_values = Column(PickleType)
242     _choice_open_values = Column(Boolean)
243     _type = Column(String(50))
244     __mapper_args__ = {
245         'polymorphic_identity': 'option',
246         'polymorphic_on': _type
247     }
248     #FIXME devrait etre une table
249     _optiondescription_group_type = Column(String)
250
251     def __init__(self):
252         self.commit()
253
254     def commit(self):
255         session.add(self)
256         session.commit()
257
258     def _get_property_object(self, propname):
259         prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == propname).first()
260         if prop_obj is None:
261             prop_obj = _PropertyOption(propname)
262         return prop_obj
263
264     #def _add_require(self, require):
265     #    self._requires.append(_RequireOption(*require))
266
267     def _add_callback(self, key, values):
268         self._callback_params.append(_CallbackParam(key, values))
269
270     def _add_validator(self, key, values):
271         self._validator_params.append(_CallbackParam(key, values))
272
273     def _add_consistency(self, func, all_cons_opts):
274         _Consistency(func, all_cons_opts)
275
276     # ____________________________________________________________
277     # information
278     def impl_set_information(self, key, value):
279         """updates the information's attribute
280         (which is a dictionary)
281
282         :param key: information's key (ex: "help", "doc"
283         :param value: information's value (ex: "the help string")
284         """
285         info = session.query(_Information).filter_by(option=self.id, key=key).first()
286         #FIXME pas append ! remplacer !
287         if info is None:
288             self._informations.append(_Information(key, value))
289         else:
290             info.value = value
291
292     def impl_get_information(self, key, default=None):
293         """retrieves one information's item
294
295         :param key: the item string (ex: "help")
296         """
297         info = session.query(_Information).filter_by(option=self.id, key=key).first()
298         if info is not None:
299             return info.value
300         elif default is not None:
301             return default
302         else:
303             raise ValueError(_("information's item not found: {0}").format(
304                 key))
305
306
307 class StorageOptionDescription(object):
308     def impl_get_opt_by_path(self, path):
309         try:
310             #FIXME
311             idx = self._cache_paths[1].index(path)
312             opt_id = self._cache_paths[0][idx]
313             return session.query(_Base).filter_by(id=opt_id).first()
314         except ValueError:
315             raise AttributeError(_('no option for path {0}').format(path))
316
317     def impl_get_path_by_opt(self, opt):
318         try:
319             return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
320         except ValueError:
321             raise AttributeError(_('no option {0} found').format(opt))
322
323
324 class StorageBase(_Base):
325     @declared_attr
326     def __mapper_args__(self):
327         return {'polymorphic_identity': self.__name__.lower()}
328
329
330 #FIXME
331 SqlAlchemyBase.metadata.create_all(engine)
332 Session = sessionmaker(bind=engine)
333 session = Session()