Browse Source

Starts backref handling for mongodb datasource plugin

Methods are written.
They are not called by anyone and the only tests that were done is that no syntax error is raised.
( refs #131 )
Yann Weber 8 years ago
parent
commit
24a64eea59

+ 10
- 1
lodel/leapi/datahandlers/base_classes.py View File

@@ -256,10 +256,19 @@ class Reference(DataHandler):
256 256
         self.__back_reference = None
257 257
         self.__set_back_reference(back_reference)
258 258
         super().__init__(internal=internal, **kwargs)
259
-    
259
+ 
260
+    ##@brief Property that takes value of a copy of the back_reference tuple
260 261
     @property
261 262
     def back_reference(self):
262 263
         return copy.copy(self.__back_reference)
264
+    
265
+    ##@brief Property that takes value of datahandler of the backreference or 
266
+    #None
267
+    @property
268
+    def back_ref_datahandler(self):
269
+        if self.__back_reference is None:
270
+            return None
271
+        return self.__back_reference[0].data_handler(self.__back_reference[1])
263 272
 
264 273
     @property
265 274
     def linked_classes(self):

+ 2
- 0
lodel/leapi/datahandlers/exceptions.py View File

@@ -0,0 +1,2 @@
1
+def LodelDataHandlerException(Exception):
2
+    pass

+ 21
- 1
lodel/leapi/leobject.py View File

@@ -13,6 +13,7 @@ from lodel.plugin.exceptions import *
13 13
 from lodel.plugin.hooks import LodelHook
14 14
 from lodel.plugin import Plugin, DatasourcePlugin
15 15
 from lodel.leapi.datahandlers.base_classes import DatasConstructor
16
+from lodel.leapi.datahandlers.base_classes import Reference
16 17
 
17 18
 ##@brief Stores the name of the field present in each LeObject that indicates
18 19
 #the name of LeObject subclass represented by this object
@@ -149,6 +150,16 @@ class LeObject(object):
149 150
             raise NameError("No field named '%s' in %s" % (fieldname, cls.__name__))
150 151
         return cls._fields[fieldname]
151 152
     
153
+    ##@brief Getter for references datahandlers
154
+    #@param with_backref bool : if true return only references with back_references
155
+    #@return <code>{'fieldname': datahandler, ...}</code>
156
+    @classmethod
157
+    def reference_handlers(cls, with_backref = True):
158
+        return {    fname: fdh 
159
+                    for fname, fdh in cls.fields(True)
160
+                    if issubclass(fdh, Reference) and \
161
+                        (not with_backref or fdh.backreference is not None)}
162
+    
152 163
     ##@brief Return a LeObject child class from a name
153 164
     # @warning This method has to be called from dynamically generated LeObjects
154 165
     # @param leobject_name str : LeObject name
@@ -604,7 +615,16 @@ construction and consitency when datas are not complete\n")
604 615
         
605 616
         return objects
606 617
     
607
-
618
+    ##@brief Retrieve an object given an UID
619
+    #@todo broken multiple UID
620
+    @classmethod
621
+    def get_from_uid(cls, uid):
622
+        uidname = cls.uid_fieldname()[0] #Brokes composed UID
623
+        res = cls.get([(uidname,'=', uid)])
624
+        if len(res) > 1:
625
+            raise LodelFatalError("Get from uid returned more than one \
626
+object ! For class %s with uid value = %s" % (cls, uid))
627
+        return res
608 628
 
609 629
         
610 630
         

+ 2
- 0
plugins/__init__.py View File

@@ -0,0 +1,2 @@
1
+##@defgroup lodel2_plugins_list Plugins lodel
2
+#@brief Regroup all implemented plugin documentation

+ 8
- 0
plugins/mongodb_datasource/__init__.py View File

@@ -19,3 +19,11 @@ __fullname__ = "MongoDB plugin"
19 19
 def _activate():
20 20
     from lodel import buildconf
21 21
     return buildconf.PYMONGO
22
+
23
+#
24
+#   Doxygen comments
25
+#
26
+
27
+##@defgroup plugin_mongodb_datasource MongoDB datasource plugin
28
+#@brief Doc about mongodb datasource
29
+

+ 4
- 0
plugins/mongodb_datasource/confspec.py View File

@@ -2,6 +2,10 @@
2 2
 
3 3
 from lodel.settings.validator import SettingValidator
4 4
 
5
+##@brief Mongodb datasource plugin confspec
6
+#@ingroup plugin_mongodb_datasource
7
+#
8
+#Describe mongodb plugin configuration. Keys are :
5 9
 CONFSPEC = {
6 10
     'lodel2.datasource.mongodb_datasource.*':{
7 11
         'read_only': (False, SettingValidator('bool')),

+ 276
- 2
plugins/mongodb_datasource/datasource.py View File

@@ -11,14 +11,18 @@ from pymongo.errors import BulkWriteError
11 11
 
12 12
 from lodel import logger
13 13
 from lodel.leapi.leobject import CLASS_ID_FIELDNAME
14
+from lodel.leapi.datahandlers.base_classes import Reference, MultipleRef
15
+from lodel.exceptions import LodelException, LodelFatalError
14 16
 
15 17
 from . import utils
18
+from .exceptions import *
16 19
 from .utils import object_collection_name, collection_name, \
17 20
     MONGODB_SORT_OPERATORS_MAP, connection_string, mongo_fieldname
18 21
 
19
-class MongoDbDataSourceError(Exception):
20
-    pass
21 22
 
23
+##@brief Datasource class
24
+#@ingroup plugin_mongodb_datasource
25
+#@todo Make it inherit from an abstract datasource !
22 26
 class MongoDbDatasource(object):
23 27
 
24 28
     ##@brief Stores existing connections
@@ -238,6 +242,276 @@ class MongoDbDatasource(object):
238 242
             target.make_consistency(datas=new_datas)
239 243
         return list(res.inserted_ids)
240 244
     
245
+    ##@brief Update back references of an object
246
+    #@ingroup plugin_mongodb_bref_op
247
+    #
248
+    #old_datas and new_datas arguments are set to None to indicate 
249
+    #insertion or deletion. Calls examples :
250
+    #@par LeObject insert __update backref call
251
+    #<pre>
252
+    #Insert(datas):
253
+    #  self.make_insert(datas)
254
+    #  self.__update_backref(self.__class__, None, datas)
255
+    #</pre>
256
+    #@par LeObject delete __update backref call
257
+    #Delete()
258
+    #  old_datas = self.datas()
259
+    #  self.make_delete()
260
+    #  self.__update_backref(self.__class__, old_datas, None)
261
+    #@par LeObject update __update_backref call
262
+    #<pre>
263
+    #Update(new_datas):
264
+    #  old_datas = self.datas()
265
+    #  self.make_udpdate(new_datas)
266
+    #  self.__update_backref(self.__class__, old_datas, new_datas)
267
+    #</pre>
268
+    #
269
+    #@param target LeObject child class
270
+    #@param old_datas dict : datas state before update
271
+    #@param new_datas dict : datas state after the update process
272
+    #retun None
273
+    def __update_backref(self, target, old_datas, new_datas):
274
+        #upd_dict is the dict that will allow to run updates in an optimized
275
+        #way (or try to help doing it)
276
+        #
277
+        #It's struct looks like :
278
+        # { LeoCLASS : {
279
+        #       UID1: (
280
+        #           LeoINSTANCE,
281
+        #           { fname1 : value, fname2: value }),
282
+        #       UID2 (LeoINSTANCE, {fname...}),
283
+        #       },
284
+        #   LeoClass2: {...
285
+        #
286
+        upd_dict = {}
287
+        for fname, fdh in target.reference_handlers():
288
+            oldd = fname in old_datas
289
+            newd = fname in new_datas
290
+            if (oldd and newd and old_datas[fname] == new_datas[fname])\
291
+                    or not(oldd or newd):
292
+                #No changes or not concerned
293
+                continue
294
+            bref_cls = fdh.data_handler[0]
295
+            bref_fname = fdh.data_handler[1]
296
+            if issubclass(fdh, MultipleRef):
297
+                #fdh is a multiple ref. So the update preparation will be
298
+                #divided into two loops :
299
+                #- one loop for deleting old datas
300
+                #- one loop for inserting updated datas
301
+                #
302
+                #Preparing the list of values to delete or to add
303
+                if newd and oldd:
304
+                    old_values = old_datas[fname]
305
+                    new_values = new_datas[fname]
306
+                    to_del = [  val
307
+                                for val in old_values
308
+                                if val not in new_values]
309
+                    to_add = [  val
310
+                                for val in new_values
311
+                                if val not in old_values]
312
+                elif oldd and not newdd:
313
+                    to_del = old_datas[fname]
314
+                    to_add = []
315
+                elif not oldd and newdd:
316
+                    to_del = []
317
+                    to_add = new_datas[fname]
318
+                #Calling __back_ref_upd_one_value() with good arguments
319
+                for vtype, vlist in [('old',to_del), ('new', to_add)]:
320
+                    for value in vlist:
321
+                        #fetching backref infos
322
+                        bref_infos = self.__bref_get_check(
323
+                            bref_cls, value, bref_fname)
324
+                        #preparing the upd_dict
325
+                        upd_dict = self.__update_backref_upd_dict_prepare(
326
+                            upd_dict, bref_infos)
327
+                        #preparing updated bref_infos
328
+                        bref_cls, bref_leo, bref_dh, bref_value = bref_infos
329
+                        bref_infos = tuple(bref_cls, bref_leo, bref_dh,
330
+                            upd_dict[bref_cls][uid_val][1][bref_fname])
331
+                        vdict = {vtype: value}
332
+                        #fetch and store updated value
333
+                        new_bref_val = self.__back_ref_upd_one_value(
334
+                            fname, fdh, bref_infos, **vdict)
335
+                        upd_dict[bref_cls][uid_val][1][bref_fname] = new_bref_val
336
+            else:
337
+                #fdh is a single ref so the process is simpler, we do not have
338
+                #to loop and we may do an update in only one
339
+                #__back_ref_upd_one_value() call by giving both old and new
340
+                #value
341
+                vdict = {}
342
+                if oldd:
343
+                    vdict['old'] = new_datas[fname]
344
+                    uid_val = vdict['old']
345
+                if newd:
346
+                    vdict['new'] = new_datas[fname]
347
+                    if not oldd:
348
+                        uid_val = vdict['new']
349
+                #Fetching back ref infos
350
+                bref_infos = self.__bref_get_check(
351
+                    bref_cls, uid_val, bref_fname)
352
+                #prepare the upd_dict
353
+                upd_dict = self.__update_backref_upd_dict_prepare(
354
+                    upd_dict, bref_infos)
355
+                #forging update bref_infos
356
+                bref_cls, bref_leo, bref_dh, bref_value = bref_infos
357
+                bref_infos = tuple(bref_cls, bref_leo, bref_dh,
358
+                        upd_dict[bref_cls][uid_val][1][bref_fname])
359
+                #fetche and store updated value
360
+                new_bref_val = self.__back_ref_upd_one_value(
361
+                    fname, fdh , **vdict)
362
+                upd_dict[bref_cls][uid_val][1][bref_fname] = new_bref_val
363
+        #Now we've got our upd_dict ready.
364
+        #running the updates
365
+        for bref_cls, uid_dict in upd_dict.items():
366
+            for uidval, (leo, datas) in uid_dict.items():
367
+                leo.update(datas)
368
+    
369
+    ##@brief Utility function designed to handle the upd_dict of 
370
+    #__update_backref()
371
+    #
372
+    #@param upd_dict dict : in & out args modified by reference
373
+    #@param bref_infos tuple : as returned by __bref_get_check()
374
+    #@return the updated version of upd_dict
375
+    @staticmethod
376
+    def __update_backref_upd_dict_prepare(upd_dict,bref_infos):
377
+        bref_cls, bref_leo, bref_dh, bref_value = bref_infos
378
+        if bref_cls not in upd_dict:
379
+            upd_dict[bref_cls] = {}
380
+        if uid_val not in upd_dict[bref_cls]:
381
+            upd_dict[bref_cls][uid_val] = tuple(bref_leo, {})
382
+        if bref_fname not in upd_dict[bref_cls][uid_val]:
383
+            upd_dict[bref_cls][uid_val][1][bref_fname] = bref_value
384
+        return upd_dict
385
+        
386
+        
387
+    ##@brief Prepare a one value back reference update
388
+    #@param fname str : the source Reference field name
389
+    #@param fdh DataHandler : the source Reference DataHandler
390
+    #@param bref_infos tuple : as returned by __bref_get_check() method
391
+    #@param old mixed : (optional **values) the old value
392
+    #@param new mixed : (optional **values) the new value
393
+    #@return the new back reference field value
394
+    def __back_ref_upd_one_value(self, fname, fdh, bref_infos, **values):
395
+        bref_cls, bref_leo, bref_dh, bref_val = bref_infos
396
+        oldd = 'old' in values
397
+        newd = 'new' in values
398
+        if oldd:
399
+            #
400
+            # We got an old value. It can be an update or a delete
401
+            #
402
+            old_value = old_datas[fname]
403
+            bref_cls, bref_leo, bref_dh, bref_val = self.__bref_get_check(
404
+                bref_cls, old_value, bref_fname)
405
+            if issubclass(bref_dh, MultipleRef):
406
+                #
407
+                # Multiple ref update (iterable)
408
+                if old_value not in bref_val:
409
+                    raise MongodbConsistencyError("The value we want to \
410
+replace in this back reference update was not found in the back referenced \
411
+object : %s field %s" % (bref_leo, ref_fname))
412
+                if isinstance(old_value, set):
413
+                    #Specific handling for set (values are not indexed)
414
+                    bref_val -= set([old_value])
415
+                    if newd:
416
+                        # update value
417
+                        bref_val |= set([new_datas[fname]])
418
+                else:
419
+                    # Assert that we can handles all other iterable this 
420
+                    #way
421
+                    for ki, val in bref_val:
422
+                        if val == old_value:
423
+                            if newd:
424
+                                #Update
425
+                                bref_val[ki] = new_datas[fname]
426
+                            else:
427
+                                #Deletion
428
+                                del(bref_val[ki])
429
+                            break
430
+                    else:
431
+                        raise LodelFatalError("This error should never be \
432
+raised ! We just checked that oldv is in bref_val...")
433
+            else:
434
+                #Single ref handling
435
+                if bref_val != old_value:
436
+                    raise MongoDbConsistencyError("The value we wanted to \
437
+update do not match excpected value during aback reference  singleReference \
438
+update : expected value was '%s' but found '%s' in field '%s' of leo %s" % (
439
+                    old_value, bref_val, ref_fname, bref_leo))
440
+                
441
+                if newd:
442
+                    #update
443
+                    bref_val = new_datas[fname]
444
+                else:
445
+                    #delete
446
+                    if not hasattr(bref_dh, "default"): 
447
+                        raise MongoDbConsistencyError("Unable to delete a \
448
+value for a back reference update. The concerned field don't have a default \
449
+value : in %s field %s" % (bref_leo, ref_fname))
450
+                    bref_val = getattr(bref_dh, "default")
451
+        elif newd:
452
+            #It's an "insert"
453
+            new_value = new_datas[fname]
454
+            if issubclass(bref_dh, MultipleRef):
455
+                if isinstance(bref_val, set):
456
+                    bref_val |= set([new_value])
457
+                else:
458
+                    bref_val.append(new_value)
459
+            else:
460
+                bref_val = new_value
461
+        return bref_val
462
+
463
+        
464
+    
465
+    ##@brief Fetch back reference informations
466
+    #@param bref_cls LeObject child class : __back_reference[0]
467
+    #@param uidv mixed : UID value (the content of the reference field)
468
+    #@param bref_fname str : the name of the back_reference field
469
+    #@return tuple(bref_class, bref_LeObect_instance, bref_datahandler,
470
+    #bref_value)
471
+    #@throw MongoDbConsistencyError when LeObject instance not found given
472
+    #uidv
473
+    #@throw LodelFatalError if the back reference field is not a Reference
474
+    #subclass (major failure)
475
+    def __bref_get_check(self, bref_cls, uidv, bref_fname):
476
+        bref_leo = bref_cls.get_from_uid(old_datas[fname])
477
+        if len(bref_leo) == 0:
478
+            raise MongoDbConsistencyError("Unable to get the object we make \
479
+reference to : %s with uid = %s" % (bref_cls, repr(uidv)))
480
+        bref_dh = bref_leo.data_handler(bref_fname)
481
+        if not isinstance(bref_leo, Reference):
482
+            raise LodelFatalError("Found a back reference field that \
483
+is not a reference : '%s' field '%s'" % (bref_leo, bref_fname))
484
+        bref_val = bref_leo.data(bref_fname)
485
+        return (bref_leo.__class__, bref_leo, bref_dh, bref_val)
486
+            
487
+
488
+        
489
+    
490
+    ##@defgroup plugin_mongodb_bref_op BackReferences operations
491
+    #@brief Contains back_reference operations
492
+    #@ingroup plugin_mongodb_datasource
493
+    #
494
+    #Back reference operations are implemented by privates functions 
495
+    # - __bref_op_update()
496
+    # - __bref_op_delete()
497
+    # - __bref_op_add()
498
+    #
499
+    #This 3 methods returns the same thing, a dict containing the field
500
+    #name to update associated with the new value of this field.
501
+    #
502
+    #This 3 methods also expect that the following tests was done :
503
+    # - the datahandler pointed by the back reference is a child class of
504
+    #base_classes.Reference
505
+    # - the value of this field contains or is the given old value (not for
506
+    #the __bref_op_add() method)
507
+    #
508
+    #All this methods asserts that we allready checks that the given LeObject
509
+    #instance concerned field (the one stored as back reference) is a child
510
+    #class of base_classes.Reference
511
+    #
512
+    #@todo factorise __bref_op_update() and __bref_op_delete() and update this
513
+    #comment
514
+
241 515
     ##@brief Act on abstract LeObject child
242 516
     #
243 517
     #This method is designed to be called by insert, select and delete method

+ 17
- 0
plugins/mongodb_datasource/exceptions.py View File

@@ -0,0 +1,17 @@
1
+##@ingroup plugin_mongodb_datasource
2
+class MongoDbDataSourceError(Exception):
3
+    pass
4
+
5
+##@ingroup plugin_mongodb_datasource
6
+class MongoDbConsistencyError(MongoDbDataSourceError):
7
+    pass
8
+
9
+##@ingroup plugin_mongodb_datasource
10
+class MigrationHandlerChangeError(Exception):
11
+    pass
12
+
13
+
14
+##@ingroup plugin_mongodb_datasource
15
+class MigrationHandlerError(Exception):
16
+    pass
17
+

+ 1
- 7
plugins/mongodb_datasource/migration_handler.py View File

@@ -8,15 +8,9 @@ from lodel.leapi.datahandlers.base_classes import DataHandler
8 8
 from lodel.plugin import LodelHook
9 9
 from leapi_dyncode import *
10 10
 from .datasource import MongoDbDatasource
11
+from .exceptions import *
11 12
 from lodel import logger
12 13
 
13
-class MigrationHandlerChangeError(Exception):
14
-    pass
15
-
16
-
17
-class MigrationHandlerError(Exception):
18
-    pass
19
-
20 14
 class MigrationHandler(object):
21 15
 
22 16
     ## @brief Constructs a MongoDbMigrationHandler

Loading…
Cancel
Save