Esempio n. 1
0
 def test_is_union(self):
     self.assertTrue(is_union(Union[int, str]))
     self.assertFalse(is_union(int))
     self.assertFalse(is_union(List[str]))
     self.assertFalse(is_union(Class))
     self.assertFalse(is_union(Tuple[str, int, str]))
     self.assertFalse(is_union(Dict[str, str]))
     self.assertFalse(is_union(dict))
     self.assertFalse(is_union(list))
Esempio n. 2
0
 def hints(cls) -> List[Type]:
     """
     Return the allowed types for value v, removing the input_type hint
     """
     hints = cls.types()
     t = list(get_args(hints)) if is_union(hints) else [hints]
     if cls.input_type:
         t.remove(cls.input_type)
     return t
Esempio n. 3
0
def cast(typ: Type, v: Any, _coercion_allowed: Optional[bool] = None) -> Any:
    """
    Convert value v to type typ.  Raises TypeError if conversion is not possible.  Note that None and empty lists are
    treated as universal types

    :param typ: type to convert v to
    :param v: value to convert to type
    :param _coercion_allowed: True means type coercion is allowed.  False means only matching types work
    :return: instance of type
    """
    from funowl.base.fun_owl_choice import FunOwlChoice

    if v is None or v == []:  # Null and empty list are always allowed (a bit too permissive but...)
        return v

    # Union[...]
    if is_union(typ):
        for t in get_args(typ):
            if type(v) is t:
                return v
            elif _coercion_allowed is not False and isinstance_(v, t):
                return cast(t, v)
        raise TypeError(f"Type mismatch between {v} (type: {type(v)} and {typ}")

    # List[...]
    if is_iterable(typ):
        list_type = get_args(typ)[0]
        if isinstance(v, str) or not isinstance(v, Iterable):  # You can assign a singleton directly
            v = [v]
        return [cast(list_type, vi) for vi in v]

    if issubclass(type(v), typ):        # conversion is treated as idempotent (typ(typ(v)) = typ(v)
        return copy(v)

    if isinstance(typ, type) and issubclass(typ, FunOwlChoice):
        hints = typ.hints()
        pos_types = ', '.join([t.__name__ for t in hints])
        logging.debug(f"value: {v} (type: {type(v)}) testing against {typ}[{pos_types}]")
        for poss_type in hints:
            if issubclass(type(v), poss_type) or (_coercion_allowed is not False and isinstance(v, poss_type)):
                logging.debug(f"     Matches {poss_type.__name__}")
                if getattr(poss_type, '_parse_input', None):
                    return typ(poss_type(*poss_type._parse_input(v)))
                return typ(poss_type(v))
        logging.debug('     No match')

    # Determine whether v can be cooreced into type
    if _coercion_allowed is False or not isinstance_(v, typ):
        raise TypeError(f"value: {v} (type: {type(v)}) cannot be converted to {typ}")

    # Vanilla typing
    return typ(*(getattr(typ, '_parse_input', lambda e: e))(v))
Esempio n. 4
0
def remove_exclusions(typ: Union[Field, Type]) -> Union[List[Type], Type]:
    """
    Convert typ into an ordered list of possible types, removing any exclusions

    :param typ: Field, FunOwlChoice instance or Type definition
    :return: Ordered list of types with exclusions removed
    """
    from funowl.base.fun_owl_choice import FunOwlChoice

    if isinstance(typ, type) and issubclass(typ, FunOwlChoice):
        return typ.real_types()

    if isinstance_(typ, Field):
        # If it is a field, the actual type is Field.type.
        if is_union(typ.type):
            exclusions = typ.metadata.get('exclude', [])
            return [t for t in get_args(typ.type) if t not in exclusions]
        else:
            typ = typ.type
    return [typ]
Esempio n. 5
0
def cast(cast_to: Union[Type, Field],
         v: Any,
         _coercion_allowed: Optional[bool] = None) -> Any:
    """
    Convert value v to type cast_to.  Raises TypeError if conversion is not possible.  Note that None and empty lists are
    treated as universal types

    :param cast_to: Field, FunOwlChoice instance or Type definition we want to cast v to
    :param v: value to cast.  Note that None and empty lists are treated as always cast.
    :param _coercion_allowed: True means type coercion is allowed.  False means only matching types work
    :return: instance of cast_to
    """
    from funowl.base.fun_owl_choice import FunOwlChoice

    def cast_to_choice(choice: FunOwlChoice, v: Any) -> Any:
        """ Process a choice """
        hints = choice.real_types()
        for poss_type in hints:
            if issubclass(type(v), poss_type):
                return choice_match(poss_type)
        for poss_type in hints:
            if issubclass(type(v),
                          poss_type) or (_coercion_allowed is not False
                                         and isinstance(v, poss_type)):
                return choice_match(poss_type)
        logging.debug('     No match')

    def choice_match(matched_type: Type) -> Any:
        if hasattr(matched_type, '_parse_input'):
            rval = typ(matched_type(*matched_type._parse_input(v)))
        else:
            rval = typ(matched_type(v))
        return rval

    # TODO: this should be a parameterized type for return
    def do_cast(target_type: Type, target_value: Any) -> Any:
        return target_type(
            *(getattr(target_type, '_parse_input', lambda e: e))(target_value))

    # None and empty lists are universal types.  If already cast, we're done
    if v is None or v == [] or (isinstance_(cast_to, Field) and
                                type(v) is cast_to.type) or type(v) is cast_to:
        return v

    # Create an ordered list of target types -- these are the types IN cast_to
    type_list = remove_exclusions(cast_to)

    # If we already match the list, no coercion is necessary
    for typ in type_list:
        if type(v) is typ:
            return v

    # Iterate through the list to determine whether we can coerce v to any of the targets
    if _coercion_allowed is not False:
        for typ in type_list:
            # Note: This parallels the code in TypingHelper.isinstance_  -- it may be worth considering merging these
            # with a visitor idiom

            # Any / unrealized TypeVar is the identity function
            # TODO: not all TypeVar situations will work here
            if typ is Any or isinstance(typ, TypeVar):
                return do_cast(typ, v)

            elif isinstance_(typ, FunOwlChoice):
                return cast_to_choice(typ, v)

            elif is_union(typ):
                for t in get_args(typ):
                    if isinstance_(v, t):
                        if type(v) is t:
                            return v
                        else:
                            return do_cast(t, v)

            elif is_dict(typ):
                if isinstance(v, dict):
                    dict_args = get_args(typ)
                    return {
                        cast(dict_args[0], dk): cast(dict_args[1], dv)
                        for dk, dv in v.items()
                    } if dict_args else v

            elif is_list(typ):
                # casting x == [x]
                if isinstance(v, str) or not isinstance(v, Iterable):
                    v = [v]
                if isinstance(v, list) or isinstance(v, UserList):
                    list_type = get_args(typ)
                    return [cast(list_type[0], vi)
                            for vi in v] if list_type else v

            elif is_tuple(typ):
                if isinstance(v, tuple):
                    tuple_args = get_args(typ)
                    return tuple(
                        cast(tt, vt)
                        for tt, vt in zip(tuple_args, v)) if tuple_args else v

            elif is_set(typ):
                if isinstance_(v, set):
                    set_type = get_args(typ)
                    return set(cast(set_type[0], e)
                               for e in v) if set_type else v

            elif isinstance_(v, typ):
                return do_cast(typ, v if issubclass(typ, str) else copy(v))

    raise TypeError(
        f"Type mismatch between {v} (type: {type(v)} and {type_list}")