Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/dateparser/conf.py: 89%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

94 statements  

1import hashlib 

2from datetime import datetime 

3from functools import wraps 

4 

5from dateparser.data.languages_info import language_order 

6 

7from .parser import date_order_chart 

8from .utils import registry 

9 

10 

11@registry 

12class Settings: 

13 """Control and configure default parsing behavior of dateparser. 

14 Currently, supported settings are: 

15 

16 * `DATE_ORDER` 

17 * `PREFER_LOCALE_DATE_ORDER` 

18 * `TIMEZONE` 

19 * `TO_TIMEZONE` 

20 * `RETURN_AS_TIMEZONE_AWARE` 

21 * `PREFER_MONTH_OF_YEAR` 

22 * `PREFER_DAY_OF_MONTH` 

23 * `PREFER_DATES_FROM` 

24 * `RELATIVE_BASE` 

25 * `STRICT_PARSING` 

26 * `REQUIRE_PARTS` 

27 * `SKIP_TOKENS` 

28 * `NORMALIZE` 

29 * `RETURN_TIME_AS_PERIOD` 

30 * `PARSERS` 

31 * `DEFAULT_LANGUAGES` 

32 * `LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD` 

33 * `CACHE_SIZE_LIMIT` 

34 """ 

35 

36 _default = True 

37 _pyfile_data = None 

38 _mod_settings = dict() 

39 

40 def __init__(self, settings=None): 

41 if settings: 

42 self._updateall(settings.items()) 

43 else: 

44 self._updateall(self._get_settings_from_pyfile().items()) 

45 

46 @classmethod 

47 def get_key(cls, settings=None): 

48 if not settings: 

49 return "default" 

50 

51 keys = sorted(["%s-%s" % (key, str(settings[key])) for key in settings]) 

52 return hashlib.md5("".join(keys).encode("utf-8")).hexdigest() 

53 

54 @classmethod 

55 def _get_settings_from_pyfile(cls): 

56 if not cls._pyfile_data: 

57 from dateparser_data import settings 

58 

59 cls._pyfile_data = settings.settings 

60 return cls._pyfile_data 

61 

62 def _updateall(self, iterable): 

63 for key, value in iterable: 

64 setattr(self, key, value) 

65 

66 def replace(self, mod_settings=None, **kwds): 

67 for k, v in kwds.items(): 

68 if v is None: 

69 raise TypeError('Invalid {{"{}": {}}}'.format(k, v)) 

70 

71 for x in self._get_settings_from_pyfile().keys(): 

72 kwds.setdefault(x, getattr(self, x)) 

73 

74 kwds["_default"] = False 

75 if mod_settings: 

76 kwds["_mod_settings"] = mod_settings 

77 

78 return self.__class__(settings=kwds) 

79 

80 

81settings = Settings() 

82 

83 

84def apply_settings(f): 

85 @wraps(f) 

86 def wrapper(*args, **kwargs): 

87 mod_settings = kwargs.get("settings") 

88 kwargs["settings"] = mod_settings or settings 

89 

90 if isinstance(kwargs["settings"], dict): 

91 kwargs["settings"] = settings.replace( 

92 mod_settings=mod_settings, **kwargs["settings"] 

93 ) 

94 

95 if not isinstance(kwargs["settings"], Settings): 

96 raise TypeError( 

97 "settings can only be either dict or instance of Settings class" 

98 ) 

99 

100 return f(*args, **kwargs) 

101 

102 return wrapper 

103 

104 

105class SettingValidationError(ValueError): 

106 pass 

107 

108 

109def _check_repeated_values(setting_name, setting_value): 

110 if len(setting_value) != len(set(setting_value)): 

111 raise SettingValidationError( 

112 'There are repeated values in the "{}" setting'.format(setting_name) 

113 ) 

114 return 

115 

116 

117def _check_require_part(setting_name, setting_value): 

118 """Returns `True` if the provided list of parts contains valid values""" 

119 invalid_values = set(setting_value) - {"day", "month", "year"} 

120 if invalid_values: 

121 raise SettingValidationError( 

122 '"{}" setting contains invalid values: {}'.format( 

123 setting_name, ", ".join(invalid_values) 

124 ) 

125 ) 

126 _check_repeated_values(setting_name, setting_value) 

127 

128 

129def _check_parsers(setting_name, setting_value): 

130 """Returns `True` if the provided list of parsers contains valid values""" 

131 existing_parsers = [ 

132 "timestamp", 

133 "relative-time", 

134 "custom-formats", 

135 "absolute-time", 

136 "no-spaces-time", 

137 "negative-timestamp", 

138 ] # FIXME: Extract the list of existing parsers from another place (#798) 

139 unknown_parsers = set(setting_value) - set(existing_parsers) 

140 if unknown_parsers: 

141 raise SettingValidationError( 

142 'Found unknown parsers in the "{}" setting: {}'.format( 

143 setting_name, ", ".join(unknown_parsers) 

144 ) 

145 ) 

146 _check_repeated_values(setting_name, setting_value) 

147 

148 

149def _check_default_languages(setting_name, setting_value): 

150 unsupported_languages = set(setting_value) - set(language_order) 

151 if unsupported_languages: 

152 raise SettingValidationError( 

153 "Found invalid languages in the '{}' setting: {}".format( 

154 setting_name, ", ".join(map(repr, unsupported_languages)) 

155 ) 

156 ) 

157 _check_repeated_values(setting_name, setting_value) 

158 

159 

160def _check_between_0_and_1(setting_name, setting_value): 

161 is_valid = 0 <= setting_value <= 1 

162 if not is_valid: 

163 raise SettingValidationError( 

164 "{} is not a valid value for {}. It can take values between 0 and " 

165 "1.".format( 

166 setting_value, 

167 setting_name, 

168 ) 

169 ) 

170 

171 

172def check_settings(settings): 

173 """ 

174 Check if provided settings are valid, if not it raises `SettingValidationError`. 

175 Only checks for the modified settings. 

176 """ 

177 settings_values = { 

178 "DATE_ORDER": { 

179 "values": tuple(date_order_chart.keys()), 

180 "type": str, 

181 }, 

182 "TIMEZONE": { 

183 # we don't check invalid Timezones as they raise an error 

184 "type": str, 

185 }, 

186 "TO_TIMEZONE": { 

187 # It defaults to None, but it's not allowed to use it directly 

188 # "values" can take unlimited options 

189 "type": str 

190 }, 

191 "RETURN_AS_TIMEZONE_AWARE": { 

192 # It defaults to 'default', but it's not allowed to use it directly 

193 "type": bool 

194 }, 

195 "PREFER_MONTH_OF_YEAR": {"values": ("current", "first", "last"), "type": str}, 

196 "PREFER_DAY_OF_MONTH": {"values": ("current", "first", "last"), "type": str}, 

197 "PREFER_DATES_FROM": { 

198 "values": ("current_period", "past", "future"), 

199 "type": str, 

200 }, 

201 "RELATIVE_BASE": { 

202 # "values" can take unlimited options 

203 "type": datetime 

204 }, 

205 "STRICT_PARSING": {"type": bool}, 

206 "REQUIRE_PARTS": { 

207 # "values" covered by the 'extra_check' 

208 "type": list, 

209 "extra_check": _check_require_part, 

210 }, 

211 "SKIP_TOKENS": { 

212 # "values" can take unlimited options 

213 "type": list, 

214 }, 

215 "NORMALIZE": {"type": bool}, 

216 "RETURN_TIME_AS_PERIOD": {"type": bool}, 

217 "PARSERS": { 

218 # "values" covered by the 'extra_check' 

219 "type": list, 

220 "extra_check": _check_parsers, 

221 }, 

222 "FUZZY": {"type": bool}, 

223 "PREFER_LOCALE_DATE_ORDER": {"type": bool}, 

224 "DEFAULT_LANGUAGES": {"type": list, "extra_check": _check_default_languages}, 

225 "LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD": { 

226 "type": float, 

227 "extra_check": _check_between_0_and_1, 

228 }, 

229 "CACHE_SIZE_LIMIT": { 

230 "type": int, 

231 }, 

232 } 

233 

234 modified_settings = settings._mod_settings # check only modified settings 

235 

236 # check settings keys: 

237 for setting in modified_settings: 

238 if setting not in settings_values: 

239 raise SettingValidationError('"{}" is not a valid setting'.format(setting)) 

240 

241 for setting_name, setting_value in modified_settings.items(): 

242 setting_type = type(setting_value) 

243 setting_props = settings_values[setting_name] 

244 

245 # check type: 

246 if not isinstance(setting_value, setting_props["type"]): 

247 raise SettingValidationError( 

248 '"{}" must be "{}", not "{}".'.format( 

249 setting_name, setting_props["type"].__name__, setting_type.__name__ 

250 ) 

251 ) 

252 

253 # check values: 

254 if setting_props.get("values") and setting_value not in setting_props["values"]: 

255 raise SettingValidationError( 

256 '"{}" is not a valid value for "{}", it should be: "{}" or "{}"'.format( 

257 setting_value, 

258 setting_name, 

259 '", "'.join(setting_props["values"][:-1]), 

260 setting_props["values"][-1], 

261 ) 

262 ) 

263 

264 # specific checks 

265 extra_check = setting_props.get("extra_check") 

266 if extra_check: 

267 extra_check(setting_name, setting_value)