Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/openpyxl/descriptors/base.py: 81%
154 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-20 06:34 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-20 06:34 +0000
1# Copyright (c) 2010-2023 openpyxl
4"""
5Based on Python Cookbook 3rd Edition, 8.13
6http://chimera.labs.oreilly.com/books/1230000000393/ch08.html#_discussiuncion_130
7"""
9import datetime
10import re
12from openpyxl import DEBUG
13from openpyxl.utils.datetime import from_ISO8601
15from .namespace import namespaced
17class Descriptor(object):
19 def __init__(self, name=None, **kw):
20 self.name = name
21 for k, v in kw.items():
22 setattr(self, k, v)
24 def __set__(self, instance, value):
25 instance.__dict__[self.name] = value
28class Typed(Descriptor):
29 """Values must of a particular type"""
31 expected_type = type(None)
32 allow_none = False
33 nested = False
35 def __init__(self, *args, **kw):
36 super(Typed, self).__init__(*args, **kw)
37 self.__doc__ = f"Values must be of type {self.expected_type}"
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(Typed, self).__set__(instance, value)
49 def __repr__(self):
50 return self.__doc__
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
65class Convertible(Typed):
66 """Values must be convertible to a particular type"""
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(Convertible, self).__set__(instance, value)
75class Max(Convertible):
76 """Values must be less than a `max` value"""
78 expected_type = float
79 allow_none = False
81 def __init__(self, **kw):
82 if 'max' not in kw and not hasattr(self, 'max'):
83 raise TypeError('missing max value')
84 super(Max, self).__init__(**kw)
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(Max, self).__set__(instance, value)
95class Min(Convertible):
96 """Values must be greater than a `min` value"""
98 expected_type = float
99 allow_none = False
101 def __init__(self, **kw):
102 if 'min' not in kw and not hasattr(self, 'min'):
103 raise TypeError('missing min value')
104 super(Min, self).__init__(**kw)
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(Min, self).__set__(instance, value)
115class MinMax(Min, Max):
116 """Values must be greater than `min` value and less than a `max` one"""
117 pass
120class Set(Descriptor):
121 """Value can only be from a set of know values"""
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(Set, self).__init__(name, **kw)
128 self.__doc__ = "Value must be one of {0}".format(self.values)
130 def __set__(self, instance, value):
131 if value not in self.values:
132 raise ValueError(self.__doc__)
133 super(Set, self).__set__(instance, value)
136class NoneSet(Set):
138 """'none' will be treated as None"""
140 def __init__(self, name=None, **kw):
141 super(NoneSet, self).__init__(name, **kw)
142 self.values.add(None)
144 def __set__(self, instance, value):
145 if value == 'none':
146 value = None
147 super(NoneSet, self).__set__(instance, value)
150class Integer(Convertible):
152 expected_type = int
155class Float(Convertible):
157 expected_type = float
160class Bool(Convertible):
162 expected_type = bool
164 def __set__(self, instance, value):
165 if isinstance(value, str):
166 if value in ('false', 'f', '0'):
167 value = False
168 super(Bool, self).__set__(instance, value)
171class String(Typed):
173 expected_type = str
176class Text(String, Convertible):
178 pass
181class ASCII(Typed):
183 expected_type = bytes
186class Tuple(Typed):
188 expected_type = tuple
191class Length(Descriptor):
193 def __init__(self, name=None, **kw):
194 if "length" not in kw:
195 raise TypeError("value length must be supplied")
196 super(Length, self).__init__(**kw)
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(Length, self).__set__(instance, value)
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 """
211 def __init__(self, name=None, **kw):
212 if "defaults" not in kw:
213 kw['defaults'] = {}
214 super(Default, self).__init__(**kw)
216 def __call__(self):
217 return self.expected_type()
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 """
227 def __init__(self, alias):
228 self.alias = alias
230 def __set__(self, instance, value):
231 setattr(instance, self.alias, value)
233 def __get__(self, instance, cls):
234 return getattr(instance, self.alias)
237class MatchPattern(Descriptor):
238 """Values must match a regex pattern """
239 allow_none = False
241 def __init__(self, name=None, **kw):
242 if 'pattern' not in kw and not hasattr(self, 'pattern'):
243 raise TypeError('missing pattern value')
245 super(MatchPattern, self).__init__(name, **kw)
246 self.test_pattern = re.compile(self.pattern, re.VERBOSE)
249 def __set__(self, instance, value):
251 if value is None and not self.allow_none:
252 raise ValueError("Value must not be none")
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))
259 super(MatchPattern, self).__set__(instance, value)
262class DateTime(Typed):
264 expected_type = datetime.datetime
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(DateTime, self).__set__(instance, value)