git-commit-msg-ai 1.2.0__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,8 @@ from typing import Final, cast
3
3
 
4
4
  import anthropic
5
5
 
6
+ from git_commit_msg_ai.exceptions import AIError
7
+
6
8
  SYSTEM_PROMPT: Final[str] = textwrap.dedent("""\
7
9
  You are a Git commit message generator. Output only the commit message, nothing else. Follow the Conventional Commits specification:
8
10
  - First line: <type>(<optional scope>)<!>: <short subject> (50 chars max); append ! before the colon if the commit introduces a breaking change
@@ -16,17 +18,25 @@ MAX_TOKENS: Final[int] = 1024
16
18
 
17
19
 
18
20
  def generate_commit_message(diff: str) -> str:
19
- anthropic_client = anthropic.Anthropic()
20
-
21
- anthropic_api_response = anthropic_client.messages.create(
22
- model=MODEL,
23
- max_tokens=MAX_TOKENS,
24
- system=SYSTEM_PROMPT,
25
- messages=[{'role': 'user', 'content': diff}],
26
- )
21
+ try:
22
+ anthropic_client = anthropic.Anthropic()
23
+
24
+ anthropic_api_response = anthropic_client.messages.create(
25
+ model=MODEL,
26
+ max_tokens=MAX_TOKENS,
27
+ system=SYSTEM_PROMPT,
28
+ messages=[{'role': 'user', 'content': diff}],
29
+ )
30
+ except anthropic.AuthenticationError:
31
+ raise AIError('Anthropic API key is missing or invalid. Set the ANTHROPIC_API_KEY environment variable.')
32
+ except anthropic.RateLimitError:
33
+ raise AIError('Anthropic API rate limit reached. Wait a moment and try again.')
34
+ except anthropic.APIConnectionError:
35
+ raise AIError('Could not reach the Anthropic API. Check your network connection.')
36
+ except anthropic.APIStatusError as error:
37
+ raise AIError(f'Anthropic API returned an error: {error.status_code}.')
27
38
 
28
39
  anthropic_api_response_message = anthropic_api_response.content[0]
29
40
  commit_message = cast(anthropic.types.TextBlock, anthropic_api_response_message).text.strip()
30
41
 
31
42
  return commit_message
32
-
git_commit_msg_ai/cli.py CHANGED
@@ -1,22 +1,35 @@
1
+ import sys
2
+
1
3
  from git_commit_msg_ai import ai_client, editor, git_ops
4
+ from git_commit_msg_ai.exceptions import GitCommitAIError
2
5
 
3
6
 
4
7
  def main() -> None:
5
- diff = git_ops.get_staged_diff()
6
- commit_message = ai_client.generate_commit_message(diff)
7
- print(commit_message)
8
+ try:
9
+ diff = git_ops.get_staged_diff()
10
+ commit_message = ai_client.generate_commit_message(diff)
11
+ print(commit_message)
8
12
 
9
- print()
10
- user_selection = input('[a]ccept / [e]dit / [r]eject: ').strip().lower()
13
+ print()
14
+ user_selection = input('[a]ccept / [e]dit / [r]eject: ').strip().lower()
11
15
 
12
- if user_selection == 'a':
13
- git_ops.commit(commit_message)
14
- elif user_selection == 'e':
15
- updated_commit_message = editor.open_in_editor(commit_message)
16
- git_ops.commit(updated_commit_message)
17
- elif user_selection == 'r':
18
- print('User rejected the generated commit message. No commit made.')
16
+ if user_selection == 'a':
17
+ git_ops.commit(commit_message)
18
+ elif user_selection == 'e':
19
+ updated_commit_message = editor.open_in_editor(commit_message)
20
+ git_ops.commit(updated_commit_message)
21
+ elif user_selection == 'r':
22
+ print('User rejected the generated commit message. No commit made.')
23
+ else:
24
+ print('Invalid selection.')
25
+ sys.exit(1)
26
+ except GitCommitAIError as error:
27
+ print(str(error))
28
+ sys.exit(1)
29
+ except (KeyboardInterrupt, EOFError):
30
+ print('Aborted.')
31
+ sys.exit(1)
19
32
 
20
33
 
21
- if __name__ == '__main__':
34
+ if __name__ == '__main__': # pragma: no cover
22
35
  main()
@@ -1,19 +1,43 @@
1
1
  import os
2
+ import platform
2
3
  import subprocess
3
4
  import tempfile
4
5
 
6
+ from git_commit_msg_ai.exceptions import EditorError
7
+
8
+
9
+ def get_default_editor() -> str:
10
+ current_platform = platform.system()
11
+ is_windows = current_platform == 'Windows'
12
+
13
+ return 'notepad' if is_windows else 'vi'
14
+
5
15
 
6
16
  def open_in_editor(initial_text: str) -> str:
7
- with tempfile.NamedTemporaryFile(suffix='.txt', mode='w', delete=False) as temp_file:
8
- temp_file.write(initial_text)
9
- temp_file_path = temp_file.name
17
+ try:
18
+ with tempfile.NamedTemporaryFile(suffix='.txt', mode='w', delete=False) as temp_file:
19
+ temp_file.write(initial_text)
20
+ temp_file_path = temp_file.name
21
+ except OSError:
22
+ raise EditorError('Could not create a temporary file.')
10
23
 
11
- editor_command = os.environ.get('EDITOR', 'notepad')
12
- subprocess.run([editor_command, temp_file_path])
24
+ platform_default_editor = get_default_editor()
25
+ editor_command = os.environ.get('EDITOR', platform_default_editor)
13
26
 
14
- with open(temp_file_path) as temp_file:
15
- edited_text = temp_file.read().strip()
27
+ try:
28
+ try:
29
+ subprocess.run([editor_command, temp_file_path], check=True)
30
+ except FileNotFoundError:
31
+ raise EditorError(f'Editor "{editor_command}" was not found. Set the EDITOR environment variable to a valid editor.')
32
+ except subprocess.CalledProcessError:
33
+ raise EditorError(f'Editor "{editor_command}" exited with an error.')
16
34
 
17
- os.unlink(temp_file_path)
35
+ try:
36
+ with open(temp_file_path) as temp_file:
37
+ edited_text = temp_file.read().strip()
38
+ except OSError:
39
+ raise EditorError('Could not read the edited commit message from the temporary file.')
40
+ finally:
41
+ os.unlink(temp_file_path)
18
42
 
19
43
  return edited_text
@@ -0,0 +1,14 @@
1
+ class GitCommitAIError(Exception):
2
+ """Base exception for the application."""
3
+
4
+
5
+ class GitError(GitCommitAIError):
6
+ """Raised when a git operation fails."""
7
+
8
+
9
+ class AIError(GitCommitAIError):
10
+ """Raised when the Anthropic API call fails."""
11
+
12
+
13
+ class EditorError(GitCommitAIError):
14
+ """Raised when opening or reading from the editor fails."""
@@ -1,12 +1,28 @@
1
1
  import subprocess
2
2
 
3
+ from git_commit_msg_ai.exceptions import GitError
4
+
3
5
 
4
6
  def get_staged_diff() -> str:
5
- staged_diff = subprocess.check_output(['git', 'diff', '--cached']).decode()
7
+ try:
8
+ staged_diff = subprocess.check_output(['git', 'diff', '--cached']).decode('utf-8')
9
+ except FileNotFoundError:
10
+ raise GitError('git is not installed or not on PATH.')
11
+ except subprocess.CalledProcessError:
12
+ raise GitError('Failed to get staged diff. Are you inside a git repository?')
13
+ except UnicodeDecodeError:
14
+ raise GitError('Staged diff contains bytes that could not be decoded as UTF-8.')
15
+
16
+ if not staged_diff:
17
+ raise GitError('No staged changes found. Stage files with git add before running.')
6
18
 
7
19
  return staged_diff
8
20
 
9
21
 
10
22
  def commit(message: str) -> None:
11
- subprocess.run(['git', 'commit', '-m', message])
12
-
23
+ try:
24
+ subprocess.run(['git', 'commit', '-m', message], check=True)
25
+ except FileNotFoundError:
26
+ raise GitError('git is not installed or not on PATH.')
27
+ except subprocess.CalledProcessError:
28
+ raise GitError('git commit failed.')
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-msg-ai
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: AI-powered git commit message generator following Conventional Commits
5
5
  License-Expression: MIT
6
- Requires-Python: >=3.9
6
+ Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  Requires-Dist: anthropic
9
9
  Provides-Extra: dev
10
10
  Requires-Dist: mypy; extra == "dev"
11
11
  Requires-Dist: ruff; extra == "dev"
12
+ Requires-Dist: pytest; extra == "dev"
13
+ Requires-Dist: pytest-cov; extra == "dev"
12
14
 
13
15
  # git-commit-msg-ai
14
16
 
@@ -16,7 +18,7 @@ AI-powered git commit message generator that follows the [Conventional Commits](
16
18
 
17
19
  ## Prerequisites
18
20
 
19
- - Python 3.9+
21
+ - Python 3.10+
20
22
  - An Anthropic API key set as an environment variable:
21
23
 
22
24
  ```sh
@@ -52,7 +54,7 @@ The tool will:
52
54
  ```
53
55
 
54
56
  - **a** — commits immediately with the generated message
55
- - **e** — opens the message in your `$EDITOR`, lets you modify it, then commits
57
+ - **e** — opens the message in your `$EDITOR` (defaults to `notepad` on Windows, `vi` on Linux/macOS), lets you modify it, then commits
56
58
  - **r** — exits without committing
57
59
 
58
60
  ## Commit message format
@@ -0,0 +1,11 @@
1
+ git_commit_msg_ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ git_commit_msg_ai/ai_client.py,sha256=RnzgIx1ieuiiH6w2VijA6_F-cs6tIQR57W1x3avg8lE,1887
3
+ git_commit_msg_ai/cli.py,sha256=s94orvxin-38vqUYWAO-j5_JZTzxBXbZSVOHYZ5KO_k,1059
4
+ git_commit_msg_ai/editor.py,sha256=JnVm5SZwkunyfOjisDKvTuPm3_NPbijVk_ho_yQy-3o,1430
5
+ git_commit_msg_ai/exceptions.py,sha256=7Hwluf3zHMjs4lpGktWS-Lwgo8y_4Xbb1WqzPQHkkUA,352
6
+ git_commit_msg_ai/git_ops.py,sha256=vYc3sLdpWk8KE2dpSemww8mvga-1DV5Z3eDA7NqPAaE,977
7
+ git_commit_msg_ai-1.4.0.dist-info/METADATA,sha256=AYGCKtLplgI_qCA_Uy84JIQSuk1PC7EsylmvXdLByR8,2066
8
+ git_commit_msg_ai-1.4.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ git_commit_msg_ai-1.4.0.dist-info/entry_points.txt,sha256=KTu6wUhl0p3nf27k8L4vpSH_hpeRQpwzMPSmKv7K5Cs,65
10
+ git_commit_msg_ai-1.4.0.dist-info/top_level.txt,sha256=XYQC2BXvrcREGKEG7sm9nbwO7ifqcUSVgU7SW8BTURs,18
11
+ git_commit_msg_ai-1.4.0.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- git_commit_msg_ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- git_commit_msg_ai/ai_client.py,sha256=uSqGxQihITI-4OcZ7jcZnsm7SoZfSJCPIAo5MPENqtM,1262
3
- git_commit_msg_ai/cli.py,sha256=hiSvdBXYgNb9NuWSzWpTe6F3WqyiWLfauL3vTYHoh2c,662
4
- git_commit_msg_ai/editor.py,sha256=gNhHrDlMNl650tunwKwhS4i2IWoKMEbwMLY3NyGrim0,518
5
- git_commit_msg_ai/git_ops.py,sha256=ebTvWg1mUnXrEpup0XZaQ1jMv_JeQtFu-CpUuldjhBg,244
6
- git_commit_msg_ai-1.2.0.dist-info/METADATA,sha256=gLF-4BqEjBahtHkDNHWHmQ1YsGMqdHLDa4X70uGjk1w,1926
7
- git_commit_msg_ai-1.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
- git_commit_msg_ai-1.2.0.dist-info/entry_points.txt,sha256=KTu6wUhl0p3nf27k8L4vpSH_hpeRQpwzMPSmKv7K5Cs,65
9
- git_commit_msg_ai-1.2.0.dist-info/top_level.txt,sha256=XYQC2BXvrcREGKEG7sm9nbwO7ifqcUSVgU7SW8BTURs,18
10
- git_commit_msg_ai-1.2.0.dist-info/RECORD,,