diff --git a/aperturedb/Utils.py b/aperturedb/Utils.py
index c062e991..f0232f0d 100644
--- a/aperturedb/Utils.py
+++ b/aperturedb/Utils.py
@@ -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.
@@ -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'''<
-
- | {entity} ({matched:,}) |
- '''
- 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'| '
- f'{prop.strip()} | '
- f''
- f'{matched:,} | '
- f''
- f'{idx_str}, {typ} |
'
- )
- 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 += (
- '| '
- '{} ({:,}) |
'
- ).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 += (
- '| '
- '{} | '
- ''
- '{} | '
- ''
- '{}, {} |
'
- ).format(cp_bg, cp_fg, prop.strip(), cp_bg, cp_fg, f"{matched:,}", cp_bg, cp_fg, idx_str, typ)
-
- 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'''<
+
+ | {entity_key} ({matched:,}) |
+ '''
+ 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'| '
+ f'{prop.strip()} | '
+ f''
+ f'{matched_prop:,} | '
+ f''
+ f'{idx_str}, {typ} |
'
+ )
+ 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 += (
+ '| '
+ '{} ({:,}) |
'
+ ).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 += (
+ '| '
+ '{} | '
+ ''
+ '{} | '
+ ''
+ '{}, {} |
'
+ ).format(cp_bg, cp_fg, prop.strip(), cp_bg, cp_fg, f"{matched_prop:,}", cp_bg, cp_fg, idx_str, typ)
+
+ 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"]}')
@@ -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)
diff --git a/aperturedb/__init__.py b/aperturedb/__init__.py
index 0b008225..2eec4e8a 100644
--- a/aperturedb/__init__.py
+++ b/aperturedb/__init__.py
@@ -3,7 +3,6 @@
import datetime
import os
import json
-import requests
from string import Template
import platform
import faulthandler
@@ -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.")
diff --git a/test/test_Utils.py b/test/test_Utils.py
index e7719b24..37312675 100644
--- a/test/test_Utils.py
+++ b/test/test_Utils.py
@@ -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()