Commit 37cf2d00 authored by Tim Bleimehl's avatar Tim Bleimehl 🤸🏼
Browse files

wip

parent 3a3f81d3
...@@ -2,32 +2,59 @@ from typing import Union, Dict, List ...@@ -2,32 +2,59 @@ from typing import Union, Dict, List
import time import time
import py2neo import py2neo
class CapturePoint():
class CapturePoint:
"""The state of a Neo4j database in terms of relationship and node count at a certain time""" """The state of a Neo4j database in terms of relationship and node count at a certain time"""
def __init__(self,labels:Dict[str,int],relations:Dict[str,int]):
self.timestamp:float = time.time() def __init__(
self.labels = labels self, labels_count: Dict[str, int], relations_count: Dict[str, int], schema
self.relations = relations ):
self.timestamp: float = time.time()
class NeoMetaLogger(): self.labels = labels_count
def __init__(self,connection: Union[py2neo.Graph,Dict]): self.relations = relations_count
self.capture_points:List[CapturePoint] = [] self.schema = schema
tn = schema["nodes"][0]
print(tn, tn.relationships)
exit()
def __hash__(self):
return hash(self.timestamp)
def __eq__(self, other: "CapturePoint") -> bool:
return type(other) is type(self) and self.timestamp == other.timestamp
class NeoMetaLogger:
def __init__(self, connection: Union[py2neo.Graph, Dict]):
self.capture_points: List[CapturePoint] = []
if isinstance(connection, dict): if isinstance(connection, dict):
self.graph: py2neo.Graph = py2neo.Graph(**connection) self.graph: py2neo.Graph = py2neo.Graph(**connection)
elif isinstance(connection,py2neo.Graph): elif isinstance(connection, py2neo.Graph):
self.graph = connection self.graph = connection
else: else:
raise TypeError(f"Expected 'py2neo.Graph' or 'dict'. Got '{type(connection)}'") raise TypeError(
f"Expected 'py2neo.Graph' or 'dict'. Got '{type(connection)}'"
)
def capture(self): def capture(self):
labels_count = self._count_labels() labels_count = self._count_labels()
relations_count = self._count_relations() relations_count = self._count_relations()
self.capture_points.append(CapturePoint(labels_count, relations_count))
def _count_labels(self) -> Dict[str,int]: self.capture_points.append(
all_labels: List[str] = self.graph.run("CALL db.labels() yield label return collect(label) as res").data()[0]["res"] CapturePoint(labels_count, relations_count, self._query_schema())
labels_count: Dict[str,int] = {} )
def _query_schema(self):
# call db.schema.visualization
return self.graph.run(
"call db.schema.visualization yield nodes, relationships"
).data()[0]
def _count_labels(self) -> Dict[str, int]:
all_labels: List[str] = self.graph.run(
"CALL db.labels() yield label return collect(label) as res"
).data()[0]["res"][0]
labels_count: Dict[str, int] = {}
for label in all_labels: for label in all_labels:
query_label_count = f""" query_label_count = f"""
...@@ -36,22 +63,91 @@ class NeoMetaLogger(): ...@@ -36,22 +63,91 @@ class NeoMetaLogger():
""" """
labels_count[label] = self.graph.run(query_label_count).data()[0]["res"] labels_count[label] = self.graph.run(query_label_count).data()[0]["res"]
return labels_count return labels_count
def _count_relations(self) -> Dict[str,int]: def _count_relations(self) -> Dict[str, int]:
all_rels: List[str] = self.graph.run(
"CALL db.relationshipTypes() yield relationshipType return collect(relationshipType) as res"
).data()[0]["res"]
rels_count: Dict[str, int] = {}
for rel in all_rels:
query_rel_count = f"""
MATCH ()-[:{rel}]->()
return count(*) AS res
"""
rels_count[rel] = self.graph.run(query_rel_count).data()[0]["res"]
return rels_count
pass pass
def get_last_changes(self) -> Dict[str,Dict[str,int]]: def get_last_changes(self) -> Dict[str, Dict[str, int]]:
"""Get changes, in terms of quantity, of labels and relations since the last capture compared to the current capture """Get changes, in terms of quantity, of labels and relations since the last capture compared to the current capture
Returns: Returns:
Dict[str,Dict[str,int]]: A base dictonary "{'labels':{...},'relation':{...}}" containing two dictoniries listing changes (in terms of quantity) for labels and relations. Dict[str,Dict[str,int]]: A base dictonary "{'labels':{...},'relation':{...}}" containing two dictoniries listing changes (in terms of quantity) for labels and relations.
""" """
pass return self.get_changes_since(capture_point_index=-2)
def get_changes_since(self,capture:CapturePoint=None,capture_index:int=None,time:float=None) -> Dict[str,Dict[str,int]]:
def get_changes_since(
self,
capture_point: CapturePoint = None,
capture_point_index: int = None,
capture_point_time: float = None,
) -> Dict[str, Dict[str, int]]:
"""Get changes, in terms of quantity, of labels and relations since the last capture compared to the current capture """Get changes, in terms of quantity, of labels and relations since the last capture compared to the current capture
Returns: Returns:
Dict[str,Dict[str,int]]: A base dictonary "{'labels':{...},'relation':{...}}" containing two dictoniries listing changes (in terms of quantity) for labels and relations. Dict[str,Dict[str,int]]: A base dictonary "{'labels':{...},'relation':{...}}" containing two dictoniries listing changes (in terms of quantity) for labels and relations.
""" """
pass current_capture_point: CapturePoint = (
self.capture_points[-1] if self.capture_points else None
)
compare_capture_point: CapturePoint = None
if capture_point_time:
raise NotImplementedError()
elif capture_point_index:
compare_capture_point = self.capture_points[capture_point_index]
elif capture_point:
compare_capture_point = capture_point
return self.get_changes(
from_capture=compare_capture_point, to_capture=current_capture_point
)
def get_changes(self, from_capture: CapturePoint, to_capture: CapturePoint):
if len(self.capture_points) <= 1:
raise ValueError(
f"You need at least 2 capture points to compare any changes. Got only {len(self.capture_points)} points"
)
# Compare labels
result: Dict = {"labels": {}, "relations": {}}
if to_capture.labels or from_capture.labels:
diff_labels = set(to_capture.labels.items()) ^ set(
from_capture.labels.items()
)
for diff_label in set([lbl[0] for lbl in diff_labels]):
result["labels"][diff_label] = (
to_capture.labels[diff_label]
if diff_label in to_capture.labels
else 0
) - (
from_capture.labels[diff_label]
if diff_label in from_capture.labels
else 0
)
# Compare relations
if not to_capture.relations and not from_capture.relations:
return result
diff_relations = set(to_capture.relations.items()) ^ set(
from_capture.relations.items()
)
for diff_rel in set([lbl[0] for lbl in diff_relations]):
result["relations"][diff_rel] = (
to_capture.relations[diff_rel]
if diff_rel in to_capture.relations
else 0
) - (
from_capture.relations[diff_rel]
if diff_rel in from_capture.relations
else 0
)
return result
py2neo py2neo
\ No newline at end of file DZDUtils
\ No newline at end of file
import sys import sys
import os import os
from typing import Dict,List
import json
import py2neo
from DZDutils.neo4j import wait_for_db_boot
if __name__ == "__main__": if __name__ == "__main__":
SCRIPT_DIR = os.path.dirname( SCRIPT_DIR = os.path.dirname(
os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))) os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__)))
...@@ -10,6 +13,12 @@ if __name__ == "__main__": ...@@ -10,6 +13,12 @@ if __name__ == "__main__":
sys.path.insert(0, os.path.normpath(SCRIPT_DIR)) sys.path.insert(0, os.path.normpath(SCRIPT_DIR))
from neo_meta_logger import NeoMetaLogger from neo_meta_logger import NeoMetaLogger
NEO4J: Dict = json.loads(os.getenv("NEO4J","{}"))
wait_for_db_boot(NEO4J)
graph = py2neo.Graph(**NEO4J)
mlog = NeoMetaLogger({}) mlog = NeoMetaLogger({})
graph.run("CREATE p = (:NEO_META_LOG_TESTNODE{name:'1'})-[:NEO_META_LOG_TESTREL]->(:NEO_META_LOG_TARGET_TESTNODE {name: '2'})")
mlog.capture()
graph.run("CREATE p = (:NEO_META_LOG_TESTNODE{name:'3'})-[:NEO_META_LOG_TESTREL]->(:NEO_META_LOG_TARGET_TESTNODE {name: '4'})")
mlog.capture() mlog.capture()
print(mlog.get_last_changes())
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment