Skip to content
This repository was archived by the owner on Jul 21, 2025. It is now read-only.

Commit d4f4f5c

Browse files
✨ Refactor: Clean up CI/CD workflows and pre-commit hooks (#5)
## 💌 Description - Remove unused release draft workflow - Update main release workflow - Update contributing guidelines - Improve README formatting - Simplify and optimize pre-commit hook script - Minor updates to project configuration These changes aim to improve the project's development workflow by removing unnecessary files, updating existing workflows, and optimizing pre-commit hooks. The README file has also been updated to improve readability and provide more information about the project. ## 🩹 Related issue <!-- If your PR refers to a related issue, link it here. --> Fixes: # ## 🏗️ Type of change <!-- Mark with an `x` all the checkboxes that apply (like `[x]`) --> - [ ] 📚 Examples / docs / tutorials / dependencies update - [ ] 🐛 Bug fix (non-breaking change which fixes an issue) - [ ] 🥂 Improvement (non-breaking change which improves an existing feature) - [ ] 🚀 New feature (non-breaking change which adds functionality) - [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) - [ ] 🔐 Security fix - [ ] ⬆️ Dependencies update ## ✅ Checklist <!-- Mark with an `x` all the checkboxes that apply (like `[x]`) --> - [ ] I've read the [`CODE_OF_CONDUCT.md`](https://github.com/DariuszPorowski/chatgpt-pre-commit-hooks/blob/main/CODE_OF_CONDUCT.md) document. - [ ] I've read the [`CONTRIBUTING.md`](https://github.com/DariuszPorowski/chatgpt-pre-commit-hooks/blob/main/CONTRIBUTING.md) guide. - [ ] I've written tests for all new methods and classes that I created. - [ ] I've written the docstring in Google format for all the methods and classes that I used.
1 parent 2b82764 commit d4f4f5c

7 files changed

Lines changed: 82 additions & 101 deletions

File tree

.github/workflows/workflow.ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ jobs:
173173
python-version: ${{ matrix.python-version }}
174174

175175
- name: Install dependencies
176-
run: pip install . .[dev]
176+
run: pip install . .[build]
177177

178178
- name: Test Build
179179
run: python -m build --wheel

.github/workflows/workflow.release.draft.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ on:
55
push:
66
branches:
77
- main
8-
paths:
9-
- "*.py"
10-
- "chatgpt_pre_commit_hooks/**"
11-
- ".pre-commit-config.yaml"
12-
- ".pre-commit-hooks.yaml"
13-
- "pyproject.toml"
148
workflow_dispatch:
159
# pull_request:
1610
# # Only following types are handled by the action, but one can default to all as well

.github/workflows/workflow.release.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
python-version-file: .python-version
3131

3232
- name: Install dependencies
33-
run: pip install . .[dev]
33+
run: pip install . .[build]
3434

3535
- name: Build
3636
run: python -m build --wheel
@@ -79,7 +79,6 @@ jobs:
7979
name: PyPI Release (prod)
8080
needs:
8181
- build
82-
- pypi-release-test
8382
environment:
8483
name: pypi-prod
8584
url: https://pypi.org/project/chatgpt-pre-commit-hooks

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ To contribute, please follow these steps:
3232
.venv\Scripts\Activate.ps1
3333

3434
# install project dependencies
35-
pip install . .[dev]
35+
pip install . .[dev] .[build]
3636

3737
# do test build
3838
python -m build --wheel

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Pre-commit hooks collection that utilizes ChatGPT and OpenAI platform to validat
2323
- [Arguments](#arguments)
2424
- [`--env-prefix`](#--env-prefix)
2525
- [Variables precedence](#variables-precedence)
26+
- [💸 Payments](#-payments)
2627
- [👥 Contributing](#-contributing)
2728
- [📄 License](#-license)
2829

@@ -173,7 +174,7 @@ repos:
173174
- or include it in a `requirements.txt` file in your project:
174175

175176
```text
176-
chatgpt-pre-commit-hooks~=0.1.0
177+
chatgpt-pre-commit-hooks~=0.1.1
177178
```
178179

179180
and run:
@@ -274,12 +275,21 @@ For instance, if your prefix is `personal`, then the environment variable must b
274275
275276
Example:
276277
278+
```shell
279+
export PERSONAL__OPENAI_API_KEY="sk-xxxxxx"
280+
export WORK__OPENAI_API_KEY="sk-xxxxxx"
281+
```
282+
277283
### Variables precedence
278284
279285
1. hard-coded args, e.g. `--openai-max-tokens`
280286
1. prefixed environment variable, e.g. `PERSONAL__OPENAI_MAX_TOKENS`
281287
1. global environment variable, e.g. `OPENAI_MAX_TOKENS`
282288
289+
## 💸 Payments
290+
291+
Project by default uses `gpt-3.5-turbo` model because of [its lower cost](https://openai.com/pricing). You have to pay for your own OpenAI API requests.
292+
283293
## 👥 Contributing
284294
285295
Contributions to the project are very welcome! Please follow [Contributing Guide](https://github.com/DariuszPorowski/chatgpt-pre-commit-hooks/blob/main/CONTRIBUTING.md).

chatgpt_pre_commit_hooks/chatgpt_commit_message.py

Lines changed: 65 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,55 @@
55
The commit message is then used to populate the commit message automatically.
66
"""
77

8+
import argparse
89
import logging
910
import os
1011
import sys
11-
from argparse import ArgumentParser, BooleanOptionalAction, Namespace
1212
from pathlib import Path
1313
from typing import Dict, List, Optional
1414

1515
import openai
1616
import tiktoken
1717
from git.repo import Repo
1818

19-
logging.basicConfig(level=logging.NOTSET)
20-
# logging.basicConfig(level=logging.DEBUG, filename="debug.log", filemode="w") # noqa: ERA001
19+
logger = logging.getLogger(__name__)
2120

2221

23-
def get_args() -> Namespace:
22+
def get_args() -> argparse.Namespace:
2423
"""Get input arguments."""
25-
logging.debug(f"SYS_ARGV: {sys.argv}")
26-
parser = ArgumentParser()
24+
parser = argparse.ArgumentParser()
2725
parser.add_argument("commit_msg_filename", nargs="?", default=None) # args.commit_msg_filename
2826
parser.add_argument("prepare_commit_message_source", nargs="?", default=None) # args.prepare_commit_message_source
2927
parser.add_argument("commit_object_name", nargs="?", default=None) # args.commit_object_name
3028
parser.add_argument("--max-char-count", type=int, default=10000) # args.max_char_count
31-
parser.add_argument("--emoji", action=BooleanOptionalAction, default=False) # args.emoji
32-
parser.add_argument("--description", action=BooleanOptionalAction, default=False) # args.description
29+
parser.add_argument("--emoji", action=argparse.BooleanOptionalAction, default=False) # args.emoji
30+
parser.add_argument("--description", action=argparse.BooleanOptionalAction, default=False) # args.description
31+
parser.add_argument("--log-level", choices=[key.lower() for key in logging._nameToLevel], default="warning", required=False) # args.log_level # noqa: SLF001
3332
parser.add_argument("--env-prefix", type=str, default=None, required=False) # args.env_prefix
34-
parser.add_argument("--openai-model", type=str, default=None, required=False) # args.openai_model
35-
parser.add_argument("--openai-max-tokens", type=int, default=None, required=False) # args.openai_max_tokens
36-
parser.add_argument("--openai-api-base", type=str, default=None, required=False) # args.openai_api_base
37-
parser.add_argument("--openai-api-type", type=str, default=None, required=False) # args.openai_api_type
38-
parser.add_argument("--openai-proxy", type=str, default=None, required=False) # args.openai_proxy
39-
args = parser.parse_args()
40-
logging.debug(f"ARGS: {args}")
41-
return args
33+
34+
temp_args = parser.parse_args()
35+
env_prefix = ""
36+
if temp_args.env_prefix is not None and temp_args.env_prefix:
37+
env_prefix = f"{temp_args.env_prefix.upper()}__"
38+
39+
parser.add_argument("--openai-model", type=str, default=os.environ.get(f"{env_prefix}OPENAI_MODEL", "gpt-3.5-turbo")) # args.openai_model
40+
parser.add_argument("--openai-max-tokens", type=int, default=os.environ.get(f"{env_prefix}OPENAI_MAX_TOKENS", "1024")) # args.openai_max_tokens
41+
parser.add_argument("--openai-api-base", type=str, default=os.environ.get(f"{env_prefix}OPENAI_API_BASE", openai.api_base)) # args.openai_api_base
42+
parser.add_argument("--openai-api-type", type=str, default=os.environ.get(f"{env_prefix}OPENAI_API_TYPE", openai.api_type)) # args.openai_api_type
43+
parser.add_argument("--openai-proxy", type=str, default=os.environ.get(f"{env_prefix}OPENAI_PROXY", None), required=False) # args.openai_proxy
44+
parser.add_argument("--openai-api-key", type=str, default=os.environ.get(f"{env_prefix}OPENAI_API_KEY", openai.api_key), help=argparse.SUPPRESS) # args.openai_api_key
45+
46+
openai_api_type = parser.parse_args().openai_api_type
47+
if openai_api_type == openai.api_type:
48+
parser.add_argument(
49+
"--openai-organization",
50+
type=str,
51+
default=os.environ.get(f"{env_prefix}OPENAI_ORGANIZATION", openai.organization),
52+
help=argparse.SUPPRESS,
53+
required=False,
54+
) # args.openai_organization
55+
56+
return parser.parse_args()
4257

4358

4459
def get_git_diff(max_char_count: int, git_repo_path: str) -> str:
@@ -51,13 +66,13 @@ def get_git_diff(max_char_count: int, git_repo_path: str) -> str:
5166
diff = repo.git.diff(staged=True)
5267
if len(diff) > max_char_count:
5368
diff = repo.git.diff(staged=True, stat=True)
54-
logging.debug(f"GIT_DIFF: {diff}")
69+
logger.debug(f"GIT_DIFF: {diff}")
5570
return diff
5671

5772

5873
def get_user_commit_message(commit_msg_file_path: str, prepare_commit_message_source: Optional[str]) -> Optional[str]:
5974
"""Get user commit message (if specified)."""
60-
logging.debug(f"PREPARE_COMMIT_MESSAGE_SOURCE: {prepare_commit_message_source}")
75+
logger.debug(f"PREPARE_COMMIT_MESSAGE_SOURCE: {prepare_commit_message_source}")
6176
user_commit_message = None
6277
if prepare_commit_message_source == "message" or prepare_commit_message_source is None:
6378
commit_msg_file = Path(commit_msg_file_path)
@@ -68,7 +83,7 @@ def get_user_commit_message(commit_msg_file_path: str, prepare_commit_message_so
6883
if lines != []:
6984
user_commit_message = "".join(lines).strip()
7085

71-
logging.debug(f"USER_COMMIT_MESSAGE: {user_commit_message}")
86+
logger.debug(f"USER_COMMIT_MESSAGE: {user_commit_message}")
7287

7388
if user_commit_message is not None:
7489
skip_keywords = ["#no-ai", "#no-openai", "#no-chatgpt", "#no-gpt", "#skip-ai", "#skip-openai", "#skip-chatgpt", "#skip-gpt"]
@@ -79,53 +94,7 @@ def get_user_commit_message(commit_msg_file_path: str, prepare_commit_message_so
7994
return user_commit_message
8095

8196

82-
def _get_openai_config(args: Namespace) -> Dict[str, Optional[str]]:
83-
"""Get OpenAI API Key from environment variable."""
84-
env_prefix = ""
85-
if args.env_prefix is not None:
86-
env_prefix = f"{args.env_prefix.upper()}__"
87-
88-
# https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety
89-
openai_api_key = os.environ.get(f"{env_prefix}OPENAI_API_KEY", openai.api_key) # $env:OPENAI_API_KEY
90-
if openai_api_key is None or not openai_api_key:
91-
raise ValueError(f"`{env_prefix}OPENAI_API_KEY` environment variable is not set.")
92-
93-
openai_model = os.environ.get(f"{env_prefix}OPENAI_MODEL", "gpt-3.5-turbo") # $env:OPENAI_MODEL
94-
if args.openai_model is not None:
95-
openai_model = args.openai_model
96-
97-
openai_max_tokens = os.environ.get(f"{env_prefix}OPENAI_MAX_TOKENS", "1024") # $env:OPENAI_MAX_TOKENS
98-
if args.openai_max_tokens is not None:
99-
openai_max_tokens = str(args.openai_max_tokens)
100-
101-
openai_api_type = os.environ.get(f"{env_prefix}OPENAI_API_TYPE", openai.api_type) # $env:OPENAI_API_TYPE
102-
if args.openai_api_type is not None:
103-
openai_api_type = args.openai_api_type
104-
105-
openai_organization = None
106-
if openai_api_type is not None and openai_api_type == openai.api_type:
107-
openai_organization = os.environ.get(f"{env_prefix}OPENAI_ORGANIZATION", openai.organization) # $env:OPENAI_ORGANIZATION
108-
109-
openai_api_base = os.environ.get(f"{env_prefix}OPENAI_API_BASE", openai.api_base) # $env:OPENAI_API_BASE
110-
if args.openai_api_base is not None:
111-
openai_api_base = args.openai_api_base
112-
113-
openai_proxy = os.environ.get(f"{env_prefix}OPENAI_PROXY", None) # $env:OPENAI_PROXY
114-
if args.openai_proxy is not None:
115-
openai_proxy = args.openai_proxy
116-
117-
return {
118-
"openai_api_key": openai_api_key,
119-
"openai_organization": openai_organization,
120-
"openai_api_base": openai_api_base,
121-
"openai_api_type": openai_api_type,
122-
"openai_proxy": openai_proxy,
123-
"openai_model": openai_model,
124-
"openai_max_tokens": openai_max_tokens,
125-
}
126-
127-
128-
def get_openai_chat_prompt_messages(user_commit_message: Optional[str], git_diff: str, args: Namespace) -> List[Dict[str, str]]:
97+
def get_openai_chat_prompt_messages(user_commit_message: Optional[str], git_diff: str, emoji: bool, description: bool) -> List[Dict[str, str]]: # noqa: FBT001
12998
"""Get prompt messages."""
13099
role_system = [
131100
"You are a software engineer assistant to write a 'Commit message with scope'.",
@@ -135,14 +104,14 @@ def get_openai_chat_prompt_messages(user_commit_message: Optional[str], git_diff
135104
role_user = [git_diff]
136105

137106
# GitMoji
138-
if args.emoji is True:
107+
if emoji is True:
139108
role_system.append("Use the 'GitMoji convention' to preface the commit with the UNICODE characters format.")
140109
role_system.append("Do not use shortcode representation.")
141110
else:
142111
role_system.append("Do not preface the commit message with anything.")
143112

144113
# description
145-
if args.description is True:
114+
if description is True:
146115
role_system.append("Add a short description to the commit message in the body section of why these changes were made.")
147116
role_system.append('Omit "This commit" at the beginning - briefly describe changes.')
148117
role_system.append("Each sentence of the description should be in new line.")
@@ -162,40 +131,37 @@ def get_openai_chat_prompt_messages(user_commit_message: Optional[str], git_diff
162131
role_system_prompt = " ".join(role_system)
163132
role_user_prompt = " ".join(role_user)
164133

165-
logging.debug(f"ROLE_SYSTEM_PROMPT: {role_system_prompt}")
166-
logging.debug(f"ROLE_USER_PROMPT: {role_user_prompt}")
134+
logger.debug(f"ROLE_SYSTEM_PROMPT: {role_system_prompt}")
135+
logger.debug(f"ROLE_USER_PROMPT: {role_user_prompt}")
167136

168137
return [
169138
{"role": "system", "content": role_system_prompt},
170139
{"role": "user", "content": role_user_prompt},
171140
]
172141

173142

174-
def get_openai_chat_response(messages: List[Dict[str, str]], args: Namespace) -> str:
143+
def get_openai_chat_response(messages: List[Dict[str, str]], args: argparse.Namespace) -> str:
175144
"""Get OpenAI Chat Response."""
176-
config = _get_openai_config(args)
177-
logging.debug(f"CONFIG: {config}")
178-
179-
if logging.getLogger().isEnabledFor(logging.DEBUG):
180-
_num_tokens_from_messages(messages, str(config["openai_model"]))
145+
if logger.isEnabledFor(logging.DEBUG):
146+
_num_tokens_from_messages(messages, str(args.openai_model))
181147
openai.debug = True
182148

183-
openai.api_key = config["openai_api_key"]
184-
openai.organization = config["openai_organization"]
185-
openai.api_base = config["openai_api_base"]
186-
openai.api_type = config["openai_api_type"]
187-
openai.proxy = config["openai_proxy"]
149+
openai.api_key = args.openai_api_key
150+
openai.organization = args.openai_organization
151+
openai.api_base = args.openai_api_base
152+
openai.api_type = args.openai_api_type
153+
openai.proxy = args.openai_proxy
188154

189155
# ref: https://platform.openai.com/docs/api-reference/chat-completions/create
190156
# ref: https://platform.openai.com/docs/guides/chat
191157
response = openai.ChatCompletion.create(
192-
model=config["openai_model"],
158+
model=args.openai_model,
193159
messages=messages,
194-
max_tokens=int(config["openai_max_tokens"]),
160+
max_tokens=int(args.openai_max_tokens),
195161
temperature=0,
196162
top_p=0.1,
197163
)
198-
logging.debug(f"OPENAI_CHAT_RESPONSE: {response}")
164+
logger.debug(f"OPENAI_CHAT_RESPONSE: {response}")
199165

200166
return response["choices"][0]["message"]["content"]
201167

@@ -231,7 +197,7 @@ def _num_tokens_from_messages(messages: List[Dict[str, str]], model: str) -> int
231197
if key == "name":
232198
num_tokens += tokens_per_name
233199
num_tokens += 3 # every reply is primed with <|start|>assistant<|message|>
234-
logging.debug(f"NUM_TOKENS: {num_tokens}")
200+
logger.debug(f"NUM_TOKENS: {num_tokens}")
235201
return num_tokens
236202

237203

@@ -245,13 +211,12 @@ def set_commit_message(commit_msg_file_path: str, commit_msg: str) -> None:
245211
commit_msg_file_wrapper.close()
246212

247213

248-
def main() -> int:
214+
def main(args: argparse.Namespace) -> int:
249215
"""Main function of module."""
250216
try:
251-
args = get_args()
252217
user_commit_message = get_user_commit_message(args.commit_msg_filename, args.prepare_commit_message_source)
253218
git_diff = get_git_diff(args.max_char_count, ".")
254-
openai_chat_prompt_messages = get_openai_chat_prompt_messages(user_commit_message, git_diff, args)
219+
openai_chat_prompt_messages = get_openai_chat_prompt_messages(user_commit_message, git_diff, args.emoji, args.description)
255220
openai_chat_response = get_openai_chat_response(openai_chat_prompt_messages, args)
256221
set_commit_message(args.commit_msg_filename, openai_chat_response)
257222
except Exception as error:
@@ -261,4 +226,15 @@ def main() -> int:
261226

262227

263228
if __name__ == "__main__":
264-
sys.exit(main())
229+
args = get_args()
230+
logger = logging.getLogger(__name__)
231+
logger.setLevel(args.log_level.upper())
232+
233+
if logger.isEnabledFor(logging.DEBUG):
234+
fh = logging.FileHandler(filename="debug.log", mode="w")
235+
logger.addHandler(fh)
236+
237+
logger.debug(f"SYS_ARGV: {sys.argv}")
238+
logger.debug(f"ARGS: {args}")
239+
240+
sys.exit(main(args))

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ dependencies = ["openai~=0.27.4", "tiktoken~=0.3.3", "GitPython~=3.1.31"]
3535
"Bug Tracker" = "https://github.com/DariuszPorowski/chatgpt-pre-commit-hooks/issues"
3636

3737
[project.optional-dependencies]
38-
dev = ["build", "wheel", "twine", "ruff", "mypy", "pre-commit"]
38+
dev = ["ruff", "mypy", "pre-commit"]
39+
build = ["build", "wheel", "twine"]
3940

4041
[project.scripts]
4142
chatgpt-commit-message = "chatgpt_pre_commit_hooks.chatgpt_commit_message:main"
@@ -97,6 +98,7 @@ py-version = "3.8"
9798
ignore = [".venv"]
9899
max-line-length = 180
99100
recursive = true
101+
disable = ["W1203", "W0212"]
100102

101103
[tool.pydocstyle]
102104
# https://www.pydocstyle.org/en/stable/usage.html#available-options

0 commit comments

Comments
 (0)