Skip to content

Commit 64a18c9

Browse files
KRRT7claude
andcommitted
refactor: use helper file for async decorator instrumentation
Replace inline code injection with a helper file approach that writes decorator implementations to a separate codeflash_async_wrapper.py file. This removes the codeflash package import dependency from instrumented source files while keeping line numbers stable (only 1 import + 1 decorator line added, same as before). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9f80ea6 commit 64a18c9

5 files changed

Lines changed: 161 additions & 139 deletions

File tree

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
import time
21
import asyncio
32

43

54
async def retry_with_backoff(func, max_retries=3):
65
if max_retries < 1:
76
raise ValueError("max_retries must be at least 1")
87
last_exception = None
8+
_sleep = asyncio.sleep
99
for attempt in range(max_retries):
1010
try:
1111
return await func()
1212
except Exception as e:
1313
last_exception = e
1414
if attempt < max_retries - 1:
15-
time.sleep(0.0001 * attempt)
15+
delay = 0.0001 * attempt
16+
if delay:
17+
await _sleep(delay)
1618
raise last_exception

codeflash/code_utils/instrument_existing_tests.py

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1700,69 +1700,44 @@ def get_async_inline_code(mode: TestingMode) -> str:
17001700
return get_performance_async_inline_code()
17011701

17021702

1703-
class AsyncInlineCodeInjector(cst.CSTTransformer):
1704-
"""Injects async decorator function definitions inline instead of importing from codeflash."""
1703+
ASYNC_HELPER_FILENAME = "codeflash_async_wrapper.py"
17051704

1706-
def __init__(self, mode: TestingMode = TestingMode.BEHAVIOR) -> None:
1707-
self.mode = mode
1708-
self.has_inline_definition = False
1709-
self.has_old_import = False
1710-
1711-
def _get_decorator_name(self) -> str:
1712-
if self.mode == TestingMode.BEHAVIOR:
1713-
return "codeflash_behavior_async"
1714-
if self.mode == TestingMode.CONCURRENCY:
1715-
return "codeflash_concurrency_async"
1716-
return "codeflash_performance_async"
1717-
1718-
def visit_ImportFrom(self, node: cst.ImportFrom) -> None:
1719-
if (
1720-
isinstance(node.module, cst.Attribute)
1721-
and isinstance(node.module.value, cst.Attribute)
1722-
and isinstance(node.module.value.value, cst.Name)
1723-
and node.module.value.value.value == "codeflash"
1724-
and node.module.value.attr.value == "code_utils"
1725-
and node.module.attr.value == "codeflash_wrap_decorator"
1726-
and not isinstance(node.names, cst.ImportStar)
1727-
):
1728-
decorator_name = self._get_decorator_name()
1729-
for import_alias in node.names:
1730-
if import_alias.name.value == decorator_name:
1731-
self.has_old_import = True
17321705

1733-
def visit_FunctionDef(self, node: cst.FunctionDef) -> None:
1734-
if node.name.value == self._get_decorator_name():
1735-
self.has_inline_definition = True
1706+
def get_decorator_name_for_mode(mode: TestingMode) -> str:
1707+
if mode == TestingMode.BEHAVIOR:
1708+
return "codeflash_behavior_async"
1709+
if mode == TestingMode.CONCURRENCY:
1710+
return "codeflash_concurrency_async"
1711+
return "codeflash_performance_async"
1712+
17361713

1737-
def leave_Module(self, original_node: cst.Module, updated_node: cst.Module) -> cst.Module:
1738-
if self.has_inline_definition or self.has_old_import:
1739-
return updated_node
1740-
inline_code = get_async_inline_code(self.mode)
1741-
inline_stmts = cst.parse_module(inline_code).body
1742-
return updated_node.with_changes(body=[*inline_stmts, *list(updated_node.body)])
1714+
def write_async_helper_file(target_dir: Path, mode: TestingMode) -> Path:
1715+
"""Write the async decorator helper file to the target directory."""
1716+
helper_path = target_dir / ASYNC_HELPER_FILENAME
1717+
if helper_path.exists():
1718+
decorator_name = get_decorator_name_for_mode(mode)
1719+
if f"def {decorator_name}" in helper_path.read_text("utf-8"):
1720+
return helper_path
1721+
helper_path.write_text(get_async_inline_code(mode), "utf-8")
1722+
return helper_path
17431723

17441724

17451725
def add_async_decorator_to_function(
1746-
source_path: Path, function: FunctionToOptimize, mode: TestingMode = TestingMode.BEHAVIOR
1726+
source_path: Path,
1727+
function: FunctionToOptimize,
1728+
mode: TestingMode = TestingMode.BEHAVIOR,
1729+
project_root: Path | None = None,
17471730
) -> bool:
17481731
"""Add async decorator to an async function definition and write back to file.
17491732
1750-
Args:
1751-
----
1752-
source_path: Path to the source file to modify in-place.
1753-
function: The FunctionToOptimize object representing the target async function.
1754-
mode: The testing mode to determine which decorator to apply.
1755-
1756-
Returns:
1757-
-------
1758-
Boolean indicating whether the decorator was successfully added.
1733+
Writes a helper file containing the decorator implementation to project_root (or source directory
1734+
as fallback) and adds a standard import + decorator to the source file.
17591735
17601736
"""
17611737
if not function.is_async:
17621738
return False
17631739

17641740
try:
1765-
# Read source code
17661741
with source_path.open(encoding="utf8") as f:
17671742
source_code = f.read()
17681743

@@ -1772,10 +1747,14 @@ def add_async_decorator_to_function(
17721747
decorator_transformer = AsyncDecoratorAdder(function, mode)
17731748
module = module.visit(decorator_transformer)
17741749

1775-
# Add the import if decorator was added
17761750
if decorator_transformer.added_decorator:
1777-
import_transformer = AsyncInlineCodeInjector(mode)
1778-
module = module.visit(import_transformer)
1751+
# Write the helper file to project_root (on sys.path) or source dir as fallback
1752+
helper_dir = project_root if project_root is not None else source_path.parent
1753+
write_async_helper_file(helper_dir, mode)
1754+
# Add the import via CST so sort_imports can place it correctly
1755+
decorator_name = get_decorator_name_for_mode(mode)
1756+
import_node = cst.parse_statement(f"from codeflash_async_wrapper import {decorator_name}")
1757+
module = module.with_changes(body=[import_node, *list(module.body)])
17791758

17801759
modified_code = sort_imports(code=module.code, float_to_top=True)
17811760
except Exception as e:

codeflash/optimization/function_optimizer.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,7 +2296,10 @@ def establish_original_code_baseline(
22962296
from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function
22972297

22982298
success = add_async_decorator_to_function(
2299-
self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.BEHAVIOR
2299+
self.function_to_optimize.file_path,
2300+
self.function_to_optimize,
2301+
TestingMode.BEHAVIOR,
2302+
project_root=self.project_root,
23002303
)
23012304

23022305
# Instrument codeflash capture
@@ -2361,7 +2364,10 @@ def establish_original_code_baseline(
23612364
from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function
23622365

23632366
add_async_decorator_to_function(
2364-
self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.PERFORMANCE
2367+
self.function_to_optimize.file_path,
2368+
self.function_to_optimize,
2369+
TestingMode.PERFORMANCE,
2370+
project_root=self.project_root,
23652371
)
23662372

23672373
try:
@@ -2535,7 +2541,10 @@ def run_optimized_candidate(
25352541
from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function
25362542

25372543
add_async_decorator_to_function(
2538-
self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.BEHAVIOR
2544+
self.function_to_optimize.file_path,
2545+
self.function_to_optimize,
2546+
TestingMode.BEHAVIOR,
2547+
project_root=self.project_root,
25392548
)
25402549

25412550
try:
@@ -2611,7 +2620,10 @@ def run_optimized_candidate(
26112620
from codeflash.code_utils.instrument_existing_tests import add_async_decorator_to_function
26122621

26132622
add_async_decorator_to_function(
2614-
self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.PERFORMANCE
2623+
self.function_to_optimize.file_path,
2624+
self.function_to_optimize,
2625+
TestingMode.PERFORMANCE,
2626+
project_root=self.project_root,
26152627
)
26162628

26172629
try:
@@ -2974,7 +2986,10 @@ def run_concurrency_benchmark(
29742986
try:
29752987
# Add concurrency decorator to the source function
29762988
add_async_decorator_to_function(
2977-
self.function_to_optimize.file_path, self.function_to_optimize, TestingMode.CONCURRENCY
2989+
self.function_to_optimize.file_path,
2990+
self.function_to_optimize,
2991+
TestingMode.CONCURRENCY,
2992+
project_root=self.project_root,
29782993
)
29792994

29802995
# Run the concurrency benchmark tests

tests/test_async_run_and_parse_tests.py

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import pytest
99

1010
from codeflash.code_utils.instrument_existing_tests import (
11+
ASYNC_HELPER_FILENAME,
1112
add_async_decorator_to_function,
12-
get_async_inline_code,
13+
get_decorator_name_for_mode,
1314
inject_profiling_into_existing_test,
1415
)
1516
from codeflash.discovery.functions_to_optimize import FunctionToOptimize
@@ -56,19 +57,22 @@ async def test_async_sort():
5657
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True)
5758

5859
# For async functions, instrument the source module directly with decorators
59-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.BEHAVIOR)
60+
source_success = add_async_decorator_to_function(
61+
fto_path, func, TestingMode.BEHAVIOR, project_root=project_root_path
62+
)
6063

6164
assert source_success
6265

6366
# Verify the file was modified with exact expected output
6467
instrumented_source = fto_path.read_text("utf-8")
6568
from codeflash.code_utils.formatter import sort_imports
6669

67-
inline_code = get_async_inline_code(TestingMode.BEHAVIOR)
70+
decorator_name = get_decorator_name_for_mode(TestingMode.BEHAVIOR)
6871
decorated_original = original_code.replace(
69-
"async def async_sorter", "@codeflash_behavior_async\nasync def async_sorter"
72+
"async def async_sorter", f"@{decorator_name}\nasync def async_sorter"
7073
)
71-
expected = sort_imports(code=inline_code + decorated_original, float_to_top=True)
74+
code_with_import = f"from codeflash_async_wrapper import {decorator_name}\n{decorated_original}"
75+
expected = sort_imports(code=code_with_import, float_to_top=True)
7276
assert instrumented_source.strip() == expected.strip()
7377

7478
# Add codeflash capture
@@ -147,6 +151,9 @@ async def test_async_sort():
147151
test_path.unlink()
148152
if test_path_perf.exists():
149153
test_path_perf.unlink()
154+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
155+
if helper_path.exists():
156+
helper_path.unlink()
150157

151158

152159
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -187,7 +194,9 @@ async def test_async_class_sort():
187194
is_async=True,
188195
)
189196

190-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.BEHAVIOR)
197+
source_success = add_async_decorator_to_function(
198+
fto_path, func, TestingMode.BEHAVIOR, project_root=project_root_path
199+
)
191200

192201
assert source_success
193202

@@ -269,6 +278,9 @@ async def test_async_class_sort():
269278
test_path.unlink()
270279
if test_path_perf.exists():
271280
test_path_perf.unlink()
281+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
282+
if helper_path.exists():
283+
helper_path.unlink()
272284

273285

274286
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -299,19 +311,22 @@ async def test_async_perf():
299311
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True)
300312

301313
# Instrument the source module with async performance decorators
302-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.PERFORMANCE)
314+
source_success = add_async_decorator_to_function(
315+
fto_path, func, TestingMode.PERFORMANCE, project_root=project_root_path
316+
)
303317

304318
assert source_success
305319

306320
# Verify the file was modified
307321
instrumented_source = fto_path.read_text("utf-8")
308322
from codeflash.code_utils.formatter import sort_imports
309323

310-
inline_code = get_async_inline_code(TestingMode.PERFORMANCE)
324+
decorator_name = get_decorator_name_for_mode(TestingMode.PERFORMANCE)
311325
decorated_original = original_code.replace(
312-
"async def async_sorter", "@codeflash_performance_async\nasync def async_sorter"
326+
"async def async_sorter", f"@{decorator_name}\nasync def async_sorter"
313327
)
314-
expected = sort_imports(code=inline_code + decorated_original, float_to_top=True)
328+
code_with_import = f"from codeflash_async_wrapper import {decorator_name}\n{decorated_original}"
329+
expected = sort_imports(code=code_with_import, float_to_top=True)
315330
assert instrumented_source.strip() == expected.strip()
316331

317332
instrument_codeflash_capture(func, {}, tests_root)
@@ -368,6 +383,9 @@ async def test_async_perf():
368383
# Clean up test files
369384
if test_path.exists():
370385
test_path.unlink()
386+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
387+
if helper_path.exists():
388+
helper_path.unlink()
371389

372390

373391
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -413,7 +431,9 @@ async def async_error_function(lst):
413431
function_name="async_error_function", parents=[], file_path=Path(fto_path), is_async=True
414432
)
415433

416-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.BEHAVIOR)
434+
source_success = add_async_decorator_to_function(
435+
fto_path, func, TestingMode.BEHAVIOR, project_root=project_root_path
436+
)
417437

418438
assert source_success
419439

@@ -422,11 +442,12 @@ async def async_error_function(lst):
422442

423443
from codeflash.code_utils.formatter import sort_imports
424444

425-
inline_code = get_async_inline_code(TestingMode.BEHAVIOR)
445+
decorator_name = get_decorator_name_for_mode(TestingMode.BEHAVIOR)
426446
decorated_modified = modified_code.replace(
427-
"async def async_error_function", "@codeflash_behavior_async\nasync def async_error_function"
447+
"async def async_error_function", f"@{decorator_name}\nasync def async_error_function"
428448
)
429-
expected = sort_imports(code=inline_code + decorated_modified, float_to_top=True)
449+
code_with_import = f"from codeflash_async_wrapper import {decorator_name}\n{decorated_modified}"
450+
expected = sort_imports(code=code_with_import, float_to_top=True)
430451
assert instrumented_source.strip() == expected.strip()
431452
instrument_codeflash_capture(func, {}, tests_root)
432453

@@ -488,6 +509,9 @@ async def async_error_function(lst):
488509
test_path.unlink()
489510
if test_path_perf.exists():
490511
test_path_perf.unlink()
512+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
513+
if helper_path.exists():
514+
helper_path.unlink()
491515

492516

493517
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -525,7 +549,9 @@ async def test_async_multi():
525549

526550
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True)
527551

528-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.BEHAVIOR)
552+
source_success = add_async_decorator_to_function(
553+
fto_path, func, TestingMode.BEHAVIOR, project_root=project_root_path
554+
)
529555

530556
assert source_success
531557
instrument_codeflash_capture(func, {}, tests_root)
@@ -598,6 +624,9 @@ async def test_async_multi():
598624
test_path.unlink()
599625
if test_path_perf.exists():
600626
test_path_perf.unlink()
627+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
628+
if helper_path.exists():
629+
helper_path.unlink()
601630

602631

603632
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -640,7 +669,9 @@ async def test_async_edge_cases():
640669

641670
func = FunctionToOptimize(function_name="async_sorter", parents=[], file_path=Path(fto_path), is_async=True)
642671

643-
source_success = add_async_decorator_to_function(fto_path, func, TestingMode.BEHAVIOR)
672+
source_success = add_async_decorator_to_function(
673+
fto_path, func, TestingMode.BEHAVIOR, project_root=project_root_path
674+
)
644675

645676
assert source_success
646677
instrument_codeflash_capture(func, {}, tests_root)
@@ -715,6 +746,9 @@ async def test_async_edge_cases():
715746
test_path.unlink()
716747
if test_path_perf.exists():
717748
test_path_perf.unlink()
749+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
750+
if helper_path.exists():
751+
helper_path.unlink()
718752

719753

720754
@pytest.mark.skipif(sys.platform == "win32", reason="pending support for asyncio on windows")
@@ -949,7 +983,9 @@ async def test_mixed_sorting():
949983
function_name="async_merge_sort", parents=[], file_path=Path(mixed_fto_path), is_async=True
950984
)
951985

952-
source_success = add_async_decorator_to_function(mixed_fto_path, async_func, TestingMode.BEHAVIOR)
986+
source_success = add_async_decorator_to_function(
987+
mixed_fto_path, async_func, TestingMode.BEHAVIOR, project_root=project_root_path
988+
)
953989

954990
assert source_success
955991

@@ -1022,3 +1058,6 @@ async def test_mixed_sorting():
10221058
test_path.unlink()
10231059
if test_path_perf.exists():
10241060
test_path_perf.unlink()
1061+
helper_path = project_root_path / ASYNC_HELPER_FILENAME
1062+
if helper_path.exists():
1063+
helper_path.unlink()

0 commit comments

Comments
 (0)