def test_traversal_function_set(solar_system): """## set_""" # The **set_** function modifies the json document. # Use the set_ function to modify the star name. sun = get(path.star.name, solar_system) assert sun == 'Sun' set_(path.star.name, "RedSun", solar_system) sun = get(path.star.name, solar_system) assert sun == 'RedSun' assert solar_system["star"]["name"] == 'RedSun' # Use the set_ to add planet9. This example creates multiple objects in one step. name = get(path.star.planets.outer[4].name, solar_system, default=None) assert name is None planets_count = len(list(find(path.star.planets.wc[wc].name, solar_system))) assert planets_count == 8 set_(path.star.planets.outer[4].name, 'planet9', solar_system, cascade=True) name = get(path.star.planets.outer[4].name, solar_system, default=None) assert name == 'planet9' planets_count = len(list(find(path.star.planets.wc[wc].name, solar_system))) assert planets_count == 9
def test_query_examples_list_first_two_inner_planets(solar_system): expected = [solar_system["star"]["planets"]["inner"][0], solar_system["star"]["planets"]["inner"][1]] first_two_planets = [p for p in find(path.star.planets.inner[0:2], solar_system)] assert first_two_planets == expected first_two_planets = [p for p in find(path.star.planets.inner[0, 1], solar_system)] assert first_two_planets == expected
def test_path_has_filter_type_conversion(solar_system): """### has filter type conversion""" # Sometimes the value is the wrong type for the comparison operator. In this example the attribute # "Number of Moons" is str type. planets = [planet for planet in find(path.rec[has(path["Number of Moons"] > "5")].name, solar_system)] assert planets == ['Jupiter', 'Saturn'] # This is how to convert the type to an int before applying the comparison operator. planets = [planet for planet in find(path.rec[has(path["Number of Moons"] > 5, int)].name, solar_system)] assert planets == ['Jupiter', 'Saturn', 'Uranus', 'Neptune']
def test_path_filter_has_all(solar_system): """### logical and, or and not filters""" # #### has_all # A regular express to test if second letter in the value is an a. second_letter_is_a = re.compile(r".a.*").fullmatch # The **has_all** function evaluates as the logical **and** operator. It is equivalent to: (arg1 and arg2 and ...) found = [planet for planet in find( path.rec[has_all(path.diameter < 10000, (path.name, second_letter_is_a))].name, solar_system) ] assert found == ['Mars'] # #### has_any # The **has_any** function evaluates as the logical **or** operator. It is equivalent to: (arg1 and arg2 and ...) found = [planet for planet in find( path.rec[has_any(path.diameter < 10000, (path.name, second_letter_is_a))].name, solar_system) ] assert found == ['Mercury', 'Earth', 'Mars', 'Saturn'] # #### has_not # The **has_not** function evaluates as the logical **not** operator. It is equivalent to: (not arg) # This example find all the planets names not not equal to Earth. Note the double nots. found = [planet for planet in find( path.rec[has_not(path.name != 'Earth')].name, solar_system) ] assert found == ['Earth'] # #### Combining has, has_all, has_any, and has_not filters. # Each of the **has** function can be passed as arguments to any of the other **has** function to construct complex # boolean equation. This example is equivalent to: # (10000 > diameter or diameter > 20000) and second_letter_is_a(name)) found = [planet for planet in find( path.rec[has_all(has_any(path.diameter < 10000, path.diameter > 20000), (path.name, second_letter_is_a))].name, solar_system) ] assert found == ['Mars', 'Saturn'] # #### has.these # The decorator **has.these** can be used to construct the boolean equations more explicitly. This example shows # to use python built in and, or and not operators. @has.these(path.diameter < 10000, path.diameter > 20000, (path.name, second_letter_is_a)) def predicate(parent_match: Match, small_diameter, large_diameter, name_second_letter_is_a): return (small_diameter(parent_match) or large_diameter(parent_match)) and name_second_letter_is_a(parent_match) found = [planet for planet in find(path.rec[predicate].name, solar_system)] assert found == ['Mars', 'Saturn']
def test_traversal_function_find(solar_system): """## find""" # The **find** function returns an Iterator that iterates to each value the path leads to. Each value is # determine on its iteration. # Find all of the planet names. inner_planets = [planet for planet in find(path.star.planets.inner[wc].name, solar_system)] assert inner_planets == ['Mercury', 'Venus', 'Earth', 'Mars'] # The data source can be a json data structure or a Match object. parent_match = get_match(path.star.planets.inner, solar_system) inner_planets = [planet for planet in find(path[wc].name, parent_match)] assert inner_planets == ['Mercury', 'Venus', 'Earth', 'Mars']
def test_path_list_wildcard(solar_system): """### Wildcard as an Index.""" # The **wildcard** word can be used as a list index. It is useful for iterating over attributes. all_outer = [planet for planet in find(path.star.planets.outer[wildcard].name, solar_system)] assert all_outer == ["Jupiter", "Saturn", "Uranus", "Neptune"] # The **wc** is the short version of wildcard. all_outer = [planet for planet in find(path.star.planets.outer[wc].name, solar_system)] assert all_outer == ["Jupiter", "Saturn", "Uranus", "Neptune"] # The dictionary wildcard is given as dot notation and cannot be used to iterator over a list. The list wildcard # is given as an index and cannot be used to iterate over dictionary keys. all_planets = [p for p in find(path.star.planets.wc[wc].name, solar_system)] assert all_planets == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
def test_path_keys_wildcard(solar_system): """### Wildcard as a Key.""" # The **wildcard** attribute specifies all sibling keys. It is useful for iterating over attributes. star_children = [child for child in find(path.star.wildcard, solar_system)] assert star_children == [solar_system["star"]["name"], solar_system["star"]["diameter"], solar_system["star"]["age"], solar_system["star"]["planets"], ] # The **wc** is the short version of wildcard. star_children = [child for child in find(path.star.wc, solar_system)] assert star_children == [solar_system["star"]["name"], solar_system["star"]["diameter"], solar_system["star"]["age"], solar_system["star"]["planets"], ]
def test_quick_start(solar_system): """# Quick start""" # All of the treepath components should be imported as follows: # ```python # from treepath import path, find, wc, set_, get, has, get_match, find_matches, pathd, wildcard, \ # MatchNotFoundError, Match, log_to, has_all, has_any, has_not, pprop, mprop # ``` # A treepath example that fetches the value 1 from data. data = { "a": { "b": [ { "c": 1 }, { "c": 2 }] } } value = get(path.a.b[0].c, data) assert value == 1 # A treepath example that fetches the values 1 and 2 from data. value = [value for value in find(path.a.b[wc].c, data)] assert value == [1, 2]
def test_k_a_a_k_a_a_a_k_find_all_tuple(k_a_a_k_a_a_a_k): result = find(path.y[0, 1, 2][0, 1, 2].y[0, 1, 2][0, 1, 2][0, 1, 2].y, k_a_a_k_a_a_a_k) for expected_path, expected_value in gen_test_data(k_a_a_k_a_a_a_k, nyiy, naia, naia, nyiy, naia, naia, naia, yyia): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_keys_find_rec_x_trace(keys): expected_trace_messages = [ " at $.x got {'x': {'x': '1', 'y'...", " at $.x. got {'x': {'x': '1', 'y'...", " at $.x.x got {'x': '1', 'y': '2',...", " at $.x. got {'x': '1', 'y': '2',...", " at $.x.x.x got '1'", " at $.x.x. got '1'", ' at $.x.x.x. got no match', " at $.x.x. got '2'", ' at $.x.x.y. got no match', " at $.x.x. got '3'", ' at $.x.x.z. got no match', ' at $.x.x. got no match', " at $.x. got {'x': '4', 'y': '5',...", " at $.x.y.x got '4'", " at $.x.y. got '4'", ' at $.x.y.x. got no match', " at $.x.y. got '5'", ' at $.x.y.y. got no match', " at $.x.y. got '6'", ' at $.x.y.z. got no match', ' at $.x.y. got no match', " at $.x. got {'x': '7', 'y': '8',...", " at $.x.z.x got '7'", " at $.x.z. got '7'", ' at $.x.z.x. got no match', " at $.x.z. got '8'", ' at $.x.z.y. got no match', " at $.x.z. got '9'", ' at $.x.z.z. got no match', ' at $.x.z. got no match', ' at $.x. got no match' ] actual_trace_messages = [] def mock_print(message): actual_trace_messages.append(message) for _ in find(path.x.rec.x, keys, trace=log_to(mock_print)): pass assert actual_trace_messages == expected_trace_messages
def test_path_filter_customer_predicate(solar_system): """### A custom filter.""" # A predicate is a single argument function that returns anything. The argument is the current match. The has # function is a fancy predicate. # This example writes a custom predicate that find all of Earth's neighbours. def my_neighbor_is_earth(match: Match): i_am_planet = get_match(path.parent.parent.parent.planets, match, must_match=False) if not i_am_planet: return False index_before_planet = match.data_name - 1 before_planet = get_match(path[index_before_planet][has(path.name == "Earth")], match.parent, must_match=False) if before_planet: return True index_after_planet = match.data_name + 1 before_planet = get_match(path[index_after_planet][has(path.name == "Earth")], match.parent, must_match=False) if before_planet: return True return False earth = [planet for planet in find(path.rec[my_neighbor_is_earth].name, solar_system)] assert earth == ['Venus', 'Mars']
def test_a_k_k_a_k_k_k_a_find_all_tuple(a_k_k_a_k_k_k_a): result = find(path[0, 1, 2].y.y[0, 1, 2].y.y.y[0, 1, 2], a_k_k_a_k_k_k_a) for expected_path, expected_value in gen_test_data(a_k_k_a_k_k_k_a, naia, nyiy, nyiy, naia, nyiy, nyiy, nyiy, yaia): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_a_k_k_a_k_k_k_a_find_all_wildcard(a_k_k_a_k_k_k_a): result = find(path[0].wc.wc[0].wc.wc.wc[0], a_k_k_a_k_k_k_a) for expected_path, expected_value in gen_test_data(a_k_k_a_k_k_k_a, n0i0, naia, naia, n0i0, naia, naia, naia, y0i0): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_path_recursion(solar_system): """## Recursion""" # The **recursive** word implies recursive search. It executes a preorder tree traversal. The search algorithm # descends the tree hierarchy evaluating the path on each vertex until a match occurs. On each iteration it # continues where it left off. This is an example that finds all the planets names. all_planets = [p for p in find(path.star.planets.recursive.name, solar_system)] assert all_planets == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune'] # The **rec** is the short version of recursive. all_planets = [p for p in find(path.star.planets.rec.name, solar_system)] assert all_planets == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune'] # Here is another example that finds all the celestial bodies names. all_celestial_bodies = [p for p in find(path.rec.name, solar_system)] assert all_celestial_bodies == ['Sun', 'Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
def test_3d_find_all_slice_variety(three_dimensional_list): result = find(path[::-1][:1:][0::2], three_dimensional_list) for x in range(*slice(None, None, -1).indices(3)): for y in range(*slice(None, 1, None).indices(3)): for z in range(*slice(0, None, 2).indices(3)): actual = next(result) assert actual == three_dimensional_list[x][y][z] assert_done_iterating(result)
def test_3d_find_all_comma_delimited(three_dimensional_list): result = find(path[2, 1, 0][0, 1][0, 2, 1], three_dimensional_list) for x in [2, 1, 0]: for y in [0, 1]: for z in [0, 2, 1]: actual = next(result) assert actual == three_dimensional_list[x][y][z] assert_done_iterating(result)
def test_k_a_a_k_a_a_a_k_find_all_wildcard(k_a_a_k_a_a_a_k): result = find(path.wc[0][0].wc[0][0][0].wc, k_a_a_k_a_a_a_k) for expected_path, expected_value in gen_test_data(k_a_a_k_a_a_a_k, naia, n0i0, n0i0, naia, n0i0, n0i0, n0i0, yaia): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_path_has_filter(solar_system): """### has filter""" # The **has** function is a filter that evaluates a branched off path relative to its parent path. This example # finds all celestial bodies that have planets. sun = get(path.rec[has(path.planets)].name, solar_system) assert sun == "Sun" # This search finds all celestial bodies that have a has-moons attribute. all_celestial_bodies_moon_attribute = [planet for planet in find(path.rec[has(pathd.has_moons)].name, solar_system)] assert all_celestial_bodies_moon_attribute == ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune'] # This search finds all celestial bodies that have moons. Note the **operator.truth** is used to exclude planets # that don't have moons. all_celestial_bodies_moon_attribute = [planet for planet in find(path.rec[has(pathd.has_moons, operator.truth)].name, solar_system)] assert all_celestial_bodies_moon_attribute == ['Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
def test_k_a_a_k_a_a_a_k_find_all_tuple(k_a_a_k_a_a_a_k): result = find( path["x", "y", "z"][0][0]["x", "y", "z"][0][0][0]["x", "y", "z"], k_a_a_k_a_a_a_k) for expected_path, expected_value in gen_test_data(k_a_a_k_a_a_a_k, naia, n0i0, n0i0, naia, n0i0, n0i0, n0i0, yaia): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_path_list(solar_system): """### Indexes""" # List can be access using index. earth = get(path.star.planets.inner[2], solar_system) assert earth == solar_system["star"]["planets"]["inner"][2] # List the third inner and outer planet. last_two = [planet for planet in find(path.star.wc.wc[2].name, solar_system)] assert last_two == ['Earth', 'Uranus']
def test_path_list_slice(solar_system): """### Slices""" # List can be access using slices. # List the first two planets. first_two = [planet for planet in find(path.star.planets.outer[:2].name, solar_system)] assert first_two == ["Jupiter", "Saturn"] # List the last two planets. last_two = [planet for planet in find(path.star.planets.outer[-2:].name, solar_system)] assert last_two == ["Uranus", "Neptune"] # List all outer planets in reverse. last_two = [planet for planet in find(path.star.planets.outer[::-1].name, solar_system)] assert last_two == ["Neptune", "Uranus", "Saturn", "Jupiter"] # List the last inner and outer planets. last_two = [planet for planet in find(path.star.wc.wc[-1:].name, solar_system)] assert last_two == ["Mars", "Neptune"]
def test_3d_find_specific_tuple(three_dimensional_list): result = find(path[2, 3, "a", 1], three_dimensional_list) actual = next(result) expected = three_dimensional_list[2] assert actual == expected actual = next(result) expected = three_dimensional_list[1] assert actual == expected assert_done_iterating(result)
def test_path_has_filter_operators_as_single_argument_functions(solar_system): """### has filter comparison operators as single argument functions""" # A filter operator can be specified as a single argument function. Here an example that searches for planets that # have the same diameter as earth. earths_diameter = partial(operator.eq, 12756) earth = [planet for planet in find(path.rec[has(path.diameter, earths_diameter)].name, solar_system)] assert earth == ['Earth'] # Any single argument function can be used as an operator. This example uses a Regular Expression to finds # planets that end with s. name_ends_with_s = re.compile(r"\w+s").match earth = [planet for planet in find(path.rec[has(path.name, name_ends_with_s)].name, solar_system)] assert earth == ['Venus', 'Mars', 'Uranus'] # This example uses a closure to find planets that have the same diameter as earth. def smaller_than_earth(value): return value < 12756 earth = [planet for planet in find(path.rec[has(path.diameter, smaller_than_earth)].name, solar_system)] assert earth == ['Mercury', 'Venus', 'Mars']
def test_TraversingError_validate_message(keys): expected = f"TraversingError(Evaluation of predicate failed because of error: NotImplementedError(){os.linesep}" \ f" path: $.x.x.x{os.linesep}" \ f" last_match: $.x.x.x=1)" with pytest.raises(TraversingError) as exc_info: def crap(*args): raise NotImplementedError next(find(path.x.x.x[crap], keys)) assert repr(exc_info.value) == expected
def test_a_k_k_a_k_k_k_a_find_all_tuple(a_k_k_a_k_k_k_a): result = find( path[0]["x", "y", "z"]["x", "y", "z"][0]["x", "y", "z"]["x", "y", "z"]["x", "y", "z"][0], a_k_k_a_k_k_k_a) for expected_path, expected_value in gen_test_data(a_k_k_a_k_k_k_a, n0i0, naia, naia, n0i0, naia, naia, naia, y0i0): actual = next(result) assert actual == expected_value assert_done_iterating(result)
def test_keys_find_specific_tuple(keys): result = find(path["z", "a", 1, "x"], keys) actual = next(result) expected = keys["z"] assert actual == expected actual = next(result) expected = keys["x"] assert actual == expected assert_done_iterating(result)
def test_keys_find_x_a_z_trace(keys): expected_trace_messages = [ " at $.x got {'x': {'x': '1', 'y'...", ' at $.x.a got no match' ] actual_trace_messages = [] def mock_print(message): actual_trace_messages.append(message) for _ in find(path.x.a.z, keys, trace=log_to(mock_print)): pass assert actual_trace_messages == expected_trace_messages
def test_tracing(solar_system): """## Tracing Debugging""" # All of the functions: get, find, get_match and find_matchesm, support tracing. An option, when enabled, # records the route the algorithm takes to determine a match. # This example logs the route the algorithm takes to find the inner planets. The **print** # function is give to capture the logs, but any single argument function can be used. inner_planets = [planet for planet in find(path.star.planets.inner[wc].name, solar_system, trace=log_to(print))] assert inner_planets == ['Mercury', 'Venus', 'Earth', 'Mars'] # The results """
def test_keys_find_x_y_z_trace(keys): expected_trace_messages = [ " at $.x got {'x': {'x': '1', 'y'...", " at $.x.y got {'x': '4', 'y': '5',...", " at $.x.y.z got '6'" ] actual_trace_messages = [] def mock_print(message): actual_trace_messages.append(message) expected = keys["x"]["y"]["z"] for actual in find(path.x.y.z, keys, trace=log_to(mock_print)): assert actual == expected assert actual_trace_messages == expected_trace_messages
def test_keys_find_z_y_has_a_then_no_match_trace(keys): expected_trace_messages = [ " at $.z got {'x': {'x': '19', 'y...", " at $.z.y got {'x': '22', 'y': '23...", ' has .a got no match', ' at $.z.y[has($.a)] got no match' ] actual_trace_messages = [] def mock_print(message): actual_trace_messages.append(message) for _ in find(path.z.y[has(path.a)].x, keys, trace=log_to(mock_print)): pass assert actual_trace_messages == expected_trace_messages