From b1adc7aa2dbbd68b14e5a18f71be3cc3a313cc3e Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 13:53:44 -0600 Subject: [PATCH 01/32] adding all important readme image. --- README.md | 2 ++ transmogrifydict.png | Bin 0 -> 4207 bytes 2 files changed, 2 insertions(+) create mode 100644 transmogrifydict.png diff --git a/README.md b/README.md index d2beabb..aaf4829 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ The "turn a dict from one API into a dict for another" python module. +![That dict is so cool...](/transmogrifydict.png) + ## methods * `resolve_path_to_value(source, path)` - fetch a value out of `source` using `path` as the pointer to the desired value. see docstring for path string formats. diff --git a/transmogrifydict.png b/transmogrifydict.png new file mode 100644 index 0000000000000000000000000000000000000000..014f94e4a8290e4e3c3378cbf19743ceac525c19 GIT binary patch literal 4207 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 Date: Fri, 4 Aug 2017 15:01:01 -0600 Subject: [PATCH 02/32] adding first couple of tests and setup test running. #2 --- .gitignore | 1 + requirements.txt | 1 + setup.py | 11 +++++-- tests/__init__.py | 0 tests/helpers.py | 28 +++++++++++++++++ tests/test_resolve_mapping_to_dict.py | 1 + tests/test_resolve_path_to_value.py | 43 +++++++++++++++++++++++++++ 7 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 requirements.txt create mode 100644 tests/__init__.py create mode 100644 tests/helpers.py create mode 100644 tests/test_resolve_mapping_to_dict.py create mode 100644 tests/test_resolve_path_to_value.py diff --git a/.gitignore b/.gitignore index 73cd9bd..d1feb9c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist *.egg-info venv +venvpy3 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d270018 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six>=1.10.0,<2.0.0 diff --git a/setup.py b/setup.py index 9dad9ac..8b5a00b 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,9 @@ -from setuptools import setup, find_packages +from setuptools import setup +import unittest + + +def test_suite(): + return unittest.TestLoader().discover('tests', pattern='test_*.py') setup( name='transmogrifydict', @@ -7,5 +12,7 @@ description='The "turn a dict from one API into a dict for another" python module.', author='Emergence by Design', author_email='support@emergence.com', - py_modules=['transmogrifydict'] + py_modules=['transmogrifydict'], + test_suite='setup.test_suite', + install_requires=[x for x in open('./requirements.txt').read().split('\n') if x] ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 0000000..9ab4472 --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,28 @@ +from unittest import TestCase +import six +if six.PY3: + from functools import partialmethod +else: + from functools import partial + + # noinspection PyPep8Naming + class partialmethod(partial): + def __get__(self, instance, owner): + if instance is None: + return self + return partial(self.func, instance, + *(self.args or ()), **(self.keywords or {})) + + +class BaseUnitTest(TestCase): + def run_test(self, method, output, **kwargs): + actual = method(**kwargs) + self.assertEqual(output, actual) + + +class KwargsToOutputDynamicTestsMetaClass(type): + def __new__(cls, name, bases, dict): + method = dict.get('run_test', BaseUnitTest.run_test) + for name, args in dict['tests'].items(): + dict['test_%s' % (name, )] = partialmethod(method, dict['func'], args['output'], **args['kwargs']) + return type(name, bases, dict) diff --git a/tests/test_resolve_mapping_to_dict.py b/tests/test_resolve_mapping_to_dict.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/test_resolve_mapping_to_dict.py @@ -0,0 +1 @@ + diff --git a/tests/test_resolve_path_to_value.py b/tests/test_resolve_path_to_value.py new file mode 100644 index 0000000..588ce15 --- /dev/null +++ b/tests/test_resolve_path_to_value.py @@ -0,0 +1,43 @@ +from collections import OrderedDict + +from tests.helpers import BaseUnitTest, KwargsToOutputDynamicTestsMetaClass +from transmogrifydict import resolve_path_to_value +from six import with_metaclass + + +class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaClass, BaseUnitTest)): + func = resolve_path_to_value + tests = OrderedDict(( + ( + 'simple', + { + 'kwargs': { + 'source': { + 'one': 1, + 'two': 2, + 'three': 3 + }, + 'path': 'two' + }, + 'output': (True, 2) + } + ), + ( + 'with_sub_keys', + { + 'kwargs': { + 'source': { + 'one': 1, + 'two': { + 'sleeping': 'bereft of life', + 'pining for the fjords': 'has ceased to be', + 'stunned': 'an ex-parrot', + }, + 'three': 3 + }, + 'path': 'two.sleeping' + }, + 'output': (True, 'bereft of life') + } + ) + )) From bf967ec24c269844a263c138ffa5c5142e92f299 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 15:10:17 -0600 Subject: [PATCH 03/32] make library work in python 3. closes #1 --- .gitignore | 1 + transmogrifydict.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d1feb9c..f3f9a84 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist *.egg-info venv venvpy3 +.eggs diff --git a/transmogrifydict.py b/transmogrifydict.py index cb6842f..246be47 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -1,4 +1,5 @@ import json +import six def resolve_path_to_value(source, path): @@ -16,7 +17,7 @@ def resolve_path_to_value(source, path): key[Key~SubKey=Value] if the substring `Value` `isdigit()`, we look for an `int` version. You can wrap `'8'` into `'"8"'` to find the - basestring version. + six.string_types version. examples: >>> source_dict = { @@ -77,7 +78,7 @@ def resolve_path_to_value(source, path): :param source: potentially holds the desired value :type source: dict :param path: points to the desired value - :type path: basestring + :type path: six.string_types :returns: a boolean indicating found status, the value that was found :rtype: tuple :raises ValueError: if we don't understand what went inside some square brackets. @@ -88,7 +89,7 @@ def resolve_path_to_value(source, path): for path_part in path.split('.'): parts = path_part.split('[') try: - if isinstance(mapped_value, basestring): + if isinstance(mapped_value, six.string_types): # ugh, maybe it is json? try: mapped_value = json.loads(mapped_value) @@ -119,7 +120,7 @@ def resolve_path_to_value(source, path): try: while sub_keys: sub_item = sub_item[sub_keys.pop(0)] - except KeyError, IndexError: + except (KeyError, IndexError): pass else: if sub_item == find_value: From abe6702f52a55ee06f37d82f4c117932c41fe6c2 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 15:16:14 -0600 Subject: [PATCH 04/32] attribute code from gist for older python partialmethod. use import error instead of version checking. #1 --- tests/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/helpers.py b/tests/helpers.py index 9ab4472..e48d414 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,8 +1,8 @@ from unittest import TestCase -import six -if six.PY3: +try: from functools import partialmethod -else: +except ImportError: + # Partial method for Python 2.7 - https://gist.github.com/carymrobbins/8940382 from functools import partial # noinspection PyPep8Naming From 7b9adbe3a8dc6707d1f1e5488a7ef98e3209a64f Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 15:25:01 -0600 Subject: [PATCH 05/32] add master branch build status badges to readme. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index aaf4829..8728250 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ The "turn a dict from one API into a dict for another" python module. ![That dict is so cool...](/transmogrifydict.png) +Py2: [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2) +Py3: [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3) + ## methods * `resolve_path_to_value(source, path)` - fetch a value out of `source` using `path` as the pointer to the desired value. see docstring for path string formats. From ca7fbdd2f344efb7b7f8fd5cda8be017ffdd3cbe Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 15:41:54 -0600 Subject: [PATCH 06/32] add previous doctests to unittests. #2 --- tests/test_resolve_path_to_value.py | 326 +++++++++++++++++++++++++++- transmogrifydict.py | 2 +- 2 files changed, 326 insertions(+), 2 deletions(-) diff --git a/tests/test_resolve_path_to_value.py b/tests/test_resolve_path_to_value.py index 588ce15..656d182 100644 --- a/tests/test_resolve_path_to_value.py +++ b/tests/test_resolve_path_to_value.py @@ -39,5 +39,329 @@ class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaCl }, 'output': (True, 'bereft of life') } - ) + ), + ( + 'with_sub_keys_with_spaces', + { + 'kwargs': { + 'source': { + 'one': 1, + 't w o': { + 'sleeping': 'bereft of life', + 'pining for the fjords': 'has ceased to be', + 'stunned': 'an ex-parrot', + }, + 'three': 3 + }, + 'path': 't w o.pining for the fjords' + }, + 'output': (True, 'has ceased to be') + } + ), + ( + 'docstring_test_1', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'first_key' + }, + 'output': (True, 'a') + } + ), + ( + 'docstring_test_2', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'second_key[1]' + }, + 'output': (True, 'y') + } + ), + ( + 'docstring_test_3', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'third_key[b=3]' + }, + 'output': (True, {'h': 'qw"er', 'c': 4, 'b': 3}) + } + ), + ( + 'docstring_test_4', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'third_key[h="qw"er"]' + }, + 'output': (True, {'h': 'qw"er', 'c': 4, 'b': 3}) + } + ), + ( + 'docstring_test_5', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'third_key[h=asdf].c' + }, + 'output': (True, 2) + } + ), + ( + 'docstring_test_6', + { + 'kwargs': { + 'source': { + 'first_key': 'a', + 'second_key': [ + 'x', + 'y', + 'z', + ], + 'third_key': [ + { + 'b': 1, + 'c': 2, + 'h': 'asdf' + }, + { + 'b': 3, + 'c': 4, + 'h': 'qw"er' + } + ], + 'fourth_key': [ + { + 'd': { + 'f': 5, + 'g': 6 + }, + 'e': { + 'f': 7, + 'g': 8 + } + }, + { + 'd': { + 'f': 9, + 'g': 10 + }, + 'e': { + 'f': 11, + 'g': 12 + } + } + ] + }, + 'path': 'fourth_key[d~g=6].e.f' + }, + 'output': (True, 7) + } + ), )) diff --git a/transmogrifydict.py b/transmogrifydict.py index 246be47..72942fd 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -17,7 +17,7 @@ def resolve_path_to_value(source, path): key[Key~SubKey=Value] if the substring `Value` `isdigit()`, we look for an `int` version. You can wrap `'8'` into `'"8"'` to find the - six.string_types version. + `string` version. examples: >>> source_dict = { From 80f3fb36b089a7d9a61a151fb5b5ee30eec790df Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 15:49:32 -0600 Subject: [PATCH 07/32] add previous resolve_mapping_to_dict doctypes to unittests. #2 --- tests/test_resolve_mapping_to_dict.py | 39 +++++++++++++++++++++++++++ transmogrifydict.py | 10 +++---- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/tests/test_resolve_mapping_to_dict.py b/tests/test_resolve_mapping_to_dict.py index 8b13789..78cff01 100644 --- a/tests/test_resolve_mapping_to_dict.py +++ b/tests/test_resolve_mapping_to_dict.py @@ -1 +1,40 @@ +from collections import OrderedDict +from tests.helpers import BaseUnitTest, KwargsToOutputDynamicTestsMetaClass +from transmogrifydict import resolve_mapping_to_dict +from six import with_metaclass + + +class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaClass, BaseUnitTest)): + func = resolve_mapping_to_dict + tests = OrderedDict(( + ( + 'doctest_test_1', + { + 'kwargs': { + 'mapping': { + 'a': 'x[type=other_type].aa', + 'b': 'x[type=some_type].bb', + 'c': 'x[type=other_type].cc', + }, + 'source': { + 'x': [ + { + 'type': 'some_type', + 'aa': '4', + 'bb': '5', + 'cc': '6' + }, + { + 'type': 'other_type', + 'aa': '1', + 'bb': '2', + 'cc': '3' + } + ] + } + }, + 'output': {'a': '1', 'c': '3', 'b': '5'} + } + ), + )) diff --git a/transmogrifydict.py b/transmogrifydict.py index 72942fd..8ad39f0 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -143,12 +143,12 @@ def resolve_mapping_to_dict(mapping, source): move values from `source` into a returned dict, using `mapping` for paths and returned keys. see resolve_path_to_value for path string formats. - >>> mapping_dict = { + >>> mapping = { ... 'a': 'x[type=other_type].aa', - ... 'b': 'x[type=other_type].bb', + ... 'b': 'x[type=some_type].bb', ... 'c': 'x[type=other_type].cc', ... } - >>> source_dict = { + >>> source = { ... 'x': [ ... { ... 'type': 'some_type', @@ -164,8 +164,8 @@ def resolve_mapping_to_dict(mapping, source): ... } ... ] ... } - >>> resolve_mapping_to_dict(mapping_dict, source_dict) - {'a': '1', 'c': '3', 'b': '2'} + >>> resolve_mapping_to_dict(mapping, source) + {'a': '1', 'c': '3', 'b': '5'} :param mapping: values are paths to find the corresponding value in `source`, keys are were to store said values :type mapping: dict From 9a2416d352472aa87cf297bad0daca9e05a15907 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 16:33:01 -0600 Subject: [PATCH 08/32] shutup by default. --- setup.cfg | 2 ++ setup.py | 6 ------ 2 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2cf0afb --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[global] +verbose=0 diff --git a/setup.py b/setup.py index 8b5a00b..5082938 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,4 @@ from setuptools import setup -import unittest - - -def test_suite(): - return unittest.TestLoader().discover('tests', pattern='test_*.py') setup( name='transmogrifydict', @@ -13,6 +8,5 @@ def test_suite(): author='Emergence by Design', author_email='support@emergence.com', py_modules=['transmogrifydict'], - test_suite='setup.test_suite', install_requires=[x for x in open('./requirements.txt').read().split('\n') if x] ) From fab7bff851a93f1a476956ea47f7116f5b30e252 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 17:27:44 -0600 Subject: [PATCH 09/32] include less files in coverage report. --- .coveragerc | 2 ++ .gitignore | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..3a52cc2 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +include = transmogrifydict.py diff --git a/.gitignore b/.gitignore index f3f9a84..9b62086 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dist venv venvpy3 .eggs +.coverage +htmlcov From df206f42103cf9ba998e51cb11657cf0659840d7 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 17:43:12 -0600 Subject: [PATCH 10/32] add links to uploaded our hosted coverage report. #3 --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8728250..858569d 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ The "turn a dict from one API into a dict for another" python module. ![That dict is so cool...](/transmogrifydict.png) -Py2: [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2) -Py3: [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3) +Py2: [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2) +Py3: [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3) + +Py2: [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/py2-coverage/coverage.svg)](https://docs.emergence.com/transmogrifydict/py2-coverage/) +Py3: [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/py3-coverage/coverage.svg)](https://docs.emergence.com/transmogrifydict/py3-coverage/) ## methods From f5b4c654622252d2459832beffe62076db2ff528 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 4 Aug 2017 17:44:10 -0600 Subject: [PATCH 11/32] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5082938..72c7c8e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='transmogrifydict', url='https://github.com/emergence/transmogrifydict', - version='1.0.0', + version='1.1.0', description='The "turn a dict from one API into a dict for another" python module.', author='Emergence by Design', author_email='support@emergence.com', From 8e48ffeea6241cf72babf9f92efa3f7ee802bc9a Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Thu, 4 Jan 2018 14:49:46 -0700 Subject: [PATCH 12/32] update readme for new coverage location, add develop to coverage shown, get a nice table for formatting. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 858569d..fe756b8 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,12 @@ The "turn a dict from one API into a dict for another" python module. ![That dict is so cool...](/transmogrifydict.png) -Py2: [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2) -Py3: [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3) - -Py2: [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/py2-coverage/coverage.svg)](https://docs.emergence.com/transmogrifydict/py2-coverage/) -Py3: [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/py3-coverage/coverage.svg)](https://docs.emergence.com/transmogrifydict/py3-coverage/) +| Python | Branch | Build Status | Coverage Status | +| ------ | ------ | ------------ | --------------- | +| 2 | master | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/master) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/) | +| 2 | develop | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/develop) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/) | +| 3 | master | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/master) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/) | +| 3 | develop | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/develop) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/) | ## methods From bad73c0c5fb2b7711bca38821812268ce52fe52c Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 19 Jan 2018 10:26:51 -0700 Subject: [PATCH 13/32] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe756b8..ba7fc16 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # transmogrifydict -The "turn a dict from one API into a dict for another" python module. +The "map a dict from one API into a dict for another" python module. ![That dict is so cool...](/transmogrifydict.png) From 80d652bc82a098cbe3ead2223ed9817407512037 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Mon, 22 Jan 2018 11:44:17 -0700 Subject: [PATCH 14/32] add slash quoting for using ., [, ] and \ in keys, array sub keys and array equality values. closes #7 --- transmogrifydict.py | 129 ++++++++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 47 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 8ad39f0..0705bea 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -1,9 +1,32 @@ import json import six +import re + +# (?>> source_dict = { ... 'first_key': 'a', - ... 'second_key' : [ - ... 'x', - ... 'y', - ... 'z', - ... ], + ... 'second_key' : ['x', 'y', 'z'], ... 'third_key' : [ - ... { - ... 'b': 1, - ... 'c': 2, - ... 'h': 'asdf' - ... }, - ... { - ... 'b': 3, - ... 'c': 4, - ... 'h': 'qw"er' - ... } + ... {'c': 'asdf'}, + ... {'b': 3}, + ... {'h': 'qw"er'} ... ], ... 'fourth_key': [ ... { - ... 'd': { - ... 'f': 5, - ... 'g': 6 - ... }, - ... 'e': { - ... 'f': 7, - ... 'g': 8 - ... } + ... 'd': {'f': 5, 'g': 6}, + ... 'e': {'f': 7, 'g': 8} ... }, ... { - ... 'd': { - ... 'f': 9, - ... 'g': 10 - ... }, - ... 'e': { - ... 'f': 11, - ... 'g': 12 - ... } + ... 'd': {'f': 9, 'g': 10}, + ... 'e': {'f': 11, 'g': 12} ... } + ... ], + ... 'fifth_key': [ + ... {'b.c': '9.a'}, + ... {'b[c': '9[a'}, + ... {'b]c': '9]a'}, + ... {'b\c': '9\\a'}, ... ] ... } >>> resolve_path_to_value(source_dict, 'first_key') @@ -67,13 +73,21 @@ def resolve_path_to_value(source, path): >>> resolve_path_to_value(source_dict, 'second_key[1]') (True, 'y') >>> resolve_path_to_value(source_dict, 'third_key[b=3]') - (True, {'h': 'qw"er', 'c': 4, 'b': 3}) - >>> resolve_path_to_value(source_dict, 'third_key[h="qw"er"]') - (True, {'h': 'qw"er', 'c': 4, 'b': 3}) - >>> resolve_path_to_value(source_dict, 'third_key[h=asdf].c') - (True, 2) + (True, {'b': 3}) + >>> resolve_path_to_value(source_dict, 'third_key[h=qw"er]') + (True, {'h': 'qw"er'}) + >>> resolve_path_to_value(source_dict, 'third_key[c=asdf].c') + (True, 'asdf') >>> resolve_path_to_value(source_dict, 'fourth_key[d~g=6].e.f') (True, 7) + >>> resolve_path_to_value(source_dict, r'fifth_key[b\.c=9\.a].b\.c') + (True, '9.a') + >>> resolve_path_to_value(source_dict, r'fifth_key[b\[c=9\[a].b\[c') + (True, '9[a') + >>> resolve_path_to_value(source_dict, r'fifth_key[b\]c=9\]a].b\]c') + (True, '9]a') + >>> resolve_path_to_value(source_dict, r'fifth_key[b\\c=9\\a].b\\c') + (True, '9\\a') :param source: potentially holds the desired value :type source: dict @@ -85,9 +99,19 @@ def resolve_path_to_value(source, path): """ mapped_value = source found_value = True - # noinspection PyUnresolvedReferences - for path_part in path.split('.'): - parts = path_part.split('[') + + path_parts = non_quoted_split(PERIOD_SPLIT, path) + + for path_part_raw in path_parts: + # split on non quoted open bracket + + parts = non_quoted_split(OPEN_SQUARE_BRACKET_SPLIT, path_part_raw) + key = parts[0] + array = parts[1:] + # future: when dropping python 2 support do this instead. + #key, *array = non_quoted_split(OPEN_SQUARE_BRACKET_SPLIT, path_part_raw) + + key = un_slash_escape(key) try: if isinstance(mapped_value, six.string_types): # ugh, maybe it is json? @@ -96,12 +120,12 @@ def resolve_path_to_value(source, path): except ValueError: found_value = False break - mapped_value = mapped_value[parts[0]] + mapped_value = mapped_value[key] except KeyError: found_value = False break - for array_part_raw in parts[1:]: - array_part = array_part_raw[:-1] + for array_part_raw in array: + array_part = array_part_raw.strip(']') if array_part.isdigit(): # [0] if hasattr(mapped_value, 'keys'): @@ -109,17 +133,28 @@ def resolve_path_to_value(source, path): mapped_value = mapped_value[int(array_part)] elif '=' in array_part: # [Key=Value] or [Key~SubKey=Value] - find_key, find_value = array_part.split('=') + # split on non quoted equals signs + equal_parts = non_quoted_split(EQUAL_SPLIT, array_part) + find_key = equal_parts[0] + find_value = equal_parts[1:] + # future: when dropping python 2 support do this instead. + #find_key, *find_value = non_quoted_split(EQUAL_SPLIT, array_part) + if len(find_value) >= 2: + raise ValueError('too many unquoted equals signs in square brackets for {}'.format(array_part)) + find_value = find_value[0] if find_value.isdigit(): find_value = int(find_value) elif find_value.startswith('"') and find_value.endswith('"'): find_value = find_value[1:-1] - for item in hasattr(mapped_value, 'keys') and [mapped_value] or mapped_value: + if isinstance(find_value, six.string_types): + find_value = un_slash_escape(find_value) + for item in [mapped_value] if hasattr(mapped_value, 'keys') else mapped_value: sub_item = item - sub_keys = find_key.split('~') + sub_keys = non_quoted_split(TIDLE_SPLIT, find_key) try: while sub_keys: - sub_item = sub_item[sub_keys.pop(0)] + sub_key = un_slash_escape(sub_keys.pop(0)) + sub_item = sub_item[sub_key] except (KeyError, IndexError): pass else: From c7956514fcd25f5e8883f223997378301453cc11 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 14:32:51 -0700 Subject: [PATCH 15/32] add tests for getting arrays. closes #6 --- transmogrifydict.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 0705bea..7dc2875 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -66,7 +66,19 @@ def resolve_path_to_value(source, path): ... {'b[c': '9[a'}, ... {'b]c': '9]a'}, ... {'b\c': '9\\a'}, - ... ] + ... ], + ... 'sixth_key': { + ... 'a': [ + ... {'b':6}, + ... {'b':5}, + ... {'b':4}, + ... ], + ... 'c': [ + ... {'d':100}, + ... {'d':{'e': 3}}, + ... {'d':{'e': 2}}, + ... ] + ... } ... } >>> resolve_path_to_value(source_dict, 'first_key') (True, 'a') @@ -88,6 +100,10 @@ def resolve_path_to_value(source, path): (True, '9]a') >>> resolve_path_to_value(source_dict, r'fifth_key[b\\c=9\\a].b\\c') (True, '9\\a') + >>> resolve_path_to_value(source_dict, 'sixth_key.a[].b') + (True, [6, 5, 4]) + >>> resolve_path_to_value(source_dict, 'sixth_key.c[].d.e') + (True, [3, 2]) :param source: potentially holds the desired value :type source: dict @@ -99,10 +115,11 @@ def resolve_path_to_value(source, path): """ mapped_value = source found_value = True + went_recursive = False path_parts = non_quoted_split(PERIOD_SPLIT, path) - for path_part_raw in path_parts: + for path_parts_index, path_part_raw in enumerate(path_parts): # split on non quoted open bracket parts = non_quoted_split(OPEN_SQUARE_BRACKET_SPLIT, path_part_raw) @@ -120,6 +137,9 @@ def resolve_path_to_value(source, path): except ValueError: found_value = False break + if not hasattr(mapped_value, 'keys'): + found_value = False + break mapped_value = mapped_value[key] except KeyError: found_value = False @@ -165,9 +185,24 @@ def resolve_path_to_value(source, path): # raise KeyError('no item with %r == %r' % (find_key, find_value)) found_value = False break + elif array_part == '': + # empty [] + if hasattr(mapped_value, 'keys'): + break + if not mapped_value: + break + remainder = '.'.join(path_parts[path_parts_index+1:]) + mapped_value = [resolve_path_to_value(x, remainder) for x in mapped_value] + mapped_value = [value for found, value in mapped_value if found] + went_recursive = True # break the outer loop, we are done here. + if not mapped_value: + found_value = False + break else: raise ValueError('Expected square brackets to have be either "[number]", or "[key=value]" or ' '"[key~subkey=value]". got: %r' % array_part) + if went_recursive: + break if not found_value: break return found_value, mapped_value @@ -199,8 +234,8 @@ def resolve_mapping_to_dict(mapping, source): ... } ... ] ... } - >>> resolve_mapping_to_dict(mapping, source) - {'a': '1', 'c': '3', 'b': '5'} + >>> resolve_mapping_to_dict(mapping, source) == {'a': '1', 'b': '5', 'c': '3'} + True :param mapping: values are paths to find the corresponding value in `source`, keys are were to store said values :type mapping: dict From 8663a75e7a00ef1a817d1b9636ff5017dbb3ff1d Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 15:02:18 -0700 Subject: [PATCH 16/32] stop duplicatation of doctests into unittests manually. closes #8 --- tests/test_resolve_mapping_to_dict.py | 40 --- tests/test_resolve_path_to_value.py | 367 -------------------------- tests/tests.py | 68 +++++ transmogrifydict.py | 22 +- 4 files changed, 79 insertions(+), 418 deletions(-) delete mode 100644 tests/test_resolve_mapping_to_dict.py delete mode 100644 tests/test_resolve_path_to_value.py create mode 100644 tests/tests.py diff --git a/tests/test_resolve_mapping_to_dict.py b/tests/test_resolve_mapping_to_dict.py deleted file mode 100644 index 78cff01..0000000 --- a/tests/test_resolve_mapping_to_dict.py +++ /dev/null @@ -1,40 +0,0 @@ -from collections import OrderedDict - -from tests.helpers import BaseUnitTest, KwargsToOutputDynamicTestsMetaClass -from transmogrifydict import resolve_mapping_to_dict -from six import with_metaclass - - -class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaClass, BaseUnitTest)): - func = resolve_mapping_to_dict - tests = OrderedDict(( - ( - 'doctest_test_1', - { - 'kwargs': { - 'mapping': { - 'a': 'x[type=other_type].aa', - 'b': 'x[type=some_type].bb', - 'c': 'x[type=other_type].cc', - }, - 'source': { - 'x': [ - { - 'type': 'some_type', - 'aa': '4', - 'bb': '5', - 'cc': '6' - }, - { - 'type': 'other_type', - 'aa': '1', - 'bb': '2', - 'cc': '3' - } - ] - } - }, - 'output': {'a': '1', 'c': '3', 'b': '5'} - } - ), - )) diff --git a/tests/test_resolve_path_to_value.py b/tests/test_resolve_path_to_value.py deleted file mode 100644 index 656d182..0000000 --- a/tests/test_resolve_path_to_value.py +++ /dev/null @@ -1,367 +0,0 @@ -from collections import OrderedDict - -from tests.helpers import BaseUnitTest, KwargsToOutputDynamicTestsMetaClass -from transmogrifydict import resolve_path_to_value -from six import with_metaclass - - -class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaClass, BaseUnitTest)): - func = resolve_path_to_value - tests = OrderedDict(( - ( - 'simple', - { - 'kwargs': { - 'source': { - 'one': 1, - 'two': 2, - 'three': 3 - }, - 'path': 'two' - }, - 'output': (True, 2) - } - ), - ( - 'with_sub_keys', - { - 'kwargs': { - 'source': { - 'one': 1, - 'two': { - 'sleeping': 'bereft of life', - 'pining for the fjords': 'has ceased to be', - 'stunned': 'an ex-parrot', - }, - 'three': 3 - }, - 'path': 'two.sleeping' - }, - 'output': (True, 'bereft of life') - } - ), - ( - 'with_sub_keys_with_spaces', - { - 'kwargs': { - 'source': { - 'one': 1, - 't w o': { - 'sleeping': 'bereft of life', - 'pining for the fjords': 'has ceased to be', - 'stunned': 'an ex-parrot', - }, - 'three': 3 - }, - 'path': 't w o.pining for the fjords' - }, - 'output': (True, 'has ceased to be') - } - ), - ( - 'docstring_test_1', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'first_key' - }, - 'output': (True, 'a') - } - ), - ( - 'docstring_test_2', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'second_key[1]' - }, - 'output': (True, 'y') - } - ), - ( - 'docstring_test_3', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'third_key[b=3]' - }, - 'output': (True, {'h': 'qw"er', 'c': 4, 'b': 3}) - } - ), - ( - 'docstring_test_4', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'third_key[h="qw"er"]' - }, - 'output': (True, {'h': 'qw"er', 'c': 4, 'b': 3}) - } - ), - ( - 'docstring_test_5', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'third_key[h=asdf].c' - }, - 'output': (True, 2) - } - ), - ( - 'docstring_test_6', - { - 'kwargs': { - 'source': { - 'first_key': 'a', - 'second_key': [ - 'x', - 'y', - 'z', - ], - 'third_key': [ - { - 'b': 1, - 'c': 2, - 'h': 'asdf' - }, - { - 'b': 3, - 'c': 4, - 'h': 'qw"er' - } - ], - 'fourth_key': [ - { - 'd': { - 'f': 5, - 'g': 6 - }, - 'e': { - 'f': 7, - 'g': 8 - } - }, - { - 'd': { - 'f': 9, - 'g': 10 - }, - 'e': { - 'f': 11, - 'g': 12 - } - } - ] - }, - 'path': 'fourth_key[d~g=6].e.f' - }, - 'output': (True, 7) - } - ), - )) diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..522b6ac --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,68 @@ +import doctest + +from collections import OrderedDict + +from tests.helpers import BaseUnitTest, KwargsToOutputDynamicTestsMetaClass +import transmogrifydict +from six import with_metaclass + + +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite(transmogrifydict)) + return tests + + +class ResolvePathToValueTestCase(with_metaclass(KwargsToOutputDynamicTestsMetaClass, BaseUnitTest)): + func = transmogrifydict.resolve_path_to_value + tests = OrderedDict(( + ( + 'simple', + { + 'kwargs': { + 'source': { + 'one': 1, + 'two': 2, + 'three': 3 + }, + 'path': 'two' + }, + 'output': (True, 2) + } + ), + ( + 'with_sub_keys', + { + 'kwargs': { + 'source': { + 'one': 1, + 'two': { + 'sleeping': 'bereft of life', + 'pining for the fjords': 'has ceased to be', + 'stunned': 'an ex-parrot', + }, + 'three': 3 + }, + 'path': 'two.sleeping' + }, + 'output': (True, 'bereft of life') + } + ), + ( + 'with_sub_keys_with_spaces', + { + 'kwargs': { + 'source': { + 'one': 1, + 't w o': { + 'sleeping': 'bereft of life', + 'pining for the fjords': 'has ceased to be', + 'stunned': 'an ex-parrot', + }, + 'three': 3 + }, + 'path': 't w o.pining for the fjords' + }, + 'output': (True, 'has ceased to be') + } + ), + )) diff --git a/transmogrifydict.py b/transmogrifydict.py index 7dc2875..76c5fe2 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -13,7 +13,7 @@ SINGLE_SLASH = re.compile(r'(?= 2: raise ValueError('too many unquoted equals signs in square brackets for {}'.format(array_part)) find_value = find_value[0] @@ -167,13 +167,13 @@ def resolve_path_to_value(source, path): elif find_value.startswith('"') and find_value.endswith('"'): find_value = find_value[1:-1] if isinstance(find_value, six.string_types): - find_value = un_slash_escape(find_value) + find_value = _un_slash_escape(find_value) for item in [mapped_value] if hasattr(mapped_value, 'keys') else mapped_value: sub_item = item - sub_keys = non_quoted_split(TIDLE_SPLIT, find_key) + sub_keys = _non_quoted_split(TIDLE_SPLIT, find_key) try: while sub_keys: - sub_key = un_slash_escape(sub_keys.pop(0)) + sub_key = _un_slash_escape(sub_keys.pop(0)) sub_item = sub_item[sub_key] except (KeyError, IndexError): pass From 72db5fac054f18e4f5299f4867f2c54dcd8e956a Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 15:06:24 -0700 Subject: [PATCH 17/32] conflicting py module name. --- tests/{tests.py => test_cases.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{tests.py => test_cases.py} (100%) diff --git a/tests/tests.py b/tests/test_cases.py similarity index 100% rename from tests/tests.py rename to tests/test_cases.py From f62673d5f2ea6a11a3568b618ecb9129e2bd6fab Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 15:38:20 -0700 Subject: [PATCH 18/32] add doctest for covering looking up keys in json strings. #5 --- transmogrifydict.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/transmogrifydict.py b/transmogrifydict.py index 76c5fe2..23c784b 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -78,6 +78,9 @@ def resolve_path_to_value(source, path): ... {'d':{'e': 3}}, ... {'d':{'e': 2}}, ... ] + ... }, + ... 'seventh_key': { + ... 'bad_api': '{"z":1,"y":2,"x":3}' ... } ... } >>> resolve_path_to_value(source_dict, 'first_key') @@ -104,6 +107,8 @@ def resolve_path_to_value(source, path): (True, [6, 5, 4]) >>> resolve_path_to_value(source_dict, 'sixth_key.c[].d.e') (True, [3, 2]) + >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api.x') + (True, 3) :param source: potentially holds the desired value :type source: dict From e6d82c0c078197c63fb75af6f6401c87e8a47492 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 15:40:00 -0700 Subject: [PATCH 19/32] update error message on bad square brackets to list valid options. #6 --- transmogrifydict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 23c784b..55439fd 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -204,8 +204,8 @@ def resolve_path_to_value(source, path): found_value = False break else: - raise ValueError('Expected square brackets to have be either "[number]", or "[key=value]" or ' - '"[key~subkey=value]". got: %r' % array_part) + raise ValueError('Expected square brackets to have be either "[number]", "[key=value]", ' + '"[key~subkey=value]" or "[]". got: %r' % array_part) if went_recursive: break if not found_value: From 95e770981a24e9b55a4e81786ac86695deb50d24 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:03:37 -0700 Subject: [PATCH 20/32] add some negative test cases to doctest. make outer breaking more explicit. #5 --- transmogrifydict.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 55439fd..cea676f 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -83,16 +83,26 @@ def resolve_path_to_value(source, path): ... 'bad_api': '{"z":1,"y":2,"x":3}' ... } ... } + >>> resolve_path_to_value(source_dict, 'zero_key')[0] + False >>> resolve_path_to_value(source_dict, 'first_key') (True, 'a') >>> resolve_path_to_value(source_dict, 'second_key[1]') (True, 'y') + >>> resolve_path_to_value(source_dict, 'second_key[4]') + Traceback (most recent call last): + ... + IndexError: list index out of range >>> resolve_path_to_value(source_dict, 'third_key[b=3]') (True, {'b': 3}) + >>> resolve_path_to_value(source_dict, 'third_key[b=4]')[0] + False >>> resolve_path_to_value(source_dict, 'third_key[h=qw"er]') (True, {'h': 'qw"er'}) >>> resolve_path_to_value(source_dict, 'third_key[c=asdf].c') (True, 'asdf') + >>> resolve_path_to_value(source_dict, 'third_key[c=asdf].b') + (False, {'c': 'asdf'}) >>> resolve_path_to_value(source_dict, 'fourth_key[d~g=6].e.f') (True, 7) >>> resolve_path_to_value(source_dict, r'fifth_key[b\.c=9\.a].b\.c') @@ -109,6 +119,15 @@ def resolve_path_to_value(source, path): (True, [3, 2]) >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api.x') (True, 3) + >>> results = resolve_path_to_value(source_dict, 'seventh_key.bad_api.a') + >>> results[0] + False + >>> results[1] == {'x': 3, 'y': 2, 'z': 1} + True + >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api[bad-squares]') + Traceback (most recent call last): + ... + ValueError: Expected square brackets to have be either "[number]", "[key=value]", "[key~subkey=value]" or "[]". got: 'bad-squares' :param source: potentially holds the desired value :type source: dict @@ -120,7 +139,7 @@ def resolve_path_to_value(source, path): """ mapped_value = source found_value = True - went_recursive = False + path_parts_break = False path_parts = _non_quoted_split(PERIOD_SPLIT, path) @@ -189,26 +208,28 @@ def resolve_path_to_value(source, path): else: # raise KeyError('no item with %r == %r' % (find_key, find_value)) found_value = False + path_parts_break = True # break the outer loop, we are done here. break elif array_part == '': # empty [] if hasattr(mapped_value, 'keys'): + found_value = False + path_parts_break = True # break the outer loop, we are done here. break if not mapped_value: + path_parts_break = True # break the outer loop, we are done here. break remainder = '.'.join(path_parts[path_parts_index+1:]) mapped_value = [resolve_path_to_value(x, remainder) for x in mapped_value] mapped_value = [value for found, value in mapped_value if found] - went_recursive = True # break the outer loop, we are done here. if not mapped_value: found_value = False + path_parts_break = True # break the outer loop, we are done here. break else: raise ValueError('Expected square brackets to have be either "[number]", "[key=value]", ' '"[key~subkey=value]" or "[]". got: %r' % array_part) - if went_recursive: - break - if not found_value: + if path_parts_break: break return found_value, mapped_value From 94c290468cd0c1216ed304af7acbd69cc660b5ad Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:06:01 -0700 Subject: [PATCH 21/32] more failure tests. #5 --- transmogrifydict.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index cea676f..eb87a61 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -128,6 +128,10 @@ def resolve_path_to_value(source, path): Traceback (most recent call last): ... ValueError: Expected square brackets to have be either "[number]", "[key=value]", "[key~subkey=value]" or "[]". got: 'bad-squares' + >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api[a=b=c=]') + Traceback (most recent call last): + ... + ValueError: too many unquoted equals signs in square brackets for 'a=b=c=' :param source: potentially holds the desired value :type source: dict @@ -184,7 +188,7 @@ def resolve_path_to_value(source, path): # future: when dropping python 2 support do this instead. #find_key, *find_value = _non_quoted_split(EQUAL_SPLIT, array_part) if len(find_value) >= 2: - raise ValueError('too many unquoted equals signs in square brackets for {}'.format(array_part)) + raise ValueError('too many unquoted equals signs in square brackets for {!r}'.format(array_part)) find_value = find_value[0] if find_value.isdigit(): find_value = int(find_value) From e233a4fd8fd268e587f9bb43a9be297436cef6ad Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:22:15 -0700 Subject: [PATCH 22/32] add string of number doctest for coverage. #5 --- transmogrifydict.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/transmogrifydict.py b/transmogrifydict.py index eb87a61..ca46fde 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -49,6 +49,7 @@ def resolve_path_to_value(source, path): ... 'third_key' : [ ... {'c': 'asdf'}, ... {'b': 3}, + ... {'b': '5'}, ... {'h': 'qw"er'} ... ], ... 'fourth_key': [ @@ -97,6 +98,8 @@ def resolve_path_to_value(source, path): (True, {'b': 3}) >>> resolve_path_to_value(source_dict, 'third_key[b=4]')[0] False + >>> resolve_path_to_value(source_dict, 'third_key[b="5"]') + (True, {'b': '5'}) >>> resolve_path_to_value(source_dict, 'third_key[h=qw"er]') (True, {'h': 'qw"er'}) >>> resolve_path_to_value(source_dict, 'third_key[c=asdf].c') From fe28c0056879928d63733f4e76b87dee5da9c940 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:37:36 -0700 Subject: [PATCH 23/32] better error messages for key & index errors. #5 --- transmogrifydict.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index ca46fde..7b74a99 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -93,7 +93,7 @@ def resolve_path_to_value(source, path): >>> resolve_path_to_value(source_dict, 'second_key[4]') Traceback (most recent call last): ... - IndexError: list index out of range + IndexError: index 4 out of range on array at 'second_key'.g s >>> resolve_path_to_value(source_dict, 'third_key[b=3]') (True, {'b': 3}) >>> resolve_path_to_value(source_dict, 'third_key[b=4]')[0] @@ -135,6 +135,14 @@ def resolve_path_to_value(source, path): Traceback (most recent call last): ... ValueError: too many unquoted equals signs in square brackets for 'a=b=c=' + >>> resolve_path_to_value(source_dict, 'seventh_key[0]') + Traceback (most recent call last): + ... + ValueError: array expected at 'seventh_key', found dict-like object. + >>> resolve_path_to_value(source_dict, 'seventh_key[]') + Traceback (most recent call last): + ... + ValueError: array expected at 'seventh_key', found dict-like object. :param source: potentially holds the desired value :type source: dict @@ -179,9 +187,17 @@ def resolve_path_to_value(source, path): array_part = array_part_raw.strip(']') if array_part.isdigit(): # [0] - if hasattr(mapped_value, 'keys'): - break - mapped_value = mapped_value[int(array_part)] + try: + mapped_value = mapped_value[int(array_part)] + except KeyError: + raise ValueError('array expected at {!r}, found dict-like object.'.format( + '.'.join(path_parts[:path_parts_index] + [key]) + )) + except IndexError: + raise IndexError('index {!r} out of range on array at {!r}.'.format( + int(array_part), + '.'.join(path_parts[:path_parts_index] + [key]) + )) elif '=' in array_part: # [Key=Value] or [Key~SubKey=Value] # split on non quoted equals signs @@ -220,9 +236,9 @@ def resolve_path_to_value(source, path): elif array_part == '': # empty [] if hasattr(mapped_value, 'keys'): - found_value = False - path_parts_break = True # break the outer loop, we are done here. - break + raise ValueError('array expected at {!r}, found dict-like object.'.format( + '.'.join(path_parts[:path_parts_index] + [key]) + )) if not mapped_value: path_parts_break = True # break the outer loop, we are done here. break From 48664cdd098857c6783942fdd7da64688c3b7d49 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:39:55 -0700 Subject: [PATCH 24/32] mis-typed --- transmogrifydict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 7b74a99..b8f1981 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -93,7 +93,7 @@ def resolve_path_to_value(source, path): >>> resolve_path_to_value(source_dict, 'second_key[4]') Traceback (most recent call last): ... - IndexError: index 4 out of range on array at 'second_key'.g s + IndexError: index 4 out of range on array at 'second_key'. >>> resolve_path_to_value(source_dict, 'third_key[b=3]') (True, {'b': 3}) >>> resolve_path_to_value(source_dict, 'third_key[b=4]')[0] From e8a012844c20afd8f447b442416f0a3ff43c8619 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:45:23 -0700 Subject: [PATCH 25/32] add error for invalid json, found string when looking for dict. #5 --- transmogrifydict.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index b8f1981..26d2701 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -81,7 +81,8 @@ def resolve_path_to_value(source, path): ... ] ... }, ... 'seventh_key': { - ... 'bad_api': '{"z":1,"y":2,"x":3}' + ... 'bad_api': '{"z":1,"y":2,"x":3}', + ... 'bad_json': '{"z":1!"y":2,"x":3}', ... } ... } >>> resolve_path_to_value(source_dict, 'zero_key')[0] @@ -143,6 +144,10 @@ def resolve_path_to_value(source, path): Traceback (most recent call last): ... ValueError: array expected at 'seventh_key', found dict-like object. + >>> resolve_path_to_value(source_dict, 'seventh_key.bad_json.z') + Traceback (most recent call last): + ... + ValueError: string found when looking for dict-like object at 'seventh_key.bad_json'. failed to convert to json. :param source: potentially holds the desired value :type source: dict @@ -174,8 +179,11 @@ def resolve_path_to_value(source, path): try: mapped_value = json.loads(mapped_value) except ValueError: - found_value = False - break + raise ValueError( + 'string found when looking for dict-like object at {!r}. failed to convert to json.'.format( + '.'.join(path_parts[:path_parts_index]) + ) + ) if not hasattr(mapped_value, 'keys'): found_value = False break From a99324a677071788a48b1e76797e490af00be4c2 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:53:26 -0700 Subject: [PATCH 26/32] add coverage for empty array found when asking for subitems. #5 --- transmogrifydict.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/transmogrifydict.py b/transmogrifydict.py index 26d2701..6d1294b 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -78,7 +78,8 @@ def resolve_path_to_value(source, path): ... {'d':100}, ... {'d':{'e': 3}}, ... {'d':{'e': 2}}, - ... ] + ... ], + ... 'f': [] ... }, ... 'seventh_key': { ... 'bad_api': '{"z":1,"y":2,"x":3}', @@ -121,6 +122,12 @@ def resolve_path_to_value(source, path): (True, [6, 5, 4]) >>> resolve_path_to_value(source_dict, 'sixth_key.c[].d.e') (True, [3, 2]) + >>> resolve_path_to_value(source_dict, 'sixth_key.f') + (True, []) + >>> resolve_path_to_value(source_dict, 'sixth_key.f[]') + (True, []) + >>> resolve_path_to_value(source_dict, 'sixth_key.f[].g') + (False, []) >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api.x') (True, 3) >>> results = resolve_path_to_value(source_dict, 'seventh_key.bad_api.a') @@ -248,6 +255,8 @@ def resolve_path_to_value(source, path): '.'.join(path_parts[:path_parts_index] + [key]) )) if not mapped_value: + if path_parts[path_parts_index+1:]: + found_value = False path_parts_break = True # break the outer loop, we are done here. break remainder = '.'.join(path_parts[path_parts_index+1:]) From afda8f1fbb74f4d0307492addf2e91e39b8645f4 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 16:54:55 -0700 Subject: [PATCH 27/32] add test for no items with desired keys found in subarray. #5 --- transmogrifydict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/transmogrifydict.py b/transmogrifydict.py index 6d1294b..1c870d8 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -122,6 +122,8 @@ def resolve_path_to_value(source, path): (True, [6, 5, 4]) >>> resolve_path_to_value(source_dict, 'sixth_key.c[].d.e') (True, [3, 2]) + >>> resolve_path_to_value(source_dict, 'sixth_key.c[].x') + (False, []) >>> resolve_path_to_value(source_dict, 'sixth_key.f') (True, []) >>> resolve_path_to_value(source_dict, 'sixth_key.f[]') From 6acda5bd04af97d53cfc2d758bbe0a3e65b2da70 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 17:04:51 -0700 Subject: [PATCH 28/32] flake8 config and some fixes. --- tox.ini | 5 +++++ transmogrifydict.py | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..2ae054f --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 120 +exclude=venv,venvpy3,dist,.eggs +max-complexity=20 + diff --git a/transmogrifydict.py b/transmogrifydict.py index 1c870d8..c4d095c 100644 --- a/transmogrifydict.py +++ b/transmogrifydict.py @@ -140,7 +140,7 @@ def resolve_path_to_value(source, path): >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api[bad-squares]') Traceback (most recent call last): ... - ValueError: Expected square brackets to have be either "[number]", "[key=value]", "[key~subkey=value]" or "[]". got: 'bad-squares' + ValueError: Bad square brackets syntax on 'bad-squares' >>> resolve_path_to_value(source_dict, 'seventh_key.bad_api[a=b=c=]') Traceback (most recent call last): ... @@ -179,7 +179,7 @@ def resolve_path_to_value(source, path): key = parts[0] array = parts[1:] # future: when dropping python 2 support do this instead. - #key, *array = _non_quoted_split(OPEN_SQUARE_BRACKET_SPLIT, path_part_raw) + # key, *array = _non_quoted_split(OPEN_SQUARE_BRACKET_SPLIT, path_part_raw) key = _un_slash_escape(key) try: @@ -222,7 +222,7 @@ def resolve_path_to_value(source, path): find_key = equal_parts[0] find_value = equal_parts[1:] # future: when dropping python 2 support do this instead. - #find_key, *find_value = _non_quoted_split(EQUAL_SPLIT, array_part) + # find_key, *find_value = _non_quoted_split(EQUAL_SPLIT, array_part) if len(find_value) >= 2: raise ValueError('too many unquoted equals signs in square brackets for {!r}'.format(array_part)) find_value = find_value[0] @@ -269,8 +269,7 @@ def resolve_path_to_value(source, path): path_parts_break = True # break the outer loop, we are done here. break else: - raise ValueError('Expected square brackets to have be either "[number]", "[key=value]", ' - '"[key~subkey=value]" or "[]". got: %r' % array_part) + raise ValueError('Bad square brackets syntax on {!r}'.format(array_part)) if path_parts_break: break return found_value, mapped_value From ef181d95916ec2eb4d7ebcaf4c2777328289f46c Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 26 Jan 2018 17:08:15 -0700 Subject: [PATCH 29/32] be more specific about python version. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ba7fc16..576a34f 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ The "map a dict from one API into a dict for another" python module. | Python | Branch | Build Status | Coverage Status | | ------ | ------ | ------------ | --------------- | -| 2 | master | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/master) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/) | -| 2 | develop | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/develop) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/) | -| 3 | master | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/master) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/) | -| 3 | develop | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/develop) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/) | +| 2.7 | master | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/master) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_master/) | +| 2.7 | develop | [![Python 2 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py2/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py2/branches/develop) | [![Python 2 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py2_develop/) | +| 3.5 | master | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/master/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/master) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_master/) | +| 3.5 | develop | [![Python 3 Build Status](https://semaphoreci.com/api/v1/emergence/transmogrifydict-py3/branches/develop/shields_badge.svg)](https://semaphoreci.com/emergence/transmogrifydict-py3/branches/develop) | [![Python 3 Coverage](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/coverage.svg)](https://docs.emergence.com/transmogrifydict/htmlcov_py3_develop/) | ## methods From aa205e5e40314f3d6d92a58d74785ce2aac2b947 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 16 Feb 2018 15:50:39 -0700 Subject: [PATCH 30/32] would be nice if the requirements.txt was included in the sdist. --- MANIFEST.in | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..2b2e000 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include LICENSE +include transmogrifydict.png +include README.md +include requirements.txt +global-exclude *.pyc From 4b134e2419a172d5747358c149cc762a84f9b3b4 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 16 Feb 2018 15:54:59 -0700 Subject: [PATCH 31/32] make README.md more useful. --- README.md | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 576a34f..8ec8b9b 100644 --- a/README.md +++ b/README.md @@ -13,5 +13,135 @@ The "map a dict from one API into a dict for another" python module. ## methods -* `resolve_path_to_value(source, path)` - fetch a value out of `source` using `path` as the pointer to the desired value. see docstring for path string formats. -* `resolve_mapping_to_dict(mapping, source)` - move values from `source` into a returned dict, using `mapping` for paths and returned keys. see `resolve_path_to_value`'s docstring for path string formats. +* `resolve_mapping_to_dict(mapping, source)` - move values from `source` into a returned dict, using `mapping` for paths and returned keys. + + ```python + from transmogrifydict import resolve_mapping_to_dict + + mapping = { + 'a': 'd', + 'b': 'e', + 'c': 'f' + } + + source = { + 'd': 1, + 'e': 2, + 'f': 3 + } + + resolve_mapping_to_dict(mapping, source) + # { + # 'a': 1, + # 'b': 2, + # 'c': 3, + # } + ``` + +* `resolve_path_to_value(source, path)` - fetch a value out of `source` using `path` as the pointer to the desired value. see docstring for path string formats. + + ```python + from transmogrifydict import resolve_path_to_value + + source = { + 'd': 1, + 'e': 2, + 'f': 3 + } + + found, value = resolve_path_to_value(source, 'e') + + print((found, value)) + # (True, 2) + ``` + +## `path` or `mapping` value format +```python +from transmogrifydict import resolve_path_to_value + +source = { + 'some-key': { + 'another-key': '123' + } +} + +# dot notation can be used to descend into dictionaries. +resolve_path_to_value(source, 'some-key.another-key') +# (True, '123') + +source = { + 'some-key': '{"another-key":"123"}' +} + +# dot notation can also be used to descend into json strings that are dictionary like +resolve_path_to_value(source, 'some-key.another-key') +# (True, '123') + +source = { + 'some-key': { + 'another-key': ['1', '2', '3'] + } +} + +# square brackets can be used to get specific indexes from a list +resolve_path_to_value(source, 'some-key.another-key[1]') +# (True, '2') + +source = { + 'some-key': { + 'another-key': [ + { + 'filter-key': 'yeah', + 'each-key': 'a', + }, + { + 'filter-key': 'yeah', + 'each-key': 'b', + }, + { + 'filter-key': 'nah', + 'each-key': 'c', + } + ] + } +} + +# dot notation can be used after square brackets if the list contains dict-like values +resolve_path_to_value(source, 'some-key.another-key[1].each-key') +# (True, ['b']) + +# square brackets can be used to iterate over arrays to descend into the items +resolve_path_to_value(source, 'some-key.another-key[].each-key') +# (True, ['a', 'b', 'c']) + +# when iterating over a list, a filter can be applied using [key=value] +resolve_path_to_value(source, 'some-key.another-key[filter-key=yeah].each-key') +# (True, ['a', 'b']) + +source = { + 'a-key': [ + { + 'b-key': { + 'c-key': 1, + 'd-key': 2, + } + }, + { + 'b-key': { + 'c-key': 1, + 'd-key': 3, + } + }, + { + 'b-key': { + 'c-key': 0, + 'd-key': 4, + } + } + ] +} +# tidle notation can be used to filter on sub keys of dict list items. +resolve_path_to_value(source, 'a-key[b-key~c-key=1].b-key.d-key') +# (True, [2, 3, 4]) +# +``` From 7acd2693c5532c13daecf9897305b382472ca592 Mon Sep 17 00:00:00 2001 From: Joel Hillacre Date: Fri, 16 Feb 2018 15:55:18 -0700 Subject: [PATCH 32/32] bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 72c7c8e..a45d092 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='transmogrifydict', url='https://github.com/emergence/transmogrifydict', - version='1.1.0', + version='1.1.1', description='The "turn a dict from one API into a dict for another" python module.', author='Emergence by Design', author_email='support@emergence.com',