gpt-pr 0.2.1__py3-none-any.whl → 0.7.2__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.
- gpt_pr/__init__.py +3 -0
- gpt_pr/checkversion.py +93 -0
- gpt_pr/config.py +104 -0
- gpt_pr/gh.py +44 -0
- {gptpr → gpt_pr}/gitutil.py +26 -12
- gpt_pr/gpt.py +8 -0
- gpt_pr/main.py +117 -0
- gpt_pr/prdata.py +217 -0
- gpt_pr/test_checkversion.py +132 -0
- gpt_pr/test_config.py +138 -0
- gpt_pr/test_gh.py +60 -0
- gpt_pr/test_prdata.py +17 -0
- gpt_pr-0.7.2.dist-info/METADATA +285 -0
- gpt_pr-0.7.2.dist-info/RECORD +17 -0
- {gpt_pr-0.2.1.dist-info → gpt_pr-0.7.2.dist-info}/WHEEL +1 -2
- gpt_pr-0.7.2.dist-info/entry_points.txt +4 -0
- gpt_pr-0.2.1.dist-info/METADATA +0 -49
- gpt_pr-0.2.1.dist-info/RECORD +0 -13
- gpt_pr-0.2.1.dist-info/entry_points.txt +0 -2
- gpt_pr-0.2.1.dist-info/top_level.txt +0 -1
- gptpr/__init__.py +0 -0
- gptpr/gh.py +0 -27
- gptpr/main.py +0 -52
- gptpr/prdata.py +0 -161
- gptpr/test_prdata.py +0 -13
- gptpr/version.py +0 -1
- {gptpr → gpt_pr}/consolecolor.py +0 -0
gpt_pr-0.2.1.dist-info/METADATA
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: gpt-pr
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Automate your GitHub workflow with GPT-PR: an OpenAI powered library for streamlined PR generation.
|
|
5
|
-
Home-page: http://github.com/alissonperez/gpt-pr
|
|
6
|
-
Author: Alisson R. Perez
|
|
7
|
-
Author-email: alissonperez@outlook.com
|
|
8
|
-
License: MIT
|
|
9
|
-
Requires-Python: >=3.7
|
|
10
|
-
Requires-Dist: cffi ==1.15.1
|
|
11
|
-
Requires-Dist: cryptography ==42.0.5
|
|
12
|
-
Requires-Dist: fire ==0.6.0
|
|
13
|
-
Requires-Dist: pycparser ==2.21
|
|
14
|
-
Requires-Dist: wcwidth ==0.2.13
|
|
15
|
-
Requires-Dist: charset-normalizer ==3.3.2 ; python_full_version >= "3.7.0"
|
|
16
|
-
Requires-Dist: prompt-toolkit ==3.0.43 ; python_full_version >= "3.7.0"
|
|
17
|
-
Requires-Dist: openai ==1.14.0 ; python_full_version >= "3.7.1"
|
|
18
|
-
Requires-Dist: exceptiongroup ==1.2.0 ; python_version < "3.11"
|
|
19
|
-
Requires-Dist: cached-property ==1.5.2 ; python_version < "3.8"
|
|
20
|
-
Requires-Dist: typing-extensions ==4.7.1 ; python_version < "3.8"
|
|
21
|
-
Requires-Dist: importlib-metadata ==6.7.0 ; python_version == "3.7"
|
|
22
|
-
Requires-Dist: six ==1.16.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2"
|
|
23
|
-
Requires-Dist: deprecated ==1.2.14 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
24
|
-
Requires-Dist: idna ==3.6 ; python_version >= "3.5"
|
|
25
|
-
Requires-Dist: certifi ==2024.2.2 ; python_version >= "3.6"
|
|
26
|
-
Requires-Dist: distro ==1.9.0 ; python_version >= "3.6"
|
|
27
|
-
Requires-Dist: pynacl ==1.5.0 ; python_version >= "3.6"
|
|
28
|
-
Requires-Dist: wrapt ==1.16.0 ; python_version >= "3.6"
|
|
29
|
-
Requires-Dist: annotated-types ==0.5.0 ; python_version >= "3.7"
|
|
30
|
-
Requires-Dist: anyio ==3.7.1 ; python_version >= "3.7"
|
|
31
|
-
Requires-Dist: gitdb ==4.0.11 ; python_version >= "3.7"
|
|
32
|
-
Requires-Dist: gitpython ==3.1.42 ; python_version >= "3.7"
|
|
33
|
-
Requires-Dist: h11 ==0.14.0 ; python_version >= "3.7"
|
|
34
|
-
Requires-Dist: httpcore ==0.17.3 ; python_version >= "3.7"
|
|
35
|
-
Requires-Dist: httpx ==0.24.1 ; python_version >= "3.7"
|
|
36
|
-
Requires-Dist: pydantic ==2.5.3 ; python_version >= "3.7"
|
|
37
|
-
Requires-Dist: pydantic-core ==2.14.6 ; python_version >= "3.7"
|
|
38
|
-
Requires-Dist: pygithub ==2.2.0 ; python_version >= "3.7"
|
|
39
|
-
Requires-Dist: pyjwt[crypto] ==2.8.0 ; python_version >= "3.7"
|
|
40
|
-
Requires-Dist: requests ==2.31.0 ; python_version >= "3.7"
|
|
41
|
-
Requires-Dist: smmap ==5.0.1 ; python_version >= "3.7"
|
|
42
|
-
Requires-Dist: sniffio ==1.3.1 ; python_version >= "3.7"
|
|
43
|
-
Requires-Dist: termcolor ==2.3.0 ; python_version >= "3.7"
|
|
44
|
-
Requires-Dist: tqdm ==4.66.2 ; python_version >= "3.7"
|
|
45
|
-
Requires-Dist: urllib3 ==2.0.7 ; python_version >= "3.7"
|
|
46
|
-
Requires-Dist: zipp ==3.15.0 ; python_version >= "3.7"
|
|
47
|
-
Requires-Dist: inquirerpy ==0.3.4 ; python_version >= "3.7" and python_version < "4.0"
|
|
48
|
-
Requires-Dist: pfzy ==0.3.4 ; python_version >= "3.7" and python_version < "4.0"
|
|
49
|
-
|
gpt_pr-0.2.1.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
gptpr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
gptpr/consolecolor.py,sha256=_JmBMNjIflWMlgP2VkCWu6uQLR9oHBy52uV3TRJJgF4,800
|
|
3
|
-
gptpr/gh.py,sha256=aunn_8wi4pGoGkjdqyA2MZVAAxfItpnnmDCp20-i-WA,786
|
|
4
|
-
gptpr/gitutil.py,sha256=NBD3iRnbFEPRU47w7c5TowwtZieDYkU4zybvv0PoOU0,5783
|
|
5
|
-
gptpr/main.py,sha256=rkalqLcc1Nh5WH51w7ayEMIYNoScrRBNVYl3KLuZFdY,1270
|
|
6
|
-
gptpr/prdata.py,sha256=y4VodgCiSaARPXzJHsg32-cqrR_nZqyyGSLij6ee-oo,5846
|
|
7
|
-
gptpr/test_prdata.py,sha256=rSJ-yqOdw-iYdBWyqnA2SXbdrhT8KgIkRTTf9SY1S1g,474
|
|
8
|
-
gptpr/version.py,sha256=HfjVOrpTnmZ-xVFCYSVmX50EXaBQeJteUHG-PD6iQs8,22
|
|
9
|
-
gpt_pr-0.2.1.dist-info/METADATA,sha256=rJ-4clCl3pJh99vscyuuVQsLt4XQzqpgf4K67YYMZJY,2637
|
|
10
|
-
gpt_pr-0.2.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
11
|
-
gpt_pr-0.2.1.dist-info/entry_points.txt,sha256=aXCkyNdoUHfSJXVhRKHj8m09twDfcDmY7xC66u5N3hE,43
|
|
12
|
-
gpt_pr-0.2.1.dist-info/top_level.txt,sha256=DZcbzlsjh4BD8njGcvhOeCZ83U_oYWgCn0w8qx5--04,6
|
|
13
|
-
gpt_pr-0.2.1.dist-info/RECORD,,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
gptpr
|
gptpr/__init__.py
DELETED
|
File without changes
|
gptpr/gh.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from github import Github
|
|
3
|
-
from InquirerPy import inquirer
|
|
4
|
-
|
|
5
|
-
GH_TOKEN = os.environ.get('GH_TOKEN')
|
|
6
|
-
|
|
7
|
-
if not GH_TOKEN:
|
|
8
|
-
print("Please set GH_TOKEN environment variable")
|
|
9
|
-
exit(1)
|
|
10
|
-
|
|
11
|
-
gh = Github(GH_TOKEN)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def create_pr(pr_data, yield_confirmation):
|
|
15
|
-
repo = gh.get_repo(
|
|
16
|
-
f'{pr_data.branch_info.owner}/{pr_data.branch_info.repo}')
|
|
17
|
-
|
|
18
|
-
pr_confirmation = yield_confirmation or inquirer.confirm(
|
|
19
|
-
message="Create GitHub PR?",
|
|
20
|
-
default=True).execute()
|
|
21
|
-
|
|
22
|
-
if pr_confirmation:
|
|
23
|
-
pr = repo.create_pull(title=pr_data.title, body=pr_data.body,
|
|
24
|
-
head=pr_data.branch_info.branch, base=pr_data.branch_info.base_branch)
|
|
25
|
-
print("Pull request created successfully: ", pr.html_url)
|
|
26
|
-
else:
|
|
27
|
-
print('cancelling...')
|
gptpr/main.py
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import fire
|
|
2
|
-
from InquirerPy import inquirer
|
|
3
|
-
|
|
4
|
-
from gptpr.gitutil import get_branch_info
|
|
5
|
-
from gptpr.gh import create_pr
|
|
6
|
-
from gptpr.prdata import get_pr_data
|
|
7
|
-
from gptpr.version import __version__
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def run(base_branch='main', yield_confirmation=False, version=False):
|
|
11
|
-
'''
|
|
12
|
-
Create Pull Requests from current branch with base branch (default 'main' branch)
|
|
13
|
-
'''
|
|
14
|
-
|
|
15
|
-
if version:
|
|
16
|
-
print('Current version:', __version__)
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
branch_info = get_branch_info(base_branch, yield_confirmation)
|
|
20
|
-
|
|
21
|
-
if not branch_info:
|
|
22
|
-
exit(0)
|
|
23
|
-
|
|
24
|
-
pr_data = None
|
|
25
|
-
generate_pr_data = True
|
|
26
|
-
while generate_pr_data:
|
|
27
|
-
pr_data = get_pr_data(branch_info)
|
|
28
|
-
print('')
|
|
29
|
-
print('#########################################')
|
|
30
|
-
print(pr_data.to_display())
|
|
31
|
-
print('#########################################')
|
|
32
|
-
print('')
|
|
33
|
-
|
|
34
|
-
if yield_confirmation:
|
|
35
|
-
break
|
|
36
|
-
|
|
37
|
-
generate_pr_data = not inquirer.confirm(
|
|
38
|
-
message="Create PR with this? If 'no', let's try again...",
|
|
39
|
-
default=True).execute()
|
|
40
|
-
|
|
41
|
-
if generate_pr_data:
|
|
42
|
-
print('Generating another PR data...')
|
|
43
|
-
|
|
44
|
-
create_pr(pr_data, yield_confirmation)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def main():
|
|
48
|
-
fire.Fire(run)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if __name__ == '__main__':
|
|
52
|
-
main()
|
gptpr/prdata.py
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
2
|
-
import json
|
|
3
|
-
import os
|
|
4
|
-
from openai import OpenAI
|
|
5
|
-
|
|
6
|
-
from gptpr.gitutil import BranchInfo
|
|
7
|
-
import gptpr.consolecolor as cc
|
|
8
|
-
|
|
9
|
-
TOKENIZER_RATIO = 4
|
|
10
|
-
MAX_TOKENS = 6000
|
|
11
|
-
|
|
12
|
-
DEFAULT_PR_TEMPLATE = ('### Ref. [Link]\n\n## What was done?\n[Fill here]\n\n'
|
|
13
|
-
'## How was it done?\n[Fill here]\n\n'
|
|
14
|
-
'## How was it tested?\n[Fill here with test information from diff content or commits]')
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _get_pr_template():
|
|
18
|
-
pr_template = DEFAULT_PR_TEMPLATE
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
github_dir = os.path.join(os.getcwd(), '.github')
|
|
22
|
-
github_files = os.listdir(github_dir)
|
|
23
|
-
pr_template_file = [f for f in github_files if f.lower().startswith('pull_request_template')][0]
|
|
24
|
-
pr_template_file_path = os.path.join(github_dir, pr_template_file)
|
|
25
|
-
|
|
26
|
-
with open(pr_template_file_path, 'r') as f:
|
|
27
|
-
local_pr_template = f.read()
|
|
28
|
-
|
|
29
|
-
if local_pr_template.strip() != '':
|
|
30
|
-
print('Found PR template at:', pr_template_file_path)
|
|
31
|
-
pr_template = local_pr_template
|
|
32
|
-
else:
|
|
33
|
-
print('Empty PR template at:', pr_template_file_path, 'using default template.')
|
|
34
|
-
except Exception:
|
|
35
|
-
print('PR template not found in .github dir. Using default template.')
|
|
36
|
-
|
|
37
|
-
return pr_template
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass
|
|
41
|
-
class PrData():
|
|
42
|
-
branch_info: BranchInfo
|
|
43
|
-
title: str
|
|
44
|
-
body: str
|
|
45
|
-
|
|
46
|
-
def to_display(self):
|
|
47
|
-
return '\n'.join([
|
|
48
|
-
f'{cc.bold("Repository")}: {cc.yellow(self.branch_info.owner)}/{cc.yellow(self.branch_info.repo)}',
|
|
49
|
-
f'{cc.bold("Title")}: {cc.yellow(self.title)}',
|
|
50
|
-
f'{cc.bold("Branch name")}: {cc.yellow(self.branch_info.branch)}',
|
|
51
|
-
f'{cc.bold("Base branch")}: {cc.yellow(self.branch_info.base_branch)}',
|
|
52
|
-
f'{cc.bold("PR Description")}:\n{self.body}',
|
|
53
|
-
])
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
functions = [
|
|
57
|
-
{
|
|
58
|
-
"name": "create_pr",
|
|
59
|
-
"description": "Creates a Github Pull Request",
|
|
60
|
-
"parameters": {
|
|
61
|
-
"type": "object",
|
|
62
|
-
"properties": {
|
|
63
|
-
"title": {
|
|
64
|
-
"type": "string",
|
|
65
|
-
"description": "PR title, following angular commit convention"
|
|
66
|
-
},
|
|
67
|
-
"description": {
|
|
68
|
-
"type": "string",
|
|
69
|
-
"description": "PR description"
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
"required": ["title", "description"]
|
|
73
|
-
},
|
|
74
|
-
}
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def get_pr_data(branch_info):
|
|
79
|
-
system_content = ('You are a development assistant designed to craft Git pull requests '
|
|
80
|
-
'by incorporating information from main and secondary commits, diff changes, '
|
|
81
|
-
'and adhering to a provided PR template. Your output includes a complete PR '
|
|
82
|
-
'template with all necessary details and a suitable PR title. In the '
|
|
83
|
-
'PR description, detail the work accomplished, the methodology employed, '
|
|
84
|
-
'including testing procedures, and list significant changes in bullet points '
|
|
85
|
-
'if they are extensive. Avoid incorporating diff content directly into '
|
|
86
|
-
'the PR description.')
|
|
87
|
-
|
|
88
|
-
messages = [
|
|
89
|
-
{'role': 'system', 'content': system_content},
|
|
90
|
-
]
|
|
91
|
-
|
|
92
|
-
if len(branch_info.highlight_commits) > 0:
|
|
93
|
-
messages.append({'role': 'user', 'content': 'main commits: ' + '\n'.join(branch_info.highlight_commits)})
|
|
94
|
-
messages.append({'role': 'user', 'content': 'secondary commits: ' + '\n'.join(branch_info.commits)})
|
|
95
|
-
else:
|
|
96
|
-
messages.append({'role': 'user', 'content': 'git commits: ' + '\n'.join(branch_info.commits)})
|
|
97
|
-
|
|
98
|
-
messages.append({'role': 'user', 'content': 'PR template:\n' + _get_pr_template()})
|
|
99
|
-
|
|
100
|
-
current_total_length = sum([len(m['content']) for m in messages])
|
|
101
|
-
|
|
102
|
-
if current_total_length / TOKENIZER_RATIO > MAX_TOKENS:
|
|
103
|
-
raise Exception(f'Current total length {current_total_length} is greater than max tokens {MAX_TOKENS}')
|
|
104
|
-
|
|
105
|
-
total_length_with_diff = current_total_length + len(branch_info.diff)
|
|
106
|
-
if total_length_with_diff / TOKENIZER_RATIO > MAX_TOKENS:
|
|
107
|
-
print('Total content length (with diff) is too big.', cc.red('Skipping diff content...'))
|
|
108
|
-
else:
|
|
109
|
-
messages.append({'role': 'user', 'content': 'Diff changes:\n' + branch_info.diff})
|
|
110
|
-
|
|
111
|
-
openai_api_key = os.environ.get('OPENAI_API_KEY')
|
|
112
|
-
|
|
113
|
-
if not openai_api_key:
|
|
114
|
-
print("Please set OPENAI_API_KEY environment variable.")
|
|
115
|
-
exit(1)
|
|
116
|
-
|
|
117
|
-
client = OpenAI(api_key=openai_api_key)
|
|
118
|
-
|
|
119
|
-
chat_completion = client.chat.completions.create(
|
|
120
|
-
messages=messages,
|
|
121
|
-
model='gpt-4-0613',
|
|
122
|
-
functions=functions,
|
|
123
|
-
function_call={'name': 'create_pr'},
|
|
124
|
-
temperature=0,
|
|
125
|
-
max_tokens=512,
|
|
126
|
-
top_p=1,
|
|
127
|
-
frequency_penalty=0,
|
|
128
|
-
presence_penalty=0
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
arguments = _parse_json(chat_completion.choices[0].message.function_call.arguments)
|
|
132
|
-
|
|
133
|
-
return PrData(
|
|
134
|
-
branch_info=branch_info,
|
|
135
|
-
title=arguments['title'],
|
|
136
|
-
body=arguments['description']
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _parse_json(content):
|
|
141
|
-
'''
|
|
142
|
-
A bit of a hack to parse the json content from the chat completion
|
|
143
|
-
Sometimes it returns a string with invalid json content (line breaks) that
|
|
144
|
-
makes it hard to parse.
|
|
145
|
-
example:
|
|
146
|
-
|
|
147
|
-
content = '{\n"title": "feat(dependencies): pin dependencies versions",\n"description":
|
|
148
|
-
"### Ref. [Link]\n\n## What was done? ..."\n}'
|
|
149
|
-
'''
|
|
150
|
-
|
|
151
|
-
try:
|
|
152
|
-
content = content.replace('{\n"title":', '{"title":')
|
|
153
|
-
content = content.replace(',\n"description":', ',"description":')
|
|
154
|
-
content = content.replace('\n}', '}')
|
|
155
|
-
content = content.replace('\n', '\\n')
|
|
156
|
-
|
|
157
|
-
return json.loads(content)
|
|
158
|
-
except Exception as e:
|
|
159
|
-
print('Error to decode message:', e)
|
|
160
|
-
print('Content:', content)
|
|
161
|
-
raise e
|
gptpr/test_prdata.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from gptpr.prdata import _parse_json
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_parse_json():
|
|
5
|
-
content = ('{\n"title": "feat(dependencies): pin dependencies '
|
|
6
|
-
'versions",\n"description": "### Ref. [Link]\n\n## What was done? ..."\n}')
|
|
7
|
-
|
|
8
|
-
expected = {
|
|
9
|
-
'title': 'feat(dependencies): pin dependencies versions',
|
|
10
|
-
'description': '### Ref. [Link]\n\n## What was done? ...'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
assert _parse_json(content) == expected, "The function did not return the expected output."
|
gptpr/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.1"
|
{gptpr → gpt_pr}/consolecolor.py
RENAMED
|
File without changes
|