├── .gitignore ├── Makefile ├── README.md ├── gpt_py_review ├── __init__.py └── extract.py ├── requirements.txt ├── setup.py └── test.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | test.*.* 4 | __pycache__ 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VENV = .venv 2 | PYTHON = $(VENV)/bin/python3 3 | PIP = $(VENV)/bin/pip3 4 | 5 | include .env 6 | export 7 | 8 | $(VENV)/bin/activate: requirements.txt 9 | python3 -m venv $(VENV) 10 | $(PIP) install -r requirements.txt 11 | 12 | test: $(VENV)/bin/activate 13 | $(PYTHON) test.py 14 | 15 | clean: 16 | rm -rf __pycache__ 17 | rm -rf $(VENV) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Review your code with chatgpt 2 | 3 | ## Install 4 | pip install git+https://github.com/hunkim/gpt-py-review.git 5 | 6 | ## Review 7 | Set enviroment variables: `OPENAI_API_KEY` and `GPT_ENGINE`. 8 | If necessary, also set `REVIEW_PROMPT` and/or `TESTCASE_PROMPT`. 9 | 10 | 11 | Then run `gpt-py-review yourcode.py` 12 | 13 | It will create review comment in md and test cases in test.py. 14 | 15 | ## Example 16 | ### Original Code 17 | ```python 18 | # Factorial of a number using recursion 19 | 20 | def recur_factorial(n): 21 | if n == 1: 22 | return n 23 | else: 24 | return n*recur_factorial(n-1) 25 | 26 | num = 7 27 | 28 | # check if the number is negative 29 | if num < 0: 30 | print("Sorry, factorial does not exist for negative numbers") 31 | elif num == 0: 32 | print("The factorial of 0 is 1") 33 | else: 34 | print("The factorial of", num, "is", recur_factorial(num)) 35 | 36 | ``` 37 | 38 | ### Reviews 39 | **[DOC] It would be better to include the name and the purpose of the function in the comment.** 40 | **Calculates the factorial of a given number using recursion** 41 | 42 | **[BUG] Test cases need to be included to make sure the function is working as expected.** 43 | 44 | **[REFACTOR] It appears that the function is checking if n is equal to 1. However, it would be better to include a condition to check if n is less than or equal to 1 in order to handle the case where the input is 0 or less which would result in an error in the current implementation.** 45 | 46 | ```python 47 | def recur_factorial(n): 48 | if n <= 1: 49 | return 1 50 | else: 51 | return n*recur_factorial(n-1) 52 | ``` 53 | **[Doc] Add test cases to verify the fixed implementation** 54 | ```python 55 | def test_recur_factorial(): 56 | assert recur_factorial(0) == 1 # [TEST] 0! is 1 57 | assert recur_factorial(1) == 1 # [TEST] 1! is 1 58 | assert recur_factorial(2) == 2 # [TEST] 2! is 2 59 | assert recur_factorial(3) == 6 # [TEST] 3! is 6 60 | assert recur_factorial(5) == 120 # [TEST] 5! is 120 61 | 62 | test_recur_factorial() 63 | ``` 64 | 65 | ### Generated Test cases 66 | ```python 67 | #Test case :1 the given number is equals to 1 and output must be 1 68 | def test_recur_factorial_1(): 69 | assert recur_factorial(1) == 1 70 | 71 | #Test case :2 the given number is 6 and output must be 720 72 | def test_recur_factorial_2(): 73 | assert recur_factorial(6) == 720 74 | 75 | #Test case :3 the given number is 5 and output must be 120 76 | def test_recur_factorial_3(): 77 | assert recur_factorial(5) == 120 78 | 79 | #Test case :4 the given number is negative integer 80 | def test_recur_factorial_4(): 81 | assert recur_factorial(-5) == "It shoud not have negative interger" 82 | 83 | #Test case :5 the given number is 0 84 | def test_recur_factorial_5(): 85 | assert recur_factorial(0) == 1 86 | ``` 87 | ## Content Right 88 | The code and generated comments, test cases will be used by openai. Please do not use in commercial code. 89 | 90 | From: https://openai.com/terms/ 91 | (a) Your Content. You may provide input to the Services (“Input”), and receive output generated and returned by the Services based on the Input (“Output”). Input and Output are collectively “Content.” As between the parties and to the extent permitted by applicable law, you own all Input, and subject to your compliance with these Terms, OpenAI hereby assigns to you all its right, title and interest in and to Output. OpenAI may use Content as necessary to provide and maintain the Services, comply with applicable law, and enforce our policies. You are responsible for Content, including for ensuring that it does not violate any applicable law or these Terms. 92 | -------------------------------------------------------------------------------- /gpt_py_review/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is left empty on purpose 2 | -------------------------------------------------------------------------------- /gpt_py_review/extract.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import os 3 | import sys 4 | import openai 5 | from ast import FunctionDef, ClassDef, AsyncFunctionDef 6 | 7 | openai.api_key = os.environ["OPENAI_API_KEY"] 8 | GPT_ENGINE = os.getenv("GPT_ENGINE", "text-davinci-003") 9 | 10 | REVIEW_PROMPT = """ 11 | Please review the following code and provide comments. 12 | You can use markdown to format your comments and code to make it more readable 13 | You can also use the following tags to indicate the type of comment: 14 | [BUG], [REFACTOR], [STYLE], [DOC], [TEST], [MISC].\n\n\n 15 | """ 16 | 17 | TESTCASE_PROMPT = """ 18 | Please write pytest case code for the following function. 19 | Think of corner cases and get creative and write complete pytest test code! 20 | Put text expnation in comment with #\n\n\n 21 | """ 22 | 23 | 24 | def get_token_count(*strings): 25 | return sum([len(string) for string in strings])*2 26 | 27 | 28 | def get_gpt3_response(text, prompt=REVIEW_PROMPT): 29 | try: 30 | results = openai.Completion.create( 31 | engine=GPT_ENGINE, 32 | prompt=prompt + "\n" + text, 33 | n=1, 34 | max_tokens=4000 - get_token_count(prompt) - get_token_count(text), 35 | ) 36 | # https://sharegpt.com/c/fZUq6nU 37 | answer = results['choices'][0]['text'] 38 | # remove "Askup: " or "Askup :" 39 | answer = answer.rstrip() 40 | answer = answer.rstrip("<|im_end|>") 41 | return answer 42 | except (KeyError, IndexError) as e: 43 | return "GPT3 Return Error: " + str(e) 44 | except Exception as e: 45 | return "GPT3 Error: " + str(e) 46 | 47 | 48 | def open_file(filename, suffix=".comments", ext=None): 49 | base, ext_org = os.path.splitext(filename) 50 | if ext is None: 51 | ext = ext_org 52 | 53 | outfile = base + suffix + ext 54 | 55 | if os.path.exists(outfile): 56 | i = 2 57 | while os.path.exists(f"{base}{suffix}_{i}{ext}"): 58 | i += 1 59 | outfile = f"{base}{suffix}_{i}{ext}" 60 | 61 | return open(outfile, "w") 62 | 63 | 64 | def get_gpt(lines, prompt=REVIEW_PROMPT): 65 | code = "".join(lines) 66 | return get_gpt3_response(code, prompt=prompt) 67 | 68 | 69 | def extract_functions(code): 70 | functions = {} 71 | tree = None 72 | try: 73 | tree = ast.parse(code) 74 | except SyntaxError as e: 75 | return f"Error: Could not parse code ({e})" 76 | 77 | for node in ast.walk(tree): 78 | if isinstance(node, (FunctionDef, ClassDef, AsyncFunctionDef)): 79 | functions[node.name] = code.split( 80 | '\n')[node.lineno-1:node.body[-1].lineno] 81 | 82 | return functions 83 | 84 | 85 | def main(): 86 | 87 | if len(sys.argv) < 2: 88 | print("Usage: python script.py filename") 89 | return 90 | 91 | filename = sys.argv[1] 92 | # rest of your code 93 | 94 | with open(filename, "r") as f: 95 | code = f.read() 96 | 97 | review_file = open_file(filename, suffix=".review", ext=".md") 98 | test_file = open_file(filename, suffix=".test", ext=".py") 99 | 100 | functions = extract_functions(code) 101 | for function_name in functions: 102 | print("Working on", function_name) 103 | function_body = "\n".join(functions[function_name]) 104 | gpt_comment = get_gpt(function_body, prompt=REVIEW_PROMPT) 105 | review_file.writelines(gpt_comment) 106 | 107 | gpt_test = get_gpt(function_body, prompt=TESTCASE_PROMPT) 108 | test_file.writelines(gpt_test) 109 | 110 | review_file.close() 111 | test_file.close() 112 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.28.2 2 | openai==0.26.1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='gpt-py-review', 5 | version='0.1', 6 | packages=find_packages(), 7 | install_requires=[ 8 | 'openai', 9 | 'requests' 10 | ], 11 | entry_points={ 12 | 'console_scripts': [ 13 | 'gpt-py-review=gpt_py_review.extract:main', 14 | ], 15 | } 16 | ) 17 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | from gpt_py_review.extract import main 2 | 3 | functions = main("test.py") 4 | print(functions) --------------------------------------------------------------------------------