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'''< - - - ''' - 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' ' - f'' - ) - 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 += '
{entity} ({matched:,})
' - f'{prop.strip()}' - f'{matched:,}' - f'{idx_str}, {typ}
' - '{} ({:,})
' - '{}' - '{}' - '{}, {}
>' - 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'''< + + + ''' + 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' ' + f'' + ) + 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 += '
{entity_key} ({matched:,})
' + f'{prop.strip()}' + f'{matched_prop:,}' + f'{idx_str}, {typ}
' + '{} ({:,})
' + '{}' + '{}' + '{}, {}
>' + 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()