# -*- coding: utf-8 -*-
from setuptools import setup

package_dir = \
{'': 'src'}

packages = \
['ndl_tools']

package_data = \
{'': ['*']}

setup_kwargs = {
    'name': 'ndl-tools',
    'version': '0.0.12',
    'description': 'Tools for sorting and diffing nested dictionaries and lists.',
    'long_description': '# ndl-tools\n![test](https://github.com/nathan5280/ndl-tools/workflows/test/badge.svg)\n\nTools for sorting and diffing nested dictionaries and lists.  \n\nThe focus of the package is to support API testing.  Hashing two object trees or \nnested dictionaries to compare them works great when they are \nactually equal. If they aren\'t equal finding out why can become quite tedious.\n\nAre these equal?\n```python\nobj1 = {"a": 1, "b": 2}\nobj2 = {"b": 2, "a": 1}\n```\n\nWhat about a set that is mapped into a list when converted to JSON.\n```python\nobj1 = [1, 2, 3, 4]\nobj2 = [4, 3, 2, 1]\n```\n\n```python\nfrom datetime import datetime\n\nobj1 = {"start_date": datetime.date(1999, 1, 1)}\nobj2 = {"start_date": datetime.date(2020, 8, 19)}\n```\n\nThe dictionary isn\'t to bad to get sorted and compared correctly, but it gets messy when one \nof the values is another dictionary, list or set.  The list case needs to be sorted or not sorted\ndepending on the context of the object and if it is a list or a set.  And finally, the date one\nis hard to keep up to date in your test cases because the dates keep shifting. It isn\'t to bad if you \ncan use something like freezegun to go back in time, but if the payload comes from an external \nservice it can be a mess.\n\n## Concepts\n| Term | Definition |\n| :--- | :--- |\n| Differ | Entry point to support diff of two Nested-Dict-Lists (NDL). |\n| DiffResult | Result of calling diff() in a Differ object.   It acts like a bool for simple asserts, but also provides a two column colored difference of the two NDL. |\n| ListSorter | Classes that can be selectively applied to lists in the NDL either sort or not sort a list. |\n| Normalizer | Classes that can be applied to leaf elements to transform them to match from the left and right NDLs. |\n| Selector | Classes used to select what elements in the traversal of the NDL a given ListSorter or Normalizer is applied. |\n| Sorter | Entry point to NDL sorter functionality. This normally isn\'t used directly as it sits behind the Differ. |\n\n## Examples\n### Float Precision\n```python\nfrom ndl_tools import Differ\n\nOBJ_1 = {"a": 1.0, "b": 2.01}\nOBJ_2 = {"a": 1.01, "b": 2.011}\n\n\ndef float_mismatch():\n    differ = Differ()\n    result = differ.diff(OBJ_1, OBJ_2)\n    assert not result\n    print(result.support)\n```\n<img src="https://github.com/nathan5280/ndl-tools/blob/develop/images/float-precision-fail.png" height="75"/>\n\nNote the highlights on the differences.  Red will indicate that something was deleted and blue that\nsomething was changed and yellow that something was added.\n\n#### Match\nLets apply the *FloatRoundNormalizer* when we do the diff and see if we can get the NDLs to match.\n\n```python\nfrom ndl_tools import Differ, FloatRoundNormalizer\n\nOBJ_1 = {"a": 1.0, "b": 2.01}\nOBJ_2 = {"a": 1.01, "b": 2.011}\n\n\ndef float_match():\n    differ = Differ()\n    float_round_normalizer = FloatRoundNormalizer(places=1)\n    result = differ.diff(OBJ_1, OBJ_2, normalizers=[float_round_normalizer])\n    assert result\n    print(result.support)\n```\n\n<img src="https://github.com/nathan5280/ndl-tools/blob/develop/images/float-precision-pass.png" height="75"/>\n\n\n#### Selector to Apply Different Nomalizers\n```python\nfrom ndl_tools import Differ, FloatRoundNormalizer, ListLastComponentSelector\n\nOBJ_1 = {"a": 1.0, "b": 2.01}\nOBJ_2 = {"a": 1.01, "b": 2.011}\n\n\ndef float_two_precision_match():\n    differ = Differ()\n    # Normalize the \'a\' element to 1 decimal place.\n    a_selector = ListLastComponentSelector(component_names=["a"])\n    one_float_round_normalizer = FloatRoundNormalizer(places=1, selectors=[a_selector])\n\n    # Normalize the \'b\' element to 2 decimal places.\n    b_selector = ListLastComponentSelector(component_names=["b"])\n    two_float_round_normalizer = FloatRoundNormalizer(\n        places=2, selectors=[b_selector]\n    )\n\n    result = differ.diff(OBJ_1, OBJ_2, normalizers=[two_float_round_normalizer, one_float_round_normalizer])\n    assert result\n    print(result.support)\n```\n\n<img src="https://github.com/nathan5280/ndl-tools/blob/develop/images/float-two-precision-pass.png" height="75"/>\n\nEach of the Normalizers can have a different selector or use the default which is to apply it to\nall elements.  The list of Normalizers are called in order until one normalizes the element or all \nnormalizers are exhausted.  There is an art to figuring out how to minimize the number of \nNormalizers and  Selectors you need to get two NDLs to match.   If you start getting to \nthe point where you have many of them it might be time to think about doing some \nprework on the NDL before comparing them.\n\n# Normalizers\nNormalizers are designed to be easily extensible.  Checkout the existing [Normalizers](https://github.com/nathan5280/ndl-tools/blob/develop/src/ndl_tools/normalizer.py)\nYou can easily see ways to extend these to support exponential numbers, dates, ...\n\n| Normalizer | Usage |\n| :--- | :---|\n| FloatRoundNormalizer | Round a floating point number to a set number of places. |\n| TodayDateNormalizer | Set the date to datetime.date.today(). |\n| StrTodayDateNormalizer | Convert a string representation of a date to string representation of today.  Useful if one of the NDLs was read from JSON and the dates weren\'t converted. |\n\nHave some fun building your own Normalizers.   It only takes a few lines in the __init__() and _normalize() methods.\n\n>[!WARNING]\n>If a normalizer was applied to an element, but doesn\'t actually normalize it, the normalizer should raise NotNormalizedError()\n\n# Selectors\nSelectors determine if the normalizer they are attached to will be applied to a given element.  Again \nthere is an art to figuring out the minimum number needed or the minimum that are still clear. \n\nWhile this isn\'t the most efficient way to rewrite the example above that rounds both \'a\' and \'b\' to \none decimal place, it does show how multiple selectors can be applied to a single normalizer.\n\n```python\nfrom ndl_tools import Differ, FloatRoundNormalizer, ListLastComponentSelector\n\nOBJ_1 = {"a": 1.0, "b": 2.01}\nOBJ_2 = {"a": 1.01, "b": 2.011}\n\n\ndef selector_chaining_match():\n    differ = Differ()\n\n    a_selector = ListLastComponentSelector(component_names=["a"])\n    b_selector = ListLastComponentSelector(component_names=["b"])\n    float_round_normalizer = FloatRoundNormalizer(places=1, selectors=[a_selector, b_selector])\n\n    result = differ.diff(OBJ_1, OBJ_2, normalizers=[float_round_normalizer])\n    assert result\n    print(result.support)\n```\n\n<img src="https://github.com/nathan5280/ndl-tools/blob/develop/images/selector-multiple-pass.png" height="75"/>\n\nThere are a few selectors out of the box, but you should subclass your own to minimize the complexity\nof your diff code.\n\n| Selector | Usage |\n| :--- | :--- |\n| ListLastComponentSelector | Match the last component in the element path to a list of names. |\n| ListAnyComponentSelector | Match any component in the element path to a list of names.  Good if you want to select a branch and its child elements. |\n| RegExSelector | Match the element path with the RegEx. |\n| NegativeSelector | Inverts the selection of the Selector it wraps. |\n\n# ListSorters\nListSorters are used to control how lists/sets are sorted.  The are applied using Selectors\nin the same as with Normalizers.  You shouldn\'t need anything other than \nthe two provided ListSorters, but if you need to the extensibility is there.\n',
    'author': 'Nate Atkins',
    'author_email': 'atkinsnw@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/nathan5280/ndl-tools',
    'package_dir': package_dir,
    'packages': packages,
    'package_data': package_data,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
