Hide keyboard shortcuts

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 

3 

4from collections import OrderedDict 

5 

6from faker.config import DEFAULT_LOCALE 

7from faker.factory import Factory 

8from faker.generator import Generator 

9from faker.utils.distribution import choices_distribution 

10 

11 

12class Faker: 

13 """Proxy class capable of supporting multiple locales""" 

14 

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 ] 

21 

22 def __init__(self, locale=None, providers=None, 

23 generator=None, includes=None, **config): 

24 self._factory_map = OrderedDict() 

25 self._weights = None 

26 

27 if isinstance(locale, str): 

28 locales = [locale.replace('-', '_')] 

29 

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) 

39 

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()) 

48 

49 else: 

50 locales = [DEFAULT_LOCALE] 

51 

52 for locale in locales: 

53 self._factory_map[locale] = Factory.create(locale, providers, generator, includes, **config) 

54 

55 self._locales = locales 

56 self._factories = list(self._factory_map.values()) 

57 

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) 

65 

66 def __getitem__(self, locale): 

67 return self._factory_map[locale.replace('-', '_')] 

68 

69 def __getattribute__(self, attr): 

70 """ 

71 Handles the "attribute resolution" behavior for declared members of this proxy class 

72 

73 The class method `seed` cannot be called from an instance. 

74 

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) 

86 

87 def __getattr__(self, attr): 

88 """ 

89 Handles cache access and proxying behavior 

90 

91 :param attr: attribute name 

92 :return: the appropriate attribute 

93 """ 

94 

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) 

106 

107 def _select_factory(self, method_name): 

108 """ 

109 Returns a random factory that supports the provider method 

110 

111 :param method_name: Name of provider method 

112 :return: A factory that supports the provider method 

113 """ 

114 

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] 

121 

122 if weights: 

123 factory = choices_distribution(factories, weights, length=1)[0] 

124 else: 

125 factory = random.choice(factories) 

126 return factory 

127 

128 def _map_provider_method(self, method_name): 

129 """ 

130 Creates a 2-tuple of factories and weights for the given provider method name 

131 

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. 

134 

135 :param method_name: Name of provider method 

136 :return: 2-tuple (factories, weights) 

137 """ 

138 

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) 

143 

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 

160 

161 # Then cache and return results 

162 setattr(self, attr, mapping) 

163 return mapping 

164 

165 @classmethod 

166 def seed(cls, seed=None): 

167 """ 

168 Seeds the shared `random.Random` object across all factories 

169 

170 :param seed: seed value 

171 """ 

172 Generator.seed(seed) 

173 

174 def seed_instance(self, seed=None): 

175 """ 

176 Creates and seeds a new `random.Random` object for each factory 

177 

178 :param seed: seed value 

179 """ 

180 for factory in self._factories: 

181 factory.seed_instance(seed) 

182 

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 

186 

187 :param locale: locale string 

188 :param seed: seed value 

189 """ 

190 self._factory_map[locale.replace('-', '_')].seed_instance(seed) 

191 

192 @property 

193 def random(self): 

194 """ 

195 Proxies `random` getter calls 

196 

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 """ 

201 

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) 

207 

208 @random.setter 

209 def random(self, value): 

210 """ 

211 Proxies `random` setter calls 

212 

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 """ 

217 

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) 

223 

224 @property 

225 def locales(self): 

226 return list(self._locales) 

227 

228 @property 

229 def weights(self): 

230 return self._weights 

231 

232 @property 

233 def factories(self): 

234 return self._factories 

235 

236 def items(self): 

237 return self._factory_map.items()