diff --git a/README.rst b/README.rst index 5950ecc..03b86e0 100644 --- a/README.rst +++ b/README.rst @@ -1,16 +1,21 @@ nested_lookup ############# -.. image:: https://img.shields.io/badge/pypi-0.2.01-green.svg +.. image:: https://img.shields.io/badge/pypi-0.2.11-green.svg :target: https://pypi.python.org/pypi/nested-lookup -A small Python library which enables: +The `nested_lookup` package provides many Python functions for working with deeply nested documents. A document in this case is a a mixture of Python dictionary and list objects typically derived from YAML or JSON -#. key lookups on deeply nested documents. -#. fetching all keys from a nested dictionary. -#. get the number of occurrences of a key/value from a nested dictionary - -Documents may be built out of dictionaries (dicts) and/or lists. +*nested_lookup:* + Perform a key lookup on a deeply nested document. Returns all matches in a `list`. (Please see tutorial for more info) +*nested_delete:* + Returns a document that includes everything but the given key +*nested_update:* + Returns a document that has updated key, value pair +*get_all_keys:* + Fetch all from a nested dictionary. Returns `list` of keys. +*get_occurrence_of_key/get_occurrence_of_value:* + Returns the number of occurrences of a key/value from a nested dictionary. Make working with JSON, YAML, and XML document responses fun again! @@ -59,6 +64,15 @@ quick tutorial >>> get_occurrence_of_value(document, value='42') 1 + >>> from nested_lookup import nested_update, nested_delete + + >>> nested_update(document, key='burrito', value='Test') + [{'taco': 42}, {'salsa': [{'burrito': 'Test'}]}] + + >>> nested_delete(document, 'taco') + [{}, {'salsa': [{'burrito': {}}]}] + + longer tutorial =============== @@ -88,34 +102,6 @@ For example, given the following document: }, }, -To get a list of every nested key in a document, run this: - -.. code-block:: python - - from nested_lookup import get_all_keys - - keys = get_all_keys(my_document) - - print(keys) - -.. code-block:: python - - ['name', 'email_address', 'other', 'secondary_email', 'EMAIL_RECOVERY', 'email_address'] - -To get the number of occurrence of the given key/value - -.. code-block:: python - - from nested_lookup import get_occurrence_of_key, get_occurrence_of_value - - no_of_key_occurrence = get_occurrence_of_key(my_document, key='email_address') - - print(no_of_key_occurrence) # result => 2 - - no_of_value_occurrence = get_occurrence_of_value(my_document, value='test2@example.com') - - print(no_of_value_occurrence) # result => 1 - Next, we could act `wild` and find all the email addresses like this: .. code-block:: python @@ -154,6 +140,50 @@ Additionally, if you also needed the matched key names, you could do this: } +To get a list of every nested key in a document, run this: + +.. code-block:: python + + from nested_lookup import get_all_keys + + keys = get_all_keys(my_document) + + print(keys) + +.. code-block:: python + + ['name', 'email_address', 'other', 'secondary_email', 'EMAIL_RECOVERY', 'email_address'] + +To get the number of occurrence of the given key/value + +.. code-block:: python + + from nested_lookup import get_occurrence_of_key, get_occurrence_of_value + + no_of_key_occurrence = get_occurrence_of_key(my_document, key='email_address') + + print(no_of_key_occurrence) # result => 2 + + no_of_value_occurrence = get_occurrence_of_value(my_document, value='test2@example.com') + + print(no_of_value_occurrence) # result => 1 + + +To Get / Delete / Update a key->value pair in nested document + +.. code-block:: python + + from nested_lookup import nested_update, nested_delete + + result = nested_delete(my_document, 'EMAIL_RECOVERY') + + print(result) # result => {'other': {'secondary_email': 'test2@example.com', 'email_address': 'test4@example.com'}, 'email_address': 'test1@example.com', 'name': 'Russell Ballestrini'} + + result = nested_update(my_document, key='other', value='Test') + + print(result) # result => {'other': 'Test', 'email_address': 'test1@example.com', 'name': 'Russell Ballestrini'} + + misc ======== diff --git a/nested_lookup/__init__.py b/nested_lookup/__init__.py index e2dc9a0..ba135f6 100644 --- a/nested_lookup/__init__.py +++ b/nested_lookup/__init__.py @@ -1,2 +1,3 @@ from .nested_lookup import nested_lookup, get_all_keys, get_occurrence_of_key,\ get_occurrence_of_value +from .lookup_api import nested_update, nested_delete diff --git a/nested_lookup/lookup_api.py b/nested_lookup/lookup_api.py new file mode 100644 index 0000000..f986ca7 --- /dev/null +++ b/nested_lookup/lookup_api.py @@ -0,0 +1,54 @@ +import copy +from six import iteritems + + +def nested_delete(document, key): + duplicate = copy.deepcopy(document) + return _nested_delete(document=duplicate, key=key) + + +def _nested_delete(document, key): + """ + Method to delete a key->value pair from a nested document + Args: + document: Might be List of Dicts (or) Dict of Lists (or) + Dict of List of Dicts etc... + key: Key to delete + Return: + Returns a document that includes everything but the given key + """ + if isinstance(document, list): + for list_items in document: + _nested_delete(document=list_items, key=key) + elif isinstance(document, dict): + if document.get(key): + del document[key] + for dict_key, dict_value in iteritems(document): + _nested_delete(document=dict_value, key=key) + return document + + +def nested_update(document, key, value): + duplicate = copy.deepcopy(document) + return _nested_update(document=duplicate, key=key, value=value) + + +def _nested_update(document, key, value): + """ + Method to update a key->value pair in a nested document + Args: + document: Might be List of Dicts (or) Dict of Lists (or) + Dict of List of Dicts etc... + key: Key to update the value + Return: + Returns a document that has updated key, value pair. + """ + if isinstance(document, list): + for list_items in document: + _nested_update(document=list_items, key=key, value=value) + elif isinstance(document, dict): + if document.get(key): + document[key] = value + for dict_key, dict_value in iteritems(document): + _nested_update(document=dict_value, key=key, value=value) + return document diff --git a/nested_lookup/nested_lookup.py b/nested_lookup/nested_lookup.py index 86e264c..39787e4 100644 --- a/nested_lookup/nested_lookup.py +++ b/nested_lookup/nested_lookup.py @@ -7,7 +7,9 @@ def nested_lookup(key, document, wild=False, with_keys=False): """Lookup a key in a nested document, return a list of values""" if with_keys: d = defaultdict(list) - for k, v in _nested_lookup(key, document, wild=wild, with_keys=with_keys): + for k, v in _nested_lookup( + key, document, wild=wild, with_keys=with_keys + ): d[k].append(v) return d return list(_nested_lookup(key, document, wild=wild, with_keys=with_keys)) @@ -17,7 +19,9 @@ def _nested_lookup(key, document, wild=False, with_keys=False): """Lookup a key in a nested document, yield a value""" if isinstance(document, list): for d in document: - for result in _nested_lookup(key, d, wild=wild, with_keys=with_keys): + for result in _nested_lookup( + key, d, wild=wild, with_keys=with_keys + ): yield result if isinstance(document, dict): @@ -28,7 +32,9 @@ def _nested_lookup(key, document, wild=False, with_keys=False): else: yield v if isinstance(v, dict): - for result in _nested_lookup(key, v, wild=wild, with_keys=with_keys): + for result in _nested_lookup( + key, v, wild=wild, with_keys=with_keys + ): yield result elif isinstance(v, list): for d in v: diff --git a/setup.py b/setup.py index fe35e1b..ef20e20 100644 --- a/setup.py +++ b/setup.py @@ -1,36 +1,43 @@ # installation: pip install nested-lookup from setuptools import ( - setup, - find_packages, + setup, + find_packages, ) + # get list of requirement strings from requirements.txt -remove_whitespace = lambda x : ''.join(x.split()) -sanitize = lambda x : not x.startswith('#') and x != '' +def remove_whitespace(x): + return ''.join(x.split()) + + +def sanitize(x): + return not x.startswith('#') and x != '' + + with open('requirements.txt', 'r') as f: - requires = filter(sanitize, map(remove_whitespace, f.readlines() )) + requires = filter(sanitize, map(remove_whitespace, f.readlines())) -setup( - name = 'nested-lookup', - version = '0.2.01', - description = 'lookup a key in a deeply nested document of dicts and lists', - keywords = 'nested document dictionary dict list lookup schema json xml yaml', - long_description = open('README.rst').read(), +setup( + name='nested-lookup', + version='0.2.11', + description='lookup a key in a deeply nested document of dicts and lists', + keywords='nested document dictionary dict list lookup schema json xml yaml', + long_description=open('README.rst').read(), - author = 'Russell Ballestrini', - author_email = 'russell@ballestrini.net', - url = 'https://github.com/russellballestrini/nested-lookup', + author='Russell Ballestrini', + author_email='russell@ballestrini.net', + url='https://github.com/russellballestrini/nested-lookup', - platforms = ['All'], - license = 'Public Domain', + platforms=['All'], + license='Public Domain', - packages = find_packages(), - include_package_data = True, + packages=find_packages(), + include_package_data=True, - install_requires = requires, + install_requires=requires, - classifiers = [ + classifiers=[ # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. 'Programming Language :: Python :: 2', diff --git a/test_lookup_api.py b/test_lookup_api.py new file mode 100644 index 0000000..4b96781 --- /dev/null +++ b/test_lookup_api.py @@ -0,0 +1,141 @@ +from unittest import TestCase + +from nested_lookup import nested_update, nested_delete + + +class BaseLookUpApi(TestCase): + def setUp(self): + self.sample_data1 = { + "build_version": { + "model_name": 'MacBook Pro', + "build_version": { + "processor_name": 'Intel Core i7', + "processor_speed": '2.7 GHz', + "core_details": { + "build_version": '4', + "l2_cache(per_core)": '256 KB' + } + }, + "number_of_cores": '4', + "memory": '256 KB' + }, + "os_details": { + "product_version": '10.13.6', + "build_version": '17G65' + }, + "name": 'Test', + "date": 'YYYY-MM-DD HH:MM:SS' + } + + self.sample_data2 = { + "hardware_details": { + "model_name": 'MacBook Pro', + "processor_details": [ + { + "processor_name": 'Intel Core i7', + "processor_speed": '2.7 GHz' + }, + { + "total_number_of_cores": '4', + "l2_cache(per_core)": '256 KB' + } + ], + "total_number_of_cores": '5', + "memory": '16 GB' + } + } + + self.sample_data3 = { + "values": [{ + "checks": [{ + "monitoring_zones": + ["mzdfw", "mzfra", "mzhkg", "mziad", + "mzlon", "mzord", "mzsyd"] + }] + }] + } + + +class TestNestedDelete(BaseLookUpApi): + def test_sample_data1(self): + result = { + "os_details": { + "product_version": '10.13.6' + }, + "name": 'Test', + "date": 'YYYY-MM-DD HH:MM:SS' + } + self.assertEqual( + result, nested_delete(self.sample_data1, 'build_version') + ) + + def test_sample_data2(self): + result = { + "hardware_details": { + "model_name": 'MacBook Pro', + "total_number_of_cores": '5', + "memory": '16 GB' + } + } + self.assertEqual( + result, nested_delete(self.sample_data2, 'processor_details') + ) + + def test_sample_data3(self): + result = {"values": [{"checks": [{}]}]} + self.assertEqual( + result, nested_delete(self.sample_data3, 'monitoring_zones') + ) + + +class TestNestedUpdate(BaseLookUpApi): + def test_sample_data1(self): + result = { + "build_version": "Test1", + "os_details": { + "product_version": '10.13.6', + "build_version": 'Test1' + }, + "name": 'Test', + "date": 'YYYY-MM-DD HH:MM:SS' + } + self.assertEqual( + result, nested_update(self.sample_data1, 'build_version', 'Test1') + ) + + def test_sample_data2(self): + result = { + "hardware_details": { + "model_name": 'MacBook Pro', + "processor_details": { + 'test_key1': 'test_value1' + }, + "total_number_of_cores": '5', + "memory": '16 GB' + } + } + self.assertEqual( + result, nested_update( + self.sample_data2, 'processor_details', + {'test_key1': 'test_value1'} + ) + ) + + def test_sample_data3(self): + result = { + "values": [{ + "checks": { + 'key1': ['value1'], + 'key2': 'value2' + } + }] + } + self.assertEqual( + result, nested_update( + self.sample_data3, 'checks', + { + 'key1': ['value1'], + 'key2': 'value2' + } + ) + )