Skip to content

Commit a539dbe

Browse files
lvrfrc87chadellitdependsnetworkspszulczewskiscetron
authored
Release 0.0.2 (#88)
* 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]>
1 parent 675ff06 commit a539dbe

8 files changed

+330
-198
lines changed

CHANGELOG.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## v0.0.2
4+
5+
- Update operator logic for returned result
6+
- Update docs
7+
8+
## v0.0.1
9+
10+
- Initial release
11+
312
## v0.0.1-beta.1
413

5-
Initial release
14+
- First beta release

README.md

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
# jdiff
22

3-
`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.
3+
`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
44

5-
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).
5+
Our primary use case is the examination of structured data returned from networking devices, such as:
6+
7+
* Compare the operational state of network devices pre and post change
8+
* Compare operational state of a device vs a "known healthy" state
9+
* Compare state of similar devices, such as a pair of leafs or a pair of backbone routers
10+
* Compare operational state of a component (interface, vrf, bgp peering, etc.) migrated from one device to another
11+
12+
However, the library fits other use cases where structured data needs to be operated on.
613

714
## Installation
815

@@ -12,10 +19,21 @@ Install from PyPI:
1219
pip install jdiff
1320
```
1421

15-
## Use cases
22+
## Intelligent Comparison
23+
24+
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_:
25+
26+
* Is the route table within 10% of routes before and after a change?
27+
* Is all of the interfaces that were up before the change, still up?
28+
* Are there at least 10k sessions of traffic on my firewall?
29+
* Is there there at least 2 interfaces up within lldp neighbors?
30+
31+
## Technical Overview
32+
33+
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).
1634

17-
`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.
35+
`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.
1836

1937
## Documentation
2038

21-
Documentation is hosted on Read the Docs at [jdiff Documentation](https://jdiff.readthedocs.io/).
39+
Documentation is hosted on Read the Docs at [jdiff Documentation](https://jdiff.readthedocs.io/).

docs/usage.md

+5-7
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ Let's run an example where we want to check the `burnedInAddress` key has a stri
416416
```
417417
We define the regex for matching a MAC address string. Then we define the path query to extract the data and create the check.
418418
```python
419-
>>> mac_regex = "(?:[0-9a-fA-F]:?){12}"
419+
>>> mac_regex = "^([0-9a-fA-F]{2}:){5}([0-9a-fA-F]{2})$"
420420
>>> path = "result[*].interfaces.*.[$name$,burnedInAddress]"
421421
>>> check = CheckType.create(check_type="regex")
422422
>>> actual_value = extract_data_from_json(actual_data, path)
@@ -556,7 +556,7 @@ We are looking for peers that have the same peerGroup, vrf, and state. Return pe
556556
{'10.1.0.0': {'peerGroup': 'IPv4-UNDERLAY-SPINE',
557557
'vrf': 'default',
558558
'state': 'Idle'}}],
559-
True)
559+
False)
560560
```
561561

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

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

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

609607
See `tests` folder in the repo for more examples.

jdiff/check_types.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def _validate(params) -> None: # type: ignore[override]
193193
params_key = params.get("params", {}).get("mode")
194194
params_value = params.get("params", {}).get("operator_data")
195195

196-
if not params_key or not params_value:
196+
if not params_key or params_value is None:
197197
raise ValueError(
198198
f"'mode' and 'operator_data' arguments must be provided. You have: {list(params['params'].keys())}."
199199
)
@@ -258,4 +258,4 @@ def result(self, evaluation_result):
258258
259259
This is required as Opertor return its own boolean within result.
260260
"""
261-
return evaluation_result[0], not evaluation_result[1]
261+
return evaluation_result[0], evaluation_result[1]

jdiff/operator.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,22 @@ def call_evaluation_logic():
3131
"""Operator valuation logic wrapper."""
3232
# reverse operands: https://docs.python.org/3.8/library/operator.html#operator.contains
3333
if call_ops == "is_in":
34-
if ops[call_ops](self.reference_data, evaluated_value):
34+
if not ops[call_ops](self.reference_data, evaluated_value):
3535
result.append(item)
3636
elif call_ops == "not_contains":
37-
if not ops[call_ops](evaluated_value, self.reference_data):
37+
if ops[call_ops](evaluated_value, self.reference_data):
3838
result.append(item)
3939
elif call_ops == "not_in":
40-
if not ops[call_ops](self.reference_data, evaluated_value):
40+
if ops[call_ops](self.reference_data, evaluated_value):
4141
result.append(item)
4242
elif call_ops == "in_range":
43-
if self.reference_data[0] < evaluated_value < self.reference_data[1]:
43+
if not self.reference_data[0] < evaluated_value < self.reference_data[1]:
4444
result.append(item)
4545
elif call_ops == "not_in_range":
46-
if not self.reference_data[0] < evaluated_value < self.reference_data[1]:
46+
if self.reference_data[0] < evaluated_value < self.reference_data[1]:
4747
result.append(item)
4848
# "<", ">", "contains"
49-
elif ops[call_ops](evaluated_value, self.reference_data):
49+
elif not ops[call_ops](evaluated_value, self.reference_data):
5050
result.append(item)
5151

5252
ops = {
@@ -64,14 +64,13 @@ def call_evaluation_logic():
6464
for evaluated_value in value.values():
6565
call_evaluation_logic()
6666
if result:
67-
return (result, True)
68-
return (result, False)
67+
return (result, False)
68+
return (result, True)
6969

70-
def all_same(self) -> Tuple[bool, Any]:
70+
def all_same(self) -> Tuple[Any, bool]:
7171
"""All same operator type implementation."""
7272
list_of_values = []
7373
result = []
74-
7574
for item in self.value_to_compare:
7675
# Create a list for compare values.
7776
list_of_values.extend(iter(item.values()))
@@ -80,13 +79,12 @@ def all_same(self) -> Tuple[bool, Any]:
8079
result.append(False)
8180
else:
8281
result.append(True)
83-
8482
if self.reference_data and not all(result):
8583
return (self.value_to_compare, False)
8684
if self.reference_data:
87-
return (self.value_to_compare, True)
85+
return ([], True)
8886
if not all(result):
89-
return (self.value_to_compare, True)
87+
return ([], True)
9088
return (self.value_to_compare, False)
9189

9290
def contains(self) -> Tuple[List, bool]:

0 commit comments

Comments
 (0)