Skip to content

Commit 35f7738

Browse files
codeflash errors and codes
1 parent 674e69e commit 35f7738

File tree

9 files changed

+178
-46
lines changed

9 files changed

+178
-46
lines changed

codeflash/cli_cmds/cmd_init.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ def enter_api_key_and_save_to_rc() -> None:
10991099
if is_successful(result):
11001100
click.echo(result.unwrap())
11011101
else:
1102-
click.echo(result.failure())
1102+
click.echo(result.failure().message)
11031103
click.pause()
11041104

11051105
os.environ["CODEFLASH_API_KEY"] = api_key

codeflash/code_utils/shell_utils.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from codeflash.code_utils.compat import LF
99
from codeflash.either import Failure, Success
10+
from codeflash.errors.errors import shell_rc_not_found_error, shell_rc_permission_error
1011

1112
if TYPE_CHECKING:
1213
from codeflash.either import Result
@@ -69,13 +70,6 @@ def save_api_key_to_rc(api_key: str) -> Result[str, str]:
6970
shell_file.truncate()
7071
return Success(f"✅ {action} {shell_rc_path}")
7172
except PermissionError:
72-
return Failure(
73-
f"💡 I tried adding your Codeflash API key to {shell_rc_path} - but seems like I don't have permissions to do so.{LF}"
74-
f"You'll need to open it yourself and add the following line:{LF}{LF}{api_key_line}{LF}"
75-
)
73+
return Failure(shell_rc_permission_error(shell_rc_path, api_key_line))
7674
except FileNotFoundError:
77-
return Failure(
78-
f"💡 I went to save your Codeflash API key to {shell_rc_path}, but noticed that it doesn't exist.{LF}"
79-
f"To ensure your Codeflash API key is automatically loaded into your environment at startup, you can create {shell_rc_path} and add the following line:{LF}"
80-
f"{LF}{api_key_line}{LF}"
81-
)
75+
return Failure(shell_rc_not_found_error(shell_rc_path, api_key_line))

codeflash/either.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22

33
from typing import Generic, TypeVar
44

5+
from codeflash.cli_cmds.console import logger
6+
57
L = TypeVar("L")
68
R = TypeVar("R")
79

810

11+
class CodeflashError:
12+
def __init__(self, code: str, message_template: str, **formatting_args: str) -> None:
13+
self.code = code
14+
self.message_template = message_template
15+
self.formatting_args = formatting_args
16+
17+
@property
18+
def message(self) -> str:
19+
try:
20+
formatted = self.message_template.format(**self.formatting_args)
21+
return f"[{self.code}] {formatted}" # noqa: TRY300
22+
except KeyError:
23+
logger.debug(f"Invalid template: missing {self.formatting_args}")
24+
return self.message_template
25+
26+
def __str__(self) -> str:
27+
return self.message
28+
29+
930
class Result(Generic[L, R]):
1031
def __init__(self, value: L | R) -> None:
1132
self.value = value
@@ -22,15 +43,19 @@ def unwrap(self) -> L | R:
2243
raise ValueError(msg)
2344
return self.value
2445

25-
def failure(self) -> L | R:
46+
def failure(self) -> CodeflashError:
2647
if self.is_successful():
2748
msg = "Cannot get failure value from a success"
2849
raise ValueError(msg)
29-
return self.value
50+
if isinstance(self, Failure):
51+
return self.error_code
52+
raise ValueError("Result is not a failure")
3053

3154

3255
class Failure(Result[L, R]):
33-
pass
56+
def __init__(self, error_code: CodeflashError) -> None:
57+
super().__init__(error_code.message)
58+
self.error_code = error_code
3459

3560

3661
class Success(Result[L, R]):

codeflash/errors/__init__.py

Whitespace-only changes.

codeflash/errors/errors.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from __future__ import annotations
2+
3+
from codeflash.code_utils.compat import LF
4+
from codeflash.either import CodeflashError
5+
6+
7+
def shell_rc_permission_error(shell_rc_path: str, api_key_line: str) -> CodeflashError:
8+
return CodeflashError(
9+
"SHELL_RC_PERMISSION_ERROR",
10+
f"I tried adding your Codeflash API key to {{shell_rc_path}} - but seems like I don't have permissions to do so.{LF}"
11+
f"You'll need to open it yourself and add the following line:{LF}{LF}{{api_key_line}}{LF}",
12+
**locals(),
13+
)
14+
15+
16+
def shell_rc_not_found_error(shell_rc_path: str, api_key_line: str) -> CodeflashError:
17+
return CodeflashError(
18+
"SHELL_RC_NOT_FOUND_ERROR",
19+
f"💡 I went to save your Codeflash API key to {{shell_rc_path}}, but noticed that it doesn't exist.{LF}"
20+
f"To ensure your Codeflash API key is automatically loaded into your environment at startup, you can create {{shell_rc_path}} and add the following line:{LF}"
21+
f"{LF}{{api_key_line}}{LF}",
22+
**locals(),
23+
)
24+
25+
26+
def test_result_didnt_match_error() -> CodeflashError:
27+
return CodeflashError(
28+
"TEST_RESULT_DIDNT_MATCH_ERROR", "Test results did not match the test results of the original code."
29+
)
30+
31+
32+
def function_optimization_attempted_error() -> CodeflashError:
33+
return CodeflashError(
34+
"FUNCTION_OPTIMIZATION_ATTEMPTED_ERROR", "Function optimization previously attempted, skipping."
35+
)
36+
37+
38+
def baseline_establishment_failed_error() -> CodeflashError:
39+
return CodeflashError(
40+
"BASELINE_ESTABLISHMENT_FAILED_ERROR", "Failed to establish a baseline for the original code."
41+
)
42+
43+
44+
def no_tests_generated_error(function_name: str) -> CodeflashError:
45+
return CodeflashError("NO_TESTS_GENERATED_ERROR", "NO TESTS GENERATED for {function_name}", **locals())
46+
47+
48+
def no_optimizations_generated_error(function_name: str) -> CodeflashError:
49+
return CodeflashError(
50+
"NO_OPTIMIZATIONS_GENERATED_ERROR", "NO OPTIMIZATIONS GENERATED for {function_name}", **locals()
51+
)
52+
53+
54+
def no_best_optimization_found_error(function_name: str) -> CodeflashError:
55+
return CodeflashError(
56+
"NO_BEST_OPTIMIZATION_FOUND_ERROR", "No best optimizations found for function {function_name}", **locals()
57+
)
58+
59+
60+
def code_context_extraction_failed_error(error: str) -> CodeflashError:
61+
return CodeflashError(
62+
"CODE_CONTEXT_EXTRACTION_FAILED_ERROR", "Failed to extract code context. Error: {error}.", **locals()
63+
)
64+
65+
66+
def coverage_threshold_not_met_error() -> CodeflashError:
67+
return CodeflashError("COVERAGE_THRESHOLD_NOT_MET_ERROR", "The threshold for test coverage was not met.")
68+
69+
70+
def test_confidence_threshold_not_met_error() -> CodeflashError:
71+
return CodeflashError("TEST_CONFIDENCE_THRESHOLD_NOT_MET_ERROR", "The threshold for test confidence was not met.")
72+
73+
74+
def behavioral_test_failure_error() -> CodeflashError:
75+
return CodeflashError(
76+
"BEHAVIORAL_TEST_FAILURE_ERROR",
77+
"Failed to establish a baseline for the original code - bevhavioral tests failed.",
78+
)

codeflash/lsp/beta.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def provide_api_key(server: CodeflashLanguageServer, params: ProvideApiKeyParams
201201

202202
result = save_api_key_to_rc(api_key)
203203
if not is_successful(result):
204-
return {"status": "error", "message": result.failure()}
204+
return {"status": "error", "message": result.failure().message}
205205

206206
# clear cache to ensure the new api key is used
207207
get_codeflash_api_key.cache_clear()
@@ -251,15 +251,23 @@ def perform_function_optimization( # noqa: PLR0911
251251

252252
initialization_result = function_optimizer.can_be_optimized()
253253
if not is_successful(initialization_result):
254-
return {"functionName": params.functionName, "status": "error", "message": initialization_result.failure()}
254+
return {
255+
"functionName": params.functionName,
256+
"status": "error",
257+
"message": initialization_result.failure().message,
258+
}
255259

256260
should_run_experiment, code_context, original_helper_code = initialization_result.unwrap()
257261

258262
test_setup_result = function_optimizer.generate_and_instrument_tests(
259263
code_context, should_run_experiment=should_run_experiment
260264
)
261265
if not is_successful(test_setup_result):
262-
return {"functionName": params.functionName, "status": "error", "message": test_setup_result.failure()}
266+
return {
267+
"functionName": params.functionName,
268+
"status": "error",
269+
"message": test_setup_result.failure().message,
270+
}
263271
(
264272
generated_tests,
265273
function_to_concolic_tests,
@@ -282,7 +290,11 @@ def perform_function_optimization( # noqa: PLR0911
282290
)
283291

284292
if not is_successful(baseline_setup_result):
285-
return {"functionName": params.functionName, "status": "error", "message": baseline_setup_result.failure()}
293+
return {
294+
"functionName": params.functionName,
295+
"status": "error",
296+
"message": baseline_setup_result.failure().message,
297+
}
286298

287299
(
288300
function_to_optimize_qualified_name,

codeflash/optimization/function_optimizer.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,19 @@
6060
from codeflash.context import code_context_extractor
6161
from codeflash.context.unused_definition_remover import detect_unused_helper_functions, revert_unused_helper_functions
6262
from codeflash.discovery.functions_to_optimize import was_function_previously_optimized
63-
from codeflash.either import Failure, Success, is_successful
63+
from codeflash.either import CodeflashError, Failure, Success, is_successful
64+
from codeflash.errors.errors import (
65+
baseline_establishment_failed_error,
66+
behavioral_test_failure_error,
67+
code_context_extraction_failed_error,
68+
coverage_threshold_not_met_error,
69+
function_optimization_attempted_error,
70+
no_best_optimization_found_error,
71+
no_optimizations_generated_error,
72+
no_tests_generated_error,
73+
test_confidence_threshold_not_met_error,
74+
test_result_didnt_match_error,
75+
)
6476
from codeflash.models.ExperimentMetadata import ExperimentMetadata
6577
from codeflash.models.models import (
6678
BestOptimization,
@@ -252,14 +264,18 @@ def can_be_optimized(self) -> Result[tuple[bool, CodeOptimizationContext, dict[P
252264
has_any_async_functions(code_string.code) for code_string in code_context.read_writable_code.code_strings
253265
)
254266
if async_code:
255-
return Failure("Codeflash does not support async functions in the code to optimize.")
267+
return Failure(
268+
CodeflashError(
269+
"ASYNC_CODE_ERROR", "Codeflash does not support async functions in the code to optimize."
270+
)
271+
)
256272
# Random here means that we still attempt optimization with a fractional chance to see if
257273
# last time we could not find an optimization, maybe this time we do.
258274
# Random is before as a performance optimization, swapping the two 'and' statements has the same effect
259275
if random.random() > REPEAT_OPTIMIZATION_PROBABILITY and was_function_previously_optimized( # noqa: S311
260276
self.function_to_optimize, code_context, self.args
261277
):
262-
return Failure("Function optimization previously attempted, skipping.")
278+
return Failure(function_optimization_attempted_error())
263279

264280
return Success((should_run_experiment, code_context, original_helper_code))
265281

@@ -430,7 +446,7 @@ def optimize_function(self) -> Result[BestOptimization, str]:
430446
if self.args.override_fixtures:
431447
restore_conftest(original_conftest_content)
432448
if not best_optimization:
433-
return Failure(f"No best optimizations found for function {self.function_to_optimize.qualified_name}")
449+
return Failure(no_best_optimization_found_error(self.function_to_optimize.qualified_name))
434450
return Success(best_optimization)
435451

436452
def determine_best_candidate(
@@ -852,7 +868,7 @@ def get_code_optimization_context(self) -> Result[CodeOptimizationContext, str]:
852868
self.function_to_optimize, self.project_root
853869
)
854870
except ValueError as e:
855-
return Failure(str(e))
871+
return Failure(code_context_extraction_failed_error(str(e)))
856872

857873
return Success(
858874
CodeOptimizationContext(
@@ -1012,7 +1028,7 @@ def generate_tests_and_optimizations(
10121028
# Retrieve results
10131029
candidates: list[OptimizedCandidate] = future_optimization_candidates.result()
10141030
if not candidates:
1015-
return Failure(f"/!\\ NO OPTIMIZATIONS GENERATED for {self.function_to_optimize.function_name}")
1031+
return Failure(no_optimizations_generated_error(self.function_to_optimize.function_name))
10161032

10171033
candidates_experiment = future_candidates_exp.result() if future_candidates_exp else None
10181034

@@ -1040,7 +1056,7 @@ def generate_tests_and_optimizations(
10401056
)
10411057
if not tests:
10421058
logger.warning(f"Failed to generate and instrument tests for {self.function_to_optimize.function_name}")
1043-
return Failure(f"/!\\ NO TESTS GENERATED for {self.function_to_optimize.function_name}")
1059+
return Failure(no_tests_generated_error(self.function_to_optimize.function_name))
10441060
function_to_concolic_tests, concolic_test_str = future_concolic_tests.result()
10451061
logger.info(f"Generated {len(tests)} tests for {self.function_to_optimize.function_name}")
10461062
console.rule()
@@ -1100,14 +1116,21 @@ def setup_and_establish_baseline(
11001116
return Failure(baseline_result.failure())
11011117

11021118
original_code_baseline, test_functions_to_remove = baseline_result.unwrap()
1103-
if isinstance(original_code_baseline, OriginalCodeBaseline) and (
1104-
not coverage_critic(original_code_baseline.coverage_results, self.args.test_framework)
1105-
or not quantity_of_tests_critic(original_code_baseline)
1106-
):
1107-
if self.args.override_fixtures:
1108-
restore_conftest(original_conftest_content)
1109-
cleanup_paths(paths_to_cleanup)
1110-
return Failure("The threshold for test confidence was not met.")
1119+
if isinstance(original_code_baseline, OriginalCodeBaseline):
1120+
error = None
1121+
sufficent_coverage = coverage_critic(original_code_baseline.coverage_results, self.args.test_framework)
1122+
sufficent_tests = quantity_of_tests_critic(original_code_baseline)
1123+
1124+
if not sufficent_coverage:
1125+
error = coverage_threshold_not_met_error()
1126+
elif not sufficent_tests:
1127+
error = test_confidence_threshold_not_met_error()
1128+
1129+
if error:
1130+
if self.args.override_fixtures:
1131+
restore_conftest(original_conftest_content)
1132+
cleanup_paths(paths_to_cleanup)
1133+
return Failure(error)
11111134

11121135
return Success(
11131136
(
@@ -1394,9 +1417,9 @@ def establish_original_code_baseline(
13941417
f"Couldn't run any tests for original function {self.function_to_optimize.function_name}. SKIPPING OPTIMIZING THIS FUNCTION."
13951418
)
13961419
console.rule()
1397-
return Failure("Failed to establish a baseline for the original code - bevhavioral tests failed.")
1420+
return Failure(behavioral_test_failure_error())
13981421
if not coverage_critic(coverage_results, self.args.test_framework):
1399-
return Failure("The threshold for test coverage was not met.")
1422+
return Failure(coverage_threshold_not_met_error())
14001423
if test_framework == "pytest":
14011424
line_profile_results = self.line_profiler_step(
14021425
code_context=code_context, original_helper_code=original_helper_code, candidate_index=0
@@ -1456,7 +1479,7 @@ def establish_original_code_baseline(
14561479
console.rule()
14571480
success = False
14581481
if not success:
1459-
return Failure("Failed to establish a baseline for the original code.")
1482+
return Failure(baseline_establishment_failed_error())
14601483

14611484
loop_count = max([int(result.loop_index) for result in benchmarking_results.test_results])
14621485
logger.info(
@@ -1540,7 +1563,7 @@ def run_optimized_candidate(
15401563
else:
15411564
logger.info("Test results did not match the test results of the original code.")
15421565
console.rule()
1543-
return Failure("Test results did not match the test results of the original code.")
1566+
return Failure(test_result_didnt_match_error())
15441567

15451568
if test_framework == "pytest":
15461569
candidate_benchmarking_results, _ = self.run_and_parse_tests(

codeflash/optimization/optimizer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ def run(self) -> None:
359359
f"Optimizing {functions_to_optimize[i + 1].qualified_name}",
360360
)
361361
else:
362-
logger.warning(best_optimization.failure())
362+
logger.warning(best_optimization.failure().message)
363363
console.rule()
364364
continue
365365
finally:

0 commit comments

Comments
 (0)