Robot Framework Integrated Development Environment (RIDE)
typeconverters.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 from ast import literal_eval
17 from collections import OrderedDict
18 try:
19  from collections import abc
20 except ImportError: # Python 2
21  import collections as abc
22 from datetime import datetime, date, timedelta
23 from decimal import InvalidOperation, Decimal
24 try:
25  from enum import Enum
26 except ImportError: # Standard in Py 3.4+ but can be separately installed
27  class Enum():
28  pass
29 from numbers import Integral, Real
30 
31 from robotide.lib.robot.libraries.DateTime import convert_date, convert_time
32 from robotide.lib.robot.utils import (FALSE_STRINGS, IRONPYTHON, TRUE_STRINGS, PY_VERSION,
33  PY2, seq2str, type_name, unicode)
34 
35 
36 class TypeConverter():
37  type = None
38  abc = None
39  aliases = ()
40  convert_none = True
41 
44  _converters = OrderedDict()
45 
48  _type_aliases = {}
49 
50  @property
51  type_name = property
52 
53  def type_name(self):
54  return self.typetype.__name__.lower()
55 
56  @classmethod
57  def register(cls, converter_class):
58  converter = converter_class()
59  cls._converters_converters[converter.type] = converter
60  for name in (converter.type_name,) + converter.aliases:
61  if name is not None:
62  cls._type_aliases_type_aliases[name.lower()] = converter.type
63  return converter_class
64 
65  @classmethod
66  def converter_for(cls, type_):
67  # Types defined in the typing module in Python 3.7+. For details see
68  # https://bugs.python.org/issue34568
69  if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
70  type_ = type_.__origin__
71  if isinstance(type_, (str, unicode)):
72  try:
73  type_ = cls._type_aliases_type_aliases[type_.lower()]
74  except KeyError:
75  return None
76  if not isinstance(type_, type) or issubclass(type_, unicode):
77  return None
78  if type_ in cls._converters_converters:
79  return cls._converters_converters[type_]
80  for converter in cls._converters_converters.values():
81  if converter.handles(type_):
82  return converter.get_converter(type_)
83  return None
84 
85  def handles(self, type_):
86  return (issubclass(type_, self.typetype) or
87  self.abcabc and issubclass(type_, self.abcabc))
88 
89  def get_converter(self, type_):
90  return self
91 
92  def convert(self, name, value, explicit_type=True):
93  if self.convert_noneconvert_none and value.upper() == 'NONE':
94  return None
95  try:
96  return self._convert_convert(value, explicit_type)
97  except ValueError as error:
98  return self._handle_error_handle_error(name, value, error, explicit_type)
99 
100  def _convert(self, value, explicit_type=True):
101  raise NotImplementedError
102 
103  def _handle_error(self, name, value, error, explicit_type=True):
104  if not explicit_type:
105  return value
106  ending = u': %s' % error if error.args else '.'
107  raise ValueError("Argument '%s' got value '%s' that cannot be "
108  "converted to %s%s"
109  % (name, value, self.type_nametype_nametype_name, ending))
110 
111  def _literal_eval(self, value, expected):
112  # ast.literal_eval has some issues with sets:
113  if expected is set:
114  # On Python 2 it doesn't handle sets at all.
115  if PY2:
116  raise ValueError('Sets are not supported on Python 2.')
117  # There is no way to define an empty set.
118  if value == 'set()':
119  return set()
120  try:
121  value = literal_eval(value)
122  except (ValueError, SyntaxError):
123  # Original errors aren't too informative in these cases.
124  raise ValueError('Invalid expression.')
125  except TypeError as err:
126  raise ValueError('Evaluating expression failed: %s' % err)
127  if not isinstance(value, expected):
128  raise ValueError('Value is %s, not %s.' % (type_name(value),
129  expected.__name__))
130  return value
131 
132 
133 @TypeConverter.register
135  type = bool
136  type_name = 'boolean'
137  aliases = ('bool',)
138 
139  def _convert(self, value, explicit_type=True):
140  upper = value.upper()
141  if upper in TRUE_STRINGS:
142  return True
143  if upper in FALSE_STRINGS:
144  return False
145  return value
146 
147 
148 @TypeConverter.register
150  type = int
151  abc = Integral
152  type_name = 'integer'
153  aliases = ('int', 'long')
154 
155  def _convert(self, value, explicit_type=True):
156  try:
157  return int(value)
158  except ValueError:
159  if not explicit_type:
160  try:
161  return float(value)
162  except ValueError:
163  pass
164  raise ValueError
165 
166 
167 @TypeConverter.register
169  type = float
170  abc = Real
171  aliases = ('double',)
172 
173  def _convert(self, value, explicit_type=True):
174  try:
175  return float(value)
176  except ValueError:
177  raise ValueError
178 
179 
180 @TypeConverter.register
182  type = Decimal
183 
184  def _convert(self, value, explicit_type=True):
185  try:
186  return Decimal(value)
187  except InvalidOperation:
188  # With Python 3 error messages by decimal module are not very
189  # useful and cannot be included in our error messages:
190  # https://bugs.python.org/issue26208
191  raise ValueError
192 
193 
194 @TypeConverter.register
196  type = bytes
197  abc = getattr(abc, 'ByteString', None) # ByteString is new in Python 3
198  type_name = 'bytes' # Needed on Python 2
199  convert_none = False
200 
201  def _convert(self, value, explicit_type=True):
202  if PY2 and not explicit_type:
203  return value
204  try:
205  value = value.encode('latin-1')
206  except UnicodeEncodeError as err:
207  raise ValueError("Character '%s' cannot be mapped to a byte."
208  % value[err.start:err.start+1])
209  return value if not IRONPYTHON else bytes(value)
210 
211 
212 @TypeConverter.register
214  type = bytearray
215  convert_none = False
216 
217  def _convert(self, value, explicit_type=True):
218  try:
219  return bytearray(value, 'latin-1')
220  except UnicodeEncodeError as err:
221  raise ValueError("Character '%s' cannot be mapped to a byte."
222  % value[err.start:err.start+1])
223 
224 
225 @TypeConverter.register
227  type = datetime
228 
229  def _convert(self, value, explicit_type=True):
230  return convert_date(value, result_format='datetime')
231 
232 
233 @TypeConverter.register
235  type = date
236 
237  def _convert(self, value, explicit_type=True):
238  dt = convert_date(value, result_format='datetime')
239  if dt.hour or dt.minute or dt.second or dt.microsecond:
240  raise ValueError("Value is datetime, not date.")
241  return dt.date()
242 
243 
244 @TypeConverter.register
246  type = timedelta
247 
248  def _convert(self, value, explicit_type=True):
249  return convert_time(value, result_format='timedelta')
250 
251 
252 @TypeConverter.register
254  type = Enum
255 
256  def __init__(self, enum=None):
257  self._enum_enum = enum
258 
259  @property
260  type_name = property
261 
262  def type_name(self):
263  return self._enum_enum.__name__ if self._enum_enum else None
264 
265  def get_converter(self, type_):
266  return EnumConverter(type_)
267 
268  def _convert(self, value, explicit_type=True):
269  try:
270  # This is compatible with the enum module in Python 3.4, its
271  # enum34 backport, and the older enum module. `self._enum[value]`
272  # wouldn't work with the old enum module.
273  return getattr(self._enum_enum, value)
274  except AttributeError:
275  members = self._get_members_get_members(self._enum_enum)
276  raise ValueError("%s does not have member '%s'. Available: %s"
277  % (self.type_nametype_nametype_nametype_nametype_name, value, seq2str(members)))
278 
279  def _get_members(self, enum):
280  try:
281  return list(enum.__members__)
282  except AttributeError: # old enum module
283  return [attr for attr in dir(enum) if not attr.startswith('_')]
284 
285 
286 @TypeConverter.register
288  type = type(None)
289 
290  def _convert(self, value, explicit_type=True):
291  return value
292 
293 
294 @TypeConverter.register
296  type = list
297  abc = abc.Sequence
298 
299  def _convert(self, value, explicit_type=True):
300  return self._literal_eval_literal_eval(value, list)
301 
302 
303 @TypeConverter.register
305  type = tuple
306 
307  def _convert(self, value, explicit_type=True):
308  return self._literal_eval_literal_eval(value, tuple)
309 
310 
311 @TypeConverter.register
313  type = dict
314  abc = abc.Mapping
315  type_name = 'dictionary'
316  aliases = ('dict', 'map')
317 
318  def _convert(self, value, explicit_type=True):
319  return self._literal_eval_literal_eval(value, dict)
320 
321 
322 @TypeConverter.register
324  type = set
325  abc = abc.Set
326 
327  def _convert(self, value, explicit_type=True):
328  return self._literal_eval_literal_eval(value, set)
329 
330 
331 @TypeConverter.register
333  type = frozenset
334 
335  def _convert(self, value, explicit_type=True):
336  # There are issues w/ literal_eval. See self._literal_eval for details.
337  if value == 'frozenset()' and not PY2:
338  return frozenset()
339  return frozenset(self._literal_eval_literal_eval(value, set))
def convert(self, name, value, explicit_type=True)
def _handle_error(self, name, value, error, explicit_type=True)
def convert_time(time, result_format='number', exclude_millis=False)
Converts between supported time formats.
Definition: DateTime.py:398
def convert_date(date, result_format='timestamp', exclude_millis=False, date_format=None)
Converts between supported date formats.
Definition: DateTime.py:377
def seq2str(sequence, quote="'", sep=', ', lastsep=' and ')
Returns sequence in format ‘'item 1’, 'item 2' and 'item 3'`.
Definition: misc.py:115