git-commit-msg-ai 1.5.0__tar.gz → 1.5.1__tar.gz

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.
Files changed (23) hide show
  1. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/PKG-INFO +1 -1
  2. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/ai_client.py +12 -12
  3. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/cli.py +12 -12
  4. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/editor.py +15 -15
  5. git_commit_msg_ai-1.5.1/git_commit_msg_ai/git_ops.py +52 -0
  6. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/PKG-INFO +1 -1
  7. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/pyproject.toml +2 -2
  8. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_ai_client.py +39 -39
  9. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_cli.py +64 -64
  10. git_commit_msg_ai-1.5.1/tests/test_editor.py +245 -0
  11. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_exceptions.py +3 -3
  12. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/tests/test_git_ops.py +48 -48
  13. git_commit_msg_ai-1.5.0/git_commit_msg_ai/git_ops.py +0 -52
  14. git_commit_msg_ai-1.5.0/tests/test_editor.py +0 -245
  15. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/README.md +0 -0
  16. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/__init__.py +0 -0
  17. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai/exceptions.py +0 -0
  18. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/SOURCES.txt +0 -0
  19. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/dependency_links.txt +0 -0
  20. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/entry_points.txt +0 -0
  21. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/requires.txt +0 -0
  22. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/git_commit_msg_ai.egg-info/top_level.txt +0 -0
  23. {git_commit_msg_ai-1.5.0 → git_commit_msg_ai-1.5.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-msg-ai
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: AI-powered git commit message generator following Conventional Commits
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -16,7 +16,7 @@ SYSTEM_PROMPT: Final[str] = textwrap.dedent("""\
16
16
  - If there is a breaking change, add a blank line after the body followed by "BREAKING CHANGE: <description of what breaks and why>
17
17
  Types: feat, fix, docs, style, refactor, test, chore""")
18
18
 
19
- MODEL: Final[str] = 'claude-haiku-4-5-20251001'
19
+ MODEL: Final[str] = "claude-haiku-4-5-20251001"
20
20
  MAX_TOKENS: Final[int] = 1024
21
21
 
22
22
 
@@ -24,29 +24,29 @@ def generate_commit_message(diff: str) -> str:
24
24
  try:
25
25
  anthropic_client = anthropic.Anthropic()
26
26
 
27
- logger.debug('Calling Anthropic API: model=%s max_tokens=%d', MODEL, MAX_TOKENS)
27
+ logger.debug("Calling Anthropic API: model=%s max_tokens=%d", MODEL, MAX_TOKENS)
28
28
  anthropic_api_response = anthropic_client.messages.create(
29
29
  model=MODEL,
30
30
  max_tokens=MAX_TOKENS,
31
31
  system=SYSTEM_PROMPT,
32
- messages=[{'role': 'user', 'content': diff}],
32
+ messages=[{"role": "user", "content": diff}],
33
33
  )
34
34
  except anthropic.AuthenticationError:
35
- logger.error('Anthropic authentication error')
36
- raise AIError('Anthropic API key is missing or invalid. Set the ANTHROPIC_API_KEY environment variable.')
35
+ logger.error("Anthropic authentication error")
36
+ raise AIError("Anthropic API key is missing or invalid. Set the ANTHROPIC_API_KEY environment variable.")
37
37
  except anthropic.RateLimitError:
38
- logger.error('Anthropic rate limit error')
39
- raise AIError('Anthropic API rate limit reached. Wait a moment and try again.')
38
+ logger.error("Anthropic rate limit error")
39
+ raise AIError("Anthropic API rate limit reached. Wait a moment and try again.")
40
40
  except anthropic.APIConnectionError:
41
- logger.error('Anthropic API connection error')
42
- raise AIError('Could not reach the Anthropic API. Check your network connection.')
41
+ logger.error("Anthropic API connection error")
42
+ raise AIError("Could not reach the Anthropic API. Check your network connection.")
43
43
  except anthropic.APIStatusError as error:
44
- logger.error('Anthropic API status error: %s', error.status_code)
45
- raise AIError(f'Anthropic API returned an error: {error.status_code}.')
44
+ logger.error("Anthropic API status error: %s", error.status_code)
45
+ raise AIError(f"Anthropic API returned an error: {error.status_code}.")
46
46
 
47
47
  anthropic_api_response_message = anthropic_api_response.content[0]
48
48
  commit_message = cast(anthropic.types.TextBlock, anthropic_api_response_message).text.strip()
49
49
 
50
- logger.info('Commit message generated: %d chars', len(commit_message))
50
+ logger.info("Commit message generated: %d chars", len(commit_message))
51
51
 
52
52
  return commit_message
@@ -6,20 +6,20 @@ from typing import Final
6
6
  from git_commit_msg_ai import ai_client, editor, git_ops
7
7
  from git_commit_msg_ai.exceptions import GitCommitAIError
8
8
 
9
- LOG_LEVEL_ENV_VAR: Final[str] = 'GIT_COMMIT_AI_LOG_LEVEL'
9
+ LOG_LEVEL_ENV_VAR: Final[str] = "GIT_COMMIT_AI_LOG_LEVEL"
10
10
 
11
11
 
12
12
  def main() -> None:
13
- log_level_name = os.environ.get(LOG_LEVEL_ENV_VAR, 'WARNING').upper()
13
+ log_level_name = os.environ.get(LOG_LEVEL_ENV_VAR, "WARNING").upper()
14
14
  numeric_log_level = getattr(logging, log_level_name, None)
15
15
 
16
16
  if not isinstance(numeric_log_level, int):
17
17
  numeric_log_level = logging.WARNING
18
18
 
19
- logging.basicConfig(level=numeric_log_level, format='%(levelname)s:%(name)s:%(message)s', stream=sys.stderr)
19
+ logging.basicConfig(level=numeric_log_level, format="%(levelname)s:%(name)s:%(message)s", stream=sys.stderr)
20
20
 
21
21
  if not isinstance(getattr(logging, log_level_name, None), int):
22
- logging.warning('Invalid %s value %r, defaulting to WARNING', LOG_LEVEL_ENV_VAR, log_level_name)
22
+ logging.warning("Invalid %s value %r, defaulting to WARNING", LOG_LEVEL_ENV_VAR, log_level_name)
23
23
 
24
24
  try:
25
25
  diff = git_ops.get_staged_diff()
@@ -27,25 +27,25 @@ def main() -> None:
27
27
  print(commit_message)
28
28
 
29
29
  print()
30
- user_selection = input('[a]ccept / [e]dit / [r]eject: ').strip().lower()
30
+ user_selection = input("[a]ccept / [e]dit / [r]eject: ").strip().lower()
31
31
 
32
- if user_selection == 'a':
32
+ if user_selection == "a":
33
33
  git_ops.commit(commit_message)
34
- elif user_selection == 'e':
34
+ elif user_selection == "e":
35
35
  updated_commit_message = editor.open_in_editor(commit_message)
36
36
  git_ops.commit(updated_commit_message)
37
- elif user_selection == 'r':
38
- print('User rejected the generated commit message. No commit made.')
37
+ elif user_selection == "r":
38
+ print("User rejected the generated commit message. No commit made.")
39
39
  else:
40
- print('Invalid selection.')
40
+ print("Invalid selection.")
41
41
  sys.exit(1)
42
42
  except GitCommitAIError as error:
43
43
  print(str(error))
44
44
  sys.exit(1)
45
45
  except (KeyboardInterrupt, EOFError):
46
- print('Aborted.')
46
+ print("Aborted.")
47
47
  sys.exit(1)
48
48
 
49
49
 
50
- if __name__ == '__main__': # pragma: no cover
50
+ if __name__ == "__main__": # pragma: no cover
51
51
  main()
@@ -11,49 +11,49 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
  def get_default_editor() -> str:
13
13
  current_platform = platform.system()
14
- is_windows = current_platform == 'Windows'
14
+ is_windows = current_platform == "Windows"
15
15
 
16
- return 'notepad' if is_windows else 'vi'
16
+ return "notepad" if is_windows else "vi"
17
17
 
18
18
 
19
19
  def open_in_editor(initial_text: str) -> str:
20
20
  try:
21
- with tempfile.NamedTemporaryFile(suffix='.txt', mode='w', delete=False) as temp_file:
21
+ with tempfile.NamedTemporaryFile(suffix=".txt", mode="w", delete=False) as temp_file:
22
22
  temp_file.write(initial_text)
23
23
  temp_file_path = temp_file.name
24
24
  except OSError:
25
- logger.error('Could not create temporary file')
26
- raise EditorError('Could not create a temporary file.')
25
+ logger.error("Could not create temporary file")
26
+ raise EditorError("Could not create a temporary file.")
27
27
 
28
- logger.debug('Temporary file created: %s', temp_file_path)
28
+ logger.debug("Temporary file created: %s", temp_file_path)
29
29
 
30
30
  platform_default_editor = get_default_editor()
31
- editor_command = os.environ.get('EDITOR', platform_default_editor)
31
+ editor_command = os.environ.get("EDITOR", platform_default_editor)
32
32
 
33
- logger.debug('Selected editor: %s', editor_command)
33
+ logger.debug("Selected editor: %s", editor_command)
34
34
 
35
35
  try:
36
36
  try:
37
- logger.debug('Opening editor: %s %s', editor_command, temp_file_path)
37
+ logger.debug("Opening editor: %s %s", editor_command, temp_file_path)
38
38
  subprocess.run([editor_command, temp_file_path], check=True)
39
39
  except FileNotFoundError:
40
- logger.error('Editor not found: %s', editor_command)
40
+ logger.error("Editor not found: %s", editor_command)
41
41
  raise EditorError(f'Editor "{editor_command}" was not found. Set the EDITOR environment variable to a valid editor.')
42
42
  except subprocess.CalledProcessError:
43
- logger.error('Editor exited with error: %s', editor_command)
43
+ logger.error("Editor exited with error: %s", editor_command)
44
44
  raise EditorError(f'Editor "{editor_command}" exited with an error.')
45
45
 
46
- logger.debug('Editor closed, reading edited content')
46
+ logger.debug("Editor closed, reading edited content")
47
47
 
48
48
  try:
49
49
  with open(temp_file_path) as temp_file:
50
50
  edited_text = temp_file.read().strip()
51
51
  except OSError:
52
- logger.error('Could not read temporary file: %s', temp_file_path)
53
- raise EditorError('Could not read the edited commit message from the temporary file.')
52
+ logger.error("Could not read temporary file: %s", temp_file_path)
53
+ raise EditorError("Could not read the edited commit message from the temporary file.")
54
54
  finally:
55
55
  os.unlink(temp_file_path)
56
56
 
57
- logger.debug('Edited text read: %d chars', len(edited_text))
57
+ logger.debug("Edited text read: %d chars", len(edited_text))
58
58
 
59
59
  return edited_text
@@ -0,0 +1,52 @@
1
+ import logging
2
+ import subprocess
3
+ from typing import Final
4
+
5
+ from git_commit_msg_ai.exceptions import GitError
6
+
7
+ GIT_COMMAND: Final[str] = "git"
8
+ DIFF_SUBCOMMAND: Final[str] = "diff"
9
+ CACHED_FLAG: Final[str] = "--cached"
10
+ COMMIT_SUBCOMMAND: Final[str] = "commit"
11
+ MESSAGE_FLAG: Final[str] = "-m"
12
+ UTF8_ENCODING: Final[str] = "utf-8"
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def get_staged_diff() -> str:
18
+ try:
19
+ logger.debug("Running: %s %s %s", GIT_COMMAND, DIFF_SUBCOMMAND, CACHED_FLAG)
20
+ raw_diff_bytes = subprocess.check_output([GIT_COMMAND, DIFF_SUBCOMMAND, CACHED_FLAG])
21
+ staged_diff = raw_diff_bytes.decode(UTF8_ENCODING)
22
+ except FileNotFoundError:
23
+ logger.error("git not found on PATH")
24
+ raise GitError("git is not installed or not on PATH.")
25
+ except subprocess.CalledProcessError:
26
+ logger.error("git diff --cached exited non-zero")
27
+ raise GitError("Failed to get staged diff. Are you inside a git repository?")
28
+ except UnicodeDecodeError:
29
+ logger.error("Staged diff could not be decoded as UTF-8")
30
+ raise GitError("Staged diff contains bytes that could not be decoded as UTF-8.")
31
+
32
+ logger.debug("Staged diff received: %d chars", len(staged_diff))
33
+
34
+ if not staged_diff:
35
+ logger.warning("No staged changes found")
36
+ raise GitError("No staged changes found. Stage files with git add before running.")
37
+
38
+ return staged_diff
39
+
40
+
41
+ def commit(message: str) -> None:
42
+ try:
43
+ logger.debug("Running: %s %s %s <message>", GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG)
44
+ subprocess.run([GIT_COMMAND, COMMIT_SUBCOMMAND, MESSAGE_FLAG, message], check=True)
45
+ except FileNotFoundError:
46
+ logger.error("git not found on PATH")
47
+ raise GitError("git is not installed or not on PATH.")
48
+ except subprocess.CalledProcessError:
49
+ logger.error("git commit exited non-zero")
50
+ raise GitError("git commit failed.")
51
+
52
+ logger.info("Commit created successfully")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-commit-msg-ai
3
- Version: 1.5.0
3
+ Version: 1.5.1
4
4
  Summary: AI-powered git commit message generator following Conventional Commits
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "git-commit-msg-ai"
7
- version = "1.5.0"
7
+ version = "1.5.1"
8
8
  description = "AI-powered git commit message generator following Conventional Commits"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -30,7 +30,7 @@ strict = true
30
30
 
31
31
  [tool.pytest.ini_options]
32
32
  testpaths = ["tests"]
33
- addopts = "--cov --cov-report=term-missing"
33
+ addopts = "--cov --cov-report=term-missing --cov-report=html --cov-fail-under=80"
34
34
 
35
35
  [tool.coverage.run]
36
36
  source = ["git_commit_msg_ai"]
@@ -23,136 +23,136 @@ def _make_status_error(exception_class: type[anthropic.APIStatusError], status_c
23
23
  mock_response = MagicMock()
24
24
  mock_response.status_code = status_code
25
25
 
26
- return exception_class('error', response=mock_response, body={})
26
+ return exception_class("error", response=mock_response, body={})
27
27
 
28
28
 
29
29
  class TestGenerateCommitMessage:
30
30
  def test_returns_stripped_commit_message(self) -> None:
31
31
  with ExitStack() as stack:
32
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
32
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
33
33
  mock_client = MagicMock()
34
- mock_client.messages.create.return_value = _make_api_response(' feat: add feature ')
34
+ mock_client.messages.create.return_value = _make_api_response(" feat: add feature ")
35
35
  mock_anthropic_class.return_value = mock_client
36
36
 
37
- result = generate_commit_message('diff content')
37
+ result = generate_commit_message("diff content")
38
38
 
39
- assert result == 'feat: add feature'
39
+ assert result == "feat: add feature"
40
40
 
41
41
  def test_raises_ai_error_on_authentication_error(self) -> None:
42
42
  with ExitStack() as stack:
43
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
43
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
44
44
  mock_client = MagicMock()
45
45
  mock_client.messages.create.side_effect = _make_status_error(anthropic.AuthenticationError, 401)
46
46
  mock_anthropic_class.return_value = mock_client
47
47
 
48
- with pytest.raises(AIError, match='ANTHROPIC_API_KEY'):
49
- generate_commit_message('diff content')
48
+ with pytest.raises(AIError, match="ANTHROPIC_API_KEY"):
49
+ generate_commit_message("diff content")
50
50
 
51
51
  def test_raises_ai_error_on_rate_limit_error(self) -> None:
52
52
  with ExitStack() as stack:
53
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
53
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
54
54
  mock_client = MagicMock()
55
55
  mock_client.messages.create.side_effect = _make_status_error(anthropic.RateLimitError, 429)
56
56
  mock_anthropic_class.return_value = mock_client
57
57
 
58
- with pytest.raises(AIError, match='rate limit'):
59
- generate_commit_message('diff content')
58
+ with pytest.raises(AIError, match="rate limit"):
59
+ generate_commit_message("diff content")
60
60
 
61
61
  def test_raises_ai_error_on_api_connection_error(self) -> None:
62
62
  mock_request = MagicMock()
63
63
 
64
64
  with ExitStack() as stack:
65
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
65
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
66
66
  mock_client = MagicMock()
67
67
  mock_client.messages.create.side_effect = anthropic.APIConnectionError(request=mock_request)
68
68
  mock_anthropic_class.return_value = mock_client
69
69
 
70
- with pytest.raises(AIError, match='network'):
71
- generate_commit_message('diff content')
70
+ with pytest.raises(AIError, match="network"):
71
+ generate_commit_message("diff content")
72
72
 
73
73
  def test_raises_ai_error_on_api_status_error_with_status_code(self) -> None:
74
74
  with ExitStack() as stack:
75
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
75
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
76
76
  mock_client = MagicMock()
77
77
  mock_client.messages.create.side_effect = _make_status_error(anthropic.APIStatusError, 500)
78
78
  mock_anthropic_class.return_value = mock_client
79
79
 
80
- with pytest.raises(AIError, match='500'):
81
- generate_commit_message('diff content')
80
+ with pytest.raises(AIError, match="500"):
81
+ generate_commit_message("diff content")
82
82
 
83
83
  def test_logs_debug_before_api_call(self, caplog: pytest.LogCaptureFixture) -> None:
84
- with caplog.at_level(logging.DEBUG, logger='git_commit_msg_ai.ai_client'):
84
+ with caplog.at_level(logging.DEBUG, logger="git_commit_msg_ai.ai_client"):
85
85
  with ExitStack() as stack:
86
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
86
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
87
87
  mock_client = MagicMock()
88
- mock_client.messages.create.return_value = _make_api_response('feat: add feature')
88
+ mock_client.messages.create.return_value = _make_api_response("feat: add feature")
89
89
  mock_anthropic_class.return_value = mock_client
90
- generate_commit_message('diff content')
90
+ generate_commit_message("diff content")
91
91
 
92
92
  messages = [r.message for r in caplog.records]
93
93
  assert any(MODEL in m and str(MAX_TOKENS) in m for m in messages)
94
94
 
95
95
  def test_logs_info_after_message_generated(self, caplog: pytest.LogCaptureFixture) -> None:
96
- with caplog.at_level(logging.INFO, logger='git_commit_msg_ai.ai_client'):
96
+ with caplog.at_level(logging.INFO, logger="git_commit_msg_ai.ai_client"):
97
97
  with ExitStack() as stack:
98
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
98
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
99
99
  mock_client = MagicMock()
100
- mock_client.messages.create.return_value = _make_api_response('feat: add feature')
100
+ mock_client.messages.create.return_value = _make_api_response("feat: add feature")
101
101
  mock_anthropic_class.return_value = mock_client
102
- generate_commit_message('diff content')
102
+ generate_commit_message("diff content")
103
103
 
104
- assert any(r.levelno == logging.INFO and 'Commit message generated' in r.message for r in caplog.records)
104
+ assert any(r.levelno == logging.INFO and "Commit message generated" in r.message for r in caplog.records)
105
105
 
106
106
  def test_logs_error_on_authentication_error(self, caplog: pytest.LogCaptureFixture) -> None:
107
- with caplog.at_level(logging.ERROR, logger='git_commit_msg_ai.ai_client'):
107
+ with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
108
108
  with ExitStack() as stack:
109
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
109
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
110
110
  mock_client = MagicMock()
111
111
  mock_client.messages.create.side_effect = _make_status_error(anthropic.AuthenticationError, 401)
112
112
  mock_anthropic_class.return_value = mock_client
113
113
 
114
114
  with pytest.raises(AIError):
115
- generate_commit_message('diff content')
115
+ generate_commit_message("diff content")
116
116
 
117
117
  assert any(r.levelno == logging.ERROR for r in caplog.records)
118
118
 
119
119
  def test_logs_error_on_rate_limit_error(self, caplog: pytest.LogCaptureFixture) -> None:
120
- with caplog.at_level(logging.ERROR, logger='git_commit_msg_ai.ai_client'):
120
+ with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
121
121
  with ExitStack() as stack:
122
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
122
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
123
123
  mock_client = MagicMock()
124
124
  mock_client.messages.create.side_effect = _make_status_error(anthropic.RateLimitError, 429)
125
125
  mock_anthropic_class.return_value = mock_client
126
126
 
127
127
  with pytest.raises(AIError):
128
- generate_commit_message('diff content')
128
+ generate_commit_message("diff content")
129
129
 
130
130
  assert any(r.levelno == logging.ERROR for r in caplog.records)
131
131
 
132
132
  def test_logs_error_on_api_connection_error(self, caplog: pytest.LogCaptureFixture) -> None:
133
133
  mock_request = MagicMock()
134
134
 
135
- with caplog.at_level(logging.ERROR, logger='git_commit_msg_ai.ai_client'):
135
+ with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
136
136
  with ExitStack() as stack:
137
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
137
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
138
138
  mock_client = MagicMock()
139
139
  mock_client.messages.create.side_effect = anthropic.APIConnectionError(request=mock_request)
140
140
  mock_anthropic_class.return_value = mock_client
141
141
 
142
142
  with pytest.raises(AIError):
143
- generate_commit_message('diff content')
143
+ generate_commit_message("diff content")
144
144
 
145
145
  assert any(r.levelno == logging.ERROR for r in caplog.records)
146
146
 
147
147
  def test_logs_error_on_api_status_error(self, caplog: pytest.LogCaptureFixture) -> None:
148
- with caplog.at_level(logging.ERROR, logger='git_commit_msg_ai.ai_client'):
148
+ with caplog.at_level(logging.ERROR, logger="git_commit_msg_ai.ai_client"):
149
149
  with ExitStack() as stack:
150
- mock_anthropic_class = stack.enter_context(patch('git_commit_msg_ai.ai_client.anthropic.Anthropic'))
150
+ mock_anthropic_class = stack.enter_context(patch("git_commit_msg_ai.ai_client.anthropic.Anthropic"))
151
151
  mock_client = MagicMock()
152
152
  mock_client.messages.create.side_effect = _make_status_error(anthropic.APIStatusError, 500)
153
153
  mock_anthropic_class.return_value = mock_client
154
154
 
155
155
  with pytest.raises(AIError):
156
- generate_commit_message('diff content')
156
+ generate_commit_message("diff content")
157
157
 
158
- assert any(r.levelno == logging.ERROR and '500' in r.message for r in caplog.records)
158
+ assert any(r.levelno == logging.ERROR and "500" in r.message for r in caplog.records)