Skip to content

Commit

Permalink
feat(config): add configuration management and update documentation (#26
Browse files Browse the repository at this point in the history
)

* feat(config): add config file

* fix(config): change to ensure config default keys exists

* feat(config): add gh_token to config and remove unused parts

* feat: add config command

* test: change test makefile to be verbose

* docs(config): add config reset command
  • Loading branch information
alissonperez authored May 19, 2024
1 parent c2fbefb commit 60eef33
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 44 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ publish: build

lint:
pipenv run flake8 .

test:
pipenv run pytest -vv .
111 changes: 80 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ GPT-PR is an open-source command-line tool designed to streamline your GitHub wo

- [Features](#features)
- [Prerequisites](#prerequisites)
- [Installation and Usage](#installation-and-usage)
- [Authentication & API Keys](#authentication--api-keys)
- [Installation](#installation)
- [Configuration](#configuration)
- [Usage](#usage)
- [How to Contribute](#how-to-contribute)
- [Roadmap](#roadmap)

Expand Down Expand Up @@ -40,7 +41,7 @@ pip install -U gpt-pr

> Note: Use this command to **update** gpt-pr package to the latest version.
2. Export API keys as environment variables ([Authentication & API Keys](#authentication--api-keys)).
2. Setup API keys for GitHub and OpenAI, take a look at [Configuration](#configuration).

3. Inside the Git repository you are working on, ensure you have pushed your branch to origin, then run:

Expand All @@ -63,70 +64,118 @@ cd gpt-pr
pipenv install
```

After exporting api keys as environment variables ([Authentication & API Keys](#authentication--api-keys)), you can use GPT-PR within any git project directory. Suppose you've cloned **this project** to `~/workplace/gpt-pr`, here's how you can use it:
After setting up API keys ([Configuration](#configuration)), you can use GPT-PR within any git project directory. Suppose you've cloned **this project** to `~/workplace/gpt-pr`, here's how you can use it:

```bash
PYTHONPATH=~/workplace/gpt-pr/gpt-pr \
PIPENV_PIPFILE=~/workplace/gpt-pr/Pipfile \
pipenv run python ~/workplace/gpt-pr/gptpr/main.py --help
```

## Usage

### Generating Github Pull Requests

To create a Pull request from your current branch commits to merge with `main` branch, just run:

```
gpt-pr
```

If you would like to compare with other base branch that is not `main`, just use `-b` param:

```
gpt-pr -b my-other-branch
```

### Usage help

To show help commands:

```
gpt-pr -h
```

## Authentication & API Keys
## Configuration

### Setting up GitHub Token (`GH_TOKEN`)

GPT-PR tool will look for a `GH_TOKEN` in current shell env var OR in gpt-pr config file (at `~/.gpt-pr.ini`).

To authenticate with GitHub, generate and export a GitHub Personal Access Token:

1. Navigate to [GitHub's Personal Access Token page](https://github.com/settings/tokens).
2. Click "Generate new token."
3. Provide a description and select the required permissions `repo` for the token.
4. Click "Generate token" at the bottom of the page.
5. Copy the generated token.
6. Export it as an environment variable:
6. Set `gh_token` config running (supposing your gh token is `ghp_4Mb1QEr9gY5e8Lk3tN1KjPzX7W9z2V4HtJ2b`):

```bash
gpt-pr-config set gh_token ghp_4Mb1QEr9gY5e8Lk3tN1KjPzX7W9z2V4HtJ2b
```

Or just export it as an environment variable in your shell initializer:

```bash
export GH_TOKEN=your_generated_token_here
```

### Setting up OpenAI API Key (`OPENAI_API_KEY`)

GPT-PR tool will look for a `OPENAI_API_KEY` env var in current shell OR in gpt-pr config file (at `~/.gpt-pr.ini`).

This project needs to interact with the ChatGPT API to generate the pull request description. So, you need to generate and export an OpenAI API Key:

1. Navigate to [OpenAI's API Key page](https://platform.openai.com/signup).
2. If you don't have an account, sign up and log in.
3. Go to the API Keys section and click "Create new key."
4. Provide a description and click "Create."
5. Copy the generated API key.
6. Export it as an environment variable:
6. Set `openai_api_key` config running (supposing your openai_api_key is `QEr9gY5e8Lk3tN1KjPzX7W9z2V4Ht`):

```bash
gpt-pr-config set openai_api_key QEr9gY5e8Lk3tN1KjPzX7W9z2V4Ht
```

Or just export it as an environment variable in your shell initializer:

```bash
export OPENAI_API_KEY=your_generated_api_key_here
```

### Change OpenAI model

To change OpenAI model, just run:

```bash
gpt-pr-config set openai_model gpt-3.5-turbo
```

To see a full list of available models, access [OpenAI Models Documentation](https://platform.openai.com/docs/models)

### See all configs available

To print all default configs and what is being used, just run:

```bash
gpt-pr-config print
```

### Reset config

To reset any config to default value, just run:

```bash
gpt-pr-config reset config-name
```

Example:

```bash
gpt-pr-config reset openai_model
```

## Usage

### Generating Github Pull Requests

To create a Pull request from your current branch commits to merge with `main` branch, just run:

```
gpt-pr
```

If you would like to compare with other base branch that is not `main`, just use `-b` param:

```
gpt-pr -b my-other-branch
```

### Usage help

To show help commands:

```
gpt-pr -h
```

Output:
![image](https://github.com/alissonperez/gpt-pr/assets/756802/cc6c0ca4-5759-44ce-ad35-e4e7305b3875)

Expand Down
97 changes: 97 additions & 0 deletions gptpr/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from copy import deepcopy
import configparser
import os


def config_command_example(name, value_sample):
return f'gpt-pr-config set {name} {value_sample}'


CONFIG_README_SECTION = 'https://github.com/alissonperez/gpt-pr?tab=readme-ov-file#authentication--api-keys'


class Config:

config_filename = '.gpt-pr.ini'

_default_config = {
# Github
'GH_TOKEN': '',

# Open AI info
'OPENAI_MODEL': 'gpt-4o',
'OPENAI_API_KEY': '',
}

def __init__(self, config_dir=None):
self.default_config = deepcopy(self._default_config)
self._config_dir = config_dir or os.path.expanduser('~')
self._config = configparser.ConfigParser()
self._initialized = False

def load(self):
if self._initialized:
return

config_file_path = self.get_filepath()

if os.path.exists(config_file_path):
self._config.read(config_file_path)
self._ensure_default_values()
else:
self._config['user'] = {}
self._config['DEFAULT'] = deepcopy(self.default_config)
self.persist()

self._initialized = True

def _ensure_default_values(self):
added = False
for key, value in self.default_config.items():
if key not in self._config['DEFAULT']:
self._config['DEFAULT'][key] = value
added = True

if added:
self.persist()

def persist(self):
config_file_path = self.get_filepath()

with open(config_file_path, 'w') as configfile:
self._config.write(configfile)

def get_filepath(self):
return os.path.join(self._config_dir, self.config_filename)

def set_user_config(self, name, value):
self.load()
self._config['user'][name] = value

def reset_user_config(self, name):
self.load()
self._config['user'][name] = self.default_config[name]
self.persist()

def get_user_config(self, name):
self.load()
return self._config['user'][name]

def all_values(self):
self.load()

# iterate over all sections and values and return them in a list
result = []

# add default section
for option in self._config['DEFAULT']:
result.append(('DEFAULT', option, self._config['DEFAULT'][option]))

for section in self._config.sections():
for option in self._config[section]:
result.append((section, option, self._config[section][option]))

return result


config = Config()
20 changes: 15 additions & 5 deletions gptpr/gh.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import os
from github import Github
from InquirerPy import inquirer
from gptpr.config import config, config_command_example, CONFIG_README_SECTION

GH_TOKEN = os.environ.get('GH_TOKEN')

if not GH_TOKEN:
print("Please set GH_TOKEN environment variable")
exit(1)
def _get_gh_token():
gh_token = config.get_user_config('GH_TOKEN')
if not gh_token:
gh_token = os.environ.get('GH_TOKEN')

gh = Github(GH_TOKEN)
if not gh_token:
print('Please set "gh_token" config. Just run:',
config_command_example('gh_token', '[my gh token]'),
'more about at', CONFIG_README_SECTION)
exit(1)

return gh_token


gh = Github(_get_gh_token())


def create_pr(pr_data, yield_confirmation):
Expand Down
45 changes: 45 additions & 0 deletions gptpr/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from gptpr.gh import create_pr
from gptpr.prdata import get_pr_data
from gptpr.version import __version__
from gptpr.config import config, config_command_example, CONFIG_README_SECTION
from gptpr import consolecolor as cc


def run(base_branch='main', yield_confirmation=False, version=False):
Expand Down Expand Up @@ -44,9 +46,52 @@ def run(base_branch='main', yield_confirmation=False, version=False):
create_pr(pr_data, yield_confirmation)


def set_config(name, value):
name = name.upper()
config.set_user_config(name, value)
config.persist()

print('Config value', cc.bold(name), 'set to', cc.yellow(value))


def get_config(name):
upper_name = name.upper()
print('Config value', cc.bold(name), '=', cc.yellow(config.get_user_config(upper_name)))


def reset_config(name):
upper_name = name.upper()
config.reset_user_config(upper_name)
print('Config value', cc.bold(name), '=', cc.yellow(config.get_user_config(upper_name)))


def print_config():
print('Config values at', cc.yellow(config.get_filepath()))
print('')
print('To set values, just run:', cc.yellow(config_command_example('[config name]', '[value]')))
print('More about at', cc.yellow(CONFIG_README_SECTION))
print('')
current_section = None
for section, option, value in config.all_values():
if current_section != section:
print('')
current_section = section

print(f'[{cc.bold(section)}]', option, '=', cc.yellow(value))


def main():
fire.Fire(run)


def run_config():
fire.Fire({
'set': set_config,
'get': get_config,
'print': print_config,
'reset': reset_config
})


if __name__ == '__main__':
main()
Loading

0 comments on commit 60eef33

Please sign in to comment.