Skip to content

API Reference

ttp_templates.parse_output(data: str, platform: Optional[str] = None, command: Optional[str] = None, path: Optional[str] = None, yang: Optional[str] = None, misc: Optional[str] = None, get: Optional[str] = None, structure: Optional[str] = 'list', template_vars: Optional[Dict] = None) -> Union[Dict, List]

Load a template and parse the provided data string with TTP.

path argument is always preferred over other arguments.

Valid combinations of template location arguments:

  • path="./misc/foo/bar.txt"
  • platform="cisco_ios", command="show version"
  • yang="ietf-interfaces", platform="cisco_ios"
  • misc="foo_folder/bar_template.txt"
  • get="inventory", platform="cisco_xr"

Parameters:

  • data (str) –

    Raw text data to parse.

  • path (Optional[str]) –

    OS path or ttp:// URI to the template file to load.

  • platform (Optional[str]) –

    Platform name to load the template for, e.g. cisco_ios. Required when get is used — it selects the platform-specific input inside the getter template (e.g. "cisco_xr", "arista_eos").

  • command (Optional[str]) –

    CLI command to load the template for, e.g. show ip arp.

  • yang (Optional[str]) –

    YANG module name to load the template for, e.g. ietf-interfaces.

  • misc (Optional[str]) –

    Path to the template relative to the repository misc folder.

  • get (Optional[str]) –

    Name of the getter template, e.g. "inventory". Works similarly to NAPALM getters — a single getter bundles platform-specific parsing logic and returns a normalized, platform-agnostic structure. platform must also be supplied so the correct input section is selected.

  • structure (Optional[str]) –

    Output structure format - list, dictionary, or flat_list.

  • template_vars (Optional[Dict]) –

    Additional variables to pass into the TTP template object.

Returns:

  • Union[Dict, List]

    Parsed results as a dict or list, depending on the structure argument.

Raises:

  • ValueError

    If no valid template-locating argument combination is provided, or if get is used without platform.

  • RuntimeError

    If get is used and none of the getter's inputs support the specified platform.

Source code in ttp_templates\ttp_templates.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
def parse_output(
    data: str,
    platform: Optional[str] = None,
    command: Optional[str] = None,
    path: Optional[str] = None,
    yang: Optional[str] = None,
    misc: Optional[str] = None,
    get: Optional[str] = None,
    structure: Optional[str] = "list",
    template_vars: Optional[Dict] = None,
) -> Union[Dict, List]:
    """Load a template and parse the provided data string with TTP.

    ``path`` argument is always preferred over other arguments.

    Valid combinations of template location arguments:

    * ``path="./misc/foo/bar.txt"``
    * ``platform="cisco_ios", command="show version"``
    * ``yang="ietf-interfaces", platform="cisco_ios"``
    * ``misc="foo_folder/bar_template.txt"``
    * ``get="inventory", platform="cisco_xr"``

    Args:
        data: Raw text data to parse.
        path: OS path or ``ttp://`` URI to the template file to load.
        platform: Platform name to load the template for, e.g. ``cisco_ios``.
            **Required** when ``get`` is used — it selects the platform-specific
            input inside the getter template (e.g. ``"cisco_xr"``, ``"arista_eos"``).
        command: CLI command to load the template for, e.g. ``show ip arp``.
        yang: YANG module name to load the template for, e.g. ``ietf-interfaces``.
        misc: Path to the template relative to the repository ``misc`` folder.
        get: Name of the getter template, e.g. ``"inventory"``. Works similarly to
            NAPALM getters — a single getter bundles platform-specific parsing logic
            and returns a normalized, platform-agnostic structure. ``platform`` must
            also be supplied so the correct input section is selected.
        structure: Output structure format - ``list``, ``dictionary``, or
            ``flat_list``.
        template_vars: Additional variables to pass into the TTP template object.

    Returns:
        Parsed results as a dict or list, depending on the ``structure`` argument.

    Raises:
        ValueError: If no valid template-locating argument combination is provided,
            or if ``get`` is used without ``platform``.
        RuntimeError: If ``get`` is used and none of the getter's inputs support
            the specified ``platform``.
    """
    template_vars = template_vars or {}

    log.debug(
        "parse_output: loading template (platform=%r, command=%r, "
        "path=%r, yang=%r, misc=%r)",
        platform,
        command,
        path,
        yang,
        misc,
    )

    # retrieve the template text using the provided locator arguments
    template = get_template(
        platform=platform, command=command, path=path, yang=yang, misc=misc, get=get
    )

    if template is None:
        raise ValueError(
            "parse_output: no valid template-locating argument combination was "
            "provided; supply one of: path, platform+command, yang+platform, or misc."
        )

    log.debug("parse_output: creating TTP parser, structure=%r", structure)

    # handle getter if platform is given
    if get:
        if not platform:
            raise ValueError(f"'{get}' getter need platform name to parse provided data")
        parser = ttp(template=template, vars=template_vars)
        # sort input data across inputs
        input_found = False
        for template_name, inputs in parser.get_input_load().items():
            for input_name, params in inputs.items():
                if platform in params.get("platform", []):
                    parser.add_input(template_name=template_name, input_name=input_name, data=data)
                    input_found = True
                    break
            if input_found:
                break
        else:
            raise RuntimeError(f"None of the '{get}' getter inputs support platform '{platform}'")
        # parse the data and return result only for template with matched input
        parser.parse(one=True)
        results = parser.result(structure="dictionary")
        results = results[template_name]
    else:
        # parse the data
        parser = ttp(data=data, template=template, vars=template_vars)
        parser.parse(one=True)
        results = parser.result(structure=structure)

    log.debug("parse_output: parsing complete")
    return results

ttp_templates.get_template(path: Optional[str] = None, platform: Optional[str] = None, command: Optional[str] = None, yang: Optional[str] = None, misc: Optional[str] = None, get: Optional[str] = None) -> Optional[str]

Locate a template file and return its content.

path argument is always preferred over other arguments.

Valid combinations of template location arguments:

  • path="./misc/foo/bar.txt"
  • platform="cisco_ios", command="show version"
  • yang="ietf-interfaces", platform="cisco_ios"
  • misc="foo_folder/bar_template.txt"
  • get="foo_folder/bar_template.txt"

Parameters:

  • path (Optional[str]) –

    OS path or ttp:// URI to the template file to load.

  • platform (Optional[str]) –

    Platform name to load the template for, e.g. cisco_ios.

  • command (Optional[str]) –

    CLI command to load the template for, e.g. show ip arp.

  • yang (Optional[str]) –

    YANG module name to load the template for, e.g. ietf-interfaces.

  • misc (Optional[str]) –

    Path to the template relative to the repository misc folder.

  • get (Optional[str]) –

    Name of the getter template e.g. "inventory"

Returns:

  • Optional[str]

    Template file content as a string, or None if no valid argument

  • Optional[str]

    combination was provided.

Source code in ttp_templates\ttp_templates.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def get_template(
    path: Optional[str] = None,
    platform: Optional[str] = None,
    command: Optional[str] = None,
    yang: Optional[str] = None,
    misc: Optional[str] = None,
    get: Optional[str] = None,
) -> Optional[str]:
    """Locate a template file and return its content.

    ``path`` argument is always preferred over other arguments.

    Valid combinations of template location arguments:

    * ``path="./misc/foo/bar.txt"``
    * ``platform="cisco_ios", command="show version"``
    * ``yang="ietf-interfaces", platform="cisco_ios"``
    * ``misc="foo_folder/bar_template.txt"``
    * ``get="foo_folder/bar_template.txt"``

    Args:
        path: OS path or ``ttp://`` URI to the template file to load.
        platform: Platform name to load the template for, e.g. ``cisco_ios``.
        command: CLI command to load the template for, e.g. ``show ip arp``.
        yang: YANG module name to load the template for, e.g. ``ietf-interfaces``.
        misc: Path to the template relative to the repository ``misc`` folder.
        get: Name of the getter template e.g. "inventory"

    Returns:
        Template file content as a string, or ``None`` if no valid argument
        combination was provided.
    """
    # resolve the relative path to the template file based on provided arguments
    if path:
        # strip the optional "ttp://" scheme prefix used by TTP library
        if path.strip().startswith("ttp://"):
            path = path.strip()[6:]
        log.debug("get_template: using explicit path '%s'", path)
    elif get:
        path = "get/{}".format(get) if get.endswith(".txt") else "get/{}.txt".format(get)
        log.debug("get_template: resolved get path '%s'", path)
    elif platform and command:
        platform = platform.lower()
        command = command.lower()
        # replace pipe symbol with the word "pipe" to form a valid filename
        command = command.replace("|", "pipe")
        for symbol in [" ", "-"]:
            platform = platform.replace(symbol, "_")
            command = command.replace(symbol, "_")
        path = "platform/{}_{}.txt".format(platform, command)
        log.debug("get_template: resolved platform+command path '%s'", path)
    elif platform and yang:
        platform = platform.lower()
        yang = yang.lower()
        for symbol in [" "]:
            platform = platform.replace(symbol, "_")
            yang = yang.replace(symbol, "_")
        path = "yang/{}_{}.txt".format(yang, platform)
        log.debug("get_template: resolved yang+platform path '%s'", path)
    elif misc:
        path = "misc/{}".format(misc)
        log.debug("get_template: resolved misc path '%s'", path)
    else:
        log.warning(
            "get_template: no valid argument combination provided, " "returning None"
        )
        return None

    template_dir = os.path.abspath(os.path.dirname(__file__))
    # Resolve symlinks and ".." segments so the containment check is reliable
    template_filename = os.path.realpath(os.path.join(template_dir, path))

    # Ensure the resolved path stays inside the package directory to prevent
    # path-traversal attacks (e.g. path="../../etc/passwd").
    if not template_filename.startswith(template_dir + os.sep):
        raise ValueError(
            f"Template path '{path}' resolves outside the package directory."
        )

    log.debug("get_template: loading template file '%s'", template_filename)

    # read and return the template file content
    with open(template_filename, mode="r", encoding="utf-8") as f:
        content = f.read()

    log.debug(
        "get_template: loaded template '%s', %d characters",
        template_filename,
        len(content),
    )
    return content

ttp_templates.list_templates(pattern: str = '*') -> Dict

List available templates whose filenames match the given glob pattern.

The primary use case for this function is to simplify integration with other applications by providing a programmatic API to enumerate all available TTP templates.

Parameters:

  • pattern (str) –

    Glob pattern used to filter template filenames. Defaults to "*" which matches all templates.

Returns:

  • Dict

    Dictionary with three top-level keys:

  • Dict
    • platform - list of matching platform template filenames.
  • Dict
    • yang - list of matching YANG template filenames.
  • Dict
    • misc - nested dict mirroring the misc/ directory hierarchy, with leaf values being lists of matching filenames.
Source code in ttp_templates\ttp_templates.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
def list_templates(pattern: str = "*") -> Dict:
    """List available templates whose filenames match the given glob pattern.

    The primary use case for this function is to simplify integration with
    other applications by providing a programmatic API to enumerate all
    available TTP templates.

    Args:
        pattern: Glob pattern used to filter template filenames. Defaults to
            ``"*"`` which matches all templates.

    Returns:
        Dictionary with three top-level keys:

        * ``platform`` - list of matching platform template filenames.
        * ``yang`` - list of matching YANG template filenames.
        * ``misc`` - nested dict mirroring the ``misc/`` directory hierarchy,
            with leaf values being lists of matching filenames.
    """
    res: Dict = {
        "platform": [],
        "yang": [],
        "misc": {},
        "get": [],
    }
    # filenames to exclude regardless of the caller's pattern
    skip_files = ["readme.md"]
    paths = ["platform", "yang", "misc", "get"]
    ttp_templates_dir = os.path.abspath(os.path.dirname(__file__))

    log.debug(
        "list_templates: scanning '%s' with pattern '%s'", ttp_templates_dir, pattern
    )

    for path in paths:
        dirname = os.path.join(ttp_templates_dir, path)
        for dirpath, dirnames, filenames in os.walk(dirname):
            # build a list of folder names relative to the package root
            # e.g. "/platform" → ["platform"], "/misc/N2G/cli_ip_data" → ["misc", "N2G", "cli_ip_data"]
            dirpath_items = dirpath.replace(ttp_templates_dir, "").split(os.sep)[1:]

            # skip directories that contain no files
            if not filenames:
                continue

            # keep only files that match the caller's glob pattern and are not in the skip list;
            # sort the result so the output is deterministic regardless of filesystem ordering
            files = sorted(
                filename
                for filename in filenames
                if (
                    fnmatchcase(filename, pattern)
                    and filename.lower() not in skip_files
                )
            )

            # traverse the result dict to the correct nesting level and store the file list
            ref = res
            for index, item in enumerate(dirpath_items):
                if index + 1 == len(dirpath_items):
                    # Reached the leaf directory: store the matching files list.
                    # Guard against overwriting an existing sub-dict, which would
                    # happen if a template file sits directly inside misc/ alongside
                    # subdirectories (e.g. misc/my_template.txt).  In that case we
                    # store the files under the empty-string key so the dict
                    # structure is preserved and subsequent iterations don't crash.
                    if isinstance(ref.get(item), dict):
                        ref[item][""] = files
                    else:
                        ref[item] = files
                else:
                    # descend one level deeper, creating intermediate dicts as needed
                    ref = ref.setdefault(item, {})

    log.debug("list_templates: scan complete")
    return res
Back to top