diff --git a/.travis.yml b/.travis.yml index 1c90cea60547bf8e2f375e7a7f0d43acc3c97764..34c476c17af9bcc10a789ea221120cd8159feaeb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,10 @@ sudo: false cache: - pip matrix: - allow_failure: + include: + - python: "3.5" + env: TOXENV=docs + allow_failures: - python: "3.2" install: diff --git a/README.rst b/README.rst index 53af308f5b1084483f358efccb7819b700fade56..1447ebb40278758bb8fe919e1d818c0582cd1b7c 100644 --- a/README.rst +++ b/README.rst @@ -47,8 +47,11 @@ The following Jinja filters were added: - :code:`combine`: Combine 2 dictionaries. - :code:`to_toml`: Convert to toml. - :code:`from_toml`: Convert from toml. +- :code:`jmespath`: Queries data using the `JMESPath <http://jmespath.org/>`_ + query language. -Example usage can be seen in :code:`tests.sh`. +Example usage can be seen in :code:`tests.sh` and for specific filters in the +docstrings in :code:`template/filters.py`. Testing ------- @@ -81,11 +84,8 @@ at: https://www.shore.co.il/git/. TODO ---- -- Add unit tests of filters using doctest. -- Fix combining dictionaries test. -- Fix Travis CI test on Python 3.2 (https://travis-ci.org/adarnimrod/template/jobs/187388235). +- Fix test failure on Python 3.2 + (https://travis-ci.org/adarnimrod/template/jobs/194581463). - Release on tagged commits to PyPI in Travis CI (https://docs.travis-ci.com/user/deployment/pypi/ and https://docs.travis-ci.com/user/encryption-keys/). -- Add JMESPath support. -- Add TOML support? diff --git a/setup.py b/setup.py index de9bd4e97030c70463a97c7ee37bd6651fb01de1..c39c646fa2d92a1bce7447e1ebd125c152a53a78 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup( ], keywords='config configuration jinja template environment', packages=find_packages(), - install_requires=['Jinja2', 'PyYAML', 'toml'], + install_requires=['Jinja2', 'PyYAML', 'jmespath', 'toml'], extras_require={ 'dev': ['tox'], }, entry_points={ diff --git a/template/filters.py b/template/filters.py index bac6b5b5162ebdf61f096b78f6efe2f796d88027..c2b99563f6ddd5d5f3755cc567ce97691051fcba 100644 --- a/template/filters.py +++ b/template/filters.py @@ -1,42 +1,149 @@ #!/usr/bin/env python +from __future__ import (absolute_import, division, + print_function, unicode_literals) def to_yaml(value): + ''' + Converts given data structure to YAML form. + Examples: + + >>> to_yaml([1,2,3]) + '[1, 2, 3]\\n' + >>> to_yaml({'a': 1, 'b': 2}) + '{a: 1, b: 2}\\n' + >>> to_yaml({1: {'a': [1,2,3]}}) + '1:\\n a: [1, 2, 3]\\n' + >>> to_yaml("abc") + 'abc\\n...\\n' + ''' from yaml import safe_dump return safe_dump(value) def to_json(value): + ''' + Converts given data structure to JSON form. + Examples: + + >>> to_json([1,2,3]) + '[1, 2, 3]' + >>> to_json({'b':2}) + '{"b": 2}' + >>> to_json(2) + '2' + >>> to_json({1: {'a': [1,2,3]}}) + '{"1": {"a": [1, 2, 3]}}' + ''' from json import dumps return dumps(value) def from_json(value): + ''' + Returns native data structure from the given JSON string. + Examples: + + >>> import six + >>> from_json('[1, 2, 3]') + [1, 2, 3] + >>> from_json('"a"') == six.text_type('a') + True + >>> from_json('{"1": {"a": [1, 2, 3]}}') == {'1': {'a': [1, 2, 3]}} + True + ''' from json import loads return loads(value) def from_yaml(value): + ''' + Returns native data structure from the given YAML string. + Examples: + + >>> from_yaml('a') + 'a' + >>> from_yaml('[1, 2, 3]') + [1, 2, 3] + >>> from_yaml('{"1": {"a": [1, 2, 3]}}') + {'1': {'a': [1, 2, 3]}} + ''' from yaml import safe_load return safe_load(value) def pprint(value): + ''' + Returns a pretty string representation of the data structure given. + Examples: + >>> pprint(1) + '1' + >>> import six + >>> output = pprint([{'first_name': 'John', 'last_name': 'Doe'}, {'first_name': 'Jane', 'last_name': 'Doe'}]) # noqa: E501 + >>> if six.PY3: + ... output == "[{'first_name': 'John', 'last_name': 'Doe'},\\n {'first_name': 'Jane', 'last_name': 'Doe'}]" + ... elif six.PY2: + ... output == "[{u'first_name': u'John', u'last_name': u'Doe'},\\n {u'first_name': u'Jane', u'last_name': u'Doe'}]" + ... + True + ''' from pprint import pformat return pformat(value) def combine(default, override): + ''' + Returns a combined dictionary of the 2 dictionaries given (with the 2nd + overriding the 1st). + Examples: + >>> combine({'a': 1, 'b': 2}, {'b': 3, 'c': 4}) == {'a': 1, 'b': 3, 'c': 4} + True + ''' combined = default.copy() combined.update(override) return combined def from_toml(value): + ''' + Returns a data structure from the TOML string given. + Examples: + >>> from_toml('[table]\\nkey = "value"\\n') == {'table': {'key': 'value'}} + True + ''' from toml import loads return loads(value) def to_toml(value): + ''' + Returns a string of the TOML representation for the data structure given. + Examples: + >>> import six + >>> to_toml({'key': [1, 2]}) == six.text_type("key = [ 1, 2,]\\n") + True + ''' from toml import dumps return dumps(value) + + +def jmespath(value, query): + ''' + Queries the data using the JMESPath query language. + Examples: + >>> import six + >>> locations = [{'name': 'Seattle', 'state': 'WA'}, + ... {"name": "New York", "state": "NY"}, + ... {"name": "Bellevue", "state": "WA"}, + ... {"name": "Olympia", "state": "WA"}] + >>> query = "[?state == 'WA'].name | sort(@) | {WashingtonCities: join(', ', @)}" # noqa: E501 + >>> WACities = jmespath(locations, query) + >>> if six.PY2: + ... WACities == {u'WashingtonCities': u'Bellevue, Olympia, Seattle'} + ... elif six.PY3: + ... WACities == {'WashingtonCities': 'Bellevue, Olympia, Seattle'} + ... + True + ''' + import jmespath + return jmespath.search(query, value) diff --git a/tests.sh b/tests.sh index a2797ea2b433296cee19de948f4687b5a47401ff..d329d33f939fd7cfe4975a21a033357c9b3c433b 100755 --- a/tests.sh +++ b/tests.sh @@ -14,39 +14,4 @@ export name='John' template --output "$outfile" "$infile" test "$(cat $outfile)" = "$name" -echo Testing JSON parsing. -export json='{"a": 1, "b": 2}' -echo '{{ (json|from_json)["a"] }}' > "$infile" -test "$(template $infile)" = "1" - -echo Testing JSON output. -echo '{{ [1, 1+2, 3] | to_json }}' > "$infile" -test "$(template $infile)" = '[1, 3, 3]' - -echo Testing YAML parsing. -export yaml='a: 1 -b: 2' -echo '{{ (yaml|from_yaml)["a"] }}' > "$infile" -test "$(template $infile)" = "1" - -echo Testing YAML output. -echo '{{ [1, 1+2, 3] | to_yaml }}' > "$infile" -test "$(template $infile)" = '[1, 3, 3]' - -echo Testing pprint. -echo '{{ [1, ] + [2, ] }}' > "$infile" -test "$(template $infile)" = "[1, 2]" - -# echo Testing combining dictionaries. -# echo '{{ {"a": 1, "b": 2}|combine({"a": 11, "c": 33}) }}' > "$infile" -# test "$(template $infile)" = "{'a': 11, 'c': 33, 'b': 2}" - -echo Testing TOML parsing. -echo '{{ "[table]\n key = value" | from_toml }}' > "$infile" -test "$(template $infile)" = "table = {'key': 'value'}" - -echo Testing TOML output. -echo "{{ {'key': [1, 2]} | to_toml }}" > "$infile" -test "$(template $infile)" = "key = [ 1, 2,]" - rm "$infile" "$outfile" diff --git a/tox.ini b/tox.ini index 719a30a718fdc97aa0bcfb6d9ec7e9bfb9820fa7..772e7c959ff9ff451888b69dcb978d573d9a8b95 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{2,3} +envlist = py{2,3},docs [travis] python = @@ -17,12 +17,18 @@ deps = check-manifest readme_renderer flake8 + six commands = check-manifest --ignore tox.ini,tests* - python setup.py check -m -r -s flake8 . + python -m doctest template/filters.py template/__init__.py ./tests.sh +[testenv:docs] +basepython = python +deps = readme_renderer +commands = python setup.py check -m -r -s + [testenv:release] basepython = python whitelist_externals =