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.
Files changed (53) hide show
  1. {codeflash-0.3.4 → codeflash-0.3.6}/PKG-INFO +3 -1
  2. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/analytics/posthog.py +20 -5
  3. codeflash-0.3.6/codeflash/analytics/sentry.py +14 -0
  4. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/api/cfapi.py +10 -8
  5. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/cli.py +13 -8
  6. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/cmd_init.py +166 -85
  7. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/workflows/codeflash-optimize.yaml +6 -1
  8. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_extractor.py +24 -7
  9. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_replacer.py +86 -40
  10. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/code_utils.py +20 -12
  11. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/config_consts.py +2 -0
  12. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/config_parser.py +13 -1
  13. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/env_utils.py +33 -3
  14. codeflash-0.3.6/codeflash/code_utils/formatter.py +26 -0
  15. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/git_utils.py +15 -16
  16. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/instrument_existing_tests.py +296 -140
  17. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/discovery/discover_unit_tests.py +114 -73
  18. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/discovery/functions_to_optimize.py +27 -8
  19. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/github/PrComment.py +1 -2
  20. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/main.py +335 -174
  21. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/optimization/function_context.py +60 -35
  22. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/result/create_pr.py +21 -8
  23. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/tracing/tracer.py +1 -1
  24. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/update_license_version.py +2 -2
  25. codeflash-0.3.6/codeflash/verification/__init__.py +0 -0
  26. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/comparator.py +1 -1
  27. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/parse_test_output.py +57 -53
  28. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/test_runner.py +1 -1
  29. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/verifier.py +13 -4
  30. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/version.py +2 -2
  31. {codeflash-0.3.4 → codeflash-0.3.6}/pyproject.toml +18 -10
  32. codeflash-0.3.4/codeflash/code_utils/linter.py +0 -18
  33. {codeflash-0.3.4 → codeflash-0.3.6}/README.md +0 -0
  34. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/LICENSE +0 -0
  35. {codeflash-0.3.4/codeflash/analytics → codeflash-0.3.6/codeflash}/__init__.py +0 -0
  36. {codeflash-0.3.4/codeflash/api → codeflash-0.3.6/codeflash/analytics}/__init__.py +0 -0
  37. {codeflash-0.3.4/codeflash/cli_cmds → codeflash-0.3.6/codeflash/api}/__init__.py +0 -0
  38. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/api/aiservice.py +0 -0
  39. {codeflash-0.3.4/codeflash/code_utils → codeflash-0.3.6/codeflash/cli_cmds}/__init__.py +0 -0
  40. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/cli_cmds/logging_config.py +0 -0
  41. {codeflash-0.3.4/codeflash/discovery → codeflash-0.3.6/codeflash/code_utils}/__init__.py +0 -0
  42. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/sqlalchemy_utils.py +0 -0
  43. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/code_utils/time_utils.py +0 -0
  44. {codeflash-0.3.4/codeflash/github → codeflash-0.3.6/codeflash/discovery}/__init__.py +0 -0
  45. {codeflash-0.3.4/codeflash/optimization → codeflash-0.3.6/codeflash/github}/__init__.py +0 -0
  46. {codeflash-0.3.4/codeflash/result → codeflash-0.3.6/codeflash/optimization}/__init__.py +0 -0
  47. {codeflash-0.3.4/codeflash/tracing → codeflash-0.3.6/codeflash/result}/__init__.py +0 -0
  48. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/result/explanation.py +0 -0
  49. {codeflash-0.3.4/codeflash/verification → codeflash-0.3.6/codeflash/tracing}/__init__.py +0 -0
  50. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/tracing/replay_test.py +0 -0
  51. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/equivalence.py +0 -0
  52. {codeflash-0.3.4 → codeflash-0.3.6}/codeflash/verification/test_results.py +0 -0
  53. {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.4
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
- if properties is None:
22
- properties = {}
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, dict[str, 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, dict[str, 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
- response = make_cfapi_request(
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
- pyproject_config = parse_config_file(args.config_file)
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
- repo = git.Repo(search_parent_directories=True)
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
- "Could not find a git repository in the current directory. "
90
- "We need a git repository to run --all and open PRs for optimizations. Exiting..."
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(repo)
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 get_codeflash_api_key
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["run_tests"]
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=project_name
124
- if project_name in module_subdir_options
125
- else module_subdir_options[0],
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=default_tests_subdir
145
- if default_tests_subdir in test_subdir_options
146
- else test_subdir_options[0],
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": r"\[pytest\]",
188
- "pyproject.toml": r"\[tool\.pytest\.ini_options\]",
189
- "tox.ini": r"\[pytest\]",
190
- "setup.cfg": r"\[tool:pytest\]",
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 re.search(pytest_config_patterns[pytest_file], contents):
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
- node = ast.parse(contents)
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" I couldn't find a pyproject.toml or a setup.py in the current directory ({curdir}).\n"
277
- "Please make sure you're running codeflash init from your project's root directory.\n"
278
- "See https://app.codeflash.ai/app/getting-started for more details!"
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
- sys.exit(1)
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"Could not find a pyproject.toml in the current directory.\n"
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(f"{value} does not start with the prefix 'cf-'. Please retry.", param, ctx)
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
- use_existing_key = click.prompt(
422
- f"I found a CODEFLASH_API_KEY in your environment [{display_key}]!\n"
423
- f"Press Enter to use this key, or type any other key to change it",
424
- default="",
425
- type=CFAPIKeyType(),
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
- ).strip()
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
- def enter_api_key_and_save_to_rc(existing_api_key: str = ""):
507
+
508
+ def enter_api_key_and_save_to_rc():
442
509
  browser_launched = False
443
- api_key = existing_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
- shell_rc_path = os.path.expanduser(
462
- f"~/.{os.environ.get('SHELL', '/bin/bash').split('/')[-1]}rc"
463
- )
464
- api_key_line = f'export CODEFLASH_API_KEY="{api_key}"'
465
- api_key_pattern = re.compile(r'^export CODEFLASH_API_KEY=".*"$', re.M)
466
- with open(shell_rc_path, "r+") as shell_rc:
467
- shell_contents = shell_rc.read()
468
- if api_key_pattern.search(shell_contents):
469
- # Replace the existing API key line
470
- updated_shell_contents = api_key_pattern.sub(api_key_line, shell_contents)
471
- else:
472
- # Append the new API key line
473
- updated_shell_contents = shell_contents.rstrip() + f"\n{api_key_line}\n"
474
- shell_rc.seek(0)
475
- shell_rc.write(updated_shell_contents)
476
- shell_rc.truncate()
477
- click.echo(f"✅ Updated CODEFLASH_API_KEY in {shell_rc_path}")
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
- exit 0
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