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 to_json_value(self): dct = {} dct['_tname'] = self.__class__.__name__ for f in dataclasses.fields(self): f_type = f.type value = getattr(self, f.name) if (isinstance(f_type, type) and issubclass(f_type, CompositeConfigType) and value is not None): value = value.to_json_value() elif typing_inspect.is_generic_type(f_type): value = list(value) dct[f.name] = value return dct
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))