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