Skip to content

Commit

Permalink
Release 0.0.2 (#88)
Browse files Browse the repository at this point in the history
* Update readme to start with use cases (#84)

* Update readme to start with use cases

* Apply suggestions from code review

Co-authored-by: Patryk Szulczewski <[email protected]>
Co-authored-by: Stephen Corry <[email protected]>

* Update README.md

Co-authored-by: Patryk Szulczewski <[email protected]>
Co-authored-by: Stephen Corry <[email protected]>

* Doc update (#87)

* Fix operator checks to follow other check_type logic. (#85)

* Fix operator checks to follow other check_type logic.

* Add new release 0.0.2

Co-authored-by: Patryk Szulczewski <[email protected]>

Co-authored-by: Christian Adell <[email protected]>
Co-authored-by: Ken Celenza <[email protected]>
Co-authored-by: Patryk Szulczewski <[email protected]>
Co-authored-by: Stephen Corry <[email protected]>
Co-authored-by: Patryk Szulczewski <[email protected]>
  • Loading branch information
6 people authored Sep 23, 2022
1 parent 675ff06 commit a539dbe
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 198 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## v0.0.2

- Update operator logic for returned result
- Update docs

## v0.0.1

- Initial release

## v0.0.1-beta.1

Initial release
- First beta release
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
# jdiff

`jdiff` is a lightweight Python library allowing you to examine structured data. `jdiff` provides an interface to intelligently compare JSON data objects and test for the presence (or absence) of keys. You can also examine and compare corresponding key-values.
`jdiff` is a lightweight Python library allowing you to examine structured data. `jdiff` provides an interface to intelligently compare--via key presense/absense and value comparison--JSON data objects

The library heavily relies on [JMESPath](https://jmespath.org/) for traversing the JSON object and finding the values to be evaluated. More on that [here](#customized-jmespath).
Our primary use case is the examination of structured data returned from networking devices, such as:

* Compare the operational state of network devices pre and post change
* Compare operational state of a device vs a "known healthy" state
* Compare state of similar devices, such as a pair of leafs or a pair of backbone routers
* Compare operational state of a component (interface, vrf, bgp peering, etc.) migrated from one device to another

However, the library fits other use cases where structured data needs to be operated on.

## Installation

Expand All @@ -12,10 +19,21 @@ Install from PyPI:
pip install jdiff
```

## Use cases
## Intelligent Comparison

The library provides the ability to ask more intelligent questions of a given data structure. Comparisons of data such as "Is my pre change state the same as my post change state", is not that interesting of a comparison. The library intends to ask intelligent questions _like_:

* Is the route table within 10% of routes before and after a change?
* Is all of the interfaces that were up before the change, still up?
* Are there at least 10k sessions of traffic on my firewall?
* Is there there at least 2 interfaces up within lldp neighbors?

## Technical Overview

The library heavily relies on [JMESPath](https://jmespath.org/) for traversing the JSON object and finding the values to be evaluated. More on that [here](#customized-jmespath).

`jdiff` has been developed around diffing and testing structured data returned from APIs and other Python modules and libraries (such as TextFSM). Our primary use case is the examination of structured data returned from networking devices. However, we found the library fits other use cases where structured data needs to be operated on, and is especially useful when working or dealing with data returned from APIs.
`jdiff` has been developed around diffing and testing structured data returned from Network APIs and libraries (such as TextFSM) but is equally useful when working or dealing with data returned from APIs.

## Documentation

Documentation is hosted on Read the Docs at [jdiff Documentation](https://jdiff.readthedocs.io/).
Documentation is hosted on Read the Docs at [jdiff Documentation](https://jdiff.readthedocs.io/).
12 changes: 5 additions & 7 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ Let's run an example where we want to check the `burnedInAddress` key has a stri
```
We define the regex for matching a MAC address string. Then we define the path query to extract the data and create the check.
```python
>>> mac_regex = "(?:[0-9a-fA-F]:?){12}"
>>> mac_regex = "^([0-9a-fA-F]{2}:){5}([0-9a-fA-F]{2})$"
>>> path = "result[*].interfaces.*.[$name$,burnedInAddress]"
>>> check = CheckType.create(check_type="regex")
>>> actual_value = extract_data_from_json(actual_data, path)
Expand Down Expand Up @@ -556,7 +556,7 @@ We are looking for peers that have the same peerGroup, vrf, and state. Return pe
{'10.1.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE',
'vrf': 'default',
'state': 'Idle'}}],
True)
False)
```

Let's now look to an example for the `in` operator. Keeping the same `data` and class object as above:
Expand All @@ -573,7 +573,7 @@ We are looking for "prefixesReceived" value in the operator_data list.
```python
>>> result = check.evaluate(check_args, value)
>>> result
([{'10.1.0.0': {'prefixesReceived': 50}}], False)
([{'7.7.7.7': {'prefixesReceived': 101}}], False)
```

What about `str` operator?
Expand All @@ -587,7 +587,7 @@ What about `str` operator?
{'10.1.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE'}}]
>>> result = check.evaluate(check_args, value)
>>> result
([{'7.7.7.7': {'peerGroup': 'EVPN-OVERLAY-SPINE'}}], False)
([{'10.1.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE'}}], False)
```

Can you guess what would be the outcome for an `int`, `float` operator?
Expand All @@ -601,9 +601,7 @@ Can you guess what would be the outcome for an `int`, `float` operator?
{'10.1.0.0': {'prefixesReceived': 50}}]
>>> result = check.evaluate(check_args, value)
>>> result
([{'7.7.7.7': {'prefixesReceived': 101}},
{'10.1.0.0': {'prefixesReceived': 50}}],
False)
([], True)
```

See `tests` folder in the repo for more examples.
4 changes: 2 additions & 2 deletions jdiff/check_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def _validate(params) -> None: # type: ignore[override]
params_key = params.get("params", {}).get("mode")
params_value = params.get("params", {}).get("operator_data")

if not params_key or not params_value:
if not params_key or params_value is None:
raise ValueError(
f"'mode' and 'operator_data' arguments must be provided. You have: {list(params['params'].keys())}."
)
Expand Down Expand Up @@ -258,4 +258,4 @@ def result(self, evaluation_result):
This is required as Opertor return its own boolean within result.
"""
return evaluation_result[0], not evaluation_result[1]
return evaluation_result[0], evaluation_result[1]
24 changes: 11 additions & 13 deletions jdiff/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,22 @@ def call_evaluation_logic():
"""Operator valuation logic wrapper."""
# reverse operands: https://docs.python.org/3.8/library/operator.html#operator.contains
if call_ops == "is_in":
if ops[call_ops](self.reference_data, evaluated_value):
if not ops[call_ops](self.reference_data, evaluated_value):
result.append(item)
elif call_ops == "not_contains":
if not ops[call_ops](evaluated_value, self.reference_data):
if ops[call_ops](evaluated_value, self.reference_data):
result.append(item)
elif call_ops == "not_in":
if not ops[call_ops](self.reference_data, evaluated_value):
if ops[call_ops](self.reference_data, evaluated_value):
result.append(item)
elif call_ops == "in_range":
if self.reference_data[0] < evaluated_value < self.reference_data[1]:
if not self.reference_data[0] < evaluated_value < self.reference_data[1]:
result.append(item)
elif call_ops == "not_in_range":
if not self.reference_data[0] < evaluated_value < self.reference_data[1]:
if self.reference_data[0] < evaluated_value < self.reference_data[1]:
result.append(item)
# "<", ">", "contains"
elif ops[call_ops](evaluated_value, self.reference_data):
elif not ops[call_ops](evaluated_value, self.reference_data):
result.append(item)

ops = {
Expand All @@ -64,14 +64,13 @@ def call_evaluation_logic():
for evaluated_value in value.values():
call_evaluation_logic()
if result:
return (result, True)
return (result, False)
return (result, False)
return (result, True)

def all_same(self) -> Tuple[bool, Any]:
def all_same(self) -> Tuple[Any, bool]:
"""All same operator type implementation."""
list_of_values = []
result = []

for item in self.value_to_compare:
# Create a list for compare values.
list_of_values.extend(iter(item.values()))
Expand All @@ -80,13 +79,12 @@ def all_same(self) -> Tuple[bool, Any]:
result.append(False)
else:
result.append(True)

if self.reference_data and not all(result):
return (self.value_to_compare, False)
if self.reference_data:
return (self.value_to_compare, True)
return ([], True)
if not all(result):
return (self.value_to_compare, True)
return ([], True)
return (self.value_to_compare, False)

def contains(self) -> Tuple[List, bool]:
Expand Down
Loading

0 comments on commit a539dbe

Please sign in to comment.