class ValueKind(Enum):
    VALUE = 0
    MANDATORY_MISSING = 1
    INTERPOLATION = 2 def _is_missing_literal(value: Any) -> bool:
    # Uses literal '???' instead of the MISSING const for performance reasons.
    return isinstance(value, str) and value == "???" def _is_none(
    value: Any, resolve: bool = False, throw_on_resolution_failure: bool = True
) -> bool:
    from omegaconf import Node

    if not isinstance(value, Node):
        return value is None

    if resolve:
        value = value._maybe_dereference_node(
            throw_on_resolution_failure=throw_on_resolution_failure
        )
        if not throw_on_resolution_failure and value is None:
            # Resolution failure: consider that it is *not* None.
            return False
        assert isinstance(value, Node)

    return value._is_none() def _resolve_optional(type_: Any) -> Tuple[bool, Any]:
    """Check whether `type_` is equivalent to `typing.Optional[T]` for some T."""
    if getattr(type_, "__origin__", None) is Union:
        args = type_.__args__
        if len(args) == 2 and args[1] == type(None):  # noqa E721
            return True, args[0]
    if type_ is Any:
        return True, Any

    return False, type_ def format_and_raise(
    node: Any,
    key: Any,
    value: Any,
    msg: str,
    cause: Exception,
    type_override: Any = None,
) -> None:
    from omegaconf import OmegaConf
    from omegaconf.base import Node

    if isinstance(cause, AssertionError):
        raise

    if isinstance(cause, OmegaConfBaseException) and cause._initialized:
        ex = cause
        if type_override is not None:
            ex = type_override(str(cause))
            ex.__dict__ = copy.deepcopy(cause.__dict__)
        _raise(ex, cause)

    object_type: Optional[Type[Any]]
    object_type_str: Optional[str] = None
    ref_type: Optional[Type[Any]]
    ref_type_str: Optional[str]

    child_node: Optional[Node] = None
    if node is None:
        full_key = key if key is not None else ""
        object_type = None
        ref_type = None
        ref_type_str = None
    else:
        if key is not None and not node._is_none():
            child_node = node._get_node(key, validate_access=False)

        try:
            full_key = node._get_full_key(key=key)
        except Exception as exc:
            # Since we are handling an exception, raising a different one here would
            # be misleading. Instead, we display it in the key.
            full_key = f"<unresolvable due to {type(exc).__name__}: {exc}>"

        object_type = OmegaConf.get_type(node)
        object_type_str = type_str(object_type)

        ref_type = get_ref_type(node)
        ref_type_str = type_str(ref_type)

    msg = string.Template(msg).safe_substitute(
        REF_TYPE=ref_type_str,
        OBJECT_TYPE=object_type_str,
        KEY=key,
        FULL_KEY=full_key,
        VALUE=value,
        VALUE_TYPE=type_str(type(value), include_module_name=True),
        KEY_TYPE=f"{type(key).__name__}",
    )

    if ref_type not in (None, Any):
        template = dedent(
            """\
            $MSG
                full_key: $FULL_KEY
                reference_type=$REF_TYPE
                object_type=$OBJECT_TYPE"""
        )
    else:
        template = dedent(
            """\
            $MSG
                full_key: $FULL_KEY
                object_type=$OBJECT_TYPE"""
        )
    s = string.Template(template=template)

    message = s.substitute(
        REF_TYPE=ref_type_str, OBJECT_TYPE=object_type_str, MSG=msg, FULL_KEY=full_key
    )
    exception_type = type(cause) if type_override is None else type_override
    if exception_type == TypeError:
        exception_type = ConfigTypeError
    elif exception_type == IndexError:
        exception_type = ConfigIndexError

    ex = exception_type(f"{message}")
    if issubclass(exception_type, OmegaConfBaseException):
        ex._initialized = True
        ex.msg = message
        ex.parent_node = node
        ex.child_node = child_node
        ex.key = key
        ex.full_key = full_key
        ex.value = value
        ex.object_type = object_type
        ex.object_type_str = object_type_str
        ex.ref_type = ref_type
        ex.ref_type_str = ref_type_str

    _raise(ex, cause) def get_value_kind(
    value: Any, strict_interpolation_validation: bool = False
) -> ValueKind:
    """
    Determine the kind of a value
    Examples:
    VALUE: "10", "20", True
    MANDATORY_MISSING: "???"
    INTERPOLATION: "${foo.bar}", "${foo.${bar}}", "${foo:bar}", "[${foo}, ${bar}]",
                   "ftp://${host}/path", "${foo:${bar}, [true], {'baz': ${baz}}}"

    :param value: Input to classify.
    :param strict_interpolation_validation: If `True`, then when `value` is a string
        containing "${", it is parsed to validate the interpolation syntax. If `False`,
        this parsing step is skipped: this is more efficient, but will not detect errors.
    """

    if _is_missing_value(value):
        return ValueKind.MANDATORY_MISSING

    value = _get_value(value)

    # We identify potential interpolations by the presence of "${" in the string.
    # Note that escaped interpolations (ex: "esc: \${bar}") are identified as
    # interpolations: this is intended, since they must be processed as interpolations
    # for the string to be properly un-escaped.
    # Keep in mind that invalid interpolations will only be detected when
    # `strict_interpolation_validation` is True.
    if isinstance(value, str) and "${" in value:
        if strict_interpolation_validation:
            # First try the cheap regex matching that detects common interpolations.
            if SIMPLE_INTERPOLATION_PATTERN.match(value) is None:
                # If no match, do the more expensive grammar parsing to detect errors.
                parse(value)
        return ValueKind.INTERPOLATION
    else:
        return ValueKind.VALUE def is_int(st: str) -> bool:
    try:
        int(st)
        return True
    except ValueError:
        return False def is_primitive_list(obj: Any) -> bool:
    from .base import Container

    return not isinstance(obj, Container) and isinstance(obj, (list, tuple)) def is_structured_config(obj: Any) -> bool:
    return is_attr_class(obj) or is_dataclass(obj) def type_str(t: Any, include_module_name: bool = False) -> str:
    is_optional, t = _resolve_optional(t)
    if t is None:
        return type(t).__name__
    if t is Any:
        return "Any"
    if t is ...:
        return "..."

    if sys.version_info < (3, 7, 0):  # pragma: no cover
        # Python 3.6
        if hasattr(t, "__name__"):
            name = str(t.__name__)
        else:
            if t.__origin__ is not None:
                name = type_str(t.__origin__)
            else:
                name = str(t)
                if name.startswith("typing."):
                    name = name[len("typing.") :]
    else:  # pragma: no cover
        # Python >= 3.7
        if hasattr(t, "__name__"):
            name = str(t.__name__)
        else:
            if t._name is None:
                if t.__origin__ is not None:
                    name = type_str(
                        t.__origin__, include_module_name=include_module_name
                    )
            else:
                name = str(t._name)

    args = getattr(t, "__args__", None)
    if args is not None:
        args = ", ".join(
            [type_str(t, include_module_name=include_module_name) for t in t.__args__]
        )
        ret = f"{name}[{args}]"
    else:
        ret = name
    if include_module_name:
        if (
            hasattr(t, "__module__")
            and t.__module__ != "builtins"
            and t.__module__ != "typing"
            and not t.__module__.startswith("omegaconf.")
        ):
            module_prefix = t.__module__ + "."
        else:
            module_prefix = ""
        ret = module_prefix + ret
    if is_optional:
        return f"Optional[{ret}]"
    else:
        return ret class Container(Node):
    """
    Container tagging interface
    """

    _metadata: ContainerMetadata

    def _get_node(
        self,
        key: Any,
        validate_access: bool = True,
        validate_key: bool = True,
        throw_on_missing_value: bool = False,
        throw_on_missing_key: bool = False,
    ) -> Union[Optional[Node], List[Optional[Node]]]:
        ...

    @abstractmethod
    def __delitem__(self, key: Any) -> None:
        ...

    @abstractmethod
    def __setitem__(self, key: Any, value: Any) -> None:
        ...

    @abstractmethod
    def __iter__(self) -> Iterator[Any]:
        ...

    @abstractmethod
    def __getitem__(self, key_or_index: Any) -> Any:
        ...

    def __copy__(self) -> Any:
        # real shallow copy is impossible because of the reference to the parent.
        return copy.deepcopy(self)

    def _resolve_key_and_root(self, key: str) -> Tuple["Container", str]:
        orig = key
        if not key.startswith("."):
            return self._get_root(), key
        else:
            root: Optional[Container] = self
            assert key.startswith(".")
            while True:
                assert root is not None
                key = key[1:]
                if not key.startswith("."):
                    break
                root = root._get_parent()
                if root is None:
                    raise ConfigKeyError(f"Error resolving key '{orig}'")

            return root, key

    def _select_impl(
        self,
        key: str,
        throw_on_missing: bool,
        throw_on_resolution_failure: bool,
        memo: Optional[Set[int]] = None,
    ) -> Tuple[Optional["Container"], Optional[str], Optional[Node]]:
        """
        Select a value using dot separated key sequence
        """
        from .omegaconf import _select_one

        if key == "":
            return self, "", self

        split = split_key(key)
        root: Optional[Container] = self
        for i in range(len(split) - 1):
            if root is None:
                break

            k = split[i]
            ret, _ = _select_one(
                c=root,
                key=k,
                throw_on_missing=throw_on_missing,
                throw_on_type_error=throw_on_resolution_failure,
            )
            if isinstance(ret, Node):
                ret = ret._maybe_dereference_node(
                    throw_on_resolution_failure=throw_on_resolution_failure,
                    memo=memo,
                )

            if ret is not None and not isinstance(ret, Container):
                parent_key = ".".join(split[0 : i + 1])
                child_key = split[i + 1]
                raise ConfigTypeError(
                    f"Error trying to access {key}: node `{parent_key}` "
                    f"is not a container and thus cannot contain `{child_key}`"
                )
            root = ret

        if root is None:
            return None, None, None

        last_key = split[-1]
        value, _ = _select_one(
            c=root,
            key=last_key,
            throw_on_missing=throw_on_missing,
            throw_on_type_error=throw_on_resolution_failure,
        )
        if value is None:
            return root, last_key, None

        if memo is not None:
            vid = id(value)
            if vid in memo:
                raise InterpolationResolutionError("Recursive interpolation detected")
            # push to memo "stack"
            memo.add(vid)

        try:
            value = root._maybe_resolve_interpolation(
                parent=root,
                key=last_key,
                value=value,
                throw_on_resolution_failure=throw_on_resolution_failure,
                memo=memo,
            )
        finally:
            if memo is not None:
                # pop from memo "stack"
                memo.remove(vid)

        return root, last_key, value

    def _resolve_interpolation_from_parse_tree(
        self,
        parent: Optional["Container"],
        value: "Node",
        key: Any,
        parse_tree: OmegaConfGrammarParser.ConfigValueContext,
        throw_on_resolution_failure: bool,
        memo: Optional[Set[int]],
    ) -> Optional["Node"]:
        """
        Resolve an interpolation.

        This happens in two steps:
            1. The parse tree is visited, which outputs either a `Node` (e.g.,
               for node interpolations "${foo}"), a string (e.g., for string
               interpolations "hello ${name}", or any other arbitrary value
               (e.g., or custom interpolations "${foo:bar}").
            2. This output is potentially validated and converted when the node
               being resolved (`value`) is typed.

        If an error occurs in one of the above steps, an `InterpolationResolutionError`
        (or a subclass of it) is raised, *unless* `throw_on_resolution_failure` is set
        to `False` (in which case the return value is `None`).

        :param parent: Parent of the node being resolved.
        :param value: Node being resolved.
        :param key: The associated key in the parent.
        :param parse_tree: The parse tree as obtained from `grammar_parser.parse()`.
        :param throw_on_resolution_failure: If `False`, then exceptions raised during
            the resolution of the interpolation are silenced, and instead `None` is
            returned.

        :return: A `Node` that contains the interpolation result. This may be an existing
            node in the config (in the case of a node interpolation "${foo}"), or a new
            node that is created to wrap the interpolated value. It is `None` if and only if
            `throw_on_resolution_failure` is `False` and an error occurs during resolution.
        """

        try:
            resolved = self.resolve_parse_tree(
                parse_tree=parse_tree, node=value, key=key, parent=parent, memo=memo
            )
        except InterpolationResolutionError:
            if throw_on_resolution_failure:
                raise
            return None

        return self._validate_and_convert_interpolation_result(
            parent=parent,
            value=value,
            key=key,
            resolved=resolved,
            throw_on_resolution_failure=throw_on_resolution_failure,
        )

    def _validate_and_convert_interpolation_result(
        self,
        parent: Optional["Container"],
        value: "Node",
        key: Any,
        resolved: Any,
        throw_on_resolution_failure: bool,
    ) -> Optional["Node"]:
        from .nodes import AnyNode, InterpolationResultNode, ValueNode

        # If the output is not a Node already (e.g., because it is the output of a
        # custom resolver), then we will need to wrap it within a Node.
        must_wrap = not isinstance(resolved, Node)

        # If the node is typed, validate (and possibly convert) the result.
        if isinstance(value, ValueNode) and not isinstance(value, AnyNode):
            res_value = _get_value(resolved)
            try:
                conv_value = value.validate_and_convert(res_value)
            except ValidationError as e:
                if throw_on_resolution_failure:
                    self._format_and_raise(
                        key=key,
                        value=res_value,
                        cause=e,
                        msg=f"While dereferencing interpolation '{value}': {e}",
                        type_override=InterpolationValidationError,
                    )
                return None

            # If the converted value is of the same type, it means that no conversion
            # was actually needed. As a result, we can keep the original `resolved`
            # (and otherwise, the converted value must be wrapped into a new node).
            if type(conv_value) != type(res_value):
                must_wrap = True
                resolved = conv_value

        if must_wrap:
            return InterpolationResultNode(value=resolved, key=key, parent=parent)
        else:
            assert isinstance(resolved, Node)
            return resolved

    def _validate_not_dereferencing_to_parent(self, node: Node, target: Node) -> None:
        parent: Optional[Node] = node
        while parent is not None:
            if parent is target:
                raise InterpolationResolutionError(
                    "Interpolation to parent node detected"
                )
            parent = parent._get_parent()

    def _resolve_node_interpolation(
        self, inter_key: str, memo: Optional[Set[int]]
    ) -> "Node":
        """A node interpolation is of the form `${foo.bar}`"""
        try:
            root_node, inter_key = self._resolve_key_and_root(inter_key)
        except ConfigKeyError as exc:
            raise InterpolationKeyError(
                f"ConfigKeyError while resolving interpolation: {exc}"
            ).with_traceback(sys.exc_info()[2])

        try:
            parent, last_key, value = root_node._select_impl(
                inter_key,
                throw_on_missing=True,
                throw_on_resolution_failure=True,
                memo=memo,
            )
        except MissingMandatoryValue as exc:
            raise InterpolationToMissingValueError(
                f"MissingMandatoryValue while resolving interpolation: {exc}"
            ).with_traceback(sys.exc_info()[2])

        if parent is None or value is None:
            raise InterpolationKeyError(f"Interpolation key '{inter_key}' not found")
        else:
            self._validate_not_dereferencing_to_parent(node=self, target=value)
            return value

    def _evaluate_custom_resolver(
        self,
        key: Any,
        node: Node,
        inter_type: str,
        inter_args: Tuple[Any, ...],
        inter_args_str: Tuple[str, ...],
    ) -> Any:
        from omegaconf import OmegaConf

        resolver = OmegaConf._get_resolver(inter_type)
        if resolver is not None:
            root_node = self._get_root()
            return resolver(
                root_node,
                self,
                node,
                inter_args,
                inter_args_str,
            )
        else:
            raise UnsupportedInterpolationType(
                f"Unsupported interpolation type {inter_type}"
            )

    def _maybe_resolve_interpolation(
        self,
        parent: Optional["Container"],
        key: Any,
        value: Node,
        throw_on_resolution_failure: bool,
        memo: Optional[Set[int]] = None,
    ) -> Optional[Node]:
        value_kind = get_value_kind(value)
        if value_kind != ValueKind.INTERPOLATION:
            return value

        parse_tree = parse(_get_value(value))
        return self._resolve_interpolation_from_parse_tree(
            parent=parent,
            value=value,
            key=key,
            parse_tree=parse_tree,
            throw_on_resolution_failure=throw_on_resolution_failure,
            memo=memo if memo is not None else set(),
        )

    def resolve_parse_tree(
        self,
        parse_tree: ParserRuleContext,
        node: Node,
        memo: Optional[Set[int]] = None,
        key: Optional[Any] = None,
        parent: Optional["Container"] = None,
    ) -> Any:
        """
        Resolve a given parse tree into its value.

        We make no assumption here on the type of the tree's root, so that the
        return value may be of any type.
        """

        def node_interpolation_callback(
            inter_key: str, memo: Optional[Set[int]]
        ) -> Optional["Node"]:
            return self._resolve_node_interpolation(inter_key=inter_key, memo=memo)

        def resolver_interpolation_callback(
            name: str, args: Tuple[Any, ...], args_str: Tuple[str, ...]
        ) -> Any:
            return self._evaluate_custom_resolver(
                key=key,
                node=node,
                inter_type=name,
                inter_args=args,
                inter_args_str=args_str,
            )

        visitor = GrammarVisitor(
            node_interpolation_callback=node_interpolation_callback,
            resolver_interpolation_callback=resolver_interpolation_callback,
            memo=memo,
        )
        try:
            return visitor.visit(parse_tree)
        except InterpolationResolutionError:
            raise
        except Exception as exc:
            # Other kinds of exceptions are wrapped in an `InterpolationResolutionError`.
            raise InterpolationResolutionError(
                f"{type(exc).__name__} raised while resolving interpolation: {exc}"
            ).with_traceback(sys.exc_info()[2])

    def _re_parent(self) -> None:
        from .dictconfig import DictConfig
        from .listconfig import ListConfig

        # update parents of first level Config nodes to self

        if isinstance(self, Container):
            if isinstance(self, DictConfig):
                content = self.__dict__["_content"]
                if isinstance(content, dict):
                    for _key, value in self.__dict__["_content"].items():
                        if value is not None:
                            value._set_parent(self)
                        if isinstance(value, Container):
                            value._re_parent()
            elif isinstance(self, ListConfig):
                content = self.__dict__["_content"]
                if isinstance(content, list):
                    for item in self.__dict__["_content"]:
                        if item is not None:
                            item._set_parent(self)
                        if isinstance(item, Container):
                            item._re_parent()

    def _invalidate_flags_cache(self) -> None:
        from .dictconfig import DictConfig
        from .listconfig import ListConfig

        # invalidate subtree cache only if the cache is initialized in this node.

        if self.__dict__["_flags_cache"] is not None:
            self.__dict__["_flags_cache"] = None
            if isinstance(self, DictConfig):
                content = self.__dict__["_content"]
                if isinstance(content, dict):
                    for value in self.__dict__["_content"].values():
                        value._invalidate_flags_cache()
            elif isinstance(self, ListConfig):
                content = self.__dict__["_content"]
                if isinstance(content, list):
                    for item in self.__dict__["_content"]:
                        item._invalidate_flags_cache()

    def _has_ref_type(self) -> bool:
        return self._metadata.ref_type is not Any class ContainerMetadata(Metadata):
    key_type: Any = None
    element_type: Any = None

    def __post_init__(self) -> None:
        if self.ref_type is None:
            self.ref_type = Any
        assert self.key_type is Any or isinstance(self.key_type, type)
        if self.element_type is not None:
            if not valid_value_annotation_type(self.element_type):
                raise ValidationError(
                    f"Unsupported value type: '{type_str(self.element_type, include_module_name=True)}'"
                )

        if self.flags is None:
            self.flags = {} class Node(ABC):
    _metadata: Metadata

    _parent: Optional["Container"]
    _flags_cache: Optional[Dict[str, Optional[bool]]]

    def __init__(self, parent: Optional["Container"], metadata: Metadata):
        self.__dict__["_metadata"] = metadata
        self.__dict__["_parent"] = parent
        self.__dict__["_flags_cache"] = None

    def __getstate__(self) -> Dict[str, Any]:
        # Overridden to ensure that the flags cache is cleared on serialization.
        state_dict = copy.copy(self.__dict__)
        del state_dict["_flags_cache"]
        return state_dict

    def __setstate__(self, state_dict: Dict[str, Any]) -> None:
        self.__dict__.update(state_dict)
        self.__dict__["_flags_cache"] = None

    def _set_parent(self, parent: Optional["Container"]) -> None:
        assert parent is None or isinstance(parent, Container)
        self.__dict__["_parent"] = parent
        self._invalidate_flags_cache()

    def _invalidate_flags_cache(self) -> None:
        self.__dict__["_flags_cache"] = None

    def _get_parent(self) -> Optional["Container"]:
        parent = self.__dict__["_parent"]
        assert parent is None or isinstance(parent, Container)
        return parent

    def _set_flag(
        self,
        flags: Union[List[str], str],
        values: Union[List[Optional[bool]], Optional[bool]],
    ) -> "Node":
        if isinstance(flags, str):
            flags = [flags]

        if values is None or isinstance(values, bool):
            values = [values]

        if len(values) == 1:
            values = len(flags) * values

        if len(flags) != len(values):
            raise ValueError("Inconsistent lengths of input flag names and values")

        for idx, flag in enumerate(flags):
            value = values[idx]
            if value is None:
                assert self._metadata.flags is not None
                if flag in self._metadata.flags:
                    del self._metadata.flags[flag]
            else:
                assert self._metadata.flags is not None
                self._metadata.flags[flag] = value
        self._invalidate_flags_cache()
        return self

    def _get_node_flag(self, flag: str) -> Optional[bool]:
        """
        :param flag: flag to inspect
        :return: the state of the flag on this node.
        """
        assert self._metadata.flags is not None
        return self._metadata.flags.get(flag)

    def _get_flag(self, flag: str) -> Optional[bool]:
        cache = self.__dict__["_flags_cache"]
        if cache is None:
            cache = self.__dict__["_flags_cache"] = {}

        ret = cache.get(flag, _DEFAULT_MARKER_)
        if ret is _DEFAULT_MARKER_:
            ret = self._get_flag_no_cache(flag)
            cache[flag] = ret
        assert ret is None or isinstance(ret, bool)
        return ret

    def _get_flag_no_cache(self, flag: str) -> Optional[bool]:
        """
        Returns True if this config node flag is set
        A flag is set if node.set_flag(True) was called
        or one if it's parents is flag is set
        :return:
        """
        flags = self._metadata.flags
        assert flags is not None
        if flag in flags and flags[flag] is not None:
            return flags[flag]

        if self._is_flags_root():
            return None

        parent = self._get_parent()
        if parent is None:
            return None
        else:
            # noinspection PyProtectedMember
            return parent._get_flag(flag)

    def _format_and_raise(
        self,
        key: Any,
        value: Any,
        cause: Exception,
        msg: Optional[str] = None,
        type_override: Any = None,
    ) -> None:
        format_and_raise(
            node=self,
            key=key,
            value=value,
            msg=str(cause) if msg is None else msg,
            cause=cause,
            type_override=type_override,
        )
        assert False

    @abstractmethod
    def _get_full_key(self, key: Optional[Union[DictKeyType, int]]) -> str:
        ...

    def _dereference_node(self) -> "Node":
        node = self._dereference_node_impl(throw_on_resolution_failure=True)
        assert node is not None
        return node

    def _maybe_dereference_node(
        self,
        throw_on_resolution_failure: bool = False,
        memo: Optional[Set[int]] = None,
    ) -> Optional["Node"]:
        return self._dereference_node_impl(
            throw_on_resolution_failure=throw_on_resolution_failure,
            memo=memo,
        )

    def _dereference_node_impl(
        self,
        throw_on_resolution_failure: bool,
        memo: Optional[Set[int]] = None,
    ) -> Optional["Node"]:
        if not self._is_interpolation():
            return self

        parent = self._get_parent()
        if parent is None:
            if throw_on_resolution_failure:
                raise InterpolationResolutionError(
                    "Cannot resolve interpolation for a node without a parent"
                )
            return None
        assert parent is not None
        key = self._key()
        return parent._resolve_interpolation_from_parse_tree(
            parent=parent,
            key=key,
            value=self,
            parse_tree=parse(_get_value(self)),
            throw_on_resolution_failure=throw_on_resolution_failure,
            memo=memo,
        )

    def _get_root(self) -> "Container":
        root: Optional[Container] = self._get_parent()
        if root is None:
            assert isinstance(self, Container)
            return self
        assert root is not None and isinstance(root, Container)
        while root._get_parent() is not None:
            root = root._get_parent()
            assert root is not None and isinstance(root, Container)
        return root

    def _is_missing(self) -> bool:
        """
        Check if the node's value is `???` (does *not* resolve interpolations).
        """
        return _is_missing_value(self)

    def _is_none(self) -> bool:
        """
        Check if the node's value is `None` (does *not* resolve interpolations).
        """
        return self._value() is None

    @abstractmethod
    def __eq__(self, other: Any) -> bool:
        ...

    @abstractmethod
    def __ne__(self, other: Any) -> bool:
        ...

    @abstractmethod
    def __hash__(self) -> int:
        ...

    @abstractmethod
    def _value(self) -> Any:
        ...

    @abstractmethod
    def _set_value(self, value: Any, flags: Optional[Dict[str, bool]] = None) -> None:
        ...

    @abstractmethod
    def _is_optional(self) -> bool:
        ...

    @abstractmethod
    def _is_interpolation(self) -> bool:
        ...

    def _key(self) -> Any:
        return self._metadata.key

    def _set_key(self, key: Any) -> None:
        self._metadata.key = key

    def _is_flags_root(self) -> bool:
        return self._metadata.flags_root

    def _set_flags_root(self, flags_root: bool) -> None:
        if self._metadata.flags_root != flags_root:
            self._metadata.flags_root = flags_root
            self._invalidate_flags_cache() class BaseContainer(Container, ABC):
    _resolvers: ClassVar[Dict[str, Any]] = {}

    def __init__(self, parent: Optional["Container"], metadata: ContainerMetadata):
        if not (parent is None or isinstance(parent, Container)):
            raise ConfigTypeError("Parent type is not omegaconf.Container")
        super().__init__(parent=parent, metadata=metadata)
        self.__dict__["_content"] = None

    def _resolve_with_default(
        self,
        key: Union[DictKeyType, int],
        value: Node,
        default_value: Any = _DEFAULT_MARKER_,
    ) -> Any:
        """returns the value with the specified key, like obj.key and obj['key']"""
        if _is_missing_value(value):
            if default_value is not _DEFAULT_MARKER_:
                return default_value
            raise MissingMandatoryValue("Missing mandatory value: $FULL_KEY")

        resolved_node = self._maybe_resolve_interpolation(
            parent=self,
            key=key,
            value=value,
            throw_on_resolution_failure=True,
        )

        return _get_value(resolved_node)

    def __str__(self) -> str:
        return self.__repr__()

    def __repr__(self) -> str:
        if self.__dict__["_content"] is None:
            return "None"
        elif self._is_interpolation() or self._is_missing():
            v = self.__dict__["_content"]
            return f"'{v}'"
        else:
            return self.__dict__["_content"].__repr__()  # type: ignore

    # Support pickle
    def __getstate__(self) -> Dict[str, Any]:
        dict_copy = copy.copy(self.__dict__)

        # no need to serialize the flags cache, it can be re-constructed later
        dict_copy.pop("_flags_cache", None)

        dict_copy["_metadata"] = copy.copy(dict_copy["_metadata"])
        ref_type = self._metadata.ref_type
        if is_container_annotation(ref_type):
            if is_dict_annotation(ref_type):
                dict_copy["_metadata"].ref_type = Dict
            elif is_list_annotation(ref_type):
                dict_copy["_metadata"].ref_type = List
            else:
                assert False
        if sys.version_info < (3, 7):  # pragma: no cover
            element_type = self._metadata.element_type
            if _is_union(element_type):
                raise OmegaConfBaseException(
                    "Serializing structured configs with `Union` element type requires python >= 3.7"
                )
        return dict_copy

    # Support pickle
    def __setstate__(self, d: Dict[str, Any]) -> None:
        from omegaconf import DictConfig
        from omegaconf._utils import is_generic_dict, is_generic_list

        if isinstance(self, DictConfig):
            key_type = d["_metadata"].key_type

            # backward compatibility to load OmegaConf 2.0 configs
            if key_type is None:
                key_type = Any
                d["_metadata"].key_type = key_type

        element_type = d["_metadata"].element_type

        # backward compatibility to load OmegaConf 2.0 configs
        if element_type is None:
            element_type = Any
            d["_metadata"].element_type = element_type

        ref_type = d["_metadata"].ref_type
        if is_container_annotation(ref_type):
            if is_generic_dict(ref_type):
                d["_metadata"].ref_type = Dict[key_type, element_type]  # type: ignore
            elif is_generic_list(ref_type):
                d["_metadata"].ref_type = List[element_type]  # type: ignore
            else:
                assert False

        d["_flags_cache"] = None
        self.__dict__.update(d)

    @abstractmethod
    def __delitem__(self, key: Any) -> None:
        ...

    def __len__(self) -> int:
        if self._is_none() or self._is_missing() or self._is_interpolation():
            return 0
        content = self.__dict__["_content"]
        return len(content)

    def merge_with_cli(self) -> None:
        args_list = sys.argv[1:]
        self.merge_with_dotlist(args_list)

    def merge_with_dotlist(self, dotlist: List[str]) -> None:
        from omegaconf import OmegaConf

        def fail() -> None:
            raise ValueError("Input list must be a list or a tuple of strings")

        if not isinstance(dotlist, (list, tuple)):
            fail()

        for arg in dotlist:
            if not isinstance(arg, str):
                fail()

            idx = arg.find("=")
            if idx == -1:
                key = arg
                value = None
            else:
                key = arg[0:idx]
                value = arg[idx + 1 :]
                value = yaml.load(value, Loader=get_yaml_loader())

            OmegaConf.update(self, key, value)

    def is_empty(self) -> bool:
        """return true if config is empty"""
        return len(self.__dict__["_content"]) == 0

    @staticmethod
    def _to_content(
        conf: Container,
        resolve: bool,
        throw_on_missing: bool,
        enum_to_str: bool = False,
        structured_config_mode: SCMode = SCMode.DICT,
    ) -> Union[None, Any, str, Dict[DictKeyType, Any], List[Any]]:
        from omegaconf import MISSING, DictConfig, ListConfig

        def convert(val: Node) -> Any:
            value = val._value()
            if enum_to_str and isinstance(value, Enum):
                value = f"{value.name}"

            return value

        def get_node_value(key: Union[DictKeyType, int]) -> Any:
            try:
                node = conf._get_node(key, throw_on_missing_value=throw_on_missing)
            except MissingMandatoryValue as e:
                conf._format_and_raise(key=key, value=None, cause=e)
            assert isinstance(node, Node)
            if resolve:
                try:
                    node = node._dereference_node()
                except InterpolationResolutionError as e:
                    conf._format_and_raise(key=key, value=None, cause=e)

            if isinstance(node, Container):
                value = BaseContainer._to_content(
                    node,
                    resolve=resolve,
                    throw_on_missing=throw_on_missing,
                    enum_to_str=enum_to_str,
                    structured_config_mode=structured_config_mode,
                )
            else:
                value = convert(node)
            return value

        if conf._is_none():
            return None
        elif conf._is_missing():
            if throw_on_missing:
                conf._format_and_raise(
                    key=None,
                    value=None,
                    cause=MissingMandatoryValue("Missing mandatory value"),
                )
            else:
                return MISSING
        elif not resolve and conf._is_interpolation():
            inter = conf._value()
            assert isinstance(inter, str)
            return inter

        if resolve:
            _conf = conf._dereference_node()
            assert isinstance(_conf, Container)
            conf = _conf

        if isinstance(conf, DictConfig):
            if (
                conf._metadata.object_type is not None
                and structured_config_mode == SCMode.DICT_CONFIG
            ):
                return conf
            if structured_config_mode == SCMode.INSTANTIATE and is_structured_config(
                conf._metadata.object_type
            ):
                return conf._to_object()

            retdict: Dict[DictKeyType, Any] = {}
            for key in conf.keys():
                value = get_node_value(key)
                if enum_to_str and isinstance(key, Enum):
                    key = f"{key.name}"
                retdict[key] = value
            return retdict
        elif isinstance(conf, ListConfig):
            retlist: List[Any] = []
            for index in range(len(conf)):
                item = get_node_value(index)
                retlist.append(item)

            return retlist
        assert False

    @staticmethod
    def _map_merge(dest: "BaseContainer", src: "BaseContainer") -> None:
        """merge src into dest and return a new copy, does not modified input"""
        from omegaconf import AnyNode, DictConfig, ValueNode

        assert isinstance(dest, DictConfig)
        assert isinstance(src, DictConfig)
        src_type = src._metadata.object_type
        src_ref_type = get_ref_type(src)
        assert src_ref_type is not None

        # If source DictConfig is:
        #  - None => set the destination DictConfig to None
        #  - an interpolation => set the destination DictConfig to be the same interpolation
        if src._is_none() or src._is_interpolation():
            dest._set_value(src._value())
            _update_types(node=dest, ref_type=src_ref_type, object_type=src_type)
            return

        dest._validate_merge(value=src)

        def expand(node: Container) -> None:
            rt = node._metadata.ref_type
            val: Any
            if rt is not Any:
                if is_dict_annotation(rt):
                    val = {}
                elif is_list_annotation(rt) or is_tuple_annotation(rt):
                    val = []
                else:
                    val = rt
            elif isinstance(node, DictConfig):
                val = {}
            else:
                assert False

            node._set_value(val)

        if (
            src._is_missing()
            and not dest._is_missing()
            and is_structured_config(src_ref_type)
        ):
            # Replace `src` with a prototype of its corresponding structured config
            # whose fields are all missing (to avoid overwriting fields in `dest`).
            src = _create_structured_with_missing_fields(
                ref_type=src_ref_type, object_type=src_type
            )

        if (dest._is_interpolation() or dest._is_missing()) and not src._is_missing():
            expand(dest)

        src_items = src.items_ex(resolve=False) if not src._is_missing() else []
        for key, src_value in src_items:
            src_node = src._get_node(key, validate_access=False)
            dest_node = dest._get_node(key, validate_access=False)
            assert src_node is None or isinstance(src_node, Node)
            assert dest_node is None or isinstance(dest_node, Node)

            if isinstance(dest_node, DictConfig):
                dest_node._validate_merge(value=src_node)

            missing_src_value = _is_missing_value(src_value)

            if (
                isinstance(dest_node, Container)
                and dest_node._is_none()
                and not missing_src_value
                and not _is_none(src_value, resolve=True)
            ):
                expand(dest_node)

            if dest_node is not None and dest_node._is_interpolation():
                target_node = dest_node._maybe_dereference_node()
                if isinstance(target_node, Container):
                    dest[key] = target_node
                    dest_node = dest._get_node(key)

            is_optional, et = _resolve_optional(dest._metadata.element_type)
            if dest_node is None and is_structured_config(et) and not missing_src_value:
                # merging into a new node. Use element_type as a base
                dest[key] = DictConfig(
                    et, parent=dest, ref_type=et, is_optional=is_optional
                )
                dest_node = dest._get_node(key)

            if dest_node is not None:
                if isinstance(dest_node, BaseContainer):
                    if isinstance(src_value, BaseContainer):
                        dest_node._merge_with(src_value)
                    elif not missing_src_value:
                        dest.__setitem__(key, src_value)
                else:
                    if isinstance(src_value, BaseContainer):
                        dest.__setitem__(key, src_value)
                    else:
                        assert isinstance(dest_node, ValueNode)
                        assert isinstance(src_node, ValueNode)
                        # Compare to literal missing, ignoring interpolation
                        src_node_missing = _is_missing_literal(src_value)
                        try:
                            if isinstance(dest_node, AnyNode):
                                if src_node_missing:
                                    node = copy.copy(src_node)
                                    # if src node is missing, use the value from the dest_node,
                                    # but validate it against the type of the src node before assigment
                                    node._set_value(dest_node._value())
                                else:
                                    node = src_node
                                dest.__setitem__(key, node)
                            else:
                                if not src_node_missing:
                                    dest_node._set_value(src_value)

                        except (ValidationError, ReadonlyConfigError) as e:
                            dest._format_and_raise(key=key, value=src_value, cause=e)
            else:
                from omegaconf import open_dict

                if is_structured_config(src_type):
                    # verified to be compatible above in _validate_merge
                    with open_dict(dest):
                        dest[key] = src._get_node(key)
                else:
                    dest[key] = src._get_node(key)

        _update_types(node=dest, ref_type=src_ref_type, object_type=src_type)

        # explicit flags on the source config are replacing the flag values in the destination
        flags = src._metadata.flags
        assert flags is not None
        for flag, value in flags.items():
            if value is not None:
                dest._set_flag(flag, value)

    @staticmethod
    def _list_merge(dest: Any, src: Any) -> None:
        from omegaconf import DictConfig, ListConfig, OmegaConf

        assert isinstance(dest, ListConfig)
        assert isinstance(src, ListConfig)

        if src._is_none():
            dest._set_value(None)
        elif src._is_missing():
            # do not change dest if src is MISSING.
            if dest._metadata.element_type is Any:
                dest._metadata.element_type = src._metadata.element_type
        elif src._is_interpolation():
            dest._set_value(src._value())
        else:
            temp_target = ListConfig(content=[], parent=dest._get_parent())
            temp_target.__dict__["_metadata"] = copy.deepcopy(
                dest.__dict__["_metadata"]
            )
            is_optional, et = _resolve_optional(dest._metadata.element_type)
            if is_structured_config(et):
                prototype = DictConfig(et, ref_type=et, is_optional=is_optional)
                for item in src._iter_ex(resolve=False):
                    if isinstance(item, DictConfig):
                        item = OmegaConf.merge(prototype, item)
                    temp_target.append(item)
            else:
                for item in src._iter_ex(resolve=False):
                    temp_target.append(item)

            dest.__dict__["_content"] = temp_target.__dict__["_content"]

        # explicit flags on the source config are replacing the flag values in the destination
        flags = src._metadata.flags
        assert flags is not None
        for flag, value in flags.items():
            if value is not None:
                dest._set_flag(flag, value)

    def merge_with(
        self,
        *others: Union[
            "BaseContainer", Dict[str, Any], List[Any], Tuple[Any, ...], Any
        ],
    ) -> None:
        try:
            self._merge_with(*others)
        except Exception as e:
            self._format_and_raise(key=None, value=None, cause=e)

    def _merge_with(
        self,
        *others: Union[
            "BaseContainer", Dict[str, Any], List[Any], Tuple[Any, ...], Any
        ],
    ) -> None:
        from .dictconfig import DictConfig
        from .listconfig import ListConfig

        """merge a list of other Config objects into this one, overriding as needed"""
        for other in others:
            if other is None:
                raise ValueError("Cannot merge with a None config")

            my_flags = {}
            if self._get_flag("allow_objects") is True:
                my_flags = {"allow_objects": True}
            other = _ensure_container(other, flags=my_flags)

            if isinstance(self, DictConfig) and isinstance(other, DictConfig):
                BaseContainer._map_merge(self, other)
            elif isinstance(self, ListConfig) and isinstance(other, ListConfig):
                BaseContainer._list_merge(self, other)
            else:
                raise TypeError("Cannot merge DictConfig with ListConfig")

        # recursively correct the parent hierarchy after the merge
        self._re_parent()

    # noinspection PyProtectedMember
    def _set_item_impl(self, key: Any, value: Any) -> None:
        """
        Changes the value of the node key with the desired value. If the node key doesn't
        exist it creates a new one.
        """
        from omegaconf.omegaconf import _maybe_wrap

        from .nodes import AnyNode, ValueNode

        if isinstance(value, Node):
            do_deepcopy = not self._get_flag("no_deepcopy_set_nodes")
            if not do_deepcopy and isinstance(value, Container):
                # if value is from the same config, perform a deepcopy no matter what.
                if self._get_root() is value._get_root():
                    do_deepcopy = True

            if do_deepcopy:
                value = copy.deepcopy(value)
            value._set_parent(None)

            try:
                old = value._key()
                value._set_key(key)
                self._validate_set(key, value)
            finally:
                value._set_key(old)
        else:
            self._validate_set(key, value)

        if self._get_flag("readonly"):
            raise ReadonlyConfigError("Cannot change read-only config container")

        input_config = isinstance(value, Container)
        target_node_ref = self._get_node(key)
        special_value = value is None or _is_missing_value(value)

        input_node = isinstance(value, ValueNode)
        if isinstance(self.__dict__["_content"], dict):
            target_node = key in self.__dict__["_content"] and isinstance(
                target_node_ref, ValueNode
            )

        elif isinstance(self.__dict__["_content"], list):
            target_node = isinstance(target_node_ref, ValueNode)
        # We use set_value if:
        # 1. Target node is a container and the value is MISSING or None
        # 2. Target node is a container and has an explicit ref_type
        # 3. If the target is a NodeValue then it should set his value.
        #    Furthermore if it's an AnyNode it should wrap when the input is
        # a container and set when the input is an compatible type(primitive type).
        should_set_value = target_node_ref is not None and (
            (
                isinstance(target_node_ref, Container)
                and (special_value or target_node_ref._has_ref_type())
            )
            or (target_node and not isinstance(target_node_ref, AnyNode))
            or (isinstance(target_node_ref, AnyNode) and is_primitive_type(value))
        )

        def wrap(key: Any, val: Any) -> Node:
            if not is_structured_config(val):
                is_optional, ref_type = _resolve_optional(self._metadata.element_type)
            else:
                target = self._get_node(key)
                if target is None:
                    is_optional, ref_type = _resolve_optional(
                        self._metadata.element_type
                    )
                else:
                    assert isinstance(target, Node)
                    is_optional = target._is_optional()
                    ref_type = target._metadata.ref_type
            return _maybe_wrap(
                ref_type=ref_type,
                key=key,
                value=val,
                is_optional=is_optional,
                parent=self,
            )

        def assign(value_key: Any, val: ValueNode) -> None:
            assert val._get_parent() is None
            v = val
            v._set_parent(self)
            v._set_key(value_key)
            _update_types(v, self._metadata.element_type, None)
            self.__dict__["_content"][value_key] = v

        if input_node and target_node:
            # both nodes, replace existing node with new one
            assign(key, value)
        elif not input_node and target_node:
            # input is not node, can be primitive or config
            if should_set_value:
                self.__dict__["_content"][key]._set_value(value)
            elif input_config:
                assign(key, value)
            else:
                self.__dict__["_content"][key] = wrap(key, value)
        elif input_node and not target_node:
            # target must be config, replace target with input node
            assign(key, value)
        elif not input_node and not target_node:
            if should_set_value:
                self.__dict__["_content"][key]._set_value(value)
            elif input_config:
                assign(key, value)
            else:
                self.__dict__["_content"][key] = wrap(key, value)

    @staticmethod
    def _item_eq(
        c1: Container,
        k1: Union[DictKeyType, int],
        c2: Container,
        k2: Union[DictKeyType, int],
    ) -> bool:
        v1 = c1._get_node(k1)
        v2 = c2._get_node(k2)
        assert v1 is not None and v2 is not None

        assert isinstance(v1, Node)
        assert isinstance(v2, Node)

        if v1._is_none() and v2._is_none():
            return True

        if v1._is_missing() and v2._is_missing():
            return True

        v1_inter = v1._is_interpolation()
        v2_inter = v2._is_interpolation()
        dv1: Optional[Node] = v1
        dv2: Optional[Node] = v2

        if v1_inter:
            dv1 = v1._maybe_dereference_node()
        if v2_inter:
            dv2 = v2._maybe_dereference_node()

        if v1_inter and v2_inter:
            if dv1 is None or dv2 is None:
                return v1 == v2
            else:
                # both are not none, if both are containers compare as container
                if isinstance(dv1, Container) and isinstance(dv2, Container):
                    if dv1 != dv2:
                        return False
                dv1 = _get_value(dv1)
                dv2 = _get_value(dv2)
                return dv1 == dv2
        elif not v1_inter and not v2_inter:
            v1 = _get_value(v1)
            v2 = _get_value(v2)
            ret = v1 == v2
            assert isinstance(ret, bool)
            return ret
        else:
            dv1 = _get_value(dv1)
            dv2 = _get_value(dv2)
            ret = dv1 == dv2
            assert isinstance(ret, bool)
            return ret

    def _is_optional(self) -> bool:
        return self.__dict__["_metadata"].optional is True

    def _is_interpolation(self) -> bool:
        return _is_interpolation(self.__dict__["_content"])

    @abstractmethod
    def _validate_get(self, key: Any, value: Any = None) -> None:
        ...

    @abstractmethod
    def _validate_set(self, key: Any, value: Any) -> None:
        ...

    def _value(self) -> Any:
        return self.__dict__["_content"]

    def _get_full_key(self, key: Union[DictKeyType, int, slice, None]) -> str:
        from .listconfig import ListConfig
        from .omegaconf import _select_one

        if not isinstance(key, (int, str, Enum, float, bool, slice, type(None))):
            return ""

        def _slice_to_str(x: slice) -> str:
            if x.step is not None:
                return f"{x.start}:{x.stop}:{x.step}"
            else:
                return f"{x.start}:{x.stop}"

        def prepand(full_key: str, parent_type: Any, cur_type: Any, key: Any) -> str:
            if isinstance(key, slice):
                key = _slice_to_str(key)
            elif isinstance(key, Enum):
                key = key.name
            elif isinstance(key, (int, float, bool)):
                key = str(key)

            if issubclass(parent_type, ListConfig):
                if full_key != "":
                    if issubclass(cur_type, ListConfig):
                        full_key = f"[{key}]{full_key}"
                    else:
                        full_key = f"[{key}].{full_key}"
                else:
                    full_key = f"[{key}]"
            else:
                if full_key == "":
                    full_key = key
                else:
                    if issubclass(cur_type, ListConfig):
                        full_key = f"{key}{full_key}"
                    else:
                        full_key = f"{key}.{full_key}"
            return full_key

        if key is not None and key != "":
            assert isinstance(self, Container)
            cur, _ = _select_one(
                c=self, key=str(key), throw_on_missing=False, throw_on_type_error=False
            )
            if cur is None:
                cur = self
                full_key = prepand("", type(cur), None, key)
                if cur._key() is not None:
                    full_key = prepand(
                        full_key, type(cur._get_parent()), type(cur), cur._key()
                    )
            else:
                full_key = prepand("", type(cur._get_parent()), type(cur), cur._key())
        else:
            cur = self
            if cur._key() is None:
                return ""
            full_key = self._key()

        assert cur is not None
        memo = {id(cur)}  # remember already visited nodes so as to detect cycles
        while cur._get_parent() is not None:
            cur = cur._get_parent()
            if id(cur) in memo:
                raise ConfigCycleDetectedException(
                    f"Cycle when iterating over parents of key `{key}`"
                )
            memo.add(id(cur))
            assert cur is not None
            if cur._key() is not None:
                full_key = prepand(
                    full_key, type(cur._get_parent()), type(cur), cur._key()
                )

        return full_key class ConfigAttributeError(OmegaConfBaseException, AttributeError):
    """
    Thrown from a config object when a regular access would have caused an AttributeError.
    """ class ConfigTypeError(OmegaConfBaseException, TypeError):
    """
    Thrown from a config object when a regular access would have caused a TypeError.
    """ class ConfigValueError(OmegaConfBaseException, ValueError):
    """
    Thrown from a config object when a regular access would have caused a ValueError.
    """ class KeyValidationError(OmegaConfBaseException, ValueError):
    """
    Thrown when an a key of invalid type is used
    """ class MissingMandatoryValue(OmegaConfBaseException):
    """Thrown when a variable flagged with '???' value is accessed to
    indicate that the value was not set""" class ReadonlyConfigError(OmegaConfBaseException):
    """
    Thrown when someone tries to modify a frozen config
    """ class ValidationError(OmegaConfBaseException, ValueError):
    """
    Thrown when a value fails validation
    """
import copy
import itertools
from typing import (
    Any,
    Callable,
    Dict,
    Iterable,
    Iterator,
    List,
    MutableSequence,
    Optional,
    Tuple,
    Type,
    Union,
)
from ._utils import (
    ValueKind,
    _is_missing_literal,
    _is_none,
    _resolve_optional,
    format_and_raise,
    get_value_kind,
    is_int,
    is_primitive_list,
    is_structured_config,
    type_str,
)
from .base import Container, ContainerMetadata, Node
from .basecontainer import BaseContainer
from .errors import (
    ConfigAttributeError,
    ConfigTypeError,
    ConfigValueError,
    KeyValidationError,
    MissingMandatoryValue,
    ReadonlyConfigError,
    ValidationError,
)
        from omegaconf import OmegaConf
        from omegaconf.omegaconf import _maybe_wrap
            from .nodes import ValueNode
        from omegaconf import MISSING, flag_override



class ListConfig(BaseContainer, MutableSequence[Any]):

    _content: Union[List[Node], None, str]

    def __init__(
        self,
        content: Union[List[Any], Tuple[Any, ...], str, None],
        key: Any = None,
        parent: Optional[Container] = None,
        element_type: Union[Type[Any], Any] = Any,
        is_optional: bool = True,
        ref_type: Union[Type[Any], Any] = Any,
        flags: Optional[Dict[str, bool]] = None,
    ) -> None:
        try:
            if isinstance(content, ListConfig):
                if flags is None:
                    flags = content._metadata.flags
            super().__init__(
                parent=parent,

                metadata=ContainerMetadata(