New Features (nested_update, nested_delete) (#12)
* Fix for issue #10 * Renamed the test module * New Features (nested_get, nested_update, nested_delete) * Update Version to 0.3.0 * Updated Readme for new features * Removed method (nested_get) * Addressed review comments * Removed nested_get references from README * Rearanged the order of the functions
This commit is contained in:
parent
d49c15f293
commit
4564f8ed80
6 changed files with 297 additions and 58 deletions
100
README.rst
100
README.rst
|
|
@ -1,16 +1,21 @@
|
||||||
nested_lookup
|
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
|
: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.
|
*nested_lookup:*
|
||||||
#. fetching all keys from a nested dictionary.
|
Perform a key lookup on a deeply nested document. Returns all matches in a `list`. (Please see tutorial for more info)
|
||||||
#. get the number of occurrences of a key/value from a nested dictionary
|
*nested_delete:*
|
||||||
|
Returns a document that includes everything but the given key
|
||||||
Documents may be built out of dictionaries (dicts) and/or lists.
|
*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!
|
Make working with JSON, YAML, and XML document responses fun again!
|
||||||
|
|
||||||
|
|
@ -59,6 +64,15 @@ quick tutorial
|
||||||
>>> get_occurrence_of_value(document, value='42')
|
>>> get_occurrence_of_value(document, value='42')
|
||||||
1
|
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
|
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:
|
Next, we could act `wild` and find all the email addresses like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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
|
misc
|
||||||
========
|
========
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
from .nested_lookup import nested_lookup, get_all_keys, get_occurrence_of_key,\
|
from .nested_lookup import nested_lookup, get_all_keys, get_occurrence_of_key,\
|
||||||
get_occurrence_of_value
|
get_occurrence_of_value
|
||||||
|
from .lookup_api import nested_update, nested_delete
|
||||||
|
|
|
||||||
54
nested_lookup/lookup_api.py
Normal file
54
nested_lookup/lookup_api.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -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"""
|
"""Lookup a key in a nested document, return a list of values"""
|
||||||
if with_keys:
|
if with_keys:
|
||||||
d = defaultdict(list)
|
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)
|
d[k].append(v)
|
||||||
return d
|
return d
|
||||||
return list(_nested_lookup(key, document, wild=wild, with_keys=with_keys))
|
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"""
|
"""Lookup a key in a nested document, yield a value"""
|
||||||
if isinstance(document, list):
|
if isinstance(document, list):
|
||||||
for d in document:
|
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
|
yield result
|
||||||
|
|
||||||
if isinstance(document, dict):
|
if isinstance(document, dict):
|
||||||
|
|
@ -28,7 +32,9 @@ def _nested_lookup(key, document, wild=False, with_keys=False):
|
||||||
else:
|
else:
|
||||||
yield v
|
yield v
|
||||||
if isinstance(v, dict):
|
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
|
yield result
|
||||||
elif isinstance(v, list):
|
elif isinstance(v, list):
|
||||||
for d in v:
|
for d in v:
|
||||||
|
|
|
||||||
45
setup.py
45
setup.py
|
|
@ -1,36 +1,43 @@
|
||||||
# installation: pip install nested-lookup
|
# installation: pip install nested-lookup
|
||||||
|
|
||||||
from setuptools import (
|
from setuptools import (
|
||||||
setup,
|
setup,
|
||||||
find_packages,
|
find_packages,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# get list of requirement strings from requirements.txt
|
# get list of requirement strings from requirements.txt
|
||||||
remove_whitespace = lambda x : ''.join(x.split())
|
def remove_whitespace(x):
|
||||||
sanitize = lambda x : not x.startswith('#') and x != ''
|
return ''.join(x.split())
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize(x):
|
||||||
|
return not x.startswith('#') and x != ''
|
||||||
|
|
||||||
|
|
||||||
with open('requirements.txt', 'r') as f:
|
with open('requirements.txt', 'r') as f:
|
||||||
requires = filter(sanitize, map(remove_whitespace, f.readlines() ))
|
requires = filter(sanitize, map(remove_whitespace, f.readlines()))
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = 'nested-lookup',
|
name='nested-lookup',
|
||||||
version = '0.2.01',
|
version='0.2.11',
|
||||||
description = 'lookup a key in a deeply nested document of dicts and lists',
|
description='lookup a key in a deeply nested document of dicts and lists',
|
||||||
keywords = 'nested document dictionary dict list lookup schema json xml yaml',
|
keywords='nested document dictionary dict list lookup schema json xml yaml',
|
||||||
long_description = open('README.rst').read(),
|
long_description=open('README.rst').read(),
|
||||||
|
|
||||||
author = 'Russell Ballestrini',
|
author='Russell Ballestrini',
|
||||||
author_email = 'russell@ballestrini.net',
|
author_email='russell@ballestrini.net',
|
||||||
url = 'https://github.com/russellballestrini/nested-lookup',
|
url='https://github.com/russellballestrini/nested-lookup',
|
||||||
|
|
||||||
platforms = ['All'],
|
platforms=['All'],
|
||||||
license = 'Public Domain',
|
license='Public Domain',
|
||||||
|
|
||||||
packages = find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data = True,
|
include_package_data=True,
|
||||||
|
|
||||||
install_requires = requires,
|
install_requires=requires,
|
||||||
|
|
||||||
classifiers = [
|
classifiers=[
|
||||||
# Specify the Python versions you support here. In particular, ensure
|
# Specify the Python versions you support here. In particular, ensure
|
||||||
# that you indicate whether you support Python 2, Python 3 or both.
|
# that you indicate whether you support Python 2, Python 3 or both.
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
|
|
|
||||||
141
test_lookup_api.py
Normal file
141
test_lookup_api.py
Normal file
|
|
@ -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'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue