Skip to content

Commit 4a520d9

Browse files
authored
Merge pull request #2145 from codeflash-ai/fix/mypy-strict-errors
fix: resolve mypy strict errors in models.py and code_utils.py
2 parents f7db112 + 4bf0aaf commit 4a520d9

2 files changed

Lines changed: 66 additions & 69 deletions

File tree

codeflash/code_utils/code_utils.py

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
from functools import lru_cache
1313
from pathlib import Path
1414
from tempfile import TemporaryDirectory
15+
from typing import TYPE_CHECKING
16+
17+
if TYPE_CHECKING:
18+
from collections.abc import Generator
1519

1620
import tomlkit
1721

@@ -112,7 +116,7 @@ def normalize_by_max(values: list[float]) -> list[float]:
112116
return [v / mx for v in values]
113117

114118

115-
def create_score_dictionary_from_metrics(weights: list[float], *metrics: list[float]) -> dict[int, int]:
119+
def create_score_dictionary_from_metrics(weights: list[float], *metrics: list[float]) -> dict[int, float]:
116120
"""Combine multiple metrics into a single weighted score dictionary.
117121
118122
Each metric is a list of values (smaller = better).
@@ -208,67 +212,53 @@ def filter_args(addopts_args: list[str]) -> list[str]:
208212
def modify_addopts(config_file: Path) -> tuple[str, bool]:
209213
file_type = config_file.suffix.lower()
210214
filename = config_file.name
211-
config = None
212215
if file_type not in {".toml", ".ini", ".cfg"} or not config_file.exists():
213216
return "", False
214217
# Read original file
215218
with Path.open(config_file, encoding="utf-8") as f:
216219
content = f.read()
217220
try:
218221
if filename == "pyproject.toml":
219-
# use tomlkit
220222
data = tomlkit.parse(content)
221223
original_addopts = data.get("tool", {}).get("pytest", {}).get("ini_options", {}).get("addopts", "")
222-
# nothing to do if no addopts present
223224
if original_addopts == "":
224225
return content, False
225226
if isinstance(original_addopts, list):
226227
original_addopts = " ".join(original_addopts)
227228
original_addopts = original_addopts.replace("=", " ")
228-
addopts_args = (
229-
original_addopts.split()
230-
) # any number of space characters as delimiter, doesn't look at = which is fine
231-
else:
232-
# use configparser
233-
config = configparser.ConfigParser()
234-
config.read_string(content)
235-
data = {section: dict(config[section]) for section in config.sections()}
236-
if config_file.name in {"pytest.ini", ".pytest.ini", "tox.ini"}:
237-
original_addopts = data.get("pytest", {}).get("addopts", "") # should only be a string
238-
else:
239-
original_addopts = data.get("tool:pytest", {}).get("addopts", "") # should only be a string
240-
original_addopts = original_addopts.replace("=", " ")
241229
addopts_args = original_addopts.split()
242-
new_addopts_args = filter_args(addopts_args)
243-
if new_addopts_args == addopts_args:
244-
return content, False
245-
# change addopts now
246-
if file_type == ".toml":
247-
data["tool"]["pytest"]["ini_options"]["addopts"] = " ".join(new_addopts_args)
248-
# Write modified file
230+
new_addopts_args = filter_args(addopts_args)
231+
if new_addopts_args == addopts_args:
232+
return content, False
233+
data["tool"]["pytest"]["ini_options"]["addopts"] = " ".join(new_addopts_args) # type: ignore[index]
249234
with Path.open(config_file, "w", encoding="utf-8") as f:
250235
f.write(tomlkit.dumps(data))
251-
return content, True
252-
elif config_file.name in {"pytest.ini", ".pytest.ini", "tox.ini"}:
253-
config.set("pytest", "addopts", " ".join(new_addopts_args))
254-
# Write modified file
255-
with Path.open(config_file, "w", encoding="utf-8") as f:
256-
config.write(f)
257-
return content, True
236+
return content, True
237+
config = configparser.ConfigParser()
238+
config.read_string(content)
239+
ini_data = {section: dict(config[section]) for section in config.sections()}
240+
if config_file.name in {"pytest.ini", ".pytest.ini", "tox.ini"}:
241+
original_addopts = ini_data.get("pytest", {}).get("addopts", "")
258242
else:
259-
config.set("tool:pytest", "addopts", " ".join(new_addopts_args))
260-
# Write modified file
261-
with Path.open(config_file, "w", encoding="utf-8") as f:
262-
config.write(f)
263-
return content, True
243+
original_addopts = ini_data.get("tool:pytest", {}).get("addopts", "")
244+
original_addopts = original_addopts.replace("=", " ")
245+
addopts_args = original_addopts.split()
246+
new_addopts_args = filter_args(addopts_args)
247+
if new_addopts_args == addopts_args:
248+
return content, False
249+
section = "pytest" if config_file.name in {"pytest.ini", ".pytest.ini", "tox.ini"} else "tool:pytest"
250+
config.set(section, "addopts", " ".join(new_addopts_args))
251+
with Path.open(config_file, "w", encoding="utf-8") as f:
252+
config.write(f)
253+
return content, True
264254

265255
except Exception:
266256
logger.debug("Trouble parsing")
267-
return content, False # not modified
257+
return content, False
268258

269259

270260
@contextmanager
271-
def custom_addopts() -> None:
261+
def custom_addopts() -> Generator[None, None, None]:
272262
closest_config_files = get_all_closest_config_files()
273263

274264
original_content = {}
@@ -287,18 +277,17 @@ def custom_addopts() -> None:
287277

288278

289279
@contextmanager
290-
def add_addopts_to_pyproject() -> None:
280+
def add_addopts_to_pyproject() -> Generator[None, None, None]:
291281
pyproject_file = find_pyproject_toml()
292-
original_content = None
282+
original_content: str | None = None
293283
try:
294-
# Read original file
295284
if pyproject_file.exists():
296285
with Path.open(pyproject_file, encoding="utf-8") as f:
297286
original_content = f.read()
298287
data = tomlkit.parse(original_content)
299-
data["tool"]["pytest"] = {}
300-
data["tool"]["pytest"]["ini_options"] = {}
301-
data["tool"]["pytest"]["ini_options"]["addopts"] = [
288+
data["tool"]["pytest"] = {} # type: ignore[index]
289+
data["tool"]["pytest"]["ini_options"] = {} # type: ignore[index]
290+
data["tool"]["pytest"]["ini_options"]["addopts"] = [ # type: ignore[index]
302291
"-n=auto",
303292
"-n",
304293
"1",
@@ -312,9 +301,9 @@ def add_addopts_to_pyproject() -> None:
312301
yield
313302

314303
finally:
315-
# Restore original file
316-
with Path.open(pyproject_file, "w", encoding="utf-8") as f:
317-
f.write(original_content)
304+
if original_content is not None:
305+
with Path.open(pyproject_file, "w", encoding="utf-8") as f:
306+
f.write(original_content)
318307

319308

320309
def encoded_tokens_len(s: str) -> int:
@@ -418,13 +407,18 @@ def get_all_function_names(code: str) -> tuple[bool, list[str]]:
418407
return True, function_names
419408

420409

410+
_run_tmpdir: TemporaryDirectory[str] | None = None
411+
_run_tmpdir_path: Path | None = None
412+
413+
421414
def get_run_tmp_file(file_path: Path | str) -> Path:
415+
global _run_tmpdir, _run_tmpdir_path
422416
if isinstance(file_path, str):
423417
file_path = Path(file_path)
424-
if not hasattr(get_run_tmp_file, "tmpdir_path"):
425-
get_run_tmp_file.tmpdir = TemporaryDirectory(prefix="codeflash_")
426-
get_run_tmp_file.tmpdir_path = Path(get_run_tmp_file.tmpdir.name).resolve()
427-
return get_run_tmp_file.tmpdir_path / file_path
418+
if _run_tmpdir_path is None:
419+
_run_tmpdir = TemporaryDirectory(prefix="codeflash_")
420+
_run_tmpdir_path = Path(_run_tmpdir.name).resolve()
421+
return _run_tmpdir_path / file_path
428422

429423

430424
def path_belongs_to_site_packages(file_path: Path) -> bool:

codeflash/models/models.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
from functools import lru_cache
1010
from pathlib import Path
1111
from re import Pattern
12-
from typing import TYPE_CHECKING, Any, NamedTuple, Optional, cast
12+
from typing import TYPE_CHECKING, Any, NamedTuple, Optional
1313

1414
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, ValidationError, model_validator
1515
from pydantic.dataclasses import dataclass
1616

1717
from codeflash.models.test_type import TestType
1818

1919
if TYPE_CHECKING:
20-
from collections.abc import Iterator
20+
from collections.abc import Generator
2121

2222
import libcst as cst
2323
from rich.tree import Tree
@@ -298,11 +298,13 @@ def flat(self) -> str:
298298
299299
"""
300300
if self._cache.get("flat") is not None:
301-
return self._cache["flat"]
302-
self._cache["flat"] = "\n".join(
301+
result: str = self._cache["flat"]
302+
return result
303+
flat: str = "\n".join(
303304
get_code_block_splitter(block.file_path) + "\n" + block.code for block in self.code_strings
304305
)
305-
return self._cache["flat"]
306+
self._cache["flat"] = flat
307+
return flat
306308

307309
@property
308310
def markdown(self) -> str:
@@ -332,7 +334,8 @@ def file_to_path(self) -> dict[str, str]:
332334
333335
"""
334336
try:
335-
return self._cache["file_to_path"]
337+
cached: dict[str, str] = self._cache["file_to_path"]
338+
return cached
336339
except KeyError:
337340
mapping = {str(code_string.file_path): code_string.code for code_string in self.code_strings}
338341
self._cache["file_to_path"] = mapping
@@ -494,8 +497,8 @@ def _normalize_path_for_comparison(path: Path) -> str:
494497
# Only lowercase on Windows where filesystem is case-insensitive
495498
return resolved.lower() if sys.platform == "win32" else resolved
496499

497-
def __iter__(self) -> Iterator[TestFile]:
498-
return iter(self.test_files)
500+
def __iter__(self) -> Generator[Any, None, None]: # noqa: PYI058
501+
yield from self.test_files
499502

500503
def __len__(self) -> int:
501504
return len(self.test_files)
@@ -514,9 +517,9 @@ class CandidateEvaluationContext:
514517
optimized_runtimes: dict[str, float | None] = Field(default_factory=dict)
515518
is_correct: dict[str, bool] = Field(default_factory=dict)
516519
optimized_line_profiler_results: dict[str, str] = Field(default_factory=dict)
517-
ast_code_to_id: dict = Field(default_factory=dict)
520+
ast_code_to_id: dict[str, Any] = Field(default_factory=dict)
518521
optimizations_post: dict[str, str] = Field(default_factory=dict)
519-
valid_optimizations: list = Field(default_factory=list)
522+
valid_optimizations: list[Any] = Field(default_factory=list)
520523

521524
def record_failed_candidate(self, optimization_id: str) -> None:
522525
"""Record results for a failed candidate."""
@@ -543,7 +546,7 @@ def handle_duplicate_candidate(
543546
# Copy results from the previous evaluation (use .get() in case past_opt_id was registered
544547
# but never benchmarked due to an unhandled exception in process_single_candidate)
545548
self.speedup_ratios[candidate.optimization_id] = self.speedup_ratios.get(past_opt_id)
546-
self.is_correct[candidate.optimization_id] = self.is_correct.get(past_opt_id)
549+
self.is_correct[candidate.optimization_id] = self.is_correct.get(past_opt_id, False)
547550
self.optimized_runtimes[candidate.optimization_id] = self.optimized_runtimes.get(past_opt_id)
548551

549552
# Line profiler results only available for successful runs
@@ -631,7 +634,7 @@ class OriginalCodeBaseline(BaseModel):
631634
behavior_test_results: TestResults
632635
benchmarking_test_results: TestResults
633636
replay_benchmarking_test_results: Optional[dict[BenchmarkKey, TestResults]] = None
634-
line_profile_results: dict
637+
line_profile_results: dict[str, Any]
635638
runtime: int
636639
coverage_results: Optional[CoverageData]
637640
async_throughput: Optional[int] = None
@@ -793,7 +796,7 @@ def get_src_code(self, test_path: Path) -> Optional[str]:
793796
f"// Testing function: {self.function_getting_tested}"
794797
)
795798

796-
if self.test_class_name:
799+
if self.test_class_name and self.test_function_name:
797800
for stmt in module_node.body:
798801
if isinstance(stmt, cst.ClassDef) and stmt.name.value == self.test_class_name:
799802
func_node = self.find_func_in_class(stmt, self.test_function_name)
@@ -884,7 +887,7 @@ def group_by_benchmarks(
884887
"""Group TestResults by benchmark for calculating improvements for each benchmark."""
885888
from codeflash.code_utils.code_utils import module_name_from_file_path
886889

887-
test_results_by_benchmark = defaultdict(TestResults)
890+
test_results_by_benchmark: defaultdict[BenchmarkKey, TestResults] = defaultdict(TestResults)
888891
benchmark_module_path = {}
889892
for benchmark_key in benchmark_keys:
890893
benchmark_module_path[benchmark_key] = module_name_from_file_path(
@@ -1015,7 +1018,7 @@ def effective_loop_count(self) -> int:
10151018
return max(loop_indices) if loop_indices else 0
10161019

10171020
def file_to_no_of_tests(self, test_functions_to_remove: list[str]) -> Counter[Path]:
1018-
map_gen_test_file_to_no_of_tests = Counter()
1021+
map_gen_test_file_to_no_of_tests: Counter[Path] = Counter()
10191022
for gen_test_result in self.test_results:
10201023
if (
10211024
gen_test_result.test_type == TestType.GENERATED_REGRESSION
@@ -1024,8 +1027,8 @@ def file_to_no_of_tests(self, test_functions_to_remove: list[str]) -> Counter[Pa
10241027
map_gen_test_file_to_no_of_tests[gen_test_result.file_name] += 1
10251028
return map_gen_test_file_to_no_of_tests
10261029

1027-
def __iter__(self) -> Iterator[FunctionTestInvocation]:
1028-
return iter(self.test_results)
1030+
def __iter__(self) -> Generator[Any, None, None]: # noqa: PYI058
1031+
yield from self.test_results
10291032

10301033
def __len__(self) -> int:
10311034
return len(self.test_results)
@@ -1051,7 +1054,7 @@ def __eq__(self, other: object) -> bool:
10511054
if len(self) != len(other):
10521055
return False
10531056
original_recursion_limit = sys.getrecursionlimit()
1054-
cast("TestResults", other)
1057+
assert isinstance(other, TestResults)
10551058
for test_result in self:
10561059
other_test_result = other.get_by_unique_invocation_loop_id(test_result.unique_invocation_loop_id)
10571060
if other_test_result is None:

0 commit comments

Comments
 (0)