rabaDB2 2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rabaDB/Raba.py ADDED
@@ -0,0 +1,992 @@
1
+ import sqlite3 as sq
2
+ import os, copy, pickle, random, json, abc, sys
3
+ from collections.abc import MutableSequence
4
+
5
+ from .rabaSetup import RabaConnection, RabaConfiguration
6
+ from . import fields as RabaFields
7
+
8
+ def _recClassCheck(v, cls) :
9
+ if v is cls : return True
10
+
11
+ res = False
12
+ for a in v.__bases__ :
13
+ if a is cls :
14
+ return True
15
+ res = res or _recClassCheck(a, cls)
16
+ return res
17
+
18
+ def isRabaClass(v) :
19
+ return _recClassCheck(v, Raba)
20
+
21
+ def isRabaList(v) :
22
+ return _recClassCheck(v.__class__, RabaList) or isRabaListPupa(v)
23
+
24
+ def isRabaListPupa(v) :
25
+ return _recClassCheck(v.__class__, RabaListPupa)
26
+
27
+ def isRabaObject(v) :
28
+ return isRabaClass(v.__class__) or isRabaObjectPupa(v)
29
+
30
+ def isRabaObjectPupa(v) :
31
+ return _recClassCheck(v.__class__, RabaPupa)
32
+
33
+ def isPythonPrimitive(v) :
34
+ primTypes = [int, int, float, bytes, str, memoryview, type(None)]
35
+ for t in primTypes :
36
+ if isinstance(v, t) :
37
+ return True
38
+ return False
39
+
40
+ _RabaList_instances = {}
41
+ def _registerRabaListInstance(lst, anchorObj, relationName) :
42
+ global _RabaList_instances
43
+ key = (anchorObj._runtimeId, relationName)
44
+ _RabaList_instances[key] = lst
45
+
46
+ def _unregisterRabaListInstance(lst) :
47
+ global _RabaList_instances
48
+ key = (lst.anchorObj._runtimeId, lst.relationName)
49
+ try :
50
+ del(_RabaList_instances[key])
51
+ except KeyError :
52
+ pass
53
+
54
+ def _getRabaListInstance(anchorObj, relationName) :
55
+ global _RabaList_instances
56
+ key = (anchorObj._runtimeId, relationName)
57
+ return _RabaList_instances[key]
58
+
59
+ _RabaObject_instances = {}
60
+ def _registerRabaObjectInstance(obj) :
61
+ global _RabaObject_instances
62
+ key = (obj.__class__, obj._raba_namespace, obj.raba_id)
63
+ _RabaObject_instances[key] = obj
64
+
65
+ def _unregisterRabaObjectInstance(obj) :
66
+ global _RabaObject_instances
67
+ key = (obj.__class__, obj._raba_namespace, obj.raba_id)
68
+
69
+ if not isRabaObjectPupa(obj) :
70
+ for l in obj.rabaLists :
71
+ _unregisterRabaListInstance(l)
72
+
73
+ try :
74
+ del(_RabaObject_instances[key])
75
+ except KeyError :
76
+ pass
77
+
78
+ def _getRabaObjectInstance(cls, namespace, raba_id) :
79
+ global _RabaObject_instances
80
+ key = (cls, namespace, raba_id)
81
+ return _RabaObject_instances[key]
82
+
83
+ class _RabaListPupaSingleton_Metaclass(abc.ABCMeta):
84
+
85
+ def __call__(clsObj, *args, **kwargs):
86
+ anchorObj = kwargs['anchorObj']
87
+ relationName = kwargs['relationName']
88
+ length = kwargs['length']
89
+
90
+ try :
91
+ return _getRabaListInstance(anchorObj, relationName)
92
+ except KeyError:
93
+ pass
94
+
95
+ connection = RabaConnection(anchorObj._raba_namespace)
96
+
97
+ obj = super(_RabaListPupaSingleton_Metaclass, clsObj).__call__(*args, anchorObj = anchorObj, relationName = relationName, length = length)
98
+
99
+ _registerRabaListInstance(obj, anchorObj, relationName)
100
+
101
+ return obj
102
+
103
+ class _RabaListSingleton_Metaclass(abc.ABCMeta):
104
+ def __call__(clsObj, *args, **kwargs):
105
+
106
+ if 'anchorObj' in kwargs and 'relationName' in kwargs :
107
+ anchorObj = kwargs['anchorObj']
108
+ relationName = kwargs['relationName']
109
+
110
+ try :
111
+ return _getRabaListInstance(anchorObj, relationName)
112
+ except KeyError:
113
+ pass
114
+
115
+ obj = super(_RabaListSingleton_Metaclass, clsObj).__call__(*args, **kwargs)
116
+
117
+ _registerRabaListInstance(obj, anchorObj, relationName)
118
+
119
+ return super(_RabaListSingleton_Metaclass, clsObj).__call__(*args, **kwargs)
120
+
121
+ class _RabaPupaSingleton_Metaclass(type):
122
+ def __call__(clsObj, *args, **kwargs):
123
+
124
+ if 'classObj' in kwargs :
125
+ cls = kwargs['classObj']
126
+ else :
127
+ cls = args[0]
128
+
129
+ if 'raba_id' in kwargs :
130
+ raba_id = kwargs['raba_id']
131
+ else :
132
+ raba_id = args[1]
133
+
134
+ try :
135
+ return _getRabaObjectInstance(cls, cls._raba_namespace, raba_id)
136
+ except KeyError :
137
+ pass
138
+
139
+ obj = super(_RabaPupaSingleton_Metaclass, clsObj).__call__(*args, **kwargs)
140
+ _registerRabaObjectInstance(obj)
141
+
142
+ return obj
143
+
144
+ class _RabaSingleton_MetaClass(type) :
145
+
146
+ _instances = {}
147
+
148
+ def __new__(cls, name, bases, dct) :
149
+ if '_raba_abstract' not in dct or not dct['_raba_abstract'] :
150
+ def getFields_rec(name, sqlFields, columns, columnsToLowerCase, dct, bases) :
151
+ i = 0
152
+ if name != "Raba" :
153
+ for base in bases :
154
+ i += getFields_rec(base.__name__, sqlFields, columns, columnsToLowerCase, base.__dict__, base.__bases__)
155
+
156
+ for k, v in dct.items() :
157
+ if RabaFields.isField(v) :
158
+ sk = str(k)
159
+ if k.lower() != 'raba_id' and k.lower() != 'json' :
160
+ sqlFields.append(sk)
161
+
162
+ columns[sk] = i
163
+ columnsToLowerCase[sk.lower()] = sk
164
+ i += 1
165
+ return i
166
+
167
+ sqlFields = []
168
+ columns = {}
169
+ columnsToLowerCase = {}
170
+ getFields_rec(name, sqlFields, columns, columnsToLowerCase, dct, bases)
171
+ columns["raba_id"] = 0
172
+ columns['json'] = 1
173
+
174
+ try :
175
+ con = RabaConnection(dct['_raba_namespace'])
176
+ except KeyError :
177
+ raise ValueError("The class %s has no defined namespace, please add a valid '_raba_namespace' to class attributes" % name)
178
+
179
+ uniqueStr = ''
180
+ if '_raba_uniques' in dct :
181
+ for c in dct['_raba_uniques'] :
182
+ if type(c) is str :
183
+ uniqueStr += 'UNIQUE (%s) ON CONFLICT REPLACE, ' % c
184
+ elif len(c) == 1 :
185
+ uniqueStr += 'UNIQUE (%s) ON CONFLICT REPLACE, ' % c[0]
186
+ else :
187
+ uniqueStr += 'UNIQUE %s ON CONFLICT REPLACE, ' % str(c)
188
+ uniqueStr = uniqueStr[:-2]
189
+
190
+ if not con.tableExits(name) :
191
+ idJsonStr = 'raba_id INTEGER PRIMARY KEY, json '
192
+ if len(sqlFields) > 0 :
193
+ if len(uniqueStr) > 0 :
194
+ con.createTable(name, '%s, %s, %s' % (idJsonStr, ', '.join(list(sqlFields)), uniqueStr))
195
+ else :
196
+ con.createTable(name, '%s, %s' % (idJsonStr, ', '.join(list(sqlFields))))
197
+ else :
198
+ con.createTable(name, '%s' % idJsonStr)
199
+
200
+ sqlCons = 'INSERT INTO raba_tables_constraints (table_name, constraints) VALUES (?, ?)'
201
+ con.execute(sqlCons, (name, uniqueStr))
202
+ else :
203
+ sql = 'SELECT constraints FROM raba_tables_constraints WHERE table_name = ?'
204
+
205
+ cur = con.execute(sql, (name,))
206
+ res = cur.fetchone()
207
+
208
+ if res != None and res[0]!= '' and res[0] != uniqueStr :
209
+ sys.stderr.write('Warning: The unique contraints have changed from:\n\t%s\n\nto:\n\t%s.\n-Unique constraints modification is not supported yet-\n' %(res[0], uniqueStr))
210
+ #raise FutureWarning('Warning: The unique contraints have changed from:\n\t%s\n\nto:\n\t%s.\n-Unique constraints modification is not supported yet-\n' %(res[0], uniqueStr))
211
+
212
+ cur = con.execute('PRAGMA table_info("%s")' % name)
213
+ tableColumns = set()
214
+ tableColumnsToKeep = []
215
+ tableColumnsToDrop = []
216
+
217
+ mustClean = False
218
+ columns = {}
219
+ columns['raba_id'] = 0
220
+ columns['json'] = 1
221
+ i = len(columns)
222
+ for c in cur :
223
+ if c[1] != 'raba_id' and c[1] != 'json' :
224
+ if c[1].lower() not in columnsToLowerCase :
225
+ mustClean = True
226
+ con.dropRabalist(name, c[1])
227
+ tableColumnsToDrop.append(c[1])
228
+ else :
229
+ tableColumnsToKeep.append(c[1])
230
+ columns[columnsToLowerCase[c[1].lower()]] = i
231
+ i += 1
232
+ tableColumns.add(c[1].lower())
233
+
234
+ if mustClean :
235
+ con.dropColumnsFromRabaObjTable(name, tableColumnsToKeep)
236
+
237
+ mustAlter = False
238
+ for k in columnsToLowerCase :
239
+ if k not in tableColumns :
240
+ con.execute('ALTER TABLE %s ADD COLUMN %s' % (name, columnsToLowerCase[k]))
241
+ mustAlter = True
242
+
243
+ if mustClean or mustAlter :
244
+ con.forceCommit()
245
+
246
+ dct['columns'] = columns
247
+ dct['columnsToLowerCase'] = columnsToLowerCase
248
+
249
+ clsObj = type.__new__(cls, name, bases, dct)
250
+ con.registerRabaClass(clsObj)
251
+ return clsObj
252
+
253
+ return type.__new__(cls, name, bases, dct)
254
+
255
+ def __call__(cls, *args, **fieldsDct) :
256
+ if cls == Raba :
257
+ return super(_RabaSingleton_MetaClass, cls).__call__(**fieldsDct)
258
+
259
+ if 'raba_id' in fieldsDct :
260
+ try :
261
+ return _getRabaObjectInstance(cls, cls._raba_namespace, fieldsDct['raba_id'])
262
+ except KeyError :
263
+ pass
264
+ else :
265
+ key = None
266
+
267
+ params = copy.copy(fieldsDct)
268
+ nonRabaParams = {}
269
+ for p, v in list(params.items()) :
270
+ if p in cls.columns :
271
+ if isRabaObject(v) :
272
+ params[p] = v.getJsonEncoding()
273
+ else :
274
+ nonRabaParams[p] = v
275
+ del(params[p])
276
+
277
+ connection = RabaConnection(cls._raba_namespace)
278
+ ret = connection.getRabaObjectInfos(cls.__name__, params)
279
+
280
+ if ret != None :
281
+ dbLine = ret.fetchone()
282
+ else :
283
+ dbLine = None
284
+
285
+ if dbLine != None :
286
+ if ret.fetchone() != None :
287
+ raise ValueError("More than one object fit the arguments you've provided to the constructor")
288
+
289
+ raba_id = dbLine[0]
290
+ try :
291
+ return _getRabaObjectInstance(cls, cls._raba_namespace, raba_id)
292
+ except KeyError :
293
+ pass
294
+
295
+ obj = Raba.__new__(cls, *args, **nonRabaParams)
296
+ obj._raba__init__(initDbLine = dbLine)
297
+ obj.__init__(*args, **nonRabaParams)
298
+
299
+ if not hasattr(cls, '_raba_not_a_singleton') or not getattr(cls, '_raba_not_a_singleton') :
300
+ _registerRabaObjectInstance(obj)
301
+
302
+ elif len(params) > 0 : # params provided but no result
303
+ raise KeyError("Couldn't find any object that fit the arguments you've provided to the constructor")
304
+ else :
305
+ obj = type.__call__(cls, *args, **fieldsDct)
306
+ obj._raba__init__(**fieldsDct)
307
+
308
+ return obj
309
+
310
+ def freeRegistery() :
311
+ """Empties all registeries. This is useful if you want to allow the garbage collector to free the memory
312
+ taken by the objects you've already loaded. Be careful might cause some discrepenties in your scripts"""
313
+ freeListRegistery()
314
+ freeObjectRegistery()
315
+
316
+ def freeListRegistery() :
317
+ """same as freeRegistery() bu only for lists"""
318
+ global _RabaList_instances
319
+ _RabaList_instances = {}
320
+
321
+ def freeObjectRegistery() :
322
+ """same as freeRegistery() bu only for objects"""
323
+ global _RabaObject_instances
324
+ _RabaObject_instances = {}
325
+
326
+ def removeFromRegistery(obj) :
327
+ """Removes an object/rabalist from registery. This is useful if you want to allow the garbage collector to free the memory
328
+ taken by the objects you've already loaded. Be careful might cause some discrepenties in your scripts. For objects,
329
+ cascades to free the registeries of related rabalists also"""
330
+
331
+ if isRabaObject(obj) :
332
+ _unregisterRabaObjectInstance(obj)
333
+ elif isRabaList(obj) :
334
+ _unregisterRabaListInstance(obj)
335
+
336
+ class RabaPupa(object, metaclass=_RabaPupaSingleton_Metaclass) :
337
+ """One of the founding principles of RabaDB is to separate the storage from the code. Fields are stored in the DB while the processing only depends
338
+ on your python code. This approach ensures a higher degree of stability by preventing old objects from lurking inside the DB before popping out of nowhere several decades afterwards.
339
+ According to this apparoach, raba objects are not serialised but transformed into pupas before being stored. A pupa is a very light object that contains only a reference
340
+ to the raba object class, and it's unique raba_id. Upon asking for one of the attributes of a pupa, it magically transforms into a full fledged raba object. This process is completly transparent to the user. Pupas also have the advantage of being light weight and also ensure that the only raba objects loaded are those explicitely accessed, thus potentialy saving a lot of memory.
341
+ For a pupa self._rabaClass refers to the class of the object "inside" the pupa.
342
+ """
343
+
344
+ def __init__(self, classObj, raba_id) :
345
+ self._rabaClass = classObj
346
+ self.raba_id = raba_id
347
+ self._raba_namespace = classObj._raba_namespace
348
+ self.__doc__ = classObj.__doc__
349
+
350
+ def develop(self) :
351
+ def getAttr(name) :
352
+ return object.__getattribute__(self, name)
353
+
354
+ def setAttr(name, value) :
355
+ object.__setattr__(self, name, value)
356
+
357
+ rabaClass = getAttr('_rabaClass')
358
+ uniqueId = getAttr('raba_id')
359
+ connection = RabaConnection(getAttr('_raba_namespace'))
360
+ dbLine = connection.getRabaObjectInfos(getAttr('_rabaClass').__name__, {'raba_id' : uniqueId}).fetchone()
361
+ setAttr('__class__', rabaClass)
362
+ purge = getAttr('__dict__').keys()
363
+ for k in list(purge) :
364
+ delattr(self, k)
365
+
366
+ self._rabaClass = rabaClass
367
+ self.connection = connection
368
+ rabaClass._raba__init__(self, initDbLine = dbLine)
369
+ rabaClass.__init__(self)
370
+
371
+ def getDctDescription(self) :
372
+ "returns a dict describing the object"
373
+ return {'type' : RabaFields.RABA_FIELD_TYPE_IS_RABA_OBJECT, 'className' : self._rabaClass.__name__, 'raba_id' : self.raba_id, 'raba_namespace' : self._raba_namespace}
374
+
375
+ def getJsonEncoding(self) :
376
+ "returns a json encoding of self.getDctDescription()"
377
+ return json.dumps(self.getDctDescription(), sort_keys=True) # sort_keys added during migration to python3
378
+
379
+ def __getattr__(self, name) :
380
+ develop = object.__getattribute__(self, "develop")
381
+ develop()
382
+
383
+ return self.__getattribute__(name)
384
+
385
+ def __str__(self) :
386
+ self.develop()
387
+ return str(self)
388
+
389
+ def __repr__(self) :
390
+ return "<RabaObj pupa: %s, raba_id %s>" % (self._rabaClass.__name__, self.raba_id)
391
+
392
+ class Raba(object, metaclass=_RabaSingleton_MetaClass):
393
+ "All raba object inherit from this class"
394
+ raba_id = RabaFields.Primitive()
395
+ json = RabaFields.Primitive()
396
+ _raba_abstract = True
397
+
398
+ def __init__(self, *a, **b) :
399
+ pass
400
+
401
+ def unreference(self) :
402
+ "explicit deletes the object from the singleton reference dictionary. This is mandatory to be able to delete the object using del(). Also, any attempt to reload an object with the same parameters will result un a new instance being created"
403
+ _unregisterRabaObjectInstance(self)
404
+
405
+ def _initDbLine(self, dbLine) :
406
+ self.raba_id = dbLine[self.__class__.columns['raba_id']]
407
+ self.json = dbLine[self.__class__.columns['json']]
408
+
409
+ lists = []
410
+ for kk, i in self.columns.items() :
411
+ k = self.columnsToLowerCase[kk.lower()]
412
+ elmt = getattr(self._rabaClass, k)
413
+ if RabaFields.isPrimitiveField(elmt) :
414
+ try :
415
+ self.__setattr__(k, pickle.loads(bytes(dbLine[i])))
416
+ except :
417
+ self.__setattr__(k, dbLine[i])
418
+
419
+ elif RabaFields.isRabaObjectField(elmt) :
420
+ if dbLine[i] != None :
421
+ val = json.loads(dbLine[i])
422
+ objClass = RabaConnection(val["raba_namespace"]).getClass(val["className"])
423
+ self.__setattr__(k, RabaPupa(objClass, val["raba_id"]))
424
+ elif RabaFields.isRabaListField(elmt) :
425
+ if dbLine[i] == None :
426
+ lists.append((k, 0))
427
+ else :
428
+ lists.append((k, int(dbLine[i])))
429
+ else :
430
+ raise ValueError("Unable to set field %s to %s in Raba object %s" %(k, dbLine[i], self._rabaClass.__name__))
431
+
432
+ #~ self.rabaLists = []
433
+ for k, leng in lists :
434
+ rlp = RabaListPupa(anchorObj = self, relationName = k, length = leng)
435
+ self.__setattr__(k, rlp)
436
+ self.rabaLists.append(rlp)
437
+
438
+ def _raba__init__(self, **fieldsSet) :
439
+
440
+ self.sqlSave = {}
441
+ self.sqlSaveQMarks = {}
442
+ self.listsToSave = {}
443
+ self.rabaLists = []
444
+
445
+ if self.__class__ is Raba :
446
+ raise TypeError('Raba class should never be instanciated, use inheritance')
447
+
448
+ self._runtimeId = (self.__class__.__name__, random.random()) #this is used only during runtime ex, to avoid circular calls
449
+ self._rabaClass = self.__class__
450
+
451
+ self.connection = RabaConnection(self._rabaClass._raba_namespace)
452
+ self.rabaConfiguration = RabaConfiguration(self._rabaClass._raba_namespace)
453
+
454
+ self._saved = False #True if present in the database
455
+
456
+ if 'initDbLine' in fieldsSet and 'initDbLine' != None :
457
+ self._initDbLine(fieldsSet['initDbLine'])
458
+ self._saved = True
459
+
460
+ if self.raba_id == None :
461
+ self.raba_id = self.connection.getNextRabaId(self)
462
+
463
+ def pupa(self) :
464
+ """returns a pupa version of self"""
465
+ return RabaPupa(self.__class__, self.raba_id)
466
+
467
+ def develop(self) :
468
+ "Dummy fct, so when you call develop on a full developed object you don't get nasty exceptions"
469
+ pass
470
+
471
+ @classmethod
472
+ def _parseIndex(cls, fields) :
473
+ con = RabaConnection(cls._raba_namespace)
474
+ ff = []
475
+ rlf = []
476
+ tmpf = []
477
+ if type(fields) is str :
478
+ tmpf.append(fields)
479
+ else :
480
+ tmpf = fields
481
+
482
+ for field in tmpf :
483
+ if RabaFields.isRabaListField(getattr(cls, field)) :
484
+ lname = con.makeRabaListTableName(cls.__name__, field)
485
+ rlf.append(lname, )
486
+ else :
487
+ ff.append(field)
488
+
489
+ return rlf, ff
490
+
491
+ @classmethod
492
+ def ensureIndex(cls, fields, where = '', whereValues = []) :
493
+ """Add an index for field, indexes take place and slow down saves and deletes but they speed up a lot everything else. If you are going to do a lot of saves/deletes drop the indexes first re-add them afterwards
494
+ Fields can be a list of fields for Multi-Column Indices or simply the name of a single field. But as RabaList are basicaly in separate tables you cannot create a multicolumn indice on them. A single index will
495
+ be create for the RabaList alone"""
496
+ con = RabaConnection(cls._raba_namespace)
497
+ rlf, ff = cls._parseIndex(fields)
498
+ ww = []
499
+ for i in range(len(whereValues)) :
500
+ if isRabaObject(whereValues[i]) :
501
+ ww.append(whereValues[i].getJsonEncoding())
502
+
503
+ for name in rlf :
504
+ con.createIndex(name, 'anchor_raba_id')
505
+
506
+ if len(ff) > 0 :
507
+ con.createIndex(cls.__name__, ff, where = where, whereValues = ww)
508
+ con.commit()
509
+
510
+ @classmethod
511
+ def dropIndex(cls, fields) :
512
+ "removes an index created with ensureIndex "
513
+ con = RabaConnection(cls._raba_namespace)
514
+ rlf, ff = cls._parseIndex(fields)
515
+
516
+ for name in rlf :
517
+ con.dropIndex(name, 'anchor_raba_id')
518
+
519
+ con.dropIndex(cls.__name__, ff)
520
+ con.commit()
521
+
522
+ @classmethod
523
+ def getIndexes(cls) :
524
+ "returns a list of the indexes of a class"
525
+ con = RabaConnection(cls._raba_namespace)
526
+ idxs = []
527
+ for idx in con.getIndexes(rabaOnly = True) :
528
+ if idx[2] == cls.__name__ :
529
+ idxs.append(idx)
530
+ else :
531
+ for k in cls.columns :
532
+ if RabaFields.isRabaListField(getattr(cls, k)) and idx[2] == con.makeRabaListTableName(cls.__name__, k) :
533
+ idxs.append(idx)
534
+ return idxs
535
+
536
+ @classmethod
537
+ def flushIndexes(cls) :
538
+ "drops all indexes for a class"
539
+ con = RabaConnection(cls._raba_namespace)
540
+ for idx in cls.getIndexes() :
541
+ con.dropIndexByName(idx[1])
542
+
543
+ def mutated(self) :
544
+ 'returns True if the object has changed since the last save'
545
+ return len(self.sqlSave) > 0 or len(self.listsToSave) > 0
546
+
547
+ def save(self) :
548
+ if self.mutated() :
549
+ # Reset the savedObject set so this save session starts fresh.
550
+ # Without this, lists saved in a previous save() call would
551
+ # be skipped by registerSave() and never re-saved.
552
+ self.connection.savedObject = set()
553
+ if not self.raba_id :
554
+ raise ValueError("Field raba_id of self has the not int value %s therefore i cannot save the object, sorry" % (self, ))
555
+
556
+ for k, v in self.listsToSave.items() :
557
+ v._save()
558
+ self.sqlSave[k] = len(v)
559
+ if not self._saved : #this dict is only for optimisation purpose for generating the insert sql
560
+ self.sqlSaveQMarks[k] = '?'
561
+
562
+ self.sqlSave['json'] = self.getJsonEncoding()
563
+ if not self._saved : #this dict is only for optimisation purpose for generating the insert sql
564
+ self.sqlSaveQMarks['json'] = '?'
565
+
566
+ if not self._saved :
567
+ values = list(self.sqlSave.values())
568
+ sql = 'INSERT INTO %s (%s) VALUES (%s)' % (self.__class__.__name__, ', '.join(list(self.sqlSave.keys())), ', '.join(list(self.sqlSaveQMarks.values())))
569
+ else :
570
+ values = list(self.sqlSave.values())
571
+ sql = 'UPDATE %s SET %s = ? WHERE raba_id = ?' % (self.__class__.__name__, ' = ?, '.join(list(self.sqlSave.keys())))
572
+ values.append(self.raba_id)
573
+
574
+ self.connection.execute(sql, values)
575
+ self.connection.commit()
576
+ self._saved = True
577
+ self.sqlSave = {}
578
+ self.sqlSaveQMarks = {}
579
+ self.listsToSave = {}
580
+
581
+ def delete(self) :
582
+ if self._saved :
583
+ for c in self.columnsToLowerCase.values() :
584
+ if isRabaList(getattr(self, c)) :
585
+ getattr(self, c).empty()
586
+ self.connection.delete(table = self.__class__.__name__, where = 'raba_id = ?', values = (self.raba_id, ))
587
+ self.connection.commit()
588
+
589
+ def copy(self) :
590
+ v = copy.copy(self)
591
+ v.raba_id = None
592
+ return v
593
+
594
+ def getDctDescription(self) :
595
+ "returns a dict describing the object"
596
+ return {'type' : RabaFields.RABA_FIELD_TYPE_IS_RABA_OBJECT, 'className' : self._rabaClass.__name__, 'raba_id' : self.raba_id, 'raba_namespace' : self._raba_namespace}
597
+
598
+ def getJsonEncoding(self) :
599
+ "returns a json encoding of self.getDctDescription()"
600
+ return json.dumps(self.getDctDescription(), sort_keys=True) # sort_keys added during migration to python3
601
+
602
+ def set(self, **args) :
603
+ "set multiple values quickly, ex : name = woopy"
604
+ for k, v in args.items() :
605
+ setattr(self, k, v)
606
+
607
+ def __setattr__(self, k, v) :
608
+ "This also keeps track of wich fields have been updated."
609
+ vv = v
610
+ if hasattr(self.__class__, k) and RabaFields.isField(getattr(self.__class__, k)) :
611
+ vSQL = None
612
+ if not RabaFields.isRabaListField(getattr(self.__class__, k)) :
613
+ classType = getattr(self.__class__, k)
614
+ if not classType.check(vv) :
615
+ raise ValueError("Unable to set '%s' to value '%s'. Constrain function violation" % (k, vv))
616
+ if isRabaObject(vv) :
617
+ vSQL = vv.getJsonEncoding()
618
+ elif isPythonPrimitive(vv):
619
+ vSQL = vv
620
+ else :
621
+ vSQL = memoryview(pickle.dumps(vv))
622
+
623
+ self.sqlSave[k] = vSQL
624
+
625
+ if not self._saved : #this dict is only for optimisation purpose for generating the insert sql
626
+ self.sqlSaveQMarks[k] = '?'
627
+ else :
628
+ if not isRabaList(vv) and not isRabaListPupa(vv) :
629
+ try :
630
+ vv = RabaList(v)
631
+ except :
632
+ raise ValueError("Unable to set '%s' to value '%s'. Value is not a valid RabaList" % (k, vv))
633
+
634
+ currList = object.__getattribute__(self, k)
635
+ if not RabaFields.isRabaListField(currList) and vv is not currList and len(currList) > 0 :
636
+ currList.empty()
637
+ self.connection.unregisterRabalist(anchor_class_name = self.__class__.__name__, anchor_raba_id = self.raba_id, relation_name = k)
638
+
639
+ vv._attachToObject(self, k)
640
+ self.listsToSave[k] = vv #self.sqlSave[k] and self.sqlSaveQMarks[k] are updated in self.save() for lists
641
+
642
+ object.__setattr__(self, k, vv)
643
+
644
+ def __getattribute__(self, k) :
645
+ try :
646
+ elmt = object.__getattribute__(self, k)
647
+ if RabaFields.isRabaListField(elmt) : #if empty
648
+ elmt = RabaListPupa(anchorObj = self, relationName = k, length = -1)
649
+ object.__setattr__(self, k, elmt)
650
+ elif RabaFields.isField(elmt) :
651
+ elmt = elmt.default
652
+
653
+ return elmt
654
+ except AttributeError :
655
+ return self.__getattr__(k)
656
+
657
+ def __getattr__(self, k) :
658
+ raise AttributeError('%s instance has no attribute "%s"' %(self.__class__.__name__, k))
659
+
660
+ def __getitem__(self, k) :
661
+ return self.__getattribute__(k)
662
+
663
+ def __setitem__(self, k, v) :
664
+ self. __setattr__(k, v)
665
+
666
+ def __repr__(self) :
667
+ return "<Raba obj: %s, raba_id: %s>" % (self._runtimeId, self.raba_id)
668
+
669
+ @classmethod
670
+ def getFields(cls) :
671
+ """returns a set of the available fields. In order to be able ti securely loop of the fields, "raba_id" and "json" are not included in the set"""
672
+ s = set(cls.columns.keys())
673
+ s.remove('json')
674
+ s.remove('raba_id')
675
+ return s
676
+
677
+ @classmethod
678
+ def help(cls) :
679
+ "returns a string of lisinting available fields"
680
+ return 'Available fields for %s: %s' %(cls.__name__, ', '.join(cls.getFields()))
681
+
682
+ class RabaListPupa(MutableSequence, metaclass=_RabaListPupaSingleton_Metaclass) :
683
+
684
+ _isRabaList = True
685
+
686
+ def __init__(self, **kwargs) :
687
+ self._runtimeId = (self.__class__.__name__, random.random()) #this is using only during runtime ex, to avoid circular calls
688
+ self.anchorObj = kwargs['anchorObj']
689
+ self.relationName = kwargs['relationName']
690
+ self.length = kwargs['length']
691
+ self._raba_namespace = self.anchorObj._raba_namespace
692
+
693
+ self.connection = RabaConnection(self._raba_namespace)
694
+
695
+ self.tableName = self.connection.makeRabaListTableName(self.anchorObj._rabaClass.__name__, self.relationName)
696
+
697
+ def develop(self) :
698
+ MutableSequence.__setattr__(self, '__class__', RabaList)
699
+
700
+ initFromPupa = {}
701
+
702
+ initFromPupa['relationName'] = MutableSequence.__getattribute__(self, 'relationName')
703
+ initFromPupa['anchorObj'] = MutableSequence.__getattribute__(self, 'anchorObj')
704
+ initFromPupa['_raba_namespace'] = MutableSequence.__getattribute__(self, '_raba_namespace')
705
+ initFromPupa['tableName'] = MutableSequence.__getattribute__(self, 'tableName')
706
+ initFromPupa['length'] = MutableSequence.__getattribute__(self, 'length')
707
+ #initFromPupa['raba_id'] = MutableSequence.__getattribute__(self, 'raba_id')
708
+
709
+ purge = MutableSequence.__getattribute__(self, '__dict__').keys()
710
+ for k in list(purge) :
711
+ delattr(self, k)
712
+
713
+ RabaList.__init__(self, initFromPupa = initFromPupa)
714
+ RabaList._attachToObject(self, initFromPupa['anchorObj'], initFromPupa['relationName'])
715
+
716
+ def __getitem__(self, i) :
717
+ self.develop()
718
+ return self[i]
719
+
720
+ def __delitem__(self, i) :
721
+ self.develop()
722
+ self.__delitem__(i)
723
+
724
+ def __setitem__(self, k, v) :
725
+ self.develop()
726
+ self.__setitem__(k, v)
727
+
728
+ def insert(self, i, v) :
729
+ self.develop()
730
+ self.insert(i, v)
731
+
732
+ def append(self, k) :
733
+ self.develop()
734
+ self.append(k)
735
+
736
+ def _attachToObject(self, anchorObj, relationName) :
737
+ "dummy fct for compatibility reasons, a RabaListPupa is attached by default"
738
+ #MutableSequence.__getattribute__(self, "develop")()
739
+ self.develop()
740
+ self._attachToObject(anchorObj, relationName)
741
+
742
+ def _save(self, *args, **kwargs) :
743
+ "dummy fct for compatibility reasons, a RabaListPupa represents an unmodified list so there's nothing to save"
744
+ self.develop()
745
+ RabaList._save(self)
746
+
747
+ def __getattr__(self, name) :
748
+ self.develop()
749
+ return MutableSequence.__getattribute__(self, name)
750
+
751
+ def __repr__(self) :
752
+ return "[%s length: %d, relationName: %s, anchorObj: %s, raba_id: %s]" % (self._runtimeId, self.length, self.relationName, self.anchorObj, self.raba_id)
753
+
754
+ def __len__(self) :
755
+ if self.length < 0 :
756
+ self.develop()
757
+ return self.length
758
+
759
+ class RabaList(MutableSequence, metaclass=_RabaListSingleton_Metaclass) :
760
+ """A RabaList is a list that can only contain Raba objects of the same class or (Pupas of the same class). They represent one to many relations and are stored in separate
761
+ tables that contain only one single line"""
762
+
763
+ _isRabaList = True
764
+
765
+ def _checkElmt(self, v) :
766
+ if self.anchorObj != None :
767
+ return getattr(self.anchorObj._rabaClass, self.relationName).check(v)
768
+ else :
769
+ return True
770
+
771
+ def _checkSelf(self) :
772
+ """Checks the entire list, returns (faultyElmt, list namespace), if the list passes the check, faultyElmt = None"""
773
+ for e in self :
774
+ if not self._checkElmt(e) :
775
+ return e
776
+ return None
777
+
778
+ def _dieInvalidRaba(self, v) :
779
+ st = """The element %s can't be added to the list, possible causes:
780
+ -The element is a RabaObject wich namespace is different from list's namespace
781
+ -The element violates the constraint function""" % v
782
+
783
+ raise TypeError(st)
784
+
785
+ def __init__(self, *listElements, **listArguments) :
786
+ """To avoid the check of all elements of listElements during initialisation pass : noInitCheck = True as argument
787
+ It is also possible to define both the anchor object and the namespace durint initalisation using argument keywords: anchorObj and namespace. But only do it
788
+ if you really now what you are doing."""
789
+ self._runtimeId = (self.__class__.__name__, random.random())#this is used only during runtime ex, to avoid circular calls
790
+
791
+ self.raba_id = None
792
+ self.relationName = None
793
+ self.tableName = None
794
+ self.anchorObj = None
795
+
796
+ self._raba_namespace = None
797
+ self.connection = None
798
+ self.rabaConfiguration = None
799
+ self._saved = False
800
+ self._mutated = True
801
+
802
+ if len(listElements) > 0 :
803
+ self.data = list(listElements[0])
804
+ else :
805
+ self.data = []
806
+
807
+ if 'initFromPupa' in listArguments :
808
+ pupaInit = listArguments['initFromPupa']
809
+ self.anchorObj = pupaInit['anchorObj']
810
+ self.relationName = pupaInit['relationName']
811
+ self._raba_namespace = self.anchorObj._raba_namespace
812
+ #self.raba_id = pupaInit['raba_id']
813
+ self.tableName = pupaInit['tableName']
814
+ length = pupaInit['length']
815
+
816
+ self._setNamespaceConAndConf(self.anchorObj._raba_namespace)
817
+ self.connection.createRabaListTable(self.tableName)
818
+ #if self.raba_id == None :
819
+ # self.raba_id, self.tableName = self.connection.registerRabalist(self.anchorObj._rabaClass.__name__, self.anchorObj.raba_id, self.relationName)
820
+
821
+ if length > 0 :
822
+ sql, values = 'SELECT * FROM %s WHERE anchor_raba_id = ?' % self.tableName, (self.anchorObj.raba_id, )
823
+ cur = self.connection.execute(sql, values)
824
+ for aidi in cur :
825
+ self._saved = True
826
+ value = aidi[2]
827
+ typ = aidi[3]
828
+ className = aidi[4]
829
+ raba_id = aidi[5]
830
+ raba_namespace = aidi[6]
831
+ if typ == RabaFields.RABA_FIELD_TYPE_IS_PRIMITIVE :
832
+ # Try to unpickle if value is a memoryview/bytes
833
+ # (non-primitive values are pickled before storage)
834
+ if isinstance(value, (memoryview, bytes, bytearray)) :
835
+ try :
836
+ self.append(pickle.loads(bytes(value)))
837
+ except Exception :
838
+ self.append(value)
839
+ else :
840
+ self.append(value)
841
+ elif typ == RabaFields.RABA_FIELD_TYPE_IS_RABA_LIST :
842
+ raise FutureWarning('RabaList in RabaList not supported')
843
+ elif typ == RabaFields.RABA_FIELD_TYPE_IS_RABA_OBJECT :
844
+ classObj = RabaConnection(raba_namespace).getClass(className)
845
+ self.append(RabaPupa(classObj, raba_id))
846
+ else :
847
+ raise ValueError("Unknown type: %s in rabalist %s" % (typ, self))
848
+
849
+ def mutated(self) :
850
+ 'returns True if the object has changed since the last save'
851
+ return self._mutated
852
+
853
+ def pupatizeElements(self) :
854
+ """Transform all raba object into pupas"""
855
+ for i in range(len(self)) :
856
+ self[i] = self[i].pupa()
857
+
858
+ def empty(self) :
859
+ if self.tableName != None and self.anchorObj != None :
860
+ sql = 'DELETE FROM %s WHERE anchor_raba_id = ?' % self.tableName
861
+ self.connection.execute(sql, (self.anchorObj.raba_id,))
862
+
863
+ def _save(self) :
864
+ """saves the RabaList into it's own table. This a private function that should be called directly
865
+ Before saving the entire list corresponding to the anchorObj is wiped out before being rewritten. The
866
+ alternative would be to keep the sync between the list and the table in real time (remove in both).
867
+ If the current solution proves to be to slow, i'll consider the alternative"""
868
+
869
+ if self.connection.registerSave(self) :
870
+ if len(self) == 0 :
871
+ self.connection.updateRabaListLength(self.raba_id, len(self))
872
+ return True
873
+ else :
874
+ if self.relationName == None or self.anchorObj == None :
875
+ raise ValueError('%s has not been attached to any object, impossible to save it' % (self, ))
876
+
877
+ #if self.raba_id == None :
878
+ # self.raba_id, self.tableName = self.connection.registerRabalist(self.anchorObj._rabaClass.__name__, self.anchorObj.raba_id, self.relationName)
879
+
880
+ if self._saved :
881
+ self.empty()
882
+
883
+ values = []
884
+ for e in self.data :
885
+ if isRabaObject(e) :
886
+ e.save()
887
+ objDct = e.getDctDescription()
888
+ values.append((self.anchorObj.raba_id, None, RabaFields.RABA_FIELD_TYPE_IS_RABA_OBJECT, e._rabaClass.__name__, e.raba_id, e._raba_namespace))
889
+ elif isPythonPrimitive(e) :
890
+ values.append((self.anchorObj.raba_id, e, RabaFields.RABA_FIELD_TYPE_IS_PRIMITIVE, None, None, None))
891
+ else :
892
+ values.append((self.anchorObj.raba_id, memoryview(pickle.dumps(e)), RabaFields.RABA_FIELD_TYPE_IS_PRIMITIVE, None, None, None))
893
+
894
+ self.connection.executeMany('INSERT INTO %s (anchor_raba_id, value, type, obj_raba_class_name, obj_raba_id, obj_raba_namespace) VALUES (?, ?, ?, ?, ?, ?)' % self.tableName, values)
895
+
896
+ #self.connection.updateRabaListLength(self.raba_id, len(self))
897
+ self._saved = True
898
+ self._mutated = False
899
+ return True
900
+ else :
901
+ return False
902
+
903
+ def _attachToObject(self, anchorObj, relationName) :
904
+ "Attaches the rabalist to a raba object. Only attached rabalists can be saved"
905
+ if self.anchorObj == None :
906
+ self.relationName = relationName
907
+ self.anchorObj = anchorObj
908
+ self._setNamespaceConAndConf(anchorObj._rabaClass._raba_namespace)
909
+ self.tableName = self.connection.makeRabaListTableName(self.anchorObj._rabaClass.__name__, self.relationName)
910
+ faultyElmt = self._checkSelf()
911
+ if faultyElmt != None :
912
+ raise ValueError("Element %s violates specified list or relation constraints" % faultyElmt)
913
+ elif self.anchorObj is not anchorObj :
914
+ raise ValueError("Ouch: attempt to steal rabalist, use RabaLict.copy() instead.\nthief: %s\nvictim: %s\nlist: %s" % (anchorObj, self.anchorObj, self))
915
+
916
+ def pupa(self) :
917
+ return RabaListPupa(self.namespace, self.anchorObj, self.relationName)
918
+
919
+ def _mutateNotifyAnchor(self) :
920
+ self._mutated = True
921
+ if self.anchorObj is not None and self.relationName is not None :
922
+ self.anchorObj.listsToSave[self.relationName] = self
923
+
924
+ def _setNamespaceConAndConf(self, namespace) :
925
+ self._raba_namespace = namespace
926
+ self.connection = RabaConnection(self._raba_namespace)
927
+ self.rabaConfiguration = RabaConfiguration(self._raba_namespace)
928
+
929
+ def extend(self, v) :
930
+ # Check each element against the list's constraint
931
+ for el in v :
932
+ if not self._checkElmt(el) :
933
+ self._dieInvalidRaba(el)
934
+
935
+ namespace = None
936
+ for el in v :
937
+ if isRabaObject(el) :
938
+ namespace = el._raba_namespace
939
+ break
940
+
941
+ self.data.extend(v)
942
+ if self._raba_namespace == None and namespace != None :
943
+ self._setNamespaceConAndConf(namespace)
944
+
945
+ #@profile
946
+ def append(self, v) :
947
+ if not self._checkElmt(v) :
948
+ self._dieInvalidRaba(v)
949
+
950
+ if self._raba_namespace == None and isRabaObject(v) :
951
+ self._setNamespaceConAndConf(v._raba_namespace)
952
+
953
+ self.data.append(v)
954
+ self._mutateNotifyAnchor()
955
+
956
+ def insert(self, k, v) :
957
+ if not self._checkElmt(v) :
958
+ self._dieInvalidRaba(v)
959
+
960
+ if self._raba_namespace == None and isRabaObject(v) :
961
+ self._setNamespaceConAndConf(v._raba_namespace)
962
+
963
+ self.data.insert(k, v)
964
+ self._mutateNotifyAnchor()
965
+
966
+ def set(self, lst) :
967
+ self.data = list(lst)
968
+ self._mutateNotifyAnchor()
969
+
970
+ def __setitem__(self, k, v) :
971
+ if not self._checkElmt(v) :
972
+ self._dieInvalidRaba(v)
973
+
974
+ if self._raba_namespace == None and isRabaObject(v) :
975
+ self._setNamespaceConAndConf(v._raba_namespace)
976
+
977
+ self.data[k] = v
978
+ self._mutateNotifyAnchor()
979
+
980
+ def __delitem__(self, i) :
981
+ del self.data[i]
982
+ self._mutateNotifyAnchor()
983
+
984
+ def __getitem__(self, i) :
985
+ return self.data[i]
986
+ self._mutateNotifyAnchor()
987
+
988
+ def __len__(self) :
989
+ return len(self.data)
990
+
991
+ def __repr__(self) :
992
+ return '[ %s, len: %d, anchor: %s, table: %s]' % (self._runtimeId, len(self), self.anchorObj, self.tableName)