From 9c331a0adc69eb08de9722faa4a0c45b06914313 Mon Sep 17 00:00:00 2001 From: ad-claw000 Date: Tue, 19 May 2026 02:56:24 +0000 Subject: [PATCH 1/5] fix: Utils.summary fails with list properties Fixes aperture-data/aperturedb-python#619 --- aperturedb/Utils.py | 103 ++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/aperturedb/Utils.py b/aperturedb/Utils.py index c062e991..9c5a06fa 100644 --- a/aperturedb/Utils.py +++ b/aperturedb/Utils.py @@ -173,57 +173,58 @@ 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 = [entity_data] if isinstance(entity_data, dict) else 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 = [conn_data_obj] if isinstance(conn_data_obj, dict) else 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 From b654b9e06f9add9145903368d7fe3a71dcd822e8 Mon Sep 17 00:00:00 2001 From: ad-claw000 Date: Tue, 19 May 2026 03:02:57 +0000 Subject: [PATCH 2/5] fix: pre-commit formatting --- aperturedb/Utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aperturedb/Utils.py b/aperturedb/Utils.py index 9c5a06fa..f3d6838b 100644 --- a/aperturedb/Utils.py +++ b/aperturedb/Utils.py @@ -174,7 +174,8 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source: connections = r['connections']['classes'] for entity_key, entity_data in entities.items(): - entity_data_list = [entity_data] if isinstance(entity_data, dict) else entity_data + entity_data_list = [entity_data] if isinstance( + entity_data, dict) else entity_data for data in entity_data_list: matched = data["matched"] # dictionary from name to (matched, indexed, type) @@ -196,7 +197,8 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source: f'{idx_str}, {typ}' ) for connection, conn_data_obj in connections.items(): - conn_data_list = [conn_data_obj] if isinstance(conn_data_obj, dict) else conn_data_obj + conn_data_list = [conn_data_obj] if isinstance( + conn_data_obj, dict) else conn_data_obj for conn_data in conn_data_list: if conn_data['src'] == entity_key: matched_conn = conn_data["matched"] From 5f747ebb2bb7b06d8f782f4c02634641662797df Mon Sep 17 00:00:00 2001 From: claw Date: Tue, 19 May 2026 15:57:37 +0000 Subject: [PATCH 3/5] fix: address review comments on PR #667 Addresses review feedback from Copilot --- aperturedb/Utils.py | 25 +++++++++---- test/test_Utils.py | 89 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/aperturedb/Utils.py b/aperturedb/Utils.py index f3d6838b..9d1fa424 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. @@ -174,8 +184,7 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source: connections = r['connections']['classes'] for entity_key, entity_data in entities.items(): - entity_data_list = [entity_data] if isinstance( - entity_data, dict) else entity_data + 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) @@ -197,8 +206,7 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source: f'{idx_str}, {typ}' ) for connection, conn_data_obj in connections.items(): - conn_data_list = [conn_data_obj] if isinstance( - conn_data_obj, dict) else conn_data_obj + 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"] @@ -229,7 +237,7 @@ def visualize_schema(self, filename: str = None, format: str = "png") -> Source: 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"]}') @@ -311,15 +319,16 @@ 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/test/test_Utils.py b/test/test_Utils.py index e7719b24..d692ba5a 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() From fbf2f7fd088fc02426637027521ca96a7a679c23 Mon Sep 17 00:00:00 2001 From: claw Date: Tue, 19 May 2026 16:05:36 +0000 Subject: [PATCH 4/5] fix(init): remove blocking pypi version check --- aperturedb/__init__.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/aperturedb/__init__.py b/aperturedb/__init__.py index 0b008225..e2794fc0 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 @@ -81,13 +80,4 @@ def emit(self, record): 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.") + From 1e33af360946f662e50a3b21db4b2b7ccfe9b42c Mon Sep 17 00:00:00 2001 From: claw Date: Tue, 19 May 2026 16:17:36 +0000 Subject: [PATCH 5/5] style: apply autopep8 formatting to fix CI --- aperturedb/Utils.py | 3 ++- aperturedb/__init__.py | 2 -- test/test_Utils.py | 16 ++++++++-------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/aperturedb/Utils.py b/aperturedb/Utils.py index 9d1fa424..f0232f0d 100644 --- a/aperturedb/Utils.py +++ b/aperturedb/Utils.py @@ -319,7 +319,8 @@ def summary(self): print(f"Total entities types: {total_entities}") total_nodes = 0 for c in entities_classes: - entity_data_list = self._normalize_class_data(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) diff --git a/aperturedb/__init__.py b/aperturedb/__init__.py index e2794fc0..2eec4e8a 100644 --- a/aperturedb/__init__.py +++ b/aperturedb/__init__.py @@ -79,5 +79,3 @@ def emit(self, record): error_console_handler.setLevel(log_console_level) error_console_handler.setFormatter(formatter) logger.addHandler(error_console_handler) - - diff --git a/test/test_Utils.py b/test/test_Utils.py index d692ba5a..37312675 100644 --- a/test/test_Utils.py +++ b/test/test_Utils.py @@ -11,17 +11,17 @@ 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"}}]') - + + utils.status = MagicMock( + return_value='[{"GetStatus": {"version": "1.0", "status": "OK", "info": "test"}}]') + single_dict_schema = { "entities": { "returned": 1, @@ -44,7 +44,7 @@ def test_summary_supported_schema_shapes(self): } } } - + list_schema = { "entities": { "returned": 1, @@ -67,7 +67,7 @@ def test_summary_supported_schema_shapes(self): } } } - + nested_dict_schema = { "entities": { "returned": 1, @@ -94,7 +94,7 @@ def test_summary_supported_schema_shapes(self): } } } - + for schema in [single_dict_schema, list_schema, nested_dict_schema]: utils.get_schema = MagicMock(return_value=schema) # Should not raise exception