sqlalchemy
[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 def load_callback_parm(collection_type, proxy):
153     def getter(obj):
154         if obj is None:
155             return None
156         ret = []
157         requires = getattr(obj, proxy.value_attr)
158         for require in requires:
159             if require.value is not None:
160                 ret.append(require.value)
161             else:
162                 option = session.query(_Base).filter_by(id=require.option).first()
163                 ret.append((option, require.force_permissive))
164         return tuple(ret)
165
166     def setter(obj, value):
167         setattr(obj, proxy.value_attr, value)
168     return getter, setter
169
170
171 class _CallbackParamOption(SqlAlchemyBase):
172     __tablename__ = 'callback_param_option'
173     id = Column(Integer, primary_key=True)
174     callback_param = Column(Integer, ForeignKey('callback_param.id'))
175     option = Column(Integer)
176     force_permissive = Column(Boolean)
177     value = Column(PickleType)
178
179     def __init__(self, option=None, force_permissive=None,  value=None):
180         if value is not None:
181             self.value = value
182         else:
183             self.option = option.id
184             self.force_permissive = force_permissive
185
186
187 class _CallbackParam(SqlAlchemyBase):
188     __tablename__ = 'callback_param'
189     id = Column(Integer, primary_key=True)
190     callback = Column(Integer, ForeignKey('baseoption.id'))
191     key = Column(String)
192     params = relationship('_CallbackParamOption')
193
194     def __init__(self, key, params):
195         self.key = key
196         for param in params:
197             if isinstance(param, tuple):
198                 self.params.append(_CallbackParamOption(option=param[0],
199                                                         force_permissive=param[1]))
200             else:
201                 self.params.append(_CallbackParamOption(value=param))
202
203
204 #____________________________________________________________
205 #
206 # consistency
207 consistency_table = Table('consistencyopt', SqlAlchemyBase.metadata,
208                           Column('left_id', Integer, ForeignKey('consistency.id')),
209                           Column('right_id', Integer, ForeignKey('baseoption.id'))
210                           )
211
212
213 class _Consistency(SqlAlchemyBase):
214     __tablename__ = 'consistency'
215     id = Column(Integer, primary_key=True)
216     func = Column(PickleType)
217
218     def __init__(self, func, all_cons_opts):
219         self.func = func
220         for option in all_cons_opts:
221             option._consistencies.append(self)
222
223
224 #____________________________________________________________
225 #
226 # Base
227 class _Base(SqlAlchemyBase):
228     __tablename__ = 'baseoption'
229     id = Column(Integer, primary_key=True)
230     _name = Column(String)
231     #FIXME not autoload
232     _infos = relationship("_Information",
233                           collection_class=attribute_mapped_collection('key'),
234                           cascade="all, delete-orphan")
235     _informations = association_proxy("_infos", "value")
236     _default = Column(PickleType)
237     _default_multi = Column(PickleType)
238     _reqs = relationship("_Require", collection_class=list)
239     _requires = association_proxy("_reqs", "requires", getset_factory=load_requires)
240     _multi = Column(Boolean)
241     _multitype = Column(String)
242     ######
243     _callback = Column(PickleType)
244     _call_params = relationship('_CallbackParam',
245                                 collection_class=
246                                 attribute_mapped_collection('key'))
247     _callback_params = association_proxy("_call_params", "params",
248                                          getset_factory=load_callback_parm)
249     _validator = Column(PickleType)
250     _val_params = relationship('_CallbackParam',
251                                collection_class=
252                                attribute_mapped_collection('key'))
253     _validator_params = association_proxy("_call_params", "params",
254                                           getset_factory=load_callback_parm)
255     ######
256     _parent = Column(Integer, ForeignKey('baseoption.id'))
257     _children = relationship('BaseOption', enable_typechecks=False)
258     #FIXME pas 2 fois la meme properties dans la base ...
259     #FIXME not autoload
260     #FIXME normalement tuple ... transforme en set !
261     _props = relationship("_PropertyOption", collection_class=set)
262     _properties = association_proxy("_props", "name")
263     #FIXME fusion avec expected
264     _calc_props = relationship("_CalcProperties", collection_class=set)
265     _calc_properties = association_proxy("_calc_props", "name")
266     _warnings_only = Column(Boolean)
267     _readonly = Column(Boolean, default=False)
268     _consistencies = relationship('_Consistency', secondary=consistency_table,
269                                   backref=backref('options', enable_typechecks=False))
270     _choice_values = Column(PickleType)
271     _choice_open_values = Column(Boolean)
272     _type = Column(String(50))
273     __mapper_args__ = {
274         'polymorphic_identity': 'option',
275         'polymorphic_on': _type
276     }
277     #FIXME devrait etre une table
278     _group_type = Column(String)
279
280     def __init__(self):
281         self.commit()
282
283     def commit(self):
284         session.add(self)
285         session.commit()
286
287     def _get_property_object(self, propname):
288         prop_obj = session.query(_PropertyOption).filter(_PropertyOption.name == propname).first()
289         if prop_obj is None:
290             prop_obj = _PropertyOption(propname)
291         return prop_obj
292
293     def _add_consistency(self, func, all_cons_opts):
294         _Consistency(func, all_cons_opts)
295
296     # ____________________________________________________________
297     # information
298     def impl_set_information(self, key, value):
299         """updates the information's attribute
300         (which is a dictionary)
301
302         :param key: information's key (ex: "help", "doc"
303         :param value: information's value (ex: "the help string")
304         """
305         info = session.query(_Information).filter_by(option=self.id, key=key).first()
306         #FIXME pas append ! remplacer !
307         if info is None:
308             self._informations.append(_Information(key, value))
309         else:
310             info.value = value
311
312     def impl_get_information(self, key, default=None):
313         """retrieves one information's item
314
315         :param key: the item string (ex: "help")
316         """
317         info = session.query(_Information).filter_by(option=self.id, key=key).first()
318         if info is not None:
319             return info.value
320         elif default is not None:
321             return default
322         else:
323             raise ValueError(_("information's item not found: {0}").format(
324                 key))
325
326
327 class StorageOptionDescription(object):
328     def impl_get_opt_by_path(self, path):
329         try:
330             #FIXME
331             idx = self._cache_paths[1].index(path)
332             opt_id = self._cache_paths[0][idx]
333             return session.query(_Base).filter_by(id=opt_id).first()
334         except ValueError:
335             raise AttributeError(_('no option for path {0}').format(path))
336
337     def impl_get_path_by_opt(self, opt):
338         try:
339             return self._cache_paths[1][self._cache_paths[0].index(opt.id)]
340         except ValueError:
341             raise AttributeError(_('no option {0} found').format(opt))
342
343
344 class StorageBase(_Base):
345     @declared_attr
346     def __mapper_args__(self):
347         return {'polymorphic_identity': self.__name__.lower()}
348
349
350 #FIXME
351 SqlAlchemyBase.metadata.create_all(engine)
352 Session = sessionmaker(bind=engine)
353 session = Session()