def parse_dict( ctor: _CtorType, node: yaml.MappingNode, keys: Mapping[str, Any], res_type: Type[_AstType], *, ret_name: Optional[str] = None, extra: Optional[Mapping[str, Union[str, LocalPath]]] = None, preprocess: Optional[Callable[ [_CtorType, yaml.MappingNode, Dict[str, Any]], Dict[str, Any]]] = None, find_res_type: Optional[ Callable[[_CtorType, yaml.MappingNode, Type[_AstType], Dict[str, Any]], Type[_AstType], ]] = None, ) -> _AstType: if extra is None: extra = {} if ret_name is None: ret_name = res_type.__name__ if not isinstance(node, yaml.MappingNode): node_id = node.id raise ConstructorError( None, None, f"expected a mapping node, but found '{node_id}'", node.start_mark, ) node_start = mark2pos(node.start_mark) node_end = mark2pos(node.end_mark) data = dict(extra) for k, v in node.value: key = ctor.construct_object(k) # type: ignore[no-untyped-call] if key not in keys: raise ConstructorError( f"while constructing a '{ret_name}'", node.start_mark, f"unexpected key '{key}'", k.start_mark, ) item_ctor: Any = keys[key] tmp: Any value: Any if item_ctor is None: # Get constructor from tag value = ctor.construct_object(v) # type: ignore[no-untyped-call] elif isinstance(item_ctor, type) and issubclass(item_ctor, enum.Enum): tmp = str( ctor.construct_object(v)) # type: ignore[no-untyped-call] value = item_ctor(tmp) elif isinstance(item_ctor, ast.Base): assert isinstance( v, ast.Base), f"[{type(v)}] '{v}' should be ast.Base derived" value = v elif isinstance(item_ctor, SimpleCompound): value = item_ctor.construct(ctor, v) elif isinstance(item_ctor, type) and issubclass(item_ctor, Expr): tmp = ctor.construct_object(v) # type: ignore[no-untyped-call] value = item_ctor(mark2pos(v.start_mark), mark2pos(v.end_mark), tmp) else: raise ConstructorError( f"while constructing a '{ret_name}'", node.start_mark, f"unexpected value tag '{v.tag}' for key '{key}[{item_ctor}]'", k.start_mark, ) data[key] = value if preprocess is not None: data = preprocess(ctor, node, dict(data)) if find_res_type is not None: res_type = find_res_type(ctor, node, res_type, dict(data)) ret_name = res_type.__name__ optional_fields: Dict[str, Any] = {} found_fields = data.keys() | {"_start", "_end"} field_names = set() for f in dataclasses.fields(res_type): field_names.add(f.name) if f.name == "_specified_fields": optional_fields[f.name] = set(data.keys()) elif f.name not in found_fields: key = f.name item_ctor = keys[key] if f.default is not dataclasses.MISSING: optional_fields[f.name] = f.default elif (item_ctor is None or isinstance(item_ctor, SimpleCompound) or isinstance(item_ctor, ast.Base)): if f.metadata.get("allow_none", False): optional_fields[f.name] = None else: raise ConstructorError( f"while constructing a '{ret_name}', " f"missing mandatory key '{f.name}'", node.start_mark, ) elif isinstance(item_ctor, type) and issubclass(item_ctor, Expr): default_expr = f.metadata.get("default_expr", None) if default_expr: fake_node = dataclasses.replace(node_start, line=0, col=0) optional_fields[f.name] = item_ctor( fake_node, fake_node, default_expr) elif not item_ctor.allow_none: raise ConstructorError( f"while constructing a '{ret_name}', " f"missing mandatory key '{f.name}'", node.start_mark, ) else: optional_fields[f.name] = item_ctor( node_start, node_end, None) elif isinstance(item_ctor, type) and issubclass( item_ctor, enum.Enum): if f.metadata.get("allow_none", False): optional_fields[f.name] = None else: raise ConstructorError( f"while constructing a '{ret_name}', " f"unexpected '{f.name}' constructor type '{item_ctor!r}'", node.start_mark, ) actual_names = found_fields | optional_fields.keys() missing_names = field_names - actual_names sep = "," if missing_names: raise ConstructorError( f"while constructing a '{ret_name}', " f"missing fields '{sep.join(missing_names)}'", node.start_mark, ) unknown_names = actual_names - field_names if unknown_names: raise ConstructorError( f"while constructing a '{ret_name}', " f"unexpected fields '{sep.join(missing_names)}'", node.start_mark, ) return res_type( _start=node_start, _end=node_end, **data, **optional_fields, )
def check_extra(node: yaml.Node, dct: Dict[str, Any], ret: Dict[str, Any], name: str) -> None: diff = dct.keys() - ret.keys() if diff: raise ConstructorError(f"{','.join(diff)} are not allowed in {name}", node.start_mark)
def _invalid_node(self, node, expected): raise ConstructorError( None, None, 'expected {0}, but found {1}'.format(expected, node.id), node.start_mark)