Skip to content

Commit 1214f98

Browse files
committed
Merge branch 'main' of https://github.com/codeflash-ai/codeflash into uv-migrate
2 parents 86450b3 + 96e2a2e commit 1214f98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3285
-853
lines changed

.github/workflows/codeflash-optimize.yaml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ concurrency:
1212

1313
jobs:
1414
optimize:
15-
name: Optimize new code in this PR
15+
name: Optimize new Python code
1616
if: ${{ github.actor != 'codeflash-ai[bot]' }}
1717
runs-on: ubuntu-latest
1818
env:
@@ -22,16 +22,24 @@ jobs:
2222
CODEFLASH_PR_NUMBER: ${{ github.event.number }}
2323
COLUMNS: 110
2424
steps:
25-
- uses: actions/checkout@v4
25+
- name: 🛎️ Checkout
26+
uses: actions/checkout@v4
2627
with:
2728
fetch-depth: 0
2829

29-
- name: Set up Python 3.11 for CLI
30+
- name: 🐍 Set up Python 3.11 for CLI
3031
uses: astral-sh/setup-uv@v5
3132
with:
3233
python-version: 3.11.6
3334

34-
- name: Run Codeflash to optimize code
35+
- name: 📦 Install dependencies (CLI)
36+
run: |
37+
uv tool install poetry
38+
uv venv
39+
source .venv/bin/activate
40+
poetry install --with dev
41+
42+
- name: ⚡️Codeflash Optimization
3543
id: optimize_code
3644
run: |
3745
uv run codeflash
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from code_to_optimize.bubble_sort_in_class import BubbleSortClass
2+
3+
4+
def sort_classmethod(x):
5+
y = BubbleSortClass()
6+
return y.sorter(x)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from code_to_optimize.bubble_sort_in_nested_class import WrapperClass
2+
3+
4+
def sort_classmethod(x):
5+
y = WrapperClass.BubbleSortClass()
6+
return y.sorter(x)

code_to_optimize/code_directories/simple_tracer_e2e/workload.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from concurrent.futures import ThreadPoolExecutor
2+
3+
24
def funcA(number):
35
k = 0
46
for i in range(number * 100):
@@ -9,6 +11,7 @@ def funcA(number):
911
# Use a generator expression directly in join for more efficiency
1012
return " ".join(str(i) for i in range(number))
1113

14+
1215
def test_threadpool() -> None:
1316
pool = ThreadPoolExecutor(max_workers=3)
1417
args = list(range(10, 31, 10))
@@ -19,4 +22,4 @@ def test_threadpool() -> None:
1922

2023

2124
if __name__ == "__main__":
22-
test_threadpool()
25+
test_threadpool()

codeflash/api/aiservice.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,76 @@ def optimize_python_code(
135135
console.rule()
136136
return []
137137

138+
def optimize_python_code_line_profiler(
139+
self,
140+
source_code: str,
141+
dependency_code: str,
142+
trace_id: str,
143+
line_profiler_results: str,
144+
num_candidates: int = 10,
145+
experiment_metadata: ExperimentMetadata | None = None,
146+
) -> list[OptimizedCandidate]:
147+
"""Optimize the given python code for performance by making a request to the Django endpoint.
148+
149+
Parameters
150+
----------
151+
- source_code (str): The python code to optimize.
152+
- dependency_code (str): The dependency code used as read-only context for the optimization
153+
- trace_id (str): Trace id of optimization run
154+
- num_candidates (int): Number of optimization variants to generate. Default is 10.
155+
- experiment_metadata (Optional[ExperimentalMetadata, None]): Any available experiment metadata for this optimization
156+
157+
Returns
158+
-------
159+
- List[OptimizationCandidate]: A list of Optimization Candidates.
160+
161+
"""
162+
payload = {
163+
"source_code": source_code,
164+
"dependency_code": dependency_code,
165+
"num_variants": num_candidates,
166+
"line_profiler_results": line_profiler_results,
167+
"trace_id": trace_id,
168+
"python_version": platform.python_version(),
169+
"experiment_metadata": experiment_metadata,
170+
"codeflash_version": codeflash_version,
171+
}
172+
173+
logger.info("Generating optimized candidates…")
174+
console.rule()
175+
if line_profiler_results=="":
176+
logger.info("No LineProfiler results were provided, Skipping optimization.")
177+
console.rule()
178+
return []
179+
try:
180+
response = self.make_ai_service_request("/optimize-line-profiler", payload=payload, timeout=600)
181+
except requests.exceptions.RequestException as e:
182+
logger.exception(f"Error generating optimized candidates: {e}")
183+
ph("cli-optimize-error-caught", {"error": str(e)})
184+
return []
185+
186+
if response.status_code == 200:
187+
optimizations_json = response.json()["optimizations"]
188+
logger.info(f"Generated {len(optimizations_json)} candidates.")
189+
console.rule()
190+
return [
191+
OptimizedCandidate(
192+
source_code=opt["source_code"],
193+
explanation=opt["explanation"],
194+
optimization_id=opt["optimization_id"],
195+
)
196+
for opt in optimizations_json
197+
]
198+
try:
199+
error = response.json()["error"]
200+
except Exception:
201+
error = response.text
202+
logger.error(f"Error generating optimized candidates: {response.status_code} - {error}")
203+
ph("cli-optimize-error-response", {"response_status_code": response.status_code, "error": error})
204+
console.rule()
205+
return []
206+
207+
138208
def log_results(
139209
self,
140210
function_trace_id: str,

codeflash/api/cfapi.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import TYPE_CHECKING, Any, Optional
99

1010
import requests
11+
import sentry_sdk
1112
from pydantic.json import pydantic_encoder
1213

1314
from codeflash.cli_cmds.console import console, logger
@@ -194,7 +195,8 @@ def get_blocklisted_functions() -> dict[str, set[str]] | dict[str, Any]:
194195
req.raise_for_status()
195196
content: dict[str, list[str]] = req.json()
196197
except Exception as e:
197-
logger.error(f"Error getting blocklisted functions: {e}", exc_info=True)
198+
logger.error(f"Error getting blocklisted functions: {e}")
199+
sentry_sdk.capture_exception(e)
198200
return {}
199201

200202
return {Path(k).name: {v.replace("()", "") for v in values} for k, values in content.items()}

codeflash/cli_cmds/cli.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,22 @@ def process_pyproject_config(args: Namespace) -> Namespace:
128128
assert args.tests_root is not None, "--tests-root must be specified"
129129
assert Path(args.tests_root).is_dir(), f"--tests-root {args.tests_root} must be a valid directory"
130130

131-
assert not (env_utils.get_pr_number() is not None and not env_utils.ensure_codeflash_api_key()), (
132-
"Codeflash API key not found. When running in a Github Actions Context, provide the "
133-
"'CODEFLASH_API_KEY' environment variable as a secret.\n"
134-
"You can add a secret by going to your repository's settings page, then clicking 'Secrets' in the left sidebar.\n"
135-
"Then, click 'New repository secret' and add your api key with the variable name CODEFLASH_API_KEY.\n"
136-
f"Here's a direct link: {get_github_secrets_page_url()}\n"
137-
"Exiting..."
138-
)
131+
if env_utils.get_pr_number() is not None:
132+
assert env_utils.ensure_codeflash_api_key(), (
133+
"Codeflash API key not found. When running in a Github Actions Context, provide the "
134+
"'CODEFLASH_API_KEY' environment variable as a secret.\n"
135+
"You can add a secret by going to your repository's settings page, then clicking 'Secrets' in the left sidebar.\n"
136+
"Then, click 'New repository secret' and add your api key with the variable name CODEFLASH_API_KEY.\n"
137+
f"Here's a direct link: {get_github_secrets_page_url()}\n"
138+
"Exiting..."
139+
)
140+
141+
repo = git.Repo(search_parent_directories=True)
142+
143+
owner, repo_name = get_repo_owner_and_name(repo)
144+
145+
require_github_app_or_exit(owner, repo_name)
146+
139147
if hasattr(args, "ignore_paths") and args.ignore_paths is not None:
140148
normalized_ignore_paths = []
141149
for path in args.ignore_paths:

codeflash/cli_cmds/cmd_init.py

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,27 @@ def init_codeflash() -> None:
6767

6868
did_add_new_key = prompt_api_key()
6969

70-
setup_info: SetupInfo = collect_setup_info()
70+
if should_modify_pyproject_toml():
7171

72-
configure_pyproject_toml(setup_info)
72+
setup_info: SetupInfo = collect_setup_info()
73+
74+
configure_pyproject_toml(setup_info)
7375

7476
install_github_app()
7577

76-
install_github_actions()
78+
install_github_actions(override_formatter_check=True)
79+
80+
module_string = ""
81+
if "setup_info" in locals():
82+
module_string = f" you selected ({setup_info.module_root})"
83+
7784

7885
click.echo(
7986
f"{LF}"
8087
f"⚡️ Codeflash is now set up! You can now run:{LF}"
8188
f" codeflash --file <path-to-file> --function <function-name> to optimize a function within a file{LF}"
8289
f" codeflash --file <path-to-file> to optimize all functions in a file{LF}"
83-
f" codeflash --all to optimize all functions in all files in the module you selected ({setup_info.module_root}){LF}"
90+
f" codeflash --all to optimize all functions in all files in the module{module_string}{LF}"
8491
f"-or-{LF}"
8592
f" codeflash --help to see all options{LF}"
8693
)
@@ -116,6 +123,30 @@ def ask_run_end_to_end_test(args: Namespace) -> None:
116123
bubble_sort_path, bubble_sort_test_path = create_bubble_sort_file_and_test(args)
117124
run_end_to_end_test(args, bubble_sort_path, bubble_sort_test_path)
118125

126+
def should_modify_pyproject_toml() -> bool:
127+
"""
128+
Check if the current directory contains a valid pyproject.toml file with codeflash config
129+
If it does, ask the user if they want to re-configure it.
130+
"""
131+
from rich.prompt import Confirm
132+
pyproject_toml_path = Path.cwd() / "pyproject.toml"
133+
if not pyproject_toml_path.exists():
134+
return True
135+
try:
136+
config, config_file_path = parse_config_file(pyproject_toml_path)
137+
except Exception as e:
138+
return True
139+
140+
if "module_root" not in config or config["module_root"] is None or not Path(config["module_root"]).is_dir():
141+
return True
142+
if "tests_root" not in config or config["tests_root"] is None or not Path(config["tests_root"]).is_dir():
143+
return True
144+
145+
create_toml = Confirm.ask(
146+
f"✅ A valid Codeflash config already exists in this project. Do you want to re-configure it?", default=False, show_default=True
147+
)
148+
return create_toml
149+
119150

120151
def collect_setup_info() -> SetupInfo:
121152
curdir = Path.cwd()
@@ -362,9 +393,9 @@ def check_for_toml_or_setup_file() -> str | None:
362393
return cast(str, project_name)
363394

364395

365-
def install_github_actions() -> None:
396+
def install_github_actions(override_formatter_check: bool = False) -> None:
366397
try:
367-
config, config_file_path = parse_config_file()
398+
config, config_file_path = parse_config_file(override_formatter_check=override_formatter_check)
368399

369400
ph("cli-github-actions-install-started")
370401
try:
@@ -504,11 +535,11 @@ def get_dependency_manager_installation_string(dep_manager: DependencyManager) -
504535
py_version = sys.version_info
505536
python_version_string = f"'{py_version.major}.{py_version.minor}'"
506537
if dep_manager == DependencyManager.UV:
507-
return """name: Setup UV
538+
return """name: 🐍 Setup UV
508539
uses: astral-sh/setup-uv@v4
509540
with:
510541
enable-cache: true"""
511-
return f"""name: Set up Python
542+
return f"""name: 🐍 Set up Python
512543
uses: actions/setup-python@v5
513544
with:
514545
python-version: {python_version_string}"""

codeflash/cli_cmds/console.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@
77

88
from rich.console import Console
99
from rich.logging import RichHandler
10-
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn
10+
from rich.progress import (
11+
BarColumn,
12+
MofNCompleteColumn,
13+
Progress,
14+
SpinnerColumn,
15+
TaskProgressColumn,
16+
TextColumn,
17+
TimeElapsedColumn,
18+
TimeRemainingColumn,
19+
)
1120

1221
from codeflash.cli_cmds.console_constants import SPINNER_TYPES
1322
from codeflash.cli_cmds.logging_config import BARE_LOGGING_FORMAT
@@ -22,15 +31,26 @@
2231
console = Console()
2332
logging.basicConfig(
2433
level=logging.INFO,
25-
handlers=[RichHandler(rich_tracebacks=True, markup=False, console=console, show_path=False, show_time=False)],
34+
handlers=[
35+
RichHandler(
36+
rich_tracebacks=True,
37+
markup=False,
38+
console=console,
39+
show_path=False,
40+
show_time=False,
41+
)
42+
],
2643
format=BARE_LOGGING_FORMAT,
2744
)
2845

2946
logger = logging.getLogger("rich")
47+
logging.getLogger("parso").setLevel(logging.WARNING)
3048

3149

3250
def paneled_text(
33-
text: str, panel_args: dict[str, str | bool] | None = None, text_args: dict[str, str] | None = None
51+
text: str,
52+
panel_args: dict[str, str | bool] | None = None,
53+
text_args: dict[str, str] | None = None,
3454
) -> None:
3555
"""Print text in a panel."""
3656
from rich.panel import Panel
@@ -57,7 +77,9 @@ def code_print(code_str: str) -> None:
5777

5878

5979
@contextmanager
60-
def progress_bar(message: str, *, transient: bool = False) -> Generator[TaskID, None, None]:
80+
def progress_bar(
81+
message: str, *, transient: bool = False
82+
) -> Generator[TaskID, None, None]:
6183
"""Display a progress bar with a spinner and elapsed time."""
6284
progress = Progress(
6385
SpinnerColumn(next(spinners)),
@@ -69,3 +91,25 @@ def progress_bar(message: str, *, transient: bool = False) -> Generator[TaskID,
6991
task = progress.add_task(message, total=None)
7092
with progress:
7193
yield task
94+
95+
96+
@contextmanager
97+
def test_files_progress_bar(
98+
total: int, description: str
99+
) -> Generator[tuple[Progress, TaskID], None, None]:
100+
"""Progress bar for test files."""
101+
with Progress(
102+
SpinnerColumn(next(spinners)),
103+
TextColumn("[progress.description]{task.description}"),
104+
BarColumn(
105+
complete_style="cyan",
106+
finished_style="green",
107+
pulse_style="yellow",
108+
),
109+
MofNCompleteColumn(),
110+
TimeElapsedColumn(),
111+
TimeRemainingColumn(),
112+
transient=True,
113+
) as progress:
114+
task_id = progress.add_task(description, total=total)
115+
yield progress, task_id

codeflash/cli_cmds/workflows/codeflash-optimize.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ concurrency:
1515

1616
jobs:
1717
optimize:
18-
name: Optimize new Python code in this PR
18+
name: Optimize new Python code
1919
# Don't run codeflash on codeflash-ai[bot] commits, prevent duplicate optimizations
2020
if: ${{ github.actor != 'codeflash-ai[bot]' }}
2121
runs-on: ubuntu-latest
@@ -24,11 +24,12 @@ jobs:
2424
CODEFLASH_PR_NUMBER: ${{ github.event.number }}
2525
{{ working_directory }}
2626
steps:
27-
- uses: actions/checkout@v4
27+
- name: 🛎️ Checkout
28+
uses: actions/checkout@v4
2829
with:
2930
fetch-depth: 0
3031
- {{ setup_python_dependency_manager }}
31-
- name: Install Project Dependencies
32+
- name: 📦 Install Dependencies
3233
run: {{ install_dependencies_command }}
33-
- name: Run Codeflash to optimize code
34+
- name: ⚡️Codeflash Optimization
3435
run: {{ codeflash_command }}

0 commit comments

Comments
 (0)