Archived documentation version rendered and hosted by DevNetExpertTraining.com
Skip to content

Module scrapli.helper

scrapli.helper

Expand source code
        
"""scrapli.helper"""
import importlib
from io import TextIOWrapper
from pathlib import Path
from shutil import get_terminal_size
from typing import Any, Dict, List, Optional, TextIO, Union
from warnings import warn

import pkg_resources  # pylint: disable=C0411

from scrapli.exceptions import ScrapliValueError
from scrapli.logging import logger


def _textfsm_get_template(platform: str, command: str) -> Optional[TextIO]:
    """
    Find correct TextFSM template based on platform and command executed

    Args:
        platform: ntc-templates device type; i.e. cisco_ios, arista_eos, etc.
        command: string of command that was executed (to find appropriate template)

    Returns:
        None or TextIO of opened template

    Raises:
        N/A

    """
    try:
        importlib.import_module(name=".templates", package="ntc_templates")
        CliTable = getattr(importlib.import_module(name=".clitable", package="textfsm"), "CliTable")
    except ModuleNotFoundError as exc:
        title = "Optional Extra Not Installed!"
        message = (
            "Optional extra 'textfsm' is not installed!\n"
            f"To resolve this issue, install '{exc.name}'. You can do this in one of the following"
            " ways:\n"
            "1: 'pip install -r requirements-textfsm.txt'\n"
            "2: 'pip install scrapli[textfsm]'"
        )
        user_warning(title=title, message=message)
        return None
    template_dir = pkg_resources.resource_filename("ntc_templates", "templates")
    cli_table = CliTable("index", template_dir)
    template_index = cli_table.index.GetRowMatch({"Platform": platform, "Command": command})
    if not template_index:
        logger.warning(
            f"No match in ntc_templates index for platform `{platform}` and command `{command}`"
        )
        return None
    template_name = cli_table.index.index[template_index]["Template"]
    template = open(f"{template_dir}/{template_name}")  # pylint: disable=R1732
    return template


def _textfsm_to_dict(
    structured_output: Union[List[Any], Dict[str, Any]], header: List[str]
) -> Union[List[Any], Dict[str, Any]]:
    """
    Create list of dicts from textfsm output and header

    Args:
        structured_output: parsed textfsm output
        header: list of strings representing column headers for textfsm output

    Returns:
        output: structured data

    Raises:
        N/A

    """
    logger.debug("converting textfsm output to dictionary representation")
    header_lower = [h.lower() for h in header]
    structured_output = [dict(zip(header_lower, row)) for row in structured_output]
    return structured_output


def textfsm_parse(
    template: Union[str, TextIOWrapper], output: str, to_dict: bool = True
) -> Union[List[Any], Dict[str, Any]]:
    """
    Parse output with TextFSM and ntc-templates, try to return structured output

    Args:
        template: TextIOWrapper or string path to template to use to parse data
        output: unstructured output from device to parse
        to_dict: convert textfsm output from list of lists to list of dicts -- basically create dict
            from header and row data so it is easier to read/parse the output

    Returns:
        output: structured data

    Raises:
        N/A

    """
    import textfsm  # pylint: disable=C0415

    if not isinstance(template, TextIOWrapper):
        template_file = open(template)  # pylint: disable=R1732
    else:
        template_file = template
    re_table = textfsm.TextFSM(template_file)
    try:
        structured_output: Union[List[Any], Dict[str, Any]] = re_table.ParseText(output)
        if to_dict:
            structured_output = _textfsm_to_dict(
                structured_output=structured_output, header=re_table.header
            )
        return structured_output
    except textfsm.parser.TextFSMError:
        logger.warning("failed to parse data with textfsm")
    return []


def genie_parse(platform: str, command: str, output: str) -> Union[List[Any], Dict[str, Any]]:
    """
    Parse output with Cisco genie parsers, try to return structured output

    Args:
        platform: genie device type; i.e. iosxe, iosxr, etc.
        command: string of command that was executed (to find appropriate parser)
        output: unstructured output from device to parse

    Returns:
        output: structured data

    Raises:
        N/A

    """
    try:
        Device = getattr(importlib.import_module(name=".conf.base", package="genie"), "Device")
        get_parser = getattr(
            importlib.import_module(name=".libs.parser.utils", package="genie"), "get_parser"
        )
    except ModuleNotFoundError as exc:
        title = "Optional Extra Not Installed!"
        message = (
            "Optional extra 'genie' is not installed!\n"
            f"To resolve this issue, install '{exc.name}'. You can do this in one of the following"
            " ways:\n"
            "1: 'pip install -r requirements-genie.txt'\n"
            "2: 'pip install scrapli[genie]'"
        )
        user_warning(title=title, message=message)
        return []

    genie_device = Device("scrapli_device", custom={"abstraction": {"order": ["os"]}}, os=platform)

    try:
        get_parser(command, genie_device)
        genie_parsed_result = genie_device.parse(command, output=output)
        if isinstance(genie_parsed_result, (list, dict)):
            return genie_parsed_result
    except Exception as exc:  # pylint: disable=W0703
        logger.warning(f"failed to parse data with genie, genie raised exception: `{exc}`")
    return []


def ttp_parse(template: Union[str, TextIOWrapper], output: str) -> Union[List[Any], Dict[str, Any]]:
    """
    Parse output with TTP, try to return structured output

    Args:
        template: TextIOWrapper or string path to template to use to parse data
        output: unstructured output from device to parse

    Returns:
        output: structured data

    Raises:
        N/A

    """
    try:
        ttp = getattr(importlib.import_module(name="ttp"), "ttp")
    except ModuleNotFoundError as exc:
        title = "Optional Extra Not Installed!"
        message = (
            "Optional extra 'ttp' is not installed!\n"
            f"To resolve this issue, install '{exc.name}'. You can do this in one of the following"
            " ways:\n"
            "1: 'pip install -r requirements-ttp.txt'\n"
            "2: 'pip install scrapli[ttp]'"
        )
        user_warning(title=title, message=message)
        return []

    if not isinstance(template, (str, TextIOWrapper)):
        logger.info(f"invalid template `{template}`; template should be string or TextIOWrapper")
        return []

    ttp_parser_template_name = "scrapli_ttp_parse"
    ttp_parser = ttp()
    ttp_parser.add_template(template=template, template_name=ttp_parser_template_name)
    ttp_parser.add_input(data=output, template_name=ttp_parser_template_name)
    ttp_parser.parse()
    ttp_result: Dict[str, List[Any]] = ttp_parser.result(structure="dictionary")
    return ttp_result[ttp_parser_template_name]


def resolve_file(file: str) -> str:
    """
    Resolve file from provided string

    Args:
        file: string path to file

    Returns:
        str: string path to file

    Raises:
        ScrapliValueError: if file cannot be resolved

    """
    if Path(file).is_file():
        return str(Path(file))
    if Path(file).expanduser().is_file():
        return str(Path(file).expanduser())
    raise ScrapliValueError(f"File path `{file}` could not be resolved")


def format_user_warning(title: str, message: str) -> str:
    """
    Nicely format a warning message for users

    Args:
        title: title of the warning message
        message: actual message body

    Returns:
        str: nicely formatted warning

    Raises:
        N/A

    """
    terminal_width = get_terminal_size().columns
    warning_banner_char = "*"

    if len(title) > (terminal_width - 4):
        warning_header = warning_banner_char * terminal_width
    else:
        banner_char_count = terminal_width - len(title) - 2
        left_banner_char_count = banner_char_count // 2
        right_banner_char_count = (
            banner_char_count / 2 if banner_char_count % 2 == 0 else (banner_char_count // 2) + 1
        )
        warning_header = (
            f"{warning_banner_char:{warning_banner_char}>{left_banner_char_count}}"
            f" {title} "
            f"{warning_banner_char:{warning_banner_char}<{right_banner_char_count}}"
        )

    warning_footer = warning_banner_char * terminal_width

    warning_message = (
        "\n\n"
        + warning_header
        + "\n"
        + message.center(terminal_width)
        + "\n"
        + warning_footer
        + "\n"
    )

    return warning_message


def user_warning(title: str, message: str) -> None:
    """
    Nicely raise warning messages for users

    Args:
        title: title of the warning message
        message: actual message body

    Returns:
        None

    Raises:
        N/A

    """
    warning_message = format_user_warning(title=title, message=message)
    logger.warning(warning_message)
    warn(warning_message)
        
    

Functions

format_user_warning

format_user_warning(title: str, message: str) ‑> str

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Nicely format a warning message for users

Args:
    title: title of the warning message
    message: actual message body

Returns:
    str: nicely formatted warning

Raises:
    N/A

genie_parse

genie_parse(platform: str, command: str, output: str) ‑> Union[List[Any], Dict[str, Any]]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Parse output with Cisco genie parsers, try to return structured output

Args:
    platform: genie device type; i.e. iosxe, iosxr, etc.
    command: string of command that was executed (to find appropriate parser)
    output: unstructured output from device to parse

Returns:
    output: structured data

Raises:
    N/A

resolve_file

resolve_file(file: str) ‑> str

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Resolve file from provided string

Args:
    file: string path to file

Returns:
    str: string path to file

Raises:
    ScrapliValueError: if file cannot be resolved

textfsm_parse

textfsm_parse(template: Union[str, _io.TextIOWrapper], output: str, to_dict: bool = True) ‑> Union[List[Any], Dict[str, Any]]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Parse output with TextFSM and ntc-templates, try to return structured output

Args:
    template: TextIOWrapper or string path to template to use to parse data
    output: unstructured output from device to parse
    to_dict: convert textfsm output from list of lists to list of dicts -- basically create dict
        from header and row data so it is easier to read/parse the output

Returns:
    output: structured data

Raises:
    N/A

ttp_parse

ttp_parse(template: Union[str, _io.TextIOWrapper], output: str) ‑> Union[List[Any], Dict[str, Any]]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Parse output with TTP, try to return structured output

Args:
    template: TextIOWrapper or string path to template to use to parse data
    output: unstructured output from device to parse

Returns:
    output: structured data

Raises:
    N/A

user_warning

user_warning(title: str, message: str) ‑> NoneType

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Nicely raise warning messages for users

Args:
    title: title of the warning message
    message: actual message body

Returns:
    None

Raises:
    N/A