Skip to content

Commit ecc3c7e

Browse files
authored
Sdg 785/refactor 2 (#37)
* feat(docgen): refactor documentation generation commands and remove legacy file * SDG-785: refactor(commands): optimize commit handling with lazy imports ## Related JIRA Issues * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * SDG-785: refactor(ui): enhance logging with print_info and print_warning * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * SDG-785: Update code ## Related JIRA Issues * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * refactor(llm_client): configure litellm API settings directly for SDG-785 optimization ## Related JIRA Issues * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * SDG-785: refactor(llm_client): clean up initialization and enhance response logging This update refactors the `LLMClient` class in `llm_client.py` by removing unnecessary print statements during initialization. The API key and base URL are still set up correctly, but the initialization logs have been cleaned up for better readability. Additionally, a new print statement has been added to log the LLM response, which enhances debugging and provides better visibility into the responses received from the LLM service. ## Related JIRA Issues * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * SDG-785: refactor(llm-client): initialize model for lazy loading optimization ## Related JIRA Issues * **[SDG-785](https://singularity-x.atlassian.net/browse/SDG-785)**: Optimize penifycli library by using lazy lib * Status: To Do * Type: Story * refactor(llm_client): remove redundant logging of LLM response * refactor(commit_analyzer): optimize JIRA integration and message handling * refactor(commit_analyzer, jira_client): improve logging for JIRA context retrieval * refactor(config): centralize config path handling and update gitignore * refactor(commit_analyzer): add logging for LLM commit summary fetching
1 parent 1faa083 commit ecc3c7e

10 files changed

Lines changed: 206 additions & 163 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@
33
__pycache__/
44
*.pyc
55
*.egg-info/
6-
build/
6+
build/
7+
.penify/
8+
.penify/*
Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
11
import os
22
import sys
3+
import argparse
34

4-
from penify_hook.ui_utils import print_error
5-
from penify_hook.utils import recursive_search_git_folder
6-
from ..commit_analyzer import CommitDocGenHook
7-
from ..api_client import APIClient
5+
from penify_hook.ui_utils import print_info, print_warning
86

9-
# Try importing optional dependencies
10-
try:
11-
from ..llm_client import LLMClient
12-
except ImportError:
13-
LLMClient = None
14-
15-
try:
16-
from ..jira_client import JiraClient
17-
except ImportError:
18-
JiraClient = None
197

208
def commit_code(api_url, token, message, open_terminal, generate_description,
219
llm_model=None, llm_api_base=None, llm_api_key=None,
2210
jira_url=None, jira_user=None, jira_api_token=None):
2311
"""
2412
Enhance Git commits with AI-powered commit messages.
2513
"""
14+
15+
from penify_hook.ui_utils import print_error
16+
from penify_hook.utils import recursive_search_git_folder
17+
from ..commit_analyzer import CommitDocGenHook
18+
from ..api_client import APIClient
19+
20+
# Try importing optional dependencies
21+
try:
22+
from ..llm_client import LLMClient
23+
except ImportError:
24+
LLMClient = None
25+
26+
try:
27+
from ..jira_client import JiraClient
28+
except ImportError:
29+
JiraClient = None
2630
# Create API client
2731
api_client = APIClient(api_url, token)
2832

@@ -35,10 +39,10 @@ def commit_code(api_url, token, message, open_terminal, generate_description,
3539
api_base=llm_api_base,
3640
api_key=llm_api_key
3741
)
38-
print(f"Using LLM model: {llm_model}")
42+
print_info(f"Using LLM model: {llm_model}")
3943
except Exception as e:
40-
print(f"Error initializing LLM client: {e}")
41-
print("Falling back to API for commit summary generation")
44+
print_error(f"Error initializing LLM client: {e}")
45+
print_error("Falling back to API for commit summary generation")
4246
else:
4347
if not token:
4448
print_error("No LLM model or API token provided. Please provide an LLM model or API token.")
@@ -53,12 +57,12 @@ def commit_code(api_url, token, message, open_terminal, generate_description,
5357
jira_api_token=jira_api_token
5458
)
5559
if jira_client.is_connected():
56-
print(f"Connected to JIRA: {jira_url}")
60+
print_info(f"Connected to JIRA: {jira_url}")
5761
else:
58-
print(f"Failed to connect to JIRA: {jira_url}")
62+
print_warning(f"Failed to connect to JIRA: {jira_url}")
5963
jira_client = None
6064
except Exception as e:
61-
print(f"Error initializing JIRA client: {e}")
65+
print_warning(f"Error initializing JIRA client: {e}")
6266
jira_client = None
6367

6468
try:
@@ -69,3 +73,52 @@ def commit_code(api_url, token, message, open_terminal, generate_description,
6973
except Exception as e:
7074
print(f"Error: {e}")
7175
sys.exit(1)
76+
77+
78+
79+
80+
81+
def setup_commit_parser(parser):
82+
commit_parser_description = """
83+
It generates smart commit messages. By default, it will just generate just the Title of the commit message.
84+
1. If you have not configured LLM, it will give an error. You either need to configure LLM or use the API key.
85+
2. If you have not configured JIRA. It will not enhance the commit message with JIRA issue details.
86+
3. For more information, visit https://docs.penify.dev/
87+
"""
88+
parser.help = "Generate smart commit messages using local-LLM(no login required)."
89+
parser.description = commit_parser_description
90+
parser.formatter_class = argparse.RawDescriptionHelpFormatter
91+
92+
# Add the message argument with better help
93+
parser.add_argument("-m", "--message", required=False, help="Commit with contextual commit message.", default="N/A")
94+
parser.add_argument("-e", "--terminal", action="store_true", help="Open edit terminal before committing.")
95+
parser.add_argument("-d", "--description", action="store_false", help="It will generate commit message with title and description.", default=False)
96+
97+
def handle_commit(args):
98+
from penify_hook.commands.commit_commands import commit_code
99+
from penify_hook.commands.config_commands import get_jira_config, get_llm_config, get_token
100+
from penify_hook.constants import API_URL
101+
102+
# Only import dependencies needed for commit functionality here
103+
open_terminal = args.terminal
104+
generate_description = args.description
105+
print_info(f"Generate Commit Description: {generate_description}")
106+
# Try to get from config
107+
llm_config = get_llm_config()
108+
llm_model = llm_config.get('model')
109+
llm_api_base = llm_config.get('api_base')
110+
llm_api_key = llm_config.get('api_key')
111+
token = get_token()
112+
113+
114+
115+
# Try to get from config
116+
jira_config = get_jira_config()
117+
jira_url = jira_config.get('url')
118+
jira_user = jira_config.get('username')
119+
jira_api_token = jira_config.get('api_token')
120+
121+
122+
commit_code(API_URL, token, args.message, open_terminal, generate_description,
123+
llm_model, llm_api_base, llm_api_key,
124+
jira_url, jira_user, jira_api_token)

penify_hook/commands/config_commands.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import os
23
import random
34
import webbrowser
45
import http.server
@@ -8,12 +9,46 @@
89
from threading import Thread
910
import logging
1011

12+
13+
def get_penify_config() -> Path:
14+
"""
15+
Get the home directory for the .penify configuration file.
16+
This function searches for the .penify file in the current directory
17+
and its parent directories until it finds it or reaches the home directory.
18+
"""
19+
from penify_hook.utils import recursive_search_git_folder
20+
21+
current_dir = os.getcwd()
22+
home_dir = recursive_search_git_folder(current_dir)
23+
24+
25+
if not home_dir:
26+
home_dir = Path.home()
27+
else:
28+
home_dir = Path(home_dir)
29+
30+
penify_dir = home_dir / '.penify'
31+
if penify_dir.exists():
32+
return penify_dir / 'config.json'
33+
else:
34+
# Create the .penify directory if it doesn't exist
35+
os.makedirs(penify_dir, exist_ok=True)
36+
## update gitignore
37+
38+
# Create the .penify directory
39+
os.makedirs(penify_dir, exist_ok=True)
40+
# Create an empty config.json file
41+
with open(penify_dir / 'config.json', 'w') as f:
42+
json.dump({}, f)
43+
return penify_dir / 'config.json'
44+
45+
1146
def save_llm_config(model, api_base, api_key):
1247
"""
1348
Save LLM configuration settings in the .penify file.
1449
"""
15-
home_dir = Path.home()
16-
penify_file = home_dir / '.penify'
50+
51+
penify_file = get_penify_config()
1752

1853
config = {}
1954
if penify_file.exists():
@@ -43,6 +78,8 @@ def save_jira_config(url, username, api_token):
4378
"""
4479
Save JIRA configuration settings in the .penify file.
4580
"""
81+
from penify_hook.utils import recursive_search_git_folder
82+
4683
home_dir = Path.home()
4784
penify_file = home_dir / '.penify'
4885

@@ -74,7 +111,7 @@ def get_llm_config():
74111
"""
75112
Get LLM configuration from the .penify file.
76113
"""
77-
config_file = Path.home() / '.penify'
114+
config_file = get_penify_config()
78115
if config_file.exists():
79116
try:
80117
with open(config_file, 'r') as f:
@@ -89,7 +126,7 @@ def get_jira_config():
89126
"""
90127
Get JIRA configuration from the .penify file.
91128
"""
92-
config_file = Path.home() / '.penify'
129+
config_file = get_penify_config()
93130
if config_file.exists():
94131
try:
95132
with open(config_file, 'r') as f:

penify_hook/commands/doc_commands.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
2+
import logging
13
import os
2-
import sys
3-
from ..folder_analyzer import FolderAnalyzerGenHook
4-
from ..file_analyzer import FileAnalyzerGenHook
5-
from ..git_analyzer import GitDocGenHook
6-
from ..api_client import APIClient
74

85
def generate_doc(api_url, token, location=None):
6+
import os
7+
import sys
8+
from ..folder_analyzer import FolderAnalyzerGenHook
9+
from ..file_analyzer import FileAnalyzerGenHook
10+
from ..git_analyzer import GitDocGenHook
11+
from ..api_client import APIClient
912
"""Generates documentation based on the given parameters.
1013
1114
This function initializes an API client using the provided API URL and
@@ -46,4 +49,56 @@ def generate_doc(api_url, token, location=None):
4649
analyzer.run()
4750
except Exception as e:
4851
print(f"Error: {e}")
49-
sys.exit(1)
52+
sys.exit(1)
53+
54+
55+
# Define the docgen description text
56+
docgen_description = """Generate code documentation using Penify.
57+
58+
This command requires you to be logged in to your Penify account.
59+
You can generate documentation for:
60+
- Current Git diff (default)
61+
- Specific file
62+
- Specific folder
63+
"""
64+
65+
def setup_docgen_parser(parser):
66+
# We don't need to create a new docgen_parser since it's passed as a parameter
67+
docgen_subparsers = parser.add_subparsers(title="docgen_subcommand", dest="docgen_subcommand")
68+
69+
# Docgen main options (for direct documentation generation)
70+
parser.add_argument("-l", "--location", help="[Optional] Path to the folder or file to Generate Documentation. By default it will pick the root directory.", default=None)
71+
72+
# Subcommand: install-hook (as part of docgen)
73+
install_hook_parser = docgen_subparsers.add_parser("install-hook", help="Install the Git post-commit hook.")
74+
install_hook_parser.add_argument("-l", "--location", required=False,
75+
help="Location in which to install the Git hook. Defaults to current directory.",
76+
default=os.getcwd())
77+
78+
# Subcommand: uninstall-hook (as part of docgen)
79+
uninstall_hook_parser = docgen_subparsers.add_parser("uninstall-hook", help="Uninstall the Git post-commit hook.")
80+
uninstall_hook_parser.add_argument("-l", "--location", required=False,
81+
help="Location from which to uninstall the Git hook. Defaults to current directory.",
82+
default=os.getcwd())
83+
84+
def handle_docgen(args):
85+
# Only import dependencies needed for docgen functionality here
86+
from penify_hook.commands.config_commands import get_token
87+
import sys
88+
from penify_hook.commands.doc_commands import generate_doc
89+
from penify_hook.commands.hook_commands import install_git_hook, uninstall_git_hook
90+
from penify_hook.constants import API_URL
91+
92+
token = get_token()
93+
if not token:
94+
logging.error("Error: Unable to authenticate. Please run 'penifycli login'.")
95+
sys.exit(1)
96+
97+
if args.docgen_subcommand == "install-hook":
98+
install_git_hook(args.location, token)
99+
100+
elif args.docgen_subcommand == "uninstall-hook":
101+
uninstall_git_hook(args.location)
102+
103+
else: # Direct documentation generation
104+
generate_doc(API_URL, token, args.location)

penify_hook/commit_analyzer.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77
from tqdm import tqdm
88

99
from penify_hook.base_analyzer import BaseAnalyzer
10+
from penify_hook.jira_client import JiraClient
11+
from penify_hook.ui_utils import print_info, print_success, print_warning
1012
from .api_client import APIClient
1113

1214
class CommitDocGenHook(BaseAnalyzer):
1315
def __init__(self, repo_path: str, api_client: APIClient, llm_client=None, jira_client=None):
1416
super().__init__(repo_path, api_client)
1517

1618
self.llm_client = llm_client # Add LLM client as an optional parameter
17-
self.jira_client = jira_client # Add JIRA client as an optional parameter
19+
self.jira_client: JiraClient = jira_client # Add JIRA client as an optional parameter
1820

1921
def get_summary(self, instruction: str, generate_description: bool) -> dict:
2022
"""Generate a summary for the commit based on the staged changes.
@@ -50,11 +52,11 @@ def get_summary(self, instruction: str, generate_description: bool) -> dict:
5052
# If issues found in branch, get context
5153
if issue_keys:
5254
jira_context = self.jira_client.get_commit_context_from_issues(issue_keys)
53-
print(f"Adding JIRA context from issues: {', '.join(issue_keys)}")
5455
except Exception as e:
5556
print(f"Could not get JIRA context: {e}")
5657

5758
# Use LLM client if provided, otherwise use API client
59+
print_info("Fetching commit summary from LLM...")
5860
if self.llm_client:
5961
return self.api_client.generate_commit_summary_with_llm(
6062
diff, instruction, generate_description, self.repo_details, self.llm_client, jira_context
@@ -81,7 +83,7 @@ def run(self, msg: Optional[str], edit_commit_message: bool, generate_descriptio
8183
Raises:
8284
Exception: If there is an error generating the commit summary.
8385
"""
84-
summary: dict = self.get_summary(msg, generate_description)
86+
summary: dict = self.get_summary(msg, True)
8587
if not summary:
8688
raise Exception("Error generating commit summary")
8789

@@ -91,15 +93,16 @@ def run(self, msg: Optional[str], edit_commit_message: bool, generate_descriptio
9193
# If JIRA client is available, integrate JIRA information
9294
if self.jira_client and self.jira_client.is_connected():
9395
# Add JIRA information to commit message
94-
title, description = self.process_jira_integration(title, description, msg)
96+
self.process_jira_integration(title, description, msg)
9597

9698
# commit the changes to the repository with above details
97-
commit_msg = f"{title}\n\n{description}"
99+
commit_msg = f"{title}\n\n{description}" if generate_description else title
98100
self.repo.git.commit('-m', commit_msg)
101+
print_success(f"Commit: {commit_msg}")
99102

100103
if edit_commit_message:
101104
# Open the git commit edit terminal
102-
print("Opening git commit edit terminal...")
105+
print_info("Opening git commit edit terminal...")
103106
self._amend_commit()
104107

105108
def process_jira_integration(self, title: str, description: str, msg: str) -> tuple:
@@ -129,29 +132,25 @@ def process_jira_integration(self, title: str, description: str, msg: str) -> tu
129132
for key in branch_issue_keys:
130133
if key not in issue_keys:
131134
issue_keys.append(key)
132-
print(f"Added JIRA issue {key} from branch name: {current_branch}")
135+
print_info(f"Added JIRA issue {key} from branch name: {current_branch}")
133136
except Exception as e:
134-
print(f"Could not extract JIRA issues from branch name: {e}")
137+
print_warning(f"Could not extract JIRA issues from branch name: {e}")
135138

136139
if issue_keys:
137-
print(f"Found JIRA issues: {', '.join(issue_keys)}")
140+
print_info(f"Found JIRA issues: {', '.join(issue_keys)}")
138141

139142
# Format commit message with JIRA info
140-
title, description = self.jira_client.format_commit_message_with_jira_info(
141-
title, description, issue_keys
142-
)
143143

144144
# Add comments to JIRA issues
145145
for issue_key in issue_keys:
146146
comment = (
147147
f"Commit related to this issue:\n\n"
148148
f"**{title}**\n\n"
149149
f"{description}\n\n"
150-
f"Repository: {self.repo_details.get('organization_name')}/{self.repo_details.get('repo_name')}"
151150
)
152151
self.jira_client.add_comment(issue_key, comment)
153152
else:
154-
print("No JIRA issues found in commit message or branch name")
153+
print_warning("No JIRA issues found in commit message or branch name")
155154

156155
return title, description
157156

0 commit comments

Comments
 (0)