devtools/fill.py
changeset 4337 27ea69e2cfea
parent 4252 6c4f109c2b03
child 4467 0e73d299730a
equal deleted inserted replaced
4336:45e5b8bd2f71 4337:27ea69e2cfea
     8 """
     8 """
     9 __docformat__ = "restructuredtext en"
     9 __docformat__ = "restructuredtext en"
    10 
    10 
    11 from random import randint, choice
    11 from random import randint, choice
    12 from copy import deepcopy
    12 from copy import deepcopy
    13 from datetime import datetime, date, time#timedelta
    13 from datetime import datetime, date, time, timedelta
    14 from decimal import Decimal
    14 from decimal import Decimal
    15 
    15 
       
    16 from logilab.common import attrdict
    16 from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
    17 from yams.constraints import (SizeConstraint, StaticVocabularyConstraint,
    17                               IntervalBoundConstraint)
    18                               IntervalBoundConstraint, BoundConstraint,
       
    19                               Attribute, actual_value)
    18 from rql.utils import decompose_b26 as base_decompose_b26
    20 from rql.utils import decompose_b26 as base_decompose_b26
    19 
    21 
    20 from cubicweb import Binary
    22 from cubicweb import Binary
    21 from cubicweb.schema import RQLConstraint
    23 from cubicweb.schema import RQLConstraint
       
    24 
       
    25 def custom_range(start, stop, step):
       
    26     while start < stop:
       
    27         yield start
       
    28         start += step
    22 
    29 
    23 def decompose_b26(index, ascii=False):
    30 def decompose_b26(index, ascii=False):
    24     """return a letter (base-26) decomposition of index"""
    31     """return a letter (base-26) decomposition of index"""
    25     if ascii:
    32     if ascii:
    26         return base_decompose_b26(index)
    33         return base_decompose_b26(index)
    27     return base_decompose_b26(index, u'éabcdefghijklmnopqrstuvwxyz')
    34     return base_decompose_b26(index, u'éabcdefghijklmnopqrstuvwxyz')
    28 
       
    29 def get_choices(eschema, attrname):
       
    30     """returns possible choices for 'attrname'
       
    31     if attrname doesn't have ChoiceConstraint, return None
       
    32     """
       
    33     for cst in eschema.rdef(attrname).constraints:
       
    34         if isinstance(cst, StaticVocabularyConstraint):
       
    35             return cst.vocabulary()
       
    36     return None
       
    37 
       
    38 
    35 
    39 def get_max_length(eschema, attrname):
    36 def get_max_length(eschema, attrname):
    40     """returns the maximum length allowed for 'attrname'"""
    37     """returns the maximum length allowed for 'attrname'"""
    41     for cst in eschema.rdef(attrname).constraints:
    38     for cst in eschema.rdef(attrname).constraints:
    42         if isinstance(cst, SizeConstraint) and cst.max:
    39         if isinstance(cst, SizeConstraint) and cst.max:
    43             return cst.max
    40             return cst.max
    44     return 300
    41     return 300
    45     #raise AttributeError('No Size constraint on attribute "%s"' % attrname)
    42     #raise AttributeError('No Size constraint on attribute "%s"' % attrname)
    46 
       
    47 def get_bounds(eschema, attrname):
       
    48     for cst in eschema.rdef(attrname).constraints:
       
    49         if isinstance(cst, IntervalBoundConstraint):
       
    50             return cst.minvalue, cst.maxvalue
       
    51     return None, None
       
    52 
       
    53 
    43 
    54 _GENERATED_VALUES = {}
    44 _GENERATED_VALUES = {}
    55 
    45 
    56 class _ValueGenerator(object):
    46 class _ValueGenerator(object):
    57     """generates integers / dates / strings / etc. to fill a DB table"""
    47     """generates integers / dates / strings / etc. to fill a DB table"""
    62         looks like :
    52         looks like :
    63             def values_for(etype, attrname):
    53             def values_for(etype, attrname):
    64                 # some stuff ...
    54                 # some stuff ...
    65                 return alist_of_acceptable_values # or None
    55                 return alist_of_acceptable_values # or None
    66         """
    56         """
    67         self.e_schema = eschema
       
    68         self.choice_func = choice_func
    57         self.choice_func = choice_func
    69 
    58         self.eschema = eschema
    70     def _generate_value(self, attrname, index, **kwargs):
    59 
    71         if not self.e_schema.has_unique_values(attrname):
    60     def generate_attribute_value(self, entity, attrname, index=1, **kwargs):
    72             return self.__generate_value(attrname, index, **kwargs)
    61         if attrname in entity:
    73         value = self.__generate_value(attrname, index, **kwargs)
    62             return entity[attrname]
    74         while value in _GENERATED_VALUES.get((self.e_schema.type, attrname), ()):
    63         eschema = self.eschema
    75             index += 1
    64         if not eschema.has_unique_values(attrname):
    76             value = self.__generate_value(attrname, index, **kwargs)
    65             value = self.__generate_value(entity, attrname, index, **kwargs)
    77         _GENERATED_VALUES.setdefault((self.e_schema.type, attrname), set()).add(value)
    66         else:
       
    67             value = self.__generate_value(entity, attrname, index, **kwargs)
       
    68             while value in _GENERATED_VALUES.get((eschema, attrname), ()):
       
    69                 index += 1
       
    70                 value = self.__generate_value(entity, attrname, index, **kwargs)
       
    71             _GENERATED_VALUES.setdefault((eschema, attrname), set()).add(value)
       
    72         entity[attrname] = value
    78         return value
    73         return value
    79 
    74 
    80     def __generate_value(self, attrname, index, **kwargs):
    75     def __generate_value(self, entity, attrname, index, **kwargs):
    81         """generates a consistent value for 'attrname'"""
    76         """generates a consistent value for 'attrname'"""
    82         attrtype = str(self.e_schema.destination(attrname)).lower()
    77         eschema = self.eschema
       
    78         attrtype = str(eschema.destination(attrname)).lower()
    83         # Before calling generate_%s functions, try to find values domain
    79         # Before calling generate_%s functions, try to find values domain
    84         etype = self.e_schema.type
       
    85         if self.choice_func is not None:
    80         if self.choice_func is not None:
    86             values_domain = self.choice_func(etype, attrname)
    81             values_domain = self.choice_func(eschema, attrname)
    87             if values_domain is not None:
    82             if values_domain is not None:
    88                 return choice(values_domain)
    83                 return choice(values_domain)
    89         gen_func = getattr(self, 'generate_%s_%s' % (self.e_schema.type, attrname), None)
    84         gen_func = getattr(self, 'generate_%s_%s' % (eschema, attrname),
    90         if gen_func is None:
    85                            getattr(self, 'generate_Any_%s' % attrname, None))
    91             gen_func = getattr(self, 'generate_Any_%s' % attrname, None)
       
    92         if gen_func is not None:
    86         if gen_func is not None:
    93             return gen_func(index, **kwargs)
    87             return gen_func(entity, index, **kwargs)
    94         # If no specific values domain, then generate a dummy value
    88         # If no specific values domain, then generate a dummy value
    95         gen_func = getattr(self, 'generate_%s' % (attrtype))
    89         gen_func = getattr(self, 'generate_%s' % (attrtype))
    96         return gen_func(attrname, index, **kwargs)
    90         return gen_func(entity, attrname, index, **kwargs)
    97 
    91 
    98     def generate_choice(self, attrname, index):
    92     def generate_string(self, entity, attrname, index, format=None):
    99         """generates a consistent value for 'attrname' if it's a choice"""
       
   100         choices = get_choices(self.e_schema, attrname)
       
   101         if choices is None:
       
   102             return None
       
   103         return unicode(choice(choices)) # FIXME
       
   104 
       
   105     def generate_string(self, attrname, index, format=None):
       
   106         """generates a consistent value for 'attrname' if it's a string"""
    93         """generates a consistent value for 'attrname' if it's a string"""
   107         # First try to get choices
    94         # First try to get choices
   108         choosed = self.generate_choice(attrname, index)
    95         choosed = self.get_choice(entity, attrname)
   109         if choosed is not None:
    96         if choosed is not None:
   110             return choosed
    97             return choosed
   111         # All other case, generate a default string
    98         # All other case, generate a default string
   112         attrlength = get_max_length(self.e_schema, attrname)
    99         attrlength = get_max_length(self.eschema, attrname)
   113         num_len = numlen(index)
   100         num_len = numlen(index)
   114         if num_len >= attrlength:
   101         if num_len >= attrlength:
   115             ascii = self.e_schema.rdef(attrname).internationalizable
   102             ascii = self.eschema.rdef(attrname).internationalizable
   116             return ('&'+decompose_b26(index, ascii))[:attrlength]
   103             return ('&'+decompose_b26(index, ascii))[:attrlength]
   117         # always use plain text when no format is specified
   104         # always use plain text when no format is specified
   118         attrprefix = attrname[:max(attrlength-num_len-1, 0)]
   105         attrprefix = attrname[:max(attrlength-num_len-1, 0)]
   119         if format == 'text/html':
   106         if format == 'text/html':
   120             value = u'<span>é%s<b>%d</b></span>' % (attrprefix, index)
   107             value = u'<span>é%s<b>%d</b></span>' % (attrprefix, index)
   129 """ % (attrprefix, index)
   116 """ % (attrprefix, index)
   130         else:
   117         else:
   131             value = u'é&%s%d' % (attrprefix, index)
   118             value = u'é&%s%d' % (attrprefix, index)
   132         return value[:attrlength]
   119         return value[:attrlength]
   133 
   120 
   134     def generate_password(self, attrname, index):
   121     def generate_password(self, entity, attrname, index):
   135         """generates a consistent value for 'attrname' if it's a password"""
   122         """generates a consistent value for 'attrname' if it's a password"""
   136         return u'toto'
   123         return u'toto'
   137 
   124 
   138     def generate_integer(self, attrname, index):
   125     def generate_integer(self, entity, attrname, index):
   139         """generates a consistent value for 'attrname' if it's an integer"""
   126         """generates a consistent value for 'attrname' if it's an integer"""
   140         choosed = self.generate_choice(attrname, index)
   127         return self._constrained_generate(entity, attrname, 0, 1, index)
       
   128     generate_int = generate_integer
       
   129 
       
   130     def generate_float(self, entity, attrname, index):
       
   131         """generates a consistent value for 'attrname' if it's a float"""
       
   132         return self._constrained_generate(entity, attrname, 0.0, 1.0, index)
       
   133 
       
   134     def generate_decimal(self, entity, attrname, index):
       
   135         """generates a consistent value for 'attrname' if it's a float"""
       
   136         return Decimal(str(self.generate_float(entity, attrname, index)))
       
   137 
       
   138     def generate_datetime(self, entity, attrname, index):
       
   139         """generates a random date (format is 'yyyy-mm-dd HH:MM')"""
       
   140         base = datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
       
   141         return self._constrained_generate(entity, attrname, base, timedelta(hours=1), index)
       
   142 
       
   143     def generate_date(self, entity, attrname, index):
       
   144         """generates a random date (format is 'yyyy-mm-dd')"""
       
   145         base = date(randint(2000, 2004), randint(1, 12), randint(1, 28))
       
   146         return self._constrained_generate(entity, attrname, base, timedelta(days=1), index)
       
   147 
       
   148     def generate_time(self, entity, attrname, index):
       
   149         """generates a random time (format is ' HH:MM')"""
       
   150         return time(11, index%60) #'11:%02d' % (index % 60)
       
   151 
       
   152     def generate_bytes(self, entity, attrname, index, format=None):
       
   153         fakefile = Binary("%s%s" % (attrname, index))
       
   154         fakefile.filename = u"file_%s" % attrname
       
   155         return fakefile
       
   156 
       
   157     def generate_boolean(self, entity, attrname, index):
       
   158         """generates a consistent value for 'attrname' if it's a boolean"""
       
   159         return index % 2 == 0
       
   160 
       
   161     def _constrained_generate(self, entity, attrname, base, step, index):
       
   162         choosed = self.get_choice(entity, attrname)
   141         if choosed is not None:
   163         if choosed is not None:
   142             return choosed
   164             return choosed
   143         minvalue, maxvalue = get_bounds(self.e_schema, attrname)
   165         # ensure index > 0
   144         if maxvalue is not None and maxvalue <= 0 and minvalue is None:
   166         index += 1
   145             minvalue = maxvalue - index # i.e. randint(-index, 0)
   167         minvalue, maxvalue = self.get_bounds(entity, attrname)
   146         else:
   168         if maxvalue is None:
   147             maxvalue = maxvalue or index
   169             if minvalue is not None:
   148         return randint(minvalue or 0, maxvalue)
   170                 base = max(minvalue, base)
   149 
   171             maxvalue = base + index * step
   150     generate_int = generate_integer
   172         if minvalue is None:
   151 
   173             minvalue = maxvalue - (index * step) # i.e. randint(-index, 0)
   152     def generate_float(self, attrname, index):
   174         return choice(list(custom_range(minvalue, maxvalue, step)))
   153         """generates a consistent value for 'attrname' if it's a float"""
   175 
   154         return float(randint(-index, index))
   176     def _actual_boundary(self, entity, boundary):
   155 
   177         if isinstance(boundary, Attribute):
   156     def generate_decimal(self, attrname, index):
   178             # ensure we've a value for this attribute
   157         """generates a consistent value for 'attrname' if it's a float"""
   179             self.generate_attribute_value(entity, boundary.attr)
   158         return Decimal(str(self.generate_float(attrname, index)))
   180             boundary = actual_value(boundary, entity)
   159 
   181         return boundary
   160     def generate_date(self, attrname, index):
   182 
   161         """generates a random date (format is 'yyyy-mm-dd')"""
   183     def get_bounds(self, entity, attrname):
   162         return date(randint(2000, 2004), randint(1, 12), randint(1, 28))
   184         minvalue = maxvalue = None
   163 
   185         for cst in self.eschema.rdef(attrname).constraints:
   164     def generate_time(self, attrname, index):
   186             if isinstance(cst, IntervalBoundConstraint):
   165         """generates a random time (format is ' HH:MM')"""
   187                 minvalue = self._actual_boundary(entity, cst.minvalue)
   166         return time(11, index%60) #'11:%02d' % (index % 60)
   188                 maxvalue = self._actual_boundary(entity, cst.maxvalue)
   167 
   189             elif isinstance(cst, BoundConstraint):
   168     def generate_datetime(self, attrname, index):
   190                 if cst.operator[0] == '<':
   169         """generates a random date (format is 'yyyy-mm-dd HH:MM')"""
   191                     maxvalue = self._actual_boundary(entity, cst.boundary)
   170         return datetime(randint(2000, 2004), randint(1, 12), randint(1, 28), 11, index%60)
   192                 else:
   171 
   193                     minvalue = self._actual_boundary(entity, cst.boundary)
   172 
   194         return minvalue, maxvalue
   173     def generate_bytes(self, attrname, index, format=None):
   195 
   174         # modpython way
   196     def get_choice(self, entity, attrname):
   175         fakefile = Binary("%s%s" % (attrname, index))
   197         """generates a consistent value for 'attrname' if it has some static
   176         fakefile.filename = u"file_%s" % attrname
   198         vocabulary set, else return None.
   177         fakefile.value = fakefile.getvalue()
   199         """
   178         return fakefile
   200         for cst in self.eschema.rdef(attrname).constraints:
   179 
   201             if isinstance(cst, StaticVocabularyConstraint):
   180     def generate_boolean(self, attrname, index):
   202                 return unicode(choice(cst.vocabulary()))
   181         """generates a consistent value for 'attrname' if it's a boolean"""
   203         return None
   182         return index % 2 == 0
   204 
   183 
   205     # XXX nothing to do here
   184     def generate_Any_data_format(self, index, **kwargs):
   206     def generate_Any_data_format(self, entity, index, **kwargs):
   185         # data_format attribute of Image/File has no vocabulary constraint, we
   207         # data_format attribute of Image/File has no vocabulary constraint, we
   186         # need this method else stupid values will be set which make mtconverter
   208         # need this method else stupid values will be set which make mtconverter
   187         # raise exception
   209         # raise exception
   188         return u'application/octet-stream'
   210         return u'application/octet-stream'
   189 
   211 
   190     def generate_Any_content_format(self, index, **kwargs):
   212     def generate_Any_content_format(self, entity, index, **kwargs):
   191         # content_format attribute of EmailPart has no vocabulary constraint, we
   213         # content_format attribute of EmailPart has no vocabulary constraint, we
   192         # need this method else stupid values will be set which make mtconverter
   214         # need this method else stupid values will be set which make mtconverter
   193         # raise exception
   215         # raise exception
   194         return u'text/plain'
   216         return u'text/plain'
   195 
   217 
   196     def generate_Image_data_format(self, index, **kwargs):
   218     def generate_Image_data_format(self, entity, index, **kwargs):
   197         # data_format attribute of Image/File has no vocabulary constraint, we
   219         # data_format attribute of Image/File has no vocabulary constraint, we
   198         # need this method else stupid values will be set which make mtconverter
   220         # need this method else stupid values will be set which make mtconverter
   199         # raise exception
   221         # raise exception
   200         return u'image/png'
   222         return u'image/png'
   201 
   223 
   235     :type choice_func: function
   257     :type choice_func: function
   236     :param choice_func: a function that takes an entity type, an attrname and
   258     :param choice_func: a function that takes an entity type, an attrname and
   237                         returns acceptable values for this attribute
   259                         returns acceptable values for this attribute
   238     """
   260     """
   239     # XXX HACK, remove or fix asap
   261     # XXX HACK, remove or fix asap
   240     if etype in (('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
   262     if etype in set(('String', 'Int', 'Float', 'Boolean', 'Date', 'CWGroup', 'CWUser')):
   241         return []
   263         return []
   242     queries = []
   264     queries = []
   243     for index in xrange(entity_num):
   265     for index in xrange(entity_num):
   244         restrictions = []
   266         restrictions = []
   245         args = {}
   267         args = {}
   262     by default, generate an entity to be inserted in the repository
   284     by default, generate an entity to be inserted in the repository
   263     elif form, generate an form dictionnary to be given to a web controller
   285     elif form, generate an form dictionnary to be given to a web controller
   264     """
   286     """
   265     eschema = schema.eschema(etype)
   287     eschema = schema.eschema(etype)
   266     valgen = ValueGenerator(eschema, choice_func)
   288     valgen = ValueGenerator(eschema, choice_func)
   267     entity = {}
   289     entity = attrdict()
   268     # preprocessing to deal with _format fields
   290     # preprocessing to deal with _format fields
   269     attributes = []
   291     attributes = []
   270     relatedfields = {}
   292     relatedfields = {}
   271     for rschema, attrschema in eschema.attribute_definitions():
   293     for rschema, attrschema in eschema.attribute_definitions():
   272         attrname = rschema.type
   294         attrname = rschema.type
   278         else:
   300         else:
   279             attributes.append((attrname, attrschema))
   301             attributes.append((attrname, attrschema))
   280     for attrname, attrschema in attributes:
   302     for attrname, attrschema in attributes:
   281         if attrname in relatedfields:
   303         if attrname in relatedfields:
   282             # first generate a format and record it
   304             # first generate a format and record it
   283             format = valgen._generate_value(attrname + '_format', index)
   305             format = valgen.generate_attribute_value(entity, attrname + '_format', index)
   284             entity[attrname + '_format'] = format
       
   285             # then a value coherent with this format
   306             # then a value coherent with this format
   286             value = valgen._generate_value(attrname, index, format=format)
   307             value = valgen.generate_attribute_value(entity, attrname, index, format=format)
   287         else:
   308         else:
   288             value = valgen._generate_value(attrname, index)
   309             value = valgen.generate_attribute_value(entity, attrname, index)
   289         if form: # need to encode values
   310         if form: # need to encode values
   290             if attrschema.type == 'Bytes':
   311             if attrschema.type == 'Bytes':
   291                 # twisted way
   312                 # twisted way
   292                 fakefile = value
   313                 fakefile = value
   293                 filename = value.filename
   314                 filename = value.filename
   301             elif attrschema.type == 'Float':
   322             elif attrschema.type == 'Float':
   302                 fmt = vreg.property_value('ui.float-format')
   323                 fmt = vreg.property_value('ui.float-format')
   303                 value = fmt % value
   324                 value = fmt % value
   304             else:
   325             else:
   305                 value = unicode(value)
   326                 value = unicode(value)
   306         entity[attrname] = value
       
   307     return entity
   327     return entity
   308 
   328 
   309 
   329 
   310 
   330 
   311 def select(constraints, cursor, selectvar='O', objtype=None):
   331 def select(constraints, cursor, selectvar='O', objtype=None):