Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/descriptors/base.py: 80%

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

154 statements  

1# Copyright (c) 2010-2024 openpyxl 

2 

3 

4""" 

5Based on Python Cookbook 3rd Edition, 8.13 

6http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130 

7""" 

8 

9import datetime 

10import re 

11 

12from openpyxl import DEBUG 

13from openpyxl.utils.datetime import from_ISO8601 

14 

15from .namespace import namespaced 

16 

17class Descriptor: 

18 

19 def __init__(self, name=None, **kw): 

20 self.name = name 

21 for k, v in kw.items(): 

22 setattr(self, k, v) 

23 

24 def __set__(self, instance, value): 

25 instance.__dict__[self.name] = value 

26 

27 

28class Typed(Descriptor): 

29 """Values must of a particular type""" 

30 

31 expected_type = type(None) 

32 allow_none = False 

33 nested = False 

34 

35 def __init__(self, *args, **kw): 

36 super().__init__(*args, **kw) 

37 self.__doc__ = f"Values must be of type {self.expected_type}" 

38 

39 def __set__(self, instance, value): 

40 if not isinstance(value, self.expected_type): 

41 if (not self.allow_none 

42 or (self.allow_none and value is not None)): 

43 msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but value is {type(value)}" 

44 if DEBUG: 

45 msg = f"{instance.__class__}.{self.name} should be {self.expected_type} but {value} is {type(value)}" 

46 raise TypeError(msg) 

47 super().__set__(instance, value) 

48 

49 def __repr__(self): 

50 return self.__doc__ 

51 

52 

53def _convert(expected_type, value): 

54 """ 

55 Check value is of or can be converted to expected type. 

56 """ 

57 if not isinstance(value, expected_type): 

58 try: 

59 value = expected_type(value) 

60 except: 

61 raise TypeError('expected ' + str(expected_type)) 

62 return value 

63 

64 

65class Convertible(Typed): 

66 """Values must be convertible to a particular type""" 

67 

68 def __set__(self, instance, value): 

69 if ((self.allow_none and value is not None) 

70 or not self.allow_none): 

71 value = _convert(self.expected_type, value) 

72 super().__set__(instance, value) 

73 

74 

75class Max(Convertible): 

76 """Values must be less than a `max` value""" 

77 

78 expected_type = float 

79 allow_none = False 

80 

81 def __init__(self, **kw): 

82 if 'max' not in kw and not hasattr(self, 'max'): 

83 raise TypeError('missing max value') 

84 super().__init__(**kw) 

85 

86 def __set__(self, instance, value): 

87 if ((self.allow_none and value is not None) 

88 or not self.allow_none): 

89 value = _convert(self.expected_type, value) 

90 if value > self.max: 

91 raise ValueError('Max value is {0}'.format(self.max)) 

92 super().__set__(instance, value) 

93 

94 

95class Min(Convertible): 

96 """Values must be greater than a `min` value""" 

97 

98 expected_type = float 

99 allow_none = False 

100 

101 def __init__(self, **kw): 

102 if 'min' not in kw and not hasattr(self, 'min'): 

103 raise TypeError('missing min value') 

104 super().__init__(**kw) 

105 

106 def __set__(self, instance, value): 

107 if ((self.allow_none and value is not None) 

108 or not self.allow_none): 

109 value = _convert(self.expected_type, value) 

110 if value < self.min: 

111 raise ValueError('Min value is {0}'.format(self.min)) 

112 super().__set__(instance, value) 

113 

114 

115class MinMax(Min, Max): 

116 """Values must be greater than `min` value and less than a `max` one""" 

117 pass 

118 

119 

120class Set(Descriptor): 

121 """Value can only be from a set of know values""" 

122 

123 def __init__(self, name=None, **kw): 

124 if not 'values' in kw: 

125 raise TypeError("missing set of values") 

126 kw['values'] = set(kw['values']) 

127 super().__init__(name, **kw) 

128 self.__doc__ = "Value must be one of {0}".format(self.values) 

129 

130 def __set__(self, instance, value): 

131 if value not in self.values: 

132 raise ValueError(self.__doc__) 

133 super().__set__(instance, value) 

134 

135 

136class NoneSet(Set): 

137 

138 """'none' will be treated as None""" 

139 

140 def __init__(self, name=None, **kw): 

141 super().__init__(name, **kw) 

142 self.values.add(None) 

143 

144 def __set__(self, instance, value): 

145 if value == 'none': 

146 value = None 

147 super().__set__(instance, value) 

148 

149 

150class Integer(Convertible): 

151 

152 expected_type = int 

153 

154 

155class Float(Convertible): 

156 

157 expected_type = float 

158 

159 

160class Bool(Convertible): 

161 

162 expected_type = bool 

163 

164 def __set__(self, instance, value): 

165 if isinstance(value, str): 

166 if value in ('false', 'f', '0'): 

167 value = False 

168 super().__set__(instance, value) 

169 

170 

171class String(Typed): 

172 

173 expected_type = str 

174 

175 

176class Text(String, Convertible): 

177 

178 pass 

179 

180 

181class ASCII(Typed): 

182 

183 expected_type = bytes 

184 

185 

186class Tuple(Typed): 

187 

188 expected_type = tuple 

189 

190 

191class Length(Descriptor): 

192 

193 def __init__(self, name=None, **kw): 

194 if "length" not in kw: 

195 raise TypeError("value length must be supplied") 

196 super().__init__(**kw) 

197 

198 

199 def __set__(self, instance, value): 

200 if len(value) != self.length: 

201 raise ValueError("Value must be length {0}".format(self.length)) 

202 super().__set__(instance, value) 

203 

204 

205class Default(Typed): 

206 """ 

207 When called returns an instance of the expected type. 

208 Additional default values can be passed in to the descriptor 

209 """ 

210 

211 def __init__(self, name=None, **kw): 

212 if "defaults" not in kw: 

213 kw['defaults'] = {} 

214 super().__init__(**kw) 

215 

216 def __call__(self): 

217 return self.expected_type() 

218 

219 

220class Alias(Descriptor): 

221 """ 

222 Aliases can be used when either the desired attribute name is not allowed 

223 or confusing in Python (eg. "type") or a more descriptive name is desired 

224 (eg. "underline" for "u") 

225 """ 

226 

227 def __init__(self, alias): 

228 self.alias = alias 

229 

230 def __set__(self, instance, value): 

231 setattr(instance, self.alias, value) 

232 

233 def __get__(self, instance, cls): 

234 return getattr(instance, self.alias) 

235 

236 

237class MatchPattern(Descriptor): 

238 """Values must match a regex pattern """ 

239 allow_none = False 

240 

241 def __init__(self, name=None, **kw): 

242 if 'pattern' not in kw and not hasattr(self, 'pattern'): 

243 raise TypeError('missing pattern value') 

244 

245 super().__init__(name, **kw) 

246 self.test_pattern = re.compile(self.pattern, re.VERBOSE) 

247 

248 

249 def __set__(self, instance, value): 

250 

251 if value is None and not self.allow_none: 

252 raise ValueError("Value must not be none") 

253 

254 if ((self.allow_none and value is not None) 

255 or not self.allow_none): 

256 if not self.test_pattern.match(value): 

257 raise ValueError('Value does not match pattern {0}'.format(self.pattern)) 

258 

259 super().__set__(instance, value) 

260 

261 

262class DateTime(Typed): 

263 

264 expected_type = datetime.datetime 

265 

266 def __set__(self, instance, value): 

267 if value is not None and isinstance(value, str): 

268 try: 

269 value = from_ISO8601(value) 

270 except ValueError: 

271 raise ValueError("Value must be ISO datetime format") 

272 super().__set__(instance, value)