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 |