From 1ee75a18fed25f663b4091aa9b39e7bad073b28d Mon Sep 17 00:00:00 2001 From: Jerome Guibert Date: Sat, 2 May 2020 18:22:56 +0200 Subject: [PATCH 1/6] clean doc --- README.md | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 3b2d415..73b3837 100644 --- a/README.md +++ b/README.md @@ -62,23 +62,18 @@ You could do the same with edges using ```search_edges```. [search_edges](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_edges) and [search_nodes](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_nodes) are based on [prepare_query](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.prepare_query) which return an Evaluator. -Evaluator are function with this signature: (context) -> bool - -Context is a dictionnary like structure (with in and [] methods, and support __contains__ or (__iter__ and __getitem__)) - +Quickly, ```Evaluator``` are function with this signature: (context) -> bool +And ```Context``` is a dictionary like structure (with in and [] methods, and support __contains__ or (__iter__ and __getitem__)) +With networkX, node and edge attributes are dictionary like. ## Query language -Define a json query language like [json-query-language](https://github.com/clue/json-query-language/blob/master/SYNTAX.md) +We sefine a json query language like [json-query-language](https://github.com/clue/json-query-language/blob/master/SYNTAX.md) against nodes or edges attributes. -A Path is a single string or a tuple of string which represente a path in a tree (here a dictionnary). - ### Expressions -All those expression are evaluate against a context wich is a dictionnary like (as can be a NodeDataView or an EdgeDataView). - Main expression syntax turn around this: ``` @@ -104,32 +99,35 @@ Test if a node/edge has an attribute product : { "definition": { "name": xxx }} } ``` +The tuple ```("product", "definition", "name")``` is a path in attribut dictionnary. +A Path is a single string or a tuple of string which represente a path in a tree (here a dictionary). + We support this operators: -| Name | Alias | Parameters | Description | -| -------- | :---: | --------------- | --------------------------------------------------------------------------------------------- | -| has | | Path | Check if path exists in context. | -| contains | | Path, str | Check if an attribut (specifed with path) exists and contains specified value. | -| eq | `==` | Path, Any | Check if an attribut (specifed with path) exists and equals specified value. | -| neq | `!=` | Path, Any | Check if an attribut (specifed with path) did not exists or not equals specified value. | -| gt | `<` | Path, Any | Check if an attribut (specifed with path) exists and greather that specified value. | -| lt | `<` | Path, Any | Check if an attribut (specifed with path) exists and lower that specified value. | -| gte | `>=` | Path, Any | Check if an attribut (specifed with path) exists and greather or equals that specified value. | -| lte | `<=` | Path, Any | Check if an attribut (specifed with path) exists and lower or equals that specified value. | -| in | `:=` | Path, List[Any] | Check if an attribut (specifed with path) exists and attribut value in specified values. | +| Name | Alias | Parameters | Description | +| -------- | :---: | --------------- | ----------------------------------------------------------------------------- | +| has | | Path | Check if path exists in context. | +| contains | | Path, str | Check if an attribut path exists and contains specified value. | +| eq | `==` | Path, Any | Check if an attribut path exists and equals specified value. | +| neq | `!=` | Path, Any | Check if an attribut path did not exists or not equals specified value. | +| gt | `<` | Path, Any | Check if an attribut path exists and greather that specified value. | +| lt | `<` | Path, Any | Check if an attribut path exists and lower that specified value. | +| gte | `>=` | Path, Any | Check if an attribut path exists and greather or equals that specified value. | +| lte | `<=` | Path, Any | Check if an attribut path exists and lower or equals that specified value. | +| in | `:=` | Path, List[Any] | Check if an attribut path exists and attribut value in specified values. | ### Boolean composition of matching expression We support this operators: -| Name | Alias | Parameters | Description | -| ---- | :---: | ------------- | --------------------- | -| and | `&&` | list of query | Define And operator. | -| or | \|\| | list of query | Define Or operator. | -| xor | | list of query | Define xor operator. | -| nxor | | list of query | Define nxor operator. | -| not | `!` | query | Define Not operator. | +| Name | Alias | Parameters | Description | +| ---- | :---: | ------------- | -------------- | +| and | `&&` | list of query | And operator. | +| or | \|\| | list of query | Or operator. | +| xor | | list of query | xor operator. | +| nxor | | list of query | nxor operator. | +| not | `!` | query | Not operator. | By default, a list of expressions is equivalent of an "AND" of this expressions. From 18aeca62d69adacce7b0cee69c48df8d11c1f59a Mon Sep 17 00:00:00 2001 From: Jerome Guibert Date: Mon, 4 May 2020 11:40:05 +0200 Subject: [PATCH 2/6] backup temp work --- CHANGELOG.md | 8 ++- Makefile | 2 +- README.md | 18 +++++-- networkx_query/__init__.py | 13 ++++- networkx_query/parser.py | 18 ++++++- networkx_query/query.py | 35 +++---------- networkx_query/relationship.py | 59 ++++++++++++++++++++++ networkx_query/utils.py | 84 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/test_search_relationship.py | 36 +++++++++++++ 10 files changed, 238 insertions(+), 37 deletions(-) create mode 100644 networkx_query/relationship.py create mode 100644 networkx_query/utils.py create mode 100644 tests/test_search_relationship.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d0d662c..7a00976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ # Change Log +## 1.0.1 + +- fix documentation syntax +- add search_direct_relationships function +- rewrote search_edges and search_nodes (avoid extra filter step) + ## 1.0.0 (2020-05-02) - complete documentation -- add litlle example +- add little example - add search_edges, search_nodes for quick and eazy usage - complete coverage diff --git a/Makefile b/Makefile index a1d1233..b4cfc51 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ GIT_DIR = .git @touch $@ poetry.lock: pyproject.toml - poetry lock + #poetry lock .cache: @mkdir -p .cache diff --git a/README.md b/README.md index 73b3837..b4b42ba 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ $ poetry add networkx-query ## Usage -Searching node: +### Searching nodes ```python import networkx as nx @@ -49,14 +49,23 @@ for node_id in search_nodes(g, {"==": [("product",), "chocolate"]}): print(node_id) >> 1 +``` + +### Searching edges +```python for edge_id in search_edges(g, {"eq": [("action",), "produce"]}): print(edge_id) >> (3, 2) ``` -You could do the same with edges using ```search_edges```. +### Searching relation ship + + +```python +``` + ## API @@ -66,6 +75,9 @@ Quickly, ```Evaluator``` are function with this signature: (context) -> bool And ```Context``` is a dictionary like structure (with in and [] methods, and support __contains__ or (__iter__ and __getitem__)) With networkX, node and edge attributes are dictionary like. + + + ## Query language We sefine a json query language like [json-query-language](https://github.com/clue/json-query-language/blob/master/SYNTAX.md) @@ -110,7 +122,7 @@ We support this operators: | contains | | Path, str | Check if an attribut path exists and contains specified value. | | eq | `==` | Path, Any | Check if an attribut path exists and equals specified value. | | neq | `!=` | Path, Any | Check if an attribut path did not exists or not equals specified value. | -| gt | `<` | Path, Any | Check if an attribut path exists and greather that specified value. | +| gt | `>` | Path, Any | Check if an attribut path exists and greather that specified value. | | lt | `<` | Path, Any | Check if an attribut path exists and lower that specified value. | | gte | `>=` | Path, Any | Check if an attribut path exists and greather or equals that specified value. | | lte | `<=` | Path, Any | Check if an attribut path exists and lower or equals that specified value. | diff --git a/networkx_query/__init__.py b/networkx_query/__init__.py index dcca142..7835b8e 100644 --- a/networkx_query/__init__.py +++ b/networkx_query/__init__.py @@ -2,9 +2,18 @@ from pkg_resources import DistributionNotFound, get_distribution from .definition import Evaluator, ParserException -from .query import search_nodes, search_edges, prepare_query +from .parser import prepare_query +from .query import search_nodes, search_edges +from .relationship import search_direct_relationships -__all__ = ['search_nodes', 'search_edges', 'prepare_query', 'ParserException', 'Evaluator'] +__all__ = [ + 'search_nodes', + 'search_edges', + 'search_direct_relationships', + 'prepare_query', + 'ParserException', + 'Evaluator', +] try: __version__ = get_distribution('networkx_query').version diff --git a/networkx_query/parser.py b/networkx_query/parser.py index cfc49f5..ad19d12 100644 --- a/networkx_query/parser.py +++ b/networkx_query/parser.py @@ -12,7 +12,7 @@ ) from .operator import * # noqa: F401,F403 -__all__ = ["parse", "explain", "compile_ast"] +__all__ = ["parse", "explain", "compile_ast", "prepare_query"] def _check_item_ast(item: ItemAST, stack: deque) -> ItemAST: @@ -67,3 +67,19 @@ def compile_ast(ast: ItemAST) -> Evaluator: if ast.op.combinator: return operator_factory(ast.op.function, *list(map(compile_ast, ast.args))) return operator_factory(ast.op.function, *ast.args) + + +def prepare_query(query: Dict) -> Evaluator: + """Transform expression query as a function. + + Arguments: + query (Dict): expression query as dictionary + + Returns: + (Evaluator): evaluator function + + Exceptions: + (ParserException): if a parse error occurs + + """ + return compile_ast(parse(expra=query)) diff --git a/networkx_query/query.py b/networkx_query/query.py index 0b05b40..72693d6 100644 --- a/networkx_query/query.py +++ b/networkx_query/query.py @@ -3,30 +3,15 @@ from networkx import Graph -from .definition import Evaluator, ParserException -from .parser import compile_ast, parse -__all__ = ['search_nodes', 'search_edges', 'prepare_query', 'ParserException'] +from .parser import prepare_query +from .utils import get_first_item, get_two_first_items - -def prepare_query(query: Dict) -> Evaluator: - """Transform expression query as a function. - - Arguments: - query (Dict): expression query as dictionary - - Returns: - (Evaluator): evaluator function - - Exceptions: - (ParserException): if a parse error occurs - - """ - return compile_ast(parse(expra=query)) +__all__ = ['search_nodes', 'search_edges'] def search_nodes(graph: Graph, query: Dict) -> Iterable[Any]: - """Search nodes in specified grapg which match query. + """Search nodes in specified graph which match query. Arguments: graph (Graph): networkx graph instance @@ -40,14 +25,11 @@ def search_nodes(graph: Graph, query: Dict) -> Iterable[Any]: """ _predicate = prepare_query(query) - - return map( - lambda n: n[0], filter(lambda n: n[1], map(lambda node: (node[0], _predicate(node[1])), graph.nodes(data=True))) - ) + return map(get_first_item, filter(lambda node: _predicate(node[1]), graph.nodes(data=True))) def search_edges(graph: Graph, query: Dict) -> Iterable[Tuple]: - """Search edges in specified grapg which match query. + """Search edges in specified graph which match query. Arguments: graph (Graph): networkx graph instance @@ -61,7 +43,4 @@ def search_edges(graph: Graph, query: Dict) -> Iterable[Tuple]: """ _predicate = prepare_query(query) - return map( - lambda n: n[0], - filter(lambda n: n[1], map(lambda edge: ((edge[0], edge[1]), _predicate(edge[2])), graph.edges(data=True))), - ) + return map(get_two_first_items, filter(lambda edge: _predicate(edge[2]), graph.edges(data=True))) diff --git a/networkx_query/relationship.py b/networkx_query/relationship.py new file mode 100644 index 0000000..d6f25e3 --- /dev/null +++ b/networkx_query/relationship.py @@ -0,0 +1,59 @@ +from typing import Dict, Iterable, Tuple, Optional + +from networkx import Graph +from .query import search_edges, prepare_query +from .utils import get_first_item, get_second_item +from itertools import chain + +__all__ = ["search_direct_relationships", "join_relationship"] + + +def search_direct_relationships( + graph: Graph, source: Optional[Dict] = None, edge: Optional[Dict] = None, target: Optional[Dict] = None +) -> Iterable[Tuple]: + """Search direct relation ship. + + Arguments: + graph (Graph): graph instance + source (Optional[Dict]): optional source node query constraint + edge (Optional[Dict]): optional edge query constraint + target (Optional[Dict]): optional target node query constraint + + Returns: + (Iterable[Tuple]): itrable tuple of edge + + """ + _iterable = search_edges(graph=graph, query=edge) if edge else graph.edges() + + if source: + _predicate_source = prepare_query(source) + _iterable = filter(lambda edge: _predicate_source(graph.nodes[edge[0]]), _iterable) + + if target: + _predicate_target = prepare_query(target) + _iterable = filter(lambda edge: _predicate_target(graph.nodes[edge[1]]), _iterable) + + return _iterable + + +def join_relationship( + graph: Graph, source: Iterable[Tuple], target: Iterable[Tuple], join_on_source_origin: Optional[bool] = True +) -> Iterable[Tuple]: + """Join two relation ship. + + With source = (a, b), target = (c, d) + If join_on_source_origin is set, return (e, f) as e in source(e, _) and e in target(e, _) + else return edge (e, _) or (_ e) as e in source(_, e) and e in target(e, _) + """ + + _source = set(source) + _target = set(target) + + _source_filter = get_first_item if join_on_source_origin else get_second_item + + _nodes = set(filter(_source_filter, _source)).intersection(set(filter(get_first_item, _target))) + + return chain( + filter(lambda edge: _source_filter(edge) in _nodes, _source), + filter(lambda edge: get_first_item(edge) in _nodes, _target), + ) diff --git a/networkx_query/utils.py b/networkx_query/utils.py new file mode 100644 index 0000000..e795d03 --- /dev/null +++ b/networkx_query/utils.py @@ -0,0 +1,84 @@ +"""Utilities with networkx.""" +from typing import Any, Callable, Tuple, List, Iterable + +from networkx import Graph, all_simple_paths + +from networkx.utils import pairwise + + +__all__ = [ + "get_first_item", + "get_second_item", + "get_two_first_items", + "filter_by_degree", + "get_attributs", + 'search_root_nodes', + 'search_leaf_nodes', + 'search_simple_path', +] + + +def get_first_item(t: Tuple) -> Any: + """Returns first item of tuple.""" + return t[0] + + +def get_second_item(t: Tuple) -> Any: + """Returns second item of tuple.""" + return t[1] + + +def get_two_first_items(t: Tuple) -> Tuple: + """Return (first, second) item of tuple as tuple.""" + return (t[0], t[1]) + + +def filter_by_degree(degree: int) -> Callable[[Any], bool]: + """Generate a filter for specified degree.""" + + def _filter(node: Any) -> bool: + (id, d) = node + return d == degree + + return _filter + + +def get_attributs(*names: str) -> Callable[[Tuple], List]: + """Returns attributs list for specified node or edge.""" + + def _map(t: Tuple) -> List: + (v, d) = t + return [d[name] for name in names] + + return _map + + +def search_root_nodes(graph: Graph) -> Iterable[Any]: + """Search nodes with no 'in' edges. + + Arguments: + graph (Graph): networkx graph instance + + Returns: + (Iterable[Any]): results as an iterable of node identifier. + + """ + return map(get_first_item, filter(filter_by_degree(0), graph.in_degree())) + + +def search_leaf_nodes(graph: Graph) -> Iterable[Any]: + """Search nodes with no 'out' edges. + + Arguments: + graph (Graph): networkx graph instance + + Returns: + (Iterable[Any]): results as an iterable of node identifier. + + """ + return map(get_first_item, filter(filter_by_degree(0), graph.out_degree())) + + +def search_simple_path(graph: Graph, source: Any, target: Any, cutoff=None): + """Returns a list of edges.""" + return map(pairwise, all_simple_paths(graph, source=target, target=target, cutoff=cutoff)) diff --git a/pyproject.toml b/pyproject.toml index 126d454..dd71af2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "networkx_query" -version = "1.0.0" +version = "1.0.1" description = "NetworkX Query Tool" license = "MIT" authors = ["Jerome Guibert "] diff --git a/tests/test_search_relationship.py b/tests/test_search_relationship.py new file mode 100644 index 0000000..a77198e --- /dev/null +++ b/tests/test_search_relationship.py @@ -0,0 +1,36 @@ +import networkx as nx + +from networkx_query.query import search_direct_relationships + + +g = nx.DiGraph() +for i in range(30): + g.add_node(i, data=i) + +for i in range(10, 30): + g.add_edge(i - 10, i, data=i) + + +def test_search_direct_relationships_default(): + assert search_direct_relationships(g) == g.edges + + +def test_search_direct_relationships_free_edge_and_tail(): + assert list(search_direct_relationships(graph=g, source={"lt": ["data", 3]})) == [(0, 10), (1, 11), (2, 12)] + + +def test_search_direct_relationships_free_tail(): + assert list(search_direct_relationships(graph=g, source={"lt": ["data", 5]}, edge={"gt": ["data", 15]})) == [] + + assert list(search_direct_relationships(graph=g, source={"lt": ["data", 8]}, edge={"gt": ["data", 15]})) == [ + (6, 16), + (7, 17), + ] + + +def test_search_direct_relationships(): + assert list( + search_direct_relationships( + graph=g, source={"gt": ["data", 9]}, edge={"gt": ["data", 15]}, target={'lt': ["data", 22]} + ) + ) == [(10, 20), (11, 21)] From 91be14c5de9aff3c158f0e2b26c5f9d86a3cd1c3 Mon Sep 17 00:00:00 2001 From: Jerome Guibert Date: Mon, 4 May 2020 11:43:33 +0200 Subject: [PATCH 3/6] adjust coverage as function are not yet ready --- networkx_query/query.py | 1 - networkx_query/relationship.py | 9 +++++---- networkx_query/utils.py | 16 +++++++--------- tests/test_search_relationship.py | 3 +-- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/networkx_query/query.py b/networkx_query/query.py index 72693d6..eacbd7f 100644 --- a/networkx_query/query.py +++ b/networkx_query/query.py @@ -3,7 +3,6 @@ from networkx import Graph - from .parser import prepare_query from .utils import get_first_item, get_two_first_items diff --git a/networkx_query/relationship.py b/networkx_query/relationship.py index d6f25e3..f8f3273 100644 --- a/networkx_query/relationship.py +++ b/networkx_query/relationship.py @@ -1,9 +1,10 @@ -from typing import Dict, Iterable, Tuple, Optional +from itertools import chain +from typing import Dict, Iterable, Optional, Tuple from networkx import Graph -from .query import search_edges, prepare_query + +from .query import prepare_query, search_edges from .utils import get_first_item, get_second_item -from itertools import chain __all__ = ["search_direct_relationships", "join_relationship"] @@ -38,7 +39,7 @@ def search_direct_relationships( def join_relationship( graph: Graph, source: Iterable[Tuple], target: Iterable[Tuple], join_on_source_origin: Optional[bool] = True -) -> Iterable[Tuple]: +) -> Iterable[Tuple]: # pragma: no cover """Join two relation ship. With source = (a, b), target = (c, d) diff --git a/networkx_query/utils.py b/networkx_query/utils.py index e795d03..b778c6c 100644 --- a/networkx_query/utils.py +++ b/networkx_query/utils.py @@ -1,11 +1,9 @@ """Utilities with networkx.""" -from typing import Any, Callable, Tuple, List, Iterable +from typing import Any, Callable, Iterable, List, Tuple from networkx import Graph, all_simple_paths - from networkx.utils import pairwise - __all__ = [ "get_first_item", "get_second_item", @@ -23,7 +21,7 @@ def get_first_item(t: Tuple) -> Any: return t[0] -def get_second_item(t: Tuple) -> Any: +def get_second_item(t: Tuple) -> Any: # pragma: no cover """Returns second item of tuple.""" return t[1] @@ -33,7 +31,7 @@ def get_two_first_items(t: Tuple) -> Tuple: return (t[0], t[1]) -def filter_by_degree(degree: int) -> Callable[[Any], bool]: +def filter_by_degree(degree: int) -> Callable[[Any], bool]: # pragma: no cover """Generate a filter for specified degree.""" def _filter(node: Any) -> bool: @@ -43,7 +41,7 @@ def _filter(node: Any) -> bool: return _filter -def get_attributs(*names: str) -> Callable[[Tuple], List]: +def get_attributs(*names: str) -> Callable[[Tuple], List]: # pragma: no cover """Returns attributs list for specified node or edge.""" def _map(t: Tuple) -> List: @@ -53,7 +51,7 @@ def _map(t: Tuple) -> List: return _map -def search_root_nodes(graph: Graph) -> Iterable[Any]: +def search_root_nodes(graph: Graph) -> Iterable[Any]: # pragma: no cover """Search nodes with no 'in' edges. Arguments: @@ -66,7 +64,7 @@ def search_root_nodes(graph: Graph) -> Iterable[Any]: return map(get_first_item, filter(filter_by_degree(0), graph.in_degree())) -def search_leaf_nodes(graph: Graph) -> Iterable[Any]: +def search_leaf_nodes(graph: Graph) -> Iterable[Any]: # pragma: no cover """Search nodes with no 'out' edges. Arguments: @@ -79,6 +77,6 @@ def search_leaf_nodes(graph: Graph) -> Iterable[Any]: return map(get_first_item, filter(filter_by_degree(0), graph.out_degree())) -def search_simple_path(graph: Graph, source: Any, target: Any, cutoff=None): +def search_simple_path(graph: Graph, source: Any, target: Any, cutoff=None): # pragma: no cover """Returns a list of edges.""" return map(pairwise, all_simple_paths(graph, source=target, target=target, cutoff=cutoff)) diff --git a/tests/test_search_relationship.py b/tests/test_search_relationship.py index a77198e..54cb6bc 100644 --- a/tests/test_search_relationship.py +++ b/tests/test_search_relationship.py @@ -1,7 +1,6 @@ import networkx as nx -from networkx_query.query import search_direct_relationships - +from networkx_query.relationship import search_direct_relationships g = nx.DiGraph() for i in range(30): From 55af9721428ebf14f36c72cc7afbebcc870413cf Mon Sep 17 00:00:00 2001 From: Jerome Guibert Date: Mon, 4 May 2020 11:55:20 +0200 Subject: [PATCH 4/6] add doc --- README.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b4b42ba..9fbcabd 100644 --- a/README.md +++ b/README.md @@ -62,14 +62,66 @@ for edge_id in search_edges(g, {"eq": [("action",), "produce"]}): ### Searching relation ship +With ```search_direct_relationships``` you can made a query which filter edges on their : + - source node attributes + - edge attributes + - target node attributes + +With this graph: + +```python +import networkx as nx +from networkx_query import search_direct_relationships + +g = nx.DiGraph() +for i in range(30): + g.add_node(i, data=i) + +for i in range(10, 30): + g.add_edge(i - 10, i, data=i) +``` + +We can filtering all edges with source node with data < 3: ```python +list(search_direct_relationships(graph=g, source={"lt": ["data", 3]})) + +[(0, 10), (1, 11), (2, 12)] ``` +We can filtering all edges with: + - source node with data < 8 + - edge with data > 15 + +```python +list(search_direct_relationships(graph=g, source={"lt": ["data", 8]}, edge={"gt": ["data", 15]})) + +>> [(6, 16), (7, 17)] +``` + +We can filtering all edges with: + - source node with data > 9 + - edge with data > 15 + - target node with data < 22 + +```python +search_direct_relationships( + graph=g, source={"gt": ["data", 9]}, edge={"gt": ["data", 15]}, target={'lt': ["data", 22]} + ) + ) + +>> [(10, 20), (11, 21)] +``` + ## API -[search_edges](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_edges) and [search_nodes](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_nodes) are based on [prepare_query](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.prepare_query) which return an Evaluator. +[search_edges](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_edges) +[search_nodes](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_nodes) +[search_direct_relationships](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.search_direct_relationships) + + +All this function are based on [prepare_query](https://geronimo-iia.github.io/networkx-query/api.html#networkx_query.prepare_query) which return an Evaluator. Quickly, ```Evaluator``` are function with this signature: (context) -> bool And ```Context``` is a dictionary like structure (with in and [] methods, and support __contains__ or (__iter__ and __getitem__)) @@ -77,10 +129,9 @@ With networkX, node and edge attributes are dictionary like. - ## Query language -We sefine a json query language like [json-query-language](https://github.com/clue/json-query-language/blob/master/SYNTAX.md) +We define a little json query language like [json-query-language](https://github.com/clue/json-query-language/blob/master/SYNTAX.md) against nodes or edges attributes. From 51b896a7b8898976f1881b31ea689983bfe00c8d Mon Sep 17 00:00:00 2001 From: Jerome Guibert Date: Mon, 4 May 2020 11:58:38 +0200 Subject: [PATCH 5/6] generate docs --- Makefile | 3 +- docs/.buildinfo | 2 +- docs/_modules/index.html | 4 +- docs/_modules/networkx_query/definition.html | 2 +- docs/_modules/networkx_query/parser.html | 276 ++++++++++++++++++ docs/_modules/networkx_query/query.html | 38 +-- .../_modules/networkx_query/relationship.html | 251 ++++++++++++++++ docs/_static/documentation_options.js | 2 +- docs/api.html | 57 +++- docs/changelog.html | 25 +- docs/code_of_conduct.html | 2 +- docs/contributing.html | 2 +- docs/genindex.html | 6 +- docs/index.html | 110 +++++-- docs/license.html | 2 +- docs/objects.inv | Bin 466 -> 481 bytes docs/py-modindex.html | 2 +- docs/search.html | 2 +- docs/searchindex.js | 2 +- poetry.lock | 16 +- 20 files changed, 705 insertions(+), 99 deletions(-) create mode 100644 docs/_modules/networkx_query/parser.html create mode 100644 docs/_modules/networkx_query/relationship.html diff --git a/Makefile b/Makefile index b4cfc51..80159c3 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,8 @@ GIT_DIR = .git @touch $@ poetry.lock: pyproject.toml - #poetry lock + poetry lock + @touch $@ .cache: @mkdir -p .cache diff --git a/docs/.buildinfo b/docs/.buildinfo index 6eb79b4..2442094 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 338e0dc8e9233c8bf950dd6c371793f5 +config: 8dfca22d60d3e1d5c8a532a06cef1707 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 4b4bf95..19a2722 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -8,7 +8,7 @@ - Overview: module code — networkx_query 0.2.0 documentation + Overview: module code — networkx_query 1.0.1 documentation @@ -146,7 +146,9 @@

All modules for which code is available

diff --git a/docs/_modules/networkx_query/definition.html b/docs/_modules/networkx_query/definition.html index 1f75933..695e5dd 100644 --- a/docs/_modules/networkx_query/definition.html +++ b/docs/_modules/networkx_query/definition.html @@ -8,7 +8,7 @@ - networkx_query.definition — networkx_query 0.2.0 documentation + networkx_query.definition — networkx_query 1.0.1 documentation diff --git a/docs/_modules/networkx_query/parser.html b/docs/_modules/networkx_query/parser.html new file mode 100644 index 0000000..b916f3f --- /dev/null +++ b/docs/_modules/networkx_query/parser.html @@ -0,0 +1,276 @@ + + + + + + + + + + + networkx_query.parser — networkx_query 1.0.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for networkx_query.parser

+"""Main parser and compile function."""
+from collections import deque
+from typing import Dict, List, Optional
+
+from .definition import (
+    NETWORKX_OPERATORS_REGISTERY,
+    Evaluator,
+    ItemAST,
+    OperatoryArity,
+    ParserException,
+    operator_factory,
+)
+from .operator import *  # noqa: F401,F403
+
+__all__ = ["parse", "explain", "compile_ast", "prepare_query"]
+
+
+def _check_item_ast(item: ItemAST, stack: deque) -> ItemAST:
+    (compliant, delta) = item.check_arity()
+    if not compliant:
+        raise ParserException(f'Invalid parameters for "{item.op.name}" operator ({delta} #parameters)', stack)
+    # if not item.check_profile():
+    #    raise ParserException(f'Invalid type parameters for "{item.op.name}" operator', stack)
+    return item
+
+
+def parse(expra: Dict, stack: Optional[deque] = None) -> ItemAST:
+    """Tranform json query into Item AST."""
+    result = []
+    _stack = stack if stack else deque()
+
+    _stack.append(expra)
+    for (op, v) in expra.items():
+        if op not in NETWORKX_OPERATORS_REGISTERY:
+            raise ParserException(f'Unsupported "{op}" operator', _stack)
+        operator = NETWORKX_OPERATORS_REGISTERY[op]
+
+        if operator.combinator:
+            args = []
+            # shortcut List declaration as single item
+            items = v if isinstance(v, List) else [v]
+            for item in items:
+                args.append(parse(item, _stack))
+            result.append(_check_item_ast(ItemAST(op=operator, args=args), _stack))
+
+        else:
+            result.append(_check_item_ast(ItemAST(op=operator, args=[v] if not isinstance(v, List) else v), _stack))
+
+    _stack.pop()
+    return result[0] if len(result) == 1 else ItemAST(op=NETWORKX_OPERATORS_REGISTERY['and'], args=result)
+
+
+def explain(ast: ItemAST) -> Dict:
+    """Convert ast as dict."""
+    result = {}
+    if ast.op.combinator:
+        result[ast.op.name] = list(map(explain, ast.args))
+    else:
+        result[ast.op.name] = ast.args
+    return result
+
+
+def compile_ast(ast: ItemAST) -> Evaluator:
+    """Compile AST in an Evaluator function."""
+    if ast.op.arity == OperatoryArity.UNARY:  # pragma: no cover
+        return operator_factory(ast.op.function)
+    if ast.op.combinator:
+        return operator_factory(ast.op.function, *list(map(compile_ast, ast.args)))
+    return operator_factory(ast.op.function, *ast.args)
+
+
+
[docs]def prepare_query(query: Dict) -> Evaluator: + """Transform expression query as a function. + + Arguments: + query (Dict): expression query as dictionary + + Returns: + (Evaluator): evaluator function + + Exceptions: + (ParserException): if a parse error occurs + + """ + return compile_ast(parse(expra=query))
+
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_modules/networkx_query/query.html b/docs/_modules/networkx_query/query.html index efde1b2..2d63a43 100644 --- a/docs/_modules/networkx_query/query.html +++ b/docs/_modules/networkx_query/query.html @@ -8,7 +8,7 @@ - networkx_query.query — networkx_query 0.2.0 documentation + networkx_query.query — networkx_query 1.0.1 documentation @@ -152,30 +152,14 @@

Source code for networkx_query.query

 
 from networkx import Graph
 
-from .definition import Evaluator, ParserException
-from .parser import compile_ast, parse
+from .parser import prepare_query
+from .utils import get_first_item, get_two_first_items
 
-__all__ = ['search_nodes', 'search_edges', 'prepare_query', 'ParserException']
-
-
-
[docs]def prepare_query(query: Dict) -> Evaluator: - """Transform expression query as a function. - - Arguments: - query (Dict): expression query as dictionary - - Returns: - (Evaluator): evaluator function - - Exceptions: - (ParserException): if a parse error occurs - - """ - return compile_ast(parse(expra=query))
+__all__ = ['search_nodes', 'search_edges']
[docs]def search_nodes(graph: Graph, query: Dict) -> Iterable[Any]: - """Search nodes in specified grapg which match query. + """Search nodes in specified graph which match query. Arguments: graph (Graph): networkx graph instance @@ -189,14 +173,11 @@

Source code for networkx_query.query

 
     """
     _predicate = prepare_query(query)
-
-    return map(
-        lambda n: n[0], filter(lambda n: n[1], map(lambda node: (node[0], _predicate(node[1])), graph.nodes(data=True)))
-    )
+ return map(get_first_item, filter(lambda node: _predicate(node[1]), graph.nodes(data=True)))
[docs]def search_edges(graph: Graph, query: Dict) -> Iterable[Tuple]: - """Search edges in specified grapg which match query. + """Search edges in specified graph which match query. Arguments: graph (Graph): networkx graph instance @@ -210,10 +191,7 @@

Source code for networkx_query.query

 
     """
     _predicate = prepare_query(query)
-    return map(
-        lambda n: n[0],
-        filter(lambda n: n[1], map(lambda edge: ((edge[0], edge[1]), _predicate(edge[2])), graph.edges(data=True))),
-    )
+ return map(get_two_first_items, filter(lambda edge: _predicate(edge[2]), graph.edges(data=True)))
diff --git a/docs/_modules/networkx_query/relationship.html b/docs/_modules/networkx_query/relationship.html new file mode 100644 index 0000000..5239d27 --- /dev/null +++ b/docs/_modules/networkx_query/relationship.html @@ -0,0 +1,251 @@ + + + + + + + + + + + networkx_query.relationship — networkx_query 1.0.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ +
    + +
  • Docs »
  • + +
  • Module code »
  • + +
  • networkx_query.relationship
  • + + +
  • + +
  • + +
+ + +
+
+
+
+ +

Source code for networkx_query.relationship

+from itertools import chain
+from typing import Dict, Iterable, Optional, Tuple
+
+from networkx import Graph
+
+from .query import prepare_query, search_edges
+from .utils import get_first_item, get_second_item
+
+__all__ = ["search_direct_relationships", "join_relationship"]
+
+
+
[docs]def search_direct_relationships( + graph: Graph, source: Optional[Dict] = None, edge: Optional[Dict] = None, target: Optional[Dict] = None +) -> Iterable[Tuple]: + """Search direct relation ship. + + Arguments: + graph (Graph): graph instance + source (Optional[Dict]): optional source node query constraint + edge (Optional[Dict]): optional edge query constraint + target (Optional[Dict]): optional target node query constraint + + Returns: + (Iterable[Tuple]): itrable tuple of edge + + """ + _iterable = search_edges(graph=graph, query=edge) if edge else graph.edges() + + if source: + _predicate_source = prepare_query(source) + _iterable = filter(lambda edge: _predicate_source(graph.nodes[edge[0]]), _iterable) + + if target: + _predicate_target = prepare_query(target) + _iterable = filter(lambda edge: _predicate_target(graph.nodes[edge[1]]), _iterable) + + return _iterable
+ + +def join_relationship( + graph: Graph, source: Iterable[Tuple], target: Iterable[Tuple], join_on_source_origin: Optional[bool] = True +) -> Iterable[Tuple]: # pragma: no cover + """Join two relation ship. + + With source = (a, b), target = (c, d) + If join_on_source_origin is set, return (e, f) as e in source(e, _) and e in target(e, _) + else return edge (e, _) or (_ e) as e in source(_, e) and e in target(e, _) + """ + + _source = set(source) + _target = set(target) + + _source_filter = get_first_item if join_on_source_origin else get_second_item + + _nodes = set(filter(_source_filter, _source)).intersection(set(filter(get_first_item, _target))) + + return chain( + filter(lambda edge: _source_filter(edge) in _nodes, _source), + filter(lambda edge: get_first_item(edge) in _nodes, _target), + ) +
+ +
+ +
+ + +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js index d09573a..dee849a 100644 --- a/docs/_static/documentation_options.js +++ b/docs/_static/documentation_options.js @@ -1,6 +1,6 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '0.2.0', + VERSION: '1.0.1', LANGUAGE: 'None', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/api.html b/docs/api.html index 92d739b..c6aa722 100644 --- a/docs/api.html +++ b/docs/api.html @@ -8,7 +8,7 @@ - API Reference — networkx_query 0.2.0 documentation + API Reference — networkx_query 1.0.1 documentation @@ -176,12 +176,15 @@

search_nodes(graph, query)

-

Search nodes in specified grapg which match query.

+

Search nodes in specified graph which match query.

search_edges(graph, query)

-

Search edges in specified grapg which match query.

+

Search edges in specified graph which match query.

-

prepare_query(query)

+

search_direct_relationships(graph, source, …)

+

Search direct relation ship.

+ +

prepare_query(query)

Transform expression query as a function.

@@ -205,10 +208,31 @@

Define a parser exception with stack of expression.

+
+
+networkx_query.prepare_query(query: Dict) → Callable[[Any], bool][source]
+

Transform expression query as a function.

+
+
Parameters
+

query (Dict) – expression query as dictionary

+
+
Returns
+

evaluator function

+
+
Return type
+

(Evaluator)

+
+
+
+
Exceptions:

(ParserException): if a parse error occurs

+
+
+
+
networkx_query.search_nodes(graph: networkx.classes.graph.Graph, query: Dict) → Iterable[Any][source]
-

Search nodes in specified grapg which match query.

+

Search nodes in specified graph which match query.

Parameters