Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/faker/proxy.py : 15%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import random
2import re
4from collections import OrderedDict
6from faker.config import DEFAULT_LOCALE
7from faker.factory import Factory
8from faker.generator import Generator
9from faker.utils.distribution import choices_distribution
12class Faker:
13 """Proxy class capable of supporting multiple locales"""
15 cache_pattern = re.compile(r'^_cached_\w*_mapping$')
16 generator_attrs = [
17 attr for attr in dir(Generator)
18 if not attr.startswith('__')
19 and attr not in ['seed', 'seed_instance', 'random']
20 ]
22 def __init__(self, locale=None, providers=None,
23 generator=None, includes=None, **config):
24 self._factory_map = OrderedDict()
25 self._weights = None
27 if isinstance(locale, str):
28 locales = [locale.replace('-', '_')]
30 # This guarantees a FIFO ordering of elements in `locales` based on the final
31 # locale string while discarding duplicates after processing
32 elif isinstance(locale, (list, tuple, set)):
33 assert all(isinstance(local_code, str) for local_code in locale)
34 locales = []
35 for code in locale:
36 final_locale = code.replace('-', '_')
37 if final_locale not in locales:
38 locales.append(final_locale)
40 elif isinstance(locale, OrderedDict):
41 assert all(isinstance(v, (int, float)) for v in locale.values())
42 odict = OrderedDict()
43 for k, v in locale.items():
44 key = k.replace('-', '_')
45 odict[key] = v
46 locales = list(odict.keys())
47 self._weights = list(odict.values())
49 else:
50 locales = [DEFAULT_LOCALE]
52 for locale in locales:
53 self._factory_map[locale] = Factory.create(locale, providers, generator, includes, **config)
55 self._locales = locales
56 self._factories = list(self._factory_map.values())
58 def __dir__(self):
59 attributes = set(super(Faker, self).__dir__())
60 for factory in self.factories:
61 attributes |= {
62 attr for attr in dir(factory) if not attr.startswith('_')
63 }
64 return sorted(attributes)
66 def __getitem__(self, locale):
67 return self._factory_map[locale.replace('-', '_')]
69 def __getattribute__(self, attr):
70 """
71 Handles the "attribute resolution" behavior for declared members of this proxy class
73 The class method `seed` cannot be called from an instance.
75 :param attr: attribute name
76 :return: the appropriate attribute
77 """
78 if attr == 'seed':
79 msg = (
80 'Calling `.seed()` on instances is deprecated. '
81 'Use the class method `Faker.seed()` instead.'
82 )
83 raise TypeError(msg)
84 else:
85 return super().__getattribute__(attr)
87 def __getattr__(self, attr):
88 """
89 Handles cache access and proxying behavior
91 :param attr: attribute name
92 :return: the appropriate attribute
93 """
95 if len(self._factories) == 1:
96 return getattr(self._factories[0], attr)
97 elif attr in self.generator_attrs:
98 msg = 'Proxying calls to `%s` is not implemented in multiple locale mode.' % attr
99 raise NotImplementedError(msg)
100 elif self.cache_pattern.match(attr):
101 msg = 'Cached attribute `%s` does not exist' % attr
102 raise AttributeError(msg)
103 else:
104 factory = self._select_factory(attr)
105 return getattr(factory, attr)
107 def _select_factory(self, method_name):
108 """
109 Returns a random factory that supports the provider method
111 :param method_name: Name of provider method
112 :return: A factory that supports the provider method
113 """
115 factories, weights = self._map_provider_method(method_name)
116 if len(factories) == 0:
117 msg = "No generator object has attribute '{}'".format(method_name)
118 raise AttributeError(msg)
119 elif len(factories) == 1:
120 return factories[0]
122 if weights:
123 factory = choices_distribution(factories, weights, length=1)[0]
124 else:
125 factory = random.choice(factories)
126 return factory
128 def _map_provider_method(self, method_name):
129 """
130 Creates a 2-tuple of factories and weights for the given provider method name
132 The first element of the tuple contains a list of compatible factories.
133 The second element of the tuple contains a list of distribution weights.
135 :param method_name: Name of provider method
136 :return: 2-tuple (factories, weights)
137 """
139 # Return cached mapping if it exists for given method
140 attr = '_cached_{}_mapping'.format(method_name)
141 if hasattr(self, attr):
142 return getattr(self, attr)
144 # Create mapping if it does not exist
145 if self._weights:
146 value = [
147 (factory, weight)
148 for factory, weight in zip(self.factories, self._weights)
149 if hasattr(factory, method_name)
150 ]
151 factories, weights = zip(*value)
152 mapping = list(factories), list(weights)
153 else:
154 value = [
155 factory
156 for factory in self.factories
157 if hasattr(factory, method_name)
158 ]
159 mapping = value, None
161 # Then cache and return results
162 setattr(self, attr, mapping)
163 return mapping
165 @classmethod
166 def seed(cls, seed=None):
167 """
168 Seeds the shared `random.Random` object across all factories
170 :param seed: seed value
171 """
172 Generator.seed(seed)
174 def seed_instance(self, seed=None):
175 """
176 Creates and seeds a new `random.Random` object for each factory
178 :param seed: seed value
179 """
180 for factory in self._factories:
181 factory.seed_instance(seed)
183 def seed_locale(self, locale, seed=None):
184 """
185 Creates and seeds a new `random.Random` object for the factory of the specified locale
187 :param locale: locale string
188 :param seed: seed value
189 """
190 self._factory_map[locale.replace('-', '_')].seed_instance(seed)
192 @property
193 def random(self):
194 """
195 Proxies `random` getter calls
197 In single locale mode, this will be proxied to the `random` getter
198 of the only internal `Generator` object. Subclasses will have to
199 implement desired behavior in multiple locale mode.
200 """
202 if len(self._factories) == 1:
203 return self._factories[0].random
204 else:
205 msg = 'Proxying `random` getter calls is not implemented in multiple locale mode.'
206 raise NotImplementedError(msg)
208 @random.setter
209 def random(self, value):
210 """
211 Proxies `random` setter calls
213 In single locale mode, this will be proxied to the `random` setter
214 of the only internal `Generator` object. Subclasses will have to
215 implement desired behavior in multiple locale mode.
216 """
218 if len(self._factories) == 1:
219 self._factories[0].random = value
220 else:
221 msg = 'Proxying `random` setter calls is not implemented in multiple locale mode.'
222 raise NotImplementedError(msg)
224 @property
225 def locales(self):
226 return list(self._locales)
228 @property
229 def weights(self):
230 return self._weights
232 @property
233 def factories(self):
234 return self._factories
236 def items(self):
237 return self._factory_map.items()