CF 680 don't emit unnecessary warning & improve UX when using cmd init (#487)
* CF 680 * don't * Update uv.lock * improve UX when under cmd init * fix mypy
This commit is contained in:
parent
67a9e79940
commit
f8c7f2a7ec
10 changed files with 1289 additions and 721 deletions
|
|
@ -32,13 +32,19 @@ else:
|
|||
|
||||
|
||||
def make_cfapi_request(
|
||||
endpoint: str, method: str, payload: dict[str, Any] | None = None, extra_headers: dict[str, str] | None = None
|
||||
endpoint: str,
|
||||
method: str,
|
||||
payload: dict[str, Any] | None = None,
|
||||
extra_headers: dict[str, str] | None = None,
|
||||
*,
|
||||
suppress_errors: bool = False,
|
||||
) -> Response:
|
||||
"""Make an HTTP request using the specified method, URL, headers, and JSON payload.
|
||||
|
||||
:param endpoint: The endpoint URL to send the request to.
|
||||
:param method: The HTTP method to use ('GET', 'POST', etc.).
|
||||
:param payload: Optional JSON payload to include in the POST request body.
|
||||
:param suppress_errors: If True, suppress error logging for HTTP errors.
|
||||
:return: The response object from the API.
|
||||
"""
|
||||
url = f"{CFAPI_BASE_URL}/cfapi{endpoint}"
|
||||
|
|
@ -66,9 +72,10 @@ def make_cfapi_request(
|
|||
except (ValueError, TypeError):
|
||||
error_message = response.text
|
||||
|
||||
logger.error(
|
||||
f"CF_API_Error:: making request to Codeflash API (url: {url}, method: {method}, status {response.status_code}): {error_message}"
|
||||
)
|
||||
if not suppress_errors:
|
||||
logger.error(
|
||||
f"CF_API_Error:: making request to Codeflash API (url: {url}, method: {method}, status {response.status_code}): {error_message}"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
|
|
@ -175,14 +182,17 @@ def create_pr(
|
|||
return make_cfapi_request(endpoint="/create-pr", method="POST", payload=payload)
|
||||
|
||||
|
||||
def is_github_app_installed_on_repo(owner: str, repo: str) -> bool:
|
||||
def is_github_app_installed_on_repo(owner: str, repo: str, *, suppress_errors: bool = False) -> bool:
|
||||
"""Check if the Codeflash GitHub App is installed on the specified repository.
|
||||
|
||||
:param owner: The owner of the repository.
|
||||
:param repo: The name of the repository.
|
||||
:return: The response object.
|
||||
:param suppress_errors: If True, suppress error logging when the app is not installed.
|
||||
:return: True if the app is installed, False otherwise.
|
||||
"""
|
||||
response = make_cfapi_request(endpoint=f"/is-github-app-installed?repo={repo}&owner={owner}", method="GET")
|
||||
response = make_cfapi_request(
|
||||
endpoint=f"/is-github-app-installed?repo={repo}&owner={owner}", method="GET", suppress_errors=suppress_errors
|
||||
)
|
||||
return response.ok and response.text == "true"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,13 @@ import inquirer.themes
|
|||
import tomlkit
|
||||
from git import InvalidGitRepositoryError, Repo
|
||||
from pydantic.dataclasses import dataclass
|
||||
from rich.console import Group
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from codeflash.api.cfapi import is_github_app_installed_on_repo
|
||||
from codeflash.cli_cmds.cli_common import apologize_and_exit, inquirer_wrapper, inquirer_wrapper_path
|
||||
from codeflash.cli_cmds.cli_common import apologize_and_exit
|
||||
from codeflash.cli_cmds.console import console, logger
|
||||
from codeflash.code_utils.compat import LF
|
||||
from codeflash.code_utils.config_parser import parse_config_file
|
||||
|
|
@ -66,7 +70,18 @@ class DependencyManager(Enum):
|
|||
|
||||
def init_codeflash() -> None:
|
||||
try:
|
||||
click.echo(f"⚡️ Welcome to Codeflash! Let's get you set up.{LF}")
|
||||
welcome_panel = Panel(
|
||||
Text(
|
||||
"⚡️ Welcome to Codeflash!\n\nThis setup will take just a few minutes.",
|
||||
style="bold cyan",
|
||||
justify="center",
|
||||
),
|
||||
title="🚀 Codeflash Setup",
|
||||
border_style="bright_cyan",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(welcome_panel)
|
||||
console.print()
|
||||
|
||||
did_add_new_key = prompt_api_key()
|
||||
|
||||
|
|
@ -83,22 +98,35 @@ def init_codeflash() -> None:
|
|||
if "setup_info" in locals():
|
||||
module_string = f" you selected ({setup_info.module_root})"
|
||||
|
||||
click.echo(
|
||||
f"{LF}"
|
||||
f"⚡️ Codeflash is now set up! You can now run:{LF}"
|
||||
f" codeflash --file <path-to-file> --function <function-name> to optimize a function within a file{LF}"
|
||||
f" codeflash --file <path-to-file> to optimize all functions in a file{LF}"
|
||||
f" codeflash --all to optimize all functions in all files in the module{module_string}{LF}"
|
||||
f"-or-{LF}"
|
||||
f" codeflash --help to see all options{LF}"
|
||||
usage_table = Table(show_header=False, show_lines=False, border_style="dim")
|
||||
usage_table.add_column("Command", style="cyan")
|
||||
usage_table.add_column("Description", style="white")
|
||||
|
||||
usage_table.add_row(
|
||||
"codeflash --file <path-to-file> --function <function-name>", "Optimize a specific function within a file"
|
||||
)
|
||||
usage_table.add_row("codeflash --file <path-to-file>", "Optimize all functions in a file")
|
||||
usage_table.add_row(
|
||||
f"codeflash --all{module_string if module_string else ''}", "Optimize all functions in all files"
|
||||
)
|
||||
usage_table.add_row("codeflash --help", "See all available options")
|
||||
|
||||
completion_message = "⚡️ Codeflash is now set up!\n\nYou can now run any of these commands:"
|
||||
|
||||
if did_add_new_key:
|
||||
click.echo("🐚 Don't forget to restart your shell to load the CODEFLASH_API_KEY environment variable!")
|
||||
click.echo("Or run the following command to reload:")
|
||||
if os.name == "nt":
|
||||
click.echo(f" call {get_shell_rc_path()}")
|
||||
else:
|
||||
click.echo(f" source {get_shell_rc_path()}")
|
||||
completion_message += (
|
||||
"\n\n🐚 Don't forget to restart your shell to load the CODEFLASH_API_KEY environment variable!"
|
||||
)
|
||||
reload_cmd = f"call {get_shell_rc_path()}" if os.name == "nt" else f"source {get_shell_rc_path()}"
|
||||
completion_message += f"\nOr run: {reload_cmd}"
|
||||
|
||||
completion_panel = Panel(
|
||||
Group(Text(completion_message, style="bold green"), Text(""), usage_table),
|
||||
title="🎉 Setup Complete!",
|
||||
border_style="bright_green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(completion_panel)
|
||||
|
||||
ph("cli-installation-successful", {"did_add_new_key": did_add_new_key})
|
||||
sys.exit(0)
|
||||
|
|
@ -152,6 +180,21 @@ def should_modify_pyproject_toml() -> bool:
|
|||
)
|
||||
|
||||
|
||||
# Custom theme for better UX
|
||||
class CodeflashTheme(inquirer.themes.Default):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.Question.mark_color = inquirer.themes.term.yellow
|
||||
self.Question.brackets_color = inquirer.themes.term.bright_blue
|
||||
self.Question.default_color = inquirer.themes.term.bright_cyan
|
||||
self.List.selection_color = inquirer.themes.term.bright_blue
|
||||
self.List.selection_cursor = "⚡"
|
||||
self.Checkbox.selection_color = inquirer.themes.term.bright_blue
|
||||
self.Checkbox.selection_cursor = "⚡"
|
||||
self.Checkbox.selected_icon = "✅"
|
||||
self.Checkbox.unselected_icon = "⬜"
|
||||
|
||||
|
||||
def collect_setup_info() -> SetupInfo:
|
||||
curdir = Path.cwd()
|
||||
# Check if the cwd is writable
|
||||
|
|
@ -185,23 +228,60 @@ def collect_setup_info() -> SetupInfo:
|
|||
custom_dir_option = "enter a custom directory…"
|
||||
module_subdir_options = [*valid_module_subdirs, curdir_option, custom_dir_option]
|
||||
|
||||
module_root_answer = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="Which Python module do you want me to optimize going forward? (Usually the top-most directory with "
|
||||
"all of your Python source code). Use arrow keys to select",
|
||||
choices=module_subdir_options,
|
||||
default=(project_name if project_name in module_subdir_options else module_subdir_options[0]),
|
||||
info_panel = Panel(
|
||||
Text(
|
||||
"📁 Let's identify your Python module directory.\n\n"
|
||||
"This is usually the top-level directory containing all your Python source code.\n"
|
||||
"We've automatically detected some directories for you.",
|
||||
style="cyan",
|
||||
),
|
||||
title="🔍 Module Discovery",
|
||||
border_style="bright_blue",
|
||||
)
|
||||
console.print(info_panel)
|
||||
console.print()
|
||||
|
||||
questions = [
|
||||
inquirer.List(
|
||||
"module_root",
|
||||
message="Which Python module do you want me to optimize?",
|
||||
choices=module_subdir_options,
|
||||
default=(project_name if project_name in module_subdir_options else module_subdir_options[0]),
|
||||
carousel=True,
|
||||
)
|
||||
]
|
||||
|
||||
answers = inquirer.prompt(questions, theme=CodeflashTheme())
|
||||
if not answers:
|
||||
apologize_and_exit()
|
||||
module_root_answer = answers["module_root"]
|
||||
if module_root_answer == curdir_option:
|
||||
module_root = "."
|
||||
elif module_root_answer == custom_dir_option:
|
||||
custom_module_root_answer = inquirer_wrapper_path(
|
||||
"path",
|
||||
message=f"Enter the path to your module directory inside {Path(curdir).resolve()}{os.path.sep} ",
|
||||
path_type=inquirer.Path.DIRECTORY,
|
||||
custom_panel = Panel(
|
||||
Text(
|
||||
"📂 Enter a custom module directory path.\n\nPlease provide the path to your Python module directory.",
|
||||
style="yellow",
|
||||
),
|
||||
title="📂 Custom Directory",
|
||||
border_style="bright_yellow",
|
||||
)
|
||||
if custom_module_root_answer:
|
||||
module_root = Path(custom_module_root_answer["path"])
|
||||
console.print(custom_panel)
|
||||
console.print()
|
||||
|
||||
custom_questions = [
|
||||
inquirer.Path(
|
||||
"custom_path",
|
||||
message="Enter the path to your module directory",
|
||||
path_type=inquirer.Path.DIRECTORY,
|
||||
exists=True,
|
||||
normalize_to_absolute_path=False,
|
||||
)
|
||||
]
|
||||
|
||||
custom_answers = inquirer.prompt(custom_questions, theme=CodeflashTheme())
|
||||
if custom_answers:
|
||||
module_root = Path(custom_answers["custom_path"])
|
||||
else:
|
||||
apologize_and_exit()
|
||||
else:
|
||||
|
|
@ -210,32 +290,70 @@ def collect_setup_info() -> SetupInfo:
|
|||
|
||||
# Discover test directory
|
||||
default_tests_subdir = "tests"
|
||||
create_for_me_option = f"okay, create a tests{os.pathsep} directory for me!"
|
||||
create_for_me_option = f"🆕 Create a new tests{os.pathsep} directory for me!"
|
||||
test_subdir_options = [sub_dir for sub_dir in valid_subdirs if sub_dir != module_root]
|
||||
if "tests" not in valid_subdirs:
|
||||
test_subdir_options.append(create_for_me_option)
|
||||
custom_dir_option = "enter a custom directory…"
|
||||
custom_dir_option = "📁 Enter a custom directory…"
|
||||
test_subdir_options.append(custom_dir_option)
|
||||
tests_root_answer = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="Where are your tests located? "
|
||||
f"(If you don't have any tests yet, I can create an empty tests{os.pathsep} directory for you)",
|
||||
choices=test_subdir_options,
|
||||
default=(default_tests_subdir if default_tests_subdir in test_subdir_options else test_subdir_options[0]),
|
||||
|
||||
tests_panel = Panel(
|
||||
Text(
|
||||
"🧪 Now let's locate your test directory.\n\n"
|
||||
"This is where all your test files are stored. If you don't have tests yet, "
|
||||
"I can create a directory for you!",
|
||||
style="green",
|
||||
),
|
||||
title="🧪 Test Discovery",
|
||||
border_style="bright_green",
|
||||
)
|
||||
console.print(tests_panel)
|
||||
console.print()
|
||||
|
||||
tests_questions = [
|
||||
inquirer.List(
|
||||
"tests_root",
|
||||
message="Where are your tests located?",
|
||||
choices=test_subdir_options,
|
||||
default=(default_tests_subdir if default_tests_subdir in test_subdir_options else test_subdir_options[0]),
|
||||
carousel=True,
|
||||
)
|
||||
]
|
||||
|
||||
tests_answers = inquirer.prompt(tests_questions, theme=CodeflashTheme())
|
||||
if not tests_answers:
|
||||
apologize_and_exit()
|
||||
tests_root_answer = tests_answers["tests_root"]
|
||||
|
||||
if tests_root_answer == create_for_me_option:
|
||||
tests_root = Path(curdir) / default_tests_subdir
|
||||
tests_root.mkdir()
|
||||
click.echo(f"✅ Created directory {tests_root}{os.path.sep}{LF}")
|
||||
elif tests_root_answer == custom_dir_option:
|
||||
custom_tests_root_answer = inquirer_wrapper_path(
|
||||
"path",
|
||||
message=f"Enter the path to your tests directory inside {Path(curdir).resolve()}{os.path.sep} ",
|
||||
path_type=inquirer.Path.DIRECTORY,
|
||||
custom_tests_panel = Panel(
|
||||
Text(
|
||||
"🧪 Enter a custom test directory path.\n\nPlease provide the path to your test directory.",
|
||||
style="yellow",
|
||||
),
|
||||
title="🧪 Custom Test Directory",
|
||||
border_style="bright_yellow",
|
||||
)
|
||||
if custom_tests_root_answer:
|
||||
tests_root = Path(curdir) / Path(custom_tests_root_answer["path"])
|
||||
console.print(custom_tests_panel)
|
||||
console.print()
|
||||
|
||||
custom_tests_questions = [
|
||||
inquirer.Path(
|
||||
"custom_tests_path",
|
||||
message="Enter the path to your tests directory",
|
||||
path_type=inquirer.Path.DIRECTORY,
|
||||
exists=False, # Allow creating new directories
|
||||
normalize_to_absolute_path=False,
|
||||
)
|
||||
]
|
||||
|
||||
custom_tests_answers = inquirer.prompt(custom_tests_questions, theme=CodeflashTheme())
|
||||
if custom_tests_answers:
|
||||
tests_root = Path(curdir) / Path(custom_tests_answers["custom_tests_path"])
|
||||
else:
|
||||
apologize_and_exit()
|
||||
else:
|
||||
|
|
@ -252,66 +370,35 @@ def collect_setup_info() -> SetupInfo:
|
|||
|
||||
ph("cli-tests-root-provided")
|
||||
|
||||
# Autodiscover test framework
|
||||
autodetected_test_framework = detect_test_framework(curdir, tests_root)
|
||||
autodetected_suffix = (
|
||||
f" (seems to me you're using {autodetected_test_framework})" if autodetected_test_framework else ""
|
||||
)
|
||||
test_framework = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="Which test framework do you use?" + autodetected_suffix,
|
||||
choices=["pytest", "unittest"],
|
||||
default=autodetected_test_framework or "pytest",
|
||||
carousel=True,
|
||||
)
|
||||
|
||||
framework_message = "⚗️ Let's configure your test framework.\n\n"
|
||||
if autodetected_test_framework:
|
||||
framework_message += f"I detected that you're using {autodetected_test_framework}. "
|
||||
framework_message += "Please confirm or select a different one."
|
||||
|
||||
framework_panel = Panel(Text(framework_message, style="blue"), title="⚗️ Test Framework", border_style="bright_blue")
|
||||
console.print(framework_panel)
|
||||
console.print()
|
||||
|
||||
framework_questions = [
|
||||
inquirer.List(
|
||||
"test_framework",
|
||||
message="Which test framework do you use?",
|
||||
choices=[("🧪 pytest", "pytest"), ("🐍 unittest", "unittest")],
|
||||
default=autodetected_test_framework or "pytest",
|
||||
carousel=True,
|
||||
)
|
||||
]
|
||||
|
||||
framework_answers = inquirer.prompt(framework_questions, theme=CodeflashTheme())
|
||||
if not framework_answers:
|
||||
apologize_and_exit()
|
||||
test_framework = framework_answers["test_framework"]
|
||||
|
||||
ph("cli-test-framework-provided", {"test_framework": test_framework})
|
||||
|
||||
# Get benchmarks root directory
|
||||
default_benchmarks_subdir = "benchmarks"
|
||||
create_benchmarks_option = f"okay, create a {default_benchmarks_subdir}{os.path.sep} directory for me!"
|
||||
no_benchmarks_option = "I don't need benchmarks"
|
||||
|
||||
# Check if benchmarks directory exists inside tests directory
|
||||
tests_subdirs = []
|
||||
if tests_root.exists():
|
||||
tests_subdirs = [d.name for d in tests_root.iterdir() if d.is_dir() and not d.name.startswith(".")]
|
||||
|
||||
benchmarks_options = []
|
||||
benchmarks_options.append(no_benchmarks_option)
|
||||
if default_benchmarks_subdir in tests_subdirs:
|
||||
benchmarks_options.append(default_benchmarks_subdir)
|
||||
benchmarks_options.extend([d for d in tests_subdirs if d != default_benchmarks_subdir and d not in ignore_subdirs])
|
||||
benchmarks_options.append(create_benchmarks_option)
|
||||
benchmarks_options.append(custom_dir_option)
|
||||
|
||||
benchmarks_answer = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="Where are your performance benchmarks located? (benchmarks must be a sub directory of your tests root directory)",
|
||||
choices=benchmarks_options,
|
||||
default=(
|
||||
default_benchmarks_subdir if default_benchmarks_subdir in benchmarks_options else benchmarks_options[0]
|
||||
),
|
||||
)
|
||||
|
||||
if benchmarks_answer == create_benchmarks_option:
|
||||
benchmarks_root = tests_root / default_benchmarks_subdir
|
||||
benchmarks_root.mkdir(exist_ok=True)
|
||||
click.echo(f"✅ Created directory {benchmarks_root}{os.path.sep}{LF}")
|
||||
elif benchmarks_answer == custom_dir_option:
|
||||
custom_benchmarks_answer = inquirer_wrapper_path(
|
||||
"path",
|
||||
message=f"Enter the path to your benchmarks directory inside {tests_root}{os.path.sep} ",
|
||||
path_type=inquirer.Path.DIRECTORY,
|
||||
)
|
||||
if custom_benchmarks_answer:
|
||||
benchmarks_root = tests_root / Path(custom_benchmarks_answer["path"])
|
||||
else:
|
||||
apologize_and_exit()
|
||||
elif benchmarks_answer == no_benchmarks_option:
|
||||
benchmarks_root = None
|
||||
else:
|
||||
benchmarks_root = tests_root / Path(cast("str", benchmarks_answer))
|
||||
benchmarks_root = None
|
||||
|
||||
# TODO: Implement other benchmark framework options
|
||||
# if benchmarks_root:
|
||||
|
|
@ -327,13 +414,38 @@ def collect_setup_info() -> SetupInfo:
|
|||
# carousel=True,
|
||||
# )
|
||||
|
||||
formatter = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="Which code formatter do you use?",
|
||||
choices=["black", "ruff", "other", "don't use a formatter"],
|
||||
default="black",
|
||||
carousel=True,
|
||||
formatter_panel = Panel(
|
||||
Text(
|
||||
"🎨 Let's configure your code formatter.\n\n"
|
||||
"Code formatters help maintain consistent code style. "
|
||||
"Codeflash will use this to format optimized code.",
|
||||
style="magenta",
|
||||
),
|
||||
title="🎨 Code Formatter",
|
||||
border_style="bright_magenta",
|
||||
)
|
||||
console.print(formatter_panel)
|
||||
console.print()
|
||||
|
||||
formatter_questions = [
|
||||
inquirer.List(
|
||||
"formatter",
|
||||
message="Which code formatter do you use?",
|
||||
choices=[
|
||||
("⚫ black", "black"),
|
||||
("⚡ ruff", "ruff"),
|
||||
("🔧 other", "other"),
|
||||
("❌ don't use a formatter", "don't use a formatter"),
|
||||
],
|
||||
default="black",
|
||||
carousel=True,
|
||||
)
|
||||
]
|
||||
|
||||
formatter_answers = inquirer.prompt(formatter_questions, theme=CodeflashTheme())
|
||||
if not formatter_answers:
|
||||
apologize_and_exit()
|
||||
formatter = formatter_answers["formatter"]
|
||||
|
||||
git_remote = ""
|
||||
try:
|
||||
|
|
@ -341,13 +453,30 @@ def collect_setup_info() -> SetupInfo:
|
|||
git_remotes = get_git_remotes(repo)
|
||||
if git_remotes: # Only proceed if there are remotes
|
||||
if len(git_remotes) > 1:
|
||||
git_remote = inquirer_wrapper(
|
||||
inquirer.list_input,
|
||||
message="What git remote do you want Codeflash to use for new Pull Requests? ",
|
||||
choices=git_remotes,
|
||||
default="origin",
|
||||
carousel=True,
|
||||
git_panel = Panel(
|
||||
Text(
|
||||
"🔗 Configure Git Remote for Pull Requests.\n\n"
|
||||
"Codeflash will use this remote to create pull requests with optimized code.",
|
||||
style="blue",
|
||||
),
|
||||
title="🔗 Git Remote Setup",
|
||||
border_style="bright_blue",
|
||||
)
|
||||
console.print(git_panel)
|
||||
console.print()
|
||||
|
||||
git_questions = [
|
||||
inquirer.List(
|
||||
"git_remote",
|
||||
message="Which git remote should Codeflash use for Pull Requests?",
|
||||
choices=git_remotes,
|
||||
default="origin",
|
||||
carousel=True,
|
||||
)
|
||||
]
|
||||
|
||||
git_answers = inquirer.prompt(git_questions, theme=CodeflashTheme())
|
||||
git_remote = git_answers["git_remote"] if git_answers else git_remotes[0]
|
||||
else:
|
||||
git_remote = git_remotes[0]
|
||||
else:
|
||||
|
|
@ -439,20 +568,29 @@ def check_for_toml_or_setup_file() -> str | None:
|
|||
else:
|
||||
click.echo("✅ Found setup.py.")
|
||||
ph("cli-setup-py-found")
|
||||
click.echo(
|
||||
f"💡 I couldn't find a pyproject.toml in the current directory ({curdir}).{LF}"
|
||||
f"(make sure you're running `codeflash init` from your project's root directory!){LF}"
|
||||
f"I need this file to store my configuration settings."
|
||||
toml_info_panel = Panel(
|
||||
Text(
|
||||
f"💡 No pyproject.toml found in {curdir}.\n\n"
|
||||
"This file is essential for Codeflash to store its configuration.\n"
|
||||
"Please ensure you are running `codeflash init` from your project's root directory.",
|
||||
style="yellow",
|
||||
),
|
||||
title="📋 pyproject.toml Required",
|
||||
border_style="bright_yellow",
|
||||
)
|
||||
console.print(toml_info_panel)
|
||||
console.print()
|
||||
ph("cli-no-pyproject-toml-or-setup-py")
|
||||
|
||||
# Create a pyproject.toml file because it doesn't exist
|
||||
create_toml = inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message="Do you want me to create a pyproject.toml file in the current directory?",
|
||||
default=True,
|
||||
show_default=False,
|
||||
)
|
||||
toml_questions = [
|
||||
inquirer.Confirm("create_toml", message="Create pyproject.toml in the current directory?", default=True)
|
||||
]
|
||||
|
||||
toml_answers = inquirer.prompt(toml_questions, theme=CodeflashTheme())
|
||||
if not toml_answers:
|
||||
apologize_and_exit()
|
||||
create_toml = toml_answers["create_toml"]
|
||||
if create_toml:
|
||||
ph("cli-create-pyproject-toml")
|
||||
# Define a minimal pyproject.toml content
|
||||
|
|
@ -463,8 +601,19 @@ def check_for_toml_or_setup_file() -> str | None:
|
|||
|
||||
# Check if the pyproject.toml file was created
|
||||
if pyproject_toml_path.exists():
|
||||
click.echo(f"✅ Created a pyproject.toml file at {pyproject_toml_path}")
|
||||
click.pause()
|
||||
success_panel = Panel(
|
||||
Text(
|
||||
f"✅ Created a pyproject.toml file at {pyproject_toml_path}\n\n"
|
||||
"Your project is now ready for Codeflash configuration!",
|
||||
style="green",
|
||||
justify="center",
|
||||
),
|
||||
title="🎉 Success!",
|
||||
border_style="bright_green",
|
||||
)
|
||||
console.print(success_panel)
|
||||
console.print("\n📍 Press any key to continue...")
|
||||
console.input()
|
||||
ph("cli-created-pyproject-toml")
|
||||
except OSError:
|
||||
click.echo(
|
||||
|
|
@ -495,42 +644,85 @@ def install_github_actions(override_formatter_check: bool = False) -> None: # n
|
|||
workflows_path = git_root / ".github" / "workflows"
|
||||
optimize_yaml_path = workflows_path / "codeflash.yaml"
|
||||
|
||||
actions_panel = Panel(
|
||||
Text(
|
||||
"🤖 GitHub Actions Setup\n\n"
|
||||
"GitHub Actions will automatically optimize your code in every pull request. "
|
||||
"This is the recommended way to use Codeflash for continuous optimization.",
|
||||
style="blue",
|
||||
),
|
||||
title="🤖 Continuous Optimization",
|
||||
border_style="bright_blue",
|
||||
)
|
||||
console.print(actions_panel)
|
||||
console.print()
|
||||
|
||||
# Check if the workflow file already exists
|
||||
if optimize_yaml_path.exists():
|
||||
confirm_overwrite = inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message=f"⚡️ GitHub Actions workflow already exists at {optimize_yaml_path}. Overwrite?",
|
||||
default=False, # Don't overwrite by default
|
||||
)
|
||||
ph("cli-github-optimization-confirm-workflow-overwrite", {"confirm_overwrite": confirm_overwrite})
|
||||
if not confirm_overwrite:
|
||||
click.echo("⏩️ Skipping workflow creation.")
|
||||
overwrite_questions = [
|
||||
inquirer.Confirm(
|
||||
"confirm_overwrite",
|
||||
message=f"GitHub Actions workflow already exists at {optimize_yaml_path}. Overwrite?",
|
||||
default=False,
|
||||
)
|
||||
]
|
||||
|
||||
overwrite_answers = inquirer.prompt(overwrite_questions, theme=CodeflashTheme())
|
||||
if not overwrite_answers or not overwrite_answers["confirm_overwrite"]:
|
||||
skip_panel = Panel(
|
||||
Text("⏩️ Skipping workflow creation.", style="yellow"), title="⏩️ Skipped", border_style="yellow"
|
||||
)
|
||||
console.print(skip_panel)
|
||||
ph("cli-github-workflow-skipped")
|
||||
return
|
||||
ph(
|
||||
"cli-github-optimization-confirm-workflow-overwrite",
|
||||
{"confirm_overwrite": overwrite_answers["confirm_overwrite"]},
|
||||
)
|
||||
|
||||
confirm_creation_yes = inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message="⚡️Shall I set up a GitHub action that will continuously optimize all new code in GitHub PRs"
|
||||
" for you? This is the main way of using Codeflash so we highly recommend it",
|
||||
default=True,
|
||||
)
|
||||
ph("cli-github-optimization-confirm-workflow-creation", {"confirm_creation": confirm_creation_yes})
|
||||
if not confirm_creation_yes:
|
||||
click.echo("⏩️ Exiting workflow creation.")
|
||||
creation_questions = [
|
||||
inquirer.Confirm(
|
||||
"confirm_creation", message="Set up GitHub Actions for continuous optimization?", default=True
|
||||
)
|
||||
]
|
||||
|
||||
creation_answers = inquirer.prompt(creation_questions, theme=CodeflashTheme())
|
||||
if not creation_answers or not creation_answers["confirm_creation"]:
|
||||
skip_panel = Panel(
|
||||
Text("⏩️ Skipping GitHub Actions setup.", style="yellow"), title="⏩️ Skipped", border_style="yellow"
|
||||
)
|
||||
console.print(skip_panel)
|
||||
ph("cli-github-workflow-skipped")
|
||||
return
|
||||
ph(
|
||||
"cli-github-optimization-confirm-workflow-creation",
|
||||
{"confirm_creation": creation_answers["confirm_creation"]},
|
||||
)
|
||||
workflows_path.mkdir(parents=True, exist_ok=True)
|
||||
from importlib.resources import files
|
||||
|
||||
benchmark_mode = False
|
||||
benchmarks_root = config.get("benchmarks_root", "").strip()
|
||||
if benchmarks_root and benchmarks_root != "":
|
||||
benchmark_mode = inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message="⚡️It looks like you've configured a benchmarks_root in your config. Would you like to run the Github action in benchmark mode? "
|
||||
" This will show the impact of Codeflash's suggested optimizations on your benchmarks",
|
||||
default=True,
|
||||
benchmark_panel = Panel(
|
||||
Text(
|
||||
"📊 Benchmark Mode Available\n\n"
|
||||
"I noticed you've configured a benchmarks_root in your config. "
|
||||
"Benchmark mode will show the performance impact of Codeflash's optimizations on your benchmarks.",
|
||||
style="cyan",
|
||||
),
|
||||
title="📊 Benchmark Mode",
|
||||
border_style="bright_cyan",
|
||||
)
|
||||
console.print(benchmark_panel)
|
||||
console.print()
|
||||
|
||||
benchmark_questions = [
|
||||
inquirer.Confirm("benchmark_mode", message="Run GitHub Actions in benchmark mode?", default=True)
|
||||
]
|
||||
|
||||
benchmark_answers = inquirer.prompt(benchmark_questions, theme=CodeflashTheme())
|
||||
benchmark_mode = benchmark_answers["benchmark_mode"] if benchmark_answers else False
|
||||
|
||||
optimize_yml_content = (
|
||||
files("codeflash").joinpath("cli_cmds", "workflows", "codeflash-optimize.yaml").read_text(encoding="utf-8")
|
||||
|
|
@ -540,29 +732,61 @@ def install_github_actions(override_formatter_check: bool = False) -> None: # n
|
|||
)
|
||||
with optimize_yaml_path.open("w", encoding="utf8") as optimize_yml_file:
|
||||
optimize_yml_file.write(materialized_optimize_yml_content)
|
||||
click.echo(f"{LF}✅ Created GitHub action workflow at {optimize_yaml_path}{LF}")
|
||||
# Success panel for workflow creation
|
||||
workflow_success_panel = Panel(
|
||||
Text(
|
||||
f"✅ Created GitHub action workflow at {optimize_yaml_path}\n\n"
|
||||
"Your repository is now configured for continuous optimization!",
|
||||
style="green",
|
||||
justify="center",
|
||||
),
|
||||
title="🎉 Workflow Created!",
|
||||
border_style="bright_green",
|
||||
)
|
||||
console.print(workflow_success_panel)
|
||||
console.print()
|
||||
|
||||
try:
|
||||
existing_api_key = get_codeflash_api_key()
|
||||
except OSError:
|
||||
existing_api_key = None
|
||||
click.prompt(
|
||||
f"Next, you'll need to add your CODEFLASH_API_KEY as a secret to your GitHub repo.{LF}"
|
||||
f"Press Enter to open your repo's secrets page at {get_github_secrets_page_url(repo)} {LF}"
|
||||
f"Then, click 'New repository secret' to add your api key with the variable name CODEFLASH_API_KEY.{LF}"
|
||||
f"{'Here is your CODEFLASH_API_KEY: ' + existing_api_key + ' ' + LF}"
|
||||
if existing_api_key
|
||||
else "",
|
||||
default="",
|
||||
type=click.STRING,
|
||||
prompt_suffix="",
|
||||
show_default=False,
|
||||
|
||||
# GitHub secrets setup panel
|
||||
secrets_message = (
|
||||
"🔐 Next Step: Add API Key as GitHub Secret\n\n"
|
||||
"You'll need to add your CODEFLASH_API_KEY as a secret to your GitHub repository.\n\n"
|
||||
"📋 Steps:\n"
|
||||
"1. Press Enter to open your repo's secrets page\n"
|
||||
"2. Click 'New repository secret'\n"
|
||||
"3. Add your API key with the variable name CODEFLASH_API_KEY"
|
||||
)
|
||||
|
||||
if existing_api_key:
|
||||
secrets_message += f"\n\n🔑 Your API Key: {existing_api_key}"
|
||||
|
||||
secrets_panel = Panel(
|
||||
Text(secrets_message, style="blue"), title="🔐 GitHub Secrets Setup", border_style="bright_blue"
|
||||
)
|
||||
console.print(secrets_panel)
|
||||
|
||||
console.print(f"\n📍 Press Enter to open: {get_github_secrets_page_url(repo)}")
|
||||
console.input()
|
||||
|
||||
click.launch(get_github_secrets_page_url(repo))
|
||||
click.echo(
|
||||
"🐙 I opened your Github secrets page! Note: if you see a 404, you probably don't have access to this "
|
||||
"repo's secrets; ask a repo admin to add it for you, or (not super recommended) you can temporarily "
|
||||
f"hard-code your api key into the workflow file.{LF}"
|
||||
|
||||
# Post-launch message panel
|
||||
launch_panel = Panel(
|
||||
Text(
|
||||
"🐙 I opened your GitHub secrets page!\n\n"
|
||||
"Note: If you see a 404, you probably don't have access to this repo's secrets. "
|
||||
"Ask a repo admin to add it for you, or (not recommended) you can temporarily "
|
||||
"hard-code your API key into the workflow file.",
|
||||
style="cyan",
|
||||
),
|
||||
title="🌐 Browser Opened",
|
||||
border_style="bright_cyan",
|
||||
)
|
||||
console.print(launch_panel)
|
||||
click.pause()
|
||||
click.echo()
|
||||
click.echo(
|
||||
|
|
@ -751,7 +975,7 @@ def install_github_app() -> None:
|
|||
return
|
||||
owner, repo = get_repo_owner_and_name(git_repo)
|
||||
|
||||
if is_github_app_installed_on_repo(owner, repo):
|
||||
if is_github_app_installed_on_repo(owner, repo, suppress_errors=True):
|
||||
click.echo("🐙 Looks like you've already installed the Codeflash GitHub app on this repository! Continuing…")
|
||||
|
||||
else:
|
||||
|
|
@ -774,7 +998,7 @@ def install_github_app() -> None:
|
|||
)
|
||||
|
||||
count = 2
|
||||
while not is_github_app_installed_on_repo(owner, repo):
|
||||
while not is_github_app_installed_on_repo(owner, repo, suppress_errors=True):
|
||||
if count == 0:
|
||||
click.echo(
|
||||
f"❌ It looks like the Codeflash GitHub App is not installed on the repository {owner}/{repo}.{LF}"
|
||||
|
|
@ -816,8 +1040,18 @@ def prompt_api_key() -> bool:
|
|||
existing_api_key = None
|
||||
if existing_api_key:
|
||||
display_key = f"{existing_api_key[:3]}****{existing_api_key[-4:]}"
|
||||
click.echo(f"🔑 I found a CODEFLASH_API_KEY in your environment [{display_key}]!")
|
||||
|
||||
api_key_panel = Panel(
|
||||
Text(
|
||||
f"🔑 I found a CODEFLASH_API_KEY in your environment [{display_key}]!\n\n"
|
||||
"✅ You're all set with API authentication!",
|
||||
style="green",
|
||||
justify="center",
|
||||
),
|
||||
title="🔑 API Key Found",
|
||||
border_style="bright_green",
|
||||
)
|
||||
console.print(api_key_panel)
|
||||
console.print()
|
||||
return False
|
||||
|
||||
enter_api_key_and_save_to_rc()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import uuid
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
import click
|
||||
from rich.prompt import Confirm
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import argparse
|
||||
|
|
@ -139,7 +139,7 @@ def ask_should_use_checkpoint_get_functions(args: argparse.Namespace) -> Optiona
|
|||
previous_checkpoint_functions = None
|
||||
if args.all and (sys.platform == "linux" or sys.platform == "darwin") and Path("/tmp").is_dir(): # noqa: S108 #TODO: use the temp dir from codeutils-compat.py
|
||||
previous_checkpoint_functions = get_all_historical_functions(args.module_root, Path("/tmp")) # noqa: S108
|
||||
if previous_checkpoint_functions and click.confirm(
|
||||
if previous_checkpoint_functions and Confirm.ask(
|
||||
"Previous Checkpoint detected from an incomplete optimization run, shall I continue the optimization from that point?",
|
||||
default=True,
|
||||
):
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ def find_pyproject_toml(config_file: Path | None = None) -> Path:
|
|||
return config_file
|
||||
# Search for pyproject.toml in the parent directories
|
||||
dir_path = dir_path.parent
|
||||
msg = f"Could not find pyproject.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `poetry init`, or pass the path to pyproject.toml with the --config-file argument."
|
||||
msg = f"Could not find pyproject.toml in the current directory {Path.cwd()} or any of the parent directories. Please create it by running `codeflash init`, or pass the path to pyproject.toml with the --config-file argument."
|
||||
|
||||
raise ValueError(msg)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@ from pathlib import Path
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
import git
|
||||
import inquirer
|
||||
from rich.prompt import Confirm
|
||||
from unidiff import PatchSet
|
||||
|
||||
from codeflash.cli_cmds.cli_common import inquirer_wrapper
|
||||
from codeflash.cli_cmds.console import logger
|
||||
from codeflash.code_utils.config_consts import N_CANDIDATES
|
||||
|
||||
|
|
@ -109,11 +108,9 @@ def check_running_in_git_repo(module_root: str) -> bool:
|
|||
|
||||
def confirm_proceeding_with_no_git_repo() -> str | bool:
|
||||
if sys.__stdin__.isatty():
|
||||
return inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message="WARNING: I did not find a git repository for your code. If you proceed with running codeflash, "
|
||||
"optimized code will"
|
||||
" be written over your current code and you could irreversibly lose your current code. Proceed?",
|
||||
return Confirm.ask(
|
||||
"WARNING: I did not find a git repository for your code. If you proceed with running codeflash, "
|
||||
"optimized code will be written over your current code and you could irreversibly lose your current code. Proceed?",
|
||||
default=False,
|
||||
)
|
||||
# continue running on non-interactive environments, important for GitHub actions
|
||||
|
|
@ -130,11 +127,9 @@ def check_and_push_branch(repo: git.Repo, wait_for_push: bool = False) -> bool:
|
|||
if not sys.__stdin__.isatty():
|
||||
logger.warning("Non-interactive shell detected. Branch will not be pushed.")
|
||||
return False
|
||||
if sys.__stdin__.isatty() and inquirer_wrapper(
|
||||
inquirer.confirm,
|
||||
message=f"⚡️ In order for me to create PRs, your current branch needs to be pushed. Do you want to push "
|
||||
f"the branch"
|
||||
f"'{current_branch}' to the remote repository?",
|
||||
if sys.__stdin__.isatty() and Confirm.ask(
|
||||
f"⚡️ In order for me to create PRs, your current branch needs to be pushed. Do you want to push "
|
||||
f"the branch '{current_branch}' to the remote repository?",
|
||||
default=False,
|
||||
):
|
||||
origin.push(current_branch)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from lsprotocol.types import INITIALIZE, MessageType, LogMessageParams
|
||||
from lsprotocol.types import INITIALIZE, LogMessageParams, MessageType
|
||||
from pygls import uris
|
||||
from pygls.protocol import LanguageServerProtocol, lsp_method
|
||||
from pygls.server import LanguageServer
|
||||
|
|
@ -58,21 +58,22 @@ class CodeflashLanguageServer(LanguageServer):
|
|||
|
||||
def show_message_log(self, message: str, message_type: str) -> None:
|
||||
"""Send a log message to the client's output channel.
|
||||
|
||||
|
||||
Args:
|
||||
message: The message to log
|
||||
message_type: String type - "Info", "Warning", "Error", or "Log"
|
||||
|
||||
"""
|
||||
# Convert string message type to LSP MessageType enum
|
||||
type_mapping = {
|
||||
"Info": MessageType.Info,
|
||||
"Warning": MessageType.Warning,
|
||||
"Warning": MessageType.Warning,
|
||||
"Error": MessageType.Error,
|
||||
"Log": MessageType.Log
|
||||
"Log": MessageType.Log,
|
||||
}
|
||||
|
||||
|
||||
lsp_message_type = type_mapping.get(message_type, MessageType.Info)
|
||||
|
||||
|
||||
# Send log message to client (appears in output channel)
|
||||
log_params = LogMessageParams(type=lsp_message_type, message=message)
|
||||
self.lsp.notify("window/logMessage", log_params)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"""This script is the dedicated entry point for the Codeflash Language Server.
|
||||
It initializes the server and redirects its logs to stderr so that the
|
||||
"""Dedicated entry point for the Codeflash Language Server.
|
||||
|
||||
Initializes the server and redirects its logs to stderr so that the
|
||||
VS Code client can display them in the output channel.
|
||||
|
||||
This script is run by the VS Code extension and is not intended to be
|
||||
|
|
@ -13,7 +14,7 @@ from codeflash.lsp.beta import server
|
|||
|
||||
|
||||
# Configure logging to stderr for VS Code output channel
|
||||
def setup_logging():
|
||||
def setup_logging() -> logging.Logger:
|
||||
# Clear any existing handlers to prevent conflicts
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers.clear()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def initialize_posthog(enabled: bool = True) -> None: # noqa: FBT001, FBT002
|
|||
return
|
||||
|
||||
global _posthog # noqa: PLW0603
|
||||
_posthog = Posthog(project_api_key="phc_aUO790jHd7z1SXwsYCz8dRApxueplZlZWeDSpKc5hol", host="https://us.posthog.com") # type: ignore # noqa: PGH003
|
||||
_posthog = Posthog(project_api_key="phc_aUO790jHd7z1SXwsYCz8dRApxueplZlZWeDSpKc5hol", host="https://us.posthog.com")
|
||||
_posthog.log.setLevel(logging.CRITICAL) # Suppress PostHog logging
|
||||
ph("cli-telemetry-enabled")
|
||||
|
||||
|
|
@ -41,6 +41,6 @@ def ph(event: str, properties: dict[str, Any] | None = None) -> None:
|
|||
user_id = get_user_id()
|
||||
|
||||
if user_id:
|
||||
_posthog.capture(distinct_id=user_id, event=event, properties=properties) # type: ignore # noqa: PGH003
|
||||
_posthog.capture(distinct_id=user_id, event=event, properties=properties)
|
||||
else:
|
||||
logger.debug("Failed to log event to PostHog: User ID could not be retrieved.")
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class TestGitUtils(unittest.TestCase):
|
|||
|
||||
@patch("codeflash.code_utils.git_utils.git.Repo")
|
||||
@patch("codeflash.code_utils.git_utils.sys.__stdin__.isatty", return_value=True)
|
||||
@patch("codeflash.code_utils.git_utils.inquirer.confirm", return_value=True)
|
||||
@patch("codeflash.code_utils.git_utils.Confirm.ask", return_value=True)
|
||||
def test_check_and_push_branch(self, mock_confirm, mock_isatty, mock_repo):
|
||||
mock_repo_instance = mock_repo.return_value
|
||||
mock_repo_instance.active_branch.name = "test-branch"
|
||||
|
|
|
|||
Loading…
Reference in a new issue