def query_entity(self): nmatcher = NodeMatcher(self.graph) rmatcher = RelationshipMatcher(self.graph) # 大通有哪些车 a_node = nmatcher.match('品牌', name='上汽大通').first() b_node = nmatcher.match('车系').first() relation = rmatcher.match({a_node, b_node}).first() zz = self.graph.match((a_node, ), r_type=type(relation).__name__).all() result = [] for z in zz: result.append(z.end_node.get('name')) print('大通具有:') print(result) # nodes = rmatcher.match({a_node, b_node}).all() # print(nodes[0]) # print(nodes[0].start_node) # print(nodes[0].end_node) # print(nodes[0].nodes) # print(type(nodes[0]).__name__) # # A(实体)车的基本参数(实体) a_node = nmatcher.match('车型', name='2019款 2.0T柴油自动四驱精英型长厢高底盘').first() b_node = nmatcher.match('基本参数').first() relation = rmatcher.match({a_node, b_node}).first() print(relation) print(type(relation).__name__) print(dict(nmatcher.match(name='2019款 2.0T柴油自动四驱精英型长厢高底盘').first())) zz = self.graph.match(nodes=(relation.start_node, ), r_type=type(relation).__name__).all()[0].end_node print(zz)
def match(): """这里的节点是正常的,它有两个属性name和age name是Liz age是34 match("Person").where(age=34).first() 正常 match("Person").where(name='Liz').first() 正常 match("Person", name="Liz").first() 正常 match("Person", age=34).first() 正常 match("Person", age=34).where(name="Liz").first() None match("Person", name="Liz").where(age=34).first() None """ matcher_1 = NodeMatcher(graph) matcher_2 = RelationshipMatcher(graph) # TODO: 这里的 age 属性使用后返回结果为 None node = matcher_1.match("Person", name="Liz").where(age=34).first() relation = matcher_2.match(r_type='FRIENDS') return list(relation), node, type(relation)
class NLMGraph: """ The Memory Graph. Parameters ----------- graph: Graph The Neo4j Graph instance. """ graph: Graph def __post_init__(self): self.nmatcher = NodeMatcher(self.graph) self.rmatcher = RelationshipMatcher(self.graph) @raise_customized_error(Exception, DatabaseError) def push_graph(self, subgraph: Subgraph) -> bool: """ Push a subgraph (node, relationship, subgraph) to the Neo database. """ tx = self.graph.begin() tx.create(subgraph) tx.commit() return tx.finished() def add_node(self, label: str, name: str, props: dict) -> Node: """ Add a Node to database. Parameters ------------ label: Node label name: Node name props: Node property Returns -------- out: a Node. """ node = Node(label, name=name, **props) self.push_graph(node) return node def check_update_node(self, nlmgn: GraphNode, update_props: bool = False) -> Node: """ Check whether the given node is already in the graph. Parameters ------------ nlmgn: GraphNode The defined Node data type. Includes name, labels and properties. Returns -------- out: Node Whether it is already in the graph. If is, update with the new properties, if necessary and return the updated node. If not, return the created Node (and need to commit to the graph). """ label, name, props = nlmgn.label, nlmgn.name, nlmgn.props neogn = self.nmatcher.match(label, name=name).first() if neogn: if update_props: node = self.update_property(neogn, props) else: node = neogn else: node = self.add_node(label, name, props) return node @raise_customized_error(Exception, DatabaseError) def update_property(self, neog_oj, props: dict): """ Update a neo graph node or relationship. Parameters ------------ neog_oj: neo graph object, Node or Relationship Returns -------- out: updated Node or Relationship """ neog_oj_props = dict(neog_oj) if props and props != neog_oj_props: # make sure new props is behind the exisited props. neog_oj.update({**neog_oj_props, **props}) # only can be pushed when neog_oj is already in the graph # so we do not need push_graph function here self.graph.push(neog_oj) return neog_oj def add_relationship(self, start: Node, end: Node, kind: str, props: dict) -> Relationship: """ Add a Relationship to database. Parameters ------------ start: start Node end: end Node kind: Relationship kind props: Relationship property Returns -------- out: a Relationship. """ relation = Relationship(start, kind, end, **props) self.push_graph(relation) return relation def check_update_relationship(self, nlmgr: GraphRelation, update_props: bool = False) -> Relationship: """ Parameters ------------ nlmgr: GraphRelation The defined Relationship data type. Includes kind, start, end and properties. Returns -------- out: Relationship """ kind, props = nlmgr.kind, nlmgr.props start = self.check_update_node(nlmgr.start, update_props) end = self.check_update_node(nlmgr.end, update_props) neogr = self.rmatcher.match((start, end), r_type=kind).first() if neogr: if update_props: relation = self.update_property(neogr, props) else: relation = neogr else: relation = self.add_relationship(start, end, kind, props) return relation def add(self, gin: GraphRelation or GraphNode) -> Node or List[Node] or Relationship: """ Add a Node or Relationship to the database. Parameters ------------ gin: A GraphNode or GraphRelation (kind could be None) Returns -------- out: A Node or Relationship. """ if isinstance(gin, GraphNode): return self.add_node(gin.label, gin.name, gin.props) elif isinstance(gin, GraphRelation) and gin.kind: start = self.check_update_node(gin.start) end = self.check_update_node(gin.end) return self.add_relationship(start, end, gin.kind, gin.props) elif isinstance(gin, GraphRelation) and gin.kind == None: start = self.check_update_node(gin.start) end = self.check_update_node(gin.end) return (start, end) else: raise InputError def update( self, gin: GraphRelation or GraphNode) -> Node or List[Node] or Relationship: """ Update the property of a Node or Relationship to the database. Parameters ------------ gin: A GraphNode or GraphRelation (kind could be None) Returns -------- out: A Node or Relationship. """ if isinstance(gin, GraphNode): return self.check_update_node(gin, update_props=True) elif isinstance(gin, GraphRelation) and gin.kind: return self.check_update_relationship(gin, update_props=True) elif isinstance(gin, GraphRelation) and gin.kind == None: start = self.check_update_node(gin.start, update_props=True) end = self.check_update_node(gin.end, update_props=True) return (start, end) else: raise InputError def query(self, qin, topn=1, limit=10, fuzzy=False) -> list: """ Query by user given. Parameters ----------- qin: could be GraphNode, GraphRelation, or just Cypher. Returns --------- out: queried Nodes or Relationships. """ if isinstance(qin, GraphNode): ret = self._query_by_node(qin, topn, limit, fuzzy) elif isinstance(qin, GraphRelation): ret = self._query_by_relation(qin, topn, limit, fuzzy) elif isinstance(qin, str): ret = self._query_by_cypher(qin) else: raise InputError return ret def _sort_matched(self, matched_nodes: list, props: dict) -> list: """ Sort matched nodes by comparing their properties with the given props. """ ret = [] for node in matched_nodes: nprops = dict(node) num = 0 for k, v in props.items(): if k in nprops and nprops[k] == v: num += 1 ret.append((node, num)) sorted_ret = sorted(ret, key=lambda x: x[1], reverse=True) return [n for (n, _) in sorted_ret] @raise_customized_error(Exception, QueryError) def _query_by_node(self, gn: GraphNode, topn: int, limit: int, fuzzy: bool) -> List[Node]: """ Query node by given label and name. If None, then by those nodes whose nodes contains the given name """ label, name, props = gn.label, gn.name, gn.props nmatch = self.nmatcher.match(label) nodes = nmatch.where(name=name).limit(limit) if fuzzy and nodes.first() == None: nodes = nmatch.where(name__contains=name).limit(limit) nmlst = list(nodes) return self.__from_match_to_return(nmlst, props, topn) @raise_customized_error(Exception, QueryError) def _query_by_relation(self, gr: GraphRelation, topn: int, limit: int, fuzzy: bool) -> List[Relationship]: """ Query relations by given start, end and kind. If start and end are None, return []. If start or end is None, then by kind and start or end. If result is None, then by start or end, or by both. """ starts = self._query_by_node(gr.start, topn=1, limit=5, fuzzy=fuzzy) ends = self._query_by_node(gr.end, topn=1, limit=5, fuzzy=fuzzy) start = starts[0] if starts else None end = ends[0] if ends else None kind, props = gr.kind, gr.props # print("start: ", start) # print("end:", end) if not start and not end: return [] # start, end could be None # r_type could be None relations = self.rmatcher.match((start, end), r_type=kind).limit(limit) if relations.first() == None and start and end: relations = self.rmatcher.match((start, end)).limit(limit) rmlst = list(relations) return self.__from_match_to_return(rmlst, props, topn) def _query_by_cypher(self, cypher: str) -> types.GeneratorType: """ Return a generator, the content depends on your query input. """ pattern_match = re.compile(r'^ ?MATCH') pattern_limit = re.compile(r'LIMIT \d') if not pattern_match.search(cypher): raise OverstepError searched_limit = pattern_limit.search(cypher) topn = int(searched_limit.group().split()[-1]) if searched_limit else 5 try: cursor = self.graph.run(cypher) res = [] n = 0 for item in cursor: res.append(item.data()) n += 1 if n == topn: return res except Exception as e: raise QueryError def __from_match_to_return(self, matched_list: list, props: dict, topn: int) -> list: if not matched_list: return [] if len(matched_list) > 1 and props: ret = self._sort_matched(matched_list, props)[:topn] else: ret = matched_list[:topn] return ret @property def labels(self) -> frozenset: """all labels""" return self.graph.schema.node_labels @property def relationship_types(self) -> frozenset: """all relation types""" return self.graph.schema.relationship_types @property def nodes_num(self) -> int: """all nodes amounts""" return len(self.graph.nodes) @property def relationships_num(self) -> int: """all relations amounts""" return len(self.graph.relationships) @property def nodes(self) -> types.GeneratorType: """all nodes (a generator)""" return iter(self.graph.nodes.match()) @property def relationships(self) -> types.GeneratorType: """all relations (a generator)""" return iter(self.graph.relationships.match()) def excute(self, cypher) -> dict: """ Be careful to use this function. Especially when you're updating the database. This function will not check the duplicated nodes or relationships. """ try: run = self.graph.run(cypher) return dict(run.stats()) except Exception as e: raise InputError