#!/usr/bin/env python3
"""
###########################################################################################################
# File: help.py
# Location: /help/help.py
#
# This module provides a comprehensive help utility for discovering and exploring Amorphic SDK APIs.
# It includes functionality to list available APIs, show detailed API information, describe specific
# methods with parameters, and search for APIs and methods by keywords. The utility supports both
# command-line usage and programmatic access with proper logging capabilities.
#
# Modification History:
# ===================================================================
# Date Who Description
# ========== ================= ==============================
#
# Jun 2025 Subir Adhikari Initial version of help utility
# with API discovery, method inspection,
# search functionality, and logging support
#
#
###########################################################################################################
"""
import inspect
import sys
import os
import importlib
from typing import Dict, List, Optional, Tuple
# Import API modules directly
from .api import *
[docs]
class HelpCommand:
"""
Main class that provides command-line help functionality for Amorphic SDK APIs.
Handles discovery, inspection, and documentation of available APIs and their methods.
"""
[docs]
def __init__(self):
"""Initialize the help command by discovering all available API classes."""
self.api_classes = self._get_api_classes()
def _get_api_classes(self) -> Dict[str, type]:
"""
Dynamically discover and load all API classes from the api directory.
Returns:
Dict[str, type]: Dictionary mapping API class names to their class objects
"""
api_classes = {}
# Get the path to the API module (current directory + api)
current_dir = os.path.dirname(os.path.abspath(__file__))
api_module_path = os.path.join(current_dir, 'api')
# Iterate through all Python files in the API directory
for filename in os.listdir(api_module_path):
if filename.endswith('.py') and filename != '__init__.py':
module_name = filename[:-3] # Remove .py extension
try:
# Import the module using relative import to avoid path issues
module = importlib.import_module(f'.api.{module_name}', package='openapi_client')
# Find all classes that end with 'Api' (following naming convention)
for name, obj in inspect.getmembers(module, inspect.isclass):
# Filter to only include API classes from this specific module
if name.endswith('Api') and hasattr(obj, '__module__') and f'openapi_client.api.{module_name}' in obj.__module__:
api_classes[name] = obj
except ImportError as e:
# Log import errors but continue processing other modules
print(f"Warning: Could not import api.{module_name}: {e}")
return api_classes
def _get_methods_with_docs(self, class_name: str) -> List[Tuple[str, str]]:
"""
Extract all public methods and their documentation for a given API class.
Args:
class_name (str): Name of the API class to inspect
Returns:
List[Tuple[str, str]]: List of tuples containing (method_name, description)
"""
api_class = self.api_classes[class_name]
methods = []
# Inspect all functions in the API class
for name, method in inspect.getmembers(api_class, predicate=inspect.isfunction):
# Filter out private methods and OpenAPI client utility methods
if (name.startswith('_') or
name.endswith('_with_http_info') or
name.endswith('_without_preload_content')):
continue
# Extract and clean up the method documentation
doc = inspect.getdoc(method) or "No description available"
# Extract only the main description (before parameter documentation)
# Split by double newline to separate description from parameters
doc_parts = doc.split('\n\n')
main_description = doc_parts[0]
# Normalize whitespace and remove extra newlines for clean display
main_description = ' '.join(main_description.split())
methods.append((name, main_description))
return methods
def _parse_method_details(self, api_class, method_name: str) -> Dict:
"""
Parse detailed information about a specific method including parameters and types.
This method combines information from both the method signature and docstring
to provide comprehensive parameter details.
Args:
api_class: The API class containing the method
method_name (str): Name of the method to analyze
Returns:
Dict: Dictionary containing method description, parameters, and signature
"""
method = getattr(api_class, method_name, None)
if not method:
return {}
# Extract method signature for parameter type information
try:
sig = inspect.signature(method)
except (ValueError, TypeError):
# Some methods may not have inspectable signatures
sig = None
# Get the method's docstring for documentation
doc = inspect.getdoc(method) or "No description available"
# Parse docstring to extract parameter information using Sphinx/Google style
doc_lines = doc.split('\n')
description = ""
parameters = {}
current_param = None
in_params_section = False
for line in doc_lines:
line = line.strip()
if not line:
continue
# Extract main method description (before parameter documentation)
if not in_params_section and ':param' not in line and ':type' not in line and ':return' not in line:
if description:
description += " "
description += line
# Parse parameter documentation in Sphinx format (:param name: description)
if line.startswith(':param '):
in_params_section = True
parts = line.split(':', 3)
if len(parts) >= 3:
param_name = parts[1].replace('param ', '').strip()
param_desc = parts[2].strip() if len(parts) > 2 else ""
parameters[param_name] = {'description': param_desc, 'type': 'Unknown'}
current_param = param_name
# Parse type information (:type name: type_info)
elif line.startswith(':type ') and current_param:
parts = line.split(':', 2)
if len(parts) >= 2:
type_info = parts[1].replace('type ' + current_param, '').strip()
if current_param in parameters:
parameters[current_param]['type'] = type_info
# Stop parsing parameters when we hit return documentation
elif line.startswith(':return'):
in_params_section = False
# Extract parameter information from method signature
signature_params = {}
if sig:
for param_name, param in sig.parameters.items():
if param_name == 'self':
continue # Skip 'self' parameter for instance methods
param_info = {
'type': str(param.annotation) if param.annotation != param.empty else 'Unknown',
'required': param.default == param.empty, # No default = required
'default': param.default if param.default != param.empty else None
}
signature_params[param_name] = param_info
# Merge docstring and signature information for comprehensive parameter details
combined_params = {}
for param_name in set(list(parameters.keys()) + list(signature_params.keys())):
combined_params[param_name] = {
'description': parameters.get(param_name, {}).get('description', 'No description'),
'type': signature_params.get(param_name, {}).get('type', parameters.get(param_name, {}).get('type', 'Unknown')),
'required': signature_params.get(param_name, {}).get('required', True),
'default': signature_params.get(param_name, {}).get('default')
}
return {
'description': description.strip(),
'parameters': combined_params,
'signature': str(sig) if sig else 'Not available'
}
[docs]
def list_apis(self):
"""
Display a summary list of all available APIs with method counts.
This provides a high-level overview of what APIs are available in the SDK.
"""
print("\nAvailable APIs:")
print("=" * 30)
# Display APIs in alphabetical order for consistent output
for api_name in sorted(self.api_classes.keys()):
methods_count = len(self._get_methods_with_docs(api_name))
print(f"- {api_name} ({methods_count} methods)")
[docs]
def show_api_details(self, api_name: str):
"""
Display detailed information about a specific API including all its methods.
Args:
api_name (str): Name of the API class to display details for
"""
# Validate that the requested API exists
if api_name not in self.api_classes:
print(f"Error: API '{api_name}' not found.")
print("Available APIs:")
for name in sorted(self.api_classes.keys()):
print(f" - {name}")
return
# Get all methods for this API and display detailed information
methods = self._get_methods_with_docs(api_name)
print(f"\nAPI: {api_name}")
print("=" * 30)
print(f"Total methods: {len(methods)}")
print("\nAvailable Methods:")
# Display each method with its description
for method_name, doc in sorted(methods):
print(f"\n- {method_name}")
print(f" Description: {doc}")
[docs]
def describe_api_method(self, api_name: str, method_name: str):
"""
Display comprehensive information about a specific API method.
Shows method signature, parameters, types, default values, and documentation.
Args:
api_name (str): Name of the API class containing the method
method_name (str): Name of the specific method to describe
"""
# Validate API exists
if api_name not in self.api_classes:
print(f"Error: API '{api_name}' not found.")
print("Available APIs:")
for name in sorted(self.api_classes.keys()):
print(f" - {name}")
return
api_class = self.api_classes[api_name]
# Validate method exists in the API class
if not hasattr(api_class, method_name):
print(f"Error: Method '{method_name}' not found in {api_name}.")
methods = self._get_methods_with_docs(api_name)
print("Available methods:")
for name, _ in sorted(methods):
print(f" - {name}")
return
# Parse detailed method information
details = self._parse_method_details(api_class, method_name)
# Display method header information
print(f"\nAPI: {api_name}")
print(f"Method: {method_name}")
print("=" * 50)
# Display method description
print(f"\nDescription:")
print(f" {details.get('description', 'No description available')}")
# Display method signature
print(f"\nSignature:")
print(f" {details.get('signature', 'Not available')}")
# Display detailed parameter information
parameters = details.get('parameters', {})
if parameters:
print(f"\nParameters:")
for param_name, param_info in parameters.items():
# Format parameter requirement and default value information
required_text = "(required)" if param_info.get('required', True) else "(optional)"
default_text = f" [default: {param_info.get('default')}]" if param_info.get('default') is not None else ""
# Display parameter details with proper formatting
print(f"\n - {param_name} {required_text}{default_text}")
print(f" Type: {param_info.get('type', 'Unknown')}")
print(f" Description: {param_info.get('description', 'No description')}")
else:
print(f"\nParameters: None")
[docs]
def search_apis(self, query: str):
"""
Search for APIs and methods containing the query string (case-insensitive).
Searches through API names, method names, and method descriptions to find
relevant functionality based on the user's query.
Args:
query (str): Search term to look for in API/method names and descriptions
"""
query = query.lower() # Convert to lowercase for case-insensitive search
print(f"\nSearch results for '{query}':")
print("=" * 30)
found = False
# Search through all discovered API classes
for api_name, api_class in self.api_classes.items():
methods = self._get_methods_with_docs(api_name)
# Find methods that match the search query in name or description
matching_methods = [
(name, doc) for name, doc in methods
if query in name.lower() or query in doc.lower()
]
# Display results if API name matches or if any methods match
if query in api_name.lower() or matching_methods:
found = True
print(f"\nAPI: {api_name}")
# Display matching methods with their descriptions
if matching_methods:
print("Matching Methods:")
for method_name, doc in sorted(matching_methods):
print(f"\n - {method_name}")
print(f" Description: {doc}")
# Show message if no results found
if not found:
print("No matching APIs or methods found.")
[docs]
def main():
"""
Main entry point for the help utility command-line interface.
Parses command-line arguments and routes to appropriate help functions.
Supports commands: list, api, describe, search
Error Handling:
- No arguments: Shows usage instructions
- Invalid command: Shows "Invalid command or missing arguments" + usage hint
- Missing required args: Shows "Invalid command or missing arguments" + usage hint
- Wrong API name: Shows "Error: API 'name' not found" + available APIs list
- Wrong method name: Shows "Error: Method 'name' not found" + available methods list
"""
# Initialize the help command system
help_cmd = HelpCommand()
# Check if sufficient arguments provided
if len(sys.argv) < 2:
# Display usage instructions when no command is provided
print("Usage:")
print(" python help list # List all APIs")
print(" python help api <api_name> # Show details for an API")
print(" python help describe <api_name> <method_name> # Show detailed method info")
print(" python help search <query> # Search for APIs and methods")
return
# Parse the command (case-insensitive)
command = sys.argv[1].lower()
# Route to appropriate handler based on command
if command == "list":
# Display all available APIs
help_cmd.list_apis()
elif command == "api" and len(sys.argv) > 2:
# Show detailed information about a specific API
help_cmd.show_api_details(sys.argv[2])
elif command == "describe" and len(sys.argv) > 3:
# Describe a specific method in detail
help_cmd.describe_api_method(sys.argv[2], sys.argv[3])
elif command == "search" and len(sys.argv) > 2:
# Search for APIs and methods by keyword
help_cmd.search_apis(sys.argv[2])
else:
# Handle invalid commands or missing required arguments
print("Invalid command or missing arguments.")
print("Use 'python help.py' to see usage instructions.")
# Execute main function when script is run directly (not imported)
if __name__ == "__main__":
main()