class ManualRelationshipManager
A class that manages manually defined database relationships with persistent JSON storage, allowing users to add, retrieve, update, and remove relationship definitions between database tables.
/tf/active/vicechatdev/full_smartstat/manual_relationships.py
16 - 153
moderate
Purpose
This class provides a complete management system for manually defined database relationships. It handles persistent storage of relationships in JSON format, supports CRUD operations (create, read, update, delete), and provides querying capabilities to retrieve relationships by table or specific column pairs. The class is designed for scenarios where database relationships need to be explicitly defined and maintained outside of the database schema itself, such as for documentation, data lineage tracking, or relationship discovery systems.
Source Code
class ManualRelationshipManager:
"""Manages manually defined database relationships with persistent storage."""
def __init__(self, storage_file: str = "manual_relationships.json"):
"""Initialize the manager with a storage file path."""
self.storage_file = storage_file
self.relationships = self._load_relationships()
def _load_relationships(self) -> List[Dict[str, Any]]:
"""Load relationships from the storage file."""
if not os.path.exists(self.storage_file):
return []
try:
with open(self.storage_file, 'r') as f:
data = json.load(f)
return data.get('relationships', [])
except (json.JSONDecodeError, FileNotFoundError) as e:
logger.warning(f"Could not load manual relationships: {e}")
return []
def _save_relationships(self):
"""Save relationships to the storage file."""
data = {
'relationships': self.relationships,
'last_updated': datetime.now().isoformat(),
'version': '1.0'
}
try:
with open(self.storage_file, 'w') as f:
json.dump(data, f, indent=2)
logger.info(f"Saved {len(self.relationships)} manual relationships to {self.storage_file}")
except Exception as e:
logger.error(f"Could not save manual relationships: {e}")
def add_relationship(self,
from_table: str,
from_column: str,
to_table: str,
to_column: str,
confidence: float = 1.0,
description: str = None,
created_by: str = "manual") -> bool:
"""Add a manual relationship."""
# Check if relationship already exists
existing = self.get_relationship(from_table, from_column, to_table, to_column)
if existing:
logger.info(f"Relationship {from_table}.{from_column} → {to_table}.{to_column} already exists")
return False
relationship = {
'from_table': from_table,
'from_column': from_column,
'to_table': to_table,
'to_column': to_column,
'confidence': confidence,
'detection_method': 'manual',
'created_at': datetime.now().isoformat(),
'created_by': created_by,
'description': description or f"Manual relationship: {from_table}.{from_column} → {to_table}.{to_column}"
}
self.relationships.append(relationship)
self._save_relationships()
logger.info(f"Added manual relationship: {from_table}.{from_column} → {to_table}.{to_column}")
return True
def get_relationship(self, from_table: str, from_column: str, to_table: str, to_column: str) -> Optional[Dict[str, Any]]:
"""Get a specific relationship if it exists."""
for rel in self.relationships:
if (rel['from_table'].lower() == from_table.lower() and
rel['from_column'].lower() == from_column.lower() and
rel['to_table'].lower() == to_table.lower() and
rel['to_column'].lower() == to_column.lower()):
return rel
return None
def get_all_relationships(self) -> List[Dict[str, Any]]:
"""Get all manual relationships."""
return self.relationships.copy()
def get_relationships_for_table(self, table_name: str) -> List[Dict[str, Any]]:
"""Get all relationships where the table is either the source or target."""
table_lower = table_name.lower()
return [rel for rel in self.relationships
if rel['from_table'].lower() == table_lower or rel['to_table'].lower() == table_lower]
def remove_relationship(self, from_table: str, from_column: str, to_table: str, to_column: str) -> bool:
"""Remove a manual relationship."""
original_count = len(self.relationships)
self.relationships = [rel for rel in self.relationships
if not (rel['from_table'].lower() == from_table.lower() and
rel['from_column'].lower() == from_column.lower() and
rel['to_table'].lower() == to_table.lower() and
rel['to_column'].lower() == to_column.lower())]
if len(self.relationships) < original_count:
self._save_relationships()
logger.info(f"Removed manual relationship: {from_table}.{from_column} → {to_table}.{to_column}")
return True
return False
def clear_all_relationships(self) -> int:
"""Clear all manual relationships."""
count = len(self.relationships)
self.relationships = []
self._save_relationships()
logger.info(f"Cleared {count} manual relationships")
return count
def get_statistics(self) -> Dict[str, Any]:
"""Get statistics about manual relationships."""
if not self.relationships:
return {
'total_relationships': 0,
'tables_involved': 0,
'average_confidence': 0.0
}
tables = set()
total_confidence = 0
for rel in self.relationships:
tables.add(rel['from_table'])
tables.add(rel['to_table'])
total_confidence += rel.get('confidence', 1.0)
return {
'total_relationships': len(self.relationships),
'tables_involved': len(tables),
'average_confidence': total_confidence / len(self.relationships),
'last_updated': max([rel.get('created_at', '') for rel in self.relationships], default='')
}
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
bases |
- | - |
Parameter Details
storage_file: Path to the JSON file where relationships will be persisted. Defaults to 'manual_relationships.json' in the current directory. The file will be created if it doesn't exist, and loaded on initialization if it does exist.
Return Value
Instantiation returns a ManualRelationshipManager object with loaded relationships from the storage file. Key method returns: add_relationship() returns bool (True if added, False if already exists), get_relationship() returns Optional[Dict] (relationship dict or None), get_all_relationships() returns List[Dict] of all relationships, remove_relationship() returns bool (True if removed, False if not found), clear_all_relationships() returns int (count of cleared relationships), get_statistics() returns Dict with statistics about stored relationships.
Class Interface
Methods
__init__(self, storage_file: str = 'manual_relationships.json')
Purpose: Initialize the manager with a storage file path and load existing relationships
Parameters:
storage_file: Path to JSON file for persistent storage, defaults to 'manual_relationships.json'
Returns: None (constructor)
_load_relationships(self) -> List[Dict[str, Any]]
Purpose: Private method to load relationships from the storage file
Returns: List of relationship dictionaries, empty list if file doesn't exist or on error
_save_relationships(self)
Purpose: Private method to save current relationships to the storage file with metadata
Returns: None (saves to file as side effect)
add_relationship(self, from_table: str, from_column: str, to_table: str, to_column: str, confidence: float = 1.0, description: str = None, created_by: str = 'manual') -> bool
Purpose: Add a new manual relationship between two table columns
Parameters:
from_table: Source table namefrom_column: Source column nameto_table: Target table nameto_column: Target column nameconfidence: Confidence score for the relationship (0.0 to 1.0), defaults to 1.0description: Optional description of the relationship, auto-generated if not providedcreated_by: Identifier of who created the relationship, defaults to 'manual'
Returns: True if relationship was added, False if it already exists
get_relationship(self, from_table: str, from_column: str, to_table: str, to_column: str) -> Optional[Dict[str, Any]]
Purpose: Retrieve a specific relationship by its source and target identifiers
Parameters:
from_table: Source table name (case-insensitive)from_column: Source column name (case-insensitive)to_table: Target table name (case-insensitive)to_column: Target column name (case-insensitive)
Returns: Dictionary containing relationship details if found, None otherwise
get_all_relationships(self) -> List[Dict[str, Any]]
Purpose: Retrieve all stored relationships
Returns: Copy of the list containing all relationship dictionaries
get_relationships_for_table(self, table_name: str) -> List[Dict[str, Any]]
Purpose: Get all relationships where the specified table is either source or target
Parameters:
table_name: Name of the table to search for (case-insensitive)
Returns: List of relationship dictionaries involving the specified table
remove_relationship(self, from_table: str, from_column: str, to_table: str, to_column: str) -> bool
Purpose: Remove a specific relationship from storage
Parameters:
from_table: Source table name (case-insensitive)from_column: Source column name (case-insensitive)to_table: Target table name (case-insensitive)to_column: Target column name (case-insensitive)
Returns: True if relationship was found and removed, False if not found
clear_all_relationships(self) -> int
Purpose: Remove all relationships from storage
Returns: Count of relationships that were cleared
get_statistics(self) -> Dict[str, Any]
Purpose: Calculate and return statistics about stored relationships
Returns: Dictionary with keys: total_relationships (int), tables_involved (int), average_confidence (float), last_updated (str)
Attributes
| Name | Type | Description | Scope |
|---|---|---|---|
storage_file |
str | Path to the JSON file used for persistent storage of relationships | instance |
relationships |
List[Dict[str, Any]] | In-memory list of relationship dictionaries loaded from storage file. Each dict contains: from_table, from_column, to_table, to_column, confidence, detection_method, created_at, created_by, description | instance |
Dependencies
jsonosdatetimetypinglogging
Required Imports
import json
import os
from datetime import datetime
from typing import List, Dict, Any, Optional
import logging
Usage Example
import json
import os
from datetime import datetime
from typing import List, Dict, Any, Optional
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
# Instantiate the manager
manager = ManualRelationshipManager(storage_file='my_relationships.json')
# Add a relationship
success = manager.add_relationship(
from_table='orders',
from_column='customer_id',
to_table='customers',
to_column='id',
confidence=1.0,
description='Orders to customers foreign key',
created_by='admin'
)
# Get a specific relationship
rel = manager.get_relationship('orders', 'customer_id', 'customers', 'id')
if rel:
print(f"Found relationship: {rel['description']}")
# Get all relationships for a table
table_rels = manager.get_relationships_for_table('orders')
print(f"Found {len(table_rels)} relationships for orders table")
# Get all relationships
all_rels = manager.get_all_relationships()
# Get statistics
stats = manager.get_statistics()
print(f"Total relationships: {stats['total_relationships']}")
# Remove a relationship
removed = manager.remove_relationship('orders', 'customer_id', 'customers', 'id')
# Clear all relationships
count = manager.clear_all_relationships()
Best Practices
- Always ensure a logger is configured before instantiating the class to avoid NameError
- The storage file is automatically saved after each modification (add, remove, clear), so no explicit save call is needed
- Relationship comparisons are case-insensitive for table and column names
- The class maintains an in-memory copy of relationships that is synchronized with the file system
- Use get_relationship() before add_relationship() if you need to check for duplicates explicitly
- The storage file includes metadata (last_updated, version) in addition to relationships
- All relationships have a confidence score (defaults to 1.0 for manual entries)
- The created_at timestamp is automatically set to the current time when adding relationships
- Handle file I/O exceptions gracefully - the class logs warnings/errors but continues operation
- Use get_all_relationships() with .copy() to avoid modifying the internal state accidentally
- The class is not thread-safe - use external locking if accessing from multiple threads
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function get_manual_relationship_manager 63.9% similar
-
function add_manual_relationship 63.4% similar
-
class Neo4jManager 58.7% similar
-
class DatabaseSchema_v1 53.3% similar
-
class RelTypes 53.2% similar