Browse Source

Moved exceptions + LeUpdateQuery.__query implementation

- The LeUpdateQuery implementation is broken or at least have huge performance issue
- Moved LeObject exceptions and LeQuery exceptions in lodel.leapi.exceptions
Yann Weber 8 years ago
parent
commit
9da19f3665
5 changed files with 83 additions and 86 deletions
  1. 1
    1
      Doxyfile
  2. 14
    34
      lodel/leapi/leobject.py
  3. 64
    47
      lodel/leapi/query.py
  4. 1
    1
      plugins/mongodb_datasource/datasource.py
  5. 3
    3
      tests/leapi/query/test_get.py

+ 1
- 1
Doxyfile View File

@@ -763,7 +763,7 @@ WARN_LOGFILE           =
763 763
 # spaces.
764 764
 # Note: If this tag is empty the current directory is searched.
765 765
 
766
-INPUT                  = .
766
+INPUT                  =
767 767
 
768 768
 # This tag can be used to specify the character encoding of the source files
769 769
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

+ 14
- 34
lodel/leapi/leobject.py View File

@@ -7,45 +7,13 @@ from lodel import logger
7 7
 from lodel.settings import Settings
8 8
 from lodel.settings.utils import SettingsError
9 9
 from .query import LeInsertQuery, LeUpdateQuery, LeDeleteQuery, LeGetQuery
10
+from .exceptions import *
10 11
 from lodel.plugin.hooks import LodelHook
11 12
 
12 13
 ##@brief Stores the name of the field present in each LeObject that indicates
13 14
 #the name of LeObject subclass represented by this object
14 15
 CLASS_ID_FIELDNAME = "classname"
15 16
 
16
-class LeApiErrors(Exception):
17
-    ##@brief Instanciate a new exceptions handling multiple exceptions
18
-    # @param msg str : Exception message
19
-    # @param exceptions dict : A list of data check Exception with concerned field (or stuff) as key
20
-    def __init__(self, msg = "Unknow error", exceptions = None):
21
-        self._msg = msg
22
-        self._exceptions = dict() if exceptions is None else exceptions
23
-
24
-    def __repr__(self):
25
-        return self.__str__()
26
-
27
-    def __str__(self):
28
-        msg = self._msg
29
-        for_iter = self._exceptions.items() if isinstance(self._exceptions, dict) else enumerate(self.__exceptions)
30
-        for obj, expt in for_iter:
31
-            msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
32
-                    expt_obj = obj,
33
-                    expt_name=expt.__class__.__name__,
34
-                    expt_msg=str(expt)
35
-            )
36
-        return msg
37
-
38
-
39
-##@brief When an error concern a query
40
-class LeApiQueryError(LeApiErrors):
41
-    pass
42
-
43
-
44
-##@brief When an error concerns a datas
45
-class LeApiDataCheckError(LeApiErrors):
46
-    pass
47
-
48
-
49 17
 ##@brief Wrapper class for LeObject getter & setter
50 18
 #
51 19
 # This class intend to provide easy & friendly access to LeObject fields values 
@@ -276,6 +244,12 @@ raised when trying to import Datasource"
276 244
             ds_conf[k] = getattr(ds_conf_old, k)
277 245
 
278 246
         return datasource_class(**ds_conf)
247
+    
248
+    ##@brief Return the uid of the current LeObject instance
249
+    #@return the uid value
250
+    #@warning Broke multiple uid capabilities
251
+    def uid(self):
252
+        return self.data(self._uid[0])
279 253
 
280 254
     ##@brief Read only access to all datas
281 255
     # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
@@ -290,6 +264,12 @@ raised when trying to import Datasource"
290 264
             raise RuntimeError("The field %s is not initialized yet (and have no value)" % field_name)
291 265
         return self.__datas[field_name]
292 266
     
267
+    ##@brief Read only access to all datas
268
+    #@return a dict representing datas of current instance
269
+    def datas(self):
270
+        return [self.data(fname) for fname in self.fieldnames(True)]
271
+        
272
+    
293 273
     ##@brief Datas setter
294 274
     # @note for fancy data accessor use @ref LeObject.g attribute @ref LeObjectValues instance
295 275
     # @param fname str : field name
@@ -527,7 +507,7 @@ raised when trying to import Datasource"
527 507
             
528 508
         try:
529 509
             result = query.execute(datas)
530
-        except Exception as err;
510
+        except Exception as err:
531 511
             raise err
532 512
 
533 513
         return result

+ 64
- 47
lodel/leapi/query.py View File

@@ -2,36 +2,12 @@
2 2
 
3 3
 import re
4 4
 import copy
5
+import inspect
5 6
 
6
-from .leobject import LeObject, LeApiErrors, LeApiDataCheckError
7
+from .exceptions import *
7 8
 from lodel.plugin.hooks import LodelHook
8 9
 from lodel import logger
9 10
 
10
-class LeQueryError(Exception):
11
-    ##@brief Instanciate a new exceptions handling multiple exceptions
12
-    #@param msg str : Exception message
13
-    #@param exceptions dict : A list of data check Exception with concerned
14
-    # field (or stuff) as key
15
-    def __init__(self, msg = "Unknow error", exceptions = None):
16
-        self._msg = msg
17
-        self._exceptions = dict() if exceptions is None else exceptions
18
-
19
-    def __repr__(self):
20
-        return self.__str__()
21
-
22
-    def __str__(self):
23
-        msg = self._msg
24
-        if isinstance(self._exceptions, dict):
25
-            for_iter = self._exceptions.items()
26
-        else:
27
-            for_iter = enumerate(self.__exceptions)
28
-        for obj, expt in for_iter:
29
-            msg += "\n\t{expt_obj} : ({expt_name}) {expt_msg}; ".format(
30
-                    expt_obj = obj,
31
-                    expt_name=expt.__class__.__name__,
32
-                    expt_msg=str(expt)
33
-            )
34
-        return msg
35 11
 
36 12
 ##@todo check datas when running query
37 13
 class LeQuery(object):
@@ -44,6 +20,7 @@ class LeQuery(object):
44 20
     ##@brief Abstract constructor
45 21
     # @param target_class LeObject : class of object the query is about
46 22
     def __init__(self, target_class):
23
+        from .leobject import LeObject
47 24
         if self._hook_prefix is None:
48 25
             raise NotImplementedError("Abstract class")
49 26
         if not issubclass(target_class, LeObject):
@@ -504,41 +481,81 @@ class LeInsertQuery(LeQuery):
504 481
 
505 482
 
506 483
 ##@brief A query to update datas for a given object
484
+#
485
+#@todo Change behavior, Huge optimization problem when updating using filters
486
+#and not instance. We have to run a GET and then 1 update by fecthed object...
507 487
 class LeUpdateQuery(LeFilteredQuery):
508 488
     
509 489
     _hook_prefix = 'leapi_update_'
510 490
     _data_check_args = { 'complete': True, 'allow_internal': False }
511
-
512
-    def __init__(self, target_class, query_filter):
513
-        super().__init__(target_class, query_filter)
514 491
     
515
-    ##@brief Called by __query to do severals checks before running the update
516
-    #@warning Optimization issue : each time this method is called we do 
517
-    #a LeGetQuery to fetch ALL datas and construct instances for being able to
518
-    #construct datas and check consistency
519
-    #@todo implementation (waiting for LeApi to be plugged to LeQuery)
520
-    def __fetch_construct_check_update(self, filters, rel_filters, datas):
521
-        """
522
-        instances = self._target_class.get(filters, rel_filters)
523
-        for instance in instances:
524
-            instance.check_datas_value(instance.
525
-        """
526
-        pass
527
-        
492
+    ##@brief Instanciate an update query
493
+    #
494
+    #If a class and not an instance is given, no query_filters are expected
495
+    #and the update will be fast and simple. Else we have to run a get query
496
+    #before updating (to fetch datas, update them and then, construct them
497
+    #and check their consistency)
498
+    #@param target LeObject clas or instance
499
+    #@param query_filters list|None
500
+    #@todo change strategy with instance update. We have to accept datas for
501
+    #the execute method
502
+    def __init__(self, target, query_filters = None):
503
+        ##@brief This attr is set only if the target argument is an 
504
+        #instance of a LeObject subclass
505
+        self.__leobject_datas = None
506
+        target_class = target
507
+        if not inspect.isclass(target):
508
+            if query_filters is not None:
509
+                msg = "No query_filters accepted when an instance is given as \
510
+target to LeUpdateQuery constructor"
511
+                raise AttributeError(msg)
512
+            target_class = target.__class__
513
+            if self.initialized():
514
+                self.__leobject_instance_datas = target.datas()
515
+            else:
516
+                filters = [(target._uid[0], '=', target.uid())]
517
+    
518
+        super().__init__(target_class, query_filters)
528 519
 
529 520
     ##@brief Implements an update query
530 521
     #@param filters list : see @ref LeFilteredQuery
531 522
     #@param rel_filters list : see @ref LeFilteredQuery
532 523
     #@param datas dict : datas to update
533 524
     #@returns the number of updated items
525
+    #@todo change stategy for instance update. Datas should be allowed 
526
+    #for execute method (and query)
534 527
     def __query(self, filters, rel_filters, datas):
535
-        self.__fetch_construct_check_update(filters, rel_filters, datas)
536
-        nb_updated = self._rw_datasource.update(
537
-            self._target_class, filters, rel_filters, datas)
528
+        uid_name = self._target_class._uid[0]
529
+        if self.__leobject_instance is not None:
530
+            #Instance update
531
+            #Building query_filter
532
+            filters = [(
533
+                uid_name, '=', self.__leobject_instance_datas[uid_name])]
534
+            self._rw_datasource.update(
535
+                self._target_class, filters, [],
536
+                self.__leobject_instance_datas)
537
+        else:
538
+            #Update by filters, we have to fetch datas before updating
539
+            res = self._ro_datasource.select(
540
+                self._target_class, self._target_class.fieldnames(True),
541
+                filters, rel_filters)
542
+            #Checking and constructing datas
543
+            upd_datas = dict()
544
+            for res_data in res:
545
+                res_data.update(datas)
546
+                res_datas = self._target_class.check_datas_value(
547
+                    res_data, True, True)
548
+                filters = [(uid_name, '=', res_data[uid_name])]
549
+                self._rw_datasource.update(
550
+                    self._target_class, filters, [],
551
+                    res_datas)
538 552
         return nb_updated
539 553
     
540 554
     ## @brief Execute the update query
541
-    def execute(self, datas):
555
+    def execute(self, datas = None):
556
+        if self.__leobject_instance is not None and datas is not None:
557
+            raise AttributeError("No datas expected when running an update \
558
+query on an instance")
542 559
         return super().execute(datas = datas)
543 560
 
544 561
 ##@brief A query to delete an object
@@ -639,7 +656,7 @@ class LeGetQuery(LeFilteredQuery):
639 656
                 err_l[fieldname] =  expt
640 657
         if len(err_l) > 0:
641 658
             msg = "Error while setting field_list in a get query"
642
-            raise LeQueryError(msg = msg, exceptions = err_l)
659
+            raise LeApiQueryErrors(msg = msg, exceptions = err_l)
643 660
         self.__field_list = list(set(field_list))
644 661
     
645 662
     ##@brief Execute the get query

+ 1
- 1
plugins/mongodb_datasource/datasource.py View File

@@ -77,7 +77,7 @@ class MongoDbDatasource(object):
77 77
     #@param instanciate bool : If true, the records are returned as instances, else they are returned as dict
78 78
     #@return list
79 79
     #@todo Implement the relations
80
-    def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0, instanciate=True):
80
+    def select(self, target, field_list, filters, rel_filters=None, order=None, group=None, limit=None, offset=0):
81 81
         collection_name = object_collection_name(target)
82 82
         collection = self.database[collection_name]
83 83
         query_filters = self.__process_filters(

+ 3
- 3
tests/leapi/query/test_get.py View File

@@ -5,8 +5,8 @@ import itertools
5 5
 import tests.loader_utils
6 6
 from tests.leapi.query.utils import dyncode_module as dyncode
7 7
 
8
-from lodel.leapi.leobject import LeApiDataCheckError
9
-from lodel.leapi.query import LeDeleteQuery, LeUpdateQuery, LeGetQuery, LeQueryError
8
+from lodel.leapi.query import LeDeleteQuery, LeUpdateQuery, LeGetQuery
9
+from lodel.leapi.exceptions import LeApiQueryError
10 10
 
11 11
 class LeGetQueryTestCase(unittest.TestCase):
12 12
     
@@ -89,6 +89,6 @@ class LeGetQueryTestCase(unittest.TestCase):
89 89
                             ('lodel_id', None,) )
90 90
 
91 91
         for bad_field_list in bad_field_lists:
92
-            with self.assertRaises(LeQueryError):
92
+            with self.assertRaises(LeApiQueryError):
93 93
                 LeGetQuery(dyncode.Object, [], field_list = bad_field_list)
94 94
 

Loading…
Cancel
Save