From ca37afb6e7066ee58743bd060ae758c411d2d14a Mon Sep 17 00:00:00 2001 From: Adar Nimrod <nimrod@shore.co.il> Date: Sat, 14 Aug 2021 21:56:55 +0300 Subject: [PATCH] Small refactor and cleanup. - Change the `run` filter to be a function. Usage should be nicer now: `{{ run(['id', '-u'])['stdout'] }}`. Keep the filter for a few releases to avoid breakage. - A few filters were missing from the README, add the documentation. --- README.rst | 18 ++++++++++++++++-- template/__init__.py | 14 ++++++++++++-- template/filters.py | 32 ++------------------------------ template/functions.py | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 34 deletions(-) create mode 100644 template/functions.py diff --git a/README.rst b/README.rst index d60fbe0..40472bb 100644 --- a/README.rst +++ b/README.rst @@ -81,6 +81,8 @@ Jinja filters The following Jinja filters were added: +- :code:`combine`: Merges 2 dictionaries with the 2nd overriding the 1st. + Returns the result. - :code:`to_yaml`: Convert to yaml (requires the :code:`yaml` package specifier). - :code:`from_yaml`: Convert from yaml (requires the :code:`yaml` package @@ -94,8 +96,8 @@ The following Jinja filters were added: - :code:`jmespath`: Queries data using the `JMESPath <http://jmespath.org/>`_ query language (requires the :code:`jmespath` package specifier). - :code:`run`: Runs a command and returns the stdout, stderr and returncode - using `run - <https://docs.python.org/3.6/library/subprocess.html?highlight=popen#subprocess.run>`_. + using run_. This filter is replaced with the :code:`run` function and will + be removed in the 0.10 release. - :code:`ipaddress`: Returns an IPAddress object from the netaddr_ library (requires the :code:`netaddr` package specifier). - :code:`ipnetwork`: Returns an IPNetwork object from the netaddr_ library @@ -104,10 +106,21 @@ The following Jinja filters were added: (requires the :code:`netaddr` package specifier). - :code:`ipglob`: Returns an IPGlob object from the netaddr_ library (requires the :code:`netaddr` package specifier). +- :code:`ipset`: Returns an IPSet object from the netaddr_ library (requires + the :code:`netaddr` package specifier). Example usage can be seen in :code:`tests` and for specific filters in the docstrings in :code:`template/filters.py`. +Jinja functions +--------------- + +- :code:`run`: Runs a command and returns the stdout, stderr and returncode + using run_. This function replaces the :code:`run` filter. + +Example usage can be seen in :code:`tests` and for specific filters in the +docstrings in :code:`template/functions.py`. + Testing ------- @@ -152,3 +165,4 @@ at: https://git.shore.co.il/nimrod/. .. _netaddr: https://netaddr.readthedocs.io/ .. _Pipenv: https://docs.pipenv.org +.. _run: https://docs.python.org/3.6/library/subprocess.html?highlight=popen#subprocess.run diff --git a/template/__init__.py b/template/__init__.py index c218023..fee78d1 100644 --- a/template/__init__.py +++ b/template/__init__.py @@ -13,6 +13,7 @@ from os import environ import sys import argparse import template.filters +import template.functions # I ignore import errors here and fail on them later in the main function so # the module can be imported by the setup.py with jinja missing so the @@ -31,9 +32,18 @@ def render(template_string): env = Environment(autoescape=True) # Add all functions in template.filters as Jinja filters. # pylint: disable=invalid-name - for tf in filter(lambda x: not x.startswith("_"), dir(template.filters)): + for tf in filter( + lambda x: callable(getattr(template.filters, x)) + and not x.startswith("_"), + dir(template.filters), + ): env.filters[tf] = getattr(template.filters, tf) - t = env.from_string(template_string) + functions = { + x: getattr(template.functions, x) + for x in dir(template.functions) + if callable(getattr(template.functions, x)) and not x.startswith("_") + } + t = env.from_string(template_string, globals=functions) return t.render(environ) diff --git a/template/filters.py b/template/filters.py index 085cf6f..4fb8c8b 100644 --- a/template/filters.py +++ b/template/filters.py @@ -10,6 +10,8 @@ from __future__ import ( unicode_literals, ) # pylint: disable=duplicate-code +from template.functions import run # noqa: F401 pylint: disable=unused-import + def to_yaml(value): r""" @@ -145,36 +147,6 @@ def jmespath(value, query): return jp.search(query, value) -def run(*argv, **kwargs): - """ - Runs a command and returns the stdout, stderr and returncode - using `run - <https://docs.python.org/3.5/library/subprocess.html?highlight=popen#subprocess.run>`_. - >>> run('ls')["returncode"] == 0 - True - >>> 'SHELL' not in run('echo $SHELL', shell=True)['stdout'] - True - >>> run(['ls', 'foo'])['returncode'] > 0 - True - """ - import sys - - if sys.version_info[0] < 3: # nosec - import subprocess32 as subprocess - else: - import subprocess # nosemgrep: rules.bandit.B40 - - defaults = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE} - defaults.update(kwargs) - proc = subprocess.run( # nosec, pylint: disable=subprocess-run-check - *argv, **defaults - ).__dict__ - if "text" not in kwargs or kwargs["text"]: - proc["stdout"] = proc["stdout"].decode() - proc["stderr"] = proc["stderr"].decode() - return proc - - def ipaddress(addr, version=None, flags=0): """ Returns an IPAddress object from the netaddr library. diff --git a/template/functions.py b/template/functions.py new file mode 100644 index 0000000..c219b2e --- /dev/null +++ b/template/functions.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +"""Filters for the template CLI.""" +# pylint: disable=import-error, import-outside-toplevel + + +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) # pylint: disable=duplicate-code + + +def run(*argv, **kwargs): + """ + Runs a command and returns the stdout, stderr and returncode + using `run + <https://docs.python.org/3.5/library/subprocess.html?highlight=popen#subprocess.run>`_. + >>> run('ls')["returncode"] == 0 + True + >>> 'SHELL' not in run('echo $SHELL', shell=True)['stdout'] + True + >>> run(['ls', 'foo'])['returncode'] > 0 + True + """ + import sys + + if sys.version_info[0] < 3: # nosec + import subprocess32 as subprocess + else: + import subprocess # nosemgrep: rules.bandit.B40 + + defaults = {"stdout": subprocess.PIPE, "stderr": subprocess.PIPE} + defaults.update(kwargs) + proc = subprocess.run( # nosec, pylint: disable=subprocess-run-check + *argv, **defaults + ).__dict__ + if "text" not in kwargs or kwargs["text"]: + proc["stdout"] = proc["stdout"].decode() + proc["stderr"] = proc["stderr"].decode() + return proc -- GitLab