tawada / grass-grower

0 stars 0 forks source link

Add centralized error handling mechanism in `services/github/__init__.py` module to improve consistency and maintainability. #83

Open tawada opened 1 month ago

tawada commented 1 month ago

The existing codebase does not have a centralized error handling mechanism for different exceptions raised across the modules. This issue is particularly noticeable in the services/github/__init__.py module, where exceptions are raised directly within functions like clone_repository and setup_repository. Adding a centralized error handling mechanism would ensure that all exceptions are consistently logged and handled, improving the robustness and maintainability of the codebase.

tawada commented 1 month ago

Please be specific, showing the code before the change and the code after the proposed change

tawada commented 1 month ago

Sure, I'll add centralized error handling to the services/github/__init__.py module. This will involve wrapping the core operations in a try-except block and consistently logging and/or handling the exceptions.

Before the Change:

Here are some selected pieces of the existing code which include exception handling:

clone_repository Function:

def clone_repository(repo: str) -> bool:
    """Clone the repository."""
    github_utils.make_owner_dir(DEFAULT_PATH, repo)
    try:
        return github_utils.exec_git_command_and_response_bool(
            repo[:repo.index("/")],
            ["git", "clone", "git@github.com:" + repo],
            True,
        )
    except exceptions.GitHubRepoNotFoundException as err:
        raise exceptions.GitHubRepoNotFoundException(
            f"Invalid repository: {repo}") from err

setup_repository Function:

def setup_repository(repo: str, branch_name: str = "main"):
    """Set up the repository to point to a specific branch."""
    if not github_utils.exists_repo(DEFAULT_PATH, repo):
        clone_repository(repo)
    else:
        branch = get_branch(repo)
        default_branch = get_default_branch(repo)
        if branch != default_branch:
            checkout_branch(repo, default_branch)
        pull_repository(repo)
    checkout_branch(repo, branch_name)

After the Change:

We'll introduce a centralized error handling scheme using a decorator or a context manager to wrap the necessary operations. For simplicity, I'll use a decorator here.

Define the Centralized Error Handling Decorator:

import functools
from utils.logging_utils import log
from services.github import exceptions

def handle_exceptions(func):
    """Decorator to handle exceptions and log them consistently."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except exceptions.GitHubRepoNotFoundException as err:
            log(f"GitHub repository not found: {err}", level="error")
            raise
        except exceptions.GitException as err:
            log(f"Git error: {err}", level="error")
            raise
        except subprocess.CalledProcessError as err:
            exception, _ = exceptions.parse_exception(err)
            log(f"Command execution error: {exception.message}", level="error")
            raise exception from err
        except Exception as err:
            log(f"Unexpected error: {err}", level="error")
            raise exceptions.CommandExecutionException(str(err)) from err
    return wrapper

Updated Functions with Decorator:

@handle_exceptions
def clone_repository(repo: str) -> bool:
    """Clone the repository."""
    github_utils.make_owner_dir(DEFAULT_PATH, repo)
    return github_utils.exec_git_command_and_response_bool(
        repo[:repo.index("/")],
        ["git", "clone", "git@github.com:" + repo],
        True,
    )

@handle_exceptions
def pull_repository(repo: str) -> bool:
    """Pull the repository."""
    return github_utils.exec_git_command_and_response_bool(
        repo,
        ["git", "pull"],
        True,
    )

@handle_exceptions
def setup_repository(repo: str, branch_name: str = "main"):
    """Set up the repository to point to a specific branch."""
    if not github_utils.exists_repo(DEFAULT_PATH, repo):
        clone_repository(repo)
    else:
        branch = get_branch(repo)
        default_branch = get_default_branch(repo)
        if branch != default_branch:
            checkout_branch(repo, default_branch)
        pull_repository(repo)
    checkout_branch(repo, branch_name)

@handle_exceptions
def create_issue(repo: str, title: str, body: str) -> bool:
    """Create a new issue on GitHub."""
    return github_utils.exec_git_command_and_response_bool(
        repo,
        ["gh", "issue", "create", "-t", title, "-b", body],
    )

By using the handle_exceptions decorator, the exceptions are handled centrally, which logs the messages consistently and raises them as needed. This approach enhances the maintainability and robustness of the code.