1import random 2from datetime import datetime, timedelta 3from odoo.tools import pycompat 4 5 6def Random(seed): 7 """ Return a random number generator object with the given seed. """ 8 r = random.Random() 9 r.seed(seed, version=2) 10 return r 11 12 13def format_str(val, counter, values): 14 """ Format the given value (with method ``format``) when it is a string. """ 15 if isinstance(val, str): 16 return val.format(counter=counter, values=values) 17 return val 18 19 20def chain_factories(field_factories, model_name): 21 """ Instanciate a generator by calling all the field factories. """ 22 generator = root_factory() 23 for (fname, field_factory) in field_factories: 24 generator = field_factory(generator, fname, model_name) 25 return generator 26 27 28def root_factory(): 29 """ Return a generator with empty values dictionaries (except for the flag ``__complete``). """ 30 yield {'__complete': False} 31 while True: 32 yield {'__complete': True} 33 34 35def randomize(vals, weights=None, seed=False, formatter=format_str, counter_offset=0): 36 """ Return a factory for an iterator of values dicts with pseudo-randomly 37 chosen values (among ``vals``) for a field. 38 39 :param list vals: list in which a value will be chosen, depending on `weights` 40 :param list weights: list of probabilistic weights 41 :param seed: optional initialization of the random number generator 42 :param function formatter: (val, counter, values) --> formatted_value 43 :param int counter_offset: 44 :returns: function of the form (iterator, field_name, model_name) -> values 45 :rtype: function (iterator, str, str) -> dict 46 """ 47 def generate(iterator, field_name, model_name): 48 r = Random('%s+field+%s' % (model_name, seed or field_name)) 49 for counter, values in enumerate(iterator): 50 val = r.choices(vals, weights)[0] 51 values[field_name] = formatter(val, counter + counter_offset, values) 52 yield values 53 return generate 54 55 56def cartesian(vals, weights=None, seed=False, formatter=format_str, then=None): 57 """ Return a factory for an iterator of values dicts that combines all ``vals`` for 58 the field with the other field values in input. 59 60 :param list vals: list in which a value will be chosen, depending on `weights` 61 :param list weights: list of probabilistic weights 62 :param seed: optional initialization of the random number generator 63 :param function formatter: (val, counter, values) --> formatted_value 64 :param function then: if defined, factory used when vals has been consumed. 65 :returns: function of the form (iterator, field_name, model_name) -> values 66 :rtype: function (iterator, str, str) -> dict 67 """ 68 def generate(iterator, field_name, model_name): 69 counter = 0 70 for values in iterator: 71 if values['__complete']: 72 break # will consume and lose an element, (complete so a filling element). If it is a problem, use peekable instead. 73 for val in vals: 74 yield {**values, field_name: formatter(val, counter, values)} 75 counter += 1 76 factory = then or randomize(vals, weights, seed, formatter, counter) 77 yield from factory(iterator, field_name, model_name) 78 return generate 79 80 81def iterate(vals, weights=None, seed=False, formatter=format_str, then=None): 82 """ Return a factory for an iterator of values dicts that picks a value among ``vals`` 83 for each input. Once all ``vals`` have been used once, resume as ``then`` or as a 84 ``randomize`` generator. 85 86 :param list vals: list in which a value will be chosen, depending on `weights` 87 :param list weights: list of probabilistic weights 88 :param seed: optional initialization of the random number generator 89 :param function formatter: (val, counter, values) --> formatted_value 90 :param function then: if defined, factory used when vals has been consumed. 91 :returns: function of the form (iterator, field_name, model_name) -> values 92 :rtype: function (iterator, str, str) -> dict 93 """ 94 def generate(iterator, field_name, model_name): 95 counter = 0 96 for val in vals: # iteratable order is important, shortest first 97 values = next(iterator) 98 values[field_name] = formatter(val, counter, values) 99 values['__complete'] = False 100 yield values 101 counter += 1 102 factory = then or randomize(vals, weights, seed, formatter, counter) 103 yield from factory(iterator, field_name, model_name) 104 return generate 105 106 107def constant(val, formatter=format_str): 108 """ Return a factory for an iterator of values dicts that sets the field 109 to the given value in each input dict. 110 111 :returns: function of the form (iterator, field_name, model_name) -> values 112 :rtype: function (iterator, str, str) -> dict 113 """ 114 def generate(iterator, field_name, _): 115 for counter, values in enumerate(iterator): 116 values[field_name] = formatter(val, counter, values) 117 yield values 118 return generate 119 120 121def compute(function, seed=None): 122 """ Return a factory for an iterator of values dicts that computes the field value 123 as ``function(values, counter, random)``, where ``values`` is the other field values, 124 ``counter`` is an integer, and ``random`` is a pseudo-random number generator. 125 126 :param function function: (values, counter, random) --> field_values 127 :param seed: optional initialization of the random number generator 128 :returns: function of the form (iterator, field_name, model_name) -> values 129 :rtype: function (iterator, str, str) -> dict 130 """ 131 def generate(iterator, field_name, model_name): 132 r = Random('%s+field+%s' % (model_name, seed or field_name)) 133 for counter, values in enumerate(iterator): 134 val = function(values=values, counter=counter, random=r) 135 values[field_name] = val 136 yield values 137 return generate 138 139def randint(a, b, seed=None): 140 """ Return a factory for an iterator of values dicts that sets the field 141 to the random integer between a and b included in each input dict. 142 143 :param int a: minimal random value 144 :param int b: maximal random value 145 :returns: function of the form (iterator, field_name, model_name) -> values 146 :rtype: function (iterator, str, str) -> dict 147 """ 148 def get_rand_int(random=None, **kwargs): 149 return random.randint(a, b) 150 return compute(get_rand_int, seed=seed) 151