Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 68 additions & 55 deletions aperturedb/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ def execute(self, query, blobs=[], success_statuses=[0]):

return r, b

@staticmethod
def _normalize_class_data(data):
if isinstance(data, list):
return data
if isinstance(data, dict):
if "matched" in data:
return [data]
return list(data.values())
return [data]

def status(self):
"""
Executes a `GetStatus` query.
Expand Down Expand Up @@ -173,60 +183,61 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source:
entities = r['entities']['classes']
connections = r['connections']['classes']

for entity, data in entities.items():
matched = data["matched"]
# dictionary from name to (matched, indexed, type)
properties = data["properties"]
table = f'''<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="{colors["entity_background"]}" COLSPAN="3"><FONT COLOR="{colors["entity_foreground"]}"><B>{entity}</B> ({matched:,})</FONT></TD></TR>
'''
for prop, (matched, indexed, typ) in properties.items():
bg = colors["property_background"]
fg = colors["property_foreground"]
idx_str = "Indexed" if indexed else "Unindexed"
table += (
f'<TR><TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'<B>{prop.strip()}</B></FONT></TD> '
f'<TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'{matched:,}</FONT></TD> '
f'<TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'{idx_str}, {typ}</FONT></TD></TR>'
)
for connection, data in connections.items():
data_list = [data] if isinstance(data, dict) else data
for data in data_list:
if data['src'] == entity:
matched = data["matched"]
# dictionary from name to (matched, indexed, type)
properties = data["properties"]
c_bg = colors["connection_background"]
c_fg = colors["connection_foreground"]
table += (
'<TR><TD BGCOLOR="{}" COLSPAN="3" '
'PORT="{}"><FONT COLOR="{}">'
'<B>{}</B> ({:,})</FONT></TD></TR>'
).format(c_bg, connection, c_fg, connection, matched)
if properties:
for prop, (matched, indexed, typ) in properties.items():
cp_bg = colors["connection_property_background"]
cp_fg = colors["connection_property_foreground"]
idx_str = "Indexed" if indexed else "Unindexed"
table += (
'<TR><TD BGCOLOR="{}"><FONT COLOR="{}">'
'<B>{}</B></FONT></TD> '
'<TD BGCOLOR="{}"><FONT COLOR="{}">'
'{}</FONT></TD> '
'<TD BGCOLOR="{}"><FONT COLOR="{}">'
'{}, {}</FONT></TD></TR>'
).format(cp_bg, cp_fg, prop.strip(), cp_bg, cp_fg, f"{matched:,}", cp_bg, cp_fg, idx_str, typ)

table += '</TABLE>>'
dot.node(entity, label=table)

for entity_key, entity_data in entities.items():
entity_data_list = self._normalize_class_data(entity_data)
for data in entity_data_list:
matched = data["matched"]
# dictionary from name to (matched, indexed, type)
properties = data["properties"]
table = f'''<
<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0">
<TR><TD BGCOLOR="{colors["entity_background"]}" COLSPAN="3"><FONT COLOR="{colors["entity_foreground"]}"><B>{entity_key}</B> ({matched:,})</FONT></TD></TR>
'''
for prop, (matched_prop, indexed, typ) in properties.items():
bg = colors["property_background"]
fg = colors["property_foreground"]
idx_str = "Indexed" if indexed else "Unindexed"
table += (
f'<TR><TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'<B>{prop.strip()}</B></FONT></TD> '
f'<TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'{matched_prop:,}</FONT></TD> '
f'<TD BGCOLOR="{bg}"><FONT COLOR="{fg}">'
f'{idx_str}, {typ}</FONT></TD></TR>'
)
for connection, conn_data_obj in connections.items():
conn_data_list = self._normalize_class_data(conn_data_obj)
for conn_data in conn_data_list:
if conn_data['src'] == entity_key:
matched_conn = conn_data["matched"]
# dictionary from name to (matched, indexed, type)
conn_properties = conn_data["properties"]
c_bg = colors["connection_background"]
c_fg = colors["connection_foreground"]
table += (
'<TR><TD BGCOLOR="{}" COLSPAN="3" '
'PORT="{}"><FONT COLOR="{}">'
'<B>{}</B> ({:,})</FONT></TD></TR>'
).format(c_bg, connection, c_fg, connection, matched_conn)
if conn_properties:
for prop, (matched_prop, indexed, typ) in conn_properties.items():
cp_bg = colors["connection_property_background"]
cp_fg = colors["connection_property_foreground"]
idx_str = "Indexed" if indexed else "Unindexed"
table += (
'<TR><TD BGCOLOR="{}"><FONT COLOR="{}">'
'<B>{}</B></FONT></TD> '
'<TD BGCOLOR="{}"><FONT COLOR="{}">'
'{}</FONT></TD> '
'<TD BGCOLOR="{}"><FONT COLOR="{}">'
'{}, {}</FONT></TD></TR>'
).format(cp_bg, cp_fg, prop.strip(), cp_bg, cp_fg, f"{matched_prop:,}", cp_bg, cp_fg, idx_str, typ)

table += '</TABLE>>'
dot.node(entity_key, label=table)
if isinstance(connections, dict):
for connection, data in connections.items():
data_list = [data] if isinstance(data, dict) else data
data_list = self._normalize_class_data(data)
for data in data_list:
dot.edge(f'{data["src"]}:{connection}',
f'{data["dst"]}')
Expand Down Expand Up @@ -308,15 +319,17 @@ def summary(self):
print(f"Total entities types: {total_entities}")
total_nodes = 0
for c in entities_classes:
total_nodes += self._object_summary(c, r["entities"]["classes"][c])
entity_data_list = self._normalize_class_data(
r["entities"]["classes"][c])
for entity_data in entity_data_list:
total_nodes += self._object_summary(c, entity_data)

print(f"---------------- Connections ----------------")
print(f"Total connections types: {total_connections}")
total_edges = 0
for c in connections_classes:
connections = r["connections"]["classes"][c]
connections_list = [connections] if isinstance(
connections, dict) else connections
connections_list = self._normalize_class_data(connections)

for connection in connections_list:
total_edges += self._object_summary(c, connection)
Expand Down
12 changes: 0 additions & 12 deletions aperturedb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import datetime
import os
import json
import requests
from string import Template
import platform
import faulthandler
Expand Down Expand Up @@ -80,14 +79,3 @@ def emit(self, record):
error_console_handler.setLevel(log_console_level)
error_console_handler.setFormatter(formatter)
logger.addHandler(error_console_handler)

try:
latest_version = json.loads(requests.get(
"https://pypi.org/pypi/aperturedb/json", timeout=1).text)["info"]["version"]
except Exception as e:
logger.warning(
f"Failed to get latest version: {e}. You are using version {__version__}")
latest_version = None
if __version__ != latest_version:
logger.warning(
f"The latest version of aperturedb is {latest_version}. You are using version {__version__}. It is recommended to upgrade.")
89 changes: 89 additions & 0 deletions test/test_Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,92 @@ def test_remove_all_indexes(self, utils):

def test_get_descriptorset_list(self, utils):
assert utils.get_descriptorset_list() == []

def test_summary_supported_schema_shapes(self):
from unittest.mock import MagicMock
from aperturedb.Utils import Utils

mock_connector = MagicMock()
mock_connector.clone.return_value = mock_connector
utils = Utils(mock_connector)

utils.status = MagicMock(
return_value='[{"GetStatus": {"version": "1.0", "status": "OK", "info": "test"}}]')

single_dict_schema = {
"entities": {
"returned": 1,
"classes": {
"Person": {
"matched": 10,
"properties": {}
}
}
},
"connections": {
"returned": 1,
"classes": {
"Knows": {
"src": "Person",
"dst": "Person",
"matched": 5,
"properties": {}
}
}
}
}

list_schema = {
"entities": {
"returned": 1,
"classes": {
"Person": [{
"matched": 10,
"properties": {}
}]
}
},
"connections": {
"returned": 1,
"classes": {
"Knows": [{
"src": "Person",
"dst": "Person",
"matched": 5,
"properties": {}
}]
}
}
}

nested_dict_schema = {
"entities": {
"returned": 1,
"classes": {
"Person": {
"Person": {
"matched": 10,
"properties": {}
}
}
}
},
"connections": {
"returned": 1,
"classes": {
"Knows": {
"Person_Person": {
"src": "Person",
"dst": "Person",
"matched": 5,
"properties": {}
}
}
}
}
}

for schema in [single_dict_schema, list_schema, nested_dict_schema]:
utils.get_schema = MagicMock(return_value=schema)
# Should not raise exception
utils.summary()
Loading