@@ -261,7 +261,7 @@ def compare(self, lhs_value, rhs_value):
261
261
raise RuntimeError ('unexpected size operator: {}' .format (self ._op ))
262
262
if self .is_inequality ():
263
263
if not isinstance (lhs_value , Number ):
264
- raise ValueError ( 'Number required for inequality' )
264
+ return False
265
265
py_op = self .PY_INEQ .get (self ._op , self ._op )
266
266
return eval ('{} {} {}' .format (lhs_value , py_op , rhs_value ))
267
267
if self .is_type ():
@@ -774,6 +774,65 @@ def update(self, *args):
774
774
self ._count = 0
775
775
776
776
777
+ class ConstraintSpec :
778
+ """Specification of a set of constraints for a collection.
779
+ """
780
+ FILTER_SECT = 'filter'
781
+ CONSTRAINT_SECT = 'constraints'
782
+
783
+ def __init__ (self , spec ):
784
+ """Create specification from a configuration.
785
+
786
+ :param spec: Configuration for a single collection
787
+ :type spec: dict
788
+ :raise: ValueError if specification is wrong
789
+ """
790
+ self ._sections = {}
791
+ for item in spec :
792
+ if isinstance (item , dict ):
793
+ self ._add_filtered_section (item )
794
+ else :
795
+ self ._add_simple_section (item )
796
+
797
+ def _add_filtered_section (self , item ):
798
+ """Add a section that has a filter and set of constraints
799
+
800
+ :raise: ValueError if filter or constraints is missing
801
+ """
802
+ # extract filter and constraints
803
+ cond_raw , constraints = None , None
804
+ try :
805
+ cond_raw = item [self .FILTER_SECT ]
806
+ constraints = item [self .CONSTRAINT_SECT ]
807
+ except KeyError :
808
+ if cond_raw is None :
809
+ raise ValueError ("configuration is missing '{}'" .format (self .FILTER_SECT ))
810
+ else :
811
+ raise ValueError ("configuration is missing '{}'" .format (self .CONSTRAINT_SECT ))
812
+
813
+ # make condition(s) into a tuple
814
+ if isinstance (cond_raw , basestring ):
815
+ cond = (cond_raw ,)
816
+ elif cond_raw is None :
817
+ cond = None
818
+ else :
819
+ cond = tuple (cond_raw ) # tuples can be used as keys
820
+ # add
821
+ if cond in self ._sections :
822
+ self ._sections [cond ].extend (constraints )
823
+ else :
824
+ self ._sections [cond ] = constraints
825
+
826
+ def _add_simple_section (self , item ):
827
+ self ._sections [None ] = [item ]
828
+
829
+ def __iter__ (self ):
830
+ """When invoked as an iterator, return the key, value
831
+ pairs of the filter and constraints.
832
+ """
833
+ return self ._sections .iteritems ()
834
+
835
+
777
836
class Validator (DoesLogging ):
778
837
"""Validate a collection.
779
838
"""
@@ -792,7 +851,7 @@ class Validator(DoesLogging):
792
851
)
793
852
\s*''' , re .VERBOSE )
794
853
795
- def __init__ (self , max_violations = 50 , max_dberrors = 10 , aliases = None ):
854
+ def __init__ (self , max_violations = 50 , max_dberrors = 10 , aliases = None , add_exists = False ):
796
855
DoesLogging .__init__ (self , name = 'mg.validator' )
797
856
self .set_progress (0 )
798
857
self ._aliases = aliases if aliases else {}
@@ -803,6 +862,7 @@ def __init__(self, max_violations=50, max_dberrors=10, aliases=None):
803
862
self ._find_kw = {}
804
863
self ._max_dberr = max_dberrors
805
864
self ._base_report_fields = {'_id' : 1 , 'task_id' : 1 }
865
+ self ._add_exists = add_exists
806
866
807
867
def set_aliases (self , a ):
808
868
"""Set aliases.
@@ -824,15 +884,21 @@ def num_violations(self):
824
884
return 0
825
885
return self ._progress ._count
826
886
827
- def validate (self , coll , constraint_sections , subject = 'collection' ):
887
+ def validate (self , coll , constraint_spec , subject = 'collection' ):
828
888
"""Validation of a collection.
829
889
This is a generator that yields ConstraintViolationGroups.
830
890
891
+ :param coll: Mongo collection
892
+ :type coll: pymongo.Collection
893
+ :param constraint_spec: Constraint specification
894
+ :type constraint_spec: ConstraintSpec
895
+ :param subject: Name of the thing being validated
896
+ :type subject: str
831
897
:return: Sets of constraint violation, one for each constraint_section
832
898
:rtype: ConstraintViolationGroup
833
899
"""
834
900
self ._progress .set_subject (subject )
835
- self ._build (constraint_sections )
901
+ self ._build (constraint_spec )
836
902
for cond , body in self ._sections :
837
903
cvg = self ._validate_section (subject , coll , cond , body )
838
904
if cvg is not None :
@@ -919,16 +985,19 @@ def _get_violations(self, query, record):
919
985
reasons .append (ConstraintViolation (clause .constraint , fval , expected ))
920
986
return reasons
921
987
922
- def _build (self , constraint_sections ):
988
+ def _build (self , constraint_spec ):
923
989
"""Generate queries to execute.
924
990
925
991
Sets instance variables so that Mongo query strings, etc. can now
926
992
be extracted from the object.
993
+
994
+ :param constraint_spec: Constraint specification
995
+ :type constraint_spec: ConstraintSpec
927
996
"""
928
997
self ._sections = []
929
998
self ._report_fields = self ._base_report_fields
930
999
# loopover each condition on the records
931
- for cond_expr_list , expr_list in constraint_sections . iteritems () :
1000
+ for cond_expr_list , expr_list in constraint_spec :
932
1001
#print("@@ CONDS = {}".format(cond_expr_list))
933
1002
#print("@@ MAIN = {}".format(expr_list))
934
1003
groups = self ._process_constraint_expressions (expr_list )
@@ -938,8 +1007,9 @@ def _build(self, constraint_sections):
938
1007
for c in cg :
939
1008
projection .add (c .field , c .op , c .value )
940
1009
query .add_clause (MongoClause (c ))
941
- for c in cg .existence_constraints :
942
- query .add_clause (MongoClause (c , exists_main = True ))
1010
+ if self ._add_exists :
1011
+ for c in cg .existence_constraints :
1012
+ query .add_clause (MongoClause (c , exists_main = True ))
943
1013
self ._report_fields .update (projection .to_mongo ())
944
1014
cond_query = MongoQuery ()
945
1015
if cond_expr_list is not None :
0 commit comments