commitai 1.0.4__tar.gz → 1.0.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: commitai
3
- Version: 1.0.4
3
+ Version: 1.0.5
4
4
  Summary: Commitai helps you generate git commit messages using AI
5
5
  Project-URL: Bug Tracker, https://github.com/lguibr/commitai/issues
6
6
  Project-URL: Documentation, https://github.com/lguibr/commitai/blob/main/README.md
@@ -42,15 +42,16 @@ Classifier: Topic :: Software Development :: Version Control :: Git
42
42
  Classifier: Topic :: Utilities
43
43
  Requires-Python: >=3.9
44
44
  Requires-Dist: click<9.0,>=8.0
45
- Requires-Dist: langchain-anthropic<0.3.0,>=0.1.0
46
- Requires-Dist: langchain-community<0.2.0,>=0.0.20
47
- Requires-Dist: langchain-core<0.3.0,>=0.1.0
48
- Requires-Dist: langchain-google-genai~=0.0.9
49
- Requires-Dist: langchain-openai<0.3.0,>=0.1.0
50
- Requires-Dist: langchain<0.3.0,>=0.1.0
45
+ Requires-Dist: langchain-anthropic<=0.3.12,>=0.1.0
46
+ Requires-Dist: langchain-community<=0.3.23,>=0.0.20
47
+ Requires-Dist: langchain-core<=0.3.58,>=0.1.0
48
+ Requires-Dist: langchain-google-genai~=2.1.4
49
+ Requires-Dist: langchain-ollama~=0.3.2
50
+ Requires-Dist: langchain-openai<=0.3.16,>=0.1.0
51
+ Requires-Dist: langchain<=0.3.25,>=0.1.0
51
52
  Requires-Dist: pydantic<3.0,>=2.0
52
53
  Provides-Extra: test
53
- Requires-Dist: langchain-google-genai~=0.0.9; extra == 'test'
54
+ Requires-Dist: langchain-google-genai~=2.1.4; extra == 'test'
54
55
  Requires-Dist: mypy>=1.9.0; extra == 'test'
55
56
  Requires-Dist: pytest-cov>=3.0; extra == 'test'
56
57
  Requires-Dist: pytest>=7.0; extra == 'test'
@@ -67,9 +68,13 @@ Description-Content-Type: text/markdown
67
68
  [![License](https://img.shields.io/pypi/l/CommitAi.svg)](https://github.com/lguibr/CommitAi/blob/main/LICENSE)
68
69
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
69
70
 
70
- <p align="center">
71
- <img src="bitmap.png" alt="Logo" width="300"/>
72
- </p>
71
+
72
+ <img
73
+ src="https://raw.githubusercontent.com/lguibr/commitai/main/bitmap.png"
74
+ alt="screenshot"
75
+ width="400"
76
+ />
77
+
73
78
 
74
79
  **Tired of writing Git commit messages? Let AI do the heavy lifting!**
75
80
 
@@ -102,7 +107,7 @@ Simply stage your files and run `commitai`. It analyzes the diff, optionally tak
102
107
  * 📝 **Optional Explanations**: Provide a high-level description of your changes as input to guide the AI, or let it infer the context solely from the code diff.
103
108
  * ✅ **Pre-commit Hook Integration**: Automatically runs your existing native Git pre-commit hook (`.git/hooks/pre-commit`) before generating the message, ensuring code quality and style checks pass.
104
109
  * 🔧 **Customizable Prompts via Templates**: Add custom instructions or context to the AI prompt using global environment variables or repository-specific template files.
105
- * 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, or Google.
110
+ * 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, Google or local AI models with Ollama.
106
111
  * ⚙️ **Flexible Workflow**:
107
112
  * Stages all changes automatically (`-a` flag).
108
113
  * Reviews message in your default Git editor (default behavior).
@@ -154,6 +159,14 @@ CommitAi requires API keys for the AI provider you intend to use. Set these as e
154
159
 
155
160
  You only need to set the key for the provider corresponding to the model you select (or the default, Gemini).
156
161
 
162
+ ### Ollama
163
+
164
+ CommitAi can also work with Ollama models:
165
+ ```bash
166
+ export OLLAMA_HOST="your_ollama_base_url"
167
+ ```
168
+
169
+
157
170
  ### Commit Templates (Optional)
158
171
 
159
172
  You can add custom instructions to the default system prompt used by the AI. This is useful for enforcing project-specific guidelines (e.g., mentioning ticket numbers).
@@ -290,7 +303,7 @@ Contributions are highly welcome! Please follow these steps:
290
303
  9. Run checks locally before committing:
291
304
  * Format code: `ruff format .`
292
305
  * Lint code: `ruff check .`
293
- * Run type checks: `mypy commitai commitai/tests`
306
+ * Run type checks: `mypy commitai tests`
294
307
  * Run tests: `pytest`
295
308
  10. Commit your changes (you can use `commitai`!).
296
309
  11. Push your branch to your fork: `git push origin my-feature-branch`
@@ -7,9 +7,13 @@
7
7
  [![License](https://img.shields.io/pypi/l/CommitAi.svg)](https://github.com/lguibr/CommitAi/blob/main/LICENSE)
8
8
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
9
9
 
10
- <p align="center">
11
- <img src="bitmap.png" alt="Logo" width="300"/>
12
- </p>
10
+
11
+ <img
12
+ src="https://raw.githubusercontent.com/lguibr/commitai/main/bitmap.png"
13
+ alt="screenshot"
14
+ width="400"
15
+ />
16
+
13
17
 
14
18
  **Tired of writing Git commit messages? Let AI do the heavy lifting!**
15
19
 
@@ -42,7 +46,7 @@ Simply stage your files and run `commitai`. It analyzes the diff, optionally tak
42
46
  * 📝 **Optional Explanations**: Provide a high-level description of your changes as input to guide the AI, or let it infer the context solely from the code diff.
43
47
  * ✅ **Pre-commit Hook Integration**: Automatically runs your existing native Git pre-commit hook (`.git/hooks/pre-commit`) before generating the message, ensuring code quality and style checks pass.
44
48
  * 🔧 **Customizable Prompts via Templates**: Add custom instructions or context to the AI prompt using global environment variables or repository-specific template files.
45
- * 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, or Google.
49
+ * 🤖 **Multiple AI Provider Support**: Choose your preferred AI model from OpenAI, Anthropic, Google or local AI models with Ollama.
46
50
  * ⚙️ **Flexible Workflow**:
47
51
  * Stages all changes automatically (`-a` flag).
48
52
  * Reviews message in your default Git editor (default behavior).
@@ -94,6 +98,14 @@ CommitAi requires API keys for the AI provider you intend to use. Set these as e
94
98
 
95
99
  You only need to set the key for the provider corresponding to the model you select (or the default, Gemini).
96
100
 
101
+ ### Ollama
102
+
103
+ CommitAi can also work with Ollama models:
104
+ ```bash
105
+ export OLLAMA_HOST="your_ollama_base_url"
106
+ ```
107
+
108
+
97
109
  ### Commit Templates (Optional)
98
110
 
99
111
  You can add custom instructions to the default system prompt used by the AI. This is useful for enforcing project-specific guidelines (e.g., mentioning ticket numbers).
@@ -230,7 +242,7 @@ Contributions are highly welcome! Please follow these steps:
230
242
  9. Run checks locally before committing:
231
243
  * Format code: `ruff format .`
232
244
  * Lint code: `ruff check .`
233
- * Run type checks: `mypy commitai commitai/tests`
245
+ * Run type checks: `mypy commitai tests`
234
246
  * Run tests: `pytest`
235
247
  10. Commit your changes (you can use `commitai`!).
236
248
  11. Push your branch to your fork: `git push origin my-feature-branch`
@@ -2,11 +2,12 @@
2
2
  # -*- coding: utf-8 -*-
3
3
 
4
4
  import os
5
- from typing import Optional, Tuple
5
+ from typing import Optional, Tuple, cast
6
6
 
7
7
  import click
8
8
  from langchain_anthropic import ChatAnthropic
9
9
  from langchain_core.language_models.chat_models import BaseChatModel
10
+ from langchain_ollama import ChatOllama
10
11
  from langchain_openai import ChatOpenAI
11
12
 
12
13
  # Keep SecretStr import in case it's needed elsewhere or for future refinement
@@ -34,7 +35,6 @@ from commitai.template import (
34
35
  )
35
36
 
36
37
 
37
- # Helper function to get API key with priority
38
38
  def _get_google_api_key() -> Optional[str]:
39
39
  """Gets the Google API key from environment variables in priority order."""
40
40
  return (
@@ -44,7 +44,6 @@ def _get_google_api_key() -> Optional[str]:
44
44
  )
45
45
 
46
46
 
47
- # Helper function to initialize the LLM
48
47
  def _initialize_llm(model: str) -> BaseChatModel:
49
48
  """Initializes and returns the LangChain chat model based on the model name."""
50
49
  google_api_key_str = _get_google_api_key()
@@ -56,17 +55,16 @@ def _initialize_llm(model: str) -> BaseChatModel:
56
55
  raise click.ClickException(
57
56
  "Error: OPENAI_API_KEY environment variable not set."
58
57
  )
59
- # Pass raw string and ignore Mypy SecretStr complaint
60
58
  return ChatOpenAI(model=model, api_key=api_key, temperature=0.7)
59
+
61
60
  elif model.startswith("claude-"):
62
61
  api_key = os.getenv("ANTHROPIC_API_KEY")
63
62
  if not api_key:
64
63
  raise click.ClickException(
65
64
  "Error: ANTHROPIC_API_KEY environment variable not set."
66
65
  )
67
- # Pass raw string and ignore Mypy SecretStr complaint
68
- # Also ignore missing timeout argument if it's optional
69
66
  return ChatAnthropic(model_name=model, api_key=api_key, temperature=0.7)
67
+
70
68
  elif model.startswith("gemini-"):
71
69
  if ChatGoogleGenerativeAI is None:
72
70
  raise click.ClickException(
@@ -79,30 +77,23 @@ def _initialize_llm(model: str) -> BaseChatModel:
79
77
  "Error: Google API Key not found. Set GOOGLE_API_KEY, "
80
78
  "GEMINI_API_KEY, or GOOGLE_GENERATIVE_AI_API_KEY."
81
79
  )
82
- # Pass raw string and ignore Mypy SecretStr complaint
83
- # Also ignore missing optional arguments
84
80
  return ChatGoogleGenerativeAI(
85
81
  model=model,
86
82
  google_api_key=google_api_key_str,
87
83
  temperature=0.7,
88
84
  convert_system_message_to_human=True,
89
85
  )
86
+ elif model.startswith("llama"):
87
+ # Ollama models (e.g., llama2, llama3)
88
+ return cast(BaseChatModel, ChatOllama(model=model, temperature=0.7))
90
89
  else:
91
90
  raise click.ClickException(f"🚫 Unsupported model: {model}")
91
+
92
92
  except Exception as e:
93
93
  raise click.ClickException(f"Error initializing AI model: {e}") from e
94
94
 
95
95
 
96
- # Helper function to prepare context (diff, repo, branch)
97
96
  def _prepare_context() -> str:
98
- """
99
- Gets the repository context (name, branch, diff).
100
-
101
- Returns:
102
- str: The formatted diff string.
103
- Raises:
104
- click.ClickException: If no staged changes are found.
105
- """
106
97
  diff = get_staged_changes_diff()
107
98
  if not diff:
108
99
  raise click.ClickException("⚠️ Warning: No staged changes found. Exiting.")
@@ -112,11 +103,9 @@ def _prepare_context() -> str:
112
103
  return f"{repo_name}/{branch_name}\n\n{diff}"
113
104
 
114
105
 
115
- # Helper function to build the final prompt
116
106
  def _build_prompt(
117
107
  explanation: str, formatted_diff: str, template: Optional[str]
118
108
  ) -> str:
119
- """Builds the complete prompt for the AI model."""
120
109
  system_message = default_system_message
121
110
  if template:
122
111
  system_message += adding_template
@@ -130,14 +119,7 @@ def _build_prompt(
130
119
  return f"{system_message}\n\n{diff_message}"
131
120
 
132
121
 
133
- # Helper function to handle commit message editing and creation
134
122
  def _handle_commit(commit_message: str, commit_flag: bool) -> None:
135
- """
136
- Writes message, optionally opens editor, and creates the commit.
137
-
138
- Raises:
139
- click.ClickException: On file I/O errors or if the commit is aborted.
140
- """
141
123
  repo_path = get_repository_name()
142
124
  git_dir = os.path.join(repo_path, ".git")
143
125
  try:
@@ -180,7 +162,6 @@ def _handle_commit(commit_message: str, commit_flag: bool) -> None:
180
162
 
181
163
  @click.group(context_settings={"help_option_names": ["-h", "--help"]})
182
164
  def cli() -> None:
183
- """CommitAi CLI group."""
184
165
  pass
185
166
 
186
167
 
@@ -224,7 +205,6 @@ def generate_message(
224
205
  add: bool,
225
206
  model: str,
226
207
  ) -> None:
227
- """Generates a commit message based on staged changes and description."""
228
208
  explanation = " ".join(description)
229
209
 
230
210
  llm = _initialize_llm(model)
@@ -6,7 +6,9 @@ build-backend = "hatchling.build"
6
6
  [project]
7
7
  name = "commitai"
8
8
  # Make sure to update version in commitai/__init__.py as well
9
- version = "1.0.4" # Assuming version was bumped, adjust if needed
9
+
10
+ version = "1.0.5"
11
+
10
12
  description = "Commitai helps you generate git commit messages using AI"
11
13
  readme = "README.md"
12
14
  requires-python = ">=3.9"
@@ -29,12 +31,13 @@ classifiers = [
29
31
  ]
30
32
  dependencies = [
31
33
  "click>=8.0,<9.0",
32
- "langchain>=0.1.0,<0.3.0",
33
- "langchain-core>=0.1.0,<0.3.0",
34
- "langchain-community>=0.0.20,<0.2.0",
35
- "langchain-anthropic>=0.1.0,<0.3.0",
36
- "langchain-openai>=0.1.0,<0.3.0",
37
- "langchain-google-genai~=0.0.9",
34
+ "langchain>=0.1.0,<=0.3.25",
35
+ "langchain-core>=0.1.0,<=0.3.58",
36
+ "langchain-community>=0.0.20,<=0.3.23",
37
+ "langchain-anthropic>=0.1.0,<=0.3.12",
38
+ "langchain-openai>=0.1.0,<=0.3.16",
39
+ "langchain-google-genai~=2.1.4",
40
+ "langchain-ollama~=0.3.2",
38
41
  "pydantic>=2.0,<3.0",
39
42
  ]
40
43
 
@@ -55,7 +58,7 @@ test = [
55
58
  "types-setuptools",
56
59
  # Pin ruff version to match pre-commit hook
57
60
  "ruff==0.4.4",
58
- "langchain-google-genai~=0.0.9", # Keep google genai here for mypy in pre-commit
61
+ "langchain-google-genai~=2.1.4", # Keep google genai here for mypy in pre-commit
59
62
  ]
60
63
 
61
64
  [tool.hatch.version]
@@ -10,6 +10,7 @@ from langchain_anthropic import ChatAnthropic
10
10
  from langchain_google_genai import (
11
11
  ChatGoogleGenerativeAI as ActualChatGoogleGenerativeAI,
12
12
  )
13
+ from langchain_ollama import ChatOllama
13
14
  from langchain_openai import ChatOpenAI
14
15
 
15
16
  from commitai.cli import cli
@@ -35,6 +36,7 @@ def mock_generate_deps(tmp_path):
35
36
  ) as mock_google_class_in_cli,
36
37
  patch("commitai.cli.ChatOpenAI", spec=ChatOpenAI) as mock_openai_class,
37
38
  patch("commitai.cli.ChatAnthropic", spec=ChatAnthropic) as mock_anthropic_class,
39
+ patch("commitai.cli.ChatOllama", spec=ChatOllama) as mock_ollama_class,
38
40
  patch("commitai.cli.stage_all_changes") as mock_stage,
39
41
  patch("commitai.cli.run_pre_commit_hook", return_value=True) as mock_hook,
40
42
  patch(
@@ -62,17 +64,20 @@ def mock_generate_deps(tmp_path):
62
64
  mock_openai_instance = mock_openai_class.return_value
63
65
  mock_anthropic_instance = mock_anthropic_class.return_value
64
66
  mock_google_instance = mock_google_class_in_cli.return_value
67
+ mock_ollama_instance = mock_ollama_class.return_value
65
68
 
66
69
  mock_openai_instance.spec = ChatOpenAI
67
70
  mock_anthropic_instance.spec = ChatAnthropic
68
71
  if mock_google_class_in_cli is not None:
69
72
  mock_google_instance.spec = ActualChatGoogleGenerativeAI
73
+ mock_ollama_instance.spec = ChatOllama
70
74
 
71
75
  content_mock = MagicMock()
72
76
  content_mock.content = "Generated commit message"
73
77
  mock_openai_instance.invoke.return_value = content_mock
74
78
  mock_anthropic_instance.invoke.return_value = content_mock
75
79
  mock_google_instance.invoke.return_value = content_mock
80
+ mock_ollama_instance.invoke.return_value = content_mock
76
81
 
77
82
  def getenv_side_effect(key, default=None):
78
83
  if key == "OPENAI_API_KEY":
@@ -81,6 +86,8 @@ def mock_generate_deps(tmp_path):
81
86
  return "fake_anthropic_key"
82
87
  if key == "TEMPLATE_COMMIT":
83
88
  return None
89
+ if key == "OLLAMA_HOST":
90
+ return "fake_ollama_host"
84
91
  return os.environ.get(key, default)
85
92
 
86
93
  mock_getenv.side_effect = getenv_side_effect
@@ -89,9 +96,11 @@ def mock_generate_deps(tmp_path):
89
96
  "openai_class": mock_openai_class,
90
97
  "anthropic_class": mock_anthropic_class,
91
98
  "google_class": mock_google_class_in_cli,
99
+ "ollama_class": mock_ollama_class,
92
100
  "openai_instance": mock_openai_instance,
93
101
  "anthropic_instance": mock_anthropic_instance,
94
102
  "google_instance": mock_google_instance,
103
+ "ollama_instance": mock_ollama_instance,
95
104
  "stage": mock_stage,
96
105
  "hook": mock_hook,
97
106
  "diff": mock_diff,
@@ -186,6 +195,22 @@ def test_generate_select_claude(mock_generate_deps):
186
195
  mock_generate_deps["commit"].assert_called_once()
187
196
 
188
197
 
198
+ def test_generate_select_ollama(mock_generate_deps):
199
+ """Test selecting ollama model via generate command."""
200
+ runner = CliRunner()
201
+ mock_generate_deps[
202
+ "file_open"
203
+ ].return_value.read.return_value = "Generated commit message"
204
+ result = runner.invoke(cli, ["generate", "-m", "llama3", "Test explanation"])
205
+
206
+ assert result.exit_code == 0, result.output
207
+ mock_generate_deps["ollama_class"].assert_called_once_with(
208
+ model="llama3", temperature=0.7
209
+ )
210
+ mock_generate_deps["ollama_instance"].invoke.assert_called_once()
211
+ mock_generate_deps["commit"].assert_called_once()
212
+
213
+
189
214
  def test_generate_with_add_flag(mock_generate_deps):
190
215
  """Test the -a flag with generate command."""
191
216
  runner = CliRunner()
@@ -314,18 +339,6 @@ def test_generate_google_key_priority(mock_generate_deps):
314
339
  )
315
340
 
316
341
 
317
- def test_generate_unsupported_model(mock_generate_deps):
318
- """Test generate command with an unsupported model."""
319
- runner = CliRunner()
320
- result = runner.invoke(
321
- cli, ["generate", "-m", "unsupported-model", "Test explanation"]
322
- )
323
-
324
- assert result.exit_code == 1, result.output
325
- assert "Unsupported model: unsupported-model" in result.output
326
- mock_generate_deps["commit"].assert_not_called()
327
-
328
-
329
342
  def test_generate_empty_commit_message_aborts(mock_generate_deps):
330
343
  """Test generate command aborts with empty commit message after edit."""
331
344
  runner = CliRunner()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes