def _init_parametric_base(cls) -> None: """Initialize a direct subclass of ParametricType""" # Direct subclasses of ParametricType must declare # ClassVar attributes corresponding to the Generic type vars. # For example: # class P(ParametricType, Generic[T, V]): # t: ClassVar[Type[T]] # v: ClassVar[Type[V]] params = getattr(cls, '__parameters__', None) if not params: raise TypeError(f'{cls} must be declared as Generic') mod = sys.modules[cls.__module__] annos = get_type_hints(cls, mod.__dict__) param_map = {} for attr, t in annos.items(): if not typing_inspect.is_classvar(t): continue args = typing_inspect.get_args(t) # ClassVar constructor should have the check, but be extra safe. assert len(args) == 1 arg = args[0] if typing_inspect.get_origin(arg) != type: continue arg_args = typing_inspect.get_args(arg) # Likewise, rely on Type checking its stuff in the constructor assert len(arg_args) == 1 if not typing_inspect.is_typevar(arg_args[0]): continue if arg_args[0] in params: param_map[arg_args[0]] = attr for param in params: if param not in param_map: raise TypeError(f'{cls.__name__}: missing ClassVar for' f' generic parameter {param}') cls._type_param_map = param_map
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_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, List, collections.abc.Sequence): f_default = list elif ot in (set, Set): f_default = set elif ot in (frozenset, FrozenSet): f_default = frozenset elif ot in (dict, 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 f_type is Any: f_type = object 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}') # Make sure that we can actually construct an empty # version of this type before we decide it is the default. try: f_type() except TypeError: pass else: f_default = f_type return f_default
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_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_real(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, List, collections.abc.Sequence): _check_container_type(type_, value, raise_error, list) elif ot in (set, Set): _check_container_type(type_, value, raise_error, set) elif ot in (frozenset, FrozenSet): _check_container_type(type_, value, raise_error, frozenset) elif ot in (dict, Dict): _check_mapping_type(type_, value, raise_error, dict) elif ot is not None: raise TypeError(f'unsupported typing type: {type_!r}') elif type_ is not Any: if value is not None and not isinstance(value, type_): raise_error(type_.__name__, value)
def _init_parametric_user(cls) -> None: """Initialize an indirect descendant of ParametricType.""" # For ParametricType grandchildren we have to deal with possible # TypeVar remapping and generally check for type sanity. ob = getattr(cls, '__orig_bases__', ()) generic_params: list[type] = [] for b in ob: if (isinstance(b, type) and not isinstance(b, GenericAlias) and issubclass(b, ParametricType) and b is not ParametricType): raise TypeError( f'{cls.__name__}: missing one or more type arguments for' f' base {b.__name__!r}') if not typing_inspect.is_generic_type(b): continue org = typing_inspect.get_origin(b) if not isinstance(org, type): continue if not issubclass(org, ParametricType): generic_params.extend(getattr(b, '__parameters__', ())) continue base_params = getattr(org, '__parameters__', ()) base_non_type_params = getattr(org, '_non_type_params', {}) args = typing_inspect.get_args(b) expected = len(base_params) if len(args) != expected: raise TypeError( f'{b.__name__} expects {expected} type arguments' f' got {len(args)}') base_map = dict(cls._type_param_map) subclass_map = {} for i, arg in enumerate(args): if i in base_non_type_params: continue if not typing_inspect.is_typevar(arg): raise TypeError(f'{b.__name__} expects all arguments to be' f' TypeVars') base_typevar = base_params[i] attr = base_map.get(base_typevar) if attr is not None: subclass_map[arg] = attr if len(subclass_map) != len(base_map): raise TypeError( f'{cls.__name__}: missing one or more type arguments for' f' base {org.__name__!r}') cls._type_param_map = subclass_map cls._non_type_params = { i: p for i, p in enumerate(generic_params) if p not in cls._type_param_map }
def _is_optional(type_): return (typing_inspect.is_union_type(type_) and type(None) in typing_inspect.get_args(type_, evaluate=True))
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.QualName.from_string(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.QualName.from_string(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))