def test_args(self):
     T = TypeVar('T')
     self.assertEqual(get_args(Union[int, Tuple[T, int]][str]),
                      (int, (Tuple, str, int)))
     self.assertEqual(get_args(Union[int, Union[T, int], str][int]),
                      (int, str))
     self.assertEqual(get_args(int), ())
 def test_args_evaluated(self):
     T = TypeVar('T')
     self.assertEqual(
         get_args(Union[int, Tuple[T, int]][str], evaluate=True),
         (int, Tuple[str, int]))
     self.assertEqual(
         get_args(Dict[int, Tuple[T, T]][Optional[int]], evaluate=True),
         (int, Tuple[Optional[int], Optional[int]]))
     self.assertEqual(get_args(Callable[[], T][int], evaluate=True), (
         [],
         int,
     ))
Exemple #3
0
def _check_container_type(type_, value, raise_error, instance_type):
    if not isinstance(value, instance_type):
        raise_error(str(type_), value)

    type_args = typing_inspect.get_args(type_, evaluate=True)
    eltype = type_args[0]
    for el in value:
        _check_type(eltype, el, raise_error)
Exemple #4
0
def _check_mapping_type(type_, value, raise_error, instance_type):
    if not isinstance(value, instance_type):
        raise_error(str(type_), value)

    type_args = typing_inspect.get_args(type_, evaluate=True)
    ktype = type_args[0]
    vtype = type_args[1]
    for k, v in value.items():
        _check_type(ktype, k, raise_error)
        if not k and not _is_optional(ktype):
            raise RuntimeError('empty key in map')
        _check_type(vtype, v, raise_error)
Exemple #5
0
def _check_annotation(f_type, f_fullname, f_default):
    if typing_inspect.is_tuple_type(f_type):
        if f_default is not None:
            raise RuntimeError(
                f'invalid type annotation on {f_fullname}: '
                f'default is defined for tuple type')

        f_default = tuple

    elif typing_inspect.is_union_type(f_type):
        for t in typing_inspect.get_args(f_type, evaluate=True):
            _check_annotation(t, f_fullname, f_default)

    elif typing_inspect.is_generic_type(f_type):
        if f_default is not None:
            raise RuntimeError(
                f'invalid type annotation on {f_fullname}: '
                f'default is defined for container type '
                f'{f_type!r}')

        ot = typing_inspect.get_origin(f_type)
        if ot is None:
            raise RuntimeError(
                f'cannot find origin of a generic type {f_type}')

        if ot in (list, typing.List, collections.abc.Sequence):
            f_default = list
        elif ot in (set, typing.Set):
            f_default = set
        elif ot in (frozenset, typing.FrozenSet):
            f_default = frozenset
        elif ot in (dict, typing.Dict):
            f_default = dict
        else:
            raise RuntimeError(
                f'invalid type annotation on {f_fullname}: '
                f'{f_type!r} is not supported')

    elif f_type is not None:
        if not isinstance(f_type, type):
            raise RuntimeError(
                f'invalid type annotation on {f_fullname}: '
                f'{f_type!r} is not a type')

        if typeutils.is_container_type(f_type):
            if f_default is not None:
                raise RuntimeError(
                    f'invalid type annotation on {f_fullname}: '
                    f'default is defined for container type '
                    f'{f_type!r}')
            f_default = f_type

    return f_default
Exemple #6
0
def _check_tuple_type(type_, value, raise_error, instance_type):
    if not isinstance(value, instance_type):
        raise_error(str(type_), value)

    eltype = None
    ellipsis = False
    type_args = typing_inspect.get_args(type_, evaluate=True)

    for i, el in enumerate(value):
        if not ellipsis:
            new_eltype = type_args[i]
            if new_eltype is Ellipsis:
                ellipsis = True
            else:
                eltype = new_eltype
        if eltype is not None:
            _check_type(eltype, el, raise_error)
Exemple #7
0
def _check_type(type_, value, raise_error):
    if type_ is None:
        return

    if typing_inspect.is_union_type(type_):
        for t in typing_inspect.get_args(type_, evaluate=True):
            try:
                _check_type(t, value, raise_error)
            except TypeError:
                pass
            else:
                break
        else:
            raise_error(str(type_), value)

    elif typing_inspect.is_tuple_type(type_):
        _check_tuple_type(type_, value, raise_error, tuple)

    elif typing_inspect.is_generic_type(type_):
        ot = typing_inspect.get_origin(type_)

        if ot in (list, typing.List):
            _check_container_type(type_, value, raise_error, list)

        elif ot in (set, typing.Set):
            _check_container_type(type_, value, raise_error, set)

        elif ot in (frozenset, typing.FrozenSet):
            _check_container_type(type_, value, raise_error, frozenset)

        elif ot in (dict, typing.Dict):
            _check_mapping_type(type_, value, raise_error, dict)

        elif ot is not None:
            raise TypeError(f'unsupported typing type: {type_!r}')

    else:
        if value is not None and not isinstance(value, type_):
            raise_error(type_.__name__, value)
Exemple #8
0
    def from_pyvalue(cls, data, *, allow_missing=False):
        if not isinstance(data, dict):
            raise cls._err(f'expected a dict value, got {type(data)!r}')

        spec = config.get_settings()

        data = dict(data)
        tname = data.pop('_tname', None)
        if tname is not None:
            if '::' in tname:
                tname = s_name.Name(tname).name
            cls = spec.get_type_by_name(tname)

        fields = {f.name: f for f in dataclasses.fields(cls)}

        items = {}
        inv_keys = []
        for fieldname, value in data.items():
            field = fields.get(fieldname)
            if field is None:
                if value is None:
                    # This may happen when data is produced by
                    # a polymorphic config query.
                    pass
                else:
                    inv_keys.append(fieldname)

                continue

            f_type = field.type

            if value is None:
                # Config queries return empty pointer values as None.
                continue

            if typing_inspect.is_generic_type(f_type):
                container = typing_inspect.get_origin(f_type)
                if container not in (frozenset, list):
                    raise RuntimeError(f'invalid type annotation on '
                                       f'{cls.__name__}.{fieldname}: '
                                       f'{f_type!r} is not supported')

                eltype = typing_inspect.get_args(f_type, evaluate=True)[0]
                if isinstance(value, eltype):
                    value = container((value, ))
                elif (typeutils.is_container(value)
                      and all(isinstance(v, eltype) for v in value)):
                    value = container(value)
                else:
                    raise cls._err(
                        f'invalid {fieldname!r} field value: expecting '
                        f'{eltype.__name__} or a list thereof, but got '
                        f'{type(value).__name__}')

            elif (issubclass(f_type, CompositeConfigType)
                  and isinstance(value, dict)):

                tname = value.get('_tname', None)
                if tname is not None:
                    if '::' in tname:
                        tname = s_name.Name(tname).name
                    actual_f_type = spec.get_type_by_name(tname)
                else:
                    actual_f_type = f_type
                    value['_tname'] = f_type.__name__

                value = actual_f_type.from_pyvalue(value)

            elif not isinstance(value, f_type):
                raise cls._err(
                    f'invalid {fieldname!r} field value: expecting '
                    f'{f_type.__name__}, but got {type(value).__name__}')

            items[fieldname] = value

        if inv_keys:
            inv_keys = ', '.join(repr(r) for r in inv_keys)
            raise cls._err(f'unknown fields: {inv_keys}')

        for fieldname, field in fields.items():
            if fieldname not in items and field.default is dataclasses.MISSING:
                if allow_missing:
                    items[fieldname] = None
                else:
                    raise cls._err(f'missing required field: {fieldname!r}')

        try:
            return cls(**items)
        except (TypeError, ValueError) as ex:
            raise cls._err(str(ex))
Exemple #9
0
def _is_optional(type_):
    return (typing_inspect.is_union_type(type_)
            and type(None) in typing_inspect.get_args(type_, evaluate=True))