Source code for openapi_client.help

#!/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()