Source code for robot.running.arguments.typeconverters

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from ast import literal_eval
from collections import OrderedDict
try:
    from collections import abc
except ImportError:    # Python 2
    import collections as abc
from datetime import datetime, date, timedelta
from decimal import InvalidOperation, Decimal
try:
    from enum import Enum
except ImportError:    # Standard in Py 3.4+ but can be separately installed
[docs] class Enum(object): pass
from numbers import Integral, Real from robot.libraries.DateTime import convert_date, convert_time from robot.utils import (FALSE_STRINGS, IRONPYTHON, TRUE_STRINGS, PY_VERSION, PY2, eq, seq2str, type_name, unicode)
[docs]class TypeConverter(object): type = None abc = None aliases = () convert_none = True _converters = OrderedDict() _type_aliases = {} @property def type_name(self): return self.type.__name__.lower()
[docs] @classmethod def register(cls, converter_class): converter = converter_class() cls._converters[converter.type] = converter for name in (converter.type_name,) + converter.aliases: if name is not None: cls._type_aliases[name.lower()] = converter.type return converter_class
[docs] @classmethod def converter_for(cls, type_): # Types defined in the typing module in Python 3.7+. For details see # https://bugs.python.org/issue34568 if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'): type_ = type_.__origin__ if isinstance(type_, (str, unicode)): try: type_ = cls._type_aliases[type_.lower()] except KeyError: return None if not isinstance(type_, type) or issubclass(type_, unicode): return None if type_ in cls._converters: return cls._converters[type_] for converter in cls._converters.values(): if converter.handles(type_): return converter.get_converter(type_) return None
[docs] def handles(self, type_): return (issubclass(type_, self.type) or self.abc and issubclass(type_, self.abc))
[docs] def get_converter(self, type_): return self
[docs] def convert(self, name, value, explicit_type=True): if self.convert_none and value.upper() == 'NONE': return None try: return self._convert(value, explicit_type) except ValueError as error: return self._handle_error(name, value, error, explicit_type)
def _convert(self, value, explicit_type=True): raise NotImplementedError def _handle_error(self, name, value, error, explicit_type=True): if not explicit_type: return value ending = u': %s' % error if error.args else '.' raise ValueError("Argument '%s' got value '%s' that cannot be " "converted to %s%s" % (name, value, self.type_name, ending)) def _literal_eval(self, value, expected): # ast.literal_eval has some issues with sets: if expected is set: # On Python 2 it doesn't handle sets at all. if PY2: raise ValueError('Sets are not supported on Python 2.') # There is no way to define an empty set. if value == 'set()': return set() try: value = literal_eval(value) except (ValueError, SyntaxError): # Original errors aren't too informative in these cases. raise ValueError('Invalid expression.') except TypeError as err: raise ValueError('Evaluating expression failed: %s' % err) if not isinstance(value, expected): raise ValueError('Value is %s, not %s.' % (type_name(value), expected.__name__)) return value
[docs]@TypeConverter.register class BooleanConverter(TypeConverter): type = bool type_name = 'boolean' aliases = ('bool',) def _convert(self, value, explicit_type=True): upper = value.upper() if upper in TRUE_STRINGS: return True if upper in FALSE_STRINGS: return False return value
[docs]@TypeConverter.register class IntegerConverter(TypeConverter): type = int abc = Integral type_name = 'integer' aliases = ('int', 'long') def _convert(self, value, explicit_type=True): try: return int(value) except ValueError: if not explicit_type: try: return float(value) except ValueError: pass raise ValueError
[docs]@TypeConverter.register class FloatConverter(TypeConverter): type = float abc = Real aliases = ('double',) def _convert(self, value, explicit_type=True): try: return float(value) except ValueError: raise ValueError
[docs]@TypeConverter.register class DecimalConverter(TypeConverter): type = Decimal def _convert(self, value, explicit_type=True): try: return Decimal(value) except InvalidOperation: # With Python 3 error messages by decimal module are not very # useful and cannot be included in our error messages: # https://bugs.python.org/issue26208 raise ValueError
[docs]@TypeConverter.register class BytesConverter(TypeConverter): type = bytes abc = getattr(abc, 'ByteString', None) # ByteString is new in Python 3 type_name = 'bytes' # Needed on Python 2 convert_none = False def _convert(self, value, explicit_type=True): if PY2 and not explicit_type: return value try: value = value.encode('latin-1') except UnicodeEncodeError as err: raise ValueError("Character '%s' cannot be mapped to a byte." % value[err.start:err.start+1]) return value if not IRONPYTHON else bytes(value)
[docs]@TypeConverter.register class ByteArrayConverter(TypeConverter): type = bytearray convert_none = False def _convert(self, value, explicit_type=True): try: return bytearray(value, 'latin-1') except UnicodeEncodeError as err: raise ValueError("Character '%s' cannot be mapped to a byte." % value[err.start:err.start+1])
[docs]@TypeConverter.register class DateTimeConverter(TypeConverter): type = datetime def _convert(self, value, explicit_type=True): return convert_date(value, result_format='datetime')
[docs]@TypeConverter.register class DateConverter(TypeConverter): type = date def _convert(self, value, explicit_type=True): dt = convert_date(value, result_format='datetime') if dt.hour or dt.minute or dt.second or dt.microsecond: raise ValueError("Value is datetime, not date.") return dt.date()
[docs]@TypeConverter.register class TimeDeltaConverter(TypeConverter): type = timedelta def _convert(self, value, explicit_type=True): return convert_time(value, result_format='timedelta')
[docs]@TypeConverter.register class EnumConverter(TypeConverter): type = Enum def __init__(self, enum=None): self._enum = enum @property def type_name(self): return self._enum.__name__ if self._enum else None
[docs] def get_converter(self, type_): return EnumConverter(type_)
def _convert(self, value, explicit_type=True): try: # This is compatible with the enum module in Python 3.4, its # enum34 backport, and the older enum module. `self._enum[value]` # wouldn't work with the old enum module. return getattr(self._enum, value) except AttributeError: members = sorted(self._get_members(self._enum)) matches = [m for m in members if eq(m, value, ignore='_')] if not matches: raise ValueError("%s does not have member '%s'. Available: %s" % (self.type_name, value, seq2str(members))) if len(matches) > 1: raise ValueError("%s has multiple members matching '%s'. Available: %s" % (self.type_name, value, seq2str(matches))) return getattr(self._enum, matches[0]) def _get_members(self, enum): try: return list(enum.__members__) except AttributeError: # old enum module return [attr for attr in dir(enum) if not attr.startswith('_')]
[docs]@TypeConverter.register class NoneConverter(TypeConverter): type = type(None) def _convert(self, value, explicit_type=True): return value
[docs]@TypeConverter.register class ListConverter(TypeConverter): type = list abc = abc.Sequence def _convert(self, value, explicit_type=True): return self._literal_eval(value, list)
[docs]@TypeConverter.register class TupleConverter(TypeConverter): type = tuple def _convert(self, value, explicit_type=True): return self._literal_eval(value, tuple)
[docs]@TypeConverter.register class DictionaryConverter(TypeConverter): type = dict abc = abc.Mapping type_name = 'dictionary' aliases = ('dict', 'map') def _convert(self, value, explicit_type=True): return self._literal_eval(value, dict)
[docs]@TypeConverter.register class SetConverter(TypeConverter): type = set abc = abc.Set def _convert(self, value, explicit_type=True): return self._literal_eval(value, set)
[docs]@TypeConverter.register class FrozenSetConverter(TypeConverter): type = frozenset def _convert(self, value, explicit_type=True): # There are issues w/ literal_eval. See self._literal_eval for details. if value == 'frozenset()' and not PY2: return frozenset() return frozenset(self._literal_eval(value, set))