# (c) Fastly, inc 2016
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

"""
selective.py callback plugin.

This callback only prints tasks that have been tagged with `print_action` or that have failed.
Tasks that are not printed are placed with a '.'.

For example:

- debug: msg="This will not be printed"
- debug: msg="But this will"
  tags: [print_action]"

This allows operators to focus on the tasks that provide value only.

If you increase verbosity all tasks are printed.
"""

from __future__ import absolute_import, division, print_function

import difflib
import os
import sys

from ansible.plugins.callback import CallbackBase

if sys.version_info[0] > 2:
    unicode = str

__metaclass__ = type

COLORS = {
    "normal": "\033[0m",
    "ok": "\033[92m",
    "bold": "\033[1m",
    "not_so_bold": "\033[1m\033[34m",
    "changed": "\033[93m",
    "failed": "\033[91m",
    "endc": "\033[0m",
    "skipped": "\033[96m",
}

DONT_COLORIZE = os.getenv("ANSIBLY_DONT_COLORIZE", default=False)

def dict_diff(prv, nxt):
    """Return a dict of keys that differ with another config object."""
    keys = set(prv.keys() + nxt.keys())
    result = {}
    for k in keys:
        if prv.get(k) != nxt.get(k):
            result[k] = (prv.get(k), nxt.get(k))
    return result

def colorize(msg, color):
    """Given a string add necessary codes to format the string."""
    if DONT_COLORIZE:
        return msg
    else:
        return "{}{}{}".format(COLORS[color], msg, COLORS["endc"])

class CallbackModule(CallbackBase):
    """selective.py callback plugin."""

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = "stdout"
    CALLBACK_NAME = "selective"

    def __init__(self, display=None):
        """selective.py callback plugin."""
        super(CallbackModule, self).__init__(display)
        self.last_skipped = False
        self.last_task_name = None
        self.printed_last_task = False

    def _print_task(self, task_name=None):
        if task_name is None:
            task_name = self.last_task_name

        if not self.printed_last_task:
            self.printed_last_task = True
            line_length = 120
            if self.last_skipped:
                print()
            msg = colorize(
                "# {} {}".format(task_name, "*" * (line_length - len(task_name))),
                "bold",
            )
            print(msg)

    def _indent_text(self, text, indent_level):
        lines = text.splitlines()
        result_lines = []
        for l in lines:
            result_lines.append("{}{}".format(" " * indent_level, l))
        return "\n".join(result_lines)

    def _print_diff(self, diff, indent_level):
        if isinstance(diff, dict):
            try:
                diff = "\n".join(
                    difflib.unified_diff(
                        diff["before"].splitlines(),
                        diff["after"].splitlines(),
                        fromfile=diff.get("before_header", "new_file"),
                        tofile=diff["after_header"],
                    )
                )
            except AttributeError:
                diff = dict_diff(diff["before"], diff["after"])
        if diff:
            diff = colorize(str(diff), "changed")
            print(self._indent_text(diff, indent_level + 4))

    def _print_host_or_item(
        self, host_or_item, changed, msg, diff, is_host, error, stdout, stderr
    ):
        if is_host:
            indent_level = 0
            name = colorize(host_or_item.name, "not_so_bold")
        else:
            indent_level = 4
            if isinstance(host_or_item, dict):
                if "key" in host_or_item.keys():
                    host_or_item = host_or_item["key"]
            name = colorize(unicode(host_or_item), "bold")

        if error:
            color = "failed"
            change_string = colorize("FAILED!!!", color)
        else:
            color = "changed" if changed else "ok"
            change_string = colorize("changed={}".format(changed), color)

        msg = colorize(msg, color)

        line_length = 120
        spaces = " " * (40 - len(name) - indent_level)
        line = "{}  * {}{}- {}".format(" " * indent_level, name, spaces, change_string)

        if len(msg) < 50:
            line += " -- {}".format(msg)
            print("{} {}---------".format(line, "-" * (line_length - len(line))))
        else:
            print("{} {}".format(line, "-" * (line_length - len(line))))
            print(self._indent_text(msg, indent_level + 4))

        if diff:
            self._print_diff(diff, indent_level)
        if stdout:
            stdout = colorize(stdout, "failed")
            print(self._indent_text(stdout, indent_level + 4))
        if stderr:
            stderr = colorize(stderr, "failed")
            print(self._indent_text(stderr, indent_level + 4))

    def v2_playbook_on_play_start(self, play):
        """Run on start of the play."""
        pass

    def v2_playbook_on_task_start(self, task, **kwargs):
        """Run when a task starts."""
        self.last_task_name = task.get_name()
        self.printed_last_task = False

    def v2_runner_on_ok(self, result, **kwargs):
        """Run when a task finishes correctly."""
        failed = "failed" in result._result
        unreachable = "unreachable" in result._result

        if (
            "print_action" in result._task.tags
            or failed
            or unreachable
            or self._display.verbosity > 1
        ):
            self._print_task()
            self.last_skipped = False
            msg = unicode(result._result.get("msg", "")) or unicode(
                result._result.get("reason", "")
            ) or unicode(
                result._result.get("message", "")
            )

            stderr = [
                result._result.get("exception", None),
                result._result.get("module_stderr", None),
            ]
            stderr = "\n".join([e for e in stderr if e]).strip()

            self._print_host_or_item(
                result._host,
                result._result.get("changed", False),
                msg,
                result._result.get("diff", None),
                is_host=True,
                error=failed or unreachable,
                stdout=result._result.get("module_stdout", None),
                stderr=stderr.strip(),
            )

            if "results" in result._result:
                for r in result._result["results"]:
                    failed = "failed" in r

                    stderr = [r.get("exception", None), r.get("module_stderr", None)]
                    stderr = "\n".join([e for e in stderr if e]).strip()

                    self._print_host_or_item(
                        r["item"],
                        r.get("changed", False),
                        unicode(r.get("msg", "")),
                        r.get("diff", None),
                        is_host=False,
                        error=failed,
                        stdout=r.get("module_stdout", None),
                        stderr=stderr.strip(),
                    )
        else:
            self.last_skipped = True
            print(".", end="")

    def v2_playbook_on_stats(self, stats):
        """Display info about playbook statistics."""
        print()
        self.printed_last_task = False
        self._print_task("STATS")

        hosts = sorted(stats.processed.keys())
        for host in hosts:
            s = stats.summarize(host)

            if s["failures"] or s["unreachable"]:
                color = "failed"
            elif s["changed"]:
                color = "changed"
            else:
                color = "ok"

            msg = "{}    : ok={}\tchanged={}\tfailed={}\tunreachable={}".format(
                host, s["ok"], s["changed"], s["failures"], s["unreachable"]
            )
            print(colorize(msg, color))

    def v2_runner_on_skipped(self, result, **kwargs):
        """Run when a task is skipped."""
        if self._display.verbosity > 1:
            self._print_task()
            self.last_skipped = False

            line_length = 120
            spaces = " " * (31 - len(result._host.name) - 4)

            line = "  * {}{}- {}".format(
                colorize(result._host.name, "not_so_bold"),
                spaces,
                colorize("skipped", "skipped"),
            )

            reason = result._result.get("skipped_reason", "") or result._result.get(
                "skip_reason", ""
            )
            if len(reason) < 50:
                line += " -- {}".format(reason)
                print("{} {}---------".format(line, "-" * (line_length - len(line))))
            else:
                print("{} {}".format(line, "-" * (line_length - len(line))))
                print(self._indent_text(reason, 8))
                print(reason)

    v2_playbook_on_handler_task_start = v2_playbook_on_task_start
    v2_runner_on_failed = v2_runner_on_ok
    v2_runner_on_unreachable = v2_runner_on_ok


from builtins import super
from collections import OrderedDict

from napalm_yang.parsers.jsonp import JSONParser

def _attach_data_to_path(obj, path, data, list_=False):
    if "#list" not in obj:
        obj["#list"] = []

    path = path.split(" ")
    o = obj
    first = True

    while True:
        o["#text"] = " ".join(path)
        p = path.pop(0)
        if not path:
            break
        else:
            if p not in o:
                o[p] = OrderedDict()
            o = o[p]

            if first and list_:
                obj["#list"].append({p: o})
                first = False
    if p in o:
        o[p].update(data)
    else:
        o[p] = data

    # We add a standalong flag to be able to distinguish this situation:
    # switchport
    # switchport mode access
    o[p]["#standalone"] = True

def parse_indented_config(config, current_indent=0, previous_indent=0, nested=False):
    """
    This methid basically reads a configuration that conforms to a very poor industry standard
    and returns a nested structure that behaves like a dict. For example:
        {'enable password whatever': {},
         'interface GigabitEthernet1': {
             'description "bleh"': {},
             'fake nested': {
                 'nested nested configuration': {}},
             'switchport mode trunk': {}},
         'interface GigabitEthernet2': {
             'no ip address': {}},
         'interface GigabitEthernet3': {
             'negotiation auto': {},
             'no ip address': {},
             'shutdown': {}},
         'interface Loopback0': {
             'description "blah"': {}}}
    """
    parsed = OrderedDict()
    while True:
        if not config:
            break
        line = config.pop(0)

        if line.lstrip().startswith("!"):
            continue

        last = line.lstrip()
        leading_spaces = len(line) - len(last)

        #  print("current_indent:{}, previous:{}, leading:{} - {}".format(
        #        current_indent, previous_indent, leading_spaces, line))

        if leading_spaces > current_indent:
            current = parse_indented_config(
                config, leading_spaces, current_indent, True
            )
            _attach_data_to_path(parsed, last, current, nested)
        elif leading_spaces < current_indent:
            config.insert(0, line)
            break
        else:
            if not nested:
                current = parse_indented_config(
                    config, leading_spaces, current_indent, True
                )
                _attach_data_to_path(parsed, last, current, nested)
            else:
                config.insert(0, line)
                break

    return parsed

class TextTree(JSONParser):

    def init_native(self, native):
        resp = []
        for n in native:
            if isinstance(n, dict):
                resp.append(n)
            else:
                resp.append(parse_indented_config(n.splitlines()))

        #  with open("/tmp/napalm-tree-parser", "w+") as f:
        #      import json
        #      f.write(json.dumps(resp[0]["interface"]["Ethernet1"], indent=4))
        #  raise Exception

        return resp

    def _parse_leaf_default(self, attribute, mapping, data):
        extra_path = "#standalone" if "present" in mapping else "#text"
        if "path" in mapping:
            mapping["path"] = "{}.{}".format(mapping["path"], extra_path)
        else:
            mapping["path"] = extra_path
        return super()._parse_leaf_default(attribute, mapping, data)


import netaddr

from napalm_yang.jinja_filters.helpers import check_empty

def filters():
    return {
        "netmask_to_cidr": netmask_to_cidr,
        "cidr_to_netmask": cidr_to_netmask,
        "normalize_prefix": normalize_prefix,
        "normalize_address": normalize_address,
        "prefix_to_addrmask": prefix_to_addrmask,
    }

@check_empty()
def netmask_to_cidr(value):
    """
    Converts a network mask to it's CIDR value.

    Examples:
        >>> "{{ '255.255.255.0'|netmask_to_cidr }}" -> "24"
    """
    return netaddr.IPAddress(value).netmask_bits()

@check_empty()
def cidr_to_netmask(value):
    """
    Converts a CIDR prefix-length to a network mask.

    Examples:
        >>> "{{ '24'|cidr_to_netmask }}" -> "255.255.255.0"
    """
    return str(netaddr.IPNetwork("1.1.1.1/{}".format(value)).netmask)

@check_empty()
def normalize_prefix(value):
    """
    Converts an IPv4 or IPv6 prefix writen in various formats to its CIDR representation.

    This filter works only on prefixes. Use normalize_address if you wish to normalize an address
    without a network mask.

    Examples:
        >>> "{{ '192.168.0.0 255.255.255.0'|normalize_prefix }}" -> "192.168.0.0/24"
        >>> "{{ '192.168/255.255.255.0'|normalize_prefix }}" -> "192.168.0.0/24"
        >>> "{{ '2001:DB8:0:0:1:0:0:1/64'|normalize_prefix }}" -> "2001:db8::1:0:0:1/64"
    """
    value = value.replace(" ", "/")
    return str(netaddr.IPNetwork(value))

@check_empty()
def normalize_address(value):
    """
    Converts an IPv4 or IPv6 address writen in various formats to a standard textual representation.

    This filter works only on addresses without network mask. Use normalize_prefix to normalize
    networks.

    Examples:
        >>> "{{ '192.168.0.1'|normalize_address }}" -> "192.168.0.1"
        >>> "{{ '192.168.1'|normalize_address }}" -> "192.168.0.1"
        >>> "{{ '2001:DB8:0:0:1:0:0:1'|normalize_address }}" -> "2001:db8::1:0:0:1"

    """
    return str(netaddr.IPAddress(value))

@check_empty()
def prefix_to_addrmask(value, sep=" "):
    """
    Converts a CIDR formatted prefix into an address netmask representation.
    Argument sep specifies the separator between the address and netmask parts.
    By default it's a single space.

    Examples:
        >>> "{{ '192.168.0.1/24|prefix_to_addrmask }}" -> "192.168.0.1 255.255.255.0"
        >>> "{{ '192.168.0.1/24|prefix_to_addrmask('/') }}" -> "192.168.0.1/255.255.255.0"
    """
    prefix = netaddr.IPNetwork(value)
    return "{}{}{}".format(prefix.ip, sep, prefix.netmask)


# -*- coding: utf-8 -*-
from operator import attrgetter
from pyangbind.lib.yangtypes import RestrictedPrecisionDecimalType
from pyangbind.lib.yangtypes import RestrictedClassType
from pyangbind.lib.yangtypes import TypedListType
from pyangbind.lib.yangtypes import YANGBool
from pyangbind.lib.yangtypes import YANGListType
from pyangbind.lib.yangtypes import YANGDynClass
from pyangbind.lib.yangtypes import ReferenceType
from pyangbind.lib.base import PybindBase
from collections import OrderedDict
from decimal import Decimal
from bitarray import bitarray
import six

# PY3 support of some PY2 keywords (needs improved)
if six.PY3:
    import builtins as __builtin__

    long = int
elif six.PY2:
    import __builtin__

class state(PybindBase):
    """
  This class was auto-generated by the PythonClass plugin for PYANG
  from YANG module openconfig-network-instance - based on the path /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state. Each member element of
  the container is represented as a class variable - with a specific
  YANG type.

  YANG Description: State information relating to the timers used for the BGP
group
  """
    __slots__ = (
        "_path_helper",
        "_extmethods",
        "__connect_retry",
        "__hold_time",
        "__keepalive_interval",
        "__minimum_advertisement_interval",
    )

    _yang_name = "state"

    _pybind_generated_by = "container"

    def __init__(self, *args, **kwargs):

        self._path_helper = False

        self._extmethods = False
        self.__connect_retry = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="connect-retry",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__hold_time = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(90),
            is_leaf=True,
            yang_name="hold-time",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__keepalive_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="keepalive-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__minimum_advertisement_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="minimum-advertisement-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

        load = kwargs.pop("load", None)
        if args:
            if len(args) > 1:
                raise TypeError("cannot create a YANG container with >1 argument")
            all_attr = True
            for e in self._pyangbind_elements:
                if not hasattr(args[0], e):
                    all_attr = False
                    break
            if not all_attr:
                raise ValueError("Supplied object did not have the correct attributes")
            for e in self._pyangbind_elements:
                nobj = getattr(args[0], e)
                if nobj._changed() is False:
                    continue
                setmethod = getattr(self, "_set_%s" % e)
                if load is None:
                    setmethod(getattr(args[0], e))
                else:
                    setmethod(getattr(args[0], e), load=load)

    def _path(self):
        if hasattr(self, "_parent"):
            return self._parent._path() + [self._yang_name]
        else:
            return [
                "network-instances",
                "network-instance",
                "protocols",
                "protocol",
                "bgp",
                "peer-groups",
                "peer-group",
                "timers",
                "state",
            ]

    def _get_connect_retry(self):
        """
    Getter method for connect_retry, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/connect_retry (decimal64)

    YANG Description: Time interval in seconds between attempts to establish a
session with the peer.
    """
        return self.__connect_retry

    def _set_connect_retry(self, v, load=False):
        """
    Setter method for connect_retry, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/connect_retry (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_connect_retry is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_connect_retry() directly.

    YANG Description: Time interval in seconds between attempts to establish a
session with the peer.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="connect-retry",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """connect_retry must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="connect-retry", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__connect_retry = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_connect_retry(self):
        self.__connect_retry = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="connect-retry",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_hold_time(self):
        """
    Getter method for hold_time, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/hold_time (decimal64)

    YANG Description: Time interval in seconds that a BGP session will be
considered active in the absence of keepalive or other
messages from the peer.  The hold-time is typically
set to 3x the keepalive-interval.
    """
        return self.__hold_time

    def _set_hold_time(self, v, load=False):
        """
    Setter method for hold_time, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/hold_time (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_hold_time is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_hold_time() directly.

    YANG Description: Time interval in seconds that a BGP session will be
considered active in the absence of keepalive or other
messages from the peer.  The hold-time is typically
set to 3x the keepalive-interval.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(90),
                is_leaf=True,
                yang_name="hold-time",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """hold_time must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(90), is_leaf=True, yang_name="hold-time", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__hold_time = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_hold_time(self):
        self.__hold_time = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(90),
            is_leaf=True,
            yang_name="hold-time",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_keepalive_interval(self):
        """
    Getter method for keepalive_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/keepalive_interval (decimal64)

    YANG Description: Time interval in seconds between transmission of keepalive
messages to the neighbor.  Typically set to 1/3 the
hold-time.
    """
        return self.__keepalive_interval

    def _set_keepalive_interval(self, v, load=False):
        """
    Setter method for keepalive_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/keepalive_interval (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_keepalive_interval is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_keepalive_interval() directly.

    YANG Description: Time interval in seconds between transmission of keepalive
messages to the neighbor.  Typically set to 1/3 the
hold-time.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="keepalive-interval",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """keepalive_interval must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="keepalive-interval", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__keepalive_interval = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_keepalive_interval(self):
        self.__keepalive_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="keepalive-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_minimum_advertisement_interval(self):
        """
    Getter method for minimum_advertisement_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/minimum_advertisement_interval (decimal64)

    YANG Description: Minimum time which must elapse between subsequent UPDATE
messages relating to a common set of NLRI being transmitted
to a peer. This timer is referred to as
MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to
reduce the number of UPDATE messages transmitted when a
particular set of NLRI exhibit instability.
    """
        return self.__minimum_advertisement_interval

    def _set_minimum_advertisement_interval(self, v, load=False):
        """
    Setter method for minimum_advertisement_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/minimum_advertisement_interval (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_minimum_advertisement_interval is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_minimum_advertisement_interval() directly.

    YANG Description: Minimum time which must elapse between subsequent UPDATE
messages relating to a common set of NLRI being transmitted
to a peer. This timer is referred to as
MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to
reduce the number of UPDATE messages transmitted when a
particular set of NLRI exhibit instability.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="minimum-advertisement-interval",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """minimum_advertisement_interval must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="minimum-advertisement-interval", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__minimum_advertisement_interval = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_minimum_advertisement_interval(self):
        self.__minimum_advertisement_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="minimum-advertisement-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    connect_retry = __builtin__.property(_get_connect_retry)
    hold_time = __builtin__.property(_get_hold_time)
    keepalive_interval = __builtin__.property(_get_keepalive_interval)
    minimum_advertisement_interval = __builtin__.property(
        _get_minimum_advertisement_interval
    )

    _pyangbind_elements = OrderedDict(
        [
            ("connect_retry", connect_retry),
            ("hold_time", hold_time),
            ("keepalive_interval", keepalive_interval),
            ("minimum_advertisement_interval", minimum_advertisement_interval),
        ]
    )

class state(PybindBase):
    """
  This class was auto-generated by the PythonClass plugin for PYANG
  from YANG module openconfig-network-instance-l2 - based on the path /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state. Each member element of
  the container is represented as a class variable - with a specific
  YANG type.

  YANG Description: State information relating to the timers used for the BGP
group
  """
    __slots__ = (
        "_path_helper",
        "_extmethods",
        "__connect_retry",
        "__hold_time",
        "__keepalive_interval",
        "__minimum_advertisement_interval",
    )

    _yang_name = "state"

    _pybind_generated_by = "container"

    def __init__(self, *args, **kwargs):

        self._path_helper = False

        self._extmethods = False
        self.__connect_retry = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="connect-retry",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__hold_time = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(90),
            is_leaf=True,
            yang_name="hold-time",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__keepalive_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="keepalive-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )
        self.__minimum_advertisement_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="minimum-advertisement-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

        load = kwargs.pop("load", None)
        if args:
            if len(args) > 1:
                raise TypeError("cannot create a YANG container with >1 argument")
            all_attr = True
            for e in self._pyangbind_elements:
                if not hasattr(args[0], e):
                    all_attr = False
                    break
            if not all_attr:
                raise ValueError("Supplied object did not have the correct attributes")
            for e in self._pyangbind_elements:
                nobj = getattr(args[0], e)
                if nobj._changed() is False:
                    continue
                setmethod = getattr(self, "_set_%s" % e)
                if load is None:
                    setmethod(getattr(args[0], e))
                else:
                    setmethod(getattr(args[0], e), load=load)

    def _path(self):
        if hasattr(self, "_parent"):
            return self._parent._path() + [self._yang_name]
        else:
            return [
                "network-instances",
                "network-instance",
                "protocols",
                "protocol",
                "bgp",
                "peer-groups",
                "peer-group",
                "timers",
                "state",
            ]

    def _get_connect_retry(self):
        """
    Getter method for connect_retry, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/connect_retry (decimal64)

    YANG Description: Time interval in seconds between attempts to establish a
session with the peer.
    """
        return self.__connect_retry

    def _set_connect_retry(self, v, load=False):
        """
    Setter method for connect_retry, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/connect_retry (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_connect_retry is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_connect_retry() directly.

    YANG Description: Time interval in seconds between attempts to establish a
session with the peer.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="connect-retry",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """connect_retry must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="connect-retry", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__connect_retry = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_connect_retry(self):
        self.__connect_retry = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="connect-retry",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_hold_time(self):
        """
    Getter method for hold_time, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/hold_time (decimal64)

    YANG Description: Time interval in seconds that a BGP session will be
considered active in the absence of keepalive or other
messages from the peer.  The hold-time is typically
set to 3x the keepalive-interval.
    """
        return self.__hold_time

    def _set_hold_time(self, v, load=False):
        """
    Setter method for hold_time, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/hold_time (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_hold_time is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_hold_time() directly.

    YANG Description: Time interval in seconds that a BGP session will be
considered active in the absence of keepalive or other
messages from the peer.  The hold-time is typically
set to 3x the keepalive-interval.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(90),
                is_leaf=True,
                yang_name="hold-time",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """hold_time must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(90), is_leaf=True, yang_name="hold-time", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__hold_time = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_hold_time(self):
        self.__hold_time = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(90),
            is_leaf=True,
            yang_name="hold-time",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_keepalive_interval(self):
        """
    Getter method for keepalive_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/keepalive_interval (decimal64)

    YANG Description: Time interval in seconds between transmission of keepalive
messages to the neighbor.  Typically set to 1/3 the
hold-time.
    """
        return self.__keepalive_interval

    def _set_keepalive_interval(self, v, load=False):
        """
    Setter method for keepalive_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/keepalive_interval (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_keepalive_interval is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_keepalive_interval() directly.

    YANG Description: Time interval in seconds between transmission of keepalive
messages to the neighbor.  Typically set to 1/3 the
hold-time.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="keepalive-interval",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """keepalive_interval must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="keepalive-interval", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__keepalive_interval = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_keepalive_interval(self):
        self.__keepalive_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="keepalive-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    def _get_minimum_advertisement_interval(self):
        """
    Getter method for minimum_advertisement_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/minimum_advertisement_interval (decimal64)

    YANG Description: Minimum time which must elapse between subsequent UPDATE
messages relating to a common set of NLRI being transmitted
to a peer. This timer is referred to as
MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to
reduce the number of UPDATE messages transmitted when a
particular set of NLRI exhibit instability.
    """
        return self.__minimum_advertisement_interval

    def _set_minimum_advertisement_interval(self, v, load=False):
        """
    Setter method for minimum_advertisement_interval, mapped from YANG variable /network_instances/network_instance/protocols/protocol/bgp/peer_groups/peer_group/timers/state/minimum_advertisement_interval (decimal64)
    If this variable is read-only (config: false) in the
    source YANG file, then _set_minimum_advertisement_interval is considered as a private
    method. Backends looking to populate this variable should
    do so via calling thisObj._set_minimum_advertisement_interval() directly.

    YANG Description: Minimum time which must elapse between subsequent UPDATE
messages relating to a common set of NLRI being transmitted
to a peer. This timer is referred to as
MinRouteAdvertisementIntervalTimer by RFC 4721 and serves to
reduce the number of UPDATE messages transmitted when a
particular set of NLRI exhibit instability.
    """
        if hasattr(v, "_utype"):
            v = v._utype(v)
        try:
            t = YANGDynClass(
                v,
                base=RestrictedPrecisionDecimalType(precision=2),
                default=Decimal(30),
                is_leaf=True,
                yang_name="minimum-advertisement-interval",
                parent=self,
                path_helper=self._path_helper,
                extmethods=self._extmethods,
                register_paths=True,
                namespace="http://openconfig.net/yang/network-instance",
                defining_module="openconfig-network-instance",
                yang_type="decimal64",
                is_config=False,
            )
        except (TypeError, ValueError):
            raise ValueError(
                {
                    "error-string": """minimum_advertisement_interval must be of a type compatible with decimal64""",
                    "defined-type": "decimal64",
                    "generated-type": """YANGDynClass(base=RestrictedPrecisionDecimalType(precision=2), default=Decimal(30), is_leaf=True, yang_name="minimum-advertisement-interval", parent=self, path_helper=self._path_helper, extmethods=self._extmethods, register_paths=True, namespace='http://openconfig.net/yang/network-instance', defining_module='openconfig-network-instance', yang_type='decimal64', is_config=False)""",
                }
            )

        self.__minimum_advertisement_interval = t
        if hasattr(self, "_set"):
            self._set()

    def _unset_minimum_advertisement_interval(self):
        self.__minimum_advertisement_interval = YANGDynClass(
            base=RestrictedPrecisionDecimalType(precision=2),
            default=Decimal(30),
            is_leaf=True,
            yang_name="minimum-advertisement-interval",
            parent=self,
            path_helper=self._path_helper,
            extmethods=self._extmethods,
            register_paths=True,
            namespace="http://openconfig.net/yang/network-instance",
            defining_module="openconfig-network-instance",
            yang_type="decimal64",
            is_config=False,
        )

    connect_retry = __builtin__.property(_get_connect_retry)
    hold_time = __builtin__.property(_get_hold_time)
    keepalive_interval = __builtin__.property(_get_keepalive_interval)
    minimum_advertisement_interval = __builtin__.property(
        _get_minimum_advertisement_interval
    )

    _pyangbind_elements = OrderedDict(
        [
            ("connect_retry", connect_retry),
            ("hold_time", hold_time),
            ("keepalive_interval", keepalive_interval),
            ("minimum_advertisement_interval", minimum_advertisement_interval),
        ]
    )


from functools import wraps

def check_empty(default=""):
    """
    Decorator that checks if a value passed to a Jinja filter evaluates to false
    and returns an empty string. Otherwise calls the original Jinja filter.

    Example usage:
    @check_empty
    def my_jinja_filter(value, arg1):
    """

    def real_decorator(func):

        @wraps(func)
        def wrapper(value, *args, **kwargs):
            if not value:
                return default
            else:
                return func(value, *args, **kwargs)

        return wrapper

    return real_decorator


import ast

from napalm_yang.supported_models import SUPPORTED_MODELS
from napalm_yang.parser import Parser
from napalm_yang.translator import Translator

from napalm.base import validate

from napalm_yang import models

from pyangbind.lib import yangtypes

class Root(object):
    """
    This is a container you can use as root for your other models.

    Examples:

        >>> config = napalm_yang.base.Root()
        >>>
        >>> # Adding models to the object
        >>> config.add_model(napalm_yang.models.openconfig_interfaces())
        >>> config.add_model(napalm_yang.models.openconfig_vlan())

    """
    _yang_type = "container"
    _defining_module = ""

    def __init__(self):
        self._elements = {}

    def elements(self):
        return self._elements

    def add_model(self, model, force=False):
        """
        Add a model.

        The model will be asssigned to a class attribute with the YANG name of the model.

        Args:
            model (PybindBase): Model to add.
            force (bool): If not set, verify the model is in SUPPORTED_MODELS

        Examples:

            >>> import napalm_yang
            >>> config = napalm_yang.base.Root()
            >>> config.add_model(napalm_yang.models.openconfig_interfaces)
            >>> config.interfaces
            <pyangbind.lib.yangtypes.YANGBaseClass object at 0x10bef6680>
        """
        if isinstance(model, str):
            self._load_model(model)
            return

        try:
            model = model()
        except Exception:
            pass

        if model._yang_name not in [a[0] for a in SUPPORTED_MODELS] and not force:
            raise ValueError(
                "Only models in SUPPORTED_MODELS can be added without `force=True`"
            )

        for k, v in model:
            self._elements[k] = v
            setattr(self, k, v)

    def get(self, filter=False):
        """
        Returns a dictionary with the values of the model. Note that the values
        of the leafs are YANG classes.

        Args:
            filter (bool): If set to ``True``, show only values that have been set.

        Returns:
            dict: A dictionary with the values of the model.

        Example:

            >>> pretty_print(config.get(filter=True))
            >>> {
            >>>     "interfaces": {
            >>>         "interface": {
            >>>             "et1": {
            >>>                 "config": {
            >>>                     "description": "My description",
            >>>                     "mtu": 1500
            >>>                 },
            >>>                 "name": "et1"
            >>>             },
            >>>             "et2": {
            >>>                 "config": {
            >>>                     "description": "Another description",
            >>>                     "mtu": 9000
            >>>                 },
            >>>                 "name": "et2"
            >>>             }
            >>>         }
            >>>     }
            >>> }

        """
        result = {}

        for k, v in self.elements().items():
            intermediate = v.get(filter=filter)
            if intermediate:
                result[k] = intermediate

        return result

    def __iter__(self):
        for k, v in self.elements().items():
            yield k, v

    def _load_model(self, model):
        for k, v in SUPPORTED_MODELS:
            if model in v:
                self.add_model(getattr(models, k.replace("-", "_")))
                return

        else:
            raise ValueError("Couldn't find model {}".format(model))

    def load_dict(self, data, overwrite=False, auto_load_model=True):
        """
        Load a dictionary into the model.

        Args:
            data(dict): Dictionary to load
            overwrite(bool): Whether the data present in the model should be overwritten by the
                data in the dict or not.
            auto_load_model(bool): If set to true models will be loaded as they are needed

        Examples:

            >>> vlans_dict = {
            >>>     "vlans": { "vlan": { 100: {
            >>>                             "config": {
            >>>                                 "vlan_id": 100, "name": "production"}},
            >>>                          200: {
            >>>                             "config": {
            >>>                                 "vlan_id": 200, "name": "dev"}}}}}
            >>> config.load_dict(vlans_dict)
            >>> print(config.vlans.vlan.keys())
            ... [200, 100]
            >>> print(100, config.vlans.vlan[100].config.name)
            ... (100, u'production')
            >>> print(200, config.vlans.vlan[200].config.name)
            ... (200, u'dev')
        """
        for k, v in data.items():
            if k not in self._elements.keys() and not auto_load_model:
                raise AttributeError("Model {} is not loaded".format(k))

            elif k not in self._elements.keys() and auto_load_model:
                self._load_model(k)

            attr = getattr(self, k)
            _load_dict(attr, v)

    def to_dict(self, filter=True):
        """
        Returns a dictionary with the values of the model. Note that the values
        of the leafs are evaluated to python types.

        Args:
            filter (bool): If set to ``True``, show only values that have been set.

        Returns:
            dict: A dictionary with the values of the model.

        Example:

            >>> pretty_print(config.to_dict(filter=True))
            >>> {
            >>>     "interfaces": {
            >>>         "interface": {
            >>>             "et1": {
            >>>                 "config": {
            >>>                     "description": "My description",
            >>>                     "mtu": 1500
            >>>                 },
            >>>                 "name": "et1"
            >>>             },
            >>>             "et2": {
            >>>                 "config": {
            >>>                     "description": "Another description",
            >>>                     "mtu": 9000
            >>>                 },
            >>>                 "name": "et2"
            >>>             }
            >>>         }
            >>>     }
            >>> }

        """
        result = {}
        for k, v in self:
            r = _to_dict(v, filter)
            if r:
                result[k] = r
        return result

    def parse_config(self, device=None, profile=None, native=None, attrs=None):
        """
        Parse native configuration and load it into the corresponding models. Only models
        that have been added to the root object will be parsed.

        If ``native`` is passed to the method that's what we will parse, otherwise, we will use the
        ``device`` to retrieve it.

        Args:
            device (NetworkDriver): Device to load the configuration from.
            profile (list): Profiles that the device supports. If no ``profile`` is passed it will
              be read from ``device``.
            native (list of strings): Native configuration to parse.

        Examples:

            >>> # Load from device
            >>> running_config = napalm_yang.base.Root()
            >>> running_config.add_model(napalm_yang.models.openconfig_interfaces)
            >>> running_config.parse_config(device=d)

            >>> # Load from file
            >>> with open("junos.config", "r") as f:
            >>>     config = f.read()
            >>>
            >>> running_config = napalm_yang.base.Root()
            >>> running_config.add_model(napalm_yang.models.openconfig_interfaces)
            >>> running_config.parse_config(native=[config], profile="junos")
        """
        if attrs is None:
            attrs = self.elements().values()

        for v in attrs:
            parser = Parser(
                v, device=device, profile=profile, native=native, is_config=True
            )
            parser.parse()

    def parse_state(self, device=None, profile=None, native=None, attrs=None):
        """
        Parse native state and load it into the corresponding models. Only models
        that have been added to the root object will be parsed.

        If ``native`` is passed to the method that's what we will parse, otherwise, we will use the
        ``device`` to retrieve it.

        Args:
            device (NetworkDriver): Device to load the configuration from.
            profile (list): Profiles that the device supports. If no ``profile`` is passed it will
              be read from ``device``.
            native (list string): Native output to parse.

        Examples:

            >>> # Load from device
            >>> state = napalm_yang.base.Root()
            >>> state.add_model(napalm_yang.models.openconfig_interfaces)
            >>> state.parse_config(device=d)

            >>> # Load from file
            >>> with open("junos.state", "r") as f:
            >>>     state_data = f.read()
            >>>
            >>> state = napalm_yang.base.Root()
            >>> state.add_model(napalm_yang.models.openconfig_interfaces)
            >>> state.parse_config(native=[state_data], profile="junos")
        """
        if attrs is None:
            attrs = self.elements().values()

        for v in attrs:
            parser = Parser(
                v, device=device, profile=profile, native=native, is_config=False
            )
            parser.parse()

    def translate_config(self, profile, merge=None, replace=None):
        """
        Translate the object to native configuration.

        In this context, merge and replace means the following:

        * **Merge** - Elements that exist in both ``self`` and ``merge`` will use by default the
          values in ``merge`` unless ``self`` specifies a new one. Elements that exist only
          in ``self`` will be translated as they are and elements present only in ``merge``
          will be removed.
        * **Replace** - All the elements in ``replace`` will either be removed or replaced by
          elements in ``self``.

        You can specify one of ``merge``, ``replace`` or none of them. If none of them are set we
        will just translate configuration.

        Args:
            profile (list): Which profiles to use.
            merge (Root): Object we want to merge with.
            replace (Root): Object we want to replace.
        """
        result = []
        for k, v in self:
            other_merge = getattr(merge, k) if merge else None
            other_replace = getattr(replace, k) if replace else None
            translator = Translator(
                v, profile, merge=other_merge, replace=other_replace
            )
            result.append(translator.translate())

        return "\n".join(result)

    def compliance_report(self, validation_file="validate.yml"):
        """
        Return a compliance report.
        Verify that the device complies with the given validation file and writes a compliance
        report file. See https://napalm.readthedocs.io/en/latest/validate.html.
        """
        return validate.compliance_report(self, validation_file=validation_file)

def _load_dict(cls, data):
    for k, v in data.items():
        if cls._yang_type == "list":
            try:
                attr = cls[k]
            except KeyError:
                attr = cls.add(k)
            _load_dict(attr, v)
        elif isinstance(v, dict):
            attr = getattr(cls, yangtypes.safe_name(k))
            _load_dict(attr, v)
        else:
            model = getattr(cls, yangtypes.safe_name(k))

            # We can't set attributes that are keys
            if model._is_keyval:
                continue

            setter = getattr(cls, "_set_{}".format(yangtypes.safe_name(k)))
            setter(v)

            model = getattr(model._parent, yangtypes.safe_name(k))
            model._mchanged = True

def _to_dict(element, filter):
    if isinstance(element, Root) or element._yang_type in ("container", None):
        result = _to_dict_container(element, filter)
    elif element._yang_type in ("list",):
        result = _to_dict_list(element, filter)
    else:
        result = _to_dict_leaf(element, filter)

    return result

def _to_dict_container(element, filter):
    result = {}

    for k in element.elements():
        v = getattr(element, k)
        r = _to_dict(v, filter)
        if r not in [None, {}]:
            result[k] = r
    return result

def _to_dict_list(element, filter):
    result = {}

    for k, v in element.items():
        r = _to_dict(v, filter)
        if r not in [None, {}]:
            result[k] = r
    return result

def _to_dict_leaf(element, filter):
    value = None
    if element._changed() or not filter:
        try:
            if hasattr(element, "_list"):
                value = element._list
            else:
                value = ast.literal_eval(element.__repr__())
        except Exception:
            value = element.__repr__()

    return value


from napalm_yang.jinja_filters import ip_filters
from napalm_yang.jinja_filters import json_filters
from napalm_yang.jinja_filters import vlan_filters
from napalm_yang.jinja_filters import mac_filters

JINJA_FILTERS = [ip_filters, json_filters, vlan_filters, mac_filters]

def load_filters():
    """
    Loads and returns all filters.
    """
    all_filters = {}
    for m in JINJA_FILTERS:
        if hasattr(m, "filters"):
            all_filters.update(m.filters())
    return all_filters


from lxml import etree

import itertools

import re

from napalm_yang.parsers.base import BaseParser

class XMLParser(BaseParser):

    @classmethod
    def init_native(cls, native):
        r = []
        for n in native:
            if hasattr(n, "xpath"):
                r.append(n)
            else:
                r.append(etree.fromstring(n))
        return r

    @classmethod
    def _parse_list_default(cls, mapping, data):
        post_process_filter = mapping.pop("post_process_filter", None)

        for element in data.xpath(mapping["xpath"]):
            key = element.xpath(mapping["key"])[0].text.strip()
            if post_process_filter:
                key = cls._parse_post_process_filter(post_process_filter, key)
            yield key, element, {}

    @classmethod
    def _parse_list_nested(cls, mapping, data):
        path = mapping["xpath"].split("/")
        iterators = []
        list_vars = []
        cls._parse_list_nested_recursive(data, path, iterators, list_vars)
        return cls._parse_list_container_helper(mapping, iterators, data, list_vars)

    @classmethod
    def _parse_list_nested_recursive(
        cls, data, path, iterators, list_vars, cur_vars=None
    ):
        """
        This helps parsing shit like:

            <protocols>
                <bgp>
                    <group>
                        <name>my_peers</name>
                        <neighbor>
                            <name>192.168.100.2</name>
                            <description>adsasd</description>
                            <peer-as>65100</peer-as>
                        </neighbor>
                        <neighbor>
                            <name>192.168.100.3</name>
                            <peer-as>65100</peer-as>
                        </neighbor>
                    </group>
                    <group>
                        <name>my_other_peers</name>
                        <neighbor>
                            <name>172.20.0.1</name>
                            <peer-as>65200</peer-as>
                        </neighbor>
                    </group>
                </bgp>
            </protocols>

        """
        cur_vars = dict(cur_vars) if cur_vars else {}
        if path:
            p = path[0]
            path = path[1:]
        else:
            for _ in data:
                list_vars.append(cur_vars)
            iterators.append(data)
            return data

        if p.startswith("?"):
            for x in data:
                key, var_path = p.split(".")
                cur_vars.update({key.lstrip("?"): x.xpath(var_path)[0].text})
                cls._parse_list_nested_recursive(
                    x, path, iterators, list_vars, cur_vars
                )
        else:
            x = data.xpath(p)
            cls._parse_list_nested_recursive(x, path, iterators, list_vars, cur_vars)

    @classmethod
    def _parse_list_container_helper(cls, mapping, iterators, root, list_vars=None):
        mandatory_elements = mapping.pop("mandatory", [])

        iterators = itertools.chain.from_iterable(iterators)

        for element in itertools.chain(iterators, mandatory_elements):
            if isinstance(element, dict):
                yield element["key"], element["block"], element["extra_vars"]
            else:
                key_name = "{}_name".format(
                    root.tag if mapping.get("nested", False) else root[0].tag
                )
                extra_vars = {key_name: element.tag}

                if list_vars:
                    extra_vars.update(list_vars.pop(0))

                composite_key = mapping.get("composite_key", None)
                forced_key = mapping.get("key", None)
                key_attribute = mapping.get("key_attribute", None)
                post_process_filter = mapping.pop("post_process_filter", None)

                if composite_key:
                    key = " ".join([extra_vars[k] for k in composite_key])
                elif forced_key:
                    key = forced_key
                elif key_attribute:
                    key = element.xpath(key_attribute)[0].text.strip()
                else:
                    key = element.tag

                if post_process_filter:
                    key = cls._parse_post_process_filter(
                        post_process_filter, key, extra_vars
                    )

                yield key, element, extra_vars

    @classmethod
    def _parse_list_container(cls, mapping, data):
        data = data.xpath(mapping["xpath"])

        nested = mapping.get("nested", False)
        root = data[0] if nested and len(data) else data

        return cls._parse_list_container_helper(mapping, [root], root)

    @classmethod
    def _parse_leaf_default(
        cls, mapping, data, check_default=True, check_presence=False
    ):
        element = data.xpath(mapping["xpath"])

        if element and not check_presence:
            if "attribute" in mapping.keys():
                element = element[0].get(mapping["attribute"])
            else:
                element = element[0].text.strip()

            regexp = mapping.get("regexp", None)
            if regexp:
                match = re.search(mapping["regexp"], element)
                if match:
                    return match.group("value")
            else:
                return element
        elif element and check_presence:
            return True
        elif check_default:
            return mapping.get("default", None)
        else:
            return None

    @classmethod
    def _parse_leaf_map(cls, mapping, data):
        value = cls._parse_leaf_default(mapping, data)

        if "regex" in mapping.keys():
            value = re.search(mapping["regexp"], value).group("value")

        return mapping["map"][value] if value else None

    @classmethod
    def _parse_leaf_is_present(cls, mapping, data):
        return cls._parse_leaf_default(
            mapping, data, check_default=False, check_presence=True
        )

    @classmethod
    def _parse_leaf_is_absent(cls, mapping, data):
        return not cls._parse_leaf_default(
            mapping, data, check_default=False, check_presence=True
        )


import ast
import copy

from napalm_yang import helpers

def _flatten_dictionary(obj, path, key_name):
    """
    This method tries to use the path `?my_field` to convert:

    a:
        aa: 1
        ab: 2
    b:
        ba: 3
        ba: 4

    into:

    - my_field: a
      aa: 1
      ab: 2
    - my_field: b
      ba: 3
      ba: 4
    """
    result = []
    if ">" in key_name:
        key_name, group_key = key_name.split(">")
    else:
        group_key = None

    for k, v in obj.items():
        if path:
            if k == path[0]:
                path.pop(0)
        if k.startswith("#"):
            continue
        r = _resolve_path(v, list(path))

        if isinstance(r, dict):
            # You can either have a dict here, which means your path is like ?a.b
            # or a list, which means you have a path like ?a.?b
            r = [r]

        for e in r:
            if group_key:
                e[group_key] = {kk: vv for kk, vv in v.items() if kk not in path}
            e[key_name] = k
            result.append(e)
    return result

def _flatten_list(obj, path, key_name):
    new_key, key = key_name.split(":")
    result = []

    for o in obj:
        r = _resolve_path(o, list(path))

        if isinstance(r, dict):
            # XML gets confused when there is a list with only one element
            # and is treated as a dict when converting to a dict
            r = [r]
        old_value = o.pop(key)
        for e in r:
            e[new_key] = old_value
            merged_dict = dict({kk: vv for kk, vv in o.items() if kk not in path})
            merged_dict.update(e)
            result.append(merged_dict)

    return result

def _resolve_path(obj, path):
    if not path:
        return obj
    current = path.pop(0)
    if current[0] != "?":
        if isinstance(obj, dict):
            obj = obj[current]
        elif isinstance(obj, list):
            obj = obj[int(current)]
        return _resolve_path(obj, path)
    else:
        if isinstance(obj, dict) and ":" in current:
            # We assume that this is supposed to be a list but xmldict
            # turned a one element list into a dict
            obj = [obj]

        if isinstance(obj, dict):
            return _flatten_dictionary(obj, path, current[1:])
        elif isinstance(obj, list):
            return _flatten_list(obj, path, current[1:])

def _set_pdb_trace():
    try:
        import ipdb

        ipdb.set_trace()
    except ImportError:
        import pdb

        pdb.set_trace()

class BaseParser(object):

    def __init__(self, keys, extra_vars):
        self.keys = keys
        self.extra_vars = extra_vars

    def resolve_path(self, obj, path, default=None, check_presence=False):
        if path == "":
            return obj
        path = path.split(".")
        try:
            r = _resolve_path(obj, path)
        except (KeyError, IndexError):
            return default

        if check_presence:
            return not bool(path)
        return r

    def init_native(self, native):
        return native

    def parse_list(self, attribute, mapping, bookmarks):
        mapping = copy.deepcopy(mapping)
        mapping = helpers.resolve_rule(
            mapping, attribute, self.keys, self.extra_vars, None, process_all=False
        )
        for m in mapping:
            pdb = m.get("pdb", {})
            if pdb:
                _set_pdb_trace()
                continue
            # parent will change as the tree is processed so we save it
            # so we can restore it
            parent = bookmarks["parent"]

            data = self.resolve_path(bookmarks, m.get("from", "parent"))

            for key, block, extra_vars in self._parse_list_default(attribute, m, data):
                yield key, block, extra_vars

            # we restore the parent
            bookmarks["parent"] = parent

    def parse_leaf(self, attribute, mapping, bookmarks):
        mapping = helpers.resolve_rule(
            mapping, attribute, self.keys, self.extra_vars, None, process_all=False
        )
        for m in mapping:
            pdb = m.get("pdb", {})
            if pdb:
                _set_pdb_trace()
                continue
            data = self.resolve_path(bookmarks, m.get("from", "parent"))
            result = self._parse_leaf_default(attribute, m, data)

            if result is not None:
                try:
                    result = ast.literal_eval(result)
                except (ValueError, SyntaxError):
                    pass
                return result

    def parse_container(self, attribute, mapping, bookmarks):
        mapping = helpers.resolve_rule(
            mapping, attribute, self.keys, self.extra_vars, None, process_all=False
        )
        extra_vars = {}
        for m in mapping:
            pdb = m.get("pdb", {})
            if pdb:
                _set_pdb_trace()
                continue
            # parent will change as the tree is processed so we save it
            # so we can restore it
            parent = bookmarks["parent"]
            data = self.resolve_path(bookmarks, m.get("from", "parent"))
            result, e = self._parse_container_default(attribute, m, data)
            extra_vars.update(e)
            if result:
                break

            # we restore the parent
            bookmarks["parent"] = parent
        return result, extra_vars

    def _parse_post_process_filter(self, post_process_filter, **kwargs):
        kwargs.update(self.keys)
        return helpers.template(
            post_process_filter, extra_vars=self.extra_vars, **kwargs
        )