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