gpt-pr 0.7.0__py3-none-any.whl → 0.7.1__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.

Potentially problematic release.


This version of gpt-pr might be problematic. Click here for more details.

gpt_pr/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version(__name__)
@@ -2,35 +2,35 @@ import requests
2
2
  import os
3
3
  import json
4
4
  import tempfile
5
- from gptpr.version import __version__
5
+ from gpt_pr import __version__
6
6
  from datetime import datetime, timedelta
7
7
 
8
- from gptpr import consolecolor as cc
8
+ from gpt_pr import consolecolor as cc
9
9
 
10
10
 
11
- PACKAGE_NAME = 'gpt-pr'
12
- CACHE_FILE = os.path.join(os.path.expanduser("~"), '.gpt_pr_update_cache.json')
11
+ PACKAGE_NAME = "gpt-pr"
12
+ CACHE_FILE = os.path.join(os.path.expanduser("~"), ".gpt_pr_update_cache.json")
13
13
  CACHE_DURATION = timedelta(days=1)
14
14
 
15
15
 
16
16
  def cache_daily_version(func):
17
17
  def wrapper(*args, **kwargs):
18
18
  cache = load_cache()
19
- last_checked = cache.get('last_checked')
19
+ last_checked = cache.get("last_checked")
20
20
 
21
21
  if last_checked:
22
22
  last_checked = datetime.fromisoformat(last_checked)
23
23
 
24
24
  if datetime.now() - last_checked < CACHE_DURATION:
25
25
  # Use cached version info
26
- latest_version = cache.get('latest_version')
26
+ latest_version = cache.get("latest_version")
27
27
  if latest_version:
28
28
  return latest_version
29
29
 
30
30
  latest_version = func(*args, **kwargs)
31
31
  cache = {
32
- 'last_checked': datetime.now().isoformat(),
33
- 'latest_version': latest_version
32
+ "last_checked": datetime.now().isoformat(),
33
+ "latest_version": latest_version,
34
34
  }
35
35
  save_cache(cache)
36
36
 
@@ -41,18 +41,18 @@ def cache_daily_version(func):
41
41
 
42
42
  def get_cache_file_path():
43
43
  temp_dir = tempfile.gettempdir()
44
- return os.path.join(temp_dir, f'{PACKAGE_NAME}_update_cache.json')
44
+ return os.path.join(temp_dir, f"{PACKAGE_NAME}_update_cache.json")
45
45
 
46
46
 
47
47
  @cache_daily_version
48
48
  def get_latest_version():
49
- url = f'https://pypi.org/pypi/{PACKAGE_NAME}/json'
49
+ url = f"https://pypi.org/pypi/{PACKAGE_NAME}/json"
50
50
 
51
51
  try:
52
52
  response = requests.get(url)
53
53
  response.raise_for_status()
54
54
  data = response.json()
55
- return data['info']['version']
55
+ return data["info"]["version"]
56
56
  except requests.exceptions.RequestException as e:
57
57
  print(f"Error fetching latest version info: {e}")
58
58
  return None
@@ -61,7 +61,7 @@ def get_latest_version():
61
61
  def load_cache():
62
62
  cache_file = get_cache_file_path()
63
63
  if os.path.exists(cache_file):
64
- with open(cache_file, 'r') as file:
64
+ with open(cache_file, "r") as file:
65
65
  return json.load(file)
66
66
 
67
67
  return {}
@@ -69,7 +69,7 @@ def load_cache():
69
69
 
70
70
  def save_cache(data):
71
71
  cache_file = get_cache_file_path()
72
- with open(cache_file, 'w') as file:
72
+ with open(cache_file, "w") as file:
73
73
  file.write(json.dumps(data))
74
74
 
75
75
 
@@ -77,12 +77,15 @@ def check_for_updates():
77
77
  latest_version = get_latest_version()
78
78
 
79
79
  if latest_version and latest_version != __version__:
80
- print('')
81
- print(cc.yellow(
82
- f'A new version of {PACKAGE_NAME} is available ({latest_version}). '
83
- f'You are using version {__version__}. Please update by running'),
84
- cc.green(f'pip install --upgrade {PACKAGE_NAME}.'))
85
- print('')
80
+ print("")
81
+ print(
82
+ cc.yellow(
83
+ f"A new version of {PACKAGE_NAME} is available ({latest_version}). "
84
+ f"You are using version {__version__}. Please update by running"
85
+ ),
86
+ cc.green(f"pip install --upgrade {PACKAGE_NAME}."),
87
+ )
88
+ print("")
86
89
 
87
90
 
88
91
  if __name__ == "__main__":
gpt_pr/gh.py ADDED
@@ -0,0 +1,44 @@
1
+ import os
2
+ from github import Github
3
+ from InquirerPy import inquirer
4
+ from gpt_pr.config import config, config_command_example, CONFIG_README_SECTION
5
+
6
+
7
+ def _get_gh_token():
8
+ gh_token = config.get_user_config("GH_TOKEN")
9
+ if not gh_token:
10
+ gh_token = os.environ.get("GH_TOKEN")
11
+
12
+ if not gh_token:
13
+ print(
14
+ 'Please set "gh_token" config. Just run:',
15
+ config_command_example("gh_token", "[my gh token]"),
16
+ "more about at",
17
+ CONFIG_README_SECTION,
18
+ )
19
+ raise SystemExit(1)
20
+
21
+ return gh_token
22
+
23
+
24
+ def create_pr(pr_data, yield_confirmation, gh=None):
25
+ if not gh:
26
+ gh = Github(_get_gh_token())
27
+
28
+ repo = gh.get_repo(f"{pr_data.branch_info.owner}/{pr_data.branch_info.repo}")
29
+
30
+ pr_confirmation = (
31
+ yield_confirmation
32
+ or inquirer.confirm(message="Create GitHub PR?", default=True).execute()
33
+ )
34
+
35
+ if pr_confirmation:
36
+ pr = repo.create_pull(
37
+ title=pr_data.title,
38
+ body=pr_data.create_body(),
39
+ head=pr_data.branch_info.branch,
40
+ base=pr_data.branch_info.base_branch,
41
+ )
42
+ print("Pull request created successfully: ", pr.html_url)
43
+ else:
44
+ print("cancelling...")
gpt_pr/gpt.py ADDED
@@ -0,0 +1,8 @@
1
+ import tiktoken
2
+
3
+
4
+ def num_tokens_from_string(string: str, encoding_name: str) -> int:
5
+ """Returns the number of tokens in a text string."""
6
+ encoding = tiktoken.get_encoding(encoding_name)
7
+ num_tokens = len(encoding.encode(string))
8
+ return num_tokens
gpt_pr/main.py ADDED
@@ -0,0 +1,117 @@
1
+ import fire
2
+ from InquirerPy import inquirer
3
+
4
+ from gpt_pr.gitutil import get_branch_info
5
+ from gpt_pr.gh import create_pr
6
+ from gpt_pr.prdata import get_pr_data
7
+ from gpt_pr import __version__
8
+ from gpt_pr.config import config, config_command_example, CONFIG_README_SECTION
9
+ from gpt_pr import consolecolor as cc
10
+ from gpt_pr.checkversion import check_for_updates
11
+
12
+
13
+ def run(base_branch="main", origin="origin", yield_confirmation=False, version=False):
14
+ """
15
+ Create Pull Requests from current branch with base branch (default 'main' branch)
16
+ """
17
+
18
+ if version:
19
+ print("Current version:", __version__)
20
+ return
21
+
22
+ branch_info = get_branch_info(base_branch, origin, yield_confirmation)
23
+
24
+ if not branch_info:
25
+ return
26
+
27
+ pr_data = None
28
+ generate_pr_data = True
29
+ while generate_pr_data:
30
+ pr_data = get_pr_data(branch_info)
31
+ print("")
32
+ print("#########################################")
33
+ print(pr_data.to_display())
34
+ print("#########################################")
35
+ print("")
36
+
37
+ if yield_confirmation:
38
+ break
39
+
40
+ generate_pr_data = not inquirer.confirm(
41
+ message="Create PR with this? If 'no', let's try again...", default=True
42
+ ).execute()
43
+
44
+ if generate_pr_data:
45
+ print("Generating another PR data...")
46
+
47
+ create_pr(pr_data, yield_confirmation)
48
+
49
+
50
+ def set_config(name, value):
51
+ name = name.upper()
52
+ config.set_user_config(name, value)
53
+ config.persist()
54
+
55
+ print("Config value", cc.bold(name), "set to", cc.yellow(value))
56
+
57
+
58
+ def get_config(name):
59
+ upper_name = name.upper()
60
+ print(
61
+ "Config value",
62
+ cc.bold(name),
63
+ "=",
64
+ cc.yellow(config.get_user_config(upper_name)),
65
+ )
66
+
67
+
68
+ def reset_config(name):
69
+ upper_name = name.upper()
70
+ config.reset_user_config(upper_name)
71
+ print(
72
+ "Config value",
73
+ cc.bold(name),
74
+ "=",
75
+ cc.yellow(config.get_user_config(upper_name)),
76
+ )
77
+
78
+
79
+ def print_config():
80
+ print("Config values at", cc.yellow(config.get_filepath()))
81
+ print("")
82
+ print(
83
+ "To set values, just run:",
84
+ cc.yellow(config_command_example("[config name]", "[value]")),
85
+ )
86
+ print("More about at", cc.yellow(CONFIG_README_SECTION))
87
+ print("")
88
+ current_section = None
89
+ for section, option, value in config.all_values():
90
+ if current_section != section:
91
+ print("")
92
+ current_section = section
93
+
94
+ print(f"[{cc.bold(section)}]", option, "=", cc.yellow(value))
95
+
96
+
97
+ def main():
98
+ check_for_updates()
99
+
100
+ fire.Fire(run)
101
+
102
+
103
+ def run_config():
104
+ check_for_updates()
105
+
106
+ fire.Fire(
107
+ {
108
+ "set": set_config,
109
+ "get": get_config,
110
+ "print": print_config,
111
+ "reset": reset_config,
112
+ }
113
+ )
114
+
115
+
116
+ if __name__ == "__main__":
117
+ main()
gpt_pr/prdata.py ADDED
@@ -0,0 +1,238 @@
1
+ from dataclasses import dataclass
2
+ import json
3
+ import os
4
+
5
+ import tiktoken
6
+ from openai import OpenAI
7
+
8
+ from gpt_pr.gitutil import BranchInfo
9
+ from gpt_pr.config import config, CONFIG_PROJECT_REPO_URL
10
+ import gpt_pr.consolecolor as cc
11
+
12
+ TOKENIZER_RATIO = 4
13
+
14
+ DEFAULT_PR_TEMPLATE = (
15
+ "### Ref. [Link]\n\n## What was done?\n[Fill here]\n\n"
16
+ "## How was it done?\n[Fill here]\n\n"
17
+ "## How was it tested?\n[Fill here with test information from diff content or commits]"
18
+ )
19
+
20
+
21
+ def _get_pr_template():
22
+ pr_template = DEFAULT_PR_TEMPLATE
23
+
24
+ try:
25
+ github_dir = os.path.join(os.getcwd(), ".github")
26
+ github_files = os.listdir(github_dir)
27
+ pr_template_file = [
28
+ f for f in github_files if f.lower().startswith("pull_request_template")
29
+ ][0]
30
+ pr_template_file_path = os.path.join(github_dir, pr_template_file)
31
+
32
+ with open(pr_template_file_path, "r") as f:
33
+ local_pr_template = f.read()
34
+
35
+ if local_pr_template.strip() != "":
36
+ print("Found PR template at:", pr_template_file_path)
37
+ pr_template = local_pr_template
38
+ else:
39
+ print(
40
+ "Empty PR template at:",
41
+ pr_template_file_path,
42
+ "using default template.",
43
+ )
44
+ except Exception:
45
+ print("PR template not found in .github dir. Using default template.")
46
+
47
+ return pr_template
48
+
49
+
50
+ def _get_open_ai_key():
51
+ api_key = config.get_user_config("OPENAI_API_KEY")
52
+
53
+ if not api_key:
54
+ api_key = os.environ.get("OPENAI_API_KEY")
55
+
56
+ if not api_key:
57
+ print(
58
+ 'Please set "openai_api_key" config, just run:',
59
+ cc.yellow("gpt-pr-config set openai_api_key [open ai key]"),
60
+ )
61
+ raise SystemExit(1)
62
+
63
+ return api_key
64
+
65
+
66
+ def _count_tokens(text: str) -> int:
67
+ """Returns the number of tokens in a text string."""
68
+ openai_model = config.get_user_config("OPENAI_MODEL")
69
+ try:
70
+ encoding = tiktoken.encoding_for_model(openai_model)
71
+ except KeyError:
72
+ encoding = tiktoken.get_encoding("cl100k_base")
73
+
74
+ return len(encoding.encode(text))
75
+
76
+
77
+ @dataclass
78
+ class PrData:
79
+ branch_info: BranchInfo
80
+ title: str
81
+ body: str
82
+
83
+ def to_display(self):
84
+ return "\n".join(
85
+ [
86
+ f"{cc.bold('Repository')}: {cc.yellow(self.branch_info.owner)}/{cc.yellow(self.branch_info.repo)}",
87
+ f"{cc.bold('Title')}: {cc.yellow(self.title)}",
88
+ f"{cc.bold('Branch name')}: {cc.yellow(self.branch_info.branch)}",
89
+ f"{cc.bold('Base branch')}: {cc.yellow(self.branch_info.base_branch)}",
90
+ f"{cc.bold('PR Description')}:\n{self.create_body()}",
91
+ ]
92
+ )
93
+
94
+ def create_body(self):
95
+ body = self.body
96
+
97
+ if config.get_user_config("ADD_TOOL_SIGNATURE") == "true":
98
+ pr_signature = f"Generated by [GPT-PR]({CONFIG_PROJECT_REPO_URL})"
99
+ body += "\n\n---\n\n" + pr_signature
100
+
101
+ return body
102
+
103
+
104
+ functions = [
105
+ {
106
+ "name": "create_pr",
107
+ "description": "Creates a Github Pull Request",
108
+ "parameters": {
109
+ "type": "object",
110
+ "properties": {
111
+ "title": {
112
+ "type": "string",
113
+ "description": "PR title, following angular commit convention",
114
+ },
115
+ "description": {"type": "string", "description": "PR description"},
116
+ },
117
+ "required": ["title", "description"],
118
+ },
119
+ }
120
+ ]
121
+
122
+
123
+ def get_pr_data(branch_info):
124
+ client = OpenAI(api_key=_get_open_ai_key())
125
+
126
+ messages = _get_messages(branch_info)
127
+
128
+ openai_model = config.get_user_config("OPENAI_MODEL")
129
+ print("Using OpenAI model:", cc.yellow(openai_model))
130
+
131
+ chat_completion = client.chat.completions.create(
132
+ messages=messages,
133
+ model=openai_model,
134
+ functions=functions,
135
+ function_call={"name": "create_pr"},
136
+ temperature=0,
137
+ max_tokens=1000,
138
+ top_p=1,
139
+ frequency_penalty=0,
140
+ presence_penalty=0,
141
+ )
142
+
143
+ arguments = _parse_json(chat_completion.choices[0].message.function_call.arguments)
144
+
145
+ return PrData(
146
+ branch_info=branch_info, title=arguments["title"], body=arguments["description"]
147
+ )
148
+
149
+
150
+ def _get_messages(branch_info):
151
+ system_content = (
152
+ "You are a development assistant designed to craft Git pull requests "
153
+ "by incorporating information from main and secondary commits, diff changes, "
154
+ "and adhering to a provided PR template. Your output includes a complete PR "
155
+ "template with all necessary details and a suitable PR title. In the "
156
+ "PR description, detail the work accomplished, the methodology employed, "
157
+ "including testing procedures, and list significant changes in bullet points "
158
+ "if they are extensive. Avoid incorporating diff content directly into "
159
+ "the PR description."
160
+ )
161
+
162
+ messages = [
163
+ {"role": "system", "content": system_content},
164
+ ]
165
+
166
+ if len(branch_info.highlight_commits) > 0:
167
+ messages.append(
168
+ {
169
+ "role": "user",
170
+ "content": "main commits: " + "\n".join(branch_info.highlight_commits),
171
+ }
172
+ )
173
+ messages.append(
174
+ {
175
+ "role": "user",
176
+ "content": "secondary commits: " + "\n".join(branch_info.commits),
177
+ }
178
+ )
179
+ else:
180
+ messages.append(
181
+ {
182
+ "role": "user",
183
+ "content": "git commits: \n" + "\n".join(branch_info.commits),
184
+ }
185
+ )
186
+
187
+ messages.append({"role": "user", "content": "PR template:\n" + _get_pr_template()})
188
+
189
+ joined_messages = "\n".join([m["content"] for m in messages])
190
+ current_total_tokens = _count_tokens(joined_messages)
191
+
192
+ input_max_tokens = int(config.get_user_config("INPUT_MAX_TOKENS"))
193
+
194
+ if current_total_tokens > input_max_tokens:
195
+ exp_message = (
196
+ f"Length of {current_total_tokens} tokens for basic prompt "
197
+ f"(description and commits) is greater than max tokens {input_max_tokens} "
198
+ "(config 'input_max_tokens')"
199
+ )
200
+ raise Exception(exp_message)
201
+
202
+ total_tokens_with_diff = current_total_tokens + _count_tokens(branch_info.diff)
203
+ if total_tokens_with_diff > input_max_tokens:
204
+ print_msg = (
205
+ f"Length git changes with diff is too big (total is {total_tokens_with_diff}, "
206
+ f"'input_max_tokens' config is {input_max_tokens})."
207
+ )
208
+ print(print_msg, cc.red("Skipping changes diff content..."))
209
+ else:
210
+ messages.append(
211
+ {"role": "user", "content": "Diff changes:\n" + branch_info.diff}
212
+ )
213
+
214
+ return messages
215
+
216
+
217
+ def _parse_json(content):
218
+ """
219
+ A bit of a hack to parse the json content from the chat completion
220
+ Sometimes it returns a string with invalid json content (line breaks) that
221
+ makes it hard to parse.
222
+ example:
223
+
224
+ content = '{\n"title": "feat(dependencies): pin dependencies versions",\n"description":
225
+ "### Ref. [Link]\n\n## What was done? ..."\n}'
226
+ """
227
+
228
+ try:
229
+ content = content.replace('{\n"title":', '{"title":')
230
+ content = content.replace(',\n"description":', ',"description":')
231
+ content = content.replace("\n}", "}")
232
+ content = content.replace("\n", "\\n")
233
+
234
+ return json.loads(content)
235
+ except Exception as e:
236
+ print("Error to decode message:", e)
237
+ print("Content:", content)
238
+ raise e
@@ -4,39 +4,43 @@ import json
4
4
  from datetime import datetime
5
5
  from unittest.mock import patch, mock_open
6
6
 
7
- from gptpr.version import __version__
8
- from gptpr.checkversion import (get_latest_version, load_cache,
9
- save_cache, check_for_updates,
10
- CACHE_DURATION)
7
+ from gpt_pr import __version__
8
+ from gpt_pr.checkversion import (
9
+ get_latest_version,
10
+ load_cache,
11
+ save_cache,
12
+ check_for_updates,
13
+ CACHE_DURATION,
14
+ )
11
15
 
12
16
 
13
17
  @pytest.fixture
14
18
  def mock_requests_get(mocker):
15
- return mocker.patch('requests.get')
19
+ return mocker.patch("requests.get")
16
20
 
17
21
 
18
22
  @pytest.fixture
19
23
  def mock_os_path_exists(mocker):
20
- return mocker.patch('os.path.exists')
24
+ return mocker.patch("os.path.exists")
21
25
 
22
26
 
23
27
  @pytest.fixture
24
28
  def mock_open_file(mocker):
25
- return mocker.patch('builtins.open', mock_open())
29
+ return mocker.patch("builtins.open", mock_open())
26
30
 
27
31
 
28
32
  @pytest.fixture
29
33
  def mock_datetime(mocker):
30
- return mocker.patch('gptpr.checkversion.datetime')
34
+ return mocker.patch("gpt_pr.checkversion.datetime")
31
35
 
32
36
 
33
37
  def test_get_latest_version(mock_requests_get, mock_os_path_exists):
34
38
  mock_os_path_exists.return_value = False
35
39
  mock_response = mock_requests_get.return_value
36
40
  mock_response.raise_for_status.return_value = None
37
- mock_response.json.return_value = {'info': {'version': '2.0.0'}}
41
+ mock_response.json.return_value = {"info": {"version": "2.0.0"}}
38
42
 
39
- assert get_latest_version() == '2.0.0'
43
+ assert get_latest_version() == "2.0.0"
40
44
 
41
45
 
42
46
  def test_get_latest_version_error(mock_requests_get, mock_os_path_exists):
@@ -48,13 +52,12 @@ def test_get_latest_version_error(mock_requests_get, mock_os_path_exists):
48
52
 
49
53
  def test_load_cache(mock_os_path_exists, mock_open_file):
50
54
  mock_os_path_exists.return_value = True
51
- mock_open_file.return_value.read.return_value = json.dumps({
52
- 'last_checked': datetime.now().isoformat(),
53
- 'latest_version': '2.0.0'
54
- })
55
+ mock_open_file.return_value.read.return_value = json.dumps(
56
+ {"last_checked": datetime.now().isoformat(), "latest_version": "2.0.0"}
57
+ )
55
58
 
56
59
  cache = load_cache()
57
- assert cache['latest_version'] == '2.0.0'
60
+ assert cache["latest_version"] == "2.0.0"
58
61
 
59
62
 
60
63
  def test_load_cache_no_file(mock_os_path_exists):
@@ -65,47 +68,51 @@ def test_load_cache_no_file(mock_os_path_exists):
65
68
 
66
69
 
67
70
  def test_save_cache(mock_open_file):
68
- data = {
69
- 'last_checked': datetime.now().isoformat(),
70
- 'latest_version': '2.0.0'
71
- }
71
+ data = {"last_checked": datetime.now().isoformat(), "latest_version": "2.0.0"}
72
72
 
73
73
  save_cache(data)
74
74
  mock_open_file.return_value.write.assert_called_once_with(json.dumps(data))
75
75
 
76
76
 
77
- def test_check_for_updates_new_version(mocker, mock_datetime, mock_requests_get, mock_open_file):
77
+ def test_check_for_updates_new_version(
78
+ mocker, mock_datetime, mock_requests_get, mock_open_file
79
+ ):
78
80
  # Set up mocks
79
81
  last_checked_str = (datetime(2024, 1, 1) - CACHE_DURATION).isoformat()
80
82
  mock_datetime.now.return_value = datetime(2024, 1, 2)
81
83
  mock_datetime.fromisoformat.return_value = datetime.fromisoformat(last_checked_str)
82
- mock_open_file.return_value.read.return_value = json.dumps({
83
- 'last_checked': last_checked_str,
84
- 'latest_version': '1.0.0'
85
- })
84
+ mock_open_file.return_value.read.return_value = json.dumps(
85
+ {"last_checked": last_checked_str, "latest_version": "1.0.0"}
86
+ )
86
87
  mock_requests_get.return_value.raise_for_status.return_value = None
87
- mock_requests_get.return_value.json.return_value = {'info': {'version': '2.0.0'}}
88
+ mock_requests_get.return_value.json.return_value = {"info": {"version": "2.0.0"}}
88
89
 
89
90
  # Capture the print statements
90
- with patch('builtins.print') as mocked_print:
91
+ with patch("builtins.print") as mocked_print:
91
92
  check_for_updates()
92
93
  assert mocked_print.call_count == 3
93
94
 
94
95
 
95
- def test_check_for_updates_no_new_version(mocker, mock_datetime, mock_requests_get, mock_open_file):
96
+ def test_check_for_updates_no_new_version(
97
+ mocker, mock_datetime, mock_requests_get, mock_open_file
98
+ ):
96
99
  # Set up mocks
97
100
  last_checked_str = (datetime(2024, 1, 1) - CACHE_DURATION).isoformat()
98
101
  mock_datetime.now.return_value = datetime(2024, 1, 2)
99
102
  mock_datetime.fromisoformat.return_value = datetime.fromisoformat(last_checked_str)
100
- mock_open_file.return_value.read.return_value = json.dumps({
101
- 'last_checked': (datetime(2024, 1, 1) - CACHE_DURATION).isoformat(),
102
- 'latest_version': __version__
103
- })
103
+ mock_open_file.return_value.read.return_value = json.dumps(
104
+ {
105
+ "last_checked": (datetime(2024, 1, 1) - CACHE_DURATION).isoformat(),
106
+ "latest_version": __version__,
107
+ }
108
+ )
104
109
  mock_requests_get.return_value.raise_for_status.return_value = None
105
- mock_requests_get.return_value.json.return_value = {'info': {'version': __version__}}
110
+ mock_requests_get.return_value.json.return_value = {
111
+ "info": {"version": __version__}
112
+ }
106
113
 
107
114
  # Capture the print statements
108
- with patch('builtins.print') as mocked_print:
115
+ with patch("builtins.print") as mocked_print:
109
116
  check_for_updates()
110
117
  assert mocked_print.call_count == 0
111
118
 
@@ -115,12 +122,11 @@ def test_check_for_updates_cache_valid(mock_datetime, mock_open_file):
115
122
  last_checked_str = datetime(2024, 1, 2).isoformat()
116
123
  mock_datetime.now.return_value = datetime(2024, 1, 2)
117
124
  mock_datetime.fromisoformat.return_value = datetime.fromisoformat(last_checked_str)
118
- mock_open_file.return_value.read.return_value = json.dumps({
119
- 'last_checked': last_checked_str,
120
- 'latest_version': __version__
121
- })
125
+ mock_open_file.return_value.read.return_value = json.dumps(
126
+ {"last_checked": last_checked_str, "latest_version": __version__}
127
+ )
122
128
 
123
129
  # Capture the print statements
124
- with patch('builtins.print') as mocked_print:
130
+ with patch("builtins.print") as mocked_print:
125
131
  check_for_updates()
126
132
  assert mocked_print.call_count == 0