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, ))
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)
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)
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
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)
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)
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))
def _is_optional(type_): return (typing_inspect.is_union_type(type_) and type(None) in typing_inspect.get_args(type_, evaluate=True))