codeflash 0.3.4__tar.gz → 0.3.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codeflash-0.3.4 → codeflash-0.3.6}/PKG-INFO +3 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/analytics/posthog.py +20 -5
- codeflash-0.3.6/codeflash/analytics/sentry.py +14 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/api/cfapi.py +10 -8
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/cli.py +13 -8
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/cmd_init.py +166 -85
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/workflows/codeflash-optimize.yaml +6 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_extractor.py +24 -7
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_replacer.py +86 -40
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_utils.py +20 -12
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/config_consts.py +2 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/config_parser.py +13 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/env_utils.py +33 -3
- codeflash-0.3.6/codeflash/code_utils/formatter.py +26 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/git_utils.py +15 -16
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/instrument_existing_tests.py +296 -140
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/discovery/discover_unit_tests.py +114 -73
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/discovery/functions_to_optimize.py +27 -8
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/github/PrComment.py +1 -2
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/main.py +335 -174
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/optimization/function_context.py +60 -35
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/result/create_pr.py +21 -8
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/tracing/tracer.py +1 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/update_license_version.py +2 -2
- codeflash-0.3.6/codeflash/verification/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/comparator.py +1 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/parse_test_output.py +57 -53
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/test_runner.py +1 -1
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/verifier.py +13 -4
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/version.py +2 -2
- {codeflash-0.3.4 → codeflash-0.3.6}/pyproject.toml +18 -10
- codeflash-0.3.4/codeflash/code_utils/linter.py +0 -18
- {codeflash-0.3.4 → codeflash-0.3.6}/README.md +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/LICENSE +0 -0
- {codeflash-0.3.4/codeflash/analytics → codeflash-0.3.6/codeflash}/__init__.py +0 -0
- {codeflash-0.3.4/codeflash/api → codeflash-0.3.6/codeflash/analytics}/__init__.py +0 -0
- {codeflash-0.3.4/codeflash/cli_cmds → codeflash-0.3.6/codeflash/api}/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/api/aiservice.py +0 -0
- {codeflash-0.3.4/codeflash/code_utils → codeflash-0.3.6/codeflash/cli_cmds}/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/logging_config.py +0 -0
- {codeflash-0.3.4/codeflash/discovery → codeflash-0.3.6/codeflash/code_utils}/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/sqlalchemy_utils.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/time_utils.py +0 -0
- {codeflash-0.3.4/codeflash/github → codeflash-0.3.6/codeflash/discovery}/__init__.py +0 -0
- {codeflash-0.3.4/codeflash/optimization → codeflash-0.3.6/codeflash/github}/__init__.py +0 -0
- {codeflash-0.3.4/codeflash/result → codeflash-0.3.6/codeflash/optimization}/__init__.py +0 -0
- {codeflash-0.3.4/codeflash/tracing → codeflash-0.3.6/codeflash/result}/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/result/explanation.py +0 -0
- {codeflash-0.3.4/codeflash/verification → codeflash-0.3.6/codeflash/tracing}/__init__.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/tracing/replay_test.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/equivalence.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/test_results.py +0 -0
- {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/verification_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: codeflash
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: A Code Performance Optimization Library
|
|
5
5
|
Home-page: https://codeflash.ai
|
|
6
6
|
Author: CodeFlash Inc.
|
|
@@ -19,10 +19,12 @@ Requires-Dist: inquirer (>=3.0.0)
|
|
|
19
19
|
Requires-Dist: jedi (>=0.19.0)
|
|
20
20
|
Requires-Dist: junitparser (>=3.1.0)
|
|
21
21
|
Requires-Dist: libcst (>=1.0.1)
|
|
22
|
+
Requires-Dist: parameterized (>=0.9.0)
|
|
22
23
|
Requires-Dist: posthog (>=3.0.0)
|
|
23
24
|
Requires-Dist: pydantic (>=1.10.1)
|
|
24
25
|
Requires-Dist: pytest (>=7.0.0)
|
|
25
26
|
Requires-Dist: pytest-timeout (>=2.1.0)
|
|
27
|
+
Requires-Dist: sentry-sdk (>=1.40.6,<2.0.0)
|
|
26
28
|
Requires-Dist: tiktoken (>=0.3.2)
|
|
27
29
|
Requires-Dist: timeout-decorator (>=0.5.0)
|
|
28
30
|
Requires-Dist: tomlkit (>=0.11.7)
|
|
@@ -11,18 +11,33 @@ _posthog = Posthog(
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
_ANALYTICS_ENABLED = True
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def enable_analytics(enabled: bool) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Enable or disable analytics.
|
|
20
|
+
:param enabled: Whether to enable analytics.
|
|
21
|
+
"""
|
|
22
|
+
if enabled:
|
|
23
|
+
ph("cli-analytics-enabled")
|
|
24
|
+
else:
|
|
25
|
+
ph("cli-analytics-disabled")
|
|
26
|
+
global _ANALYTICS_ENABLED
|
|
27
|
+
_ANALYTICS_ENABLED = enabled
|
|
28
|
+
|
|
29
|
+
|
|
14
30
|
def ph(event: str, properties: Dict[str, Any] = None) -> None:
|
|
15
31
|
"""
|
|
16
32
|
Log an event to PostHog.
|
|
17
33
|
:param event: The name of the event.
|
|
18
34
|
:param properties: A dictionary of properties to attach to the event.
|
|
19
35
|
"""
|
|
36
|
+
if not _ANALYTICS_ENABLED:
|
|
37
|
+
return
|
|
20
38
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
properties["cli_version"] = __version__
|
|
25
|
-
properties["cli_version_tuple"] = __version_tuple__
|
|
39
|
+
properties = properties or {}
|
|
40
|
+
properties.update({"cli_version": __version__, "cli_version_tuple": __version_tuple__})
|
|
26
41
|
|
|
27
42
|
user_id = get_user_id()
|
|
28
43
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import sentry_sdk
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def init_sentry():
|
|
5
|
+
sentry_sdk.init(
|
|
6
|
+
dsn="https://4b9a1902f9361b48c04376df6483bc96@o4506833230561280.ingest.sentry.io/4506833262477312",
|
|
7
|
+
# Set traces_sample_rate to 1.0 to capture 100%
|
|
8
|
+
# of transactions for performance monitoring.
|
|
9
|
+
traces_sample_rate=1.0,
|
|
10
|
+
# Set profiles_sample_rate to 1.0 to profile 100%
|
|
11
|
+
# of sampled transactions.
|
|
12
|
+
# We recommend adjusting this value in production.
|
|
13
|
+
profiles_sample_rate=1.0,
|
|
14
|
+
)
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
-
from functools import lru_cache
|
|
5
|
-
from typing import Optional, Dict, Any
|
|
6
|
-
|
|
7
4
|
import requests
|
|
5
|
+
from functools import lru_cache
|
|
8
6
|
from pydantic.json import pydantic_encoder
|
|
9
7
|
from requests import Response
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
10
9
|
|
|
11
10
|
from codeflash.code_utils.env_utils import get_codeflash_api_key
|
|
12
|
-
from codeflash.github.PrComment import PrComment
|
|
11
|
+
from codeflash.github.PrComment import PrComment, FileDiffContent
|
|
13
12
|
|
|
14
13
|
if os.environ.get("CFAPI_SERVER", default="prod").lower() == "local":
|
|
15
14
|
CFAPI_BASE_URL = "http://localhost:3001"
|
|
@@ -59,8 +58,9 @@ def suggest_changes(
|
|
|
59
58
|
owner: str,
|
|
60
59
|
repo: str,
|
|
61
60
|
pr_number: int,
|
|
62
|
-
file_changes: dict[str,
|
|
61
|
+
file_changes: dict[str, FileDiffContent],
|
|
63
62
|
pr_comment: PrComment,
|
|
63
|
+
existing_tests: str,
|
|
64
64
|
generated_tests: str,
|
|
65
65
|
) -> Response:
|
|
66
66
|
"""
|
|
@@ -81,6 +81,7 @@ def suggest_changes(
|
|
|
81
81
|
"pullNumber": pr_number,
|
|
82
82
|
"diffContents": file_changes,
|
|
83
83
|
"prCommentFields": pr_comment.to_json(),
|
|
84
|
+
"existingTests": existing_tests,
|
|
84
85
|
"generatedTests": generated_tests,
|
|
85
86
|
}
|
|
86
87
|
response = make_cfapi_request(endpoint="/suggest-pr-changes", method="POST", payload=payload)
|
|
@@ -91,8 +92,9 @@ def create_pr(
|
|
|
91
92
|
owner: str,
|
|
92
93
|
repo: str,
|
|
93
94
|
base_branch: str,
|
|
94
|
-
file_changes: dict[str,
|
|
95
|
+
file_changes: dict[str, FileDiffContent],
|
|
95
96
|
pr_comment: PrComment,
|
|
97
|
+
existing_tests: str,
|
|
96
98
|
generated_tests: str,
|
|
97
99
|
) -> Response:
|
|
98
100
|
"""
|
|
@@ -111,6 +113,7 @@ def create_pr(
|
|
|
111
113
|
"baseBranch": base_branch,
|
|
112
114
|
"diffContents": file_changes,
|
|
113
115
|
"prCommentFields": pr_comment.to_json(),
|
|
116
|
+
"existingTests": existing_tests,
|
|
114
117
|
"generatedTests": generated_tests,
|
|
115
118
|
}
|
|
116
119
|
response = make_cfapi_request(endpoint="/create-pr", method="POST", payload=payload)
|
|
@@ -124,8 +127,7 @@ def check_github_app_installed_on_repo(owner: str, repo: str) -> Response:
|
|
|
124
127
|
:param repo: The name of the repository.
|
|
125
128
|
:return: The response object.
|
|
126
129
|
"""
|
|
127
|
-
|
|
130
|
+
return make_cfapi_request(
|
|
128
131
|
endpoint=f"/is-github-app-installed?repo={repo}&owner={owner}",
|
|
129
132
|
method="GET",
|
|
130
133
|
)
|
|
131
|
-
return response
|
|
@@ -11,7 +11,6 @@ from codeflash.cli_cmds.logging_config import LOGGING_FORMAT
|
|
|
11
11
|
from codeflash.code_utils import env_utils
|
|
12
12
|
from codeflash.code_utils.config_parser import parse_config_file
|
|
13
13
|
from codeflash.code_utils.git_utils import (
|
|
14
|
-
git_root_dir,
|
|
15
14
|
get_repo_owner_and_name,
|
|
16
15
|
get_github_secrets_page_url,
|
|
17
16
|
)
|
|
@@ -34,7 +33,11 @@ def process_cmd_args(args: Namespace) -> Namespace:
|
|
|
34
33
|
raise ValueError(f"File {args.file} does not exist")
|
|
35
34
|
args.file = os.path.realpath(args.file)
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
try:
|
|
37
|
+
pyproject_config = parse_config_file(args.config_file)
|
|
38
|
+
except ValueError as e:
|
|
39
|
+
logging.error(e.args[0])
|
|
40
|
+
exit(1)
|
|
38
41
|
supported_keys = [
|
|
39
42
|
"module_root",
|
|
40
43
|
"tests_root",
|
|
@@ -42,6 +45,8 @@ def process_cmd_args(args: Namespace) -> Namespace:
|
|
|
42
45
|
"ignore_paths",
|
|
43
46
|
"minimum_performance_gain",
|
|
44
47
|
"pytest_cmd",
|
|
48
|
+
"formatter_cmd",
|
|
49
|
+
"enable_analytics",
|
|
45
50
|
]
|
|
46
51
|
for key in supported_keys:
|
|
47
52
|
if key in pyproject_config:
|
|
@@ -82,15 +87,14 @@ def handle_optimize_all_arg_parsing(args: Namespace) -> Namespace:
|
|
|
82
87
|
if hasattr(args, "all"):
|
|
83
88
|
# Ensure that the user can actually open PRs on the repo.
|
|
84
89
|
try:
|
|
85
|
-
|
|
86
|
-
git_root_dir(repo)
|
|
90
|
+
git_repo = git.Repo(search_parent_directories=True)
|
|
87
91
|
except git.exc.InvalidGitRepositoryError:
|
|
88
92
|
logging.error(
|
|
89
|
-
"
|
|
90
|
-
"
|
|
93
|
+
"I couldn't find a git repository in the current directory. "
|
|
94
|
+
"I need a git repository to run --all and open PRs for optimizations. Exiting..."
|
|
91
95
|
)
|
|
92
96
|
exit(1)
|
|
93
|
-
owner, repo = get_repo_owner_and_name(
|
|
97
|
+
owner, repo = get_repo_owner_and_name(git_repo)
|
|
94
98
|
try:
|
|
95
99
|
response = check_github_app_installed_on_repo(owner, repo)
|
|
96
100
|
if response.ok and response.text == "true":
|
|
@@ -100,7 +104,8 @@ def handle_optimize_all_arg_parsing(args: Namespace) -> Namespace:
|
|
|
100
104
|
raise Exception
|
|
101
105
|
except Exception as e:
|
|
102
106
|
logging.error(
|
|
103
|
-
f"Could not find the CodeFlash GitHub App installed on the repository {owner}/{repo}
|
|
107
|
+
f"Could not find the CodeFlash GitHub App installed on the repository {owner}/{repo} or the GitHub"
|
|
108
|
+
f" account linked to your CODEFLASH_API_KEY does not have access to the repository {owner}/{repo}. "
|
|
104
109
|
"Please install the CodeFlash GitHub App on your repository to use --all."
|
|
105
110
|
" Instructions at https://app.codeflash.ai \n"
|
|
106
111
|
"Exiting..."
|
|
@@ -8,11 +8,17 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
import inquirer
|
|
11
|
+
import inquirer.themes
|
|
11
12
|
import tomlkit
|
|
12
13
|
from git import Repo
|
|
13
14
|
|
|
14
15
|
from codeflash.analytics.posthog import ph
|
|
15
|
-
from codeflash.code_utils.env_utils import
|
|
16
|
+
from codeflash.code_utils.env_utils import (
|
|
17
|
+
get_codeflash_api_key,
|
|
18
|
+
read_api_key_from_shell_config,
|
|
19
|
+
SHELL_RC_EXPORT_PATTERN,
|
|
20
|
+
)
|
|
21
|
+
from codeflash.code_utils.env_utils import get_shell_rc_path
|
|
16
22
|
from codeflash.code_utils.git_utils import get_github_secrets_page_url
|
|
17
23
|
from codeflash.version import __version__ as version
|
|
18
24
|
|
|
@@ -58,6 +64,8 @@ def init_codeflash():
|
|
|
58
64
|
click.echo(
|
|
59
65
|
"🐚 Don't forget to restart your shell to load the CODEFLASH_API_KEY environment variable!"
|
|
60
66
|
)
|
|
67
|
+
click.echo("Or run the following command to reload:")
|
|
68
|
+
click.echo(f" source {get_shell_rc_path()}")
|
|
61
69
|
|
|
62
70
|
ph("cli-installation-successful", {"did_add_new_key": did_add_new_key})
|
|
63
71
|
|
|
@@ -72,7 +80,7 @@ def ask_run_end_to_end_test(setup_info):
|
|
|
72
80
|
)
|
|
73
81
|
]
|
|
74
82
|
)
|
|
75
|
-
run_tests = run_tests_answer
|
|
83
|
+
run_tests = run_tests_answer.get("run_tests", False)
|
|
76
84
|
if run_tests:
|
|
77
85
|
create_bubble_sort_file(setup_info)
|
|
78
86
|
run_end_to_end_test(setup_info)
|
|
@@ -120,9 +128,11 @@ def collect_setup_info(setup_info: dict[str, str]):
|
|
|
120
128
|
message="Which Python module do you want me to optimize going forward?\n"
|
|
121
129
|
+ "(This is usually the top-most directory where all your Python source code is located)",
|
|
122
130
|
choices=module_subdir_options,
|
|
123
|
-
default=
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
default=(
|
|
132
|
+
project_name
|
|
133
|
+
if project_name in module_subdir_options
|
|
134
|
+
else module_subdir_options[0]
|
|
135
|
+
),
|
|
126
136
|
)
|
|
127
137
|
]
|
|
128
138
|
)
|
|
@@ -134,6 +144,8 @@ def collect_setup_info(setup_info: dict[str, str]):
|
|
|
134
144
|
default_tests_subdir = "tests"
|
|
135
145
|
create_for_me_option = "okay, create a tests/ directory for me!"
|
|
136
146
|
test_subdir_options = valid_subdirs if len(valid_subdirs) > 0 else [create_for_me_option]
|
|
147
|
+
custom_dir_option = "enter a custom directory..."
|
|
148
|
+
test_subdir_options.append(custom_dir_option)
|
|
137
149
|
tests_root_answer = inquirer.prompt(
|
|
138
150
|
[
|
|
139
151
|
inquirer.List(
|
|
@@ -141,9 +153,11 @@ def collect_setup_info(setup_info: dict[str, str]):
|
|
|
141
153
|
message="Where are your tests located? "
|
|
142
154
|
"(If you don't have any tests yet, I can create an empty tests/ directory for you)",
|
|
143
155
|
choices=test_subdir_options,
|
|
144
|
-
default=
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
default=(
|
|
157
|
+
default_tests_subdir
|
|
158
|
+
if default_tests_subdir in test_subdir_options
|
|
159
|
+
else test_subdir_options[0]
|
|
160
|
+
),
|
|
147
161
|
)
|
|
148
162
|
]
|
|
149
163
|
)
|
|
@@ -151,7 +165,20 @@ def collect_setup_info(setup_info: dict[str, str]):
|
|
|
151
165
|
if tests_root == create_for_me_option:
|
|
152
166
|
tests_root = os.path.join(curdir, default_tests_subdir)
|
|
153
167
|
os.mkdir(tests_root)
|
|
154
|
-
click.echo(f"✅ Created directory {tests_root}
|
|
168
|
+
click.echo(f"✅ Created directory {tests_root}/\n")
|
|
169
|
+
elif tests_root == custom_dir_option:
|
|
170
|
+
custom_tests_root_answer = inquirer.prompt(
|
|
171
|
+
[
|
|
172
|
+
inquirer.Path(
|
|
173
|
+
"custom_tests_root", # Removed the colon and space from the message
|
|
174
|
+
message=f"Enter the path to your tests directory inside {os.path.abspath(module_root) + os.sep} ",
|
|
175
|
+
path_type=inquirer.Path.DIRECTORY,
|
|
176
|
+
exists=True,
|
|
177
|
+
normalize_to_absolute_path=True,
|
|
178
|
+
),
|
|
179
|
+
]
|
|
180
|
+
)
|
|
181
|
+
tests_root = custom_tests_root_answer["custom_tests_root"]
|
|
155
182
|
setup_info["tests_root"] = os.path.relpath(tests_root, curdir)
|
|
156
183
|
ph("cli-tests-root-provided")
|
|
157
184
|
|
|
@@ -179,22 +206,38 @@ def collect_setup_info(setup_info: dict[str, str]):
|
|
|
179
206
|
ignore_paths = []
|
|
180
207
|
setup_info["ignore_paths"] = ignore_paths
|
|
181
208
|
|
|
209
|
+
# Ask the user if they agree to enable PostHog analytics logging
|
|
210
|
+
# enable_analytics_question = [
|
|
211
|
+
# inquirer.List(
|
|
212
|
+
# "enable_analytics",
|
|
213
|
+
# message="⚡️ Is it OK to collect usage analytics to help improve CodeFlash? (recommended)",
|
|
214
|
+
# choices=[
|
|
215
|
+
# ("Sure, I'd love to help make CodeFlash better!", True),
|
|
216
|
+
# ("No, thanks.", False),
|
|
217
|
+
# ],
|
|
218
|
+
# )
|
|
219
|
+
# ]
|
|
220
|
+
# enable_analytics_answer = inquirer.prompt(enable_analytics_question)
|
|
221
|
+
# setup_info["enable_analytics"] = enable_analytics_answer["enable_analytics"]
|
|
222
|
+
|
|
223
|
+
ph("cli-analytics-choice", {"enable_analytics": setup_info["enable_analytics"]})
|
|
224
|
+
|
|
182
225
|
|
|
183
226
|
def detect_test_framework(curdir, tests_root) -> Optional[str]:
|
|
184
227
|
test_framework = None
|
|
185
228
|
pytest_files = ["pytest.ini", "pyproject.toml", "tox.ini", "setup.cfg"]
|
|
186
229
|
pytest_config_patterns = {
|
|
187
|
-
"pytest.ini":
|
|
188
|
-
"pyproject.toml":
|
|
189
|
-
"tox.ini":
|
|
190
|
-
"setup.cfg":
|
|
230
|
+
"pytest.ini": "[pytest]",
|
|
231
|
+
"pyproject.toml": "[tool.pytest.ini_options]",
|
|
232
|
+
"tox.ini": "[pytest]",
|
|
233
|
+
"setup.cfg": "[tool:pytest]",
|
|
191
234
|
}
|
|
192
235
|
for pytest_file in pytest_files:
|
|
193
236
|
file_path = os.path.join(curdir, pytest_file)
|
|
194
237
|
if os.path.exists(file_path):
|
|
195
|
-
with open(file_path, "r") as file:
|
|
238
|
+
with open(file_path, "r", encoding="utf8") as file:
|
|
196
239
|
contents = file.read()
|
|
197
|
-
if
|
|
240
|
+
if pytest_config_patterns[pytest_file] in contents:
|
|
198
241
|
test_framework = "pytest"
|
|
199
242
|
break
|
|
200
243
|
test_framework = "pytest"
|
|
@@ -202,9 +245,12 @@ def detect_test_framework(curdir, tests_root) -> Optional[str]:
|
|
|
202
245
|
# Check if any python files contain a class that inherits from unittest.TestCase
|
|
203
246
|
for filename in os.listdir(tests_root):
|
|
204
247
|
if filename.endswith(".py"):
|
|
205
|
-
with open(os.path.join(tests_root, filename), "r") as file:
|
|
248
|
+
with open(os.path.join(tests_root, filename), "r", encoding="utf8") as file:
|
|
206
249
|
contents = file.read()
|
|
207
|
-
|
|
250
|
+
try:
|
|
251
|
+
node = ast.parse(contents)
|
|
252
|
+
except SyntaxError:
|
|
253
|
+
continue
|
|
208
254
|
if any(
|
|
209
255
|
isinstance(item, ast.ClassDef)
|
|
210
256
|
and any(
|
|
@@ -230,7 +276,7 @@ def check_for_toml_or_setup_file() -> Optional[str]:
|
|
|
230
276
|
project_name = None
|
|
231
277
|
if os.path.exists(pyproject_toml_path):
|
|
232
278
|
try:
|
|
233
|
-
with open(pyproject_toml_path, "r") as f:
|
|
279
|
+
with open(pyproject_toml_path, "r", encoding="utf8") as f:
|
|
234
280
|
pyproject_toml_content = f.read()
|
|
235
281
|
project_name = tomlkit.parse(pyproject_toml_content)["tool"]["poetry"]["name"]
|
|
236
282
|
click.echo(f"✅ I found a pyproject.toml for your project {project_name}.")
|
|
@@ -239,7 +285,7 @@ def check_for_toml_or_setup_file() -> Optional[str]:
|
|
|
239
285
|
click.echo(f"✅ I found a pyproject.toml for your project.")
|
|
240
286
|
ph("cli-pyproject-toml-found")
|
|
241
287
|
elif os.path.exists(setup_py_path):
|
|
242
|
-
with open(setup_py_path, "r") as f:
|
|
288
|
+
with open(setup_py_path, "r", encoding="utf8") as f:
|
|
243
289
|
setup_py_content = f.read()
|
|
244
290
|
project_name_match = re.search(
|
|
245
291
|
r"setup\s*\([^)]*?name\s*=\s*['\"](.*?)['\"]", setup_py_content, re.DOTALL
|
|
@@ -251,38 +297,54 @@ def check_for_toml_or_setup_file() -> Optional[str]:
|
|
|
251
297
|
else:
|
|
252
298
|
click.echo(f"✅ Found setup.py.")
|
|
253
299
|
ph("cli-setup-py-found")
|
|
254
|
-
# Create a pyproject.toml file because it doesn't exist
|
|
255
|
-
create_toml = (
|
|
256
|
-
click.prompt(
|
|
257
|
-
f"I need your project to have a pyproject.toml file to store CodeFlash configuration settings.\n"
|
|
258
|
-
f"Do you want to run `poetry init` to create one?",
|
|
259
|
-
default="y",
|
|
260
|
-
type=click.STRING,
|
|
261
|
-
)
|
|
262
|
-
.lower()
|
|
263
|
-
.strip()
|
|
264
|
-
)
|
|
265
|
-
if create_toml.startswith("y"):
|
|
266
|
-
# Check if Poetry is installed, if not, install it using pip
|
|
267
|
-
poetry_check = subprocess.run(["poetry", "--version"], capture_output=True, text=True)
|
|
268
|
-
if poetry_check.returncode != 0:
|
|
269
|
-
click.echo("Poetry is not installed. Installing Poetry...")
|
|
270
|
-
subprocess.run(["pip", "install", "poetry"], check=True)
|
|
271
|
-
subprocess.run(["poetry", "init"], cwd=curdir)
|
|
272
|
-
click.echo(f"✅ Created a pyproject.toml file at {pyproject_toml_path}")
|
|
273
|
-
ph("cli-created-pyproject-toml")
|
|
274
300
|
else:
|
|
275
301
|
click.echo(
|
|
276
|
-
f"
|
|
277
|
-
"
|
|
278
|
-
"
|
|
302
|
+
f"💡 I couldn't find a pyproject.toml in the current directory ({curdir}).\n"
|
|
303
|
+
"(make sure you're running `codeflash init` from your project's root directory!)\n"
|
|
304
|
+
f"I need this file to store my configuration settings."
|
|
279
305
|
)
|
|
280
306
|
ph("cli-no-pyproject-toml-or-setup-py")
|
|
281
|
-
|
|
307
|
+
|
|
308
|
+
# Create a pyproject.toml file because it doesn't exist
|
|
309
|
+
create_toml = inquirer.confirm(
|
|
310
|
+
f"Do you want me to create a pyproject.toml file in the current directory?",
|
|
311
|
+
default=True,
|
|
312
|
+
show_default=False,
|
|
313
|
+
)
|
|
314
|
+
if create_toml:
|
|
315
|
+
ph("cli-create-pyproject-toml")
|
|
316
|
+
# Define a minimal pyproject.toml content
|
|
317
|
+
new_pyproject_toml = tomlkit.document()
|
|
318
|
+
new_pyproject_toml["tool"] = {"codeflash": {}}
|
|
319
|
+
try:
|
|
320
|
+
with open(pyproject_toml_path, "w", encoding="utf8") as pyproject_file:
|
|
321
|
+
pyproject_file.write(tomlkit.dumps(new_pyproject_toml))
|
|
322
|
+
|
|
323
|
+
# Check if the pyproject.toml file was created
|
|
324
|
+
if os.path.exists(pyproject_toml_path):
|
|
325
|
+
click.echo(f"✅ Created a pyproject.toml file at {pyproject_toml_path}")
|
|
326
|
+
click.pause()
|
|
327
|
+
ph("cli-created-pyproject-toml")
|
|
328
|
+
except IOError as e:
|
|
329
|
+
click.echo(
|
|
330
|
+
"❌ Failed to create pyproject.toml. Please check your disk permissions and available space."
|
|
331
|
+
)
|
|
332
|
+
apologize_and_exit()
|
|
333
|
+
else:
|
|
334
|
+
click.echo("⏩️ Skipping pyproject.toml creation.")
|
|
335
|
+
apologize_and_exit()
|
|
282
336
|
click.echo()
|
|
283
337
|
return project_name
|
|
284
338
|
|
|
285
339
|
|
|
340
|
+
def apologize_and_exit():
|
|
341
|
+
click.echo(
|
|
342
|
+
"💡 If you're having trouble, see https://app.codeflash.ai/app/getting-started for further help getting started with CodeFlash!"
|
|
343
|
+
)
|
|
344
|
+
click.echo("Exiting...")
|
|
345
|
+
sys.exit(1)
|
|
346
|
+
|
|
347
|
+
|
|
286
348
|
# Ask if the user wants CodeFlash to optimize new GitHub PRs
|
|
287
349
|
def prompt_github_action(setup_info: dict[str, str]):
|
|
288
350
|
optimize_prs_answer = inquirer.prompt(
|
|
@@ -329,7 +391,7 @@ def prompt_github_action(setup_info: dict[str, str]):
|
|
|
329
391
|
optimize_yml_content = optimize_yml_content.replace(
|
|
330
392
|
" {{ python_version }}", python_version_string
|
|
331
393
|
)
|
|
332
|
-
with open(optimize_yaml_path, "w") as optimize_yml_file:
|
|
394
|
+
with open(optimize_yaml_path, "w", encoding="utf8") as optimize_yml_file:
|
|
333
395
|
optimize_yml_file.write(optimize_yml_content)
|
|
334
396
|
click.echo(f"✅ Created {optimize_yaml_path}\n")
|
|
335
397
|
click.prompt(
|
|
@@ -376,24 +438,29 @@ def prompt_github_action(setup_info: dict[str, str]):
|
|
|
376
438
|
def configure_pyproject_toml(setup_info: dict[str, str]):
|
|
377
439
|
toml_path = os.path.join(os.getcwd(), "pyproject.toml")
|
|
378
440
|
try:
|
|
379
|
-
with open(toml_path, "r") as pyproject_file:
|
|
441
|
+
with open(toml_path, "r", encoding="utf8") as pyproject_file:
|
|
380
442
|
pyproject_data = tomlkit.parse(pyproject_file.read())
|
|
381
443
|
except FileNotFoundError:
|
|
382
444
|
click.echo(
|
|
383
|
-
f"
|
|
445
|
+
f"I couln't find a pyproject.toml in the current directory.\n"
|
|
384
446
|
f"Please create it by running `poetry init`, or run `codeflash init` again from a different project directory."
|
|
385
447
|
)
|
|
448
|
+
apologize_and_exit()
|
|
386
449
|
|
|
387
450
|
codeflash_section = tomlkit.table()
|
|
388
451
|
codeflash_section["module-root"] = setup_info["module_root"]
|
|
389
452
|
codeflash_section["tests-root"] = setup_info["tests_root"]
|
|
390
453
|
codeflash_section["test-framework"] = setup_info["test_framework"]
|
|
391
454
|
codeflash_section["ignore-paths"] = setup_info["ignore_paths"]
|
|
455
|
+
codeflash_section["enable-analytics"] = setup_info["enable_analytics"]
|
|
456
|
+
|
|
457
|
+
# Add the 'codeflash' section, ensuring 'tool' section exists
|
|
458
|
+
tool_section = pyproject_data.get("tool", tomlkit.table())
|
|
459
|
+
tool_section["codeflash"] = codeflash_section
|
|
460
|
+
pyproject_data["tool"] = tool_section
|
|
392
461
|
|
|
393
|
-
# Add the 'codeflash' section
|
|
394
|
-
pyproject_data["tool"]["codeflash"] = codeflash_section
|
|
395
462
|
click.echo(f"Writing CodeFlash configuration ...\r", nl=False)
|
|
396
|
-
with open(toml_path, "w") as pyproject_file:
|
|
463
|
+
with open(toml_path, "w", encoding="utf8") as pyproject_file:
|
|
397
464
|
pyproject_file.write(tomlkit.dumps(pyproject_data))
|
|
398
465
|
click.echo(f"✅ Added CodeFlash configuration to {toml_path}")
|
|
399
466
|
click.echo()
|
|
@@ -407,7 +474,11 @@ class CFAPIKeyType(click.ParamType):
|
|
|
407
474
|
if value.startswith("cf-") or value == "":
|
|
408
475
|
return value
|
|
409
476
|
else:
|
|
410
|
-
self.fail(
|
|
477
|
+
self.fail(
|
|
478
|
+
f"That key [{value}] seems to be invalid. It should start with a 'cf-' prefix. Please try again.",
|
|
479
|
+
param,
|
|
480
|
+
ctx,
|
|
481
|
+
)
|
|
411
482
|
|
|
412
483
|
|
|
413
484
|
# Returns True if the user entered a new API key, False if they used an existing one
|
|
@@ -418,34 +489,31 @@ def prompt_api_key() -> bool:
|
|
|
418
489
|
existing_api_key = None
|
|
419
490
|
if existing_api_key:
|
|
420
491
|
display_key = f"{existing_api_key[:3]}****{existing_api_key[-4:]}"
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
492
|
+
click.echo(f"🔑 I found a CODEFLASH_API_KEY in your environment [{display_key}]!")
|
|
493
|
+
|
|
494
|
+
use_existing_key = inquirer.confirm(
|
|
495
|
+
message="Do you want to use this key?",
|
|
496
|
+
default=True,
|
|
426
497
|
show_default=False,
|
|
427
|
-
)
|
|
428
|
-
if use_existing_key
|
|
498
|
+
)
|
|
499
|
+
if use_existing_key:
|
|
429
500
|
ph("cli-existing-api-key-used")
|
|
430
501
|
return False
|
|
431
|
-
else:
|
|
432
|
-
enter_api_key_and_save_to_rc(existing_api_key=use_existing_key)
|
|
433
|
-
ph("cli-new-api-key-entered")
|
|
434
|
-
return True
|
|
435
|
-
else:
|
|
436
|
-
enter_api_key_and_save_to_rc()
|
|
437
|
-
ph("cli-new-api-key-entered")
|
|
438
|
-
return True
|
|
439
502
|
|
|
503
|
+
enter_api_key_and_save_to_rc()
|
|
504
|
+
ph("cli-new-api-key-entered")
|
|
505
|
+
return True
|
|
440
506
|
|
|
441
|
-
|
|
507
|
+
|
|
508
|
+
def enter_api_key_and_save_to_rc():
|
|
442
509
|
browser_launched = False
|
|
443
|
-
api_key =
|
|
510
|
+
api_key = ""
|
|
444
511
|
while api_key == "":
|
|
445
512
|
api_key = click.prompt(
|
|
446
513
|
f"Enter your CodeFlash API key{' [or press Enter to open your API key page]' if not browser_launched else ''}",
|
|
447
514
|
hide_input=False,
|
|
448
515
|
default="",
|
|
516
|
+
type=CFAPIKeyType(),
|
|
449
517
|
show_default=False,
|
|
450
518
|
).strip()
|
|
451
519
|
if api_key:
|
|
@@ -458,23 +526,36 @@ def enter_api_key_and_save_to_rc(existing_api_key: str = ""):
|
|
|
458
526
|
)
|
|
459
527
|
click.launch("https://app.codeflash.ai/app/apikeys")
|
|
460
528
|
browser_launched = True # This does not work on remote consoles
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
529
|
+
|
|
530
|
+
shell_rc_path = get_shell_rc_path()
|
|
531
|
+
api_key_line = f"export CODEFLASH_API_KEY={api_key}"
|
|
532
|
+
try:
|
|
533
|
+
with open(shell_rc_path, "r+", encoding="utf8") as shell_file:
|
|
534
|
+
shell_contents = shell_file.read()
|
|
535
|
+
existing_api_key = read_api_key_from_shell_config()
|
|
536
|
+
|
|
537
|
+
if existing_api_key:
|
|
538
|
+
# Replace the existing API key line
|
|
539
|
+
updated_shell_contents = re.sub(
|
|
540
|
+
SHELL_RC_EXPORT_PATTERN, api_key_line, shell_contents
|
|
541
|
+
)
|
|
542
|
+
action = "Updated CODEFLASH_API_KEY in"
|
|
543
|
+
else:
|
|
544
|
+
# Append the new API key line
|
|
545
|
+
updated_shell_contents = shell_contents.rstrip() + f"\n{api_key_line}\n"
|
|
546
|
+
action = "Added CODEFLASH_API_KEY to"
|
|
547
|
+
|
|
548
|
+
shell_file.seek(0)
|
|
549
|
+
shell_file.write(updated_shell_contents)
|
|
550
|
+
shell_file.truncate()
|
|
551
|
+
click.echo(f"✅ {action} {shell_rc_path}.")
|
|
552
|
+
except IOError as e:
|
|
553
|
+
click.echo(
|
|
554
|
+
f"💡 I tried adding your CodeFlash API key to {shell_rc_path} - but seems like I don't have permissions to do so.\n"
|
|
555
|
+
f"You'll need to open it yourself and add the following line:\n\n{api_key_line}\n"
|
|
556
|
+
)
|
|
557
|
+
click.pause()
|
|
558
|
+
|
|
478
559
|
os.environ["CODEFLASH_API_KEY"] = api_key
|
|
479
560
|
|
|
480
561
|
|
|
@@ -489,7 +570,7 @@ def create_bubble_sort_file(setup_info: dict[str, str]):
|
|
|
489
570
|
return arr
|
|
490
571
|
"""
|
|
491
572
|
bubble_sort_path = os.path.join(setup_info["module_root"], "bubble_sort.py")
|
|
492
|
-
with open(bubble_sort_path, "w") as bubble_sort_file:
|
|
573
|
+
with open(bubble_sort_path, "w", encoding="utf8") as bubble_sort_file:
|
|
493
574
|
bubble_sort_file.write(bubble_sort_content)
|
|
494
575
|
click.echo(f"✅ Created {bubble_sort_path}")
|
|
495
576
|
|
|
@@ -19,26 +19,31 @@ jobs:
|
|
|
19
19
|
echo "Checking if this PR is created by CodeFlash bot..."
|
|
20
20
|
if [ "${{ github.event.pull_request.user.login }}" == "codeflash-ai[bot]" ]; then
|
|
21
21
|
echo "PR created by CodeFlash bot. Skipping optimization."
|
|
22
|
-
|
|
22
|
+
echo "skip_remaining_steps=yes" >> $GITHUB_OUTPUT
|
|
23
23
|
else
|
|
24
|
+
echo "skip_remaining_steps=no" >> $GITHUB_OUTPUT
|
|
24
25
|
echo "It's not. Proceeding with the optimization."
|
|
25
26
|
fi
|
|
26
27
|
- uses: actions/checkout@v3
|
|
28
|
+
if: steps.bot_check.outputs.skip_remaining_steps == 'no'
|
|
27
29
|
with:
|
|
28
30
|
fetch-depth: 0
|
|
29
31
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
30
32
|
# TODO: Replace this with your project's python installation method
|
|
31
33
|
- name: Set up Python
|
|
34
|
+
if: steps.bot_check.outputs.skip_remaining_steps == 'no'
|
|
32
35
|
uses: actions/setup-python@v4
|
|
33
36
|
with:
|
|
34
37
|
python-version: {{ python_version }}
|
|
35
38
|
# TODO: Replace this with your project's dependency installation method
|
|
36
39
|
- name: Install Project Dependencies
|
|
40
|
+
if: steps.bot_check.outputs.skip_remaining_steps == 'no'
|
|
37
41
|
run: |
|
|
38
42
|
python -m pip install --upgrade pip
|
|
39
43
|
pip install -r requirements.txt # TODO: Replace this with your project setup method
|
|
40
44
|
pip install codeflash
|
|
41
45
|
- name: Run CodeFlash to optimize code
|
|
46
|
+
if: steps.bot_check.outputs.skip_remaining_steps == 'no'
|
|
42
47
|
id: optimize_code
|
|
43
48
|
run: |
|
|
44
49
|
codeflash
|