1# -*- coding: utf-8 -*- 2 3# Copyright(C) 2010-2011 Romain Bignon 4# 5# This file is part of weboob. 6# 7# weboob is free software: you can redistribute it and/or modify 8# it under the terms of the GNU Lesser General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# weboob is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public License 18# along with weboob. If not, see <http://www.gnu.org/licenses/>. 19 20 21import re 22import time 23from collections import OrderedDict 24 25from weboob.tools.compat import unicode 26 27from .misc import to_unicode 28 29 30__all__ = ['ValuesDict', 'Value', 'ValueBackendPassword', 'ValueInt', 'ValueFloat', 'ValueBool'] 31 32 33class ValuesDict(OrderedDict): 34 """ 35 Ordered dictionarry which can take values in constructor. 36 37 >>> ValuesDict(Value('a', label='Test'), ValueInt('b', label='Test2')) 38 """ 39 40 def __init__(self, *values): 41 super(ValuesDict, self).__init__() 42 for v in values: 43 self[v.id] = v 44 45 46class Value(object): 47 """ 48 Value. 49 50 :param label: human readable description of a value 51 :type label: str 52 :param required: if ``True``, the backend can't load if the key isn't found in its configuration 53 :type required: bool 54 :param default: an optional default value, used when the key is not in config. If there is no default value and the key 55 is not found in configuration, the **required** parameter is implicitly set 56 :param masked: if ``True``, the value is masked. It is useful for applications to know if this key is a password 57 :type masked: bool 58 :param regexp: if specified, on load the specified value is checked against this regexp, and an error is raised if it doesn't match 59 :type regexp: str 60 :param choices: if this parameter is set, the value must be in the list 61 :type choices: (list,dict) 62 :param aliases: mapping of old choices values that should be accepted but not presented 63 :type aliases: dict 64 :param tiny: the value of choices can be entered by an user (as they are small) 65 :type tiny: bool 66 :param transient: this value is not persistent (asked only if needed) 67 :type transient: bool 68 """ 69 70 def __init__(self, *args, **kwargs): 71 if len(args) > 0: 72 self.id = args[0] 73 else: 74 self.id = '' 75 self.label = kwargs.get('label', kwargs.get('description', None)) 76 self.description = kwargs.get('description', kwargs.get('label', None)) 77 self.default = kwargs.get('default', None) 78 if isinstance(self.default, str): 79 self.default = to_unicode(self.default) 80 self.regexp = kwargs.get('regexp', None) 81 self.choices = kwargs.get('choices', None) 82 self.aliases = kwargs.get('aliases') 83 if isinstance(self.choices, (list, tuple)): 84 self.choices = OrderedDict(((v, v) for v in self.choices)) 85 self.tiny = kwargs.get('tiny', None) 86 self.transient = kwargs.get('transient', None) 87 self.masked = kwargs.get('masked', False) 88 self.required = kwargs.get('required', self.default is None) 89 self._value = kwargs.get('value', None) 90 91 def show_value(self, v): 92 if self.masked: 93 return u'' 94 else: 95 return v 96 97 def check_valid(self, v): 98 """ 99 Check if the given value is valid. 100 101 :raises: ValueError 102 """ 103 if self.required and v is None: 104 raise ValueError('Value is required and thus must be set') 105 if v == self.default: 106 return 107 if v == '' and self.default != '' and (self.choices is None or v not in self.choices): 108 raise ValueError('Value can\'t be empty') 109 if self.regexp is not None and not re.match(self.regexp + '$', unicode(v) if v is not None else ''): 110 raise ValueError('Value "%s" does not match regexp "%s"' % (self.show_value(v), self.regexp)) 111 if self.choices is not None and v not in self.choices: 112 if not self.aliases or v not in self.aliases: 113 raise ValueError('Value "%s" is not in list: %s' % ( 114 self.show_value(v), ', '.join(unicode(s) for s in self.choices))) 115 116 def load(self, domain, v, requests): 117 """ 118 Load value. 119 120 :param domain: what is the domain of this value 121 :type domain: str 122 :param v: value to load 123 :param requests: list of weboob requests 124 :type requests: weboob.core.requests.Requests 125 """ 126 return self.set(v) 127 128 def set(self, v): 129 """ 130 Set a value. 131 """ 132 if isinstance(v, str): 133 v = to_unicode(v) 134 self.check_valid(v) 135 if self.aliases and v in self.aliases: 136 v = self.aliases[v] 137 self._value = v 138 139 def dump(self): 140 """ 141 Dump value to be stored. 142 """ 143 return self.get() 144 145 def get(self): 146 """ 147 Get the value. 148 """ 149 return self._value 150 151 152class ValueTransient(Value): 153 def __init__(self, *args, **kwargs): 154 kwargs.setdefault('transient', True) 155 kwargs.setdefault('default', None) 156 kwargs.setdefault('required', False) 157 super(ValueTransient, self).__init__(*args, **kwargs) 158 159 def dump(self): 160 return '' 161 162 163class ValueBackendPassword(Value): 164 _domain = None 165 _requests = None 166 _stored = True 167 168 def __init__(self, *args, **kwargs): 169 kwargs['masked'] = kwargs.pop('masked', True) 170 self.noprompt = kwargs.pop('noprompt', False) 171 super(ValueBackendPassword, self).__init__(*args, **kwargs) 172 self.default = kwargs.get('default', '') 173 174 def load(self, domain, password, requests): 175 self.check_valid(password) 176 self._domain = domain 177 self._value = to_unicode(password) 178 self._requests = requests 179 180 def check_valid(self, passwd): 181 if passwd == '': 182 # always allow empty passwords 183 return True 184 return super(ValueBackendPassword, self).check_valid(passwd) 185 186 def set(self, passwd): 187 self.check_valid(passwd) 188 if passwd is None: 189 # no change 190 return 191 self._value = '' 192 if passwd == '': 193 return 194 if self._domain is None: 195 self._value = to_unicode(passwd) 196 return 197 198 self._value = to_unicode(passwd) 199 200 def dump(self): 201 if self._stored: 202 return self._value 203 else: 204 return '' 205 206 def get(self): 207 if self._value != '' or self._domain is None: 208 return self._value 209 210 passwd = None 211 212 if passwd is not None: 213 # Password has been read in the keyring. 214 return to_unicode(passwd) 215 216 # Prompt user to enter password by hand. 217 if not self.noprompt and self._requests: 218 self._value = self._requests.request('login', self._domain, self) 219 if self._value is None: 220 self._value = '' 221 else: 222 self._value = to_unicode(self._value) 223 self._stored = False 224 return self._value 225 226 227class ValueInt(Value): 228 def __init__(self, *args, **kwargs): 229 kwargs['regexp'] = '^\d+$' 230 super(ValueInt, self).__init__(*args, **kwargs) 231 self.default = kwargs.get('default', 0) 232 233 def get(self): 234 return int(self._value) 235 236 237class ValueFloat(Value): 238 def __init__(self, *args, **kwargs): 239 kwargs['regexp'] = '^[\d\.]+$' 240 super(ValueFloat, self).__init__(*args, **kwargs) 241 self.default = kwargs.get('default', 0.0) 242 243 def check_valid(self, v): 244 try: 245 float(v) 246 except ValueError: 247 raise ValueError('Value "%s" is not a float value' % self.show_value(v)) 248 249 def get(self): 250 return float(self._value) 251 252 253class ValueBool(Value): 254 def __init__(self, *args, **kwargs): 255 kwargs['choices'] = {'y': 'True', 'n': 'False'} 256 super(ValueBool, self).__init__(*args, **kwargs) 257 self.default = kwargs.get('default', False) 258 259 def check_valid(self, v): 260 if not isinstance(v, bool) and \ 261 unicode(v).lower() not in ('y', 'yes', '1', 'true', 'on', 262 'n', 'no', '0', 'false', 'off'): 263 raise ValueError('Value "%s" is not a boolean (y/n)' % self.show_value(v)) 264 265 def get(self): 266 return (isinstance(self._value, bool) and self._value) or \ 267 unicode(self._value).lower() in ('y', 'yes', '1', 'true', 'on') 268 269 270class ValueDate(Value): 271 DEFAULT_FORMATS = ('%Y-%m-%d',) 272 273 def __init__(self, *args, **kwargs): 274 super(ValueDate, self).__init__(*args, **kwargs) 275 self.formats = tuple(kwargs.get('formats', ())) 276 self.formats_tuple = self.DEFAULT_FORMATS + self.formats 277 278 def get_format(self, v=None): 279 for format in self.formats_tuple: 280 try: 281 dateval = time.strptime(v or self._value, format) 282 # year < 1900 is handled by strptime but not strftime, check it 283 time.strftime(self.formats_tuple[0], dateval) 284 except ValueError: 285 continue 286 return format 287 288 def check_valid(self, v): 289 super(ValueDate, self).check_valid(v) 290 if v is not None and not self.get_format(v): 291 raise ValueError('Value "%s" does not match format in %s' % (self.show_value(v), self.show_value(self.formats_tuple))) 292 293 def get(self): 294 if self.formats: 295 self._value = time.strftime(self.formats[0], time.strptime(self._value, self.get_format())) 296 return self._value 297