diff --git a/.bumpversion.cfg b/.bumpversion.cfg
deleted file mode 100644
index 31e5428..0000000
--- a/.bumpversion.cfg
+++ /dev/null
@@ -1,8 +0,0 @@
-[bumpversion]
-current_version = 0.2.0
-commit = True
-tag = True
-
-[bumpversion:file:setup.cfg]
-
-[bumpversion:file:hey/__init__.py]
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index d4a2c44..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,21 +0,0 @@
-# http://editorconfig.org
-
-root = true
-
-[*]
-indent_style = space
-indent_size = 4
-trim_trailing_whitespace = true
-insert_final_newline = true
-charset = utf-8
-end_of_line = lf
-
-[*.bat]
-indent_style = tab
-end_of_line = crlf
-
-[LICENSE]
-insert_final_newline = false
-
-[Makefile]
-indent_style = tab
diff --git a/README.md b/README.md
index 007ba96..f3ff0e0 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,11 @@
## Hey! - Your AI-powered Pair Programming Friend
-> :warning: - You need OpenAI auth token to make Hey work.
+> :sparkles: - You need a MindsDB token to use Hey. You can generate one for your personal uses for free from [here](mdb.ai)!
> :basecamp: - Watch this YouTube introduction video about Hey!
> :writing_hand: - Read the "Introducing Hey! - Your AI-powered Pair Programming Friend" article about the creation process, development phases, and a detailed overview of Hey.
-> :package: - Check out Hey on PyPI.
-
Hey is a CLI-based AI assistant that is powered by the ChatGPT AI model versions supported by [MindsDB](https://mindsdb.com/). This project is designed for [Hashnode X MindsDB](https://hashnode.com/hackathons/mindsdb?source=hncounter-feed) hackathon.
### Installation
@@ -26,92 +24,112 @@ pip install -U hey-mindsdb
pip install git+http://github.com/lnxpy/hey.git
```
-> :warning:: Hey is POSIX-friendly. It might not work properly on the Windows machines at the moment.
+> :warning:: Hey is POSIX-friendly. It might not work properly on Windows machines at the moment.
- 2. Set the MINDSDB_EMAIL_ADDRESS
environment variable
+ 2. Set the HEY_TOKEN
environment variable
-Once you got the package installed on your system, it's time to add the `MINDSDB_EMAIL_ADDRESS` environment variable. Create an account on [mindsdb.com](https://mindsdb.com/), train your GPT model and replace your email with `` in the following options.
+Once you got the package installed on your system, it's time to add the token that you just copied from [mdb.ai](https://mdb.ai) into either the `.bashrc` (or `.zshrc`) file.
-##### > If you use the default bash shell
+- If you use the default bash shell
```sh
-echo "export MINDSDB_EMAIL_ADDRESS=" >> ~/.bashrc
+echo "export HEY_TOKEN=" >> ~/.bashrc
```
-##### > If you use ZSH
+- If you use ZSH
```sh
-echo "export MINDSDB_EMAIL_ADDRESS=" >> ~/.zshrc
+echo "export HEY_TOKEN=" >> ~/.zshrc
```
-> :bulb:: Read the article for more information about training your MindsDB model.
-
-
- 3. Set your MindsDB account password
-
-Now, it's time to set your account's password. Simply run `hey` with the `--auth` option and enter your MindsDB account password.
+### Usage
+There are different commands and sub-commands implemented once you install `hey`. Check them out via the `--help` flag.
```sh
-hey --auth
+hey --help
```
-You're all set to go. :)
+```
+
+ Usage: hey [OPTIONS] COMMAND [ARGS]...
+
+ Hey is a pair-programming friend that interacts with ChatGPT and responds back in a pretty
+ style. ✨
+
+╭─ Options ─────────────────────────────────────────────────────────────────────────────────────╮
+│ --no-style,--ns Don't style the output. │
+│ --version -V Show the current version of Hey. │
+│ --install-completion Install completion for the current shell. │
+│ --show-completion Show completion for the current shell, to copy it or │
+│ customize the installation. │
+│ --help Show this message and exit. │
+╰───────────────────────────────────────────────────────────────────────────────────────────────╯
+╭─ Commands ────────────────────────────────────────────────────────────────────────────────────╮
+│ ask Ask Hey directly in-command. │
+│ config Configuration management. │
+╰───────────────────────────────────────────────────────────────────────────────────────────────╯
+```
-
+- If you want to use `Hey` in a fast and quick way, use the `ask` command.
-### Usage
-Use `hey` followed by your question and it'll process the phrase and responses back the content in Markdown.
+ ```sh
+ hey ask "explain the duality term in quantum physics."
+ ```
-```
-$ hey generate a power function in javascript
-To generate a power function in JavaScript, you can use the built-in Math.pow()
-method. Here's an example of how to create a power function using JavaScript:
+- If your query needs more explanations with code snippets maybe, they just `hey`.
+ ```sh
+ hey
+
+ ```
- function powerFunction(base, exponent) {
- return Math.pow(base, exponent);
- }
+ > Keep in mind that when you run `hey` with no sub-commands, the default `$EDITOR` will be used. Feel free to ask your question in markdown style.
- // Example usage:
- console.log(powerFunction(2, 3)); // Output: 8
- console.log(powerFunction(5, 2)); // Output: 25
-```
+### Configuration
+There is a command dedicated for more customizability. Check the following bullet-points.
-```
-$ hey tell me a programming joke
-Why do programmers always mix up Christmas and Halloween?
+- Create a base configuration file.
-Because Oct 31 == Dec 25!
-```
+ ```sh
+ hey config create
+ ```
-```
-$ hey add annotations to this function: $(cat file.py)
-To add annotations to the given Python function, you can include comments and
-docstrings to provide more information about the function's purpose and usage.
-Here's an example:
-
-
- # Importing the required module from setuptools package
- from setuptools import setup
-
- # Function to setup MindsDB package
- def mindsdb_setup():
- """
- This function sets up the MindsDB package using the setup() function from
- setuptools.
- """
- # Calling the setup() function to configure the package
- setup()
+- View and edit the configuration file.
+
+ ```sh
+ hey config edit
+ ```
+
+Here is more information about each configuration parameter.
+
+```json
+{
+ // model version
+ "model": "gpt-3.5-turbo",
+
+ // prompt
+ "prompt": "Answer in a helpful way.",
+
+ // themes used for the codeblocks
+ "code_block_theme": "github-light",
+
+ // how would you like `hey` to think?
+ "loading_text": "Thinking..",
+
+ // thinking animation symbol
+ // check out full list: python -m rich.spinner
+ "loading_spinner": "dots",
+
+ // never style the output (in case you need to copy the result)
+ "never_style": false
+}
```
### Tech Stack
-- Tools
- - [Python](https://python.org)
- Infrastructures & Hosting
- - [MindsDB](https://mindsdb.com)
- - [PyPI](https://pypi.org)
+ - [MindsDB](https://mdb.ai)
### Package Stats
![stats](media/stats.svg)
diff --git a/hey/__init__.py b/hey/__init__.py
index 5229706..cdc41bd 100644
--- a/hey/__init__.py
+++ b/hey/__init__.py
@@ -1,5 +1,7 @@
-"""Top-level package for Hey."""
+from rich.console import Console
-__author__ = """Sadra Yahyapour"""
-__email__ = 'lnxpylnxpy@gmail.com'
-__version__ = '0.2.0'
+console = Console()
+
+__author__ = "Sadra Yahyapour"
+__email__ = "lnxpylnxpy@gmail.com"
+__version__ = "0.3.0"
diff --git a/hey/cli.py b/hey/cli.py
index 6714e1a..9434861 100644
--- a/hey/cli.py
+++ b/hey/cli.py
@@ -1,100 +1,76 @@
-import argparse
-import os
-import sys
-from getpass import getpass
-
-import keyring
-from rich.console import Console
-
-from hey import __version__
-from hey.constants.informations import APPLICATION_DESCRIPTION
-from hey.constants.informations import EPILOG_DESCRIPTION
-from hey.constants.informations import INSTALLATION_GUIDE
-from hey.constants.informations import VERSION_INFO
-from hey.constants.service import KEYRING_SERVICE_NAME
-from hey.constants.system import LOCAL_EMAIL_ADDRESS_VARIABLE_NAME
-from hey.exceptions.system import BrokenCredentials
-from hey.exceptions.system import EmailEnvVarNotExists
-from hey.exceptions.system import KeyringIssue
-from hey.middlewares.mindsdb import MindsDB
-
-parser = argparse.ArgumentParser(
- description=APPLICATION_DESCRIPTION + '\n\r\n\r' + INSTALLATION_GUIDE,
- epilog=EPILOG_DESCRIPTION,
- formatter_class=argparse.RawDescriptionHelpFormatter,
- prog='hey',
-)
-
-parser.add_argument(
- 'ask',
- nargs='*',
- help='ask what you need',
-)
-
-parser.add_argument(
- '--version',
- action='version',
- version=VERSION_INFO.format(__version__),
-)
-
-parser.add_argument(
- '--auth',
- action='store_true',
- help='set your mindsdb account password',
-)
-
-
-def main():
- args = parser.parse_args()
- console = Console()
-
- if args.auth:
- email_address = os.environ.get(LOCAL_EMAIL_ADDRESS_VARIABLE_NAME)
- password = getpass(f'Password for ({email_address}):')
- if email_address:
- try:
- keyring.set_password(
- service_name=KEYRING_SERVICE_NAME.lower(),
- username=email_address,
- password=password,
- )
- except Exception as _:
- raise KeyringIssue(
- 'There is something wrong with your OS keyring system. Make sure you have right access to run hey '
- 'on your system. '
- )
- console.print(f'Password successfully set for {email_address}!')
- else:
- raise EmailEnvVarNotExists(f'Make sure you have defined {LOCAL_EMAIL_ADDRESS_VARIABLE_NAME} environment '
- f'variable properly.')
-
- credentials = keyring.get_credential(
- service_name=KEYRING_SERVICE_NAME.lower(),
- username=os.environ.get(LOCAL_EMAIL_ADDRESS_VARIABLE_NAME),
- )
-
- if not credentials:
- raise BrokenCredentials(
- f'Make sure you have set your {LOCAL_EMAIL_ADDRESS_VARIABLE_NAME} and password via --auth.'
+from datetime import datetime
+from typing import Annotated, Optional
+
+import typer
+from rich.markdown import Markdown
+from rich.panel import Panel
+
+from hey import __version__, console
+from hey.configs import cli, configs
+from hey.consts import APP_NAME
+from hey.editor import open_tmp_editor
+from hey.openai import answer
+
+app = typer.Typer()
+app.add_typer(cli.app, name="config")
+
+
+def version_callback(value: bool):
+ if value:
+ print(f"Hey - {__version__}!")
+ raise typer.Exit()
+
+
+@app.callback(invoke_without_command=True)
+def main(
+ ctx: typer.Context,
+ no_style: Annotated[
+ bool, typer.Option("--no-style", "--ns", help="Don't style the output.")
+ ] = configs.get("never_style"),
+ version: Annotated[
+ Optional[bool],
+ typer.Option(
+ "--version",
+ "-V",
+ callback=version_callback,
+ help=f"Show the current version of {APP_NAME}.",
+ ),
+ ] = None,
+):
+ """
+ Hey is a pair-programming friend that interacts with ChatGPT and responds back in a pretty style. ✨
+ """
+
+ if ctx.invoked_subcommand is None:
+ user_input = open_tmp_editor()
+ markdown_input = Markdown(
+ user_input, code_theme=configs.get("code_block_theme")
)
- if args.ask:
- with console.status('Creating instance..'):
- instance = MindsDB(
- email=credentials.username,
- password=credentials.password
- )
-
- with console.status('Authenticating..', spinner='dots2'):
- instance.authenticate()
+ user_panel = Panel(
+ markdown_input,
+ title=":bust_in_silhouette:",
+ title_align="left",
+ subtitle=datetime.now().strftime("%H:%M"),
+ subtitle_align="right",
+ style="blue",
+ )
- with console.status('Hey is typing..'):
- console.print(instance.answer(
- ' '.join(args.ask)
- ))
+ console.print(user_panel)
+ result = answer(user_input, no_style)
+ console.print(result)
- return 0
+@app.command()
+def ask(
+ user_input: str,
+ no_style: Annotated[
+ bool, typer.Option("--no-style", "--ns", help="Don't style the output.")
+ ] = configs.get("never_style"),
+):
+ """
+ Ask Hey directly in-command.
+ """
-if __name__ == "__main__":
- sys.exit(main())
+ result = answer(user_input, no_style)
+ console.print(result)
diff --git a/hey/configs/__init__.py b/hey/configs/__init__.py
new file mode 100644
index 0000000..5c67b73
--- /dev/null
+++ b/hey/configs/__init__.py
@@ -0,0 +1,3 @@
+from hey.configs.utils import read_configs
+
+configs = read_configs()
diff --git a/hey/configs/cli.py b/hey/configs/cli.py
new file mode 100644
index 0000000..6602e05
--- /dev/null
+++ b/hey/configs/cli.py
@@ -0,0 +1,33 @@
+import os
+import subprocess
+
+import typer
+from rich.prompt import Confirm
+
+from hey import console
+from hey.configs.utils import app_dir_exists, config_exists, init_config
+from hey.consts import APP_CONFIG_DIR, CONFIG_FILE_PATH
+
+app = typer.Typer(help="Configuration management.")
+
+
+@app.command()
+def create():
+ """Create a base config file."""
+ if not config_exists():
+ os.makedirs(APP_CONFIG_DIR)
+ init_config()
+ else:
+ if Confirm.ask(
+ "You already have a configuration file. Would you like to rewrite it?!"
+ ):
+ init_config()
+
+
+@app.command()
+def edit():
+ """View and modify the config file."""
+ if not app_dir_exists() or not config_exists():
+ console.print("You don't have any config file. Try `hey config create` first.")
+ else:
+ subprocess.run([os.environ["EDITOR"], CONFIG_FILE_PATH])
diff --git a/hey/configs/utils.py b/hey/configs/utils.py
new file mode 100644
index 0000000..dd03799
--- /dev/null
+++ b/hey/configs/utils.py
@@ -0,0 +1,39 @@
+import json
+import os
+from hey.consts import CONFIG_FILE_PATH, BASE_CONFIG, APP_CONFIG_DIR
+from hey import console
+
+
+def read_configs() -> dict:
+ """returns the user-defined config or the base config
+
+ Returns:
+ dict: config params
+ """
+
+ if os.path.exists(CONFIG_FILE_PATH):
+ try:
+ with open(CONFIG_FILE_PATH, "r") as file:
+ return json.loads(file.read())
+ except json.JSONDecodeError as e:
+ error_message = (
+ f"Your config file is [red bold]broken[/red bold]. "
+ f"Try `hey config edit` or `hey config init`. {e}"
+ )
+ console.print(error_message)
+ console.print("Using the [green bold]default settings[/green bold].")
+
+ return BASE_CONFIG
+
+
+def init_config():
+ with open(CONFIG_FILE_PATH, "w+") as conf_file:
+ conf_file.write(json.dumps(BASE_CONFIG, indent=4))
+
+
+def app_dir_exists() -> bool:
+ return os.path.exists(APP_CONFIG_DIR)
+
+
+def config_exists() -> bool:
+ return os.path.exists(CONFIG_FILE_PATH)
diff --git a/hey/constants/__init__.py b/hey/constants/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/hey/constants/informations.py b/hey/constants/informations.py
deleted file mode 100644
index db51f34..0000000
--- a/hey/constants/informations.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# package description for argparse
-APPLICATION_DESCRIPTION = 'Hey is your AI-powered pair programming friend that helps you speed up your productivity.'
-
-# link to installation guide
-INSTALLATION_GUIDE = 'Check out https://github.com/lnxpy/hey for ' \
- 'installation guide. '
-
-# epilog information
-EPILOG_DESCRIPTION = 'Hey is designed for Hashnode X MindsDB hackathon.'
-
-# `--version` option output pattern
-VERSION_INFO = '''
- _ _ _
- | || |___ _ _| |
- | __ / -_| || |_|
- |_||_\___|\_, (_)
- |__/ --> v{}
-'''
diff --git a/hey/constants/service.py b/hey/constants/service.py
deleted file mode 100644
index ebd2f73..0000000
--- a/hey/constants/service.py
+++ /dev/null
@@ -1,5 +0,0 @@
-# MindsDB cloud host address
-MINDSDB_HOST = 'https://cloud.mindsdb.com'
-
-# for keyring credential management
-KEYRING_SERVICE_NAME = 'Hey'
diff --git a/hey/constants/system.py b/hey/constants/system.py
deleted file mode 100644
index 87d264a..0000000
--- a/hey/constants/system.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""
-this file contains all system constants
-"""
-
-
-LOCAL_EMAIL_ADDRESS_VARIABLE_NAME = 'MINDSDB_EMAIL_ADDRESS'
diff --git a/hey/consts.py b/hey/consts.py
new file mode 100644
index 0000000..7ad0f1c
--- /dev/null
+++ b/hey/consts.py
@@ -0,0 +1,21 @@
+from platformdirs import user_config_path
+
+# App name
+APP_NAME = "Hey"
+
+# Service address
+SERVICE_URL = "https://llm.mdb.ai"
+
+
+# basic configuration setup
+BASE_CONFIG = {
+ "model": "gpt-3.5-turbo",
+ "prompt": "Answer in a helpful way.",
+ "code_block_theme": "github-light",
+ "loading_text": "Thinking..",
+ "loading_spinner": "dots",
+ "never_style": False,
+}
+
+APP_CONFIG_DIR = user_config_path(APP_NAME)
+CONFIG_FILE_PATH = APP_CONFIG_DIR / "config.json"
diff --git a/hey/editor.py b/hey/editor.py
new file mode 100644
index 0000000..2ce9ec8
--- /dev/null
+++ b/hey/editor.py
@@ -0,0 +1,25 @@
+import os
+import subprocess
+import tempfile
+
+from rich.console import Console
+
+logger = Console()
+
+
+def open_tmp_editor() -> str:
+ """opens the default editor (`$EDITOR`)
+
+ Returns:
+ str: input string
+ """
+
+ with tempfile.NamedTemporaryFile(mode="w+") as temp_file:
+ temp_file_path = temp_file.name
+
+ subprocess.run([os.environ["EDITOR"], temp_file_path])
+
+ with open(temp_file_path, "r") as file:
+ data = file.read()
+
+ return data
diff --git a/hey/exceptions.py b/hey/exceptions.py
new file mode 100644
index 0000000..8705d05
--- /dev/null
+++ b/hey/exceptions.py
@@ -0,0 +1,4 @@
+class TokenIsNotDefined(Exception): ...
+
+
+class ConnectionIssue(Exception): ...
diff --git a/hey/exceptions/__init__.py b/hey/exceptions/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/hey/exceptions/auth.py b/hey/exceptions/auth.py
deleted file mode 100644
index eec562c..0000000
--- a/hey/exceptions/auth.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""
-this file contains all custom authentication exceptions
-"""
-
-
-class CredentialsError(Exception): ...
diff --git a/hey/exceptions/connection.py b/hey/exceptions/connection.py
deleted file mode 100644
index 83df761..0000000
--- a/hey/exceptions/connection.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""
-network-related connection exceptions
-"""
-
-
-class NetworkError(Exception): ...
diff --git a/hey/exceptions/system.py b/hey/exceptions/system.py
deleted file mode 100644
index 23a3572..0000000
--- a/hey/exceptions/system.py
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
-this file contains custom exceptions that might happen during the regular system calling
-"""
-
-
-class EmailEnvVarNotExists(Exception): ...
-
-
-class BrokenCredentials(Exception): ...
-
-
-class KeyringIssue(Exception): ...
diff --git a/hey/middlewares/__init__.py b/hey/middlewares/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/hey/middlewares/mindsdb.py b/hey/middlewares/mindsdb.py
deleted file mode 100644
index f67aa7a..0000000
--- a/hey/middlewares/mindsdb.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from getpass import getuser
-
-import mindsdb_sdk
-from mindsdb_sdk.server import Databases, Server
-from pandas import DataFrame
-from requests.exceptions import ConnectionError, HTTPError
-from rich.markdown import Markdown
-
-from hey.constants.service import MINDSDB_HOST
-from hey.exceptions.auth import CredentialsError
-from hey.exceptions.connection import NetworkError
-from hey.templates.mindsdb_queries import SQL_ASK_QUERY
-
-
-def to_data(dataframe: DataFrame) -> str:
- """
- takes a pandas `DataFrame` and returns the first value from the first column
- Args:
- dataframe: the dataframe returned from MindsDB that stores the answer in the first cell of column
-
- Returns:
- first value of the first column of dataframe that MindsDB responses
- """
- return dataframe.iloc[:, 0].values[0]
-
-
-class MindsDB:
- """
- MindsDB manager class
- """
-
- def __init__(self, email: str, password: str) -> None:
- """
- initializer class.
- Args:
- email: MindsDB account email address (that is stored as an env var)
- password: MindsDB account password
- """
- self.email = email
- self.password = password
-
- self.is_authenticated: bool = False
- self.database: Databases
-
- def authenticate(self) -> None:
- """
- authorizes the email and password with MindsDB's host
- """
-
- try:
- server = mindsdb_sdk.connect(
- MINDSDB_HOST,
- login=self.email,
- password=self.password,
- )
- except HTTPError:
- raise CredentialsError(
- "Email or password is incorrect. Make sure to enter the right credentials."
- )
- except ConnectionError:
- raise NetworkError(
- "Make sure you have access to the internet and try again."
- )
-
- self.is_authenticated = True
- self.database = self.collect_database(server)
-
- @staticmethod
- def collect_database(server: Server) -> Databases:
- return server.list_databases()[0]
-
- def answer(self, question: str) -> Markdown:
- """
- takes the question and queries then converts the response into `rich.Markdown`
- Args:
- question: the value from `ask` positional argument
-
- Returns:
- response from MindsDB in Markdown format
- """
-
- return Markdown(
- to_data(
- self.database.query(
- SQL_ASK_QUERY.substitute(
- ask=question,
- user=getuser(),
- )
- ).fetch()
- )
- )
diff --git a/hey/openai.py b/hey/openai.py
new file mode 100644
index 0000000..e8ee89b
--- /dev/null
+++ b/hey/openai.py
@@ -0,0 +1,95 @@
+import os
+import time
+
+from openai import OpenAI, OpenAIError
+from rich.console import Console
+from rich.markdown import Markdown
+from rich.panel import Panel
+
+from hey.configs import configs
+from hey.consts import BASE_CONFIG, SERVICE_URL
+from hey.exceptions import ConnectionIssue, TokenIsNotDefined
+
+
+class Auth:
+ def __init__(self) -> None:
+ self.is_valid = False
+
+ def validate(self) -> str:
+ token = os.environ.get("HEY_TOKEN", None)
+ if token:
+ self.is_valid = True
+ return token
+ else:
+ raise TokenIsNotDefined(
+ "make sure the `HEY_TOKEN` is defined in the .bashrc/.zshrc file."
+ )
+
+
+class ChatGPT:
+ def __init__(
+ self,
+ model=configs.get("model", BASE_CONFIG["model"]),
+ prompt=configs.get("prompt", BASE_CONFIG["model"]),
+ auth: Auth = None,
+ ) -> None:
+ self.auth = auth or Auth()
+ self.token = self.auth.validate()
+ self.model = model
+ self.prompt = prompt
+
+ def ask(self, text: str) -> str:
+ if not text:
+ raise ValueError("provide a valid input.")
+ try:
+ client_mindsdb_serve = OpenAI(api_key=self.token, base_url=SERVICE_URL)
+ chat_completion_gpt = client_mindsdb_serve.chat.completions.create(
+ messages=[
+ {
+ "role": "system",
+ "content": self.prompt,
+ },
+ {"role": "user", "content": text},
+ ],
+ model=self.model,
+ )
+ return chat_completion_gpt.choices[0].message.content
+ except OpenAIError:
+ raise ConnectionIssue("an error occurred while calling the API endpoint.")
+
+
+def answer(question: str, no_style: bool) -> str | Panel:
+ """interface between commands and ChatGPT
+
+ Args:
+ question (str): question phrase
+ no_style (bool): whether returning in a panel or not
+
+ Returns:
+ str | Panel: solid raw result or paneled
+ """
+
+ with Console().status(
+ configs.get("loading_text", BASE_CONFIG["loading_text"]),
+ spinner=configs.get("loading_spinner", BASE_CONFIG["loading_spinner"]),
+ ):
+ start_time = time.time()
+ c = ChatGPT()
+ result = Markdown(
+ c.ask(question),
+ code_theme=configs.get("code_block_theme", BASE_CONFIG["code_block_theme"]),
+ )
+ end_time = time.time()
+
+ if no_style:
+ return result
+ else:
+ paneled = Panel(
+ result,
+ border_style="green",
+ title=":sparkles:",
+ subtitle=f"~{end_time-start_time:.1f}s",
+ subtitle_align="right",
+ title_align="left",
+ )
+ return paneled
diff --git a/hey/templates/__init__.py b/hey/templates/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/hey/templates/mindsdb_queries.py b/hey/templates/mindsdb_queries.py
deleted file mode 100644
index 440ddb8..0000000
--- a/hey/templates/mindsdb_queries.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from string import Template
-
-SQL_ASK_QUERY = Template('''
-SELECT response
-FROM mindsdb.gpt_model
-WHERE author_username = "$user"
-AND text = "$ask";
-''')
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..c39dc7a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,55 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "hey_mindsdb"
+version = "0.3.0"
+description = "Your AI-powered pair programming friend"
+authors = [{ name = "Sadra Yahyapour", email = "lnxpylnxpy@gmail.com" }]
+requires-python = ">=3.8"
+dependencies = [
+ "typer >= 0.12.3",
+ "rich >= 13.7.1",
+ "openai >= 1.30.1",
+ "platformdirs >= 4.2.2"
+]
+readme = { file = "README.md", content-type = "text/markdown" }
+license = { file = "LICENSE" }
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3 :: Only",
+]
+
+[project.scripts]
+hey = "hey.cli:app"
+
+[project.optional-dependencies]
+dev = ["coverage", "pytest-cookies"]
+
+[project.urls]
+Repository = "https://github.com/lnxpy/hey"
+
+[tool.hatch.build.targets.wheel]
+packages = ["hey"]
+
+[tool.bumpversion]
+current_version = "0.3.0"
+commit = "true"
+tag = "true"
+
+[[tool.bumpversion.files]]
+filename = "pyproject.toml"
+
+[[tool.bumpversion.files]]
+filename = "hey/__init__.py"
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 9985fb9..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,63 +0,0 @@
-[metadata]
-name = hey_mindsdb
-version = 0.2.0
-description = Your AI-powered pair programming friend.
-long_description = file: README.md
-long_description_content_type = text/markdown
-url = https://github.com/lnxpy/hey
-author = Sadra Yahyapour
-author_email = lnxpylnxpy@gmail.com
-license = MIT
-license_files = LICENSE
-classifiers =
- Development Status :: 4 - Beta
- Intended Audience :: Developers
- Intended Audience :: Education
- License :: OSI Approved :: MIT License
- Natural Language :: English
- Operating System :: POSIX
- Operating System :: Unix
- Programming Language :: Python :: 3
- Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: Implementation :: CPython
- Programming Language :: Python :: Implementation :: PyPy
- Topic :: Utilities
-keywords = hey
-
-[options]
-packages = find:
-install_requires =
- keyring>=23.13.1
- mindsdb-sdk>=1.0.2
- rich>=13.3.4
-python_requires = >=3.7
-include_package_data = True
-zip_safe = False
-
-[options.packages.find]
-include =
- hey
- hey.*
-
-[options.entry_points]
-console_scripts =
- hey=hey.cli:main
-
-[bumpversion]
-current_version = 0.2.0
-commit = True
-tag = True
-
-[bumpversion:file:setup.py]
-search = version='{current_version}'
-replace = version='{new_version}'
-
-[bumpversion:file:hey/__init__.py]
-search = __version__ = '{current_version}'
-replace = __version__ = '{new_version}'
-
-[bdist_wheel]
-universal = 1
-
-[flake8]
-exclude = docs
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 8bf1ba9..0000000
--- a/setup.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from setuptools import setup
-setup()
diff --git a/tests/__init__.py b/tests/__init__.py
index 4abfd02..e69de29 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1 +0,0 @@
-"""Unit test package for hey."""
diff --git a/tests/exceptions/__init__.py b/tests/exceptions/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/exceptions/test_auth.py b/tests/exceptions/test_auth.py
deleted file mode 100644
index 259d70f..0000000
--- a/tests/exceptions/test_auth.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pytest
-
-from hey.exceptions.auth import CredentialsError
-
-
-def test_credentials_error():
- with pytest.raises(CredentialsError):
- raise CredentialsError("Invalid credentials.")
diff --git a/tests/exceptions/test_connection.py b/tests/exceptions/test_connection.py
deleted file mode 100644
index 4d5f565..0000000
--- a/tests/exceptions/test_connection.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pytest
-
-from hey.exceptions.connection import NetworkError
-
-
-def test_network_error():
- with pytest.raises(NetworkError):
- raise NetworkError("Connection lost")
diff --git a/tests/exceptions/test_system.py b/tests/exceptions/test_system.py
deleted file mode 100644
index 42f4455..0000000
--- a/tests/exceptions/test_system.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import pytest
-from hey.exceptions.system import (
- EmailEnvVarNotExists,
- BrokenCredentials,
- KeyringIssue,
-)
-
-
-def test_email_env_var_not_exists_exception():
- with pytest.raises(EmailEnvVarNotExists):
- raise EmailEnvVarNotExists("Email environment variable not set.")
-
-def test_broken_credentials_exception():
- with pytest.raises(BrokenCredentials):
- raise BrokenCredentials("Invalid or broken credentials provided.")
-
-def test_keyring_issue_exception():
- with pytest.raises(KeyringIssue):
- raise KeyringIssue("Keyring service failed to execute.")
diff --git a/tests/middlewares/__init__.py b/tests/middlewares/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/middlewares/test_mindsdb.py b/tests/middlewares/test_mindsdb.py
deleted file mode 100644
index d0f16a0..0000000
--- a/tests/middlewares/test_mindsdb.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import pytest
-
-from pandas import DataFrame
-from unittest.mock import patch, MagicMock
-from hey.exceptions.auth import CredentialsError
-from hey.exceptions.connection import NetworkError
-from requests.exceptions import HTTPError, ConnectionError
-
-from hey.constants.service import MINDSDB_HOST
-from hey.middlewares.mindsdb import MindsDB
-
-
-@patch('mindsdb_sdk.connect')
-def test_authenticate(mock_connect):
- email = 'test@test.com'
- password = 'testpassword'
-
- mock_server = MagicMock()
- mock_connect.return_value = mock_server
-
- mindsdb = MindsDB(email, password)
-
- mindsdb.authenticate()
-
- mock_connect.assert_called_once_with(MINDSDB_HOST, login=email, password=password)
- mock_server.list_databases.assert_called_once()
-
- assert mindsdb.is_authenticated is True
-
-
-def test_authenticate_incorrect_password():
- mindsdb = MindsDB('test@test.com', 'testpassword')
-
- with pytest.raises(CredentialsError):
- with patch('mindsdb_sdk.connect', side_effect=HTTPError):
- mindsdb.authenticate()
-
-
-def test_authenticate_network_error():
- mindsdb = MindsDB('test@test.com', 'testpassword')
-
- with pytest.raises(NetworkError):
- with patch('mindsdb_sdk.connect', side_effect=ConnectionError):
- mindsdb.authenticate()
-
-
-def test_mindsdb_answer_exception():
- mock_database = MagicMock(spec=DataFrame)
- mock_database.query.return_value.fetch.return_value = [['test answer']]
-
- email = 'test@test.com'
- password = 'testpassword'
- mindsdb = MindsDB(email, password)
- mindsdb.database = mock_database
-
- mock_database.query.side_effect = Exception
- with pytest.raises(Exception):
- mindsdb.answer('test question')
diff --git a/tests/sample_test.py b/tests/sample_test.py
new file mode 100644
index 0000000..f57630d
--- /dev/null
+++ b/tests/sample_test.py
@@ -0,0 +1,2 @@
+def test_sample():
+ pass
diff --git a/tests/templates/__init__.py b/tests/templates/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/templates/test_mindsdb_queries.py b/tests/templates/test_mindsdb_queries.py
deleted file mode 100644
index 3dabaf3..0000000
--- a/tests/templates/test_mindsdb_queries.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from string import Template
-
-
-def test_template_values():
- user = "Benyamin Mahmoudyan"
- ask = "What is the weather like today?"
-
- sql_ask_query = Template('''
- SELECT response
- FROM mindsdb.gpt_model
- WHERE author_username = "$user"
- AND text = "$ask";
- ''')
-
- expected_output = '''
- SELECT response
- FROM mindsdb.gpt_model
- WHERE author_username = "Benyamin Mahmoudyan"
- AND text = "What is the weather like today?";
- '''
-
- assert sql_ask_query.substitute(user=user, ask=ask) == expected_output
diff --git a/tests/test_accounts.py b/tests/test_accounts.py
deleted file mode 100644
index 21417c8..0000000
--- a/tests/test_accounts.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import unittest
-
-
-class UserTestCase(unittest.TestCase):
- """Tests for accounts module"""
-
- def test_something(self):
- """Test something."""
- ...
diff --git a/tests/test_cli.py b/tests/test_cli.py
deleted file mode 100644
index 4c0958b..0000000
--- a/tests/test_cli.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from hey.cli import parser
-
-
-def test_argparse():
- args = parser.parse_args([])
- assert args.ask == []
-
- args = parser.parse_args(['capital', 'France'])
- assert args.ask == ['capital', 'France']
-
-
-def test_set_password_parser():
- pass
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
index 3c6cc66..4fed3c2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,11 +1,18 @@
[tox]
-minversion = 4.5.1
-env_list = py37,py38,py39,py10,py11
-isolated_build = true
+env_list = py3{8,9,10,11,12}
+ pre-commit
+skipsdist = true
+skip_install = true
[testenv]
-description = run the tests with pytest + more information generated by coverage
-deps = -rrequirements-dev.txt
+description = run tests
+deps = .[dev]
commands =
- coverage run -m pytest
+ coverage run -m pytest {posargs:tests}
coverage report
+ coverage erase
+
+[testenv:pre-commit]
+description = run pre-commit
+deps = pre-commit
+commands = pre-commit run --all-files --show-diff-on-failure