From d1cbd3b451d362272e88925b42abfc6421a1d1f9 Mon Sep 17 00:00:00 2001 From: Farshid Varno Date: Wed, 28 Dec 2022 17:43:37 -0500 Subject: [PATCH 1/6] added ci, tox, githubactions and manifest --- .github/workflows/github-actions.yml | 81 ++++++++++++++++++++++ MANIFEST.in | 13 ++++ ci/bootstrap.py | 100 +++++++++++++++++++++++++++ ci/requirements.txt | 5 ++ tox.ini | 85 +++++++++++++++++++++++ 5 files changed, 284 insertions(+) create mode 100644 .github/workflows/github-actions.yml create mode 100644 MANIFEST.in create mode 100644 ci/bootstrap.py create mode 100644 ci/requirements.txt create mode 100644 tox.ini diff --git a/.github/workflows/github-actions.yml b/.github/workflows/github-actions.yml new file mode 100644 index 0000000..86ed754 --- /dev/null +++ b/.github/workflows/github-actions.yml @@ -0,0 +1,81 @@ +name: build +on: + push + # push: + branches: + - main +jobs: + test: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: 'check' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'check' + os: 'ubuntu-latest' + - name: 'docs' + python: '3.9' + toxpython: 'python3.9' + tox_env: 'docs' + os: 'ubuntu-latest' + - name: 'py39 (ubuntu)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'ubuntu-latest' + - name: 'py39 (windows)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'windows-latest' + - name: 'py39 (macos)' + python: '3.9' + toxpython: 'python3.9' + python_arch: 'x64' + tox_env: 'py39' + os: 'macos-latest' + - name: 'py310 (ubuntu)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'ubuntu-latest' + - name: 'py310 (windows)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'windows-latest' + - name: 'py310 (macos)' + python: '3.10' + toxpython: 'python3.10' + python_arch: 'x64' + tox_env: 'py310' + os: 'macos-latest' + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + architecture: ${{ matrix.python_arch }} + - name: install dependencies + run: | + python -mpip install --progress-bar=off -r ci/requirements.txt + virtualenv --version + pip --version + tox --version + pip list --format=freeze + - name: test + env: + TOXPYTHON: '${{ matrix.toxpython }}' + run: > + tox -e ${{ matrix.tox_env }} -v diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f911cbd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,13 @@ +graft askai +graft images +graft ci +graft tests + + +include pytest.ini +include tox.ini +include .github/workflows/github-actions.yml + +include README.rst + +global-exclude *.py[cod] __pycache__/* *.so *.dylib CNAME \ No newline at end of file diff --git a/ci/bootstrap.py b/ci/bootstrap.py new file mode 100644 index 0000000..e3423ca --- /dev/null +++ b/ci/bootstrap.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals + +import os +import subprocess +import sys +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import join +from os.path import relpath + +base_path = dirname(dirname(abspath(__file__))) +templates_path = join(base_path, "ci", "templates") + + +def check_call(args): + print("+", *args) + subprocess.check_call(args) + + +def exec_in_env(): + env_path = join(base_path, ".tox", "bootstrap") + if sys.platform == "win32": + bin_path = join(env_path, "Scripts") + else: + bin_path = join(env_path, "bin") + if not exists(env_path): + import subprocess + + print("Making bootstrap env in: {0} ...".format(env_path)) + try: + check_call([sys.executable, "-m", "venv", env_path]) + except subprocess.CalledProcessError: + try: + check_call([sys.executable, "-m", "virtualenv", env_path]) + except subprocess.CalledProcessError: + check_call(["virtualenv", env_path]) + print("Installing `jinja2` into bootstrap environment...") + check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) + python_executable = join(bin_path, "python") + if not os.path.exists(python_executable): + python_executable += ".exe" + + print("Re-executing with: {0}".format(python_executable)) + print("+ exec", python_executable, __file__, "--no-env") + os.execv(python_executable, [python_executable, __file__, "--no-env"]) + + +def main(): + import jinja2 + + print("Project path: {0}".format(base_path)) + + jinja = jinja2.Environment( + loader=jinja2.FileSystemLoader(templates_path), + trim_blocks=True, + lstrip_blocks=True, + keep_trailing_newline=True, + ) + + tox_environments = [ + line.strip() + # 'tox' need not be installed globally, but must be importable + # by the Python that is running this script. + # This uses sys.executable the same way that the call in + # cookiecutter-pylibrary/hooks/post_gen_project.py + # invokes this bootstrap.py itself. + for line in subprocess.check_output( + [sys.executable, "-m", "tox", "--listenvs"], + universal_newlines=True, + ).splitlines() + ] + tox_environments = [line for line in tox_environments if line.startswith("py")] + + for root, _, files in os.walk(templates_path): + for name in files: + relative = relpath(root, templates_path) + with open(join(base_path, relative, name), "w") as fh: + fh.write( + jinja.get_template(join(relative, name)).render( + tox_environments=tox_environments + ) + ) + print("Wrote {}".format(name)) + print("DONE.") + + +if __name__ == "__main__": + args = sys.argv[1:] + if args == ["--no-env"]: + main() + elif not args: + exec_in_env() + else: + print("Unexpected arguments {0}".format(args), file=sys.stderr) + sys.exit(1) diff --git a/ci/requirements.txt b/ci/requirements.txt new file mode 100644 index 0000000..a0ef106 --- /dev/null +++ b/ci/requirements.txt @@ -0,0 +1,5 @@ +virtualenv>=16.6.0 +pip>=19.1.1 +setuptools>=18.0.1 +six>=1.14.0 +tox diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..b3069ef --- /dev/null +++ b/tox.ini @@ -0,0 +1,85 @@ +[testenv:bootstrap] +deps = + jinja2 + tox +skip_install = true +commands = + python ci/bootstrap.py --no-env +passenv = + * +; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist + +[tox] +envlist = + clean, + check, + ; docs, + # TODO: check how to workaround for miniconda + {py39} + ; {py39, py310} + report +ignore_basepython_conflict = true + +[testenv] +basepython = + py39: {env:TOXPYTHON:python3.9} + py310: {env:TOXPYTHON:python3.10} + ; {bootstrap,clean,check,report,docs,codecov}: {env:TOXPYTHON:python3} + {bootstrap,clean,check,report}: {env:TOXPYTHON:python3} +setenv = + PYTHONPATH={toxinidir}/tests + PYTHONUNBUFFERED=yes +passenv = + * +usedevelop = false +deps = + pytest + pytest-cov +commands = + {posargs:pytest --cov --cov-report=term-missing -vv tests} + +[testenv:check] +deps = + docutils + check-manifest + flake8 + readme-renderer + pygments + isort +skip_install = true +commands = + python setup.py check --strict --metadata + ; --restructuredtext + check-manifest {toxinidir} + flake8 --ignore=W504,E226,E704,E123,W503,E121,E24,E126,E203 + isort --verbose --check-only --diff --filter-files . + +; [testenv:docs] +; usedevelop = true +; deps = +; -r{toxinidir}/docs/requirements.txt +; commands = +; # TODO: use all in make instead of html as target +; sphinx-build {posargs:-E} -b html docs/source dist/docs/build +; sphinx-build -b linkcheck docs/source dist/docs/build + +; [testenv:codecov] +; deps = +; codecov +; skip_install = true +; commands = +; codecov [] + +[testenv:report] +deps = + coverage +skip_install = true +commands = + coverage report + coverage html + +[testenv:clean] +commands = coverage erase +skip_install = true +deps = + coverage From 89fbf2da4045f04ed3e59d8c8f355c7f5c759c0f Mon Sep 17 00:00:00 2001 From: Farshid Varno Date: Wed, 28 Dec 2022 17:44:21 -0500 Subject: [PATCH 2/6] fix style --- askai/entrypoint_askai.py | 99 +++++--- askai/entrypoint_config.py | 8 +- askai/utils.py | 408 +++++++++++++++++++-------------- setup.py | 4 +- tests/test_available_models.py | 1 - tests/test_config_helper.py | 82 +++---- 6 files changed, 357 insertions(+), 245 deletions(-) diff --git a/askai/entrypoint_askai.py b/askai/entrypoint_askai.py index 5be3fe7..c6e7d7d 100644 --- a/askai/entrypoint_askai.py +++ b/askai/entrypoint_askai.py @@ -1,9 +1,18 @@ import click import openai -from .constants import OPENAI_NUM_ANSWERS_MIN, OPENAI_TEMPERATURE_MIN, OPENAI_TEMPERATURE_MAX, OPENAI_MAX_TOKENS_MIN, \ - OPENAI_TOP_P_MIN, OPENAI_TOP_P_MAX, OPENAI_FREQUENCY_PENALTY_MIN, OPENAI_FREQUENCY_PENALTY_MAX, \ - OPENAI_PRESENCE_PENALTY_MIN, OPENAI_PRESENCE_PENALTY_MAX +from .constants import ( + OPENAI_NUM_ANSWERS_MIN, + OPENAI_TEMPERATURE_MIN, + OPENAI_TEMPERATURE_MAX, + OPENAI_MAX_TOKENS_MIN, + OPENAI_TOP_P_MIN, + OPENAI_TOP_P_MAX, + OPENAI_FREQUENCY_PENALTY_MIN, + OPENAI_FREQUENCY_PENALTY_MAX, + OPENAI_PRESENCE_PENALTY_MIN, + OPENAI_PRESENCE_PENALTY_MAX, +) from .utils import KeyHelper, ConfigHelper, PrintHelper, AvailableModels from .entrypoint_config import config from .entrypoint_init import init @@ -22,12 +31,13 @@ class DefaultCommandGroup(click.Group): default_command: bool def command(self, *args, **kwargs): - default_command = kwargs.pop('default_command', False) + default_command = kwargs.pop("default_command", False) if default_command and not args: - kwargs['name'] = kwargs.get('name', ' ') + kwargs["name"] = kwargs.get("name", " ") decorator = super(DefaultCommandGroup, self).command(*args, **kwargs) if default_command: + def new_decorator(f): cmd = decorator(f) self.default_command = cmd.name @@ -40,13 +50,11 @@ def new_decorator(f): def resolve_command(self, ctx, args): try: # test if the command parses - return super( - DefaultCommandGroup, self).resolve_command(ctx, args) + return super(DefaultCommandGroup, self).resolve_command(ctx, args) except click.UsageError: # command did not parse, assume it is the default command args.insert(0, self.default_command) - return super( - DefaultCommandGroup, self).resolve_command(ctx, args) + return super(DefaultCommandGroup, self).resolve_command(ctx, args) def format_help(self, ctx, formatter) -> None: PrintHelper.logo() @@ -65,21 +73,56 @@ def askai() -> None: @askai.command(default_command=True) @click.argument("prompt") -@click.option("-n", "--num-answers", type=click.IntRange(min=OPENAI_NUM_ANSWERS_MIN), help="Number of alternative answers") -@click.option("-m", "--model", type=click.Choice(choices=AvailableModels.members_as_list()), help="OpenAI model to use. E.g. `text-ada-001`") -@click.option("-t", "--temperature", type=click.FloatRange(min=OPENAI_TEMPERATURE_MIN, max=OPENAI_TEMPERATURE_MAX), help="Temperature") -@click.option("--max-tokens", type=click.IntRange(min=OPENAI_MAX_TOKENS_MIN), help="Max tokens") -@click.option("--top-p", type=click.FloatRange(min=OPENAI_TOP_P_MIN, max=OPENAI_TOP_P_MAX), help="Top p") -@click.option("--frequency-penalty", type=click.FloatRange(min=OPENAI_FREQUENCY_PENALTY_MIN, max=OPENAI_FREQUENCY_PENALTY_MAX), help="Frequency penalty") -@click.option("--presence-penalty", type=click.FloatRange(min=OPENAI_PRESENCE_PENALTY_MIN, max=OPENAI_PRESENCE_PENALTY_MAX), help="Presence penalty") -def ask(prompt: str, - num_answers: int, - model: str, - temperature: float, - max_tokens: int, - top_p: float, - frequency_penalty: float, - presence_penalty: float) -> None: +@click.option( + "-n", + "--num-answers", + type=click.IntRange(min=OPENAI_NUM_ANSWERS_MIN), + help="Number of alternative answers", +) +@click.option( + "-m", + "--model", + type=click.Choice(choices=AvailableModels.members_as_list()), + help="OpenAI model to use. E.g. `text-ada-001`", +) +@click.option( + "-t", + "--temperature", + type=click.FloatRange(min=OPENAI_TEMPERATURE_MIN, max=OPENAI_TEMPERATURE_MAX), + help="Temperature", +) +@click.option( + "--max-tokens", type=click.IntRange(min=OPENAI_MAX_TOKENS_MIN), help="Max tokens" +) +@click.option( + "--top-p", + type=click.FloatRange(min=OPENAI_TOP_P_MIN, max=OPENAI_TOP_P_MAX), + help="Top p", +) +@click.option( + "--frequency-penalty", + type=click.FloatRange( + min=OPENAI_FREQUENCY_PENALTY_MIN, max=OPENAI_FREQUENCY_PENALTY_MAX + ), + help="Frequency penalty", +) +@click.option( + "--presence-penalty", + type=click.FloatRange( + min=OPENAI_PRESENCE_PENALTY_MIN, max=OPENAI_PRESENCE_PENALTY_MAX + ), + help="Presence penalty", +) +def ask( + prompt: str, + num_answers: int, + model: str, + temperature: float, + max_tokens: int, + top_p: float, + frequency_penalty: float, + presence_penalty: float, +) -> None: openai.api_key = KeyHelper.from_file() _config = ConfigHelper.from_file() @@ -90,8 +133,12 @@ def ask(prompt: str, max_tokens=max_tokens if max_tokens else _config.max_tokens, n=num_answers if num_answers else _config.num_answers, top_p=top_p if top_p else _config.top_p, - frequency_penalty=frequency_penalty if frequency_penalty else _config.frequency_penalty, - presence_penalty=presence_penalty if presence_penalty else _config.presence_penalty + frequency_penalty=frequency_penalty + if frequency_penalty + else _config.frequency_penalty, + presence_penalty=presence_penalty + if presence_penalty + else _config.presence_penalty, ) PrintHelper.print_response(response=response) diff --git a/askai/entrypoint_config.py b/askai/entrypoint_config.py index 049e4e7..90df74d 100644 --- a/askai/entrypoint_config.py +++ b/askai/entrypoint_config.py @@ -11,7 +11,9 @@ def config() -> None: @config.command(help="Reset the config to default") def reset() -> None: - user_verification = input("Do you want to reset your config to the default values? [y/Y]? ") + user_verification = input( + "Do you want to reset your config to the default values? [y/Y]? " + ) if user_verification.lower() in ["y", "yes"]: ConfigHelper().reset() @@ -38,7 +40,9 @@ def update_all() -> None: PrintHelper.model() config_helper.input_model() - PrintHelper.step(step=2, description="SET NUMBER OF ALTERNATIVE ANSWERS GENERATED PER QUESTION") + PrintHelper.step( + step=2, description="SET NUMBER OF ALTERNATIVE ANSWERS GENERATED PER QUESTION" + ) PrintHelper.num_answers() config_helper.input_num_answer() diff --git a/askai/utils.py b/askai/utils.py index ec3d6bb..9552679 100644 --- a/askai/utils.py +++ b/askai/utils.py @@ -18,9 +18,18 @@ DEFAULT_TEMPERATURE, DEFAULT_TOP_P, DEFAULT_FREQUENCY_PENALTY, - DEFAULT_PRESENCE_PENALTY, MAX_INPUT_TRIES, OPENAI_NUM_ANSWERS_MIN, OPENAI_MAX_TOKENS_MIN, OPENAI_TEMPERATURE_MIN, - OPENAI_TEMPERATURE_MAX, OPENAI_TOP_P_MIN, OPENAI_TOP_P_MAX, OPENAI_FREQUENCY_PENALTY_MIN, - OPENAI_FREQUENCY_PENALTY_MAX, OPENAI_PRESENCE_PENALTY_MIN, OPENAI_PRESENCE_PENALTY_MAX + DEFAULT_PRESENCE_PENALTY, + MAX_INPUT_TRIES, + OPENAI_NUM_ANSWERS_MIN, + OPENAI_MAX_TOKENS_MIN, + OPENAI_TEMPERATURE_MIN, + OPENAI_TEMPERATURE_MAX, + OPENAI_TOP_P_MIN, + OPENAI_TOP_P_MAX, + OPENAI_FREQUENCY_PENALTY_MIN, + OPENAI_FREQUENCY_PENALTY_MAX, + OPENAI_PRESENCE_PENALTY_MIN, + OPENAI_PRESENCE_PENALTY_MAX, ) @@ -49,14 +58,19 @@ class ConfigHelper: presence_penalty: float = DEFAULT_PRESENCE_PENALTY @classmethod - def from_file(cls, config_path: Path = CONFIG_PATH) -> 'ConfigHelper': + def from_file(cls, config_path: Path = CONFIG_PATH) -> "ConfigHelper": if config_path.is_file(): with open(config_path, "r") as f: config = yaml.safe_load(f) return cls(**config) else: - click.echo(click.style("No config file found, can't initialize config. " - "Run 'askai config reset' to create a default config.", fg="red")) + click.echo( + click.style( + "No config file found, can't initialize config. " + "Run 'askai config reset' to create a default config.", + fg="red", + ) + ) exit() def input_model(self, max_input_tries: int = MAX_INPUT_TRIES) -> None: @@ -76,68 +90,80 @@ def input_model(self, max_input_tries: int = MAX_INPUT_TRIES) -> None: click.echo(click.style(f"Model chosen: {self.model}", fg="green")) click.echo() - def input_num_answer(self, - default_value: int = DEFAULT_NUM_ANSWERS, - min_value: int = OPENAI_NUM_ANSWERS_MIN, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_num_answer( + self, + default_value: int = DEFAULT_NUM_ANSWERS, + min_value: int = OPENAI_NUM_ANSWERS_MIN, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.num_answers = self._input_integer( default_value=default_value, predicate=lambda x: x >= min_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_max_token(self, - default_value: int = DEFAULT_MAX_TOKENS, - min_value: int = OPENAI_MAX_TOKENS_MIN, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_max_token( + self, + default_value: int = DEFAULT_MAX_TOKENS, + min_value: int = OPENAI_MAX_TOKENS_MIN, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.max_tokens = self._input_integer( default_value=default_value, predicate=lambda x: x >= min_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_temperature(self, - default_value: float = DEFAULT_TEMPERATURE, - min_value: float = OPENAI_TEMPERATURE_MIN, - max_value: float = OPENAI_TEMPERATURE_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_temperature( + self, + default_value: float = DEFAULT_TEMPERATURE, + min_value: float = OPENAI_TEMPERATURE_MIN, + max_value: float = OPENAI_TEMPERATURE_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.temperature = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_top_p(self, - default_value: float = DEFAULT_TOP_P, - min_value: float = OPENAI_TOP_P_MIN, - max_value: float = OPENAI_TOP_P_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_top_p( + self, + default_value: float = DEFAULT_TOP_P, + min_value: float = OPENAI_TOP_P_MIN, + max_value: float = OPENAI_TOP_P_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.top_p = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_frequency_penalty(self, - default_value: float = DEFAULT_FREQUENCY_PENALTY, - min_value: float = OPENAI_FREQUENCY_PENALTY_MIN, - max_value: float = OPENAI_FREQUENCY_PENALTY_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_frequency_penalty( + self, + default_value: float = DEFAULT_FREQUENCY_PENALTY, + min_value: float = OPENAI_FREQUENCY_PENALTY_MIN, + max_value: float = OPENAI_FREQUENCY_PENALTY_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.frequency_penalty = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) - def input_presence_penalty(self, - default_value: float = DEFAULT_PRESENCE_PENALTY, - min_value: float = OPENAI_PRESENCE_PENALTY_MIN, - max_value: float = OPENAI_PRESENCE_PENALTY_MAX, - max_input_tries: int = MAX_INPUT_TRIES) -> None: + def input_presence_penalty( + self, + default_value: float = DEFAULT_PRESENCE_PENALTY, + min_value: float = OPENAI_PRESENCE_PENALTY_MIN, + max_value: float = OPENAI_PRESENCE_PENALTY_MAX, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> None: self.presence_penalty = self._input_float( default_value=default_value, predicate=lambda x: min_value <= x <= max_value, - max_input_tries=max_input_tries + max_input_tries=max_input_tries, ) def as_dict(self) -> dict: @@ -159,14 +185,18 @@ def reset(config_path: Path = CONFIG_PATH) -> None: click.echo("\nDefault config has been created with the following values:") for key, value in config.items(): click.echo(f" * {key}={value}") - click.echo(click.style("Successfully set config to default values\n", fg="green")) + click.echo( + click.style("Successfully set config to default values\n", fg="green") + ) click.echo("To change the config, please see: 'askai config --help'\n") @staticmethod def show(config_path: Path = CONFIG_PATH) -> None: if not config_path.is_file(): - click.echo("No config exists. Please reset the config ('askai config reset') " - "or see 'askai config --help'.\n") + click.echo( + "No config exists. Please reset the config ('askai config reset') " + "or see 'askai config --help'.\n" + ) else: with open(config_path, "r") as f: try: @@ -174,12 +204,16 @@ def show(config_path: Path = CONFIG_PATH) -> None: for key, value in config.items(): click.echo(f"{key}: {value}") except yaml.YAMLError: - click.echo("Something is wrong with the config. Please reset the config: 'askai config reset'") + click.echo( + "Something is wrong with the config. Please reset the config: 'askai config reset'" + ) @staticmethod - def _input_integer(default_value: int, - predicate: Callable[[int], bool] = lambda x: True, - max_input_tries: int = MAX_INPUT_TRIES) -> int: + def _input_integer( + default_value: int, + predicate: Callable[[int], bool] = lambda x: True, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> int: for _ in range(max_input_tries): input_value = input(f"Choose (press enter for default = {default_value}): ") @@ -192,7 +226,9 @@ def _input_integer(default_value: int, click.echo(click.style("Input is not an integer.\n", fg="red")) continue elif not predicate(int(input_value)): - click.echo(click.style("Input is not within allowed range.\n", fg="red")) + click.echo( + click.style("Input is not within allowed range.\n", fg="red") + ) continue click.echo(click.style(f"Value chosen: {input_value}", fg="green")) @@ -203,9 +239,11 @@ def _input_integer(default_value: int, exit(1) @staticmethod - def _input_float(default_value: float, - predicate: Callable[[float], bool] = lambda x: True, - max_input_tries: int = MAX_INPUT_TRIES) -> float: + def _input_float( + default_value: float, + predicate: Callable[[float], bool] = lambda x: True, + max_input_tries: int = MAX_INPUT_TRIES, + ) -> float: for _ in range(max_input_tries): input_value = input(f"Choose (press enter for default = {default_value}): ") @@ -218,7 +256,9 @@ def _input_float(default_value: float, click.echo(click.style("Input is not a float.\n", fg="red")) continue elif not predicate(float(input_value)): - click.echo(click.style("Input is not within allowed range.\n", fg="red")) + click.echo( + click.style("Input is not within allowed range.\n", fg="red") + ) continue click.echo(click.style(f"Value chosen: {input_value}", fg="green")) @@ -264,8 +304,13 @@ def from_file(cls) -> str: api_key = f.read().strip() return api_key else: - click.echo(click.style("No API-key found, can't answer question. " - "Please add a key ('askai key add') or initialize ('askai init').", fg="red")) + click.echo( + click.style( + "No API-key found, can't answer question. " + "Please add a key ('askai key add') or initialize ('askai init').", + fg="red", + ) + ) exit() @staticmethod @@ -280,109 +325,124 @@ def _is_valid_api_key(key: str) -> bool: class PrintHelper: - @staticmethod def logo() -> None: - click.echo("\n" - "██████████ ██████████ ███ ███ ██████████ ███\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ███ ███ ███▐███ ███ ███ ▄█\n" - "███ ███ ███ █████▀ ███ ███ ███\n" - "██████████ ██████████ █████▄ ██████████ ███▌\n" - "███ ███ ███ ███▐███ ███ ███ ███▌\n" - "███ ███ ███ ███ ███ ███ ███ ███\n" - "███ ███ ██████████ ███ ███ ███ ███ █▀\n" - "\n" - " ~~~~~~~ Your simple terminal helper ~~~~~~~\n") + click.echo( + "\n" + "██████████ ██████████ ███ ███ ██████████ ███\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ███ ███ ███▐███ ███ ███ ▄█\n" + "███ ███ ███ █████▀ ███ ███ ███\n" + "██████████ ██████████ █████▄ ██████████ ███▌\n" + "███ ███ ███ ███▐███ ███ ███ ███▌\n" + "███ ███ ███ ███ ███ ███ ███ ███\n" + "███ ███ ██████████ ███ ███ ███ ███ █▀\n" + "\n" + " ~~~~~~~ Your simple terminal helper ~~~~~~~\n" + ) @staticmethod def help_what_is_askai() -> None: - click.echo("What is askai?\n" - " askai is a simple CLI integration with the worlds most powerful \n" - " and capable AI-model, OpenAI GPT3. This gives you the ability to \n" - " interact with these models straight from your terminal.\n" - "\n" - "What can it be used for?\n" - " askai enables you to ask any free-text question you want.\n" - "\n" - " For example:\n" - "\n" - " > askai \"What is the curl command to download html from a url?\"\n" - " > askai \"How do you remove '\\n' from the beginning of a string using Python?\"\n" - " > askai \"List the top 3 most common Python packages used to parse json-files\"" - "\n") + click.echo( + "What is askai?\n" + " askai is a simple CLI integration with the worlds most powerful \n" + " and capable AI-model, OpenAI GPT3. This gives you the ability to \n" + " interact with these models straight from your terminal.\n" + "\n" + "What can it be used for?\n" + " askai enables you to ask any free-text question you want.\n" + "\n" + " For example:\n" + "\n" + ' > askai "What is the curl command to download html from a url?"\n' + " > askai \"How do you remove '\\n' from the beginning of a string using Python?\"\n" + ' > askai "List the top 3 most common Python packages used to parse json-files"' + "\n" + ) @staticmethod def help_does_it_cost() -> None: - click.echo("Does it cost anything?\n" - " Yes (after the free OpenAI quota is used).\n" - "\n" - " askai is using your OpenAI API-key to generate the answers to your questions.\n" - " When creating a new account at OpenAI, you will get $18 of requests for free.\n" - " After you've consumed the free quota, you need to add payment info to your \n" - " OpenAI account to continue to use askai.\n") - + click.echo( + "Does it cost anything?\n" + " Yes (after the free OpenAI quota is used).\n" + "\n" + " askai is using your OpenAI API-key to generate the answers to your questions.\n" + " When creating a new account at OpenAI, you will get $18 of requests for free.\n" + " After you've consumed the free quota, you need to add payment info to your \n" + " OpenAI account to continue to use askai.\n" + ) + @staticmethod def help_requirements() -> None: - click.echo("Requirements:\n" - " * Create an OpenAI account and generate an API-key\n" - " * Run 'askai init' to add you API key and setup the default config.\n") + click.echo( + "Requirements:\n" + " * Create an OpenAI account and generate an API-key\n" + " * Run 'askai init' to add you API key and setup the default config.\n" + ) @staticmethod def help_main_command_options() -> None: - click.echo("Override config:\n" - " askai \"\"